[gnome-music] Rewrite the album widgets
- From: Marinus Schraal <mschraal src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-music] Rewrite the album widgets
- Date: Tue, 25 Oct 2016 14:14:41 +0000 (UTC)
commit e7fe2b7cf2e65eb9692a0f1c9e4319aa852dc29a
Author: Marinus Schraal <mschraal src gnome org>
Date: Thu Oct 13 16:29:54 2016 +0200
Rewrite the album widgets
Complete rewrite of the artists-album and album widgets to use only
native GTK+ widgets and share more components, dropping the use of libgd
here in the process. Also implements multi-disc album separation.
https://bugzilla.gnome.org/show_bug.cgi?id=750827
https://bugzilla.gnome.org/show_bug.cgi?id=772128
https://bugzilla.gnome.org/show_bug.cgi?id=761891
data/ArtistAlbumWidget.ui | 84 +++---
data/TrackWidget.ui | 61 +++-
data/application.css | 8 +
gnomemusic/grilo.py | 11 +
gnomemusic/query.py | 1 +
gnomemusic/utils.py | 14 +
gnomemusic/views/albumsview.py | 8 +-
gnomemusic/views/artistsview.py | 3 +-
gnomemusic/views/searchview.py | 9 +-
gnomemusic/widgets/Makefile.am | 1 +
gnomemusic/widgets/albumwidget.py | 285 +++++++++----------
gnomemusic/widgets/artistalbumswidget.py | 59 +++--
gnomemusic/widgets/artistalbumwidget.py | 245 +++++++---------
gnomemusic/widgets/disclistboxwidget.py | 473 ++++++++++++++++++++++++++++++
gnomemusic/window.py | 4 +-
po/POTFILES.in | 1 +
16 files changed, 898 insertions(+), 369 deletions(-)
---
diff --git a/data/ArtistAlbumWidget.ui b/data/ArtistAlbumWidget.ui
index 637d78f..7174764 100644
--- a/data/ArtistAlbumWidget.ui
+++ b/data/ArtistAlbumWidget.ui
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
<interface>
- <!-- interface-requires gtk+ 3.12 -->
+ <requires lib="gtk+" version="3.12"/>
<object class="GtkBox" id="ArtistAlbumWidget">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -16,7 +17,6 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
- <property name="padding">0</property>
<property name="position">0</property>
</packing>
</child>
@@ -33,18 +33,18 @@
<object class="GtkLabel" id="title">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="yalign">0</property>
<property name="margin_start">6</property>
<property name="margin_end">6</property>
<property name="ellipsize">middle</property>
- <style>
- <class name="title"/>
- </style>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
<attributes>
<attribute name="weight" value="bold"/>
<attribute name="foreground" value="#bababdbdb6b6"/>
</attributes>
+ <style>
+ <class name="title"/>
+ </style>
</object>
<packing>
<property name="expand">False</property>
@@ -76,42 +76,14 @@
</packing>
</child>
<child>
- <object class="GtkGrid" id="grid1">
+ <object class="DiscListBox" id="disclistbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="margin_top">24</property>
- <property name="column_homogeneous">True</property>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
+ <property name="selection_mode">none</property>
</object>
<packing>
<property name="expand">False</property>
- <property name="fill">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
@@ -123,6 +95,42 @@
</packing>
</child>
</object>
+ <object class="GtkBox" id="disc">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="margin_top">16</property>
+ <child>
+ <object class="GtkLabel" id="disclabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="margin_start">60</property>
+ <property name="margin_bottom">4</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="DiscSongsFlowBox" id="discsongsflowbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ <property name="orientation">vertical</property>
+ <property name="homogeneous">True</property>
+ <property name="max_children_per_line">0</property>
+ <property name="selection_mode">none</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
<object class="GtkListStore" id="liststore1">
<columns>
<!-- column-name gchararray1 -->
diff --git a/data/TrackWidget.ui b/data/TrackWidget.ui
index 8b69b17..02e8632 100644
--- a/data/TrackWidget.ui
+++ b/data/TrackWidget.ui
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
<interface>
- <!-- interface-requires gtk+ 3.0 -->
+ <requires lib="gtk+" version="3.10"/>
<object class="GtkEventBox" id="eventbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -33,13 +34,12 @@
</packing>
</child>
<child>
- <placeholder/>
- </child>
- <child>
<object class="GtkCheckButton" id="select">
<property name="visible">True</property>
<property name="can_focus">False</property>
+ <property name="receives_default">False</property>
<property name="no_show_all">True</property>
+ <property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
@@ -51,12 +51,13 @@
<object class="GtkLabel" id="num">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="xalign">1</property>
+ <property name="halign">end</property>
<property name="justify">right</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
+ <property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
@@ -77,40 +78,72 @@
<object class="GtkLabel" id="title">
<property name="visible">True</property>
<property name="can_focus">False</property>
+ <property name="halign">start</property>
<property name="valign">start</property>
- <property name="xalign">0</property>
<property name="ellipsize">end</property>
- <property name="max-width-chars">90</property>
+ <property name="max_width_chars">90</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">9</property>
- <property name="position">2</property>
+ <property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="duration">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="valign">start</property>
- <property name="xalign">1</property>
- <property name="yalign">0</property>
+ <property name="no_show_all">True</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <property name="single_line_mode">True</property>
</object>
<packing>
<property name="expand">False</property>
- <property name="fill">False</property>
- <property name="pack_type">end</property>
- <property name="position">3</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEventBox" id="starevent">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="no_show_all">True</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <property name="visible_window">True</property>
+ <child>
+ <object class="StarStack" id="starstack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">center</property>
+ <property name="transition_type">crossfade</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
+ <child>
+ <placeholder/>
+ </child>
</object>
</child>
</object>
diff --git a/data/application.css b/data/application.css
index 8a609ab..198fc38 100644
--- a/data/application.css
+++ b/data/application.css
@@ -11,6 +11,14 @@ flowbox, treeview, widget {
-gtk-key-bindings: unbind-ctrl-space;
}
+row, list {
+ background-color: @theme_bg_color;
+}
+
+.discsongsflowbox > flowboxchild {
+ padding: 0px;
+}
+
.cover{
padding-left:24px;
}
diff --git a/gnomemusic/grilo.py b/gnomemusic/grilo.py
index 74cd4f7..c9e816d 100644
--- a/gnomemusic/grilo.py
+++ b/gnomemusic/grilo.py
@@ -48,6 +48,7 @@ class Grilo(GObject.GObject):
METADATA_KEYS = [
Grl.METADATA_KEY_ALBUM,
Grl.METADATA_KEY_ALBUM_ARTIST,
+ Grl.METADATA_KEY_ALBUM_DISC_NUMBER,
Grl.METADATA_KEY_ARTIST,
Grl.METADATA_KEY_CREATION_DATE,
Grl.METADATA_KEY_DURATION,
@@ -280,6 +281,16 @@ class Grilo(GObject.GObject):
song_item.set_lyrics("i'm truthy")
@log
+ def set_favorite(self, song_item, favorite):
+ """Set the favorite status of a media item
+
+ :param song_item: A Grilo media item
+ :param bool favorite: Set favorite status
+ """
+ if bool(song_item.get_lyrics()) != favorite:
+ self.toggle_favorite(song_item)
+
+ @log
def search(self, q, callback, data=None):
options = self.options.copy()
self._search_callback_counter = 0
diff --git a/gnomemusic/query.py b/gnomemusic/query.py
index b688341..f990c51 100644
--- a/gnomemusic/query.py
+++ b/gnomemusic/query.py
@@ -272,6 +272,7 @@ class Query():
nmm:artistName(nmm:performer(?song)) AS ?artist
nie:title(nmm:musicAlbum(?song)) AS ?album
nfo:duration(?song) AS ?duration
+ nmm:setNumber(nmm:musicAlbumDisc(?song)) AS ?album_disc_number
IF(bound(?tag), 'truth!', '') AS ?lyrics
WHERE {
?song a nmm:MusicPiece ;
diff --git a/gnomemusic/utils.py b/gnomemusic/utils.py
index 997a926..4a15310 100644
--- a/gnomemusic/utils.py
+++ b/gnomemusic/utils.py
@@ -73,3 +73,17 @@ def get_media_title(item):
return (item.get_title()
or _("Untitled"))
+
+
+def seconds_to_string(duration):
+ """Convert a time in seconds to a hh:mm string
+
+ :param int duration: Time in seconds
+ :return: Time in hh:mm format
+ :rtype: string
+ """
+ seconds = duration
+ minutes = seconds // 60
+ seconds %= 60
+
+ return '{:d}:{:02d}'.format(minutes, seconds)
diff --git a/gnomemusic/views/albumsview.py b/gnomemusic/views/albumsview.py
index 76e8f85..e196f4c 100644
--- a/gnomemusic/views/albumsview.py
+++ b/gnomemusic/views/albumsview.py
@@ -129,12 +129,10 @@ class AlbumsView(BaseView):
@log
def get_selected_tracks(self, callback):
+ # FIXME: we call into private objects with full knowledge of
+ # what is there
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)
+ callback(self._albumWidget._disc_listbox.get_selected_items())
else:
self.items_selected = []
self.items_selected_callback = callback
diff --git a/gnomemusic/views/artistsview.py b/gnomemusic/views/artistsview.py
index 9d9a51e..eba4717 100644
--- a/gnomemusic/views/artistsview.py
+++ b/gnomemusic/views/artistsview.py
@@ -116,7 +116,8 @@ class ArtistsView(BaseView):
widget = self._artists[artist.casefold()]['widget']
if widget:
- if widget.model == self.player.running_playlist('Artist', widget.artist):
+ # FIXME: internal call
+ 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)
diff --git a/gnomemusic/views/searchview.py b/gnomemusic/views/searchview.py
index a3c393f..b9fb4cc 100644
--- a/gnomemusic/views/searchview.py
+++ b/gnomemusic/views/searchview.py
@@ -306,14 +306,11 @@ class SearchView(BaseView):
@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)
+ callback(self._albumWidget.view.get_selected_items())
elif self.get_visible_child() == self._artistAlbumsWidget:
items = []
- for row in self._artistAlbumsWidget.model:
+ # FIXME: calling into private model
+ for row in self._artistAlbumsWidget._model:
if row[6]:
items.append(row[5])
callback(items)
diff --git a/gnomemusic/widgets/Makefile.am b/gnomemusic/widgets/Makefile.am
index 8f4d03c..e02db58 100644
--- a/gnomemusic/widgets/Makefile.am
+++ b/gnomemusic/widgets/Makefile.am
@@ -5,5 +5,6 @@ app_PYTHON = \
albumwidget.py \
artistalbumswidget.py \
artistalbumwidget.py \
+ disclistboxwidget.py \
playlistdialog.py \
starhandlerwidget.py
diff --git a/gnomemusic/widgets/albumwidget.py b/gnomemusic/widgets/albumwidget.py
index ab08d24..3cb3934 100644
--- a/gnomemusic/widgets/albumwidget.py
+++ b/gnomemusic/widgets/albumwidget.py
@@ -29,7 +29,8 @@ from gnomemusic import log
from gnomemusic.albumartcache import AlbumArtCache, DefaultIcon, ArtSize
from gnomemusic.grilo import grilo
from gnomemusic.player import DiscoveryStatus
-from gnomemusic.widgets.starhandlerwidget import StarHandlerWidget
+from gnomemusic.playlists import Playlists
+from gnomemusic.widgets.disclistboxwidget import DiscBox, DiscListBox
import gnomemusic.utils as utils
NOW_PLAYING_ICON_NAME = 'media-playback-start-symbolic'
@@ -57,6 +58,8 @@ class AlbumWidget(Gtk.EventBox):
"""
Gtk.EventBox.__init__(self)
+ self._tracks = []
+
scale = self.get_scale_factor()
self._cache = AlbumArtCache(scale)
self._loading_icon_surface = DefaultIcon(scale).get(
@@ -66,35 +69,33 @@ class AlbumWidget(Gtk.EventBox):
self._player = player
self._iter_to_clean = None
- self._ui = Gtk.Builder()
- self._ui.add_from_resource('/org/gnome/Music/AlbumWidget.ui')
+ self._selection_mode = False
+
+ self._builder = Gtk.Builder()
+ self._builder.add_from_resource('/org/gnome/Music/AlbumWidget.ui')
self._create_model()
- self.view = Gd.MainView(
- shadow_type=Gtk.ShadowType.NONE
- )
- self.view.set_view_type(Gd.MainViewType.LIST)
self._album = None
self._header_bar = None
- self.view.connect('item-activated', self._on_item_activated)
-
- view_box = self._ui.get_object('view')
- self._ui.get_object('scrolledWindow').set_placement(Gtk.CornerType.
- TOP_LEFT)
- self.view.connect('selection-mode-request',
- self._on_selection_mode_request)
- child_view = self.view.get_children()[0]
- child_view.set_margin_top(64)
- child_view.set_margin_bottom(64)
- child_view.set_margin_end(32)
- self.view.remove(child_view)
- view_box.add(child_view)
-
- self.add(self._ui.get_object('AlbumWidget'))
- self._star_handler = StarHandlerWidget(self, 9)
- self._add_list_renderers()
+ self._selection_mode_allowed = True
+
+ view_box = self._builder.get_object('view')
+ self._disc_listbox = DiscListBox()
+ self._disc_listbox.set_selection_mode_allowed(True)
+ self._disc_listbox.set_margin_top(64)
+ self._disc_listbox.set_margin_bottom(64)
+ self._disc_listbox.set_margin_end(32)
+ self._disc_listbox.connect('selection-changed',
+ self._on_selection_changed)
+ view_box.add(self._disc_listbox)
+
+ # FIXME: Assigned to appease searchview
+ # get_selected_tracks
+ self.view = self._disc_listbox
+
+ self.add(self._builder.get_object('AlbumWidget'))
self.get_style_context().add_class('view')
self.get_style_context().add_class('content-view')
- self.view.get_generic_view().get_style_context().remove_class('view')
+
self.show_all()
@log
@@ -103,91 +104,9 @@ class AlbumWidget(Gtk.EventBox):
self._header_bar._select_button.clicked()
@log
- def _on_item_activated(self, widget, id, path):
- """List row activated."""
- if self._star_handler.star_renderer_click:
- self._star_handler.star_renderer_click = False
- return
-
- _iter = self.model.get_iter(path)
-
- if self.model[_iter][10] != DiscoveryStatus.FAILED:
- if (self._iter_to_clean
- and self._player.playlistId == self._album):
- item = self.model[self._iter_to_clean][5]
- title = utils.get_media_title(item)
- self.model[self._iter_to_clean][0] = title
- # Hide now playing icon
- self.model[self._iter_to_clean][6] = False
- self._player.set_playlist('Album', self._album, self.model, _iter,
- 5, 11)
- self._player.set_playing(True)
-
- @log
- def _add_list_renderers(self):
- """Create the ListView columns."""
- list_widget = self.view.get_generic_view()
-
- cols = list_widget.get_columns()
- cols[0].set_min_width(100)
- cols[0].set_max_width(200)
- cells = cols[0].get_cells()
- cells[2].set_visible(False)
- cells[1].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)
-
- type_renderer = Gd.StyledTextRenderer(
- xpad=16,
- ellipsize=Pango.EllipsizeMode.END,
- xalign=0.0
- )
-
- list_widget.add_renderer(type_renderer, lambda *args: None, None)
- cols[0].clear_attributes(type_renderer)
- cols[0].add_attribute(type_renderer, 'markup', 0)
-
- duration_renderer = Gd.StyledTextRenderer(
- xpad=16,
- ellipsize=Pango.EllipsizeMode.END,
- xalign=1.0
- )
-
- duration_renderer.add_class('dim-label')
- list_widget.add_renderer(duration_renderer, lambda *args: None, None)
- cols[0].clear_attributes(duration_renderer)
- cols[0].add_attribute(duration_renderer, 'markup', 1)
-
- self._star_handler.add_star_renderers(list_widget, cols)
-
- def _on_list_widget_icon_render(self, col, cell, model, _iter, data):
- if not self._player.currentTrackUri:
- cell.set_visible(False)
- return
-
- if model[_iter][10] == DiscoveryStatus.FAILED:
- cell.set_property('icon-name', ERROR_ICON_NAME)
- cell.set_visible(True)
- elif model[_iter][5].get_url() == self._player.currentTrackUri:
- cell.set_property('icon-name', NOW_PLAYING_ICON_NAME)
- cell.set_visible(True)
- else:
- cell.set_visible(False)
-
- @log
def _create_model(self):
"""Create the ListStore model for this widget."""
- self.model = Gtk.ListStore(
+ self._model = Gtk.ListStore(
GObject.TYPE_STRING, # title
GObject.TYPE_STRING,
GObject.TYPE_STRING,
@@ -212,36 +131,41 @@ class AlbumWidget(Gtk.EventBox):
:param header_bar: The header bar object
:param selection_toolbar: The selection toolbar object
"""
+ # reset view
+ self._tracks = []
+ self._create_model()
+ for widget in self._disc_listbox.get_children():
+ self._disc_listbox.remove(widget)
+
self.selection_toolbar = selection_toolbar
self._header_bar = header_bar
self._album = album
- self._ui.get_object('cover').set_from_surface(
+ self._builder.get_object('cover').set_from_surface(
self._loading_icon_surface)
self._cache.lookup(item, ArtSize.large, self._on_lookup, None)
self._duration = 0
- self._create_model()
+
GLib.idle_add(grilo.populate_album_songs, item, self.add_item)
header_bar._select_button.connect(
'toggled', self._on_header_select_button_toggled)
header_bar._cancel_button.connect(
'clicked', self._on_header_cancel_button_clicked)
- self.view.connect('view-selection-changed',
- self._on_view_selection_changed)
- self.view.set_model(self.model)
+
+ # FIXME: use utils
escaped_artist = GLib.markup_escape_text(artist)
escaped_album = GLib.markup_escape_text(album)
- self._ui.get_object('artist_label').set_markup(escaped_artist)
- self._ui.get_object('title_label').set_markup(escaped_album)
+ self._builder.get_object('artist_label').set_markup(escaped_artist)
+ self._builder.get_object('title_label').set_markup(escaped_album)
if (item.get_creation_date()):
- self._ui.get_object('released_label_info').set_text(
+ self._builder.get_object('released_label_info').set_text(
str(item.get_creation_date().get_year()))
else:
- self._ui.get_object('released_label_info').set_text('----')
+ self._builder.get_object('released_label_info').set_text('----')
self._player.connect('playlist-item-changed', self._update_model)
@log
- def _on_view_selection_changed(self, widget):
- items = self.view.get_selection()
+ def _on_selection_changed(self, widget):
+ items = self._disc_listbox.get_selected_items()
self.selection_toolbar._add_to_playlist_button.set_sensitive(
len(items) > 0)
if len(items) > 0:
@@ -255,7 +179,7 @@ class AlbumWidget(Gtk.EventBox):
@log
def _on_header_cancel_button_clicked(self, button):
"""Cancel selection mode callback."""
- self.view.set_selection_mode(False)
+ self._disc_listbox.set_selection_mode(False)
self._header_bar.set_selection_mode(False)
self._header_bar.header_bar.title = self._album
@@ -263,22 +187,56 @@ class AlbumWidget(Gtk.EventBox):
def _on_header_select_button_toggled(self, button):
"""Selection mode button clicked callback."""
if button.get_active():
- self.view.set_selection_mode(True)
+ self._selection_mode = True
+ self._disc_listbox.set_selection_mode(True)
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._header_bar.header_bar.set_custom_title(
self._header_bar._selection_menu_button)
else:
- self.view.set_selection_mode(False)
+ self._selection_mode = False
+ self._disc_listbox.set_selection_mode(False)
self._header_bar.set_selection_mode(False)
- self._header_bar.title = self._album
- self.selection_toolbar.actionbar.set_visible(False)
if(self._player.get_playback_status() != 2):
self._player.actionbar.set_visible(True)
@log
+ def _create_disc_box(self, disc_nr, disc_tracks):
+ disc_box = DiscBox(self._model)
+ disc_box.set_tracks(disc_tracks)
+ disc_box.set_disc_number(disc_nr)
+ disc_box.set_columns(1)
+ disc_box.show_song_numbers(False)
+ disc_box.connect('track-activated', self._track_activated)
+ disc_box.connect('selection-toggle', self._selection_mode_toggled)
+
+ return disc_box
+
+ @log
+ def _selection_mode_toggled(self, widget):
+ if not self._selection_mode_allowed:
+ return
+
+ self._selection_mode = not self._selection_mode
+ self._on_selection_mode_request()
+
+
+ @log
+ def _track_activated(self, widget, song_widget):
+ if not song_widget.can_be_played:
+ return
+
+ if self._selection_mode:
+ song_widget.check_button.toggled()
+ return
+
+ self._player.stop()
+ self._player.set_playlist('Artist', 'test', song_widget.model,
+ song_widget.itr, 5, 11)
+ self._player.set_playing(True)
+ return True
+
+ @log
def add_item(self, source, prefs, track, remaining, data=None):
"""Add a song to the item to album list.
@@ -289,22 +247,30 @@ class AlbumWidget(Gtk.EventBox):
:param data: User data
"""
if track:
+ self._tracks.append(track)
+
self._duration = self._duration + track.get_duration()
- _iter = self.model.append()
- title = utils.get_media_title(track)
- escaped_title = GLib.markup_escape_text(title)
- self.model[_iter][0, 1, 2, 3, 4, 5, 9] = [
- escaped_title,
- self._player.seconds_to_string(track.get_duration()),
- '',
- '',
- None,
- track,
- bool(track.get_lyrics())
- ]
- self._ui.get_object('running_length_label_info').set_text(
+ return
+
+ discs = {}
+ for track in self._tracks:
+ disc_nr = track.get_album_disc_number()
+ if disc_nr not in discs.keys():
+ discs[disc_nr] = [track]
+ else:
+ discs[disc_nr].append(track)
+ for disc_nr in discs:
+ disc = self._create_disc_box(disc_nr, discs[disc_nr])
+ self._disc_listbox.insert(disc, -1)
+ if len(discs) == 1:
+ disc.show_disc_label(False)
+
+ if remaining == 0:
+ self._builder.get_object('running_length_label_info').set_text(
_("%d min") % (int(self._duration / 60) + 1))
+ self.show_all()
+
@log
def _on_lookup(self, surface, data=None):
"""Albumart retrieved callback.
@@ -313,7 +279,7 @@ class AlbumWidget(Gtk.EventBox):
:param path: The filesystem location the pixbuf
:param data: User data
"""
- self._ui.get_object('cover').set_from_surface(surface)
+ self._builder.get_object('cover').set_from_surface(surface)
@log
def _update_model(self, player, playlist, current_iter):
@@ -323,29 +289,46 @@ class AlbumWidget(Gtk.EventBox):
:param playlist: The current playlist
:param current_iter: The current iter of the playlist model
"""
- # self is not our playlist, return
- if (playlist != self.model):
- return False
+ if (playlist != self._model):
+ return True
current_song = playlist[current_iter][5]
+
+ self._duration = 0
+
song_passed = False
_iter = playlist.get_iter_first()
- self._duration = 0
while _iter:
song = playlist[_iter][5]
+ song_widget = song.song_widget
self._duration += song.get_duration()
escaped_title = GLib.markup_escape_text(utils.get_media_title(song))
+
if (song == current_song):
- title = '<b>%s</b>' % escaped_title
+ song_widget.now_playing_sign.show()
+ song_widget.title.set_markup("<b>{}</b>".format(escaped_title))
song_passed = True
elif (song_passed):
- title = '<span>%s</span>' % escaped_title
+ song_widget.now_playing_sign.hide()
+ song_widget.title.set_markup(
+ "<span>{}</span>".format(escaped_title))
else:
- title = '<span color=\'grey\'>%s</span>' % escaped_title
- playlist[_iter][0] = title
+ song_widget.now_playing_sign.hide()
+ song_widget.title.set_markup(
+ "<span color=\'grey\'>{}</span>".format(escaped_title))
+
_iter = playlist.iter_next(_iter)
- self._ui.get_object('running_length_label_info').set_text(
- _("%d min") % (int(self._duration / 60) + 1))
- return False
+ self._builder.get_object('running_length_label_info').set_text(
+ _("%d min") % (int(self._duration / 60) + 1))
+
+ return True
+
+ @log
+ def select_all(self):
+ self._disc_listbox.select_all()
+
+ @log
+ def select_none(self):
+ self._disc_listbox.select_none()
diff --git a/gnomemusic/widgets/artistalbumswidget.py b/gnomemusic/widgets/artistalbumswidget.py
index 99a9f00..00763d5 100644
--- a/gnomemusic/widgets/artistalbumswidget.py
+++ b/gnomemusic/widgets/artistalbumswidget.py
@@ -57,15 +57,8 @@ class ArtistAlbumsWidget(Gtk.Box):
self.ui.get_object('artist').set_label(self.artist)
self.widgets = []
- self.model = Gtk.ListStore(GObject.TYPE_STRING, # title
- GObject.TYPE_STRING,
- GObject.TYPE_STRING,
- GObject.TYPE_BOOLEAN, # icon shown
- GObject.TYPE_STRING, # icon
- GObject.TYPE_OBJECT, # song object
- GObject.TYPE_BOOLEAN,
- GObject.TYPE_INT
- )
+ self._create_model()
+
self.row_changed_source_id = None
self._hbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
@@ -94,6 +87,24 @@ class ArtistAlbumsWidget(Gtk.Box):
self.player.connect('playlist-item-changed', self.update_model)
+ @log
+ def _create_model(self):
+ """Create the ListStore model for this widget."""
+ self._model = Gtk.ListStore(
+ GObject.TYPE_STRING, # title
+ GObject.TYPE_STRING,
+ GObject.TYPE_STRING,
+ GObject.TYPE_STRING,
+ GObject.TYPE_STRING, # placeholder
+ GObject.TYPE_OBJECT, # song object
+ GObject.TYPE_BOOLEAN, # item selected
+ GObject.TYPE_STRING,
+ GObject.TYPE_BOOLEAN,
+ GObject.TYPE_INT, # icon shown
+ GObject.TYPE_BOOLEAN,
+ GObject.TYPE_INT
+ )
+
def _on_last_album_displayed(self, data=None):
self.window.notification.dismiss()
self.show_all()
@@ -102,11 +113,12 @@ class ArtistAlbumsWidget(Gtk.Box):
def add_album(self, album, is_last_album=False):
self.window.notification.set_timeout(0)
widget = ArtistAlbumWidget(
- self.artist, album, self.player, self.model,
- self.header_bar, self.selectionModeAllowed
+ album, self.player, self._model,
+ self.header_bar, self.selectionModeAllowed,
+ self._songsGridSizeGroup, self.header_bar
)
self._coverSizeGroup.add_widget(widget.cover)
- self._songsGridSizeGroup.add_widget(widget.songsGrid)
+
self._albumBox.pack_start(widget, False, False, 0)
self.widgets.append(widget)
@@ -116,7 +128,7 @@ class ArtistAlbumsWidget(Gtk.Box):
@log
def update_model(self, player, playlist, currentIter):
# this is not our playlist, return
- if playlist != self.model:
+ if playlist != self._model:
# TODO, only clean once, but that can wait util we have clean
# the code a bit, and until the playlist refactoring.
# the overhead is acceptable for now
@@ -153,15 +165,15 @@ class ArtistAlbumsWidget(Gtk.Box):
@log
def clean_model(self):
- itr = self.model.get_iter_first()
+ itr = self._model.get_iter_first()
while itr:
- song = self.model.get_value(itr, 5)
+ song = self._model.get_value(itr, 5)
song_widget = song.song_widget
escaped_title = GLib.markup_escape_text(utils.get_media_title(song))
if song_widget.can_be_played:
song_widget.now_playing_sign.hide()
song_widget.title.set_markup('<span>%s</span>' % escaped_title)
- itr = self.model.iter_next(itr)
+ itr = self._model.iter_next(itr)
return False
@log
@@ -171,8 +183,10 @@ class ArtistAlbumsWidget(Gtk.Box):
self.selectionMode = selectionMode
try:
if self.row_changed_source_id:
- self.model.disconnect(self.row_changed_source_id)
- self.row_changed_source_id = self.model.connect('row-changed', self._model_row_changed)
+ self._model.disconnect(self.row_changed_source_id)
+ self.row_changed_source_id = self._model.connect(
+ 'row-changed',
+ self._model_row_changed)
except Exception as e:
logger.warning("Exception while tracking row-changed: %s", e)
@@ -195,3 +209,12 @@ class ArtistAlbumsWidget(Gtk.Box):
else:
self.header_bar._selection_menu_label.set_text(_("Click on items to select them"))
+ @log
+ def select_all(self):
+ for widget in self.widgets:
+ widget._disc_listbox.select_all()
+
+ @log
+ def select_none(self):
+ for widget in self.widgets:
+ widget._disc_listbox.select_none()
diff --git a/gnomemusic/widgets/artistalbumwidget.py b/gnomemusic/widgets/artistalbumwidget.py
index 066ea73..f8af03f 100644
--- a/gnomemusic/widgets/artistalbumwidget.py
+++ b/gnomemusic/widgets/artistalbumwidget.py
@@ -22,32 +22,15 @@
# code, but you are not obligated to do so. If you do not wish to do so,
# delete this exception statement from your version.
-import logging
-
from gettext import gettext as _
from gi.repository import Gdk, Gio, GLib, GObject, Gtk
from gnomemusic import log
from gnomemusic.albumartcache import AlbumArtCache, DefaultIcon, ArtSize
from gnomemusic.grilo import grilo
-from gnomemusic.player import DiscoveryStatus
-from gnomemusic.playlists import Playlists
+from gnomemusic.widgets.disclistboxwidget import DiscBox, DiscListBox
import gnomemusic.utils as utils
-logger = logging.getLogger(__name__)
-
-NOW_PLAYING_ICON_NAME = 'media-playback-start-symbolic'
-ERROR_ICON_NAME = 'dialog-error-symbolic'
-
-try:
- settings = Gio.Settings.new('org.gnome.Music')
- MAX_TITLE_WIDTH = settings.get_int('max-width-chars')
-except Exception as e:
- MAX_TITLE_WIDTH = 20
- logger.error("Error on setting widget max-width-chars: %s", str(e))
-
-playlists = Playlists.get_default()
-
class ArtistAlbumWidget(Gtk.Box):
@@ -59,95 +42,124 @@ class ArtistAlbumWidget(Gtk.Box):
return '<ArtistAlbumWidget>'
@log
- def __init__(self, artist, album, player, model, header_bar, selectionModeAllowed):
- Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
+ def __init__(self, media, player, model, header_bar,
+ selection_mode_allowed, size_group=None,
+ selection_toolbar = None):
+ super().__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
+ self._size_group = size_group
scale = self.get_scale_factor()
self._cache = AlbumArtCache(scale)
self._loading_icon_surface = DefaultIcon(scale).get(
DefaultIcon.Type.loading,
- ArtSize.large)
-
- self.player = player
- self.album = album
- self.artist = artist
- self.model = model
- self.model.connect('row-changed', self._model_row_changed)
- self.header_bar = header_bar
- self.selectionMode = False
- self.selectionModeAllowed = selectionModeAllowed
+ ArtSize.medium)
+
+ self._media = media
+ self._player = player
+ self._artist = utils.get_artist_name(self._media)
+ self._album_title = utils.get_album_title(self._media)
+ self._model = model
+ self._header_bar = header_bar
+ self._selection_mode = False
+ self._selection_mode_allowed = selection_mode_allowed
+ self._selection_toolbar = selection_toolbar
self.songs = []
+ self._tracks = []
+
+ self._header_bar._select_button.connect(
+ 'toggled', self._on_header_select_button_toggled)
+
self.ui = Gtk.Builder()
self.ui.add_from_resource('/org/gnome/Music/ArtistAlbumWidget.ui')
- GLib.idle_add(self._update_album_art)
-
self.cover = self.ui.get_object('cover')
self.cover.set_from_surface(self._loading_icon_surface)
- self.songsGrid = self.ui.get_object('grid1')
- self.ui.get_object('title').set_label(album.get_title())
- if album.get_creation_date():
+ self._disc_listbox = self.ui.get_object('disclistbox')
+ self._disc_listbox.set_selection_mode_allowed(
+ self._selection_mode_allowed)
+
+ self.ui.get_object('title').set_label(self._album_title)
+ creation_date = self._media.get_creation_date()
+ if creation_date:
+ year = creation_date.get_year()
self.ui.get_object('year').set_markup(
- '<span color=\'grey\'>(%s)</span>' %
- str(album.get_creation_date().get_year())
- )
- self.tracks = []
- grilo.populate_album_songs(album, self.add_item)
+ '<span color=\'grey\'>{}</span>'.format(year))
+
+ if self._size_group:
+ self._size_group.add_widget(self.ui.get_object('box1'))
+
self.pack_start(self.ui.get_object('ArtistAlbumWidget'), True, True, 0)
+ GLib.idle_add(self._update_album_art)
+ grilo.populate_album_songs(self._media, self._add_item)
+
+
+ def create_disc_box(self, disc_nr, disc_tracks):
+ disc_box = DiscBox(self._model)
+ disc_box.set_tracks(disc_tracks)
+ disc_box.set_disc_number(disc_nr)
+ disc_box.set_columns(2)
+ disc_box.show_duration(False)
+ disc_box.show_favorites(False)
+ disc_box.connect('track-activated', self._track_activated)
+ disc_box.connect('selection-toggle', self._selection_mode_toggled)
+
+ return disc_box
+
+ def _selection_mode_toggled(self, widget):
+ if not self._selection_mode_allowed:
+ return
+
+ self._selection_mode = not self._selection_mode
+ self._on_selection_mode_request()
+
+ return True
+
+ def _on_selection_mode_request(self):
+ self._header_bar._select_button.clicked()
+
+ def _on_header_select_button_toggled(self, button):
+ """Selection mode button clicked callback."""
+ if button.get_active():
+ self._selection_mode = True
+ self._disc_listbox.set_selection_mode(True)
+ self._header_bar.set_selection_mode(True)
+ self._player.actionbar.set_visible(False)
+ self._header_bar.header_bar.set_custom_title(
+ self._header_bar._selection_menu_button)
+ else:
+ self._selection_mode = False
+ self._disc_listbox.set_selection_mode(False)
+ self._header_bar.set_selection_mode(False)
+ if(self._player.get_playback_status() != 2):
+ self._player.actionbar.set_visible(True)
+
@log
- def add_item(self, source, prefs, track, remaining, data=None):
+ def _add_item(self, source, prefs, track, remaining, data=None):
+ if track:
+ self._tracks.append(track)
+ return
+
+ discs = {}
+ for track in self._tracks:
+ disc_nr = track.get_album_disc_number()
+ if disc_nr not in discs.keys():
+ discs[disc_nr] = [track]
+ else:
+ discs[disc_nr].append(track)
+
+ for disc_nr in discs:
+ disc = self.create_disc_box(disc_nr, discs[disc_nr])
+ self._disc_listbox.insert(disc, -1)
+ if len(discs) == 1:
+ disc.show_disc_label(False)
+
if remaining == 0:
- self.songsGrid.show_all()
self.emit("tracks-loaded")
- if track:
- self.tracks.append(track)
- else:
- for i, track in enumerate(self.tracks):
- ui = Gtk.Builder()
- ui.add_from_resource('/org/gnome/Music/TrackWidget.ui')
- song_widget = ui.get_object('eventbox1')
- self.songs.append(song_widget)
- ui.get_object('num')\
- .set_markup('<span color=\'grey\'>%d</span>'
- % len(self.songs))
- title = utils.get_media_title(track)
- ui.get_object('title').set_text(title)
- ui.get_object('title').set_alignment(0.0, 0.5)
- ui.get_object('title').set_max_width_chars(MAX_TITLE_WIDTH)
-
- self.songsGrid.attach(
- song_widget,
- int(i / (len(self.tracks) / 2)),
- int(i % (len(self.tracks) / 2)), 1, 1
- )
- track.song_widget = song_widget
- itr = self.model.append(None)
- song_widget._iter = itr
- song_widget.model = self.model
- song_widget.title = ui.get_object('title')
- song_widget.checkButton = ui.get_object('select')
- song_widget.checkButton.set_visible(self.selectionMode)
- song_widget.checkButton.connect(
- 'toggled', self._check_button_toggled, song_widget
- )
- self.model.set(itr,
- [0, 1, 2, 3, 5],
- [title, '', '', False, track])
- song_widget.now_playing_sign = ui.get_object('image1')
- song_widget.now_playing_sign.set_from_icon_name(
- NOW_PLAYING_ICON_NAME,
- Gtk.IconSize.SMALL_TOOLBAR)
- song_widget.now_playing_sign.set_no_show_all('True')
- song_widget.now_playing_sign.set_alignment(1, 0.6)
- song_widget.can_be_played = True
- song_widget.connect('button-release-event',
- self.track_selected)
-
@log
def _update_album_art(self):
- self._cache.lookup(self.album, ArtSize.medium, self._get_album_cover,
+ self._cache.lookup(self._media, ArtSize.medium, self._get_album_cover,
None)
@log
@@ -155,57 +167,22 @@ class ArtistAlbumWidget(Gtk.Box):
self.cover.set_from_surface(surface)
@log
- def track_selected(self, widget, event):
- if not widget.can_be_played:
+ def _track_activated(self, widget, song_widget):
+ if (not song_widget.can_be_played
+ or self._selection_mode):
return
- if not self.selectionMode and \
- (event.button == Gdk.BUTTON_SECONDARY or
- (event.button == 1 and event.state & Gdk.ModifierType.CONTROL_MASK)):
- if self.selectionModeAllowed:
- self.header_bar._select_button.set_active(True)
- else:
- return
-
- if self.selectionMode:
- self.model[widget._iter][6] = not self.model[widget._iter][6]
- return
+ self._player.stop()
+ self._player.set_playlist('Artist', self._artist, song_widget.model,
+ song_widget.itr, 5, 11)
+ self._player.set_playing(True)
- self.player.stop()
- self.player.set_playlist('Artist', self.artist,
- widget.model, widget._iter, 5, 6)
- self.player.set_playing(True)
+ return True
@log
- def set_selection_mode(self, selectionMode):
- if self.selectionMode == selectionMode:
+ def set_selection_mode(self, selection_mode):
+ if self._selection_mode == selection_mode:
return
- self.selectionMode = selectionMode
- for songWidget in self.songs:
- songWidget.checkButton.set_visible(selectionMode)
- if not selectionMode:
- songWidget.model[songWidget._iter][6] = False
+ self._selection_mode = selection_mode
- @log
- def _check_button_toggled(self, button, songWidget):
- if songWidget.model[songWidget._iter][6] != button.get_active():
- songWidget.model[songWidget._iter][6] = button.get_active()
-
- @log
- def _model_row_changed(self, model, path, _iter):
- if not self.selectionMode:
- return
- if not model[_iter][5]:
- return
- songWidget = model[_iter][5].song_widget
- selected = model[_iter][6]
-
- if model[_iter][11] == DiscoveryStatus.FAILED:
- songWidget.now_playing_sign.set_from_icon_name(
- ERROR_ICON_NAME,
- Gtk.IconSize.SMALL_TOOLBAR)
- songWidget.now_playing_sign.show()
- songWidget.can_be_played = False
-
- if selected != songWidget.checkButton.get_active():
- songWidget.checkButton.set_active(selected)
+ self._disc_listbox.set_selection_mode(selection_mode)
diff --git a/gnomemusic/widgets/disclistboxwidget.py b/gnomemusic/widgets/disclistboxwidget.py
new file mode 100644
index 0000000..3b82e52
--- /dev/null
+++ b/gnomemusic/widgets/disclistboxwidget.py
@@ -0,0 +1,473 @@
+# 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.
+
+import logging
+
+from gettext import gettext as _
+from gi.repository import Gdk, Gio, GObject, Gtk
+
+from gnomemusic import log
+from gnomemusic.grilo import grilo
+import gnomemusic.utils as utils
+
+logger = logging.getLogger(__name__)
+
+NOW_PLAYING_ICON_NAME = 'media-playback-start-symbolic'
+ERROR_ICON_NAME = 'dialog-error-symbolic'
+
+try:
+ settings = Gio.Settings.new('org.gnome.Music')
+ MAX_TITLE_WIDTH = settings.get_int('max-width-chars')
+except Exception as e:
+ MAX_TITLE_WIDTH = 20
+ logger.error("Error on setting widget max-width-chars: %s", str(e))
+
+
+class StarStack(Gtk.Stack):
+ """Stackwidget for starring songs"""
+ __gtype_name__ = 'StarStack'
+
+ def __init__(self):
+ super().__init__(self)
+
+ starred_icon = Gtk.Image.new_from_icon_name('starred-symbolic',
+ Gtk.IconSize.SMALL_TOOLBAR)
+ non_starred_icon = Gtk.Image.new_from_icon_name(
+ 'non-starred-symbolic',
+ Gtk.IconSize.SMALL_TOOLBAR)
+
+ self._favorite = False
+
+ self.add_named(starred_icon, 'starred')
+ self.add_named(non_starred_icon, 'non-starred')
+ self.set_visible_child_name('non-starred')
+
+ self.show_all()
+
+ def set_favorite(self, favorite):
+ """Set favorite
+
+ Set the current widget as favorite or not.
+
+ :param bool favorite: Value to switch the widget state to
+ """
+ self._favorite = favorite
+
+ if self._favorite:
+ self.set_visible_child_name('starred')
+ else:
+ self.set_visible_child_name('non-starred')
+
+ def get_favorite(self):
+ """Return the current state of the widget
+
+ :return: The state of the widget
+ :rtype: bool
+ """
+ return self._favorite
+
+ def toggle_favorite(self):
+ """Toggle the widget state"""
+ self._favorite = not self._favorite
+
+ self.set_favorite(self._favorite)
+
+
+class DiscSongsFlowBox(Gtk.FlowBox):
+ """FlowBox containing the songs on one disc
+
+ DiscSongsFlowBox allows setting the number of columns to
+ use.
+ """
+ __gtype_name__ = 'DiscSongsFlowBox'
+
+ def __init__(self, columns=1):
+ """Initialize
+
+ :param int columns: The number of columns the widget uses
+ """
+ super().__init__(self)
+ super().set_selection_mode(Gtk.SelectionMode.NONE)
+
+ self._columns = columns
+ self.get_style_context().add_class('discsongsflowbox')
+
+ def set_columns(self, columns):
+ """Set the number of columns to use
+
+ :param int columns: The number of columns the widget uses
+ """
+ self._columns = columns
+
+ children_n = len(self.get_children())
+
+ if children_n % self._columns == 0:
+ max_per_line = children_n / self._columns
+ else:
+ max_per_line = int(children_n / self._columns) + 1
+
+ self.set_max_children_per_line(max_per_line)
+ self.set_min_children_per_line(max_per_line)
+
+
+class DiscBox(Gtk.Box):
+ """A widget which compromises one disc
+
+ DiscBox contains a disc label for the disc number on top
+ with a DiscSongsFlowBox beneath.
+ """
+ __gtype_name__ = 'DiscBox'
+
+ __gsignals__ = {
+ 'selection-changed': (GObject.SignalFlags.RUN_FIRST, None, ()),
+ 'selection-toggle': (GObject.SignalFlags.RUN_FIRST, None, ()),
+ 'track-activated': (GObject.SignalFlags.RUN_FIRST, None, (Gtk.Widget,))
+
+ }
+
+ def __init__(self, model=None):
+ """Initialize
+
+ :param model: The TreeStore to use
+ """
+ super().__init__(self)
+
+ self._model = model
+ self._model.connect('row-changed', self._model_row_changed)
+
+ builder = Gtk.Builder()
+ builder.add_from_resource('/org/gnome/Music/ArtistAlbumWidget.ui')
+
+ self._label = builder.get_object('disclabel')
+ self._label.set_no_show_all(True)
+ self._disc_songs_flowbox = builder.get_object('discsongsflowbox')
+
+ self._selection_mode = False
+ self._selection_mode_allowed = True
+ self._selected_items = []
+ self._songs = []
+
+ self.pack_start(builder.get_object('disc'), True, True, 0)
+
+ def set_columns(self, columns):
+ """Set the number of columns used by the songs list
+
+ :param int columns: Number of columns to display
+ """
+ self._disc_songs_flowbox.set_columns(columns)
+
+ def set_disc_number(self, disc_number):
+ """Set the dics number to display
+
+ :param int disc_number: Disc number to display
+ """
+ self._label.set_markup(_("Disc {}").format(disc_number))
+ self._label.get_style_context().add_class('dim-label')
+ self._label.set_visible(True)
+
+ def show_disc_label(self, show_header):
+ """Wheter to show the disc number label
+
+ :param bool show_header: Display the disc number label
+ """
+ self._label.set_visible(False)
+ self._label.hide()
+
+ def show_duration(self, show_duration):
+ """Wheter to show the song durations
+
+ :param bool show_duration: Display the song durations
+ """
+ def child_show_duration(child):
+ child.get_child().duration.set_visible(show_duration)
+
+ self._disc_songs_flowbox.foreach(child_show_duration)
+
+ def show_favorites(self, show_favorites):
+ """Where to show the favorite switches
+
+ :param bool show_favorites: Display the favorite
+ switches
+ """
+ def child_show_favorites(child):
+ child.get_child().starevent.set_visible(show_favorites)
+
+ self._disc_songs_flowbox.foreach(child_show_favorites)
+
+ def show_song_numbers(self, show_song_number):
+ """Whether to show the song numbers
+
+ :param bool show_song_number: Display the song number
+ """
+ def child_show_song_number(child):
+ child.get_child().number.set_visible(show_song_number)
+
+ self._disc_songs_flowbox.foreach(child_show_song_number)
+
+ def set_tracks(self, tracks):
+ """Songs to display
+
+ :param list tracks: A list of Grilo media items to
+ add to the widget
+ """
+ for track in tracks:
+ song_widget = self._create_song_widget(track)
+ self._disc_songs_flowbox.insert(song_widget, -1)
+ track.song_widget = song_widget
+
+ def set_selection_mode(self, selection_mode):
+ """Set selection mode
+
+ :param bool selection_mode: Allow selection mode
+ """
+ self._selection_mode = selection_mode
+ self._disc_songs_flowbox.foreach(self._toggle_widget_selection)
+
+ def get_selected_items(self):
+ """Return all selected items
+
+ :returns: The selected items:
+ :rtype: A list if Grilo media items
+ """
+ self._selected_items = []
+ self._disc_songs_flowbox.foreach(self._get_selected)
+
+ return self._selected_items
+
+ def _get_selected(self, child):
+ song_widget = child.get_child()
+
+ if song_widget.check_button.get_active():
+ itr = song_widget.itr
+ self._selected_items.append(self._model[itr][5])
+
+ # FIXME: select all/none slow probably b/c of the row changes
+ # invocations, maybe workaround?
+ def select_all(self):
+ """Select all songs"""
+ def child_select_all(child):
+ song_widget = child.get_child()
+ self._model[song_widget.itr][6] = True
+
+ self._disc_songs_flowbox.foreach(child_select_all)
+
+ def select_none(self):
+ """Deselect all songs"""
+ def child_select_none(child):
+ song_widget = child.get_child()
+ self._model[song_widget.itr][6] = False
+
+ self._disc_songs_flowbox.foreach(child_select_none)
+
+ def _create_song_widget(self, track):
+ """Helper function to create a song widget for a
+ single song
+
+ :param track: A Grilo media item
+ :returns: A complete song widget
+ :rtype: Gtk.EventBox
+ """
+ builder = Gtk.Builder()
+ builder.add_from_resource('/org/gnome/Music/TrackWidget.ui')
+ song_widget = builder.get_object('eventbox1')
+ self._songs.append(song_widget)
+
+ title = utils.get_media_title(track)
+
+ itr = self._model.append(None)
+
+ self._model[itr][0, 1, 2, 5, 6] = [title, '', '', track, False]
+
+ song_widget.itr = itr
+ song_widget.model = self._model
+
+ song_widget.number = builder.get_object('num')
+ song_widget.number.set_markup(
+ '<span color=\'grey\'>{}</span>'.format(len(self._songs)))
+ song_widget.number.set_no_show_all(True)
+
+ song_widget.title = builder.get_object('title')
+ song_widget.title.set_text(title)
+ song_widget.title.set_max_width_chars(MAX_TITLE_WIDTH)
+
+ song_widget.duration = builder.get_object('duration')
+ time = utils.seconds_to_string(track.get_duration())
+ song_widget.duration.set_text(time)
+
+ song_widget.check_button = builder.get_object('select')
+ song_widget.check_button.set_visible(False)
+ song_widget.check_button.connect('toggled',
+ self._check_button_toggled,
+ song_widget)
+
+ song_widget.now_playing_sign = builder.get_object('image1')
+ song_widget.now_playing_sign.set_from_icon_name(
+ NOW_PLAYING_ICON_NAME,
+ Gtk.IconSize.SMALL_TOOLBAR)
+ song_widget.now_playing_sign.set_no_show_all(True)
+ song_widget.can_be_played = True
+ song_widget.connect('button-release-event', self._track_activated)
+
+ song_widget.star_stack = builder.get_object('starstack')
+ song_widget.star_stack.set_favorite(track.get_lyrics())
+ song_widget.star_stack.set_visible(True)
+
+ song_widget.starevent = builder.get_object('starevent')
+ song_widget.starevent.connect('button-release-event',
+ self._toggle_favorite,
+ song_widget)
+
+ return song_widget
+
+ def _toggle_favorite(self, widget, event, song_widget):
+ if event.button == Gdk.BUTTON_PRIMARY:
+ song_widget.star_stack.toggle_favorite()
+
+ # FIXME: ugleh. Should probably be triggered by a
+ # signal.
+ favorite = song_widget.star_stack.get_favorite()
+ grilo.set_favorite(self._model[song_widget.itr][5], favorite)
+ return True
+
+ def _check_button_toggled(self, widget, song_widget):
+ self.emit('selection-changed')
+
+ return True
+
+ def _toggle_widget_selection(self, child):
+ song_widget = child.get_child()
+ song_widget.check_button.set_visible(self._selection_mode)
+ if self._selection_mode == False:
+ if song_widget.check_button.get_active():
+ song_widget.check_button.set_active(False)
+
+ def _track_activated(self, widget, event):
+ # FIXME: don't think keys work correctly, if they did ever
+ # even.
+ if (not event.button == Gdk.BUTTON_SECONDARY
+ or (event.button == Gdk.BUTTON_PRIMARY
+ and event.state & Gdk.ModifierType.CONTROL_MASK)):
+ self.emit('track-activated', widget)
+ if self._selection_mode:
+ itr = widget.itr
+ self._model[itr][6] = not self._model[itr][6]
+ else:
+ self.emit('selection-toggle')
+ if self._selection_mode:
+ itr = widget.itr
+ self._model[itr][6] = True
+
+ return True
+
+ @log
+ def _model_row_changed(self, model, path, itr):
+ if (not self._selection_mode
+ or not model[itr][5]):
+ return
+
+ song_widget = model[itr][5].song_widget
+ selected = model[itr][6]
+ if selected != song_widget.check_button.get_active():
+ song_widget.check_button.set_active(selected)
+
+ return True
+
+
+class DiscListBox(Gtk.ListBox):
+ """A ListBox widget containing all discs of a particular
+ album
+ """
+ __gtype_name__ = 'DiscListBox'
+
+ __gsignals__ = {
+ 'selection-changed': (GObject.SignalFlags.RUN_FIRST, None, ()),
+ }
+
+ def __init__(self):
+ """Initialize"""
+ super().__init__()
+ super().set_selection_mode(Gtk.SelectionMode.NONE)
+
+ self._selection_mode = False
+ self._selection_mode_allowed = False
+ self._selected_items = []
+
+ def insert(self, widget, position):
+ """Insert a DiscBox widget"""
+ super().insert(widget, position)
+ widget.connect('selection-changed', self._on_selection_changed)
+
+ def _on_selection_changed(self, widget):
+ self.emit('selection-changed')
+
+ def get_selected_items(self):
+ """Returns all selected items for all discs
+
+ :returns: All selected items
+ :rtype: A list if Grilo media items
+ """
+ self._selected_items = []
+
+ def get_child_selected_items(child):
+ self._selected_items += child.get_child().get_selected_items()
+
+ self.foreach(get_child_selected_items)
+
+ return self._selected_items
+
+ def select_all(self):
+ """Select all songs"""
+ def child_select_all(child):
+ child.get_child().select_all()
+
+ self.foreach(child_select_all)
+
+ def select_none(self):
+ """Deselect all songs"""
+ def child_select_none(child):
+ child.get_child().select_none()
+
+ self.foreach(child_select_none)
+
+ def set_selection_mode(self, selection_mode):
+ """Set selection mode
+
+ :param bool selection_mode: Allow selection mode
+ """
+ if not self._selection_mode_allowed:
+ return
+
+ self._selection_mode = selection_mode
+
+ def set_child_selection_mode(child):
+ child.get_child().set_selection_mode(self._selection_mode)
+
+ self.foreach(set_child_selection_mode)
+
+ def set_selection_mode_allowed(self, allowed):
+ """Set if selection mode is allowed
+
+ :param bool allowed: Allow selection mode
+ """
+ self._selection_mode_allowed = allowed
diff --git a/gnomemusic/window.py b/gnomemusic/window.py
index 41b4b8c..3893b07 100644
--- a/gnomemusic/window.py
+++ b/gnomemusic/window.py
@@ -306,10 +306,10 @@ class Window(Gtk.ApplicationWindow):
def _on_select_none(self, action, param):
if self.toolbar._state == ToolbarState.MAIN:
view = self._stack.get_visible_child()
+ view.unselect_all()
else:
view = self._stack.get_visible_child().get_visible_child()
-
- view.unselect_all()
+ view.select_none()
def _show_notification(self):
self.notification_handler = None
diff --git a/po/POTFILES.in b/po/POTFILES.in
index ab49d21..3fc2324 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -28,6 +28,7 @@ gnomemusic/views/songsview.py
gnomemusic/widgets/albumwidget.py
gnomemusic/widgets/artistalbumswidget.py
gnomemusic/widgets/artistalbumwidget.py
+gnomemusic/widgets/disclistboxwidget.py
gnomemusic/widgets/playlistdialog.py
gnomemusic/widgets/starhandlerwidget.py
gnomemusic/window.py
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]