[gnome-music/wip/jfelder/playback-status-v3: 10/12] linearplaybox: Introduce LinearPlayBox widget



commit 7a0ad24260172b10c4d67dc552944ba23f12780f
Author: Jean Felder <jfelder src gnome org>
Date:   Thu Sep 13 19:55:00 2018 +0200

    linearplaybox: Introduce LinearPlayBox widget
    
    This widget is designed to display a list of songs. Each row is a
    TwoLineWidget which displays the album cover, song title and the
    artist.
    It will be used by PlaybackPopover widget to display a playlist from
    Artists, Songs and Playlists views.

 data/org.gnome.Music.gresource.xml       |   2 +
 data/ui/LinearPlaybackWindow.ui          |  19 +++++
 data/ui/TwoLineWidget.ui                 |  71 +++++++++++++++++
 gnomemusic/widgets/albumwidget.py        |   8 +-
 gnomemusic/widgets/artistalbumswidget.py |  10 +--
 gnomemusic/widgets/playbackpopover.py    | 131 +++++++++++++++++++++++++++++++
 gnomemusic/widgets/songwidget.py         | 102 +++++++++++++++++++++---
 7 files changed, 322 insertions(+), 21 deletions(-)
---
diff --git a/data/org.gnome.Music.gresource.xml b/data/org.gnome.Music.gresource.xml
index 97e2dfbd..9ec0b196 100644
--- a/data/org.gnome.Music.gresource.xml
+++ b/data/org.gnome.Music.gresource.xml
@@ -15,6 +15,7 @@
     <file preprocess="xml-stripblanks">ui/EmptyView.ui</file>
     <file preprocess="xml-stripblanks">ui/FilterView.ui</file>
     <file preprocess="xml-stripblanks">ui/HeaderBar.ui</file>
+    <file preprocess="xml-stripblanks">ui/LinearPlaybackWindow.ui</file>
     <file preprocess="xml-stripblanks">ui/PlayerToolbar.ui</file>
     <file preprocess="xml-stripblanks">ui/PlaylistContextMenu.ui</file>
     <file preprocess="xml-stripblanks">ui/PlaylistControls.ui</file>
@@ -25,6 +26,7 @@
     <file preprocess="xml-stripblanks">ui/SelectionToolbar.ui</file>
     <file preprocess="xml-stripblanks">ui/SidebarRow.ui</file>
     <file preprocess="xml-stripblanks">ui/SongWidget.ui</file>
+    <file preprocess="xml-stripblanks">ui/TwoLineWidget.ui</file>
     <file preprocess="xml-stripblanks">ui/TwoLineTip.ui</file>
   </gresource>
 </gresources>
diff --git a/data/ui/LinearPlaybackWindow.ui b/data/ui/LinearPlaybackWindow.ui
new file mode 100644
index 00000000..9cd2d6ba
--- /dev/null
+++ b/data/ui/LinearPlaybackWindow.ui
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="LinearPlaybackWindow" parent="GtkScrolledWindow">
+    <property name="width_request">600</property>
+    <property name="height_request">400</property>
+    <property name="hexpand">True</property>
+    <property name="vexpand">True</property>
+    <child>
+      <object class="GtkListBox" id="_listbox">
+        <property name="selection_mode">none</property>
+       <property name="visible">True</property>
+       <style>
+         <class name="view"/>
+         <class name="content-view"/>
+       </style>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/TwoLineWidget.ui b/data/ui/TwoLineWidget.ui
new file mode 100644
index 00000000..93ee5b02
--- /dev/null
+++ b/data/ui/TwoLineWidget.ui
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="TwoLineWidget" parent="GtkListBoxRow">
+    <property name="can_focus">False</property>
+    <property name="visible">True</property>
+    <child>
+      <object class="GtkBox">
+        <property name="can_focus">False</property>
+        <property name="hexpand">False</property>
+        <property name="orientation">horizontal</property>
+        <property name="visible">True</property>
+        <property name="margin">6</property>
+        <property name="spacing">8</property>
+        <child>
+          <object class="GtkImage" id="_play_icon">
+            <property name="icon_size">4</property>
+            <property name="visible">True</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="halign">fill</property>
+            <property name="hexpand">True</property>
+            <property name="visible">True</property>
+            <property name="spacing">12</property>
+            <child>
+              <object class="CoverStack" id="_cover_stack">
+                <property name="visible">True</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkBox">
+                <property name="orientation">vertical</property>
+                <property name="visible">True</property>
+                <child>
+                  <object class="GtkLabel" id="_main_label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">fill</property>
+                   <property name="hexpand">true</property>
+                    <property name="valign">start</property>
+                    <property name="xalign">0</property>
+                    <property name="ellipsize">middle</property>
+                    <property name="width_chars">8</property>
+                    <property name="max_width_chars">42</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="_secondary_label">
+                    <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">middle</property>
+                    <property name="width_chars">8</property>
+                    <property name="max_width_chars">42</property>
+                    <style>
+                      <class name="player-artist-label"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/gnomemusic/widgets/albumwidget.py b/gnomemusic/widgets/albumwidget.py
index 1814ddc4..6b31fe89 100644
--- a/gnomemusic/widgets/albumwidget.py
+++ b/gnomemusic/widgets/albumwidget.py
@@ -33,7 +33,7 @@ from gnomemusic.grilo import grilo
 from gnomemusic.player import PlayerPlaylist
 from gnomemusic.widgets.disclistboxwidget import DiscBox
 from gnomemusic.widgets.disclistboxwidget import DiscListBox  # noqa: F401
