[gnome-music/wip/jfelder/playlistview-listbox] playlistsview: Port the view to ListBox
- From: Jean Felder <jfelder src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-music/wip/jfelder/playlistview-listbox] playlistsview: Port the view to ListBox
- Date: Fri, 31 May 2019 17:09:45 +0000 (UTC)
commit ce8f044afc335554a37731cce78a31b62932cfe1
Author: Jean Felder <jfelder src gnome org>
Date: Fri May 31 10:07:42 2019 +0200
playlistsview: Port the view to ListBox
data/org.gnome.Music.css | 11 ++-
data/org.gnome.Music.gresource.xml | 1 +
data/ui/SongRow.ui | 113 +++++++++++++++++++++
gnomemusic/player.py | 71 ++++++++++++++
gnomemusic/views/playlistsview.py | 195 +++++++++----------------------------
gnomemusic/widgets/songrow.py | 182 ++++++++++++++++++++++++++++++++++
meson.build | 2 +-
7 files changed, 424 insertions(+), 151 deletions(-)
---
diff --git a/data/org.gnome.Music.css b/data/org.gnome.Music.css
index 6caaa519..5cd56c1c 100644
--- a/data/org.gnome.Music.css
+++ b/data/org.gnome.Music.css
@@ -96,15 +96,20 @@ box#ArtistAlbumsWidget .artist-label {
}
/* PlaylistDialog */
-.playlistdialog-row {
+.playlistdialog-row,
+.song-row {
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
-.playlistdialog-row:selected {
+.playlistdialog-row:selected,
+.song-row:hover,
+.song-row:selected {
color: @theme_fg_color;
background-color: @theme_insensitive_bg_color;
}
-.playlistdialog-row:selected label {
+.playlistdialog-row:selected label,
+.song-row:hover label,
+.song-row:selected label {
color: @theme_text_color;
}
diff --git a/data/org.gnome.Music.gresource.xml b/data/org.gnome.Music.gresource.xml
index 3992ffa4..c018d18c 100644
--- a/data/org.gnome.Music.gresource.xml
+++ b/data/org.gnome.Music.gresource.xml
@@ -24,6 +24,7 @@
<file preprocess="xml-stripblanks">ui/SelectionBarMenuButton.ui</file>
<file preprocess="xml-stripblanks">ui/SelectionToolbar.ui</file>
<file preprocess="xml-stripblanks">ui/SidebarRow.ui</file>
+ <file preprocess="xml-stripblanks">ui/SongRow.ui</file>
<file preprocess="xml-stripblanks">ui/SongWidget.ui</file>
<file preprocess="xml-stripblanks">ui/TwoLineTip.ui</file>
<file preprocess="xml-stripblanks">ui/Window.ui</file>
diff --git a/data/ui/SongRow.ui b/data/ui/SongRow.ui
new file mode 100644
index 00000000..f4915ae1
--- /dev/null
+++ b/data/ui/SongRow.ui
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="SongRow" parent="GtkListBoxRow">
+ <property name="can_focus">False</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkBox" id="hbox">
+ <property name="can_focus">False</property>
+ <property name="height-request">48</property>
+ <property name="margin-left">20</property>
+ <property name="margin-right">20</property>
+ <property name="spacing">64</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkBox" id="merde0">
+ <property name="spacing">20</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkImage" id="_play_icon">
+ <property name="can_focus">False</property>
+ <property name="icon_size">1</property>
+ <property name="visible">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="_title_label">
+ <property name="can_focus">False</property>
+ <property name="ellipsize">end</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="valign">center</property>
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="star_duration_box">
+ <property name="hexpand">False</property>
+ <property name="margin-start">10</property>
+ <property name="margin-end">10</property>
+ <property name="spacing">64</property>
+ <property name="vexpand">True</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkEventBox" id="_star_eventbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <signal name="button-release-event" handler="_on_star_toggle" swapped="no"/>
+ <signal name="enter-notify-event" handler="_on_star_hover" swapped="no"/>
+ <signal name="leave-notify-event" handler="_on_star_unhover" swapped="no"/>
+ <child>
+ <object class="StarImage" id="_star_image">
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="valign">center</property>
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="_duration_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">center</property>
+ <property name="single_line_mode">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="merde1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="_artist_label">
+ <property name="can_focus">False</property>
+ <property name="ellipsize">end</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="valign">center</property>
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="merde2">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="_album_label">
+ <property name="can_focus">False</property>
+ <property name="ellipsize">end</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="valign">center</property>
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+<object class="GtkSizeGroup">
+ <property name="mode">horizontal</property>
+ <widgets>
+ <widget name="merde0"/>
+ <widget name="merde1"/>
+ <widget name="merde2"/>
+ </widgets>
+</object>
+</interface>
diff --git a/gnomemusic/player.py b/gnomemusic/player.py
index 25ce9b66..d5671370 100644
--- a/gnomemusic/player.py
+++ b/gnomemusic/player.py
@@ -177,6 +177,67 @@ class PlayerPlaylist(GObject.GObject):
self._validate_next_song()
return True
+ @log
+ def set_playlist_from_listbox(self, playlist_type, playlist_id, listbox):
+ """Set a new playlist or change the song being played
+
+ If no song is requested (through model_iter), a song will be
+ automatically selected:
+ * the first song in a linear mode
+ * a random song in shuffle mode
+
+ :param PlayerPlaylist.Type playlist_type: playlist type
+ :param string playlist_id: unique identifer to recognize the playlist
+ :param GtkListStore model: list of songs to play
+ :param GtkTreeIter model_iter: requested song
+
+ :return: True if the playlist has been updated. False otherwise
+ :rtype: bool
+ """
+ selected_row = listbox.get_selected_row()
+ if selected_row:
+ self._current_index = selected_row.get_index()
+ else:
+ if self.props.repeat_mode == RepeatMode.SHUFFLE:
+ self._current_index = randrange(len(listbox))
+ else:
+ self._current_index = 0
+
+ # Playlist is the same. Check that the requested song is valid.
+ # If not, try to get the next valid one
+ if (playlist_type == self._type
+ and playlist_id == self._id):
+ if not self._current_song_is_valid():
+ self.next()
+ else:
+ self._validate_song(self._current_index)
+ self._validate_next_song()
+ return False
+
+ self._validation_indexes = defaultdict(list)
+ self._type = playlist_type
+ self._id = playlist_id
+
+ self._songs = []
+ for row in listbox:
+ self._songs.append([row.props.media, ValidationStatus.PENDING])
+
+ if self.props.repeat_mode == RepeatMode.SHUFFLE:
+ self._shuffle_indexes = list(range(len(self._songs)))
+ shuffle(self._shuffle_indexes)
+ self._shuffle_indexes.remove(self._current_index)
+ self._shuffle_indexes.insert(0, self._current_index)
+
+ # If the playlist has already been played, check that the requested
+ # song is valid. If it has never been played, validate the current
+ # song and the next song to display an error icon on failure.
+ if not self._current_song_is_valid():
+ self.next()
+ else:
+ self._validate_song(self._current_index)
+ self._validate_next_song()
+ return True
+
@log
def set_song(self, song_offset):
"""Change playlist index.
@@ -710,6 +771,16 @@ class Player(GObject.GObject):
if playlist_changed:
self.emit('playlist-changed')
+ @log
+ def set_playlist_from_listbox(
+ self, playlist_type, playlist_id, listbox):
+ playlist_changed = self._playlist.set_playlist_from_listbox(
+ playlist_type, playlist_id, listbox)
+
+ if playlist_changed:
+ self.emit('playlist-changed')
+
+
@log
def playlist_change_position(self, prev_pos, new_pos):
"""Change order of a song in the playlist.
diff --git a/gnomemusic/views/playlistsview.py b/gnomemusic/views/playlistsview.py
index aad54a63..9ffb7079 100644
--- a/gnomemusic/views/playlistsview.py
+++ b/gnomemusic/views/playlistsview.py
@@ -36,6 +36,7 @@ from gnomemusic.widgets.playlistcontextmenu import PlaylistContextMenu
from gnomemusic.widgets.playlistcontrols import PlaylistControls
from gnomemusic.widgets.playlistdialog import PlaylistDialog
from gnomemusic.widgets.sidebarrow import SidebarRow
+from gnomemusic.widgets.songrow import SongRow
import gnomemusic.utils as utils
playlists = Playlists.get_default()
@@ -66,8 +67,6 @@ class PlaylistsView(BaseView):
self._view.get_style_context().add_class('songs-list')
- self._add_list_renderers()
-
self._pl_ctrls = PlaylistControls()
self._pl_ctrls.connect('playlist-renamed', self._on_playlist_renamed)
@@ -113,8 +112,7 @@ class PlaylistsView(BaseView):
self._grid.child_set_property(sidebar_container, 'top-attach', 0)
self._grid.child_set_property(sidebar_container, 'height', 2)
- self._iter_to_clean = None
- self._iter_to_clean_model = None
+ self._current_row = None
self._current_playlist = None
self._current_playlist_index = None
self._plays_songs_on_activation = False
@@ -122,10 +120,7 @@ class PlaylistsView(BaseView):
self._songs_todelete = {}
self._songs_count = 0
- self.model.connect('row-inserted', self._on_song_inserted)
- self.model.connect('row-deleted', self._on_song_deleted)
-
- self.player.connect('song-changed', self._update_model)
+ self.player.connect('song-changed', self._update_listbox)
self.player.connect('song-validated', self._on_song_validated)
playlists.connect('playlist-created', self._on_playlist_created)
playlists.connect('playlist-updated', self._on_playlist_update)
@@ -144,17 +139,17 @@ class PlaylistsView(BaseView):
view_container = Gtk.ScrolledWindow(hexpand=True, vexpand=True)
self._box.pack_start(view_container, True, True, 0)
- self._view = Gtk.TreeView()
- self._view.set_headers_visible(False)
- self._view.set_valign(Gtk.Align.START)
- self._view.set_model(self.model)
- self._view.set_activate_on_single_click(True)
- self._view.get_selection().set_mode(Gtk.SelectionMode.SINGLE)
+ self._view = Gtk.ListBox()
+ self._view.props.activate_on_single_click = True
+ self._view.props.valign = Gtk.Align.FILL
+ self._view.props.vexpand = True
- self._view.connect('row-activated', self._on_song_activated)
- self._view.connect('drag-begin', self._drag_begin)
- self._view.connect('drag-end', self._drag_end)
self._song_drag = {'active': False}
+ self._view.connect('drag-begin', self._drag_begin) # ?
+ self._view.connect('drag-end', self._drag_end) # ?
+ self._view.connect('row-activated', self._on_song_activated) # ok
+ # self._view.connect('row-deleted', self._on_song_deleted)
+ # self._view.connect('row-inserted', self._on_song_inserted)
self._controller = Gtk.GestureMultiPress().new(self._view)
self._controller.props.propagation_phase = Gtk.PropagationPhase.CAPTURE
@@ -164,110 +159,25 @@ class PlaylistsView(BaseView):
view_container.add(self._view)
@log
- def _add_list_renderers(self):
- 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)
- self._view.append_column(column_now_playing)
-
- title_renderer = Gtk.CellRendererText(
- xpad=0, xalign=0.0, yalign=0.5, height=48,
- ellipsize=Pango.EllipsizeMode.END)
- column_title = Gtk.TreeViewColumn("Title", title_renderer, text=2)
- column_title.set_expand(True)
- self._view.append_column(column_title)
-
- column_star = Gtk.TreeViewColumn()
- self._view.append_column(column_star)
- self._star_handler.add_star_renderers(column_star)
-
- duration_renderer = Gtk.CellRendererText(xpad=32, xalign=1.0)
- column_duration = Gtk.TreeViewColumn()
- column_duration.pack_start(duration_renderer, False)
- column_duration.set_cell_data_func(
- duration_renderer, self._on_list_widget_duration_render, None)
- self._view.append_column(column_duration)
-
- artist_renderer = Gtk.CellRendererText(
- xpad=32, ellipsize=Pango.EllipsizeMode.END)
- column_artist = Gtk.TreeViewColumn("Artist", artist_renderer, text=3)
- column_artist.set_expand(True)
- self._view.append_column(column_artist)
-
- album_renderer = Gtk.CellRendererText(
- xpad=32, ellipsize=Pango.EllipsizeMode.END)
- column_album = Gtk.TreeViewColumn()
- column_album.set_expand(True)
- column_album.pack_start(album_renderer, True)
- column_album.set_cell_data_func(
- album_renderer, self._on_list_widget_album_render, None)
- self._view.append_column(column_album)
-
- def _on_list_widget_duration_render(self, col, cell, model, _iter, data):
- if not model.iter_is_valid(_iter):
- return
-
- item = model[_iter][5]
- if item:
- duration = item.get_duration()
- cell.set_property('text', utils.seconds_to_string(duration))
-
- def _on_list_widget_album_render(self, coll, cell, model, _iter, data):
- if not model.iter_is_valid(_iter):
- return
-
- item = model[_iter][5]
- if item:
- cell.set_property('text', utils.get_album_title(item))
-
- def _on_list_widget_icon_render(self, col, cell, model, _iter, data):
- if not self.player.playing_playlist(
- PlayerPlaylist.Type.PLAYLIST, self._current_playlist.get_id()):
- cell.set_visible(False)
- return
-
- if not model.iter_is_valid(_iter):
- return
-
- current_song = self.player.props.current_song
- if model[_iter][11] == ValidationStatus.FAILED:
- cell.set_property('icon-name', self._error_icon_name)
- cell.set_visible(True)
- elif model[_iter][5].get_id() == current_song.get_id():
- cell.set_property('icon-name', self._now_playing_icon_name)
- cell.set_visible(True)
- else:
- cell.set_visible(False)
-
- @log
- def _update_model(self, player):
- """Updates model when the song changes
+ def _update_listbox(self, player):
+ """Updates listbox when the song changes
:param Player player: The main player object
"""
if self._current_playlist is None:
return
- if self._iter_to_clean:
- self._iter_to_clean_model[self._iter_to_clean][10] = False
if not player.playing_playlist(
PlayerPlaylist.Type.PLAYLIST, self._current_playlist.get_id()):
return False
- index = self.player.props.current_song_index
- iter_ = self.model.get_iter_from_string(str(index))
- self.model[iter_][10] = True
- path = self.model.get_path(iter_)
- self._view.scroll_to_cell(path, None, False, 0., 0.)
- if self.model[iter_][8] != self._error_icon_name:
- self._iter_to_clean = iter_.copy()
- self._iter_to_clean_model = self.model
+ if self._current_row:
+ self._current_row.props.state = SongRow.State.UNPLAYED
+ index = self.player.props.current_song_index
+ self._current_row = self._view.get_row_at_index(index)
+ self._current_row.props.state = SongRow.State.PLAYING
+ # self._view.scroll_to_cell(path, None, False, 0., 0.)
return False
@log
@@ -330,35 +240,33 @@ class PlaylistsView(BaseView):
PlayerPlaylist.Type.PLAYLIST, self._current_playlist.get_id()):
return
- iter_ = self.model.get_iter_from_string(str(index))
- self.model[iter_][11] = status
+ row = self._view.get_row_at_index(index)
+ if status == ValidationStatus.FAILED:
+ row.props.state = SongRow.State.UNPLAYABLE
+ elif (status == ValidationStatus.SUCCEEDED
+ and row.props.state != SongRow.State.PLAYING):
+ row.props.state = SongRow.State.UNPLAYED
@log
- def _on_song_activated(self, widget, path, column):
+ def _on_song_activated(self, klass, row):
"""Action performed when clicking on a song
clicking on star column toggles favorite
clicking on an other columns launches player
Action is not performed if drag and drop is active
- :param Gtk.Tree treeview: self._view
- :param Gtk.TreePath path: activated row index
- :param Gtk.TreeViewColumn column: activated column
+ :param Gtk.ListBox klass: self._view
+ :param Gtk.ListboxRow row: activated row
"""
def activate_song():
if self._song_drag['active']:
return GLib.SOURCE_REMOVE
- if self._star_handler.star_renderer_click:
- self._star_handler.star_renderer_click = False
- return GLib.SOURCE_REMOVE
-
- _iter = None
- if path:
- _iter = self.model.get_iter(path)
playlist_id = self._current_playlist.get_id()
- self.player.set_playlist(
- PlayerPlaylist.Type.PLAYLIST, playlist_id, self.model, _iter)
+ self.player.set_playlist_from_listbox(
+ PlayerPlaylist.Type.PLAYLIST, playlist_id, self._view)
+ current_index = self.player.props.current_song_index
+ self._current_row = self._view.get_row_at_index(current_index)
self.player.play()
return GLib.SOURCE_REMOVE
@@ -537,11 +445,10 @@ class PlaylistsView(BaseView):
self._current_playlist_index = row.get_index()
# 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._iter_to_clean = None
- self._iter_to_clean_model = None
+ # use it as model, otherwise build the listbox
+ for row in self._view:
+ self._view.remove(row)
+ self._current_row = None
self._update_songs_count(0)
self._pl_ctrls.props.display_songs_count = False
grilo.populate_playlist_songs(playlist, self._add_song)
@@ -550,13 +457,13 @@ class PlaylistsView(BaseView):
self._playlist_delete_action.set_enabled(not protected_pl)
self._playlist_rename_action.set_enabled(not protected_pl)
self._remove_song_action.set_enabled(not protected_pl)
- self._view.set_reorderable(not protected_pl)
+ # self._view.set_reorderable(not protected_pl)
@log
def _add_song(self, source, param, song, remaining=0, data=None):
"""Grilo.populate_playlist_songs callback.
- Add all playlists found by Grilo to self._model
+ Add all songs found by Grilo to the ListBox
:param GrlTrackerSource source: tracker source
:param int param: param
@@ -564,9 +471,8 @@ class PlaylistsView(BaseView):
:param int remaining: next playlist_id or zero if None
:param data: associated data
"""
- self._add_song_to_model(song, self.model)
+ self._add_song_to_listbox(song)
if remaining == 0:
- self._view.set_model(self.model)
self._pl_ctrls.props.display_songs_count = True
if self._plays_songs_on_activation:
first_iter = self.model.get_iter_first()
@@ -577,27 +483,22 @@ class PlaylistsView(BaseView):
self._plays_songs_on_activation = False
@log
- def _add_song_to_model(self, song, model, index=-1):
+ def _add_song_to_listbox(self, song, index=-1):
"""Add song to a playlist
:param Grl.Media song: song to add
- :param Gtk.ListStore model: model
+ :param int index: insertion index in the ListBox
"""
if not song:
return None
- title = utils.get_media_title(song)
- song.set_title(title)
- artist = utils.get_artist_name(song)
- iter_ = model.insert_with_valuesv(
- index, [2, 3, 5, 9],
- [title, artist, song, song.get_favourite()])
-
+ song_row = SongRow(song)
+ self._view.insert(song_row, index)
self._update_songs_count(self._songs_count + 1)
- return iter_
+ return song_row
@log
def _on_play_activate(self, menuitem, data=None):
- self._view.emit('row-activated', None, None)
+ self._view.emit('row-activated', None)
@log
def _is_current_playlist(self, playlist):
@@ -685,7 +586,7 @@ class PlaylistsView(BaseView):
if not self._is_current_playlist(song_todelete['playlist']):
return
- iter_ = self._add_song_to_model(
+ iter_ = self._add_song_to_listbox(
song_todelete['song'], self.model, song_todelete['index'])
playlist_id = self._current_playlist.get_id()
@@ -746,7 +647,7 @@ class PlaylistsView(BaseView):
@log
def _on_song_added_to_playlist(self, playlists, playlist, item):
if self._is_current_playlist(playlist):
- iter_ = self._add_song_to_model(item, self.model)
+ iter_ = self._add_song_to_listbox(item, self.model)
playlist_id = self._current_playlist.get_id()
if self.player.playing_playlist(
PlayerPlaylist.Type.PLAYLIST, playlist_id):
diff --git a/gnomemusic/widgets/songrow.py b/gnomemusic/widgets/songrow.py
new file mode 100644
index 00000000..0c8717fc
--- /dev/null
+++ b/gnomemusic/widgets/songrow.py
@@ -0,0 +1,182 @@
+# Copyright 2019 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 enum import IntEnum
+
+from gi.repository import Gdk, GObject, Grl, Gtk
+
+from gnomemusic import utils
+from gnomemusic.grilo import grilo
+from gnomemusic.playlists import Playlists, SmartPlaylists
+from gnomemusic.widgets.starimage import StarImage # noqa: F401
+
+
+@Gtk.Template(resource_path="/org/gnome/Music/ui/SongRow.ui")
+class SongRow(Gtk.ListBoxRow):
+ """The single song row used in PlaylistView
+
+ Contains
+ * selection check box (optional)
+ * play icon (depending on state)
+ * song title
+ * favorite/star picker
+ * song duration
+ * song album name
+ * song artist
+ """
+
+ __gtype_name__ = "SongRow"
+
+ __gsignals__ = {
+ 'selection-changed': (GObject.SignalFlags.RUN_FIRST, None, ()),
+ }
+
+ selected = GObject.Property(type=bool, default=False)
+
+ _playlists = Playlists.get_default()
+
+ _album_label = Gtk.Template.Child()
+ _artist_label = Gtk.Template.Child()
+ _duration_label = Gtk.Template.Child()
+ _play_icon = Gtk.Template.Child()
+ # _select_button = Gtk.Template.Child()
+ _star_image = Gtk.Template.Child()
+ _title_label = Gtk.Template.Child()
+
+ class State(IntEnum):
+ """The state of the SongWidget
+ """
+ UNPLAYABLE = 0
+ UNPLAYED = 1
+ PLAYING = 2
+
+ def __repr__(self):
+ return '<SongRow>'
+
+ def __init__(self, media):
+ super().__init__()
+
+ self._media = media
+ self._selection_mode = False
+ self._state = SongRow.State.UNPLAYED
+
+ self.get_style_context().add_class('song-row')
+
+ title = utils.get_media_title(media)
+ self._title_label.props.label = title
+
+ time = utils.seconds_to_string(media.get_duration())
+ self._duration_label.props.label = time
+
+ self._star_image.props.favorite = media.get_favourite()
+
+ artist = utils.get_artist_name(media)
+ self._artist_label.props.label = artist
+
+ album = utils.get_album_title(media)
+ self._album_label.props.label = album
+
+ # self.bind_property(
+ # 'selected', self._select_button, 'active',
+ # GObject.BindingFlags.BIDIRECTIONAL
+ # | GObject.BindingFlags.SYNC_CREATE)
+
+ # @Gtk.Template.Callback()
+ # def _on_selection_changed(self, klass, value):
+ # self.emit('selection-changed')
+
+ @Gtk.Template.Callback()
+ def _on_star_toggle(self, widget, event):
+ (_, button) = event.get_button()
+ if button != Gdk.BUTTON_PRIMARY:
+ return False
+
+ favorite = not self._star_image.favorite
+ self._star_image.props.favorite = favorite
+
+ # TODO: Rework and stop updating widgets from here directly.
+ grilo.set_favorite(self._media, favorite)
+ self._playlists.update_smart_playlist(SmartPlaylists.Favorites)
+
+ return True
+
+ @Gtk.Template.Callback()
+ def _on_star_hover(self, widget, event):
+ self._star_image.props.hover = True
+
+ @Gtk.Template.Callback()
+ def _on_star_unhover(self, widget, event):
+ self._star_image.props.hover = False
+
+ @GObject.Property(type=bool, default=False)
+ def selection_mode(self):
+ """Selection mode
+
+ :returns: Selection mode
+ :rtype: bool
+ """
+ return self._selection_mode
+
+ @selection_mode.setter
+ def selection_mode(self, value):
+ """Set the selection mode
+
+ :param bool value: Selection mode
+ """
+ self._selection_mode = value
+ self._select_button.set_visible(value)
+
+ if not value:
+ self.props.selected = False
+
+ @GObject.Property(type=Grl.Media, flags=GObject.ParamFlags.READABLE)
+ def media(self):
+ return self._media
+
+ @GObject.Property(type=int, default=0)
+ def state(self):
+ """State of the widget
+
+ :returns: Widget state
+ :rtype: SongWidget.State
+ """
+ return self._state
+
+ @state.setter
+ def state(self, value):
+ """Set state of the of widget
+
+ This influences the look of the widgets label and if there is a
+ song play indicator being shown.
+
+ :param SongWidget.State value: Widget state
+ """
+ self._state = value
+ if value == SongRow.State.UNPLAYABLE:
+ self._play_icon.props.icon_name = "dialog-error-symbolic"
+ elif value == SongRow.State.PLAYING:
+ self._play_icon.props.icon_name = "media-playback-start-symbolic"
+ else:
+ self._play_icon.props.icon_name = None
+ self._play_icon.props.icon_size = 1
diff --git a/meson.build b/meson.build
index f040bc22..2cfa9b97 100644
--- a/meson.build
+++ b/meson.build
@@ -45,7 +45,7 @@ dependency('libsoup-2.4')
dependency('tracker-sparql-2.0', version: '>= 1.99.1')
dependency('pygobject-3.0', version: '>= 3.29.1')
dependency('py3cairo', version: '>= 1.14.0')
-dependency('grilo-0.3', version: '>= 0.3.8')
+dependency('grilo-0.3', version: '>= 0.3.7')
dependency('grilo-plugins-0.3', version: '>= 0.3.8')
subproject('libgd',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]