tracklist: Create an AlbumCover TrackRow
The AlbumCover shows a cover.jpg image for a specific Album in a column. I also need to do some special handling so generate a tooltip to show a larger version of the image. I try to cache the AlbumCover Texture to cut down on disk accesses, since we'll usually end up loading the same image several times for each track in an album. Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
parent
b3dcd3c0b9
commit
97bf9d48db
|
@ -78,3 +78,10 @@ columnview.emmental-track-list > listview > row > cell {
|
|||
columnview.emmental-track-list > listview > row > cell > label {
|
||||
padding: 0px 4px;
|
||||
}
|
||||
|
||||
columnview.emmental-track-list > listview > row > cell > picture {
|
||||
padding: 4px 0px;
|
||||
min-height: 36px;
|
||||
min-width: 36px;
|
||||
border-radius: 15%;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import datetime
|
|||
import dateutil.tz
|
||||
import pathlib
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import Gtk
|
||||
from .. import buttons
|
||||
from .. import factory
|
||||
|
@ -41,15 +42,26 @@ class TrackRow(factory.ListRow):
|
|||
self.album_binding = album.bind_property(self.property,
|
||||
self.child, child_prop)
|
||||
|
||||
def bind_album_to_self(self, self_prop: str) -> None:
|
||||
"""Bind an album property to the TrackRow child."""
|
||||
album = self.item.get_medium().get_album()
|
||||
self.set_property(self_prop, album.get_property(self.property))
|
||||
self.album_binding = album.bind_property(self.property,
|
||||
self, self_prop)
|
||||
|
||||
def bind_to_self(self, item_prop: str, child_prop: str) -> None:
|
||||
"""Bind an item property directly to a TrackRow property."""
|
||||
self.bind_and_set(self.item, item_prop, self, child_prop)
|
||||
|
||||
def rebind_album(self, child_prop: str) -> None:
|
||||
def rebind_album(self, child_prop: str, to_self: bool = False) -> None:
|
||||
"""Rebind an album property to the TrackRow child."""
|
||||
if self.album_binding is not None:
|
||||
self.album_binding.unbind()
|
||||
self.bind_album(child_prop)
|
||||
|
||||
if to_self:
|
||||
self.bind_album_to_self(child_prop)
|
||||
else:
|
||||
self.bind_album(child_prop)
|
||||
|
||||
@GObject.Property(type=bool, default=False)
|
||||
def active(self) -> bool:
|
||||
|
@ -228,6 +240,45 @@ class AlbumString(InscriptionRow):
|
|||
self.bind_to_self("mediumid", "mediumid")
|
||||
|
||||
|
||||
class AlbumCover(TrackRow):
|
||||
"""A Track Row to display Album art."""
|
||||
|
||||
Cache = dict()
|
||||
|
||||
filepath = GObject.Property(type=GObject.TYPE_PYOBJECT)
|
||||
|
||||
def __init__(self, listitem: Gtk.ListItem, property: str):
|
||||
"""Initialize an Album Cover row."""
|
||||
super().__init__(listitem, property)
|
||||
self.child = Gtk.Picture(content_fit=Gtk.ContentFit.COVER)
|
||||
self.connect("notify", self.__update_album_cover)
|
||||
self.child.connect("query-tooltip", self.__query_tooltip)
|
||||
|
||||
def __update_album_cover(self, row: TrackRow, param) -> None:
|
||||
match param.name:
|
||||
case "mediumid": self.rebind_album("filepath", to_self=True)
|
||||
case "filepath":
|
||||
if self.filepath is None:
|
||||
texture = None
|
||||
elif (texture := AlbumCover.Cache.get(self.filepath)) is None:
|
||||
texture = Gdk.Texture.new_from_filename(str(self.filepath))
|
||||
AlbumCover.Cache[self.filepath] = texture
|
||||
|
||||
self.child.set_paintable(texture)
|
||||
self.child.set_has_tooltip(texture is not None)
|
||||
|
||||
def __query_tooltip(self, child: Gtk.Picture, x: int, y: int,
|
||||
keyboard_mode: bool, tooltip: Gtk.Tooltip) -> bool:
|
||||
texture = AlbumCover.Cache.get(self.filepath)
|
||||
tooltip.set_custom(Gtk.Picture.new_for_paintable(texture))
|
||||
return True
|
||||
|
||||
def do_bind(self) -> None:
|
||||
"""Bind the Album Art path to the filepath property."""
|
||||
super().do_bind()
|
||||
self.bind_to_self("mediumid", "mediumid")
|
||||
|
||||
|
||||
class FavoriteButton(TrackRow):
|
||||
"""A TrackRow with an toggle to set Track favorite status."""
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ class TrackView(Gtk.Frame):
|
|||
model=self._selection)
|
||||
self._scrollwin = Gtk.ScrolledWindow(child=self._columnview)
|
||||
|
||||
self.__append_column("Art", "cover", row.AlbumCover, resizable=False)
|
||||
self.__append_column("Fav", "favorite", row.FavoriteButton,
|
||||
resizable=False)
|
||||
self.__append_column("Track", "number", row.TracknoString,
|
||||
|
|
|
@ -121,6 +121,7 @@ class TestSettings(unittest.TestCase):
|
|||
|
||||
def test_save_tracklist_column_width(self, mock_stdout: io.StringIO):
|
||||
"""Test saving tracklist column widths."""
|
||||
self.assertEqual(self.settings["tracklist.art.size"], -1)
|
||||
self.assertEqual(self.settings["tracklist.fav.size"], -1)
|
||||
self.assertEqual(self.settings["tracklist.track.size"], 55)
|
||||
self.assertEqual(self.settings["tracklist.title.size"], 300)
|
||||
|
@ -136,6 +137,7 @@ class TestSettings(unittest.TestCase):
|
|||
for column in self.win.tracklist.columns:
|
||||
column.set_fixed_width(123)
|
||||
|
||||
self.assertEqual(self.settings["tracklist.art.size"], 123)
|
||||
self.assertEqual(self.settings["tracklist.fav.size"], 123)
|
||||
self.assertEqual(self.settings["tracklist.track.size"], 123)
|
||||
self.assertEqual(self.settings["tracklist.title.size"], 123)
|
||||
|
@ -150,6 +152,7 @@ class TestSettings(unittest.TestCase):
|
|||
|
||||
def test_save_tracklist_column_visible(self, mock_stdout: io.StringIO):
|
||||
"""Test saving tracklist column visibility."""
|
||||
self.assertTrue(self.settings["tracklist.art.visible"])
|
||||
self.assertTrue(self.settings["tracklist.fav.visible"])
|
||||
self.assertTrue(self.settings["tracklist.track.visible"])
|
||||
self.assertTrue(self.settings["tracklist.title.visible"])
|
||||
|
@ -165,6 +168,7 @@ class TestSettings(unittest.TestCase):
|
|||
for column in self.win.tracklist.columns:
|
||||
column.set_visible(not column.get_visible())
|
||||
|
||||
self.assertFalse(self.settings["tracklist.art.visible"])
|
||||
self.assertFalse(self.settings["tracklist.fav.visible"])
|
||||
self.assertFalse(self.settings["tracklist.track.visible"])
|
||||
self.assertFalse(self.settings["tracklist.title.visible"])
|
||||
|
|
|
@ -8,6 +8,7 @@ import emmental.tracklist.row
|
|||
import tests.util
|
||||
import unittest.mock
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Adw
|
||||
|
||||
|
@ -20,7 +21,8 @@ class TestTrackRowWidgets(tests.util.TestCase):
|
|||
super().setUp()
|
||||
self.sql.playlists.load(now=True)
|
||||
self.library = self.sql.libraries.create(pathlib.Path("/a/b"))
|
||||
self.album = self.sql.albums.create("Test Album", "Artist", "2023")
|
||||
self.album = self.sql.albums.create("Test Album", "Artist", "2023",
|
||||
cover=tests.util.COVER_JPG)
|
||||
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,
|
||||
|
@ -168,6 +170,38 @@ class TestTrackRowWidgets(tests.util.TestCase):
|
|||
row.unbind()
|
||||
self.assertIsNone(row.album_binding)
|
||||
|
||||
def test_album_cover(self):
|
||||
"""Test the Album Cover widget."""
|
||||
self.assertDictEqual(emmental.tracklist.row.AlbumCover.Cache, {})
|
||||
cache = emmental.tracklist.row.AlbumCover.Cache
|
||||
|
||||
row = emmental.tracklist.row.AlbumCover(self.listitem, "cover")
|
||||
self.assertIsInstance(row, emmental.tracklist.row.TrackRow)
|
||||
self.assertIsInstance(row.child, Gtk.Picture)
|
||||
self.assertEqual(row.property, "cover")
|
||||
self.assertEqual(row.child.get_content_fit(), Gtk.ContentFit.COVER)
|
||||
self.assertIsNone(row.filepath)
|
||||
|
||||
row.bind()
|
||||
self.assertEqual(row.filepath, tests.util.COVER_JPG)
|
||||
|
||||
self.assertEqual(len(cache), 1)
|
||||
self.assertIsInstance(cache[tests.util.COVER_JPG], Gdk.Texture)
|
||||
self.assertEqual(row.child.get_paintable(),
|
||||
cache[tests.util.COVER_JPG])
|
||||
self.assertTrue(row.child.get_has_tooltip())
|
||||
|
||||
self.album.cover = None
|
||||
self.assertIsNone(row.filepath)
|
||||
self.assertIsNone(row.child.get_paintable())
|
||||
self.assertFalse(row.child.get_has_tooltip())
|
||||
|
||||
album = self.sql.albums.create("Other Album", "Other Artist", "2023",
|
||||
cover=tests.util.COVER_JPG)
|
||||
medium = self.sql.media.create(album, "Other Medium", number=4)
|
||||
self.track.mediumid = medium.mediumid
|
||||
self.assertEqual(row.filepath, tests.util.COVER_JPG)
|
||||
|
||||
def test_favorite_button(self):
|
||||
"""Test the Favorite Button widget."""
|
||||
row = emmental.tracklist.row.FavoriteButton(self.listitem, "favorite")
|
||||
|
|
|
@ -93,7 +93,21 @@ class TestTrackViewColumns(tests.util.TestCase):
|
|||
self.columns = self.trackview.columns
|
||||
self.listitem = Gtk.ListItem()
|
||||
|
||||
def test_favorite_column(self, i: int = 0):
|
||||
def test_artwork_column(self, i: int = 0):
|
||||
"""Test the favorite column."""
|
||||
self.assertEqual(self.columns[i].get_title(), "Art")
|
||||
self.assertEqual(self.columns[i].get_fixed_width(), -1)
|
||||
self.assertFalse(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.AlbumCover)
|
||||
|
||||
factory.emit("setup", self.listitem)
|
||||
self.assertEqual(self.listitem.listrow.property, "cover")
|
||||
|
||||
def test_favorite_column(self, i: int = 1):
|
||||
"""Test the favorite column."""
|
||||
self.assertEqual(self.columns[i].get_title(), "Fav")
|
||||
self.assertEqual(self.columns[i].get_fixed_width(), -1)
|
||||
|
@ -108,7 +122,7 @@ class TestTrackViewColumns(tests.util.TestCase):
|
|||
factory.emit("setup", self.listitem)
|
||||
self.assertEqual(self.listitem.listrow.property, "favorite")
|
||||
|
||||
def test_trackno_column(self, i: int = 1):
|
||||
def test_trackno_column(self, i: int = 2):
|
||||
"""Test the track number column."""
|
||||
self.assertEqual(self.columns[i].get_title(), "Track")
|
||||
self.assertEqual(self.columns[i].get_fixed_width(), 55)
|
||||
|
@ -125,7 +139,7 @@ class TestTrackViewColumns(tests.util.TestCase):
|
|||
self.assertEqual(self.listitem.listrow.child.get_xalign(), 1.0)
|
||||
self.assertTrue(self.listitem.listrow.child.has_css_class("numeric"))
|
||||
|
||||
def test_title_column(self, i: int = 2):
|
||||
def test_title_column(self, i: int = 3):
|
||||
"""Test the title column."""
|
||||
self.assertEqual(self.columns[i].get_title(), "Title")
|
||||
self.assertEqual(self.columns[i].get_fixed_width(), 300)
|
||||
|
@ -141,7 +155,7 @@ class TestTrackViewColumns(tests.util.TestCase):
|
|||
self.assertEqual(self.listitem.listrow.child.get_xalign(), 0.0)
|
||||
self.assertFalse(self.listitem.listrow.child.has_css_class("numeric"))
|
||||
|
||||
def test_length_column(self, i: int = 3):
|
||||
def test_length_column(self, i: int = 4):
|
||||
"""Test the length column."""
|
||||
self.assertEqual(self.columns[i].get_title(), "Length")
|
||||
self.assertEqual(self.columns[i].get_fixed_width(), -1)
|
||||
|
@ -157,7 +171,7 @@ class TestTrackViewColumns(tests.util.TestCase):
|
|||
self.assertEqual(self.listitem.listrow.child.get_xalign(), 1.0)
|
||||
self.assertTrue(self.listitem.listrow.child.has_css_class("numeric"))
|
||||
|
||||
def test_artist_column(self, i: int = 4):
|
||||
def test_artist_column(self, i: int = 5):
|
||||
"""Test the artist column."""
|
||||
self.assertEqual(self.columns[i].get_title(), "Artist")
|
||||
self.assertEqual(self.columns[i].get_fixed_width(), 250)
|
||||
|
@ -173,7 +187,7 @@ class TestTrackViewColumns(tests.util.TestCase):
|
|||
self.assertEqual(self.listitem.listrow.child.get_xalign(), 0.0)
|
||||
self.assertFalse(self.listitem.listrow.child.has_css_class("numeric"))
|
||||
|
||||
def test_album_artist_column(self, i: int = 5):
|
||||
def test_album_artist_column(self, i: int = 6):
|
||||
"""Test the album artist column."""
|
||||
self.assertEqual(self.columns[i].get_title(), "Album Artist")
|
||||
self.assertEqual(self.columns[i].get_fixed_width(), 250)
|
||||
|
@ -189,7 +203,7 @@ class TestTrackViewColumns(tests.util.TestCase):
|
|||
self.assertEqual(self.listitem.listrow.child.get_xalign(), 0.0)
|
||||
self.assertFalse(self.listitem.listrow.child.has_css_class("numeric"))
|
||||
|
||||
def test_release_column(self, i: int = 6):
|
||||
def test_release_column(self, i: int = 7):
|
||||
"""Test the release date column."""
|
||||
self.assertEqual(self.columns[i].get_title(), "Release")
|
||||
self.assertEqual(self.columns[i].get_fixed_width(), 115)
|
||||
|
@ -205,7 +219,7 @@ class TestTrackViewColumns(tests.util.TestCase):
|
|||
self.assertEqual(self.listitem.listrow.child.get_xalign(), 0.0)
|
||||
self.assertTrue(self.listitem.listrow.child.has_css_class("numeric"))
|
||||
|
||||
def test_playcount_column(self, i: int = 7):
|
||||
def test_playcount_column(self, i: int = 8):
|
||||
"""Test the play count column."""
|
||||
self.assertEqual(self.columns[i].get_title(), "Play Count")
|
||||
self.assertEqual(self.columns[i].get_fixed_width(), 135)
|
||||
|
@ -222,7 +236,7 @@ class TestTrackViewColumns(tests.util.TestCase):
|
|||
self.assertEqual(self.listitem.listrow.child.get_xalign(), 0.0)
|
||||
self.assertTrue(self.listitem.listrow.child.has_css_class("numeric"))
|
||||
|
||||
def test_last_started_column(self, i: int = 8):
|
||||
def test_last_started_column(self, i: int = 9):
|
||||
"""Test the last started column."""
|
||||
self.assertEqual(self.columns[i].get_title(), "Last Started")
|
||||
self.assertEqual(self.columns[i].get_fixed_width(), 250)
|
||||
|
@ -239,7 +253,7 @@ class TestTrackViewColumns(tests.util.TestCase):
|
|||
self.assertEqual(self.listitem.listrow.child.get_xalign(), 0.0)
|
||||
self.assertTrue(self.listitem.listrow.child.has_css_class("numeric"))
|
||||
|
||||
def test_last_played_column(self, i: int = 9):
|
||||
def test_last_played_column(self, i: int = 10):
|
||||
"""Test the last played column."""
|
||||
self.assertEqual(self.columns[i].get_title(), "Last Played")
|
||||
self.assertEqual(self.columns[i].get_fixed_width(), 250)
|
||||
|
@ -256,7 +270,7 @@ class TestTrackViewColumns(tests.util.TestCase):
|
|||
self.assertEqual(self.listitem.listrow.child.get_xalign(), 0.0)
|
||||
self.assertTrue(self.listitem.listrow.child.has_css_class("numeric"))
|
||||
|
||||
def test_filepath_column(self, i: int = 10):
|
||||
def test_filepath_column(self, i: int = 11):
|
||||
"""Test the last played column."""
|
||||
self.assertEqual(self.columns[i].get_title(), "Filepath")
|
||||
self.assertEqual(self.columns[i].get_fixed_width(), -1)
|
||||
|
|
Loading…
Reference in New Issue