factory: Create ListRow and Factory classes
The ListRow class is intended to be used as a base class for displaying individual Gtk.ListView rows. The implement some helpful functionality to make it easier to bind list items to child widgets. The Factory class is designed to create ListRow widgets. Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
parent
5d3fb980af
commit
711fa0da5b
|
@ -0,0 +1,99 @@
|
|||
# Copyright 2022 (c) Anna Schumaker.
|
||||
"""A customized Gtk.SignalListItemFactory for easier use."""
|
||||
import typing
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class ListRow(GObject.GObject):
|
||||
"""Extra state that we attach to the Gtk.ListItem."""
|
||||
|
||||
listitem = GObject.Property(type=Gtk.ListItem)
|
||||
|
||||
def __init__(self, listitem: Gtk.ListItem, **kwargs):
|
||||
"""Initialize a ListRow object."""
|
||||
GObject.GObject.__init__(self, listitem=listitem, **kwargs)
|
||||
self.bindings = []
|
||||
|
||||
def do_bind(self) -> None:
|
||||
"""Bind the list item to the child widget."""
|
||||
|
||||
def do_unbind(self) -> None:
|
||||
"""Unbind the list item from the child widget."""
|
||||
|
||||
def bind_and_set(self, src: GObject.GObject, src_prop: str,
|
||||
dst: GObject.GObject, dst_prop: str,
|
||||
bidirectional: bool = False,
|
||||
invert_boolean: bool = False) -> None:
|
||||
"""Bind and set a property from the src object to the dst object."""
|
||||
f_bidir = GObject.BindingFlags.BIDIRECTIONAL if bidirectional else 0
|
||||
f_invrt = GObject.BindingFlags.INVERT_BOOLEAN if invert_boolean else 0
|
||||
|
||||
src_value = src.get_property(src_prop)
|
||||
value = not src_value if invert_boolean else src_value
|
||||
dst.set_property(dst_prop, value)
|
||||
self.bindings.append(src.bind_property(src_prop, dst, dst_prop,
|
||||
f_bidir | f_invrt))
|
||||
|
||||
def bind_and_set_property(self, item_prop: str, child_prop: str,
|
||||
bidirectional: bool = False,
|
||||
invert_boolean: bool = False) -> None:
|
||||
"""Bind and set a list item property."""
|
||||
self.bind_and_set(self.item, item_prop, self.child, child_prop,
|
||||
bidirectional, invert_boolean)
|
||||
|
||||
def bind(self) -> None:
|
||||
"""Bind the list item to the child widget."""
|
||||
self.do_bind()
|
||||
|
||||
def unbind(self) -> None:
|
||||
"""Unbind the list item from the child widget."""
|
||||
for binding in self.bindings:
|
||||
binding.unbind()
|
||||
self.bindings.clear()
|
||||
self.do_unbind()
|
||||
|
||||
@GObject.Property(type=Gtk.Widget)
|
||||
def child(self) -> Gtk.Widget | None:
|
||||
"""Get the child widget displayed by this Row."""
|
||||
return self.listitem.get_child()
|
||||
|
||||
@child.setter
|
||||
def child(self, newval: Gtk.Widget) -> None:
|
||||
self.listitem.set_child(newval)
|
||||
|
||||
@GObject.Property(type=GObject.TYPE_PYOBJECT)
|
||||
def item(self) -> GObject.TYPE_PYOBJECT:
|
||||
"""Get the list item for this Row."""
|
||||
return self.listitem.get_item()
|
||||
|
||||
|
||||
class Factory(Gtk.SignalListItemFactory):
|
||||
"""A customized Factory for making list row widgets."""
|
||||
|
||||
def __init__(self, row_type: typing.Type[ListRow], **kwargs):
|
||||
"""Initialize a ListFactory."""
|
||||
super().__init__()
|
||||
self.row_type = row_type
|
||||
|
||||
self.connect("setup", self.__setup, kwargs)
|
||||
self.connect("bind", self.__bind)
|
||||
self.connect("unbind", self.__unbind)
|
||||
self.connect("teardown", self.__teardown)
|
||||
|
||||
def __setup(self, factory: Gtk.SignalListItemFactory,
|
||||
listitem: Gtk.ListItem, kwargs: dict) -> None:
|
||||
listitem.listrow = self.row_type(listitem, **kwargs)
|
||||
|
||||
def __bind(self, factory: Gtk.SignalListItemFactory,
|
||||
listitem: Gtk.ListItem) -> None:
|
||||
listitem.listrow.bind()
|
||||
|
||||
def __unbind(self, factory: Gtk.SignalListItemFactory,
|
||||
listitem: Gtk.ListItem) -> None:
|
||||
listitem.listrow.unbind()
|
||||
|
||||
def __teardown(self, factory: Gtk.SignalListItemFactory,
|
||||
listitem: Gtk.ListItem) -> None:
|
||||
listitem.set_child(None)
|
||||
listitem.listrow = None
|
|
@ -0,0 +1,114 @@
|
|||
# Copyright 2022 (c) Anna Schumaker.
|
||||
"""Tests our Factory implementation."""
|
||||
import unittest
|
||||
import unittest.mock
|
||||
import emmental.factory
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GObject
|
||||
|
||||
|
||||
class TestListRow(unittest.TestCase):
|
||||
"""Test the ListRow object."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.item = Gtk.Label(label="Test")
|
||||
self.listitem = Gtk.ListItem()
|
||||
self.listitem.get_item = unittest.mock.Mock(return_value=self.item)
|
||||
|
||||
self.row = emmental.factory.ListRow(self.listitem, child=Gtk.Label())
|
||||
self.row.do_bind = unittest.mock.Mock(wraps=self.row.do_bind)
|
||||
self.row.do_unbind = unittest.mock.Mock(wraps=self.row.do_unbind)
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the ListRow is set up properly."""
|
||||
self.assertIsInstance(self.row, GObject.GObject)
|
||||
self.assertIsInstance(self.listitem.get_child(), Gtk.Label)
|
||||
|
||||
self.assertListEqual(self.row.bindings, [])
|
||||
self.assertEqual(self.row.listitem, self.listitem)
|
||||
self.assertEqual(self.row.child, self.listitem.get_child())
|
||||
self.assertEqual(self.row.item, self.item)
|
||||
|
||||
def test_bind(self):
|
||||
"""Test calling bind() on the ListRow."""
|
||||
self.row.bind()
|
||||
self.row.do_bind.assert_called()
|
||||
|
||||
def test_bind_and_set_property(self):
|
||||
"""Test the ListRow bind_property() function."""
|
||||
self.row.bind_and_set_property("label", "label")
|
||||
self.assertEqual(self.row.child.get_text(), "Test")
|
||||
self.assertEqual(len(self.row.bindings), 1)
|
||||
self.assertIsInstance(self.row.bindings[0], GObject.Binding)
|
||||
|
||||
self.item.set_text("Text 2")
|
||||
self.assertEqual(self.row.child.get_text(), "Text 2")
|
||||
|
||||
self.row.child.set_text("Other Text")
|
||||
self.assertEqual(self.item.get_text(), "Text 2")
|
||||
|
||||
def test_bind_bidirectional(self):
|
||||
"""Test bidirectional bindings."""
|
||||
self.row.bind_and_set_property("label", "label", bidirectional=True)
|
||||
self.assertEqual(self.row.child.get_text(), "Test")
|
||||
self.row.child.set_text("Other Text")
|
||||
self.assertEqual(self.item.get_text(), "Other Text")
|
||||
|
||||
def test_bind_invert_boolean(self):
|
||||
"""Test invert boolean bindings."""
|
||||
self.row.bind_and_set_property("sensitive", "sensitive",
|
||||
invert_boolean=True)
|
||||
self.assertFalse(self.row.child.get_sensitive())
|
||||
self.item.set_sensitive(False)
|
||||
self.assertTrue(self.row.child.get_sensitive())
|
||||
|
||||
def test_unbind(self):
|
||||
"""Test unbinding a ListRow."""
|
||||
self.row.unbind()
|
||||
self.row.do_unbind.assert_called()
|
||||
self.assertEqual(len(self.row.bindings), 0)
|
||||
|
||||
|
||||
class TestFactory(unittest.TestCase):
|
||||
"""Test a Factory."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.item = Gtk.Label(label="Text")
|
||||
self.listitem = Gtk.ListItem()
|
||||
self.listitem.get_item = unittest.mock.Mock(return_value=self.item)
|
||||
self.factory = emmental.factory.Factory(
|
||||
row_type=emmental.factory.ListRow)
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the ListFactory is set up properly."""
|
||||
self.assertIsInstance(self.factory, Gtk.SignalListItemFactory)
|
||||
self.assertEqual(self.factory.row_type, emmental.factory.ListRow)
|
||||
|
||||
def test_setup(self):
|
||||
"""Test the setup signal."""
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.assertIsInstance(self.listitem.listrow,
|
||||
emmental.factory.ListRow)
|
||||
|
||||
def test_bind(self):
|
||||
"""Test the bind signal."""
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.listitem.listrow.bind = unittest.mock.Mock()
|
||||
self.factory.emit("bind", self.listitem)
|
||||
self.listitem.listrow.bind.assert_called()
|
||||
|
||||
def test_unbind(self):
|
||||
"""Test the unbind signal."""
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.listitem.listrow.unbind = unittest.mock.Mock()
|
||||
self.factory.emit("unbind", self.listitem)
|
||||
self.listitem.listrow.unbind.assert_called()
|
||||
|
||||
def test_teardown(self):
|
||||
"""Test the teardown signal."""
|
||||
self.factory.emit("setup", self.listitem)
|
||||
self.factory.emit("teardown", self.listitem)
|
||||
self.assertIsNone(self.listitem.get_child())
|
||||
self.assertIsNone(self.listitem.listrow)
|
Loading…
Reference in New Issue