Filter - any take over/help?

Context:

I AM A STROKE SURVIVOR (April)

I may have difficulty speaking or confuse my words.

Please be patient.

I’ll write this filter “Flat Filter”.


It’s like called like Ancestry Search.

Like this Stroke, and I not maintain this feature/code.

This that any:

That take over (326 lines) code? This than any over this over ?

Paul, I am sorrowful to learn of your stroke but ever so grateful for your survival. :folded_hands:

Was your Flat Filter gramplet an outgrowth of some filtering performance concerns? Such as the concern mentioned in Dec. 2022:

Is the code available somewhere to review?

(I recall reading mentions of experimenting with forking to customize Gramps 5.0 in the Gramps User mailing list. And back in 2012, sharing some code for ‘finessing a DOB’ . But I don’t recall a public repository.)

Was your washing to filter joins - person->parent, was person->spouse, was person->childs, parent->child.

Wash your joins, had using.

(was didn’t has work 2023, AND had stroke!!)

# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2010  Doug Blank <doug.blank@gmail.com>
# Copyright (C) 2011  Nick Hall
# Copyright (C) 2011       Tim G L Lyons
#
# 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#

#-------------------------------------------------------------------------
#
# Gramps modules
#
#-------------------------------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext
from gramps.gen.plug import Gramplet

from gramps.gen.filters.rules import Rule
from gramps.gen.filters.rules.person import RegExpName
from gramps.gen.lib.person import Person

class _RegExpNameList(Rule):
    """Rule that checks for full or partial name matches"""

    labels      = [_('Text:')]
    name        = _('People with a name matching <text>')
    description = _("Matches people's names containing a substring or "
                    "matching a regular expression")
    category    = _('General filters')
    allow_regex = True

    def field_list(self, name):
        raise NotImplementedError;

    def apply(self,db,person):
        for name in [person.get_primary_name()] + person.get_alternate_names():
            for field in self.field_list(name):
                if self.match_substring(0, field):
                    return True
        else:
            return False

class RegExpPersonal(_RegExpNameList):
    def field_list(self, name):
        return [name.first_name, name.title, name.title, name.nick]

class RegExpFamily(_RegExpNameList):
    def field_list(self, name):
        return [name.get_surname(), name.suffix, name.title, name.famnick, name.call]

#this is all slightly nasty, mixing composition and inheritance models

class _HasNamedRelation(Rule):
    labels      = [ _('Filter name:') ]
    name        = _('Children of name match')
    category    = _('Family filters')
    description = _("Matches children of anybody with a given name")

    def __init__(self, arg, name_matcher, use_regex=False):
        super().__init__(arg, use_regex)
        self.name_matcher = name_matcher(arg, use_regex)

    def prepare(self, db, user):
        self.name_matcher.requestprepare(db, user)

    def reset(self):
        self.name_matcher.requestreset()

    def get_rel_list(self, db, person):
        raise NotImplementedError;

    def get_spouse_list(self, db, person):
        all = [];
        for fam_id in person.get_family_handle_list():
            fam = db.get_family_from_handle(fam_id)
            if fam:
                for spouse_id in [fam.get_father_handle(), 
                                  fam.get_mother_handle()]:
                    if not spouse_id:
                        continue
                    if spouse_id == person.handle:
                        continue
                    all.append(spouse_id);
        return all;

    def apply(self, db, person):
        for rel_id in self.get_rel_list(db, person):
            if rel_id:
                rel = db.get_person_from_handle(rel_id)
                if self.name_matcher.apply(db, rel):
                    return True;
        return False;

class _HasNamedParent(_HasNamedRelation):
    def get_parent_familys(self, db, person):
        all = [];
        for fam_id in person.get_parent_family_handle_list():
            fam = db.get_family_from_handle(fam_id)
            if fam:
                all.append(fam);
        return all;

class HasNamedFather(_HasNamedParent):
    def get_rel_list(self, db, person):
        return map(lambda fam: fam.get_father_handle(), self.get_parent_familys(db, person));

class HasNamedMother(_HasNamedParent):
    def get_rel_list(self, db, person):
        return map(lambda fam: fam.get_mother_handle(), self.get_parent_familys(db, person));

class IsSiblingofNamedSibling(_HasNamedRelation):
    def get_rel_list(self, db, person):
        all = [];
        fam_id = person.get_main_parents_family_handle() # or all familys, per above?
        fam = db.get_family_from_handle(fam_id)
        if fam:
            for child_ref in fam.get_child_ref_list():
                if child_ref and child_ref.ref != person.handle:
                    all.append(child_ref.ref);
        return all;
                
class HasNamedChild(_HasNamedRelation):
    def get_rel_list(self, db, person):
        all = [];
        for fam_id in person.get_family_handle_list():
            fam = db.get_family_from_handle(fam_id)
            if fam:
                for child_ref in fam.get_child_ref_list():
                    if child_ref:
                        all.append(child_ref.ref);
        return all;

