diff --git a/emmental/__init__.py b/emmental/__init__.py index c942274..ed3c1ae 100644 --- a/emmental/__init__.py +++ b/emmental/__init__.py @@ -27,6 +27,8 @@ class Application(Adw.Application): player = GObject.Property(type=audio.Player) win = GObject.Property(type=window.Window) + autopause = GObject.Property(type=int, default=-1, minimum=-1, maximum=99) + def __init__(self): """Initialize an Application.""" super().__init__(application_id=gsetup.APPLICATION_ID, @@ -66,11 +68,16 @@ class Application(Adw.Application): def build_now_playing(self) -> nowplaying.Card: """Build a new now playing 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.db.settings.bind_setting("now-playing.prefer-artist", playing, "prefer-artist") + playing.connect("play", self.player.play) + playing.connect("pause", self.player.pause) return playing def build_window(self) -> window.Window: diff --git a/emmental/nowplaying/__init__.py b/emmental/nowplaying/__init__.py index f177360..49c1067 100644 --- a/emmental/nowplaying/__init__.py +++ b/emmental/nowplaying/__init__.py @@ -2,6 +2,7 @@ """A card for displaying information about the currently playing track.""" from gi.repository import GObject from gi.repository import Gtk +from . import controls from . import tags @@ -13,19 +14,53 @@ class Card(Gtk.Box): artist = GObject.Property(type=str) album_artist = GObject.Property(type=str) 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): """Initialize a Now Playing Card.""" super().__init__() self._grid = Gtk.Grid() self._tags = tags.TagInfo() + self._controls = controls.Controls() for prop in ["title", "album", "artist", "album-artist"]: self.bind_property(prop, self._tags, prop) self.bind_property("prefer-artist", self._tags, "prefer-artist", 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._controls, 1, 0, 1, 1) self.append(self._grid) 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.""" diff --git a/tests/nowplaying/test_nowplaying.py b/tests/nowplaying/test_nowplaying.py index abb8ade..c8a5f66 100644 --- a/tests/nowplaying/test_nowplaying.py +++ b/tests/nowplaying/test_nowplaying.py @@ -1,6 +1,7 @@ # Copyright 2022 (c) Anna Schumaker. """Tests our Now Playing card.""" import unittest +import unittest.mock import emmental 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._tags.get_property(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) diff --git a/tests/test_emmental.py b/tests/test_emmental.py index ac8a97c..f47dcba 100644 --- a/tests/test_emmental.py +++ b/tests/test_emmental.py @@ -103,19 +103,39 @@ class TestEmmental(unittest.TestCase): 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) - 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.""" self.application.db = emmental.db.Connection() self.application.player = emmental.audio.Player() 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"]: with self.subTest(tag=tag): self.assertEqual(win.now_playing.get_property(tag), "") self.application.player.set_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) def test_replaygain(self, mock_stdout: io.StringIO): """Test setting replaygain modes."""