From 824a9a5134e905b0f3ac12aa097e1d094061f80e Mon Sep 17 00:00:00 2001 From: "Craig J. Anderson" Date: Fri, 25 Oct 2013 20:33:55 +0000 Subject: [PATCH] Version 1 of the recursive descendant routine. More recursive classes to come. ander882@hotmail.com svn: r23412 --- gramps/plugins/lib/libplugins.gpr.py | 45 ++-- gramps/plugins/lib/librecurse.py | 307 +++++++++++++++++++++++++++ 2 files changed, 338 insertions(+), 14 deletions(-) create mode 100644 gramps/plugins/lib/librecurse.py diff --git a/gramps/plugins/lib/libplugins.gpr.py b/gramps/plugins/lib/libplugins.gpr.py index a7c997939..f7f661c44 100644 --- a/gramps/plugins/lib/libplugins.gpr.py +++ b/gramps/plugins/lib/libplugins.gpr.py @@ -26,7 +26,7 @@ # libcairo # #------------------------------------------------------------------------ -register(GENERAL, +register(GENERAL, id = 'libcairodoc', name = "Cairodoc lib", description = _("Provides a library for using Cairo to " @@ -45,7 +45,7 @@ authors_email = ["http://gramps-project.org"], # libgedcom # #------------------------------------------------------------------------ -register(GENERAL, +register(GENERAL, id = 'libgedcom', name = "GEDCOM library", description = _("Provides GEDCOM processing functionality"), @@ -57,12 +57,29 @@ authors = ["The Gramps project"], authors_email = ["http://gramps-project.org"], ) +#------------------------------------------------------------------------ +# +# librecurse +# +#------------------------------------------------------------------------ +register(GENERAL, + id='librecurse', + name="Recursive lib", + description= _("Provides recursive routines for reports"), + version='1.0', + gramps_target_version='4.1', + status=STABLE, + fname='librecurse.py', + authors=["The Gramps project"], + authors_email=["http://gramps-project.org"], + ) + #------------------------------------------------------------------------ # # libgrampsxml # #------------------------------------------------------------------------ -register(GENERAL, +register(GENERAL, id = 'libgrampsxml', name = "Grampsxml lib", description = _("Provides common functionality for Gramps XML " @@ -80,7 +97,7 @@ authors_email = ["http://gramps-project.org"], # libholiday # #------------------------------------------------------------------------ -register(GENERAL, +register(GENERAL, id = 'libholiday', name = "holiday lib", description = _("Provides holiday information for different countries.") , @@ -98,7 +115,7 @@ authors_email = ["http://gramps-project.org"], # llibhtmlbackend # #------------------------------------------------------------------------ -register(GENERAL, +register(GENERAL, id = 'libhtmlbackend', name = "htmlbackend lib", description = _("Manages a HTML file implementing DocBackend.") , @@ -116,7 +133,7 @@ authors_email = ["http://gramps-project.org"], # libhtmlconst # #------------------------------------------------------------------------ -register(GENERAL, +register(GENERAL, id = 'libhtmlconst', name = "htmlconst lib", description = _("Common constants for html files.") , @@ -134,7 +151,7 @@ authors_email = ["http://gramps-project.org"], # libhtml # #------------------------------------------------------------------------ -register(GENERAL, +register(GENERAL, id = 'libhtml', name = "html lib", description = _("Manages an HTML DOM tree.") , @@ -152,7 +169,7 @@ authors_email = ["gerald.britton@gmail.com"], # libmapservice # #------------------------------------------------------------------------ -register(GENERAL, +register(GENERAL, id = 'libmapservice', name = "mapservice lib", description = _("Provides base functionality for map services.") , @@ -169,7 +186,7 @@ authors_email = ["http://gramps-project.org"], # libnarrate # #------------------------------------------------------------------------ -register(GENERAL, +register(GENERAL, id = 'libnarrate', name = "narration lib", description = _("Provides Textual Narration.") , @@ -186,7 +203,7 @@ authors_email = ["brian@gramps-project.org"], # libodfbackend # #------------------------------------------------------------------------ -register(GENERAL, +register(GENERAL, id = 'libodfbackend', name = "odfbackend lib", description = _("Manages an ODF file implementing DocBackend.") , @@ -203,7 +220,7 @@ authors_email = ["http://gramps-project.org"], # libpersonview # #------------------------------------------------------------------------ -register(GENERAL, +register(GENERAL, id = 'libpersonview', name = "person list lib", description = _("Provides the Base needed for the List People views.") , @@ -220,7 +237,7 @@ authors_email = ["http://gramps-project.org"], # libplaceview # #------------------------------------------------------------------------ -register(GENERAL, +register(GENERAL, id = 'libplaceview', name = "place list lib", description = _("Provides the Base needed for the List Place views.") , @@ -237,7 +254,7 @@ authors_email = ["http://gramps-project.org"], # libsubstkeyword # #------------------------------------------------------------------------ -register(GENERAL, +register(GENERAL, id = 'libsubstkeyword', name = "Substitution Values", description = _("Provides variable substitution on display lines.") , @@ -253,7 +270,7 @@ authors_email = ["http://gramps-project.org"], # libtreebase # #------------------------------------------------------------------------ -register(GENERAL, +register(GENERAL, id = 'libtreebase', name = "Graphical report lib", description = _("Provides the base needed for the ancestor and " diff --git a/gramps/plugins/lib/librecurse.py b/gramps/plugins/lib/librecurse.py new file mode 100644 index 000000000..be0e91b02 --- /dev/null +++ b/gramps/plugins/lib/librecurse.py @@ -0,0 +1,307 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2013 Craig Anderson +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id: + +""" Recursive base classes for reports +""" + +from gramps.gen.plug.report import utils as ReportUtils + + +#------------------------------------------------------------------------ +# +# Class DescendPerson +# +#------------------------------------------------------------------------ +class DescendPerson(object): + """ Recursive (down) base class + + The following methods need to be sub-classed as needed: + . add_person + . add_person_again (called when a person is seen a second more more times) + if you don't want to see marriages don't subclass the following two + . add_marriage + . add_marriage_again (when a marriage is seen a second more more times) + + Public variables: + . families_seen a set of all famalies seen. + . people_seen, a set of all people seen. + . . useful for knowing if a recursion (kid marring a grandparent) + . . has happened. + These can be edited if needed + . appending can be useful for excluding parts of the tree + + Methods (tools): + is_direct_descendant - is this person a direct descendant + . in the example 'kid 1 of mom and other spouse' is NOT + stop_descending - tells the recursion to stop going down + . mostly used in add_person_again and add_marriage_again + has_children - checks to see if the person: + . is NOT already seen and has hildren. + + Methods (informative) + . These are the methods that need to be subclassed + . all methods are given the: + . . level in (Generagion, Spousal level) tuple + . . person_handle of the person + . . family_handle of the family + add_person - The recursion found a new person in the tree + add_person_again - found a person again + . a prolific person or recursion + add_marriage + add_marriage_again + + Methods (recursive) + recurse - The main recursive routine. needs: + . person_handle + . g_level - Generation level of this person + . . if max_gen is 2 and g_level is 1, only this generation + . . will be displayed. + . s_level - spousal level - most always 0 + recurse_parents - Thes same as above except: + . mom (the spouse) is still shown even if s_level == 0 + . . father will have a level of (g_level,0), mother (g_level, 1) + """ + def __init__(self, dbase, maxgen, maxspouse): + """ initalized with the + . database + . maxgen is the max generations (down) of people to return + . maxspouse is the level of spouses to recruse through + . . 0 = no spouses, 1 = spouses of a direct descendant + . . 2 = spouses of 1, 3 = spouses of 2, etc. See example below + + """ + # example: maxgen = 2, maxspouses = 2 + # (1,0) father + # (1,1) Mother + # (1,2) Mothers other spouse + # (2,0) kid 1 of mom and other spouse + # (2,0) Kid 1 of father and mother + # (1,1) fathers other spouse + # (2,0) Kid 1 of father and fathers other spouse + # (2,1) Spouse of Kid 1 of father and fathers other spouse + + self.database = dbase + + self.families_seen = set() + self.people_seen = set() + + self.max_generations = maxgen + self.max_spouses = maxspouse + + #can we bold direct descendants? + #bold_now will have only three values + #0 - no bolding + #1 - Only bold the first person + #2 - Bold all direct descendants + self.__bold_now = 1 + self.__this_slevel = -1 + self.__stop_descending = False + + def is_direct_descendant(self): + return self.__bold_now != 0 and self.__this_slevel == 0 + + def stop_descending(self): + self.__stop_descending = True + + def has_children(self, person_handle): + """ + Quickly check to see if this person has children + still we want to respect the people_seen list + """ + + if not person_handle or person_handle in self.people_seen: + return False + + person = self.database.get_person_from_handle(person_handle) + + for family_handle in person.get_family_handle_list(): + if family_handle not in self.families_seen: + + family = self.database.get_family_from_handle(family_handle) + + if family.get_child_ref_list(): + return True + return False + + def add_person(self, level, person_handle, family_handle): + """ Makes a person box """ + pass + + def add_person_again(self, level, person_handle, family_handle): + pass + + def __add_person(self, level, person_handle, family_handle): + if person_handle is not None and person_handle in self.people_seen: + self.add_person_again(level, person_handle, family_handle) + else: + self.add_person(level, person_handle, family_handle) + if person_handle is not None: + self.people_seen.add(person_handle) + + def add_marriage(self, level, person_handle, family_handle): + """ Makes a marriage box """ + pass + + def add_marriage_again(self, level, person_handle, family_handle): + """ Makes a marriage box """ + pass + + def __add_marriage(self, level, person_handle, family_handle): + """ Makes a marriage box """ + if family_handle in self.families_seen: + self.add_marriage_again(level, person_handle, family_handle) + else: + self.add_marriage(level, person_handle, family_handle) + self.families_seen.add(family_handle) + + def recurse(self, person_handle, g_level, s_level): + """traverse the descendants recursively + until either the end of a line is found, + or until we reach the maximum number of generations + or we reach the max number of spouses + that we want to deal with""" + + if not person_handle: + return + if g_level > self.max_generations: + return + if s_level > 0 and s_level == self.max_spouses: + return + #if person_handle in self.people_seen: return + + person = self.database.get_person_from_handle(person_handle) + family_handles = person.get_family_handle_list() + if s_level == 0: + val = family_handles[0] if family_handles else None + self.__this_slevel = s_level + self.__add_person((g_level, s_level), person_handle, val) + + if self.__bold_now == 1: + self.__bold_now = 0 + + if self.__stop_descending: + self.__stop_descending = False + return + + if s_level == 1: + tmp_bold = self.__bold_now + self.__bold_now = 0 + + for family_handle in family_handles: + #Marriage box if the option is there. + self.__add_marriage((g_level, s_level + 1), + person_handle, family_handle) + + if self.__stop_descending: + self.__stop_descending = False + continue + + family = self.database.get_family_from_handle(family_handle) + + spouse_handle = ReportUtils.find_spouse(person, family) + if self.max_spouses > s_level: + self.__this_slevel = s_level + 1 + self.__add_person((g_level, s_level + 1), + spouse_handle, family_handle) + + if self.__stop_descending: + self.__stop_descending = False + continue + + mykids = [kid.ref for kid in family.get_child_ref_list()] + + if not self.__stop_descending: + for child_ref in mykids: + self.recurse(child_ref, g_level + 1, 0) + else: + self.__stop_descending = False + + if self.max_spouses > s_level: + #spouse_handle = ReportUtils.find_spouse(person,family) + self.recurse(spouse_handle, g_level, s_level + 1) + + if s_level == 1: + self.__bold_now = tmp_bold + + def recurse_parents(self, family_handle, g_level): + """ + Adds a family. + ignoring maxspouse, s_level assumed 0 and 1 + father is (g_level,0) and mother is (g_level,1) + children are (g_level+1,0) and respects maxgen + """ + + if family_handle is None: + return + + family = self.database.get_family_from_handle(family_handle) + father_h = family.get_father_handle() + mother_h = family.get_mother_handle() + + self.__bold_now = 2 + self.__this_slevel = 0 + #if father_h: + father_b = self.__add_person((g_level, 0), father_h, family_handle) + #else: + # #TODO - should send family_h instead of None? + # father_b = self.__add_person((g_level, 0), None, family_h) + #self.people_seen.add(father_h) + + family_b = self.__add_marriage((g_level, 1), father_h, family_handle) + + self.__bold_now = 0 + self.__this_slevel = 1 + mother_b = self.__add_person((g_level, 1), mother_h, family_handle) + + self.__bold_now = 2 + for child_ref in family.get_child_ref_list(): + self.recurse(child_ref.ref, g_level + 1, 0) + + self.__bold_now = 0 + + return (father_b, family_b, mother_b) + + def recurse_if(self, person_handle, g_level): + """ + Quickly check to see if we want to continue recursion + we still we want to respect the FamiliesSeen list + """ + + person = self.database.get_person_from_handle(person_handle) + + show = False + myfams = person.get_family_handle_list() + if len(myfams) > 1: # and self.max_spouses > 0 + show = True + + #if self.max_spouses == 0 and not self.has_children(person_handle): + # self.people_seen.add(person_handle) + # show = False + + if show: + self.__bold_now = 1 + self.recurse(person_handle, g_level, 0) + +#------------ +# Jer 29:11: "For I know the plans I have for you," declares the LORD, +# "plans to prosper you and not to harm you, plans to give you hope +# and a future."