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:
parent
847c15f64b
commit
081cae4cd8
|
@ -30,7 +30,9 @@ class Application(Adw.Application):
|
||||||
|
|
||||||
def build_header(self) -> header.Header:
|
def build_header(self) -> header.Header:
|
||||||
"""Build a new header instance."""
|
"""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:
|
def build_window(self) -> window.Window:
|
||||||
"""Build a new window instance."""
|
"""Build a new window instance."""
|
||||||
|
|
|
@ -4,26 +4,49 @@ from gi.repository import GObject
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
from gi.repository import Adw
|
from gi.repository import Adw
|
||||||
from .. import db
|
from .. import db
|
||||||
|
from .. import buttons
|
||||||
|
from . import volume
|
||||||
if __debug__:
|
if __debug__:
|
||||||
from . import settings
|
from . import settings
|
||||||
|
|
||||||
SUBTITLE = "The Cheesy Music Player"
|
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):
|
class Header(Gtk.HeaderBar):
|
||||||
"""Our custom Gtk.HeaderBar containing window title and volume controls."""
|
"""Our custom Gtk.HeaderBar containing window title and volume controls."""
|
||||||
|
|
||||||
sql = GObject.Property(type=db.Connection)
|
sql = GObject.Property(type=db.Connection)
|
||||||
title = GObject.Property(type=str)
|
title = GObject.Property(type=str)
|
||||||
subtitle = 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):
|
def __init__(self, sql: db.Connection, title: str):
|
||||||
"""Initialize the HeaderBar."""
|
"""Initialize the HeaderBar."""
|
||||||
super().__init__(title=title, subtitle=SUBTITLE, sql=sql)
|
super().__init__(title=title, subtitle=SUBTITLE, sql=sql)
|
||||||
self._title = Adw.WindowTitle(title=self.title, subtitle=self.subtitle)
|
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("title", self._title, "title")
|
||||||
self.bind_property("subtitle", self._title, "subtitle")
|
self.bind_property("subtitle", self._title, "subtitle")
|
||||||
|
self.bind_property("volume", self._volume, "volume",
|
||||||
|
GObject.BindingFlags.BIDIRECTIONAL)
|
||||||
|
|
||||||
if __debug__:
|
if __debug__:
|
||||||
self._window = settings.Window(sql)
|
self._window = settings.Window(sql)
|
||||||
|
@ -31,8 +54,13 @@ class Header(Gtk.HeaderBar):
|
||||||
self._settings.connect("clicked", self.__run_settings)
|
self._settings.connect("clicked", self.__run_settings)
|
||||||
self.pack_start(self._settings)
|
self.pack_start(self._settings)
|
||||||
|
|
||||||
|
self.pack_end(self._button)
|
||||||
self.set_title_widget(self._title)
|
self.set_title_widget(self._title)
|
||||||
|
self.connect("notify::volume", self.__notify_volume)
|
||||||
|
|
||||||
def __run_settings(self, button: Gtk.Button) -> None:
|
def __run_settings(self, button: Gtk.Button) -> None:
|
||||||
if __debug__:
|
if __debug__:
|
||||||
self._window.present()
|
self._window.present()
|
||||||
|
|
||||||
|
def __notify_volume(self, header, param) -> None:
|
||||||
|
self._button.set_icon_name(_volume_icon(self.volume))
|
||||||
|
|
|
@ -46,3 +46,40 @@ class TestHeader(tests.util.TestCase):
|
||||||
"present") as mock_present:
|
"present") as mock_present:
|
||||||
self.header._settings.emit("clicked")
|
self.header._settings.emit("clicked")
|
||||||
mock_present.assert_called()
|
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)
|
||||||
|
|
|
@ -32,3 +32,11 @@ class TestSettings(unittest.TestCase):
|
||||||
|
|
||||||
win = self.app.build_window()
|
win = self.app.build_window()
|
||||||
self.assertEqual(win.get_default_size(), (100, 200))
|
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)
|
||||||
|
|
Loading…
Reference in New Issue