tracklist: Add a Selection OSD to the TrackView

The OSD will eventually contain buttons for modifying playlists, but for
now it just has functions and properties for mananging the current track
selection.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2023-01-14 15:17:51 -05:00
parent a485a3806b
commit ff1d772a05
4 changed files with 117 additions and 2 deletions

View File

@ -0,0 +1,31 @@
# Copyright 2023 (c) Anna Schumaker.
"""An OSD to show when tracks are selected."""
from gi.repository import GObject
from gi.repository import Gtk
from .. import playlist
class OSD(Gtk.Overlay):
"""An Overlay with extra controls for the Tracklist."""
playlist = GObject.Property(type=playlist.playlist.Playlist)
selection = GObject.Property(type=Gtk.SelectionModel)
have_selected = GObject.Property(type=bool, default=False)
n_selected = GObject.Property(type=int)
def __init__(self, selection: Gtk.SelectionModel, **kwargs):
"""Initialize an OSD."""
super().__init__(selection=selection, **kwargs)
self.selection.connect("selection-changed", self.__selection_changed)
def __selection_changed(self, selection: Gtk.SelectionModel,
position: int, n_items: int) -> None:
self.n_selected = selection.get_selection().get_size()
self.have_selected = self.n_selected > 0
def clear_selection(self, *args) -> None:
"""Clear the current selection."""
self.selection.unselect_all()
self.__selection_changed(self.selection, 0, 0)

View File

@ -7,6 +7,7 @@ from .. import db
from .. import factory
from .. import playlist
from . import row
from . import selection
class TrackView(Gtk.Frame):
@ -16,6 +17,9 @@ class TrackView(Gtk.Frame):
n_tracks = GObject.Property(type=int)
runtime = GObject.Property(type=float)
n_selected = GObject.Property(type=int)
have_selected = GObject.Property(type=bool, default=False)
def __init__(self, sql: db.Connection, **kwargs):
"""Initialize a TrackView."""
super().__init__(**kwargs)
@ -27,6 +31,7 @@ class TrackView(Gtk.Frame):
enable_rubberband=True,
model=self._selection)
self._scrollwin = Gtk.ScrolledWindow(child=self._columnview)
self._osd = selection.OSD(self._selection, child=self._scrollwin)
self.__append_column("Art", "cover", row.AlbumCover, resizable=False)
self.__append_column("Fav", "favorite", row.FavoriteButton,
@ -52,13 +57,16 @@ class TrackView(Gtk.Frame):
self.__append_column("Filepath", "path", row.PathString, visible=False)
self.bind_property("playlist", self._filtermodel, "model")
self.bind_property("playlist", self._osd, "playlist")
self._osd.bind_property("have-selected", self, "have-selected")
self._osd.bind_property("n-selected", self, "n-selected")
self._selection.bind_property("n-items", self, "n-tracks")
self._selection.connect("items-changed", self.__runtime_changed)
self._columnview.connect("activate", self.__track_activated)
self._columnview.add_css_class("emmental-track-list")
self.set_child(self._scrollwin)
self.set_child(self._osd)
def __append_column(self, title: str, property: str, row_type: type,
*, width: int = -1, visible: bool = True,
@ -87,6 +95,10 @@ class TrackView(Gtk.Frame):
pos = max(i - 3, 0) * adjustment.get_upper()
adjustment.set_value(pos / self._selection.get_n_items())
def clear_selected_tracks(self) -> None:
"""Clear the currently selected tracks."""
self._osd.clear_selection()
@GObject.Property(type=Gio.ListModel)
def columns(self) -> Gio.ListModel:
"""Get the ListModel for the columns."""

View File

@ -0,0 +1,49 @@
# Copyright 2023 (c) Anna Schumaker.
"""Tests our Tracklist Selection OSD."""
import emmental.buttons
import emmental.tracklist.selection
import tests.util
from gi.repository import Gtk
class TestOsd(tests.util.TestCase):
"""Test the Tracklist OSD."""
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.model = Gtk.StringList.new(["Test", "OSD", "Strings"])
self.selection = Gtk.MultiSelection(model=self.model)
self.osd = emmental.tracklist.selection.OSD(self.selection)
def test_init(self):
"""Test that the OSD is set up properly."""
self.assertIsInstance(self.osd, Gtk.Overlay)
self.assertEqual(self.osd.selection, self.selection)
self.assertIsNone(self.osd.playlist)
def test_selection_properties(self):
"""Test updating properties when the selection changes."""
self.assertFalse(self.osd.have_selected)
self.assertEqual(self.osd.n_selected, 0)
self.selection.select_item(1, True)
self.assertTrue(self.osd.have_selected)
self.assertEqual(self.osd.n_selected, 1)
def test_clear_selection(self):
"""Test the clear_selection() function."""
self.selection.select_item(1, True)
self.osd.clear_selection()
self.assertEqual(self.selection.get_selection().get_size(), 0)
self.assertEqual(self.osd.n_selected, 0)
self.assertFalse(self.osd.have_selected)
self.osd.playlist = self.playlist
self.assertEqual(self.selection.get_selection().get_size(), 0)
self.assertEqual(self.osd.n_selected, 0)
self.assertFalse(self.osd.have_selected)

View File

@ -30,8 +30,12 @@ class TestTrackView(tests.util.TestCase):
"""Test that the TrackView is initialized properly."""
self.assertIsInstance(self.trackview, Gtk.Frame)
self.assertIsInstance(self.trackview._scrollwin, Gtk.ScrolledWindow)
self.assertIsInstance(self.trackview._osd,
emmental.tracklist.selection.OSD)
self.assertEqual(self.trackview.get_child(), self.trackview._scrollwin)
self.assertEqual(self.trackview.get_child(), self.trackview._osd)
self.assertEqual(self.trackview._osd.get_child(),
self.trackview._scrollwin)
self.assertEqual(self.trackview._scrollwin.get_child(),
self.trackview._columnview)
@ -76,12 +80,20 @@ class TestTrackView(tests.util.TestCase):
requested.assert_called_with(self.playlist, self.track)
mock_unselect.assert_called()
def test_clear_selected_tracks(self):
"""Test the clear_selected_tracks() function."""
with unittest.mock.patch.object(self.trackview._osd,
"clear_selection") as mock_clear:
self.trackview.clear_selected_tracks()
mock_clear.assert_called()
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)
self.assertEqual(self.trackview._osd.playlist, self.playlist)
def test_n_tracks(self):
"""Test the n-tracks property."""
@ -97,6 +109,17 @@ class TestTrackView(tests.util.TestCase):
self.db_plist.add_track(self.track)
self.assertEqual(self.trackview.runtime, 10.0)
def test_n_selected(self):
"""Test the n-selected and have-selected properties."""
self.assertEqual(self.trackview.n_selected, 0)
self.assertFalse(self.trackview.have_selected)
self.trackview._osd.n_selected = 4
self.trackview._osd.have_selected = True
self.assertEqual(self.trackview.n_selected, 4)
self.assertTrue(self.trackview.have_selected)
class TestTrackViewColumns(tests.util.TestCase):
"""Test the TrackView Columns."""