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

View File

@ -7,12 +7,62 @@ from gi.repository import Gio
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):
"""Test the Visible Columns button."""
def setUp(self):
"""Set up common variables."""
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)
def test_init(self):
@ -24,51 +74,37 @@ class TestVisibleColumns(unittest.TestCase):
def test_popover_child(self):
"""Test that the popover_child was set up properly."""
self.assertIsInstance(self.button.popover_child, Gtk.ColumnView)
self.assertIsInstance(self.button._selection, Gtk.NoSelection)
self.assertTrue(self.button.popover_child.get_show_row_separators())
self.assertTrue(self.button.popover_child.has_css_class("data-table"))
self.assertIsInstance(self.button.popover_child, Gtk.ListBox)
self.assertEqual(self.button.popover_child.props.selection_mode,
Gtk.SelectionMode.NONE)
self.assertTrue(self.button.popover_child.has_css_class("boxed-list"))
self.assertEqual(self.button.popover_child.get_model(),
self.button._selection)
self.assertEqual(self.button._selection.get_model(),
self.button.columns)
def test_create_func(self):
"""Test that the Gtk.ListBox creates VisibleRows correctly."""
row = self.button.popover_child.get_row_at_index(0)
self.assertIsInstance(row, emmental.tracklist.buttons.VisibleRow)
self.assertEqual(row.title, "title")
self.assertTrue(row.active)
def test_columns(self):
"""Test the popover_child columns."""
columns = self.button.popover_child.get_columns()
self.assertEqual(len(columns), 2)
row.active = False
self.assertFalse(self.columns[0].get_visible())
row.active = True
self.assertTrue(self.columns[0].get_visible())
self.columns[0].set_visible(False)
self.assertFalse(row.active)
self.assertIsInstance(columns[0].get_factory(),
emmental.factory.InscriptionFactory)
self.assertEqual(columns[0].get_title(), "Column")
self.assertEqual(columns[0].get_fixed_width(), 125)
row = self.button.popover_child.get_row_at_index(1)
self.assertIsInstance(row, emmental.tracklist.buttons.VisibleRow)
self.assertEqual(row.title, "title2")
self.assertFalse(row.active)
self.assertIsInstance(columns[1].get_factory(),
emmental.factory.Factory)
self.assertEqual(columns[1].get_factory().row_type,
emmental.tracklist.buttons.VisibleSwitch)
self.assertEqual(columns[1].get_title(), "Visible")
self.assertEqual(columns[1].get_fixed_width(), -1)
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)
def test_activate(self):
"""Test activating a Gtk.ListBox row."""
row = self.button.popover_child.get_row_at_index(0)
self.button.popover_child.emit("row-activated", row)
self.assertFalse(row.active)
self.button.popover_child.emit("row-activated", row)
self.assertTrue(row.active)
class TestLoopButton(unittest.TestCase):