-from gnomemusic.widgets.songwidget import SongWidget
+from gnomemusic.widgets.songwidget import WidgetState
 import gnomemusic.utils as utils
 
 
@@ -276,13 +276,13 @@ class AlbumWidget(Gtk.EventBox):
             self._duration += song.get_duration()
 
             if (song.get_id() == current_song.get_id()):
-                song_widget.props.state = SongWidget.State.PLAYING
+                song_widget.props.state = WidgetState.PLAYING
                 song_passed = True
             elif (song_passed):
                 # Counter intuitive, but this is due to call order.
-                song_widget.props.state = SongWidget.State.UNPLAYED
+                song_widget.props.state = WidgetState.UNPLAYED
             else:
-                song_widget.props.state = SongWidget.State.PLAYED
+                song_widget.props.state = WidgetState.PLAYED
 
         self._set_duration_label()
 
diff --git a/gnomemusic/widgets/artistalbumswidget.py b/gnomemusic/widgets/artistalbumswidget.py
index b9150d71..274766ca 100644
--- a/gnomemusic/widgets/artistalbumswidget.py
+++ b/gnomemusic/widgets/artistalbumswidget.py
@@ -29,7 +29,7 @@ from gi.repository import GObject, Gtk
 from gnomemusic import log
 from gnomemusic.player import PlayerPlaylist
 from gnomemusic.widgets.artistalbumwidget import ArtistAlbumWidget
-from gnomemusic.widgets.songwidget import SongWidget
+from gnomemusic.widgets.songwidget import WidgetState
 
 logger = logging.getLogger(__name__)
 
@@ -157,13 +157,13 @@ class ArtistAlbumsWidget(Gtk.Box):
             song_widget = song.song_widget
 
             if (song.get_id() == current_song.get_id()):
-                song_widget.props.state = SongWidget.State.PLAYING
+                song_widget.props.state = WidgetState.PLAYING
                 song_passed = True
             elif (song_passed):
                 # Counter intuitive, but this is due to call order.
-                song_widget.props.state = SongWidget.State.UNPLAYED
+                song_widget.props.state = WidgetState.UNPLAYED
             else:
-                song_widget.props.state = SongWidget.State.PLAYED
+                song_widget.props.state = WidgetState.PLAYED
 
             itr = self._model.iter_next(itr)
 
@@ -176,7 +176,7 @@ class ArtistAlbumsWidget(Gtk.Box):
         while itr:
             song = self._model[itr][5]
             song_widget = song.song_widget
-            song_widget.props.state = SongWidget.State.UNPLAYED
+            song_widget.props.state = WidgetState.UNPLAYED
 
             itr = self._model.iter_next(itr)
 
