# # Gramps addon rule: parents of a non-birth child # # Copyright (C) 2026 Henning # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # """ Filter rule that matches people who are a parent in a family where at least one child is recorded with a NON-BIRTH relationship to THEM. The relationship type is stored per parent role on each child reference (father relation / mother relation), so the rule only looks at the person's own role. Examples: * Foster family: the child has Foster to both parents -> both match. * Step family: the child has Birth to the mother and Stepchild to the father -> only the stepfather matches. * The biological parents in the child's birth family never match through that child. Companion to "People recorded as a non-birth child" (HasNonBirthChildRelation), which lists the children themselves. """ from gramps.gen.filters.rules import Rule from gramps.gen.lib import ChildRefType from gramps.gen.const import GRAMPS_LOCALE as glocale try: _trans = glocale.get_addon_translator(__file__) except ValueError: _trans = glocale.translation _ = _trans.gettext # ------------------------------------------------------------------------- # Drop-down widget for the filter editor, supplied through the editor's # (label, factory) tuple hook (present in Gramps 5.1 through 6.1). # GTK is imported lazily so headless/CLI use never pulls in the GUI. # ------------------------------------------------------------------------- def _child_relation_widget(db): from gi.repository import Gtk class _ChildRelCombo(Gtk.ComboBox): """Shows localized names, stores the language-neutral keyword. An empty value means "any non-birth relationship".""" def __init__(self): store = Gtk.ListStore(str, str) # (display, keyword) store.append([_("(any non-birth)"), ""]) for value in ( ChildRefType.ADOPTED, ChildRefType.FOSTER, ChildRefType.STEPCHILD, ChildRefType.SPONSORED, ChildRefType.NONE, ChildRefType.UNKNOWN, ): crt = ChildRefType(value) store.append([str(crt), crt.xml_str()]) # user-defined custom types from the database try: customs = sorted(db.get_child_reference_types()) if db else [] except Exception: customs = [] for name in customs: if name: store.append([name, name]) Gtk.ComboBox.__init__(self, model=store) cell = Gtk.CellRendererText() self.pack_start(cell, True) self.add_attribute(cell, "text", 0) self.set_active(0) def get_text(self): it = self.get_active_iter() if it is None: return "" return self.get_model().get_value(it, 1) def set_text(self, val): target = (val or "").lower() for idx, row in enumerate(self.get_model()): if row[1].lower() == target: self.set_active(idx) return self.set_active(0) return _ChildRelCombo() # ------------------------------------------------------------------------- # IsNonBirthParent # ------------------------------------------------------------------------- class IsNonBirthParent(Rule): """Parents of a child recorded with a non-birth relationship to them.""" labels = [(_("Child relationship type:"), _child_relation_widget)] name = _("Parents of a non-birth child") category = _("Family filters") description = _( "Matches people who are a parent in a family where a child is " "recorded with a relationship other than 'Birth' to that parent " "(adopted, foster, step, etc.). Only the person's own role is " "checked, so biological parents are not included. Choose a single " "type from the list or '(any non-birth)' to match them all." ) def prepare(self, db, user): self.db = db self.wanted = (self.list[0] or "").strip().lower() def _rel_matches(self, rel): if self.wanted: return self.wanted in {str(rel).lower(), rel.xml_str().lower()} return int(rel) != ChildRefType.BIRTH def _apply(self, db, person): handle = person.get_handle() for fam_handle in person.get_family_handle_list(): fam = db.get_family_from_handle(fam_handle) if fam is None: continue if fam.get_father_handle() == handle: get_rel = lambda cref: cref.get_father_relation() elif fam.get_mother_handle() == handle: get_rel = lambda cref: cref.get_mother_relation() else: continue for cref in fam.get_child_ref_list(): if self._rel_matches(get_rel(cref)): return True return False # Gramps 5.x calls apply(), Gramps 6.x calls apply_to_one(). def apply(self, db, person): return self._apply(db, person) def apply_to_one(self, db, person): return self._apply(db, person)