header: Convert the ReplayGain selector into an Adw.ExpanderRow

This will be added to a ListBox with the volume controls. Expanding the
row will enable ReplayGain and give the user a menu to select ReplayGain
mode.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2023-05-31 10:48:40 -04:00
parent 03e5b9ad1b
commit dae588bfaf
4 changed files with 182 additions and 117 deletions

View File

@ -44,7 +44,7 @@ class Header(Gtk.HeaderBar):
self._title = Adw.WindowTitle(title=self.title, subtitle=self.subtitle,
tooltip_text=gsetup.env_string())
self._volume = volume.VolumeRow()
self._replaygain = replaygain.Selector()
self._replaygain = replaygain.ReplayGainRow()
self._box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
self._box.append(self._volume)

View File

@ -2,9 +2,36 @@
"""A widget for selecting ReplayGain mode."""
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import Adw
class Selector(Gtk.Grid):
class CheckRow(Adw.ActionRow):
"""A custom Adw.ActionRow displaying a Check Button."""
active = GObject.Property(type=bool, default=False)
group = GObject.Property(type=Adw.ActionRow)
mode = GObject.Property(type=str)
def __init__(self, mode: str, active: bool = False,
group: Adw.ActionRow | None = None, **kwargs):
"""Initialize the Check Row."""
super().__init__(mode=mode, active=active, group=group, **kwargs)
self._prefix = Gtk.CheckButton(active=active,
group=group._prefix if group else None)
self.bind_property("active", self._prefix, "active",
GObject.BindingFlags.BIDIRECTIONAL)
self.set_activatable_widget(self._prefix)
self.add_prefix(self._prefix)
def set_active(self, newval: bool) -> None:
"""Set the active property."""
if self.active != newval:
self.active = newval
class ReplayGainRow(Adw.ExpanderRow):
"""Build up a widget for configuring ReplayGain settings."""
enabled = GObject.Property(type=bool, default=False)
@ -12,42 +39,40 @@ class Selector(Gtk.Grid):
def __init__(self):
"""Initialize the ReplayGain selector."""
super().__init__(column_spacing=6, margin_top=8)
self._title = Gtk.Label(label="Volume Normalization", yalign=0.8,
hexpand=True, vexpand=True)
self._switch = Gtk.Switch()
self._auto = Gtk.CheckButton(label="Decide automatically",
sensitive=False, active=True)
self._album = Gtk.CheckButton(label="Albums have the same volume",
sensitive=False, group=self._auto)
self._track = Gtk.CheckButton(label="Tracks have the same volume",
sensitive=False, group=self._auto)
super().__init__(title="Volume Normalization",
subtitle="Configure ReplayGain normalizing")
self._switch = Gtk.Switch(valign=Gtk.Align.CENTER)
self._automatic = CheckRow(title="Automatic Mode",
subtitle="Emmental decides automatically",
mode="auto", active=True)
self._album = CheckRow(title="Album Mode",
subtitle="Albums have the same volume",
mode="album", group=self._automatic)
self._track = CheckRow(title="Track Mode",
subtitle="Tracks have the same volume",
mode="track", group=self._automatic)
self.attach(self._title, 0, 0, 1, 1)
self.attach(self._switch, 1, 0, 1, 1)
self.attach(self._auto, 0, 1, 2, 1)
self.attach(self._album, 0, 2, 2, 1)
self.attach(self._track, 0, 3, 2, 1)
self.add_prefix(self._switch)
self.add_row(self._automatic)
self.add_row(self._album)
self.add_row(self._track)
self.connect("notify::mode", self.__notify_mode)
self._auto.connect("toggled", self.__mode_toggled, "auto")
self._album.connect("toggled", self.__mode_toggled, "album")
self._track.connect("toggled", self.__mode_toggled, "track")
self._automatic.connect("notify::active", self.__row_activated)
self._album.connect("notify::active", self.__row_activated)
self._track.connect("notify::active", self.__row_activated)
self._switch.bind_property("state", self._auto, "sensitive")
self._switch.bind_property("state", self._album, "sensitive")
self._switch.bind_property("state", self._track, "sensitive")
self.bind_property("enabled", self._switch, "state",
self._switch.bind_property("active", self, "expanded",
GObject.BindingFlags.BIDIRECTIONAL)
self.bind_property("enabled", self._switch, "active",
GObject.BindingFlags.BIDIRECTIONAL)
self._title.add_css_class("title-4")
def __notify_mode(self, selector: Gtk.Grid, param) -> None:
match selector.get_property("mode"):
def __notify_mode(self, row: Adw.ExpanderRow, param) -> None:
match self.mode:
case "album": self._album.set_active(True)
case "track": self._track.set_active(True)
case _: self._auto.set_active(True)
case _: self._automatic.set_active(True)
def __mode_toggled(self, check: Gtk.CheckButton, new_mode: str) -> None:
if check.get_active():
self.mode = new_mode
def __row_activated(self, row: CheckRow, param: GObject.ParamSpec) -> None:
if row.active:
self.mode = row.mode

View File

@ -88,7 +88,7 @@ class TestHeader(tests.util.TestCase):
def test_replaygain(self):
"""Test that we can configure ReplayGain as expected."""
self.assertIsInstance(self.header._replaygain,
emmental.header.replaygain.Selector)
emmental.header.replaygain.ReplayGainRow)
self.assertFalse(self.header.rg_enabled)
self.assertEqual(self.header.rg_mode, "auto")

