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:
parent
3157c53423
commit
2ed34d3465
|
@ -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:
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
Loading…
Reference in New Issue