diff --git a/emmental/__init__.py b/emmental/__init__.py index d734dc8..10e0757 100644 --- a/emmental/__init__.py +++ b/emmental/__init__.py @@ -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.""" diff --git a/emmental/header/__init__.py b/emmental/header/__init__.py index dba4d9f..1349f7c 100644 --- a/emmental/header/__init__.py +++ b/emmental/header/__init__.py @@ -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)) diff --git a/tests/header/test_header.py b/tests/header/test_header.py index dbd6d6e..3f35b63 100644 --- a/tests/header/test_header.py +++ b/tests/header/test_header.py @@ -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) diff --git a/tests/test_settings.py b/tests/test_settings.py index e076574..059bbfb 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -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)