diff --git a/gnomemusic/widgets/playbackpopover.py b/gnomemusic/widgets/playbackpopover.py
new file mode 100644
index 00000000..c48d7283
--- /dev/null
+++ b/gnomemusic/widgets/playbackpopover.py
@@ -0,0 +1,131 @@
+# 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 gi.repository import Gtk
+
+from gnomemusic import log
+from gnomemusic.widgets.songwidget import WidgetState, TwoLineWidget
+
+
+@Gtk.Template(resource_path='/org/gnome/Music/ui/LinearPlaybackWindow.ui')
+class LinearPlaybackWindow(Gtk.ScrolledWindow):
+
+    __gtype_name__ = 'LinearPlaybackWindow'
+
+    _listbox = Gtk.Template.Child()
+
+    _current_index = 0
+    _playlist_type = None
+    _playlist_id = None
+    _window_height = 0.0
+    _row_height = 0.0
+
+    def __repr__(self):
+        return '<LinearPlaybackWindow>'
+
+    @log
+    def __init__(self, player):
+        super().__init__()
+
+        self._player = player
+        self._player.connect('song-changed', self._on_song_changed)
+        self._player.connect('notify::repeat-mode', self._on_repeat_changed)
+
+        self._listbox.connect('row-activated', self._on_row_activated)
+
+        self.props.vadjustment.connect(
+            'changed', self._vertical_adjustment_changed)
+
+    @log
+    def _vertical_adjustment_changed(self, klass):
+        v_adjust = self.props.vadjustment
+        if v_adjust.props.upper != self._window_height:
+            self._window_height = v_adjust.props.upper
+            self._row_height = self._window_height / len(self._listbox)
+            v_adjust.props.value = (self._current_index * self._row_height
+                                    + self._row_height / 2
+                                    - v_adjust.props.page_size / 2)
+
+    @log
+    def _init_listbox_rows(self):
+        songs = self._player.get_mpris_playlist()
+        for index, song in enumerate(songs):
+            row = TwoLineWidget(song, WidgetState.UNPLAYED)
+            self._listbox.add(row)
+
+        last_song = songs[-1]
+        for index in range(len(songs), 21):
+            row = TwoLineWidget(last_song, WidgetState.UNPLAYED)
+            self._listbox.add(row)
+            row.hide()
+
+    @log
+    def update(self):
+        self._playlist_type = self._player.get_playlist_type()
+        self._playlist_id = self._player.get_playlist_id()
+
+        if len(self._listbox) == 0:
+            self._init_listbox_rows()
+
+        current_song_id = self._player.props.current_song.get_id()
+        songs = self._player.get_mpris_playlist()
+        song_passed = False
+        for index, song in enumerate(songs):
+            state = WidgetState.PLAYED
+            if song.get_id() == current_song_id:
+                song_passed = True
+                self._current_index = index
+                state = WidgetState.PLAYING
+            elif song_passed:
+                # Counter intuitive, but this is due to call order.
+                state = WidgetState.UNPLAYED
+            row = self._listbox.get_row_at_index(index)
+            row.update(song, state)
+            row.show()
+
+        for index in range(len(songs), 21):
+            row = self._listbox.get_row_at_index(index)
+            row.hide()
+
+    @log
+    def _on_song_changed(self, klass, position):
+        if not self._player.playing_playlist(
+                self._playlist_type, self._playlist_id):
+            return
+
+        current_song = self._player.props.current_song
+        playing_row = self._listbox.get_row_at_index(self._current_index)
+        if current_song.get_id() == playing_row.props.song_id:
+            return
+
+        self.update()
+
+    @log
+    def _on_repeat_changed(self, klass, param):
+        self.update()
+
+    @log
+    def _on_row_activated(self, klass, row):
+        index = row.get_index()
+        self._player.play(index - self._current_index)
diff --git a/gnomemusic/widgets/songwidget.py b/gnomemusic/widgets/songwidget.py
index 4322f8a8..f4956efc 100644
--- a/gnomemusic/widgets/songwidget.py
+++ b/gnomemusic/widgets/songwidget.py
@@ -31,11 +31,20 @@ from gi.repository.Dazzle import BoldingLabel  # noqa: F401
 
 from gnomemusic import log
 from gnomemusic import utils
+from gnomemusic.albumartcache import Art
 from gnomemusic.grilo import grilo
 from gnomemusic.playlists import Playlists, StaticPlaylists
 from gnomemusic.widgets.starimage import StarImage  # noqa: F401
 
 