class HasNamedSpouse(_HasNamedRelation):
    def get_rel_list(self, db, person):
        return self.get_spouse_list(db, person)

class HasName(_HasNamedRelation):
    def get_rel_list(self, db, person):
        if(person.gender == Person.FEMALE and isinstance(self.name_matcher, RegExpFamily)):
            # for female surnames, we want to trawl the spouses surnames
            all = self.get_spouse_list(db, person)
            all.append(person.handle)
            return all
        else:
            return [ person.handle ]
                
from gi.repository import Gtk

from gramps.gui import widgets
from gramps.gen.lib import Date, Event, EventType
from gramps.gen.datehandler import displayer
from gramps.gui.filters import build_filter_model
from gramps.gui.filters.sidebar import SidebarFilter
from gramps.gen.filters import GenericFilter, rules
from gramps.gen.filters.rules.person import ProbablyAlive

def extract_text(entry_widget):
    """
    Extract the text from the entry widget, strips off any extra spaces, 
    and converts the string to unicode. 
    
    For some strange reason a gtk bug prevents the extracted string from being 
    of type unicode.
    
    """
    return str(entry_widget.get_text().strip())

# leverage to split the name into fore and aft
class SearchableNamePair:
    def __init__(self, label, rule_class):
        self.widget_personal = widgets.BasicEntry()
        self.widget_family = widgets.BasicEntry()
        self.label = label
        self.rule_class = rule_class

    def place(self, sidebar):
        # container.add_text_entry(self.label, self.widget_personal)
        # self.add_text_entry(container, self.label, self.widget_personal)
        # unrolled

        sidebar.grid.attach(widgets.BasicLabel(self.label), 1, sidebar.position, 1, 1)

        self.widget_personal.set_hexpand(True)
        sidebar.grid.attach(self.widget_personal, 2, sidebar.position, 1, 1)
        self.widget_personal.connect('key-press-event', sidebar.key_press)

        self.widget_family.set_hexpand(True)
        sidebar.grid.attach(self.widget_family, 3, sidebar.position, 1, 1)
        self.widget_family.connect('key-press-event', sidebar.key_press)
        sidebar.position += 1

    def clear(self):
        self.widget_personal.set_text('')
        self.widget_family.set_text('')

    def _add_to_filter(self, generic_filter, regex, widget, search_class):
        v = extract_text(widget)
        if(v):
            rule = self.rule_class([ v ], search_class, use_regex=regex)
            generic_filter.add_rule(rule)

    def add_to_filter(self, generic_filter, regex):
        self._add_to_filter(generic_filter, regex, self.widget_personal, RegExpPersonal)
        self._add_to_filter(generic_filter, regex, self.widget_family, RegExpFamily)


#-------------------------------------------------------------------------
#
# PersonSidebarFilter class
#
#-------------------------------------------------------------------------
class PersonSidebarFilter2(SidebarFilter):

    def __init__(self, dbstate, uistate, clicked):
        self.clicked_func = clicked

        self.names = [
            SearchableNamePair(_("Father"), HasNamedFather),
            SearchableNamePair(_("Mother"), HasNamedMother),
            SearchableNamePair(_("Name"), HasName),
            SearchableNamePair(_("Spouse"), HasNamedSpouse),
            SearchableNamePair(_("Sibling"), IsSiblingofNamedSibling),
            SearchableNamePair(_("Sibling"), IsSiblingofNamedSibling),
            SearchableNamePair(_("Child"), HasNamedChild),
            SearchableNamePair(_("Child"), HasNamedChild)
        ]
        self.filter_alive = widgets.DateEntry(uistate, [])
            
        self.filter_regex = Gtk.CheckButton(label=_('Use regular expressions'))

        SidebarFilter.__init__(self, dbstate, uistate, "Person")

    def create_widget(self):
        exdate1 = Date()
        exdate2 = Date()
        exdate1.set(Date.QUAL_NONE, Date.MOD_RANGE, 
                    Date.CAL_GREGORIAN, (0, 0, 1800, False, 
                                                0, 0, 1900, False))
        exdate2.set(Date.QUAL_NONE, Date.MOD_BEFORE, 
                    Date.CAL_GREGORIAN, (0, 0, 1850, False))

        msg1 = displayer.display(exdate1)
        msg2 = displayer.display(exdate2)

        for w in self.names:
            w.place(self)

        self.add_text_entry(_('Probably Alive'), self.filter_alive, 
                            _('example: "%(msg1)s" or "%(msg2)s"') % {'msg1':msg1, 'msg2':msg2})
        self.add_regex_entry(self.filter_regex)

    def clear(self, obj):
        for w in self.names:
            w.clear()
        self.filter_alive.set_text('')

    def get_filter(self):
        """
        Extracts the text strings from the sidebar, and uses them to build up
        a new filter.
        """

        regex = self.filter_regex.get_active()

        # build a GenericFilter
        generic_filter = GenericFilter()
        for w in self.names:
            w.add_to_filter(generic_filter, regex)

        alive = extract_text(self.filter_alive)
        if alive:
            rule = ProbablyAlive([alive])
            generic_filter.add_rule(rule)

        return generic_filter

