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:
parent
a485a3806b
commit
ff1d772a05
|
@ -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)
|
|
@ -7,6 +7,7 @@ from .. import db
|
||||||
from .. import factory
|
from .. import factory
|
||||||
from .. import playlist
|
from .. import playlist
|
||||||
from . import row
|
from . import row
|
||||||
|
from . import selection
|
||||||
|
|
||||||
|
|
||||||
class TrackView(Gtk.Frame):
|
class TrackView(Gtk.Frame):
|
||||||
|
@ -16,6 +17,9 @@ class TrackView(Gtk.Frame):
|
||||||
n_tracks = GObject.Property(type=int)
|
n_tracks = GObject.Property(type=int)
|
||||||
runtime = GObject.Property(type=float)
|
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):
|
def __init__(self, sql: db.Connection, **kwargs):
|
||||||
"""Initialize a TrackView."""
|
"""Initialize a TrackView."""
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
@ -27,6 +31,7 @@ class TrackView(Gtk.Frame):
|
||||||
enable_rubberband=True,
|
enable_rubberband=True,
|
||||||
model=self._selection)
|
model=self._selection)
|
||||||
self._scrollwin = Gtk.ScrolledWindow(child=self._columnview)
|
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("Art", "cover", row.AlbumCover, resizable=False)
|
||||||
self.__append_column("Fav", "favorite", row.FavoriteButton,
|
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.__append_column("Filepath", "path", row.PathString, visible=False)
|
||||||
|
|
||||||
self.bind_property("playlist", self._filtermodel, "model")
|
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.bind_property("n-items", self, "n-tracks")
|
||||||
|
|
||||||
self._selection.connect("items-changed", self.__runtime_changed)
|
self._selection.connect("items-changed", self.__runtime_changed)
|
||||||
self._columnview.connect("activate", self.__track_activated)
|
self._columnview.connect("activate", self.__track_activated)
|
||||||
self._columnview.add_css_class("emmental-track-list")
|
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,
|
def __append_column(self, title: str, property: str, row_type: type,
|
||||||
*, width: int = -1, visible: bool = True,
|
*, width: int = -1, visible: bool = True,
|
||||||
|
@ -87,6 +95,10 @@ class TrackView(Gtk.Frame):
|
||||||
pos = max(i - 3, 0) * adjustment.get_upper()
|
pos = max(i - 3, 0) * adjustment.get_upper()
|
||||||
adjustment.set_value(pos / self._selection.get_n_items())
|
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)
|
@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."""
|
||||||
|
|
|
@ -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)
|
|
@ -30,8 +30,12 @@ class TestTrackView(tests.util.TestCase):
|
||||||
"""Test that the TrackView is initialized properly."""
|
"""Test that the TrackView is initialized properly."""
|
||||||
self.assertIsInstance(self.trackview, Gtk.Frame)
|
self.assertIsInstance(self.trackview, Gtk.Frame)
|
||||||
self.assertIsInstance(self.trackview._scrollwin, Gtk.ScrolledWindow)
|
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.assertEqual(self.trackview._scrollwin.get_child(),
|
||||||
self.trackview._columnview)
|
self.trackview._columnview)
|
||||||
|
|
||||||
|
@ -76,12 +80,20 @@ class TestTrackView(tests.util.TestCase):
|
||||||
requested.assert_called_with(self.playlist, self.track)
|
requested.assert_called_with(self.playlist, self.track)
|
||||||
mock_unselect.assert_called()
|
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):
|
def test_playlist(self):
|
||||||
"""Test the playlist property."""
|
"""Test the playlist property."""
|
||||||
self.assertIsNone(self.trackview.playlist)
|
self.assertIsNone(self.trackview.playlist)
|
||||||
self.trackview.playlist = self.playlist
|
self.trackview.playlist = self.playlist
|
||||||
self.assertEqual(self.trackview._filtermodel.get_model(),
|
self.assertEqual(self.trackview._filtermodel.get_model(),
|
||||||
self.playlist)
|
self.playlist)
|
||||||
|
self.assertEqual(self.trackview._osd.playlist, self.playlist)
|
||||||
|
|
||||||
def test_n_tracks(self):
|
def test_n_tracks(self):
|
||||||
"""Test the n-tracks property."""
|
"""Test the n-tracks property."""
|
||||||
|
@ -97,6 +109,17 @@ class TestTrackView(tests.util.TestCase):
|
||||||
self.db_plist.add_track(self.track)
|
self.db_plist.add_track(self.track)
|
||||||
self.assertEqual(self.trackview.runtime, 10.0)
|
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):
|
class TestTrackViewColumns(tests.util.TestCase):
|
||||||
"""Test the TrackView Columns."""
|
"""Test the TrackView Columns."""
|
||||||
|
|
Loading…
Reference in New Issue