nowplaying: Add Controls to the Now Playing card

And wire up signals between the Now Playing card and the player.

Implements: #45 (Create a new NowPlaying widget)
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2022-07-01 10:30:02 -04:00
parent 3157c53423
commit 2ed34d3465
4 changed files with 105 additions and 2 deletions

View File

@ -27,6 +27,8 @@ class Application(Adw.Application):
player = GObject.Property(type=audio.Player) player = GObject.Property(type=audio.Player)
win = GObject.Property(type=window.Window) win = GObject.Property(type=window.Window)
autopause = GObject.Property(type=int, default=-1, minimum=-1, maximum=99)
def __init__(self): def __init__(self):
"""Initialize an Application.""" """Initialize an Application."""
super().__init__(application_id=gsetup.APPLICATION_ID, super().__init__(application_id=gsetup.APPLICATION_ID,
@ -66,11 +68,16 @@ class Application(Adw.Application):
def build_now_playing(self) -> nowplaying.Card: def build_now_playing(self) -> nowplaying.Card:
"""Build a new now playing card.""" """Build a new now playing card."""
playing = nowplaying.Card() playing = nowplaying.Card()
for prop in ["title", "album", "artist", "album-artist"]: playing.bind_property("autopause", self, "autopause",
GObject.BindingFlags.BIDIRECTIONAL)
for prop in ["title", "album", "artist", "album-artist",
"playing", "have-track"]:
self.player.bind_property(prop, playing, prop) self.player.bind_property(prop, playing, prop)
self.db.settings.bind_setting("now-playing.prefer-artist", self.db.settings.bind_setting("now-playing.prefer-artist",
playing, "prefer-artist") playing, "prefer-artist")
playing.connect("play", self.player.play)
playing.connect("pause", self.player.pause)
return playing return playing
def build_window(self) -> window.Window: def build_window(self) -> window.Window:

View File

@ -2,6 +2,7 @@
"""A card for displaying information about the currently playing track.""" """A card for displaying information about the currently playing track."""
from gi.repository import GObject from gi.repository import GObject
from gi.repository import Gtk from gi.repository import Gtk
from . import controls
from . import tags from . import tags
@ -13,19 +14,53 @@ class Card(Gtk.Box):
artist = GObject.Property(type=str) artist = GObject.Property(type=str)
album_artist = GObject.Property(type=str) album_artist = GObject.Property(type=str)
prefer_artist = GObject.Property(type=bool, default=True) prefer_artist = GObject.Property(type=bool, default=True)
playing = GObject.Property(type=bool, default=False)
autopause = GObject.Property(type=int, default=-1, minimum=-1, maximum=99)
have_next = GObject.Property(type=bool, default=False)
have_previous = GObject.Property(type=bool, default=False)
have_track = GObject.Property(type=bool, default=False)
def __init__(self): def __init__(self):
"""Initialize a Now Playing Card.""" """Initialize a Now Playing Card."""
super().__init__() super().__init__()
self._grid = Gtk.Grid() self._grid = Gtk.Grid()
self._tags = tags.TagInfo() self._tags = tags.TagInfo()
self._controls = controls.Controls()
for prop in ["title", "album", "artist", "album-artist"]: for prop in ["title", "album", "artist", "album-artist"]:
self.bind_property(prop, self._tags, prop) self.bind_property(prop, self._tags, prop)
self.bind_property("prefer-artist", self._tags, "prefer-artist", self.bind_property("prefer-artist", self._tags, "prefer-artist",
GObject.BindingFlags.BIDIRECTIONAL) GObject.BindingFlags.BIDIRECTIONAL)
for prop in ["playing", "have-next", "have-previous", "have-track"]:
self.bind_property(prop, self._controls, prop)
self.bind_property("autopause", self._controls, "autopause",
GObject.BindingFlags.BIDIRECTIONAL)
for sig in ["play", "pause", "previous", "next"]:
self._controls.connect(sig, self.__on_control, sig)
self._grid.attach(self._tags, 0, 0, 1, 1) self._grid.attach(self._tags, 0, 0, 1, 1)
self._grid.attach(self._controls, 1, 0, 1, 1)
self.append(self._grid) self.append(self._grid)
self.add_css_class("card") self.add_css_class("card")
def __on_control(self, controls: controls.Controls, signal: str) -> None:
self.emit(signal)
@GObject.Signal
def play(self) -> None:
"""Signal that the Play button has been clicked."""
@GObject.Signal
def pause(self) -> None:
"""Signal that the Pause button has been clicked."""
@GObject.Signal
def previous(self) -> None:
"""Signal that the Previous button has been clicked."""
@GObject.Signal
def next(self) -> None:
"""Signal that the Nause button has been clicked."""

View File

@ -1,6 +1,7 @@
# Copyright 2022 (c) Anna Schumaker. # Copyright 2022 (c) Anna Schumaker.
"""Tests our Now Playing card.""" """Tests our Now Playing card."""
import unittest import unittest
import unittest.mock
import emmental import emmental
from gi.repository import Gtk from gi.repository import Gtk
@ -44,3 +45,43 @@ class TestNowPlaying(unittest.TestCase):
self.assertEqual(self.card.get_property(tag), f"test {tag}") self.assertEqual(self.card.get_property(tag), f"test {tag}")
self.assertEqual(self.card._tags.get_property(tag), self.assertEqual(self.card._tags.get_property(tag),
f"test {tag}") f"test {tag}")
def test_controls(self):
"""Test the now playing controls."""
self.assertIsInstance(self.card._controls,
emmental.nowplaying.controls.Controls)
self.assertEqual(self.card._grid.get_child_at(1, 0),
self.card._controls)
for signal in ["play", "pause", "previous", "next"]:
with self.subTest(signal=signal):
handler = unittest.mock.Mock()
self.card.connect(signal, handler)
self.card.emit(signal)
handler.assert_called_with(self.card)
def test_playing(self):
"""Test the 'playing' property."""
self.assertFalse(self.card.playing)
self.card.playing = True
self.assertTrue(self.card._controls.playing)
self.card.playing = False
self.assertFalse(self.card._controls.playing)
def test_have_properties(self):
"""Test the 'have-{next, previous, track} properties."""
for property in ["have-next", "have-previous", "have-track"]:
with self.subTest(property=property):
self.assertFalse(self.card.get_property(property))
self.card.set_property(property, True)
self.assertTrue(self.card._controls.get_property(property))
def test_autopause(self):
"""Test the 'autopause' property."""
self.assertEqual(self.card.autopause, -1)
self.card._controls.autopause = 1
self.assertEqual(self.card.autopause, 1)
self.card.autopause = 3
self.assertEqual(self.card.autopause, 3)
self.assertEqual(self.card._controls.autopause, 3)

View File

@ -103,19 +103,39 @@ class TestEmmental(unittest.TestCase):
self.assertEqual(win.header.title, emmental.VERSION_STRING) self.assertEqual(win.header.title, emmental.VERSION_STRING)
@unittest.mock.patch("emmental.audio.Player.pause")
@unittest.mock.patch("emmental.audio.Player.play")
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO) @unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
def test_nowplaying(self, mock_stdout: io.StringIO): def test_nowplaying(self, mock_stdout: io.StringIO,
play_func: unittest.mock.Mock,
pause_func: unittest.mock.Mock):
"""Check that the nowplaying widget is wired up properly.""" """Check that the nowplaying widget is wired up properly."""
self.application.db = emmental.db.Connection() self.application.db = emmental.db.Connection()
self.application.player = emmental.audio.Player() self.application.player = emmental.audio.Player()
win = self.application.build_window() win = self.application.build_window()
for (property, value) in [("have-track", True), ("playing", True)]:
with self.subTest(property=property, value=value):
self.application.player.set_property(property, value)
self.assertEqual(win.now_playing.get_property(property), value)
win.now_playing.emit("play")
play_func.assert_called()
win.now_playing.emit("pause")
pause_func.assert_called()
for tag in ["title", "album", "artist", "album-artist"]: for tag in ["title", "album", "artist", "album-artist"]:
with self.subTest(tag=tag): with self.subTest(tag=tag):
self.assertEqual(win.now_playing.get_property(tag), "") self.assertEqual(win.now_playing.get_property(tag), "")
self.application.player.set_property(tag, "Test Tag") self.application.player.set_property(tag, "Test Tag")
self.assertEqual(win.now_playing.get_property(tag), "Test Tag") self.assertEqual(win.now_playing.get_property(tag), "Test Tag")
self.assertEqual(self.application.autopause, -1)
win.now_playing.autopause = 1
self.assertEqual(self.application.autopause, 1)
self.application.autopause = 2
self.assertEqual(win.now_playing.autopause, 2)
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO) @unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
def test_replaygain(self, mock_stdout: io.StringIO): def test_replaygain(self, mock_stdout: io.StringIO):
"""Test setting replaygain modes.""" """Test setting replaygain modes."""