tracklist: Create TrackRow, InscriptionRow, and TrackString widgets
The TrackRow widget is used to bind Tracks to a generic Widget. The InscriptionRow builds on this to create a Gtk.Inscription that can be used in derived classes. Finally, the TrackString widget implements binding a string Track property directly to the Inscription. I use these widgets to create a Title and Artist column in the TrackView. Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
parent
999a3eb523
commit
9edfc4a5b0
|
@ -160,6 +160,12 @@ class Application(Adw.Application):
|
||||||
def build_tracklist(self) -> tracklist.Card:
|
def build_tracklist(self) -> tracklist.Card:
|
||||||
"""Build a new tracklist card."""
|
"""Build a new tracklist card."""
|
||||||
track_list = tracklist.Card(sql=self.db)
|
track_list = tracklist.Card(sql=self.db)
|
||||||
|
for column in track_list.columns:
|
||||||
|
name = column.get_title().lower().replace(" ", "-")
|
||||||
|
self.db.settings.bind_setting(f"tracklist.{name}.size",
|
||||||
|
column, "fixed-width")
|
||||||
|
self.db.settings.bind_setting(f"tracklist.{name}.visible",
|
||||||
|
column, "visible")
|
||||||
self.factory.bind_property("visible-playlist", track_list, "playlist")
|
self.factory.bind_property("visible-playlist", track_list, "playlist")
|
||||||
return track_list
|
return track_list
|
||||||
|
|
||||||
|
|
|
@ -69,3 +69,12 @@ button.emmental-delete>image {
|
||||||
button.emmental-stop>image {
|
button.emmental-stop>image {
|
||||||
color: @red_3;
|
color: @red_3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
columnview.emmental-track-list > listview > row > cell {
|
||||||
|
padding: 0px 2px;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
columnview.emmental-track-list > listview > row > cell > label {
|
||||||
|
padding: 0px 4px;
|
||||||
|
}
|
||||||
|
|
|
@ -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 Gio
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
from .. import db
|
from .. import db
|
||||||
from .. import entry
|
from .. import entry
|
||||||
|
@ -37,3 +38,8 @@ 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())
|
||||||
|
|
||||||
|
@GObject.Property(type=Gio.ListModel)
|
||||||
|
def columns(self) -> Gio.ListModel:
|
||||||
|
"""Get the columns displayed in the Tracklist."""
|
||||||
|
return self._trackview.columns
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
# Copyright 2023 (c) Anna Schumaker.
|
||||||
|
"""Widgets for displaying Track information in the TrackView."""
|
||||||
|
from gi.repository import GObject
|
||||||
|
from gi.repository import Gtk
|
||||||
|
from .. import factory
|
||||||
|
|
||||||
|
|
||||||
|
class TrackRow(factory.ListRow):
|
||||||
|
"""Base class for Track Row widgets."""
|
||||||
|
|
||||||
|
property = GObject.Property(type=str)
|
||||||
|
|
||||||
|
def __init__(self, listitem: Gtk.ListItem, property: str):
|
||||||
|
"""Initialize a TrackRow."""
|
||||||
|
super().__init__(listitem, property=property)
|
||||||
|
|
||||||
|
def do_bind(self) -> None:
|
||||||
|
"""Bind a Track to this Row."""
|
||||||
|
super().do_bind()
|
||||||
|
library = self.item.get_library()
|
||||||
|
self.bind_and_set(library, "online", self, "online")
|
||||||
|
self.bind_active("active")
|
||||||
|
|
||||||
|
@GObject.Property(type=bool, default=False)
|
||||||
|
def active(self) -> bool:
|
||||||
|
"""Get the active state of this Row."""
|
||||||
|
if parent := self.listitem.get_child().get_parent():
|
||||||
|
if parent := parent.get_parent():
|
||||||
|
return parent.get_state_flags() & Gtk.StateFlags.CHECKED
|
||||||
|
return False
|
||||||
|
|
||||||
|
@active.setter
|
||||||
|
def active(self, newval: bool) -> None:
|
||||||
|
if parent := self.listitem.get_child().get_parent():
|
||||||
|
if parent := parent.get_parent():
|
||||||
|
if newval:
|
||||||
|
parent.set_state_flags(Gtk.StateFlags.CHECKED, False)
|
||||||
|
else:
|
||||||
|
parent.unset_state_flags(Gtk.StateFlags.CHECKED)
|
||||||
|
|
||||||
|
@GObject.Property(type=bool, default=True)
|
||||||
|
def online(self) -> bool:
|
||||||
|
"""Get the online state of this Row."""
|
||||||
|
return self.listitem.get_activatable()
|
||||||
|
|
||||||
|
@online.setter
|
||||||
|
def online(self, newval: bool) -> None:
|
||||||
|
self.listitem.set_activatable(newval)
|
||||||
|
self.child.set_sensitive(newval)
|
||||||
|
|
||||||
|
|
||||||
|
class InscriptionRow(TrackRow):
|
||||||
|
"""Base class for Track Rows displaying a Gtk.Inscription."""
|
||||||
|
|
||||||
|
def __init__(self, listitem: Gtk.ListItem, property: str):
|
||||||
|
"""Initialize a LabelRow."""
|
||||||
|
super().__init__(listitem, property)
|
||||||
|
self.child = Gtk.Inscription(xalign=0.0)
|
||||||
|
self.child.bind_property("text", self.child, "tooltip-text")
|
||||||
|
|
||||||
|
|
||||||
|
class TrackString(InscriptionRow):
|
||||||
|
"""An InscriptionRow displaying a string Track property."""
|
||||||
|
|
||||||
|
def do_bind(self) -> None:
|
||||||
|
"""Bind a track string to the Track Inscription."""
|
||||||
|
super().do_bind()
|
||||||
|
self.bind_and_set_property(self.property, "text")
|
|
@ -1,9 +1,12 @@
|
||||||
# Copyright 2022 (c) Anna Schumaker.
|
# Copyright 2022 (c) Anna Schumaker.
|
||||||
"""A Gtk.ColumnView for displaying Tracks."""
|
"""A Gtk.ColumnView for displaying Tracks."""
|
||||||
from gi.repository import GObject
|
from gi.repository import GObject
|
||||||
|
from gi.repository import Gio
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
from .. import db
|
from .. import db
|
||||||
|
from .. import factory
|
||||||
from .. import playlist
|
from .. import playlist
|
||||||
|
from . import row
|
||||||
|
|
||||||
|
|
||||||
class TrackView(Gtk.Frame):
|
class TrackView(Gtk.Frame):
|
||||||
|
@ -25,6 +28,9 @@ class TrackView(Gtk.Frame):
|
||||||
model=self._selection)
|
model=self._selection)
|
||||||
self._scrollwin = Gtk.ScrolledWindow(child=self._columnview)
|
self._scrollwin = Gtk.ScrolledWindow(child=self._columnview)
|
||||||
|
|
||||||
|
self.__append_column("Title", "title", row.TrackString, width=300)
|
||||||
|
self.__append_column("Artist", "artist", row.TrackString, width=250)
|
||||||
|
|
||||||
self.bind_property("playlist", self._filtermodel, "model")
|
self.bind_property("playlist", self._filtermodel, "model")
|
||||||
self._selection.bind_property("n-items", self, "n-tracks")
|
self._selection.bind_property("n-items", self, "n-tracks")
|
||||||
|
|
||||||
|
@ -33,6 +39,18 @@ class TrackView(Gtk.Frame):
|
||||||
|
|
||||||
self.set_child(self._scrollwin)
|
self.set_child(self._scrollwin)
|
||||||
|
|
||||||
|
def __append_column(self, title: str, property: str, row_type: type,
|
||||||
|
*, width: int = -1, visible: bool = True) -> None:
|
||||||
|
fctry = factory.Factory(row_type=row_type, property=property)
|
||||||
|
col = Gtk.ColumnViewColumn(title=title, factory=fctry, visible=visible,
|
||||||
|
resizable=True, fixed_width=width)
|
||||||
|
self._columnview.append_column(col)
|
||||||
|
|
||||||
def __runtime_changed(self, selection: Gtk.MultiSelection,
|
def __runtime_changed(self, selection: Gtk.MultiSelection,
|
||||||
position: int, removed: int, added: int) -> None:
|
position: int, removed: int, added: int) -> None:
|
||||||
self.runtime = sum(t.length for t in self._selection)
|
self.runtime = sum(t.length for t in self._selection)
|
||||||
|
|
||||||
|
@GObject.Property(type=Gio.ListModel)
|
||||||
|
def columns(self) -> Gio.ListModel:
|
||||||
|
"""Get the ListModel for the columns."""
|
||||||
|
return self._columnview.get_columns()
|
||||||
|
|
|
@ -118,3 +118,25 @@ class TestSettings(unittest.TestCase):
|
||||||
self.assertTrue(self.settings["sidebar.artists.show-all"])
|
self.assertTrue(self.settings["sidebar.artists.show-all"])
|
||||||
|
|
||||||
self.assertTrue(self.app.build_window().sidebar.show_all_artists)
|
self.assertTrue(self.app.build_window().sidebar.show_all_artists)
|
||||||
|
|
||||||
|
def test_save_tracklist_column_width(self, mock_stdout: io.StringIO):
|
||||||
|
"""Test saving tracklist column widths."""
|
||||||
|
self.assertEqual(self.settings["tracklist.title.size"], 300)
|
||||||
|
self.assertEqual(self.settings["tracklist.artist.size"], 250)
|
||||||
|
|
||||||
|
for column in self.win.tracklist.columns:
|
||||||
|
column.set_fixed_width(123)
|
||||||
|
|
||||||
|
self.assertEqual(self.settings["tracklist.title.size"], 123)
|
||||||
|
self.assertEqual(self.settings["tracklist.artist.size"], 123)
|
||||||
|
|
||||||
|
def test_save_tracklist_column_visible(self, mock_stdout: io.StringIO):
|
||||||
|
"""Test saving tracklist column visibility."""
|
||||||
|
self.assertTrue(self.settings["tracklist.title.visible"])
|
||||||
|
self.assertTrue(self.settings["tracklist.artist.visible"])
|
||||||
|
|
||||||
|
for column in self.win.tracklist.columns:
|
||||||
|
column.set_visible(not column.get_visible())
|
||||||
|
|
||||||
|
self.assertFalse(self.settings["tracklist.title.visible"])
|
||||||
|
self.assertFalse(self.settings["tracklist.artist.visible"])
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
# Copyright 2023 (c) Anna Schumaker.
|
||||||
|
"""Tests our TrackView Row widgets."""
|
||||||
|
import pathlib
|
||||||
|
import emmental.factory
|
||||||
|
import emmental.tracklist.row
|
||||||
|
import tests.util
|
||||||
|
import unittest.mock
|
||||||
|
from gi.repository import Gtk
|
||||||
|
from gi.repository import Adw
|
||||||
|
|
||||||
|
|
||||||
|
class TestTrackRowWidgets(tests.util.TestCase):
|
||||||
|
"""Tests our Track Row widgets."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Set up common variables."""
|
||||||
|
super().setUp()
|
||||||
|
self.sql.playlists.load()
|
||||||
|
self.library = self.sql.libraries.create(pathlib.Path("/a/b"))
|
||||||
|
self.album = self.sql.albums.create("Test Album", "Artist", "2023")
|
||||||
|
self.medium = self.sql.media.create(self.album, "", number=1)
|
||||||
|
self.year = self.sql.years.create(2023)
|
||||||
|
self.track = self.sql.tracks.create(self.library,
|
||||||
|
pathlib.Path("/a/b/1.ogg"),
|
||||||
|
self.medium, self.year,
|
||||||
|
title="Test Title")
|
||||||
|
|
||||||
|
self.listitem = Gtk.ListItem()
|
||||||
|
self.listitem.get_item = unittest.mock.Mock(return_value=self.track)
|
||||||
|
|
||||||
|
self.columnrow = Adw.Bin()
|
||||||
|
self.listrow = Adw.Bin(child=self.columnrow)
|
||||||
|
|
||||||
|
def test_track_row(self):
|
||||||
|
"""Test the base Track Row."""
|
||||||
|
row = emmental.tracklist.row.TrackRow(self.listitem, "property")
|
||||||
|
self.assertIsInstance(row, emmental.factory.ListRow)
|
||||||
|
self.assertEqual(row.property, "property")
|
||||||
|
|
||||||
|
row.child = Gtk.Label()
|
||||||
|
self.columnrow.set_child(row.child)
|
||||||
|
self.library.online = False
|
||||||
|
self.track.active = True
|
||||||
|
|
||||||
|
row.bind()
|
||||||
|
self.assertFalse(row.online)
|
||||||
|
self.assertFalse(self.listitem.get_activatable())
|
||||||
|
self.assertFalse(row.child.get_sensitive())
|
||||||
|
self.assertTrue(row.active)
|
||||||
|
self.assertTrue(self.listrow.get_state_flags() &
|
||||||
|
Gtk.StateFlags.CHECKED)
|
||||||
|
|
||||||
|
self.library.online = True
|
||||||
|
self.track.active = False
|
||||||
|
self.assertTrue(row.online)
|
||||||
|
self.assertTrue(self.listitem.get_activatable())
|
||||||
|
self.assertTrue(row.child.get_sensitive())
|
||||||
|
self.assertFalse(row.active)
|
||||||
|
self.assertFalse(self.listrow.get_state_flags() &
|
||||||
|
Gtk.StateFlags.CHECKED)
|
||||||
|
|
||||||
|
def test_inscription_row(self):
|
||||||
|
"""Test the base Inscription Row."""
|
||||||
|
row = emmental.tracklist.row.InscriptionRow(self.listitem, "property")
|
||||||
|
self.assertIsInstance(row, emmental.tracklist.row.TrackRow)
|
||||||
|
self.assertIsInstance(row.child, Gtk.Inscription)
|
||||||
|
self.assertEqual(row.child.get_xalign(), 0.0)
|
||||||
|
|
||||||
|
row.child.set_text("Test Text")
|
||||||
|
self.assertEqual(row.child.get_tooltip_text(), "Test Text")
|
||||||
|
|
||||||
|
def test_track_string(self):
|
||||||
|
"""Test the Track String widget."""
|
||||||
|
row = emmental.tracklist.row.TrackString(self.listitem, "title")
|
||||||
|
self.assertIsInstance(row, emmental.tracklist.row.InscriptionRow)
|
||||||
|
self.assertEqual(row.property, "title")
|
||||||
|
|
||||||
|
row.bind()
|
||||||
|
self.assertEqual(row.child.get_text(), "Test Title")
|
||||||
|
self.track.title = "New Title"
|
||||||
|
self.assertEqual(row.child.get_text(), "New Title")
|
|
@ -65,6 +65,8 @@ class TestTracklist(tests.util.TestCase):
|
||||||
|
|
||||||
self.assertEqual(self.tracklist._trackview.get_margin_start(), 6)
|
self.assertEqual(self.tracklist._trackview.get_margin_start(), 6)
|
||||||
self.assertEqual(self.tracklist._trackview.get_margin_end(), 6)
|
self.assertEqual(self.tracklist._trackview.get_margin_end(), 6)
|
||||||
|
self.assertEqual(self.tracklist.columns,
|
||||||
|
self.tracklist._trackview.columns)
|
||||||
|
|
||||||
def test_playlist(self):
|
def test_playlist(self):
|
||||||
"""Test the playlist property."""
|
"""Test the playlist property."""
|
||||||
|
|
|
@ -51,6 +51,8 @@ class TestTrackView(tests.util.TestCase):
|
||||||
self.assertIsInstance(self.trackview._columnview, Gtk.ColumnView)
|
self.assertIsInstance(self.trackview._columnview, Gtk.ColumnView)
|
||||||
self.assertEqual(self.trackview._columnview.get_model(),
|
self.assertEqual(self.trackview._columnview.get_model(),
|
||||||
self.trackview._selection)
|
self.trackview._selection)
|
||||||
|
self.assertEqual(self.trackview._columnview.get_columns(),
|
||||||
|
self.trackview.columns)
|
||||||
|
|
||||||
self.assertTrue(self.trackview._columnview.get_hexpand())
|
self.assertTrue(self.trackview._columnview.get_hexpand())
|
||||||
self.assertTrue(self.trackview._columnview.get_vexpand())
|
self.assertTrue(self.trackview._columnview.get_vexpand())
|
||||||
|
@ -79,3 +81,40 @@ class TestTrackView(tests.util.TestCase):
|
||||||
self.trackview._selection.set_model(self.playlist)
|
self.trackview._selection.set_model(self.playlist)
|
||||||
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)
|
||||||
|
|
||||||
|
|
||||||
|
class TestTrackViewColumns(tests.util.TestCase):
|
||||||
|
"""Test the TrackView Columns."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Set up common variables."""
|
||||||
|
super().setUp()
|
||||||
|
self.trackview = emmental.tracklist.trackview.TrackView(self.sql)
|
||||||
|
self.columns = self.trackview.columns
|
||||||
|
self.listitem = Gtk.ListItem()
|
||||||
|
|
||||||
|
def test_title_column(self, i: int = 0):
|
||||||
|
"""Test the title column."""
|
||||||
|
self.assertEqual(self.columns[i].get_title(), "Title")
|
||||||
|
self.assertEqual(self.columns[i].get_fixed_width(), 300)
|
||||||
|
self.assertTrue(self.columns[i].get_resizable())
|
||||||
|
self.assertTrue(self.columns[i].get_visible())
|
||||||
|
|
||||||
|
factory = self.columns[i].get_factory()
|
||||||
|
self.assertIsInstance(factory, emmental.factory.Factory)
|
||||||
|
self.assertEqual(factory.row_type, emmental.tracklist.row.TrackString)
|
||||||
|
factory.emit("setup", self.listitem)
|
||||||
|
self.assertEqual(self.listitem.listrow.property, "title")
|
||||||
|
|
||||||
|
def test_artist_column(self, i: int = 1):
|
||||||
|
"""Test the artist column."""
|
||||||
|
self.assertEqual(self.columns[i].get_title(), "Artist")
|
||||||
|
self.assertEqual(self.columns[i].get_fixed_width(), 250)
|
||||||
|
self.assertTrue(self.columns[i].get_resizable())
|
||||||
|
self.assertTrue(self.columns[i].get_visible())
|
||||||
|
|
||||||
|
factory = self.columns[i].get_factory()
|
||||||
|
self.assertIsInstance(factory, emmental.factory.Factory)
|
||||||
|
self.assertEqual(factory.row_type, emmental.tracklist.row.TrackString)
|
||||||
|
factory.emit("setup", self.listitem)
|
||||||
|
self.assertEqual(self.listitem.listrow.property, "artist")
|
||||||
|
|
Loading…
Reference in New Issue