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)