header: Add new volume control widgets to the Header class

They live in a Gtk.MenuButton with a custom popover box that can have
replaygain options added to it. I also modify the Application to save
the volume when it is changed.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2022-05-31 10:18:53 -04:00
parent 847c15f64b
commit 081cae4cd8
4 changed files with 76 additions and 1 deletions

View File

@ -30,7 +30,9 @@ class Application(Adw.Application):
def build_header(self) -> header.Header:
"""Build a new header instance."""
return header.Header(sql=self.db, title=VERSION_STRING)
hdr = header.Header(sql=self.db, title=VERSION_STRING)
self.db.settings.bind_setting("audio.volume", hdr, "volume")
return hdr
def build_window(self) -> window.Window:
"""Build a new window instance."""

View File

@ -4,26 +4,49 @@ from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import Adw
from .. import db
from .. import buttons
from . import volume
if __debug__:
from . import settings
SUBTITLE = "The Cheesy Music Player"
def _volume_icon(vol: float) -> str:
if vol == 0.0:
return "audio-volume-muted-symbolic"
if vol <= 1/3:
return "audio-volume-low-symbolic"
if vol <= 2/3:
return "audio-volume-medium-symbolic"
return "audio-volume-high-symbolic"
class Header(Gtk.HeaderBar):
"""Our custom Gtk.HeaderBar containing window title and volume controls."""
sql = GObject.Property(type=db.Connection)
title = GObject.Property(type=str)
subtitle = GObject.Property(type=str)
volume = GObject.Property(type=float, default=1.0)
def __init__(self, sql: db.Connection, title: str):
"""Initialize the HeaderBar."""
super().__init__(title=title, subtitle=SUBTITLE, sql=sql)
self._title = Adw.WindowTitle(title=self.title, subtitle=self.subtitle)
self._volume = volume.Controls()
self._box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
self._box.append(self._volume)
icon = _volume_icon(self.volume)
self._button = buttons.PopoverButton(popover_child=self._box,
icon_name=icon)
self.bind_property("title", self._title, "title")
self.bind_property("subtitle", self._title, "subtitle")
self.bind_property("volume", self._volume, "volume",
GObject.BindingFlags.BIDIRECTIONAL)
if __debug__:
self._window = settings.Window(sql)
@ -31,8 +54,13 @@ class Header(Gtk.HeaderBar):
self._settings.connect("clicked", self.__run_settings)
self.pack_start(self._settings)
self.pack_end(self._button)
self.set_title_widget(self._title)
self.connect("notify::volume", self.__notify_volume)
def __run_settings(self, button: Gtk.Button) -> None:
if __debug__:
self._window.present()
def __notify_volume(self, header, param) -> None:
self._button.set_icon_name(_volume_icon(self.volume))

View File

@ -46,3 +46,40 @@ class TestHeader(tests.util.TestCase):
"present") as mock_present:
self.header._settings.emit("clicked")
mock_present.assert_called()
def test_volume(self):
"""Check that volume widgets work as expected."""
self.assertIsInstance(self.header._volume,
emmental.header.volume.Controls)
self.assertEqual(self.header.volume, 1.0)
for i, vol in [(x, x/10) for x in range(11)]:
with self.subTest(volume=vol):
widget = self.header if i % 2 else self.header._volume
match i:
case 0: icon = "muted"
case 1 | 2 | 3: icon = "low"
case 4 | 5 | 6: icon = "medium"
case _: icon = "high"
widget.volume = vol
self.assertEqual(self.header.volume, vol)
self.assertEqual(self.header._volume.volume, vol)
self.assertEqual(self.header._button.get_icon_name(),
f"audio-volume-{icon}-symbolic")
def test_popover(self):
"""Check that the menu popover was set up correctly."""
self.assertIsInstance(self.header._button,
emmental.buttons.PopoverButton)
self.assertIsInstance(self.header._box, Gtk.Box)
self.assertEqual(self.header._box.get_orientation(),
Gtk.Orientation.VERTICAL)
self.assertEqual(self.header._box.get_spacing(), 0)
self.assertEqual(self.header._button.get_icon_name(),
"audio-volume-high-symbolic")
self.assertEqual(self.header._button.popover_child, self.header._box)
self.assertEqual(self.header._box.get_first_child(),
self.header._volume)

View File

@ -32,3 +32,11 @@ class TestSettings(unittest.TestCase):
win = self.app.build_window()
self.assertEqual(win.get_default_size(), (100, 200))
def test_save_volume(self):
"""Check saving and loading volume from the database."""
self.assertEqual(self.settings["audio.volume"], 1.0)
self.win.header.volume = 0.5
self.assertEqual(self.settings["audio.volume"], 0.5)
self.assertEqual(self.app.build_header().volume, 0.5)