From 9d7763a730ead6ea1ad5da9139442ec3ffaa8950 Mon Sep 17 00:00:00 2001 From: Anna Schumaker Date: Mon, 17 Oct 2022 15:24:12 -0400 Subject: [PATCH] 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 --- emmental/factory.py | 49 ++++++++++++++++++++++++++++++ tests/test_factory.py | 69 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/emmental/factory.py b/emmental/factory.py index 3812da6..a9ba319 100644 --- a/emmental/factory.py +++ b/emmental/factory.py @@ -2,6 +2,7 @@ """A customized Gtk.SignalListItemFactory for easier use.""" import typing from gi.repository import GObject +from gi.repository import Gio from gi.repository import Gtk @@ -86,6 +87,54 @@ class InscriptionRow(ListRow): 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): """A customized Factory for making list row widgets.""" diff --git a/tests/test_factory.py b/tests/test_factory.py index 217fb17..5db4d1e 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -3,6 +3,7 @@ import unittest import unittest.mock import emmental.factory +from gi.repository import Gio from gi.repository import Gtk from gi.repository import GObject @@ -102,6 +103,74 @@ class TestInscriptionRow(unittest.TestCase): 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): """Test a Factory."""