# 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