From b537712152764b95392cd094cacdfe6dae6e6337 Mon Sep 17 00:00:00 2001 From: Don Allingham Date: Sat, 2 Nov 2002 21:02:27 +0000 Subject: [PATCH] HasCommonANcestorWith patch from Alexandre Duret-Lutz svn: r1159 --- gramps/src/GenericFilter.py | 109 +++++++++++++++++++--------- gramps/src/RelLib.py | 30 +++++--- gramps/src/Utils.py | 32 ++++++++ gramps/src/plugins/GraphViz.py | 9 ++- gramps/src/plugins/IndivComplete.py | 16 ++-- gramps/src/plugins/WebPage.py | 9 ++- gramps/src/plugins/WriteGedcom.py | 9 ++- 7 files changed, 152 insertions(+), 62 deletions(-) diff --git a/gramps/src/GenericFilter.py b/gramps/src/GenericFilter.py index 61d373f49..ff9844005 100644 --- a/gramps/src/GenericFilter.py +++ b/gramps/src/GenericFilter.py @@ -54,6 +54,7 @@ from RelLib import * import Date from intl import gettext _ = gettext +from Utils import for_each_ancestor #------------------------------------------------------------------------- # @@ -98,7 +99,7 @@ class Rule: def check(self): return len(self.list) == len(self.labels) - def apply(self,p): + def apply(self,db,p): return 1 def display_values(self): @@ -121,7 +122,7 @@ class Everyone(Rule): def name(self): return 'Everyone' - def apply(self,p): + def apply(self,db,p): return 1 #------------------------------------------------------------------------- @@ -137,7 +138,7 @@ class HasIdOf(Rule): def name(self): return 'Has the Id' - def apply(self,p): + def apply(self,db,p): return p.getId() == self.list[0] #------------------------------------------------------------------------- @@ -153,7 +154,7 @@ class IsFemale(Rule): def name(self): return 'Is a female' - def apply(self,p): + def apply(self,db,p): return p.getGender() == Person.female #------------------------------------------------------------------------- @@ -170,7 +171,7 @@ class IsDescendantOf(Rule): def name(self): return 'Is a descendant of' - def apply(self,p): + def apply(self,db,p): return self.search(p) def search(self,p): @@ -197,7 +198,7 @@ class IsDescendantFamilyOf(Rule): def name(self): return "Is a descendant family member of" - def apply(self,p): + def apply(self,db,p): return self.search(p,1) def search(self,p,val): @@ -233,7 +234,7 @@ class IsAncestorOf(Rule): def name(self): return 'Is an ancestor of' - def apply(self,p): + def apply(self,db,p): return self.search(p) def search(self,p): @@ -247,6 +248,44 @@ class IsAncestorOf(Rule): return 1 return 0 +#------------------------------------------------------------------------- +# +# HasCommonAncestorWith +# +#------------------------------------------------------------------------- +class HasCommonAncestorWith(Rule): + """Rule that checks for a person that has a common ancestor with a specified person""" + + labels = [ _('ID') ] + + def name(self): + return 'Has a common ancestor with' + + def __init__(self,list): + Rule.__init__(self,list) + # Keys in `ancestor_cache' are ancestors of list[0]. + # We delay the computation of ancestor_cache until the + # first use, because it's not uncommon to instantiate + # this class and not use it. + self.ancestor_cache = {} + + def init_ancestor_cache(self,db): + # list[0] is an Id, but we need to pass a Person to for_each_ancestor. + p = db.getPerson(self.list[0]) + if p: + def init(self,pid): self.ancestor_cache[pid] = 1 + for_each_ancestor([p],init,self) + + def apply(self,db,p): + # On the first call, we build the ancestor cache for the + # reference person. Then, for each person to test, + # we browse his ancestors until we found one in the cache. + if len(self.ancestor_cache) == 0: + self.init_ancestor_cache(db) + return for_each_ancestor([p], + lambda self,p: self.ancestor_cache.has_key(p), + self); + #------------------------------------------------------------------------- # # IsMale @@ -260,7 +299,7 @@ class IsMale(Rule): def name(self): return 'Is a male' - def apply(self,p): + def apply(self,db,p): return p.getGender() == Person.male #------------------------------------------------------------------------- @@ -284,7 +323,7 @@ class HasEvent(Rule): def name(self): return 'Has the personal event' - def apply(self,p): + def apply(self,db,p): for event in p.getEventList(): val = 1 if self.list[0] and event.getName() != self.list[0]: @@ -322,7 +361,7 @@ class HasFamilyEvent(Rule): def name(self): return 'Has the family event' - def apply(self,p): + def apply(self,db,p): for f in p.getFamilyList(): for event in f.getEventList(): val = 1 @@ -355,7 +394,7 @@ class HasRelationship(Rule): def name(self): return 'Has the relationships' - def apply(self,p): + def apply(self,db,p): rel_type = 0 cnt = 0 num_rel = len(p.getFamilyList()) @@ -411,7 +450,7 @@ class HasBirth(Rule): def name(self): return 'Has the birth' - def apply(self,p): + def apply(self,db,p): event = p.getBirth() if len(self.list) > 2 and find(event.getDescription(),self.list[2])==-1: return 0 @@ -443,7 +482,7 @@ class HasDeath(Rule): def name(self): return 'Has the death' - def apply(self,p): + def apply(self,db,p): event = p.getDeath() if self.list[2] and find(event.getDescription(),self.list[2])==-1: return 0 @@ -467,7 +506,7 @@ class HasAttribute(Rule): def name(self): return 'Has the personal attribute' - def apply(self,p): + def apply(self,db,p): for event in p.getAttributes(): if self.list[0] and event.getType() != self.list[0]: return 0 @@ -488,7 +527,7 @@ class HasFamilyAttribute(Rule): def name(self): return 'Has the family attribute' - def apply(self,p): + def apply(self,db,p): for f in p.getFamilyList(): for event in f.getAttributes(): val = 1 @@ -513,7 +552,7 @@ class HasNameOf(Rule): def name(self): return 'Has a name' - def apply(self,p): + def apply(self,db,p): self.f = self.list[0] self.l = self.list[1] self.s = self.list[2] @@ -541,7 +580,7 @@ class MatchesFilter(Rule): def name(self): return 'Matches the filter named' - def apply(self, p): + def apply(self,db,p): for filter in SystemFilters.get_filters(): if filter.get_name() == self.list[0]: return filter.check(p) @@ -608,10 +647,10 @@ class GenericFilter: def get_rules(self): return self.flist - def check_or(self,p): + def check_or(self,db,p): test = 0 for rule in self.flist: - test = test or rule.apply(p) + test = test or rule.apply(db,p) if test: break if self.invert: @@ -619,20 +658,20 @@ class GenericFilter: else: return test - def check_xor(self,p): + def check_xor(self,db,p): test = 0 for rule in self.flist: - temp = rule.apply(p) + temp = rule.apply(db,p) test = ((not test) and temp) or (test and (not temp)) if self.invert: return not test else: return test - def check_one(self,p): + def check_one(self,db,p): count = 0 for rule in self.flist: - if rule.apply(p): + if rule.apply(db,p): count = count + 1 if count > 1: break @@ -641,10 +680,10 @@ class GenericFilter: else: return count == 1 - def check_and(self,p): + def check_and(self,db,p): test = 1 for rule in self.flist: - test = test and rule.apply(p) + test = test and rule.apply(db,p) if not test: break if self.invert: @@ -652,21 +691,24 @@ class GenericFilter: else: return test - def check(self,p): + def get_check_func(self): try: m = getattr(self, 'check_' + self.logical_op) except AttributeError: m = self.check_and + return m - return m(p) + def check(self,db,p): + return self.get_check_func()(db,p) - def apply(self,list): - try: - m = getattr(self, 'check_' + self.logical_op) - except AttributeError: - m = self.check_and + def apply(self,db,list): + m = self.get_check_func() + res = [] + for p in list: + if m(db,p): + res.append(p) + return res - return filter(m, list) #------------------------------------------------------------------------- # @@ -683,6 +725,7 @@ tasks = { _("Is a descendant of") : IsDescendantOf, _("Is a descendant family member of"): IsDescendantFamilyOf, _("Is an ancestor of") : IsAncestorOf, + _("Has a common ancestor with") : HasCommonAncestorWith, _("Is a female") : IsFemale, _("Is a male") : IsMale, _("Has the personal event") : HasEvent, diff --git a/gramps/src/RelLib.py b/gramps/src/RelLib.py index dd0cebb03..21c20d6a5 100644 --- a/gramps/src/RelLib.py +++ b/gramps/src/RelLib.py @@ -1988,14 +1988,17 @@ class GrampsDB(Persistent): self.default.setAncestor(0) self.default = person self.default.setAncestor(1) - + def getDefaultPerson(self): """returns the default Person of the database""" return self.default def getPerson(self,id): - """returns a map of gramps's IDs to Person instances""" - return self.personMap[id] + """returns the Person instance associated to id, or None""" + if self.personMap.has_key(id): + return self.personMap[id] + else: + return None def getPersonMap(self): """returns a map of gramps's IDs to Person instances""" @@ -2018,8 +2021,11 @@ class GrampsDB(Persistent): return extmap(self.familyMap) def getFamily(self,id): - """returns a map of gramps's IDs to Family instances""" - return self.familyMap[id] + """returns the Family instance associated to id, or None""" + if self.familyMap.has_key(id): + return self.familyMap[id] + else: + return None def setFamilyMap(self,map): """sets the map of gramps's IDs to Family instances""" @@ -2320,18 +2326,23 @@ class GrampsDB(Persistent): return self.placeTable.keys() def getPlace(self,key): - return self.placeMap[key] + """returns the Place instance associated to key, or None""" + if self.placeMap.has_key(key): + return self.placeMap[key] + else: + return None def getPlaceDisplay(self,key): return self.placeTable[key] - + def getSourceKeys(self): return self.sourceTable.keys() - + def getSourceDisplay(self,key): return self.sourceTable[key] def getSource(self,key): + """returns the Source instance associated to key, or None""" if self.sourceMap.has_key(key): return self.sourceMap[key] else: @@ -2399,6 +2410,3 @@ class GrampsDB(Persistent): """deletes the Family instance from the database""" if self.familyMap.has_key(family.getId()): del self.familyMap[family.getId()] - - - diff --git a/gramps/src/Utils.py b/gramps/src/Utils.py index 0888b9064..52b73c5d1 100644 --- a/gramps/src/Utils.py +++ b/gramps/src/Utils.py @@ -553,3 +553,35 @@ def build_string_optmenu(mapping, start_val): myMenu.set_active(start_index) return myMenu +#------------------------------------------------------------------------- +# +# Iterate over ancestors. +# +#------------------------------------------------------------------------- +def for_each_ancestor(start, func, data): + """ + Recursively iterate (breadth-first) over ancestors of + people listed in start. + Call func(data,pid) for the Id of each person encountered. + Exit and return 1, as soon as func returns true. + Return 0 otherwise. + """ + todo = start + doneIds = {} + while len(todo): + p = todo.pop() + pid = p.getId() + # Don't process the same Id twice. This can happen + # if there is a cycle in the database, or if the + # initial list contains X and some of X's ancestors. + if doneIds.has_key(pid): + continue + doneIds[pid] = 1 + if func(data,pid): + return 1 + for fam, mrel, frel in p.getParentList(): + f = fam.getFather() + m = fam.getMother() + if f: todo.append(f) + if m: todo.append(m) + return 0 diff --git a/gramps/src/plugins/GraphViz.py b/gramps/src/plugins/GraphViz.py index b2472a251..acd297976 100644 --- a/gramps/src/plugins/GraphViz.py +++ b/gramps/src/plugins/GraphViz.py @@ -93,7 +93,11 @@ class GraphVizDialog(ReportDialog): ans.set_name(_("Ancestors of %s") % name) ans.add_rule(GenericFilter.IsAncestorOf([self.person.getId()])) - return [all,des,ans] + com = GenericFilter.GenericFilter() + com.set_name(_("People with common ancestor with %s") % name) + com.add_rule(GenericFilter.HasCommonAncestorWith([self.person.getId()])) + + return [all,des,ans,com] def add_user_options(self): self.arrowstyle_optionmenu = gtk.GtkOptionMenu() @@ -270,7 +274,7 @@ class GraphVizDialog(ReportDialog): file = open(self.target_path,"w") - ind_list = self.filter.apply(self.db.getPersonMap().values()) + ind_list = self.filter.apply(self.db, self.db.getPersonMap().values()) write_dot(file, ind_list, self.orien, width, height, self.tb_margin, self.lr_margin, self.hpages, @@ -443,4 +447,3 @@ register_report( category=_("Graphical Reports"), description=get_description() ) - diff --git a/gramps/src/plugins/IndivComplete.py b/gramps/src/plugins/IndivComplete.py index 729bf2a7a..c59e0445c 100644 --- a/gramps/src/plugins/IndivComplete.py +++ b/gramps/src/plugins/IndivComplete.py @@ -336,7 +336,7 @@ class IndivComplete: self.d.end_cell() def write_report(self): - ind_list = self.filter.apply(self.database.getPersonMap().values()) + ind_list = self.filter.apply(self.database,self.database.getPersonMap().values()) count = 0 for self.person in ind_list: self.write_person(count) @@ -480,11 +480,15 @@ class IndivSummaryDialog(TextReportDialog): ans.set_name(_("Ancestors of %s") % name) ans.add_rule(GenericFilter.IsAncestorOf([self.person.getId()])) + com = GenericFilter.GenericFilter() + com.set_name(_("People with common ancestor with %s") % name) + com.add_rule(GenericFilter.HasCommonAncestorWith([self.person.getId()])) + all = GenericFilter.GenericFilter() all.set_name(_("Entire Database")) all.add_rule(GenericFilter.Everyone([])) - return [id,des,ans,all] + return [id,des,ans,com,all] #------------------------------------------------------------------------ # @@ -659,11 +663,3 @@ register_report( description=_("Produces a complete report on the selected people."), xpm=get_xpm_image() ) - - - - - - - - diff --git a/gramps/src/plugins/WebPage.py b/gramps/src/plugins/WebPage.py index a106f5ba4..2d2f19996 100644 --- a/gramps/src/plugins/WebPage.py +++ b/gramps/src/plugins/WebPage.py @@ -766,7 +766,7 @@ class WebReport(Report): image_dir_name) return - ind_list = self.filter.apply(self.db.getPersonMap().values()) + ind_list = self.filter.apply(self.db,self.db.getPersonMap().values()) self.progress_bar_setup(float(len(ind_list))) doc = HtmlLinkDoc(self.selected_style,None,self.template_name,None) @@ -925,7 +925,11 @@ class WebReportDialog(ReportDialog): ans.set_name(_("Ancestors of %s") % name) ans.add_rule(GenericFilter.IsAncestorOf([self.person.getId()])) - return [all,des,df,ans] + com = GenericFilter.GenericFilter() + com.set_name(_("People with common ancestor with %s") % name) + com.add_rule(GenericFilter.HasCommonAncestorWith([self.person.getId()])) + + return [all,des,df,ans,com] #------------------------------------------------------------------------ # @@ -1253,4 +1257,3 @@ register_report( description=_("Generates web (HTML) pages for individuals, or a set of individuals."), xpm=get_xpm_image() ) - diff --git a/gramps/src/plugins/WriteGedcom.py b/gramps/src/plugins/WriteGedcom.py index f231fa372..99396fe2a 100644 --- a/gramps/src/plugins/WriteGedcom.py +++ b/gramps/src/plugins/WriteGedcom.py @@ -340,7 +340,12 @@ class GedcomWriter: ans.set_name(_("Ancestors of %s") % person.getPrimaryName().getName()) ans.add_rule(GenericFilter.IsAncestorOf([person.getId()])) - self.filter_menu = GenericFilter.build_filter_menu([all,des,ans]) + com = GenericFilter.GenericFilter() + com.set_name(_("People with common ancestor with %s") % + person.getPrimaryName().getName()) + com.add_rule(GenericFilter.HasCommonAncestorWith([person.getId()])) + + self.filter_menu = GenericFilter.build_filter_menu([all,des,ans,com]) filter_obj.set_menu(self.filter_menu) gedmap = GedcomInfoDB() @@ -388,7 +393,7 @@ class GedcomWriter: for p in self.db.getPersonKeys(): self.plist[p] = 1 else: - for p in cfilter.apply(self.db.getPersonMap().values()): + for p in cfilter.apply(self.db,self.db.getPersonMap().values()): self.plist[p.getId()] = 1 self.flist = {}