+class WidgetState(IntEnum):
+        """The state of the SongWidget
+        """
+        PLAYED = 0
+        PLAYING = 1
+        UNPLAYED = 2
+
+
 @Gtk.Template(resource_path='/org/gnome/Music/ui/SongWidget.ui')
 class SongWidget(Gtk.EventBox):
     """The single song widget used in DiscListBox
@@ -70,13 +79,6 @@ class SongWidget(Gtk.EventBox):
     _star_image = Gtk.Template.Child()
     _play_icon = Gtk.Template.Child()
 
-    class State(IntEnum):
-        """The state of the SongWidget
-        """
-        PLAYED = 0
-        PLAYING = 1
-        UNPLAYED = 2
-
     def __repr__(self):
         return '<SongWidget>'
 
@@ -86,7 +88,7 @@ class SongWidget(Gtk.EventBox):
 
         self._media = media
         self._selection_mode = False
-        self._state = SongWidget.State.UNPLAYED
+        self._state = WidgetState.UNPLAYED
 
         song_number = media.get_track_number()
         if song_number == 0:
@@ -183,7 +185,7 @@ class SongWidget(Gtk.EventBox):
         """State of the widget
 
         :returns: Widget state
-        :rtype: SongWidget.State
+        :rtype: WidgetState
         """
         return self._state
 
@@ -194,7 +196,7 @@ class SongWidget(Gtk.EventBox):
         This influences the look of the widgets label and if there is a
         song play indicator being shown.
 
-        :param SongWidget.State value: Widget state
+        :param WidgetState value: Widget state
         """
         self._state = value
 
@@ -204,8 +206,84 @@ class SongWidget(Gtk.EventBox):
         style_ctx.remove_class('playing-song-label')
         self._play_icon.set_visible(False)
 
-        if value == SongWidget.State.PLAYED:
+        if value == WidgetState.PLAYED:
             style_ctx.add_class('dim-label')
-        elif value == SongWidget.State.PLAYING:
+        elif value == WidgetState.PLAYING:
             self._play_icon.set_visible(True)
             style_ctx.add_class('playing-song-label')
+
+
+@Gtk.Template(resource_path='/org/gnome/Music/ui/TwoLineWidget.ui')
+class TwoLineWidget(Gtk.ListBoxRow):
+
+    __gtype_name__ = 'TwoLineWidget'
+
+    _cover_stack = Gtk.Template.Child()
+    _main_label = Gtk.Template.Child()
+    _play_icon = Gtk.Template.Child()
+    _secondary_label = Gtk.Template.Child()
+
+    def __repr__(self):
+        return '<TwoLineWidget>'
+
+    @log
+    def __init__(self, song, state):
+        super().__init__()
+
+        self.update(song, state)
+
+    @log
+    def update(self, song, state):
+        self._song_id = song.get_id()
+
+        self._cover_stack.props.size = Art.Size.SMALL
+        self._cover_stack.update(song)
+
+        self._main_label.props.label = utils.get_media_title(song)
+        self._secondary_label.props.label = utils.get_artist_name(song)
+
+        self.props.state = state
+
+    @GObject.Property(type=int)
+    def state(self):
+        """State of the widget
+
+        :returns: Widget state
+        :rtype: WidgetState
+        """
+        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 WidgetState value: Widget state
+        """
+        self._state = value
+
+        main_style_ctx = self._main_label.get_style_context()
+        secondary_style_ctx = self._secondary_label.get_style_context()
+
+        main_style_ctx.remove_class('dim-label')
+        main_style_ctx.remove_class('playing-song-label')
+        secondary_style_ctx.remove_class('dim-label')
+        self._play_icon.props.icon_name = ''
+
+        if value == WidgetState.PLAYED:
+            main_style_ctx.add_class('dim-label')
+            secondary_style_ctx.add_class('dim-label')
+        elif value == WidgetState.PLAYING:
+            self._play_icon.props.icon_name = 'media-playback-start-symbolic'
+            main_style_ctx.add_class('playing-song-label')
+
+    @GObject.Property(type=int, flags=GObject.ParamFlags.READABLE)
+    def song_id(self):
+        """Song id getter
+
+        :returns: the song id
+        :rtype: int
+        """
+        return self._song_id


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]