Develop an Add-on Rule

From Gramps
Revision as of 16:54, 23 September 2022 by Bamaustin (talk | contribs) (Sample Gramps Plug-in source file)
Jump to: navigation, search
Gnome-important.png
🚧 Work In Progress

This wikipage is a cloned outline from a template. It is being roughed in.

Since you are reading this paragraph, then the WikiContributor has not progressed to the point of trimming out the excess template material. That means the wikipage is probably not ready for collaborative editing yet. Multiple people editing now might unintentionally overwrite their work. Please post your suggestion on the Discussion page instead of directly editing the content.

Custom Filters are built upon Query Rules. Sometimes a filter rule has not been provided to search the part of the Tree desired. Or the existing Rules can not be combined to isolate the desired part of the Tree. If those particular must be repeatedly isolated, building a query rule might be the next logical step.

coding style guide (placeholder)

In various rules that allow a Person ID to be selected as a parameter value, both built-in rules & add-on rules use inconsistent placeholders:

  • <Id>
  • <id>
  • <person>

It would be better if they were harmonized.

I suspect any variant of <ID> would ambiguous because sometimes these rules (while needing a Person Gramps ID) are in different category custom rules.

What should the standard nomenclature be for the rule names, descriptions & documentation?

Using add-ons to design and test flight Filter Rule

Designing and optimizing a quality query can be challenging. Even a slow rough-cut query is acceptable for a single-use. But clean and fast code is vital to a frequently used add-on Filter Rule.

There are some power tool add-ons to help when designing queries, testing them and optimize their runtimes.

Particularly, using SuperTool design a rough query, the Query Gramplet to experiment with optimizations, the Generic custom rules to commit the query to a something usable with Custom Filters, and FilterParams to tune is one way to use add-ons in concert.

Converting a query to an add-on Filter Rule

Sample Gramps Plug-in Registration file

infamilyrule.gpr.py

#
# GPR (Gramps plug-in Registration) of a Python module
#    for Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2020  Paul Culley
# Copyright (C) 2020  Matthias Kemmer
#
# 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.
#
"""Filter rule that matches people who are matched by a family filter."""

register(RULE,
  id = 'PersonsInFamilyFilterMatch',
  name = _('People who are part of families matching <filter>'),
  description = _('People who are part of families matching <filter>'),
  version = '1.0.9',
  authors = ["Matthias Kemmer", "Paul Culley"],
  authors_email = ["[email protected]", "[email protected]"],
  gramps_target_version = '5.1',
  status = STABLE,
  fname = "infamilyrule.py",
  ruleclass = 'PersonsInFamilyFilterMatch',  # must match the rule class name
  namespace = 'Person',  # one of the primary object classes
  )

Sample Gramps Plug-in source file

infamilyrule.py

Filenames or Filenames

#
#
# plug-in Python module
#    for Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2020  Paul Culley
# Copyright (C) 2020  Matthias Kemmer
#
# 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.
#

"""Filter rule to match persons in a matching family."""

# -------------------------------------------------------------------------
#
# Gramps modules
#
# -------------------------------------------------------------------------
from gramps.gui.editors.filtereditor import MyBoolean, MyFilters
from gramps.gen.filters.rules._matchesfilterbase import MatchesFilterBase
from gramps.gen.const import GRAMPS_LOCALE as glocale
try:
    _trans = glocale.get_addon_translator(__file__)
except ValueError:
    _trans = glocale.translation
_ = _trans.gettext

# These globals are the only easy way I could think of to communicate between
# checkboxes to make sure at least one was selected.
child_state = True  # used to indicate state of checkbox
parent_state = True


class IncChildren(MyBoolean):
    """Include children."""

    def __init__(self, db):
        MyBoolean.__init__(self, _("Include Children"))
        self.set_tooltip_text(_("Include the children in the matching"
                                " families."))
        self.connect("toggled", self.toggled)
        self.set_active(True)

    def toggled(self, widget):
        """Make sure user doesn't get to turn off both children and parents."""
        if not parent_state:
            if not widget.get_active():
                widget.set_active(True)
        global child_state
        child_state = widget.get_active()
        # print("child:", child_state)

    def set_text(self, val):
        """Set the selector state to display the passed value."""
        is_active = bool(int(val))
        self.set_active(is_active)
        global child_state
        child_state = is_active


class IncParents(MyBoolean):
    """Provide a negation switch."""

    def __init__(self, db):
        MyBoolean.__init__(self, _("Include Parents"))
        self.set_tooltip_text(_("Include the parents in the matching"
                                " families."))
        self.connect("toggled", self.toggled)
        self.set_active(True)

    def toggled(self, widget):
        """Make sure user doesn't get to turn off both children and parents."""
        if not child_state:
            if not widget.get_active():
                widget.set_active(True)
        global parent_state
        parent_state = widget.get_active()
        # print("parent:", parent_state)

    def set_text(self, val):
        """Set the selector state to display the passed value."""
        is_active = bool(int(val))
        self.set_active(is_active)
        global parent_state
        parent_state = is_active


class FamFilt(MyFilters):
    """Add custom family filter selector."""

    # This is a horrible hack that is needed because the filtereditor doesn't
    # have support for a 'Family Filter name' selector. So we have to make our
    # own. Furthermore, we don't have the needed reference to the 'filterdb',
    # the list of custom filters.
    def __init__(self, db):
        import inspect
        stack = inspect.stack()  # our stack frame
        caller_locals = stack[1][0].f_locals  # locals from caller
        # the caller has an attribute 'filterdb' which has what we need
        MyFilters.__init__(self,
                           caller_locals["filterdb"].get_filters('Family'))


# -------------------------------------------------------------------------
#
# Person part of matching family
#
# -------------------------------------------------------------------------
class PersonsInFamilyFilterMatch(MatchesFilterBase):
    """Rule that checks for a person with a selected event role."""

    labels = [(_('Family Filter name:'), FamFilt),
              (_('Include Children'), IncChildren),
              (_('Include Parents'), IncParents)]
    name = _('People who are part of families matching <filter>')
    description = _("People who are part of families matching <filter>")
    category = _('General filters')
    # we want to have this filter show family filters
    namespace = 'Family'

    def prepare(self, db, user):
        """Prepare a reference list for the filter."""
        self.persons = set()
        MatchesFilterBase.prepare(self, db, user)
        self.MFF_filt = self.find_filter()
        if self.MFF_filt:
            for family_handle in db.iter_family_handles():
                if self.MFF_filt.check(db, family_handle):
                    family = db.get_family_from_handle(family_handle)
                    if bool(int(self.list[2])):
                        father = family.get_father_handle()
                        mother = family.get_mother_handle()
                        self.persons.add(father)
                        self.persons.add(mother)
                    if bool(int(self.list[1])):
                        for child_ref in family.get_child_ref_list():
                            self.persons.add(child_ref.ref)

    def apply(self, _db, obj):
        """
        Return True if a person applies to the filter rule.

        :returns: True or False
        """
        if obj.get_handle() in self.persons:
            return True
        return False

Publishing a new Filter Rule

Update the Rule Expansions wiki page

See also

  • In in MantisBT bug reporter for Gramps
    • Feature Requests: search for add-on custom filter Rules. (Filter for "filter, rule")
    • 0011689: [GrampsAIO-5.1.2-new_libs_win64] Active and Default Person filter rules