nowplaying: Add an Album Art class

This displays the current track's album art with a fancy frame drawn
around it. Clicking the image opens a popover showing the artwork at
full size.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2022-07-20 10:24:00 -04:00
parent ee8db58fb2
commit b3d04805d7
2 changed files with 139 additions and 0 deletions

View File

@ -0,0 +1,49 @@
# Copyright 2022 (c) Anna Schumaker.
"""Our custom Album Art widget."""
import pathlib
from gi.repository import GObject
from gi.repository import Gtk
from .. import gsetup
FALLBACK_RESOURCE = f"{gsetup.RESOURCE_ICONS}/emmental.svg"
class Artwork(Gtk.Frame):
"""Our custom Album Art widget that draws a border around a picture."""
def __init__(self):
"""Initialize the Album Art widget."""
super().__init__(margin_top=6, margin_bottom=6, margin_start=6,
margin_end=6, halign=Gtk.Align.CENTER,
valign=Gtk.Align.CENTER)
self._picture = Gtk.Picture(content_fit=Gtk.ContentFit.CONTAIN)
self._fullsize = Gtk.Picture(content_fit=Gtk.ContentFit.FILL)
self._popover = Gtk.Popover(child=self._fullsize)
self._clicked = Gtk.GestureClick()
self._clicked.connect("released", self.clicked)
self.add_controller(self._clicked)
self._popover.set_parent(self)
self.set_child(self._picture)
self.filepath = None
@GObject.Property(type=GObject.TYPE_PYOBJECT)
def filepath(self) -> pathlib.Path:
"""Get the current artwork path."""
name = self._picture.get_file().get_parse_name()
return None if name.startswith("resource:") else pathlib.Path(name)
@filepath.setter
def filepath(self, path: pathlib.Path) -> None:
if path is not None:
self._picture.set_filename(str(path))
self._fullsize.set_filename(str(path))
else:
self._picture.set_resource(FALLBACK_RESOURCE)
self._fullsize.set_resource(FALLBACK_RESOURCE)
def clicked(self, gesture: Gtk.GestureClick, n_press: int,
x: float, y: float) -> None:
"""Handle a click event."""
self._popover.popup()

View File

@ -0,0 +1,90 @@
# Copyright 2022 (c) Anna Schumaker.
"""Tests our Album Art widget."""
import unittest
import emmental.nowplaying.artwork
import tests.util
from gi.repository import Gtk
class TestArtwork(unittest.TestCase):
"""Test case for our Album Art widget."""
def setUp(self):
"""Set up common variables."""
self.fallback = emmental.nowplaying.artwork.FALLBACK_RESOURCE
self.artwork = emmental.nowplaying.artwork.Artwork()
def test_constants(self):
"""Test constant values."""
self.assertEqual(self.fallback,
f"{emmental.gsetup.RESOURCE_ICONS}/emmental.svg")
def test_init(self):
"""Test that the artwork widget is configured correctly."""
self.assertIsInstance(self.artwork, Gtk.Frame)
self.assertEqual(self.artwork.get_halign(), Gtk.Align.CENTER)
self.assertEqual(self.artwork.get_valign(), Gtk.Align.CENTER)
self.assertEqual(self.artwork.get_margin_top(), 6)
self.assertEqual(self.artwork.get_margin_bottom(), 6)
self.assertEqual(self.artwork.get_margin_start(), 6)
self.assertEqual(self.artwork.get_margin_end(), 6)
def test_picture(self):
"""Test that the artwork picture is configured correctly."""
self.assertIsInstance(self.artwork._picture, Gtk.Picture)
self.assertEqual(self.artwork._picture.get_content_fit(),
Gtk.ContentFit.CONTAIN)
self.assertEqual(self.artwork.get_child(), self.artwork._picture)
self.assertIsNone(self.artwork.filepath)
self.assertIsNotNone(self.artwork._picture.get_paintable())
self.assertEqual(self.artwork._picture.get_file().get_parse_name(),
f"resource://{self.fallback}")
self.artwork.filepath = tests.util.COVER_JPG
self.assertIsNotNone(self.artwork._picture.get_paintable())
self.assertEqual(self.artwork._picture.get_file().get_parse_name(),
str(tests.util.COVER_JPG))
self.assertEqual(self.artwork.filepath, tests.util.COVER_JPG)
self.artwork.filepath = None
self.assertIsNotNone(self.artwork._picture.get_paintable())
self.assertEqual(self.artwork._picture.get_file().get_parse_name(),
f"resource://{self.fallback}")
def test_fullsize(self):
"""Test that the fullsize picture is configured correctly."""
self.assertIsInstance(self.artwork._fullsize, Gtk.Picture)
self.assertEqual(self.artwork._fullsize.get_content_fit(),
Gtk.ContentFit.FILL)
self.assertEqual(self.artwork._popover.get_child(),
self.artwork._fullsize)
self.assertIsNotNone(self.artwork._fullsize.get_paintable())
self.assertEqual(self.artwork._fullsize.get_file().get_parse_name(),
f"resource://{self.fallback}")
self.artwork.filepath = tests.util.COVER_JPG
self.assertIsNotNone(self.artwork._fullsize.get_paintable())
self.assertEqual(self.artwork._fullsize.get_file().get_parse_name(),
str(tests.util.COVER_JPG))
self.artwork.filepath = None
self.assertIsNotNone(self.artwork._fullsize.get_paintable())
self.assertEqual(self.artwork._fullsize.get_file().get_parse_name(),
f"resource://{self.fallback}")
def test_popover(self):
"""Test that the popover picture is configured correctly."""
self.assertIsInstance(self.artwork._popover, Gtk.Popover)
self.assertIsInstance(self.artwork._clicked, Gtk.GestureClick)
self.assertEqual(self.artwork._popover.get_parent(), self.artwork)
self.assertEqual(self.artwork._clicked.get_widget(), self.artwork)
with unittest.mock.patch.object(self.artwork._popover,
"popup") as mock_popup:
self.artwork._clicked.emit("released", 1, 1, 1)
mock_popup.assert_called()