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, self._title = Adw.WindowTitle(title=self.title, subtitle=self.subtitle,
tooltip_text=gsetup.env_string()) tooltip_text=gsetup.env_string())
self._volume = volume.VolumeRow() self._volume = volume.VolumeRow()
self._replaygain = replaygain.Selector() self._replaygain = replaygain.ReplayGainRow()
self._box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) self._box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
self._box.append(self._volume) self._box.append(self._volume)

View File

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

View File

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

View File

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