View File

@ -1,119 +1,159 @@
# Copyright 2022 (c) Anna Schumaker
"""Tests our ReplayGain selector."""
import unittest
import unittest.mock
import emmental.header.replaygain
from gi.repository import Gtk
from gi.repository import Adw
class TestSelector(unittest.TestCase):
"""Test case for our custom ReplayGain Selector."""
class TestCheckRow(unittest.TestCase):
"""Test case for our custom CheckRow ListBox row."""
def setUp(self):
"""Set up common variables."""
self.selector = emmental.header.replaygain.Selector()
self.checkrow = emmental.header.replaygain.CheckRow("test mode",
title="My Title")
def test_selector(self):
"""Check that the Selector is set up properly."""
self.assertIsInstance(self.selector, Gtk.Grid)
self.assertEqual(self.selector.get_column_spacing(), 6)
self.assertEqual(self.selector.get_margin_top(), 8)
def test_init(self):
"""Test that the CheckRow is set up properly."""
self.assertIsInstance(self.checkrow, Adw.ActionRow)
self.assertIsInstance(self.checkrow._prefix, Gtk.CheckButton)
self.assertEqual(self.selector.mode, "auto")
self.assertFalse(self.selector.enabled)
self.assertEqual(self.checkrow.mode, "test mode")
self.assertEqual(self.checkrow.get_title(), "My Title")
self.assertEqual(self.checkrow.get_activatable_widget(),
self.checkrow._prefix)
self.selector.enabled = True
self.assertTrue(self.selector._switch.get_state())
self.selector.enabled = False
self.assertFalse(self.selector._switch.get_state())
def test_active(self):
"""Test the CheckRow active property."""
self.assertFalse(self.checkrow.active)
def test_title(self):
"""Check that the Selector title label is set up properly."""
self.assertIsInstance(self.selector._title, Gtk.Label)
self.assertEqual(self.selector.get_child_at(0, 0),
self.selector._title)
self.checkrow.active = True
self.assertTrue(self.checkrow._prefix.get_active())
self.checkrow._prefix.set_active(False)
self.assertFalse(self.checkrow.active)
self.assertEqual(self.selector._title.get_text(),
"Volume Normalization")
self.assertAlmostEqual(self.selector._title.get_yalign(), 0.8)
self.assertTrue(self.selector._title.has_css_class("title-4"))
self.assertTrue(self.selector._title.get_hexpand())
self.assertTrue(self.selector._title.get_vexpand())
checkrow2 = emmental.header.replaygain.CheckRow("other", active=True)
self.assertTrue(checkrow2.active)
self.assertTrue(checkrow2._prefix.get_active())
def test_group(self):
"""Test the CheckRow group property."""
self.assertIsNone(self.checkrow.group)
checkrow2 = emmental.header.replaygain.CheckRow("other",
group=self.checkrow)
self.assertEqual(checkrow2.group, self.checkrow)
def test_set_active(self):
"""Test the set_active() property."""
notify = unittest.mock.Mock()
self.checkrow.connect("notify::active", notify)
self.checkrow.set_active(True)
self.assertTrue(self.checkrow.active)
notify.assert_called()
notify.reset_mock()
self.checkrow.set_active(True)
notify.assert_not_called()
class TestReplayGainRow(unittest.TestCase):
"""Test case for our custom ReplayGain ListBox row."""
def setUp(self):
"""Set up common variables."""
self.replaygain = emmental.header.replaygain.ReplayGainRow()
def test_init(self):
"""Check that the ReplayGainRow is set up properly."""
self.assertIsInstance(self.replaygain, Adw.ExpanderRow)
self.assertEqual(self.replaygain.get_title(), "Volume Normalization")
self.assertEqual(self.replaygain.get_subtitle(),
"Configure ReplayGain normalizing")
self.assertEqual(self.replaygain.mode, "auto")
self.assertFalse(self.replaygain.enabled)
self.replaygain.set_expanded(True)
self.assertTrue(self.replaygain._switch.get_active())
self.replaygain.set_expanded(False)
self.assertFalse(self.replaygain._switch.get_active())
def test_switch(self):
"""Check that the Selector switch works as intended."""
self.assertIsInstance(self.selector._switch, Gtk.Switch)
self.assertEqual(self.selector.get_child_at(1, 0),
self.selector._switch)
self.assertFalse(self.selector._switch.get_active())
"""Check that the ReplayGainRow switch works as intended."""
self.assertIsInstance(self.replaygain._switch, Gtk.Switch)
self.assertEqual(self.replaygain._switch.get_valign(),
Gtk.Align.CENTER)
self.assertFalse(self.replaygain._switch.get_active())
self.selector._switch.set_active(True)
self.assertTrue(self.selector.enabled)
self.assertTrue(self.selector._auto.get_sensitive())
self.assertTrue(self.selector._album.get_sensitive())
self.assertTrue(self.selector._track.get_sensitive())
self.selector._switch.set_active(False)
self.assertFalse(self.selector.enabled)
self.assertFalse(self.selector._auto.get_sensitive())
self.assertFalse(self.selector._album.get_sensitive())
self.assertFalse(self.selector._track.get_sensitive())
self.replaygain._switch.set_active(True)
self.assertTrue(self.replaygain.get_expanded())
self.replaygain._switch.set_active(False)
self.assertFalse(self.replaygain.get_expanded())
def test_automatic_mode(self):
"""Test the Selector automatic mode button."""
self.assertIsInstance(self.selector._auto, Gtk.CheckButton)
self.assertEqual(self.selector.get_child_at(0, 1), self.selector._auto)
"""Test the ReplayGainRow automatic mode option."""
self.assertIsInstance(self.replaygain._automatic,
emmental.header.replaygain.CheckRow)
self.assertEqual(self.replaygain._automatic.get_title(),
"Automatic Mode")
self.assertEqual(self.replaygain._automatic.get_subtitle(),
"Emmental decides automatically")
self.assertEqual(self.selector._auto.get_label(),
"Decide automatically")
self.assertFalse(self.selector._auto.get_sensitive())
self.assertTrue(self.selector._auto.get_active())
self.assertEqual(self.replaygain._automatic.mode, "auto")
self.assertTrue(self.replaygain._automatic.active)
self.selector._track.set_active(True)
self.selector._auto.set_active(True)
self.assertEqual(self.selector.mode, "auto")
self.replaygain._track.active = True
self.replaygain._automatic.active = True
self.assertEqual(self.replaygain.mode, "auto")
self.assertFalse(self.replaygain._track.active)
def test_album_mode(self):
"""Test the Selector album mode button."""
self.assertIsInstance(self.selector._album, Gtk.CheckButton)
self.assertEqual(self.selector.get_child_at(0, 2),
self.selector._album)
self.assertEqual(self.selector._album.get_label(),
"""Test the ReplayGainRow album mode option."""
self.assertIsInstance(self.replaygain._album,
emmental.header.replaygain.CheckRow)
self.assertEqual(self.replaygain._album.get_title(), "Album Mode")
self.assertEqual(self.replaygain._album.get_subtitle(),
"Albums have the same volume")
self.assertFalse(self.selector._album.get_sensitive())
self.assertFalse(self.selector._album.get_active())
self.selector._album.set_active(True)
self.assertEqual(self.selector.mode, "album")
self.assertEqual(self.replaygain._album.mode, "album")
self.assertEqual(self.replaygain._album.group,
self.replaygain._automatic)
self.replaygain._album.active = True
self.assertEqual(self.replaygain.mode, "album")
def test_track_mode(self):
"""Test the Selector album mode button."""
self.assertIsInstance(self.selector._track, Gtk.CheckButton)
self.assertEqual(self.selector.get_child_at(0, 3),
self.selector._track)
self.assertEqual(self.selector._track.get_label(),
"""Test the ReplayGainRow track mode option."""
self.assertIsInstance(self.replaygain._track,
emmental.header.replaygain.CheckRow)
self.assertEqual(self.replaygain._track.get_title(), "Track Mode")
self.assertEqual(self.replaygain._track.get_subtitle(),
"Tracks have the same volume")
self.assertFalse(self.selector._track.get_sensitive())
self.assertFalse(self.selector._track.get_active())
self.selector._track.set_active(True)
self.assertEqual(self.selector.mode, "track")
self.assertEqual(self.replaygain._track.mode, "track")
self.assertEqual(self.replaygain._track.group,
self.replaygain._automatic)
self.replaygain._track.active = True
self.assertEqual(self.replaygain.mode, "track")
def test_mode_property(self):
"""Test that the mode property is set correctly."""
self.selector.mode = "album"
self.assertTrue(self.selector._album.get_active())
self.assertFalse(self.selector._auto.get_active())
self.assertFalse(self.selector._track.get_active())
self.replaygain.mode = "album"
self.assertTrue(self.replaygain._album.active)
self.assertFalse(self.replaygain._automatic.active)
self.assertFalse(self.replaygain._track.active)
self.selector.mode = "track"
self.assertTrue(self.selector._track.get_active())
self.assertFalse(self.selector._auto.get_active())
self.assertFalse(self.selector._album.get_active())
self.replaygain.mode = "track"
self.assertTrue(self.replaygain._track.active)
self.assertFalse(self.replaygain._automatic.active)
self.assertFalse(self.replaygain._album.active)
self.selector.mode = "anything else"
self.assertTrue(self.selector._auto.get_active())
self.assertFalse(self.selector._album.get_active())
self.assertFalse(self.selector._track.get_active())
self.assertEqual(self.selector.mode, "auto")
self.replaygain.mode = "anything else"
self.assertTrue(self.replaygain._automatic.active)
self.assertFalse(self.replaygain._album.active)
self.assertFalse(self.replaygain._track.active)
self.assertEqual(self.replaygain.mode, "auto")