[gnome-music/wip/jfelder/smartplaylists-ui-v2: 22/25] Introduce smart playlist widgets
- From: Jean Felder <jfelder src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-music/wip/jfelder/smartplaylists-ui-v2: 22/25] Introduce smart playlist widgets
- Date: Mon, 3 Feb 2020 08:13:01 +0000 (UTC)
commit a3ead0a28b49e414e4b6c8b849a95d38bca4e831
Author: Jean Felder <jfelder src gnome org>
Date: Wed Sep 11 14:00:55 2019 +0200
Introduce smart playlist widgets
Smart Playlist are grouped together in flowboxes. Each smart playlist
is a flowbox item.
A play button is shown on hover. This remains while a playlist is
being played, and turns into a pause button when hovered.
Remove the "protected playlist" mechanism as it becomes obsolote: all
user playlists can be edited. Button and context menu are not visible for
the smart playlists.
Closes: #49
data/org.gnome.Music.css | 7 +-
data/org.gnome.Music.gresource.xml | 2 +
data/ui/SmartPlaylistCover.ui | 75 ++++++++++++++
data/ui/SmartPlaylistsWidget.ui | 43 ++++++++
gnomemusic/widgets/smartplaylistcover.py | 161 +++++++++++++++++++++++++++++
gnomemusic/widgets/smartplaylistswidget.py | 95 +++++++++++++++++
po/POTFILES.in | 1 +
7 files changed, 383 insertions(+), 1 deletion(-)
---
diff --git a/data/org.gnome.Music.css b/data/org.gnome.Music.css
index 0d181cb1..8ee06574 100644
--- a/data/org.gnome.Music.css
+++ b/data/org.gnome.Music.css
@@ -50,7 +50,7 @@ box#ArtistAlbumsWidget .artist-label {
font-size: smaller;
}
-/* PlaylistControls */
+/* PlaylistControls and SmartPlaylistCover */
.playlist-name-label {
font-weight: bold;
font-size: x-large;
@@ -60,6 +60,11 @@ box#ArtistAlbumsWidget .artist-label {
margin: 18px 24px 18px 18px;
}
+.smartplaylist-image {
+ border-radius: 50%;
+ color: white;
+}
+
/* ArtistAlbumWidget */
.artist-albums-widget,
.artist-albums-widget:hover {
diff --git a/data/org.gnome.Music.gresource.xml b/data/org.gnome.Music.gresource.xml
index 80ea92f9..eb4f8440 100644
--- a/data/org.gnome.Music.gresource.xml
+++ b/data/org.gnome.Music.gresource.xml
@@ -29,6 +29,8 @@
<file preprocess="xml-stripblanks">ui/SearchView.ui</file>
<file preprocess="xml-stripblanks">ui/SelectionBarMenuButton.ui</file>
<file preprocess="xml-stripblanks">ui/SelectionToolbar.ui</file>
+ <file preprocess="xml-stripblanks">ui/SmartPlaylistCover.ui</file>
+ <file preprocess="xml-stripblanks">ui/SmartPlaylistsWidget.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/SmartPlaylistCover.ui b/data/ui/SmartPlaylistCover.ui
new file mode 100644
index 00000000..3323cb1d
--- /dev/null
+++ b/data/ui/SmartPlaylistCover.ui
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="SmartPlaylistCover" parent="GtkFlowBoxChild">
+ <property name="no_show_all">True</property>
+ <child>
+ <object class="GtkBox" id="box_container">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkOverlay" id="_overlay">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_bottom">4</property>
+ <child>
+ <object class="GtkImage" id="_bg_image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="vexpand">True</property>
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <property name="icon-size">6</property>
+ </object>
+ </child>
+ <child type="overlay">
+ <object class="GtkBox">
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkImage" id="_fg_image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="icon-size">6</property>
+ <style>
+ <class name="smartplaylist-image"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="_title">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="justify">center</property>
+ <property name="wrap">True</property>
+ <property name="ellipsize">middle</property>
+ <property name="max_width_chars">20</property>
+ <property name="lines">2</property>
+ <property name="tooltip_text" bind-source="_title" bind-property="label" bind-flags="default" />
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkEventControllerMotion" id="_motions_ctrlr">
+ <property name="widget">_overlay</property>
+ <property name="propagation-phase">capture</property>
+ <signal name="enter" handler="_on_smartplaylist_enter" swapped="no"/>
+ <signal name="leave" handler="_on_smartplaylist_leave" swapped="no"/>
+ </object>
+ <object class="GtkGestureMultiPress" id="_click_ctrlr">
+ <property name="widget">_overlay</property>
+ <property name="propagation-phase">capture</property>
+ <signal name="pressed" handler="_on_smartplaylist_pressed" swapped="no"/>
+ </object>
+</interface>
diff --git a/data/ui/SmartPlaylistsWidget.ui b/data/ui/SmartPlaylistsWidget.ui
new file mode 100644
index 00000000..28988462
--- /dev/null
+++ b/data/ui/SmartPlaylistsWidget.ui
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="SmartPlaylistsWidget" parent="GtkBox">
+ <property name="hexpand">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox" id="title_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ <property name="orientation">vertical</property>
+ <style>
+ <class name="playlist-title-container"/>
+ </style>
+ <child>
+ <object class="GtkLabel" id="title_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Smart Playlists</property>
+ <style>
+ <class name="playlist-name-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFlowBox" id="_flowbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">True</property>
+ <property name="selection_mode">none</property>
+ <property name="halign">fill</property>
+ <property name="valign">start</property>
+ <property name="margin">18</property>
+ <property name="row_spacing">12</property>
+ <property name="column_spacing">6</property>
+ <property name="min_children_per_line">1</property>
+ <property name="max_children_per_line">5</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/gnomemusic/widgets/smartplaylistcover.py b/gnomemusic/widgets/smartplaylistcover.py
new file mode 100644
index 00000000..ba4f2ec4
--- /dev/null
+++ b/gnomemusic/widgets/smartplaylistcover.py
@@ -0,0 +1,161 @@
+# Copyright 2020 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, Gtk
+
+from gnomemusic.albumartcache import Art
+from gnomemusic.grilowrappers.grltrackerplaylists import SmartPlaylist
+from gnomemusic.player import PlayerPlaylist
+
+
+@Gtk.Template(resource_path="/org/gnome/Music/ui/SmartPlaylistCover.ui")
+class SmartPlaylistCover(Gtk.FlowBoxChild):
+ """ A smart playlist cover is a widget which displays
+ the playlist name and its icon.
+ A play or pause icon is displayed if the playlist
+ is playing or if the pointer is above it.
+ """
+
+ __gtype_name__ = "SmartPlaylistCover"
+
+ _play_icon = "media-playback-start-symbolic"
+ _pause_icon = "media-playback-pause-symbolic"
+
+ class State(IntEnum):
+ STOPPED = 0
+ PAUSED = 1
+ PLAYING = 2
+
+ _click_ctrlr = Gtk.Template.Child()
+ _overlay = Gtk.Template.Child()
+ _bg_image = Gtk.Template.Child()
+ _fg_image = Gtk.Template.Child()
+ _title = Gtk.Template.Child()
+ _motions_ctrlr = Gtk.Template.Child()
+
+ state = GObject.Property(type=int, default=0)
+
+ def __init__(self, smart_playlist, coremodel, player):
+ """Initialize the cover.
+
+ :param SmartPlaylit smart_playlist: the smart playlist to use
+ :param CoreModel coremodel: the CoreModel object
+ :param Player player: The Player object
+ """
+ super().__init__()
+
+ self._coremodel = coremodel
+ self._player = player
+
+ self._playlist = smart_playlist
+
+ self._title.props.label = smart_playlist.props.title
+
+ self._overlay.add_events(
+ Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK)
+
+ self._bg_image.override_background_color(
+ Gtk.StateType.NORMAL, smart_playlist.props.color)
+ self._bg_image.props.width_request = Art.Size.MEDIUM.width
+ self._bg_image.props.height_request = Art.Size.MEDIUM.height
+ self._bg_image.props.icon_name = smart_playlist.props.icon_name
+
+ self._fg_image.override_background_color(
+ Gtk.StateType.NORMAL, Gdk.RGBA(0., 0., 0., 1.))
+ self._fg_image.props.width_request = Art.Size.MEDIUM.width / 2
+ self._fg_image.props.height_request = Art.Size.MEDIUM.height / 2
+
+ self._hover = False
+ self.connect("notify::state", self._on_state_changed)
+
+ self._playlist.props.model.connect(
+ "items-changed", self._on_model_items_changed)
+ self.stop()
+
+ def _on_model_items_changed(self, model, position, removed, added):
+ self.set_visibility()
+
+ def set_visibility(self):
+ """Display the playlist if has some songs."""
+ self.props.visible = self._playlist.props.model.get_n_items() > 0
+
+ @Gtk.Template.Callback()
+ def _on_smartplaylist_enter(self, ctrlr, x, y):
+ self._hover = True
+ self._on_state_changed(self)
+
+ @Gtk.Template.Callback()
+ def _on_smartplaylist_leave(self, ctrlr):
+ self._hover = False
+ self._on_state_changed(self)
+
+ def _on_state_changed(self, klass, value=None):
+ if self._hover is True:
+ self._fg_image.props.opacity = 0.8
+ if self.props.state == SmartPlaylistCover.State.PLAYING:
+ self._fg_image.props.icon_name = self._pause_icon
+ elif self.props.state == SmartPlaylistCover.State.PAUSED:
+ self._fg_image.props.icon_name = self._play_icon
+ else:
+ if self.props.state == SmartPlaylistCover.State.PLAYING:
+ self._fg_image.props.opacity = 0.8
+ self._fg_image.props.icon_name = self._play_icon
+ elif self.props.state == SmartPlaylistCover.State.PAUSED:
+ self._fg_image.props.opacity = 0.8
+ self._fg_image.props.icon_name = self._pause_icon
+ else:
+ self._fg_image.props.opacity = 0.0
+
+ @Gtk.Template.Callback()
+ def _on_smartplaylist_pressed(self, gesture, n_press, x, y):
+ if self.props.state == SmartPlaylistCover.State.PAUSED:
+ self._player.play()
+ self.props.state = SmartPlaylistCover.State.PLAYING
+ return
+ elif self.props.state == SmartPlaylistCover.State.PLAYING:
+ self._player.pause()
+ self.props.state = SmartPlaylistCover.State.PAUSED
+ return
+
+ def _on_playlist_loaded(klass, playlist_type):
+ self._player.play()
+ self.props.state = SmartPlaylistCover.State.PLAYING
+ self._coremodel.disconnect(signal_id)
+
+ signal_id = self._coremodel.connect(
+ "playlist-loaded", _on_playlist_loaded)
+ self._coremodel.set_player_model(
+ PlayerPlaylist.Type.PLAYLIST, self._playlist.props.model)
+
+ @GObject.Property(
+ type=SmartPlaylist, default=None, flags=GObject.ParamFlags.READABLE)
+ def playlist(self):
+ return self._playlist
+
+ def stop(self):
+ self.props.state = SmartPlaylistCover.State.STOPPED
+ self._fg_image.props.icon_name = self._play_icon
+ self._fg_image.props.opacity = 0.0
diff --git a/gnomemusic/widgets/smartplaylistswidget.py b/gnomemusic/widgets/smartplaylistswidget.py
new file mode 100644
index 00000000..c94f73f1
--- /dev/null
+++ b/gnomemusic/widgets/smartplaylistswidget.py
@@ -0,0 +1,95 @@
+# Copyright 2020 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 gi.repository import Gtk
+
+from gnomemusic.widgets.smartplaylistcover import SmartPlaylistCover
+
+
+@Gtk.Template(resource_path="/org/gnome/Music/ui/SmartPlaylistsWidget.ui")
+class SmartPlaylistsWidget(Gtk.Box):
+ """
+
+ """
+
+ __gtype_name__ = "SmartPlaylistsWidget"
+
+ _flowbox = Gtk.Template.Child()
+
+ def __init__(self, coremodel, player):
+ """FIXME! briefly describe function
+
+ :param CoreModel coremodel: Main CoreModel object
+ :param Player player: Main Player object
+ """
+ super().__init__()
+
+ self._coremodel = coremodel
+ self._smart_pls_model = self._coremodel.props.smart_playlists_sort
+
+ self._player = player
+
+ self._playing_cover = None
+
+ self._flowbox.bind_model(self._smart_pls_model, self._add_playlist)
+ self._smart_pls_model.connect(
+ "items-changed", self._on_model_items_changed)
+ self._on_model_items_changed(
+ self._smart_pls_model, 0, 0, self._smart_pls_model.get_n_items())
+
+ def _add_playlist(self, smart_playlist):
+ child = SmartPlaylistCover(
+ smart_playlist, self._coremodel, self._player)
+ child.connect("notify::state", self._on_cover_state_changed)
+
+ return child
+
+ def _on_model_items_changed(self, model, position, removed, added):
+ if added == 0:
+ return
+
+ for index in range(added):
+ cover = self._flowbox.get_child_at_index(position + index)
+ cover.set_visibility()
+
+ def _on_cover_state_changed(self, selected_cover, value):
+ if (selected_cover.props.state != SmartPlaylistCover.State.PLAYING
+ or selected_cover == self._playing_cover):
+ return
+
+ if self._playing_cover is not None:
+ self._playing_cover.stop()
+ self._playing_cover = selected_cover
+
+ def select(self, playlist):
+ """Changes the state of the corresponding cover to playing.
+ This function is called when the playlist is starts playing
+ from MPRIS.
+
+ :param SmartPlaylist playlist: new active playlist
+ """
+ for cover in self._flowbox:
+ if cover.props.playlist == playlist:
+ cover.props.state = SmartPlaylistCover.State.PLAYING
+ break
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 2df13b28..04006ddc 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -16,6 +16,7 @@ data/ui/SearchHeaderBar.ui
data/ui/SearchView.ui
data/ui/SelectionBarMenuButton.ui
data/ui/SelectionToolbar.ui
+data/ui/SmartPlaylistsWidget.ui
gnomemusic/__init__.py
gnomemusic/albumartcache.py
gnomemusic/application.py
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]