From a485a3806b78f95c7d2980c50da875e2b194c4f8 Mon Sep 17 00:00:00 2001 From: Anna Schumaker Date: Fri, 28 Apr 2023 13:57:09 -0400 Subject: [PATCH] tracklist: Scroll to the requested Track And wire this up to not only the Now Playing "jump" signal, but also the next track pickers so we scroll when tracks are changed. Signed-off-by: Anna Schumaker --- emmental/__init__.py | 7 +++++++ emmental/tracklist/__init__.py | 10 ++++++++++ emmental/tracklist/trackview.py | 10 ++++++++++ tests/tracklist/test_tracklist.py | 18 ++++++++++++++++++ 4 files changed, 45 insertions(+) diff --git a/emmental/__init__.py b/emmental/__init__.py index a3374eb..0901b3c 100644 --- a/emmental/__init__.py +++ b/emmental/__init__.py @@ -64,12 +64,17 @@ class Application(Adw.Application): else: track.start() self.__set_replaygain(rg_auto=rg_auto) + self.__on_jump() def __pick_next_track(self, *, user: bool, gapless: bool = False) -> None: (track, rg_auto, restart) = self.factory.next_track(user=user) self.__load_track(track, gapless=gapless, rg_auto=rg_auto, restart=restart) + def __on_jump(self, nowplay: nowplaying.Card | None = None) -> None: + """Handle a jump event.""" + self.win.tracklist.scroll_to_track(self.db.tracks.current_track) + def __on_seek(self, nowplay: nowplaying.Card, newpos: float) -> None: """Handle a seek event.""" self.player.seek(newpos) @@ -114,6 +119,7 @@ class Application(Adw.Application): self.player.file = track_table.current_track.path self.player.pause() track_table.current_track.start() + self.__on_jump() def build_header(self) -> header.Header: """Build a new header instance.""" @@ -147,6 +153,7 @@ class Application(Adw.Application): self.db.settings.bind_setting("now-playing.prefer-artist", playing, "prefer-artist") + playing.connect("jump", self.__on_jump) playing.connect("play", self.player.play) playing.connect("pause", self.player.pause) playing.connect("seek", self.__on_seek) diff --git a/emmental/tracklist/__init__.py b/emmental/tracklist/__init__.py index a0094f0..561172c 100644 --- a/emmental/tracklist/__init__.py +++ b/emmental/tracklist/__init__.py @@ -1,6 +1,7 @@ # Copyright 2022 (c) Anna Schumaker. """A card for displaying a list of tracks.""" from gi.repository import GObject +from gi.repository import GLib from gi.repository import Gio from gi.repository import Gtk from .. import db @@ -39,6 +40,15 @@ class Card(Gtk.Box): def __search_changed(self, filter: entry.Filter) -> None: self.sql.tracks.filter(filter.get_query()) + def __scroll_idle(self, track: db.tracks.Track) -> bool: + self._trackview.scroll_to_track(track) + return GLib.SOURCE_REMOVE + + def scroll_to_track(self, track: db.tracks.Track) -> None: + """Scroll to the requested Track.""" + if self.playlist is not None: + GLib.idle_add(self.__scroll_idle, track) + @GObject.Property(type=Gio.ListModel) def columns(self) -> Gio.ListModel: """Get the columns displayed in the Tracklist.""" diff --git a/emmental/tracklist/trackview.py b/emmental/tracklist/trackview.py index 2fe3431..69e33c3 100644 --- a/emmental/tracklist/trackview.py +++ b/emmental/tracklist/trackview.py @@ -77,6 +77,16 @@ class TrackView(Gtk.Frame): self.playlist.request_track(self._selection[position]) self._selection.unselect_all() + def scroll_to_track(self, track: db.tracks.Track) -> None: + """Scroll to the requested Track.""" + # This is a workaround until the ColumnView has better scrolling + # support, which seems to be targeted for Gtk 4.10. + adjustment = self._scrollwin.get_vadjustment() + for (i, t) in enumerate(self._selection): + if t == track: + pos = max(i - 3, 0) * adjustment.get_upper() + adjustment.set_value(pos / self._selection.get_n_items()) + @GObject.Property(type=Gio.ListModel) def columns(self) -> Gio.ListModel: """Get the ListModel for the columns.""" diff --git a/tests/tracklist/test_tracklist.py b/tests/tracklist/test_tracklist.py index efd54ca..66a265f 100644 --- a/tests/tracklist/test_tracklist.py +++ b/tests/tracklist/test_tracklist.py @@ -3,6 +3,7 @@ import unittest.mock import emmental.tracklist import tests.util +from gi.repository import GLib from gi.repository import Gtk @@ -75,3 +76,20 @@ class TestTracklist(tests.util.TestCase): self.tracklist.playlist = self.playlist self.assertEqual(self.tracklist.playlist, self.playlist) self.assertEqual(self.tracklist._trackview.playlist, self.playlist) + + @unittest.mock.patch("gi.repository.GLib.idle_add") + def test_scroll_to_track(self, mock_idle_add: unittest.mock.Mock): + """Test the scroll_to_track() function.""" + self.tracklist.scroll_to_track(None) + mock_idle_add.assert_not_called() + + self.tracklist.playlist = self.playlist + self.tracklist.scroll_to_track(None) + mock_idle_add.assert_called_with(self.tracklist._Card__scroll_idle, + None) + + with unittest.mock.patch.object(self.tracklist._trackview, + "scroll_to_track") as mock_scroll: + self.assertEqual(self.tracklist._Card__scroll_idle(None), + GLib.SOURCE_REMOVE) + mock_scroll.assert_called_with(None)