From 999a3eb523ea32f6073384a9a25ced9fdf15be7c Mon Sep 17 00:00:00 2001 From: Anna Schumaker Date: Tue, 18 Oct 2022 16:19:35 -0400 Subject: [PATCH] tracklist: Create a TrackView The TrackView sets up a scrollable Gtk.ColumnView inside a nice looking frame. It also creates a FilterListModel for filtering tracks in a separate layer from the Playlist so we don't affect choosing the next track. Finally, I add the TrackView to the TrackList Card. Signed-off-by: Anna Schumaker --- emmental/tracklist/__init__.py | 6 +++ emmental/tracklist/trackview.py | 38 +++++++++++++++ tests/tracklist/test_tracklist.py | 11 +++++ tests/tracklist/test_trackview.py | 81 +++++++++++++++++++++++++++++++ 4 files changed, 136 insertions(+) create mode 100644 emmental/tracklist/trackview.py create mode 100644 tests/tracklist/test_trackview.py diff --git a/emmental/tracklist/__init__.py b/emmental/tracklist/__init__.py index 1261897..71ee5fa 100644 --- a/emmental/tracklist/__init__.py +++ b/emmental/tracklist/__init__.py @@ -5,6 +5,7 @@ from gi.repository import Gtk from .. import db from .. import entry from .. import playlist +from . import trackview class Card(Gtk.Box): @@ -21,9 +22,14 @@ class Card(Gtk.Box): margin_end=6) self._filter = entry.Filter("tracks", hexpand=True, margin_start=100, margin_end=100) + self._trackview = trackview.TrackView(sql, margin_start=6, + margin_end=6) self._top_box.set_center_widget(self._filter) self.append(self._top_box) + self.append(self._trackview) + + self.bind_property("playlist", self._trackview, "playlist") self._filter.connect("search-changed", self.__search_changed) diff --git a/emmental/tracklist/trackview.py b/emmental/tracklist/trackview.py new file mode 100644 index 0000000..401a4a5 --- /dev/null +++ b/emmental/tracklist/trackview.py @@ -0,0 +1,38 @@ +# Copyright 2022 (c) Anna Schumaker. +"""A Gtk.ColumnView for displaying Tracks.""" +from gi.repository import GObject +from gi.repository import Gtk +from .. import db +from .. import playlist + + +class TrackView(Gtk.Frame): + """A Gtk.ColumnView that has been configured to show Tracks.""" + + playlist = GObject.Property(type=playlist.playlist.Playlist) + n_tracks = GObject.Property(type=int) + runtime = GObject.Property(type=float) + + def __init__(self, sql: db.Connection, **kwargs): + """Initialize a TrackView.""" + super().__init__(**kwargs) + self._filtermodel = Gtk.FilterListModel(filter=sql.tracks.get_filter(), + incremental=True) + self._selection = Gtk.MultiSelection(model=self._filtermodel) + self._columnview = Gtk.ColumnView(hexpand=True, vexpand=True, + show_row_separators=True, + enable_rubberband=True, + model=self._selection) + self._scrollwin = Gtk.ScrolledWindow(child=self._columnview) + + self.bind_property("playlist", self._filtermodel, "model") + self._selection.bind_property("n-items", self, "n-tracks") + + self._selection.connect("items-changed", self.__runtime_changed) + self._columnview.add_css_class("emmental-track-list") + + self.set_child(self._scrollwin) + + def __runtime_changed(self, selection: Gtk.MultiSelection, + position: int, removed: int, added: int) -> None: + self.runtime = sum(t.length for t in self._selection) diff --git a/tests/tracklist/test_tracklist.py b/tests/tracklist/test_tracklist.py index 347791d..c2a6191 100644 --- a/tests/tracklist/test_tracklist.py +++ b/tests/tracklist/test_tracklist.py @@ -56,9 +56,20 @@ class TestTracklist(tests.util.TestCase): self.tracklist._filter.emit("search-changed") mock_filter.assert_called_with("*test text*") + def test_trackview(self): + """Test the Trackview widget.""" + self.assertIsInstance(self.tracklist._trackview, + emmental.tracklist.trackview.TrackView) + self.assertEqual(self.tracklist._top_box.get_next_sibling(), + self.tracklist._trackview) + + self.assertEqual(self.tracklist._trackview.get_margin_start(), 6) + self.assertEqual(self.tracklist._trackview.get_margin_end(), 6) + 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) + self.assertEqual(self.tracklist._trackview.playlist, self.playlist) diff --git a/tests/tracklist/test_trackview.py b/tests/tracklist/test_trackview.py new file mode 100644 index 0000000..01b98b4 --- /dev/null +++ b/tests/tracklist/test_trackview.py @@ -0,0 +1,81 @@ +# Copyright 2022 (c) Anna Schumaker. +"""Tests our Track ColumnView.""" +import pathlib +import emmental.tracklist.trackview +import tests.util +from gi.repository import Gtk + + +class TestTrackView(tests.util.TestCase): + """Tests the TrackView widget.""" + + def setUp(self): + """Set up common variables.""" + super().setUp() + self.db_plist = self.sql.playlists.create("Test Playlist") + self.playlist = emmental.playlist.playlist.Playlist(self.sql, + self.db_plist) + self.trackview = emmental.tracklist.trackview.TrackView(self.sql) + + self.library = self.sql.libraries.create(pathlib.Path("/a/b/")) + self.album = self.sql.albums.create("Test Album", "Artist", "2023") + self.medium = self.sql.media.create(self.album, "", number=1) + self.year = self.sql.years.create(2023) + self.track = self.sql.tracks.create(self.library, + pathlib.Path("/a/b/1.ogg"), + self.medium, self.year, length=10) + + def test_init(self): + """Test that the TrackView is initialized properly.""" + self.assertIsInstance(self.trackview, Gtk.Frame) + self.assertIsInstance(self.trackview._scrollwin, Gtk.ScrolledWindow) + + self.assertEqual(self.trackview.get_child(), self.trackview._scrollwin) + self.assertEqual(self.trackview._scrollwin.get_child(), + self.trackview._columnview) + + def test_list_models(self): + """Test our filter and selection list models.""" + self.assertIsInstance(self.trackview._filtermodel, Gtk.FilterListModel) + self.assertIsInstance(self.trackview._selection, Gtk.MultiSelection) + + self.assertEqual(self.trackview._filtermodel.get_filter(), + self.sql.tracks.get_filter()) + self.assertTrue(self.trackview._filtermodel.get_incremental()) + + self.assertEqual(self.trackview._selection.get_model(), + self.trackview._filtermodel) + + def test_columnview(self): + """Test the columnview.""" + self.assertIsInstance(self.trackview._columnview, Gtk.ColumnView) + self.assertEqual(self.trackview._columnview.get_model(), + self.trackview._selection) + + self.assertTrue(self.trackview._columnview.get_hexpand()) + self.assertTrue(self.trackview._columnview.get_vexpand()) + self.assertTrue(self.trackview._columnview.get_show_row_separators()) + self.assertTrue(self.trackview._columnview.get_enable_rubberband()) + self.assertTrue(self.trackview._columnview.has_css_class( + "emmental-track-list")) + + def test_playlist(self): + """Test the playlist property.""" + self.assertIsNone(self.trackview.playlist) + self.trackview.playlist = self.playlist + self.assertEqual(self.trackview._filtermodel.get_model(), + self.playlist) + + def test_n_tracks(self): + """Test the n-tracks property.""" + self.assertEqual(self.trackview.n_tracks, 0) + self.trackview._selection.set_model(self.playlist) + self.db_plist.add_track(self.track) + self.assertEqual(self.trackview.n_tracks, 1) + + def test_runtime(self): + """Test the runtime property.""" + self.assertEqual(self.trackview.runtime, 0) + self.trackview._selection.set_model(self.playlist) + self.db_plist.add_track(self.track) + self.assertEqual(self.trackview.runtime, 10.0)