From 2c2462c3d612d3ad6cd8d92a9acf9bfdbef4ca3b Mon Sep 17 00:00:00 2001 From: Anna Schumaker Date: Fri, 9 Sep 2022 09:48:42 -0400 Subject: [PATCH] tracklist: Create a basic Tracklist widget For now it only has the Gtk.CenterBox child with an entry.Filter widget, but this will be expanded on in future patches. I also take the chance to bind the factory:visible-playlist property to the playlist displayed in the tracklist. Signed-off-by: Anna Schumaker --- emmental/__init__.py | 10 ++++- emmental/tracklist/__init__.py | 33 ++++++++++++++++ tests/test_emmental.py | 17 ++++++++ tests/tracklist/test_tracklist.py | 64 +++++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 emmental/tracklist/__init__.py create mode 100644 tests/tracklist/test_tracklist.py diff --git a/emmental/__init__.py b/emmental/__init__.py index 03e9420..8e80259 100644 --- a/emmental/__init__.py +++ b/emmental/__init__.py @@ -11,6 +11,7 @@ from . import nowplaying from . import options from . import playlist from . import sidebar +from . import tracklist from . import window from gi.repository import GObject from gi.repository import GLib @@ -156,12 +157,19 @@ class Application(Adw.Application): "show-all-artists") return side_bar + def build_tracklist(self) -> tracklist.Card: + """Build a new tracklist card.""" + track_list = tracklist.Card(sql=self.db) + self.factory.bind_property("visible-playlist", track_list, "playlist") + return track_list + def build_window(self) -> window.Window: """Build a new window instance.""" win = window.Window(VERSION_STRING, header=self.build_header(), now_playing=self.build_now_playing(), - sidebar=self.build_sidebar()) + sidebar=self.build_sidebar(), + tracklist=self.build_tracklist()) for (setting, property) in [("window.width", "default-width"), ("window.height", "default-height"), diff --git a/emmental/tracklist/__init__.py b/emmental/tracklist/__init__.py new file mode 100644 index 0000000..1261897 --- /dev/null +++ b/emmental/tracklist/__init__.py @@ -0,0 +1,33 @@ +# Copyright 2022 (c) Anna Schumaker. +"""A card for displaying a list of tracks.""" +from gi.repository import GObject +from gi.repository import Gtk +from .. import db +from .. import entry +from .. import playlist + + +class Card(Gtk.Box): + """Our Tracklist.""" + + sql = GObject.Property(type=db.Connection) + playlist = GObject.Property(type=playlist.playlist.Playlist) + + def __init__(self, sql: db.Connection, **kwargs): + """Set up the Tracklist widget.""" + super().__init__(sql=sql, orientation=Gtk.Orientation.VERTICAL, + spacing=6, **kwargs) + self._top_box = Gtk.CenterBox(margin_top=6, margin_start=6, + margin_end=6) + self._filter = entry.Filter("tracks", hexpand=True, + margin_start=100, margin_end=100) + + self._top_box.set_center_widget(self._filter) + self.append(self._top_box) + + self._filter.connect("search-changed", self.__search_changed) + + self.add_css_class("card") + + def __search_changed(self, filter: entry.Filter) -> None: + self.sql.tracks.filter(filter.get_query()) diff --git a/tests/test_emmental.py b/tests/test_emmental.py index 83bb0a2..a71ae49 100644 --- a/tests/test_emmental.py +++ b/tests/test_emmental.py @@ -109,6 +109,7 @@ class TestEmmental(unittest.TestCase): self.assertIsInstance(win.header, emmental.header.Header) self.assertIsInstance(win.now_playing, emmental.nowplaying.Card) self.assertIsInstance(win.sidebar, emmental.sidebar.Card) + self.assertIsInstance(win.tracklist, emmental.tracklist.Card) self.assertEqual(win.header.title, emmental.VERSION_STRING) @@ -168,6 +169,22 @@ class TestEmmental(unittest.TestCase): self.assertEqual(win.sidebar.sql, self.application.db) + @unittest.mock.patch("sys.stdout", new_callable=io.StringIO) + def test_tracklist(self, new_callable: io.StringIO): + """Check that the tracklist widget is wired up properly.""" + self.application.db = emmental.db.Connection() + self.application.factory = emmental.playlist.Factory( + self.application.db) + self.application.player = emmental.audio.Player() + win = self.application.build_window() + + self.assertEqual(win.tracklist.sql, self.application.db) + + playlist = self.application.db.playlists.create("Test Playlist") + self.application.factory.db_visible = playlist + self.assertEqual(win.tracklist.playlist, + self.application.factory.visible_playlist) + @unittest.mock.patch("sys.stdout", new_callable=io.StringIO) def test_playlist_factory(self, mock_stdout: io.StringIO): """Test that the Playlist Factory is wired up properly.""" diff --git a/tests/tracklist/test_tracklist.py b/tests/tracklist/test_tracklist.py new file mode 100644 index 0000000..347791d --- /dev/null +++ b/tests/tracklist/test_tracklist.py @@ -0,0 +1,64 @@ +# Copyright 2022 (c) Anna Schumaker. +"""Tests our Tracklist card.""" +import unittest.mock +import emmental.tracklist +import tests.util +from gi.repository import Gtk + + +class TestTracklist(tests.util.TestCase): + """Tests the Tracklist card.""" + + def setUp(self): + """Set up common variables.""" + super().setUp() + self.tracklist = emmental.tracklist.Card(self.sql) + self.db_plist = self.sql.playlists.create("Test Playlist") + self.playlist = emmental.playlist.playlist.Playlist(self.sql, + self.db_plist) + + def test_init(self): + """Test that the Tracklist has been set up correctly.""" + self.assertIsInstance(self.tracklist, Gtk.Box) + self.assertIsInstance(self.tracklist._top_box, Gtk.CenterBox) + + self.assertEqual(self.tracklist.sql, self.sql) + self.assertEqual(self.tracklist.get_spacing(), 6) + self.assertEqual(self.tracklist.get_orientation(), + Gtk.Orientation.VERTICAL) + + self.assertEqual(self.tracklist._top_box.get_margin_top(), 6) + self.assertEqual(self.tracklist._top_box.get_margin_start(), 6) + self.assertEqual(self.tracklist._top_box.get_margin_end(), 6) + self.assertEqual(self.tracklist._top_box.get_orientation(), + Gtk.Orientation.HORIZONTAL) + + self.assertEqual(self.tracklist.get_first_child(), + self.tracklist._top_box) + + self.assertTrue(self.tracklist.has_css_class("card")) + + def test_filter(self): + """Test the Tracklist Filter entry.""" + self.assertIsInstance(self.tracklist._filter, emmental.entry.Filter) + self.assertEqual(self.tracklist._top_box.get_center_widget(), + self.tracklist._filter) + + self.assertEqual(self.tracklist._filter.get_margin_start(), 100) + self.assertEqual(self.tracklist._filter.get_margin_end(), 100) + self.assertEqual(self.tracklist._filter.get_placeholder_text(), + "type to filter tracks") + self.assertTrue(self.tracklist._filter.get_hexpand()) + + with unittest.mock.patch.object(self.sql.tracks, + "filter") as mock_filter: + self.tracklist._filter.set_text("test text") + self.tracklist._filter.emit("search-changed") + mock_filter.assert_called_with("*test text*") + + def test_playlist(self): + """Test the playlist property.""" + self.assertIsNone(self.tracklist.playlist) + + self.tracklist.playlist = self.playlist + self.assertEqual(self.tracklist.playlist, self.playlist)