tracklist: Rework the VisibleColumns button to use a ListBox

I create a custom Gtk.ListBoxRow for displaying each individual column
name and visibility status. I then bind it to a listbox placed as the
popover button's popover child. This lets me set the 'boxed-list' style
class on the listbox to give it a nicer appearance, and clicking the
label will also toggle column visibility.

Implements: #57 ("Rework visible columns button")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2023-08-10 18:10:13 -04:00
parent 3d6350d7bd
commit f6481f0182
2 changed files with 107 additions and 62 deletions

View File

@ -8,16 +8,26 @@ from .. import buttons
from .. import factory from .. import factory
class VisibleSwitch(factory.ListRow): class VisibleRow(Gtk.ListBoxRow):
"""A list row containing a Gtk.Switch.""" """A ListBoxRow containing a Gtk.Switch and a title Label."""
def __init__(self, listitem: Gtk.ListItem): active = GObject.Property(type=bool, default=True)
"""Initialize a VisibleSwitch ListRow.""" title = GObject.Property(type=str)
super().__init__(listitem=listitem, child=Gtk.Switch())
def do_bind(self) -> None: def __init__(self, title: str, active: bool):
"""Bind the visible property to the switch active property.""" """Initialize a VisibleRow ListBoxRow."""
self.bind_and_set_property("visible", "active", bidirectional=True) super().__init__(title=title, active=active,
child=Gtk.Box(margin_start=6, margin_end=6,
margin_top=6, margin_bottom=6,
spacing=6))
self._switch = Gtk.Switch(active=active)
self._label = Gtk.Label.new(title)
self.bind_property("active", self._switch, "active",
GObject.BindingFlags.BIDIRECTIONAL)
self.props.child.append(self._switch)
self.props.child.append(self._label)
class VisibleColumns(buttons.PopoverButton): class VisibleColumns(buttons.PopoverButton):
@ -29,20 +39,19 @@ class VisibleColumns(buttons.PopoverButton):
"""Initialize the VisibleColumns button.""" """Initialize the VisibleColumns button."""
super().__init__(columns=columns, icon_name="columns-symbolic", super().__init__(columns=columns, icon_name="columns-symbolic",
has_frame=False, **kwargs) has_frame=False, **kwargs)
self._selection = Gtk.NoSelection(model=self.columns) self.popover_child = Gtk.ListBox(selection_mode=Gtk.SelectionMode.NONE)
self.popover_child = Gtk.ColumnView(model=self._selection, self.popover_child.bind_model(columns, self.__create_func)
show_row_separators=True) self.popover_child.connect("row-activated", self.__row_activated)
self.__append_column(factory.InscriptionFactory("title"), self.popover_child.add_css_class("boxed-list")
"Column", width=125)
self.__append_column(factory.Factory(row_type=VisibleSwitch),
"Visible")
self.popover_child.add_css_class("data-table")
def __append_column(self, factory: factory.Factory, def __create_func(self, column: Gtk.ColumnViewColumn) -> VisibleRow:
title: str, *, width: int = -1) -> None: row = VisibleRow(column.get_title(), column.get_visible())
column = Gtk.ColumnViewColumn(factory=factory, title=title, row.bind_property("active", column, "visible",
fixed_width=width) GObject.BindingFlags.BIDIRECTIONAL)
self.popover_child.append_column(column) return row
def __row_activated(self, box: Gtk.ListBox, row: Gtk.ListBoxRow) -> None:
row.active = not row.active
class LoopButton(buttons.ImageToggle): class LoopButton(buttons.ImageToggle):

View File