#-------------------------------------------------------------------------
#
# Filter class
#
#-------------------------------------------------------------------------
class Filter2(Gramplet):
    """
    The base class for all filter gramplets.
    """
    FILTER_CLASS = None
    
    def init(self):
        self.filter = self.FILTER_CLASS(self.dbstate, self.uistate,
                                        self.__filter_clicked)
        self.widget = self.filter.get_widget()                          
        self.gui.get_container_widget().remove(self.gui.textview)
        self.gui.get_container_widget().add(self.widget)
        self.widget.show_all()

    def __filter_clicked(self):
        """
        Called when the filter apply button is clicked.
        """
        self.gui.view.generic_filter = self.filter.get_filter()
        self.gui.view.build_tree()

#-------------------------------------------------------------------------
#
# PersonFilter class
#
#-------------------------------------------------------------------------
class FlatFilter(Filter2):
    """
    A gramplet providing a Person Filter.
    """
    FILTER_CLASS = PersonSidebarFilter2


@GaryGriffin and @Nick-Hall

This is a compelling example of a “New Maintainer” request. Can we make a final determination on how to mark a .gpr.py as such? It might possibly be reasonable to set as an experimental or beta status for any addon that was created for private use and usability has not been vetted by multiple users.

Then publish through the addons-source repo (after creating the necessary .gpr.py and README.md file)?

I can pick this up and revise, polish as needed.

filter2.gpr.py

Gramps - a GTK+/GNOME based genealogy program



Copyright (C) 2015      Nick Hall



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.



This program is distributed in the hope that it will be useful,

but WITHOUT ANY WARRANTY; without even the implied warranty of

MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the

GNU General Public License for more details.



You should have received a copy of the GNU General Public License

along with this program; if not, write to the Free Software

Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.



"""
Gramps registration file
"""

#------------------------------------------------------------------------



Filter2



#------------------------------------------------------------------------

register(GRAMPLET,
id="Person Flat Filter",
name=_("Person Flat Filter"),
description = ("Gramplet providing a person filter"),
version="1.0.0",
gramps_target_version="5.1",
status = STABLE,
fname="filter2.py",
height=200,
gramplet = 'FlatFilter',
gramplet_title=("Flat Filter"),
navtypes=["Person"],
)

@bugbear
It is a nice filter. Thank you for sharing it.

I like being able to simultaneously search for persons with more than 1 sibling or offspring name.

Here’s one tweaked for the 6.x version. It has placeholder prompts for the unlabeled fields and re-labelings indicating that the 2 rows of siblings and children are intentional:


(see the README.md documentation)

I have a 2 filter custom that looks for women by their first name and their husband’s surname. Have to reset the paremeters in both filters for new searches. This will make it so much easier.

Looking forward to the 6.1 incarnation.

Thanks to @emyoulation knowing the changes in filters needed to run on 6.0 and 6.1 beta, I was able to install the gramplet.

I just did a test with Father’s surname and Mother’s Given name. It was for the great-grandmother I worked on today. The filter returned 0 people. Even adding her maiden name (which defeats the reason for the search) still returned 0 people. ???

Okay. I should have used the women’s given name in the Name option and the surname of the Spouse. That returned the person I wanted.

But my original test, Father’s surname and Mother’s Given name, should have return their 8 children. 0 people were returned. Removing the Father’s surname parameter, returned 122 people with the mother’s name “Beulah”, including her 8.

Personally, If dealing with a known father and mother data, I would have opted to used the Family category’s standard Filter gramplet instead of this new one. It has single field entry to do inclusive searches across Alternative names, Nicknames, prefixes, suffixes… whatever.

The more intriguing capability here is when you know a couple sibs or offspring. Or when they might have been alive.

My original 2 part custom filter was to find “Mrs Mary Smith”. A filter to find any Mary’s who married Smith’s. I used my 4th great-grandmother in the test because her record was the active and staring at me.

But using the Father and Mother fields of the Flat Filter would not be good for that. It would find the offspring of those 'Mary’s who married 'Smith’s, not the 'Mary’s.

That is what I now understand. The Name and Spouse fields return the individuals; the Mary’s who married a Smith.

But in my test with my great-grandmother, it should have returned her children. It did not!

I have fixes for 6.1. will post on GitHub shortly