# Copyright 2022 (c) Anna Schumaker. """A customized Gtk.SignalListItemFactory for easier use.""" import typing from gi.repository import GObject from gi.repository import Gio 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_active(self, item_prop: str) -> None: """Bind a property to the Row's active property.""" self.bind_and_set(self.item, item_prop, self, "active") 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=bool, default=False) def active(self) -> bool: """Get the active state of this Row.""" if self.listrow is not None: return self.listrow.has_css_class("emmental-active-row") return False @active.setter def active(self, newval: bool) -> None: if self.listrow is not None: if newval: self.listrow.add_css_class("emmental-active-row") else: self.listrow.remove_css_class("emmental-active-row") @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() @GObject.Property(type=Gtk.Widget) def listrow(self) -> Gtk.Widget: """Get the listrow widget that our child widget is contained in.""" return self.listitem.props.child.props.parent class InscriptionRow(ListRow): """A ListRow for displaying Gtk.Inscription widgets.""" item_property = GObject.Property(type=str) def __init__(self, listitem: Gtk.ListItem, item_property: str, xalign: float = 0.0, numeric: bool = False) -> None: """Create a new Gtk.Label.""" super().__init__(listitem, item_property=item_property) self.child = Gtk.Inscription(xalign=xalign) if numeric: self.child.add_css_class("numeric") def do_bind(self) -> None: """Bind a ListItem to the Label.""" 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.""" 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 class InscriptionFactory(Factory): """A Factory that creates InscriptionRows.""" def __init__(self, item_property: str, script_type: typing.Type[InscriptionRow] = InscriptionRow, xalign: float = 0.0, numeric: bool = False): """Initialize a LabelFactory.""" super().__init__(row_type=script_type, item_property=item_property, xalign=xalign, numeric=numeric)