@ -7,12 +7,62 @@ from gi.repository import Gio
from gi.repository import Gtk from gi.repository import Gtk
class TestVisibleColumnRow(unittest.TestCase):
"""Test the Visible Column ListBoxRow."""
def setUp(self):
"""Set up common variables."""
self.row = emmental.tracklist.buttons.VisibleRow("title", True)
def test_init(self):
"""Test that the VisibleRow was set up properly."""
self.assertIsInstance(self.row, Gtk.ListBoxRow)
self.assertIsInstance(self.row.props.child, Gtk.Box)
self.assertEqual(self.row.title, "title")
self.assertTrue(self.row.active)
self.assertEqual(self.row.props.child.props.margin_start, 6)
self.assertEqual(self.row.props.child.props.margin_end, 6)
self.assertEqual(self.row.props.child.props.margin_top, 6)
self.assertEqual(self.row.props.child.props.margin_bottom, 6)
self.assertEqual(self.row.props.child.props.spacing, 6)
row2 = emmental.tracklist.buttons.VisibleRow("title2", False)
self.assertEqual(row2.title, "title2")
self.assertFalse(row2.active)
def test_switch(self):
"""Test the VisibleRow switch."""
self.assertIsInstance(self.row._switch, Gtk.Switch)
self.assertEqual(self.row._switch.props.parent, self.row.props.child)
self.assertTrue(self.row._switch.props.active)
self.row.active = False
self.assertFalse(self.row._switch.props.active)
self.row._switch.props.active = True
self.assertTrue(self.row.active)
row2 = emmental.tracklist.buttons.VisibleRow("title2", False)
self.assertFalse(row2._switch.props.active)
def test_label(self):
"""Test the VisibleRow title label."""
self.assertIsInstance(self.row._label, Gtk.Label)
self.assertEqual(self.row._label.props.label, "title")
self.assertEqual(self.row._switch.get_next_sibling(),
self.row._label)
class TestVisibleColumns(unittest.TestCase): class TestVisibleColumns(unittest.TestCase):
"""Test the Visible Columns button.""" """Test the Visible Columns button."""
def setUp(self): def setUp(self):
"""Set up common variables.""" """Set up common variables."""
self.columns = Gio.ListStore() self.columns = Gio.ListStore()
self.columns.append(Gtk.ColumnViewColumn(title="title", visible=True))
self.columns.append(Gtk.ColumnViewColumn(title="title2",
visible=False))
self.button = emmental.tracklist.buttons.VisibleColumns(self.columns) self.button = emmental.tracklist.buttons.VisibleColumns(self.columns)
def test_init(self): def test_init(self):
@ -24,51 +74,37 @@ class TestVisibleColumns(unittest.TestCase):
def test_popover_child(self): def test_popover_child(self):
"""Test that the popover_child was set up properly.""" """Test that the popover_child was set up properly."""
self.assertIsInstance(self.button.popover_child, Gtk.ColumnView) self.assertIsInstance(self.button.popover_child, Gtk.ListBox)
self.assertIsInstance(self.button._selection, Gtk.NoSelection) self.assertEqual(self.button.popover_child.props.selection_mode,
self.assertTrue(self.button.popover_child.get_show_row_separators()) Gtk.SelectionMode.NONE)
self.assertTrue(self.button.popover_child.has_css_class("data-table")) self.assertTrue(self.button.popover_child.has_css_class("boxed-list"))
self.assertEqual(self.button.popover_child.get_model(), def test_create_func(self):
self.button._selection) """Test that the Gtk.ListBox creates VisibleRows correctly."""
self.assertEqual(self.button._selection.get_model(), row = self.button.popover_child.get_row_at_index(0)
self.button.columns) self.assertIsInstance(row, emmental.tracklist.buttons.VisibleRow)
self.assertEqual(row.title, "title")
self.assertTrue(row.active)
def test_columns(self): row.active = False
"""Test the popover_child columns.""" self.assertFalse(self.columns[0].get_visible())
columns = self.button.popover_child.get_columns() row.active = True
self.assertEqual(len(columns), 2) self.assertTrue(self.columns[0].get_visible())
self.columns[0].set_visible(False)
self.assertFalse(row.active)
self.assertIsInstance(columns[0].get_factory(), row = self.button.popover_child.get_row_at_index(1)
emmental.factory.InscriptionFactory) self.assertIsInstance(row, emmental.tracklist.buttons.VisibleRow)
self.assertEqual(columns[0].get_title(), "Column") self.assertEqual(row.title, "title2")
self.assertEqual(columns[0].get_fixed_width(), 125) self.assertFalse(row.active)
self.assertIsInstance(columns[1].get_factory(), def test_activate(self):
emmental.factory.Factory) """Test activating a Gtk.ListBox row."""
self.assertEqual(columns[1].get_factory().row_type, row = self.button.popover_child.get_row_at_index(0)
emmental.tracklist.buttons.VisibleSwitch) self.button.popover_child.emit("row-activated", row)
self.assertEqual(columns[1].get_title(), "Visible") self.assertFalse(row.active)
self.assertEqual(columns[1].get_fixed_width(), -1) self.button.popover_child.emit("row-activated", row)
self.assertTrue(row.active)
def test_visible_switch(self):
"""Test the visible switch widget."""
item = Gtk.Label()
listitem = Gtk.ListItem()
listitem.get_item = unittest.mock.Mock(return_value=item)
switch = emmental.tracklist.buttons.VisibleSwitch(listitem)
self.assertIsInstance(switch, emmental.factory.ListRow)
self.assertIsInstance(switch.child, Gtk.Switch)
switch.bind()
self.assertEqual(len(switch.bindings), 1)
self.assertTrue(switch.child.get_active())
item.set_visible(False)
self.assertFalse(switch.child.get_active())
switch.child.set_active(True)
item.set_visible(True)
class TestLoopButton(unittest.TestCase): class TestLoopButton(unittest.TestCase):