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 <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2023-04-28 13:57:09 -04:00
parent 481c4856c7
commit a485a3806b
4 changed files with 45 additions and 0 deletions

View File

@ -64,12 +64,17 @@ class Application(Adw.Application):
else: else:
track.start() track.start()
self.__set_replaygain(rg_auto=rg_auto) self.__set_replaygain(rg_auto=rg_auto)
self.__on_jump()
def __pick_next_track(self, *, user: bool, gapless: bool = False) -> None: def __pick_next_track(self, *, user: bool, gapless: bool = False) -> None:
(track, rg_auto, restart) = self.factory.next_track(user=user) (track, rg_auto, restart) = self.factory.next_track(user=user)
self.__load_track(track, gapless=gapless, self.__load_track(track, gapless=gapless,
rg_auto=rg_auto, restart=restart) 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: def __on_seek(self, nowplay: nowplaying.Card, newpos: float) -> None:
"""Handle a seek event.""" """Handle a seek event."""
self.player.seek(newpos) self.player.seek(newpos)
@ -114,6 +119,7 @@ class Application(Adw.Application):
self.player.file = track_table.current_track.path self.player.file = track_table.current_track.path
self.player.pause() self.player.pause()
track_table.current_track.start() track_table.current_track.start()
self.__on_jump()
def build_header(self) -> header.Header: def build_header(self) -> header.Header:
"""Build a new header instance.""" """Build a new header instance."""
@ -147,6 +153,7 @@ class Application(Adw.Application):
self.db.settings.bind_setting("now-playing.prefer-artist", self.db.settings.bind_setting("now-playing.prefer-artist",
playing, "prefer-artist") playing, "prefer-artist")
playing.connect("jump", self.__on_jump)
playing.connect("play", self.player.play) playing.connect("play", self.player.play)
playing.connect("pause", self.player.pause) playing.connect("pause", self.player.pause)
playing.connect("seek", self.__on_seek) playing.connect("seek", self.__on_seek)

View File

@ -1,6 +1,7 @@
# Copyright 2022 (c) Anna Schumaker. # Copyright 2022 (c) Anna Schumaker.
"""A card for displaying a list of tracks.""" """A card for displaying a list of tracks."""
from gi.repository import GObject from gi.repository import GObject
from gi.repository import GLib
from gi.repository import Gio from gi.repository import Gio
from gi.repository import Gtk from gi.repository import Gtk
from .. import db from .. import db
@ -39,6 +40,15 @@ class Card(Gtk.Box):
def __search_changed(self, filter: entry.Filter) -> None: def __search_changed(self, filter: entry.Filter) -> None:
self.sql.tracks.filter(filter.get_query()) 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) @GObject.Property(type=Gio.ListModel)
def columns(self) -> Gio.ListModel: def columns(self) -> Gio.ListModel:
"""Get the columns displayed in the Tracklist.""" """Get the columns displayed in the Tracklist."""

View File

@ -77,6 +77,16 @@ class TrackView(Gtk.Frame):
self.playlist.request_track(self._selection[position]) self.playlist.request_track(self._selection[position])
self._selection.unselect_all() 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) @GObject.Property(type=Gio.ListModel)
def columns(self) -> Gio.ListModel: def columns(self) -> Gio.ListModel:
"""Get the ListModel for the columns.""" """Get the ListModel for the columns."""

View File

@ -3,6 +3,7 @@
import unittest.mock import unittest.mock
import emmental.tracklist import emmental.tracklist
import tests.util import tests.util
from gi.repository import GLib
from gi.repository import Gtk from gi.repository import Gtk
@ -75,3 +76,20 @@ class TestTracklist(tests.util.TestCase):
self.tracklist.playlist = self.playlist self.tracklist.playlist = self.playlist
self.assertEqual(self.tracklist.playlist, self.playlist) self.assertEqual(self.tracklist.playlist, self.playlist)
self.assertEqual(self.tracklist._trackview.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)