factory: Create a TreeRow

The TreeRow is used to display rows from a Gtk.TreeListModel. I adjust
the TreeRow "item" and "child" properties so they still access the
underlying item or child widget instead of the Gtk.TreeRow or
Gtk.TreeExpander.

I also give the TreeRow widget "n-children" and "have-children" properties
which are used to dynamically show or hide the expander arrow when there
aren't any children.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2022-10-17 15:24:12 -04:00
parent 40c463da81
commit 9d7763a730
2 changed files with 118 additions and 0 deletions

View File

@ -2,6 +2,7 @@
"""A customized Gtk.SignalListItemFactory for easier use.""" """A customized Gtk.SignalListItemFactory for easier use."""
import typing import typing
from gi.repository import GObject from gi.repository import GObject
from gi.repository import Gio
from gi.repository import Gtk from gi.repository import Gtk
@ -86,6 +87,54 @@ class InscriptionRow(ListRow):
self.bind_and_set_property(self.item_property, "text") self.bind_and_set_property(self.item_property, "text")
class TreeRow(ListRow):
"""A ListRow for displaying child widgets in a Tree."""
n_children = GObject.Property(type=int)
have_children = GObject.Property(type=bool, default=False)
indented = GObject.Property(type=bool, default=True)
def __init__(self, listitem: Gtk.ListItem, **kwargs) -> None:
"""Create a new TreeRow."""
super().__init__(listitem, **kwargs)
listitem.set_child(Gtk.TreeExpander(hide_expander=True,
indent_for_icon=self.indented))
self.bind_property("n-children", self, "have-children")
self.bind_property("have-children", listitem.get_child(),
"hide-expander",
GObject.BindingFlags.INVERT_BOOLEAN)
self.bind_property("indented", listitem.get_child(), "indent-for-icon")
def bind(self) -> None:
"""Bind a TreeRow to the TreeExpander."""
self.listitem.get_child().set_list_row(self.listitem.get_item())
super().bind()
def bind_n_children(self, children: Gio.ListModel | None) -> None:
"""Bind to the n-items property of the child listmodel."""
if children is not None:
self.bind_and_set(children, "n-items", self, "n-children")
def unbind(self) -> None:
"""Unbind a TreeRow from the TreeExpander."""
self.listitem.get_child().set_list_row(None)
super().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().get_child()
@child.setter
def child(self, newval=Gtk.Widget) -> None:
self.listitem.get_child().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().get_item()
class Factory(Gtk.SignalListItemFactory): class Factory(Gtk.SignalListItemFactory):
"""A customized Factory for making list row widgets.""" """A customized Factory for making list row widgets."""

View File

@ -3,6 +3,7 @@
import unittest import unittest
import unittest.mock import unittest.mock
import emmental.factory import emmental.factory
from gi.repository import Gio
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import GObject from gi.repository import GObject
@ -102,6 +103,74 @@ class TestInscriptionRow(unittest.TestCase):
self.assertTrue(row.child.has_css_class("numeric")) self.assertTrue(row.child.has_css_class("numeric"))
class TestTreeRow(unittest.TestCase):
"""Test our pre-configured TreeRow."""
def setUp(self):
"""Set up common variables."""
self.item = Gtk.Label(label="Test")
self.child = Gtk.Label()
self.treerow = Gtk.TreeListRow()
self.treerow.get_item = unittest.mock.Mock(return_value=self.item)
self.listitem = Gtk.ListItem()
self.listitem.get_item = unittest.mock.Mock(return_value=self.treerow)
self.row = emmental.factory.TreeRow(self.listitem)
def test_tree_row(self):
"""Test that the TreeRow works as expected."""
self.assertIsInstance(self.row, emmental.factory.ListRow)
self.assertIsInstance(self.listitem.get_child(), Gtk.TreeExpander)
self.assertEqual(self.row.item, self.item)
self.row.child = self.child
self.assertEqual(self.listitem.get_child().get_child(), self.child)
self.assertEqual(self.row.child, self.child)
self.row.bind()
self.assertEqual(self.listitem.get_child().get_list_row(),
self.treerow)
self.row.unbind()
self.assertIsNone(self.listitem.get_child().get_list_row())
def test_indented(self):
"""Test the indended property."""
self.assertTrue(self.row.indented)
self.assertTrue(self.listitem.get_child().get_indent_for_icon())
self.row.indented = False
self.assertFalse(self.listitem.get_child().get_indent_for_icon())
row2 = emmental.factory.TreeRow(self.listitem, indented=False)
self.assertFalse(row2.indented)
self.assertFalse(self.listitem.get_child().get_indent_for_icon())
def test_bind_n_children(self):
"""Test binding to the n_children property."""
expander = self.listitem.get_child()
liststore = Gio.ListStore()
self.assertEqual(self.row.n_children, 0)
self.assertFalse(self.row.have_children)
self.assertTrue(expander.get_hide_expander())
self.row.n_children = 1
self.assertTrue(self.row.have_children)
self.assertFalse(expander.get_hide_expander())
self.row.bind_n_children(None)
self.assertEqual(len(self.row.bindings), 0)
self.row.bind_n_children(liststore)
self.assertEqual(self.row.n_children, 0)
self.assertTrue(expander.get_hide_expander())
liststore.append(Gtk.Label())
self.assertEqual(self.row.n_children, 1)
self.assertFalse(expander.get_hide_expander())
class TestFactory(unittest.TestCase): class TestFactory(unittest.TestCase):
"""Test a Factory.""" """Test a Factory."""