# # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2008-2010 Craig J. Anderson ander882@hotmail.com # # 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: Descend2.py """ Reports/Graphical Reports/Familial Tree Reports/Graphical Reports/Personal Tree """ #------------------------------------------------------------------------ # # GRAMPS modules # #------------------------------------------------------------------------ try: from TransUtils import get_addon_translator _ = get_addon_translator(__file__).gettext except: import gettext _ = gettext.gettext from Errors import ReportError from gen.plug.menu import TextOption, NumberOption, EnumeratedListOption, \ StringOption, BooleanOption, PersonOption, FamilyOption from gen.plug.report import Report from gen.plug.report import utils as ReportUtils from gui.plug.report import MenuReportOptions PT2CM = ReportUtils.pt2cm #------------------------------------------------------------------------ # # Constants # #------------------------------------------------------------------------ _BORN = _('short for born|b.') _DIED = _('short for died|d.') _MARR = _('short for married|m.') _RPT_NAME = 'descend_chart' from libtreebase import * #------------------------------------------------------------------------ # # Box classes # #------------------------------------------------------------------------ class DescendantBoxBase(BoxBase): """ Base for all descendant boxes. Set the boxstr and some new attributes that are needed """ def __init__(self, boxstr): BoxBase.__init__(self) self.boxstr = boxstr self.next = None self.prev = None self.line_to = None self.father = None def calc_text(self, database, person, family): """ A sinble place to calculate box text """ gui = GuiConnect() calc = gui.calc_lines(database) self.text = calc.calc_lines(person, family, gui.working_lines(self)) class PersonBox(DescendantBoxBase): """ Calculates information about the box that will print on a page """ def __init__(self, level, boldable = 0): DescendantBoxBase.__init__(self, "CG2-box") self.level = level self.shadow = PT2CM(9) * .6 def set_bold(self): """ update me to a bolded box """ self.boxstr = "CG2b-box" class FamilyBox(DescendantBoxBase): """ Calculates information about the box that will print on a page """ def __init__(self, level): DescendantBoxBase.__init__(self, "CG2-fam-box") self.level = level class PlaceHolderBox(BoxBase): """ I am a box that does not print. I am used to make sure information does not run over areas that we don't wnat information (boxes) """ def __init__(self, level): BoxBase.__init__(self) self.boxstr = "None" self.level = level def calc_text(self, database, person, family): """ move along. Nothing to see here """ return #------------------------------------------------------------------------ # # Line class # #------------------------------------------------------------------------ class Line(LineBase): """ Our line class.""" def __init__(self, start): LineBase.__init__(self, start) self.linestr = "CG2-line" #------------------------------------------------------------------------ # # Titles Class(es) # #------------------------------------------------------------------------ class DescendantTitleBase(TitleBox): def __init__(self, dbase, doc, boxstr = "CG2-Title"): TitleBox.__init__(self, doc, boxstr) self.database = dbase def descendant_print(self, person_list, person_list2 = []): names = self._get_names(person_list) if person_list2 != []: names2 = self._get_names(person_list2) if person_list2 == []: if len(names) == 1: title = _("Descendant Chart for %(person)s") % \ {'person': names[0] } elif len(names) == 2: title = _("Descendant Chart for %(father)s and %(mother)s") % \ {'father': names[0], 'mother': names[1] } else: if len(person_list + person_list2) == 3: if len(person_list) == 1: title = _("Descendant Chart for %(person)s and" + \ " %(father1)s, %(mother1)s") % \ {'person': names[0], \ 'father1': names2[0], \ 'mother1': names2[1], } else: title = _("Descendant Chart for %(person)s, %(father1)s " + \ " and %(mother1)s") % \ {'father1': names[0], \ 'mother1': names[1], \ 'person': names2[0] } else: #if len(person_list + person_list2) == 4: title = _("Descendant Chart for %(father1)s, %(father2)s " + \ "and %(mother1)s, %(mother2)s") % \ {'father1': names[0], \ 'mother1': names[1], \ 'father2': names2[0], \ 'mother2': names2[1] } return title def get_parents(self, family_id): family1 = self.database.get_family_from_gramps_id(family_id) father_h = family1.get_father_handle() mother_h = family1.get_mother_handle() parents = [] if father_h: parents.append(self.database.get_person_from_handle(father_h)) if mother_h: parents.append(self.database.get_person_from_handle(mother_h)) return parents class TitleNone(TitleBox): """Family Chart Title class for the report """ def __init__(self, dbase, doc): TitleBox.__init__(self, doc, "None") def calc_title(self, persons): """Calculate the title of the report""" return class TitleDPY(DescendantTitleBase): """Descendant (Person yes start with parents) Chart Title class for the report """ def __init__(self, dbase, doc): DescendantTitleBase.__init__(self, dbase, doc) def calc_title(self, person_id): """Calculate the title of the report""" center = self.database.get_person_from_gramps_id(person_id) family2_h = center.get_main_parents_family_handle() family2 = self.database.get_family_from_handle(family2_h) person_list = [] mother2_h = father2_h = None if family2: father2_h = family2.get_father_handle() if father2_h: person_list.append( self.database.get_person_from_handle(father2_h)) mother2_h = family2.get_mother_handle() if mother2_h: person_list.append( self.database.get_person_from_handle(mother2_h)) if person_list == []: person_list.append(center) #else: # title = str(tmp) + " " + str(len(tmp)) self.text = self.descendant_print(person_list) self.set_box_height_width() class TitleDPN(DescendantTitleBase): """Descendant (Person no start with parents) Chart Title class for the report """ def __init__(self, dbase, doc): DescendantTitleBase.__init__(self, dbase, doc) def calc_title(self, person_id): """Calculate the title of the report""" center = self.database.get_person_from_gramps_id(person_id) title = self.descendant_print([center]) self.text = title self.set_box_height_width() class TitleDFY(DescendantTitleBase): """Descendant (Family yes start with parents) Chart Title class for the report """ def __init__(self, dbase, doc): DescendantTitleBase.__init__(self, dbase, doc) def get_parent_list(self, person): """ return a list of my parents. If none, return me """ if person is None: return None parent_list = [] family_h = person.get_main_parents_family_handle() family = self.database.get_family_from_handle(family_h) if family is not None: #family = fathers parents father_h = family.get_father_handle() if father_h is not None: parent_list.append( self.database.get_person_from_handle(father_h)) mother_h = family.get_mother_handle() if mother_h is not None: parent_list.append( self.database.get_person_from_handle(mother_h)) else: return [person] if parent_list == []: return [person] else: return parent_list def calc_title(self, family_id): """Calculate the title of the report""" my_parents = self.get_parents(family_id) dad_parents = self.get_parent_list(my_parents[0]) mom_parents = [] if len(my_parents) > 1: if dad_parents is None: dad_parents = self.get_parent_list(my_parents[1]) else: mom_parents = self.get_parent_list(my_parents[1]) self.text = self.descendant_print(dad_parents, mom_parents) self.set_box_height_width() class TitleDFN(DescendantTitleBase): """Descendant (Family no start with parents) Chart Title class for the report """ def __init__(self, dbase, doc): DescendantTitleBase.__init__(self, dbase, doc) def calc_title(self, family_id): """Calculate the title of the report""" self.text = self.descendant_print( self.get_parents(family_id) ) self.set_box_height_width() class TitleF(DescendantTitleBase): """Family Chart Title class for the report """ def __init__(self, dbase, doc): DescendantTitleBase.__init__(self, dbase, doc) def calc_title(self, family_id): """Calculate the title of the report""" parents = self.get_parents(family_id) names = self._get_names(parents) if len(parents) == 1: title = _("Family Chart for %(person)s") % {'person': names[0] } elif len(parents) == 2: title = _("Family Chart for %(father1)s and %(mother1)s") % \ {'father1': names[0], 'mother1': names[1] } #else: # title = str(tmp) + " " + str(len(tmp)) self.text = title self.set_box_height_width() class TitleC(DescendantTitleBase): """Cousin Chart Title class for the report """ def __init__(self, dbase, doc): DescendantTitleBase.__init__(self, dbase, doc) def calc_title(self, family_id): """Calculate the title of the report""" family = self.database.get_family_from_gramps_id(family_id) kids = [] for kid in family.get_child_ref_list(): kids.append(self.database.get_person_from_handle(kid.ref)) #ok we have the children. Make a title off of them tmp = self._get_names(kids) title = "" while tmp != []: if title != "": title += ", " title += tmp.pop(0) title = "Cousin Chart for " + title self.text = title self.set_box_height_width() #------------------------------------------------------------------------ # # Class RecurseDown # #------------------------------------------------------------------------ class RecurseDown: """ The main recursive functions that will use add_person to make the tree of people (Descendants) to be included within the report. """ def __init__(self, dbase, canvas): self.database = dbase self.canvas = canvas self.famalies_seen = [] self.cols = [] gui = GuiConnect() self.do_gparents = gui.get_val('show_gparents') self.max_generations = gui.get_val('maxgen') self.max_spouses = gui.get_val('maxspouse') self.inlc_marr = gui.get_val('incmarr') if self.max_spouses == 0: self.inlc_marr = False #is the option even available? self.bold_direct = gui.get_val('bolddirect') #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 = 0 gui = None def add_to_col(self, box): """ Add the box to a column on the canvas. we will do these things: set the .next .prev attribs for the boxs in this col get the height and width of this box and set it no the column also we set the .x_cm to any s_level (indentation) here we will calculate the real .x_cm later (with indentation) """ level = box.level[0] #make the column list of people while len(self.cols) <= level: self.cols.append(None) if self.cols[level] is not None: self.cols[level].next = box box.prev = self.cols[level] self.cols[level] = box box.x_cm = self.canvas.doc.report_opts.spouse_offset * box.level[1] self.canvas.set_box_height_width(box) tmp = box.prev if tmp is not None: #set my y_cm. box.y_cm = tmp.y_cm + tmp.height + tmp.shadow def add_person_box(self, level, indi_handle, fams_handle, father): """ Makes a person box and add that person into the Canvas. """ myself = PersonBox(level) if myself.level[1] == 0 and self.bold_direct and self.bold_now != 0: if self.bold_now == 1: self.bold_now = 0 myself.set_bold() if level[1] == 0 and father is not None and \ myself.level[0] != father.level[0]: #I am a child if father.line_to is None: line = Line(father) father.line_to = line self.canvas.add_line(father.line_to) else: line = father.line_to father.line_to.end.append(myself) myself.father = father #calculate the text. myself.calc_text(self.database, indi_handle, fams_handle) self.add_to_col(myself) if myself.y_cm != 0.0: myself.y_cm += self.canvas.doc.report_opts.box_pgap #check to see if the direct decendant above me is a sibling or other if myself.level[1] == 0: sibling = True this_box = myself.prev while this_box is not None: if this_box.level[1] == 0: if this_box.father != myself.father: sibling = False break this_box = this_box.prev if sibling == False: myself.y_cm += self.canvas.doc.report_opts.box_pgap self.canvas.add_box(myself) return myself def add_marriage_box(self, level, indi_handle, fams_handle, father): """ Makes a marriage box and add that person into the Canvas. """ myself = FamilyBox(level) #if father is not None: # myself.father = father #calculate the text. myself.calc_text(self.database, indi_handle, fams_handle) self.add_to_col(myself) if myself.y_cm != 0.0: myself.y_cm += self.canvas.doc.report_opts.box_mgap self.canvas.add_box(myself) return myself def recurse(self, person_handle, x_level, s_level, father): """traverse the ancestors 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 x_level > self.max_generations: return if s_level > 0 and s_level == self.max_spouses: return if person_handle == None: return if person_handle in self.famalies_seen: return myself = None person = self.database.get_person_from_handle(person_handle) family_handles = person.get_family_handle_list() if s_level == 0: if family_handles == []: val = None else: val = family_handles[0] myself = self.add_person_box( (x_level, s_level), person_handle, val, father) marr = None spouse = None first = 1 for family_handle in family_handles: if family_handle not in self.famalies_seen: self.famalies_seen.append(family_handle) family = self.database.get_family_from_handle(family_handle) if self.inlc_marr and self.max_spouses > 0: if s_level == 0: marr = self.add_marriage_box((x_level, s_level+1), person_handle, family_handle, myself) else: marr = self.add_marriage_box((x_level, s_level+1), person_handle, family_handle, father) spouse_handle = ReportUtils.find_spouse(person, family) if self.max_spouses > s_level and \ spouse_handle not in self.famalies_seen: if s_level > 0: spouse = self.add_person_box((x_level, s_level+1), spouse_handle, family_handle, father) elif first == 1: spouse = self.add_person_box((x_level, s_level+1), spouse_handle, family_handle, myself) elif self.inlc_marr: spouse = self.add_person_box((x_level, s_level+1), spouse_handle, family_handle, marr) else: spouse = self.add_person_box((x_level, s_level+1), spouse_handle, family_handle, myself) mykids = [] for kid in family.get_child_ref_list(): mykids.append(kid.ref) for child_ref in mykids: if self.inlc_marr and self.max_spouses > 0: self.recurse(child_ref, x_level+1, 0, marr) elif spouse is not None: self.recurse(child_ref, x_level+1, 0, spouse) else: self.recurse(child_ref, x_level+1, 0, myself) first = 0 if self.max_spouses > s_level and spouse_handle != None and \ self.famalies_seen.count(spouse_handle) == 0: #spouse_handle = ReportUtils.find_spouse(person,family) self.recurse(spouse_handle, x_level, s_level+1, spouse) def add_family(self, level, family, father2): """ Adds a family into the canvas. only will be used for my direct grandparents, and my parents only. """ family_h = family.get_handle() father_h = family.get_father_handle() mother_h = family.get_mother_handle() self.bold_now = 2 if father_h == None: father_b = self.add_person_box( (level, 0), None, None, father2) else: father_b = self.add_person_box( (level, 0), father_h, family_h, father2) if self.inlc_marr: family_b = self.add_marriage_box( (level, 1), father_h, family_h, father_b) self.famalies_seen.append(family_h) if mother_h: mother_b = self.add_person_box( (level, 0), mother_h, family_h, father_b) else: mother_b = self.add_person_box( (level, 0), None, None, father_b) #child_refs = [] #for child_ref in family.get_child_ref_list(): # child_refs.append(child_ref) for child_ref in family.get_child_ref_list(): if self.inlc_marr: self.recurse(child_ref.ref, level+1, 0, family_b) else: self.recurse(child_ref.ref, level+1, 0, father_b) #Future code. #if self.inlc_marr: # family_b.line_to.start.append(father_b) # family_b.line_to.start.append(mother_b) #else: # father_b.line_to.start.append(mother_b) self.bold_now = 0 return father_b def has_children(self, person_handle): """ Quickly check to see if this person has children still we want to respect the FamaliesSeen list """ if person_handle == None: return False if person_handle in self.famalies_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.famalies_seen: family = self.database.get_family_from_handle(family_handle) if family.get_child_ref_list() != []: return True return False def recurse_if(self, person_handle, level): """ Quickly check to see if we want to continue recursion still we want to respect the FamaliesSeen 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 not self.inlc_marr: #if the condition is true, we only want to show #this parent again IF s/he has other children show = self.has_children(person_handle) #if self.max_spouses == 0 and not self.has_children(person_handle): # self.famalies_seen.append(person_handle) # show = False if show == True: self.bold_now = 1 self.recurse(person_handle, level, 0, None) #------------------------------------------------------------------------ # # Class MakePersonTree (Personal Descendant Tree option) # #------------------------------------------------------------------------ class MakePersonTree(RecurseDown): """ The main procedure to use recursion to make the tree based off of a person. order of people inserted into Persons is important. makes sure that order is done correctly. """ def __init__(self, dbase, canvas): RecurseDown.__init__(self, dbase, canvas) self.max_generations -= 1 def start(self, person_id): """follow the steps to make a tree off of a person""" persons = [] father1 = self.database.get_person_from_gramps_id(person_id) father1_h = father1.get_handle() #could be mom too. family2 = family2_h = None if self.do_gparents: family2_h = father1.get_main_parents_family_handle() family2 = self.database.get_family_from_handle(family2_h) mother2_h = father2_h = None if family2: father2_h = family2.get_father_handle() mother2_h = family2.get_mother_handle() ####################### #don't do center person's parents family. if family2_h: self.famalies_seen.append(family2_h) ####################### #Center person's Fathers OTHER wives ####################### #update to only run if he HAD other wives! if father2_h: self.recurse_if(father2_h, 0) ####################### #Center persons parents only! ####################### #now it will ONLY be my fathers parents if family2: father2_id = self.add_family( 0, family2, None ) #if father2_h is not None: # persons.append(self.database.get_person_from_handle(father2_h)) #if mother2_h is not None: # persons.append(self.database.get_person_from_handle(mother2_h)) else: self.bold_now = 2 self.recurse(father1_h, 0, 0, None) #persons.append(self.database.get_person_from_handle(father1_h)) self.bold_now = 0 ####################### #Center person's mothers OTHER husbands ####################### #update to only run if she HAD other husbands! if mother2_h: self.recurse_if(mother2_h, 0) return persons #------------------------------------------------------------------------ # # Class MakeFamilyTree (Familial Descendant Tree option) # #------------------------------------------------------------------------ class MakeFamilyTree(RecurseDown): """ The main procedure to use recursion to make the tree based off of a family. order of people inserted into Persons is important. makes sure that order is done correctly. """ def __init__(self, dbase, canvas): RecurseDown.__init__(self, dbase, canvas) def start(self, family_id): """follow the steps to make a tree off of a family""" ## (my) referes to the children of family_id # Step 1 print out my fathers, fathers, # other wives families first (if needed) family1 = self.database.get_family_from_gramps_id(family_id) family1_h = family1.get_handle() ####################### #Initial setup of variables ####################### father1_h = family1.get_father_handle() mother1_h = family1.get_mother_handle() father1 = mother1 = family2 = family2_h = None if father1_h: father1 = self.database.get_person_from_handle(father1_h) if self.do_gparents: #b3 - remove grandparents? family2_h = father1.get_main_parents_family_handle() family2 = self.database.get_family_from_handle(family2_h) if mother1_h: mother1 = self.database.get_person_from_handle(mother1_h) mother2_h = father2_h = None if family2: #family2 = fathers parents mother2_h = family2.get_mother_handle() mother2 = self.database.get_person_from_handle(mother2_h) father2_h = family2.get_father_handle() father2 = self.database.get_person_from_handle(father2_h) #Helper variables. Assigned in one section, used in another. father2_id = family2_id = None mother1_id = None ####################### #don't do my fathers parents family. will be done later if family2_h: self.famalies_seen.append(family2_h) ####################### #my father mothers OTHER husbands ####################### #update to only run if she HAD other husbands! if mother2_h: self.recurse_if(mother2_h, 0) ####################### #father Fathers OTHER wives ####################### #update to only run if he HAD other wives! if father2_h: self.recurse_if(father2_h, 0) ####################### #don't do my parents family in recurse. will be done later self.famalies_seen.append(family1_h) ##If dad has no other children from other marriages. remove him if self.max_spouses == 0 and not self.has_children(father1_h): self.famalies_seen.append(father1_h) ####################### #my fathers parents! ####################### #now it will ONLY be my fathers parents #will print dads parents. dad's other wifes will also print if family2: myfams = father1.get_family_handle_list() show = False if len(myfams) > 1: show = True if not self.inlc_marr and self.max_spouses == 0: #if the condition is true, we only want to show #this parent again IF s/he has children show = self.has_children(father1_h) if show == False: self.famalies_seen.append(father1_h) father2_id = self.add_family( 0, family2, None ) if self.inlc_marr: family2_id = father2_id.next ####################### #my father other wives (if all of the above does nothing) #if my father does not have parents (he is the highest) ####################### #do his OTHER wives first. if family2 == None and father1: self.recurse_if(father1_h, 1) ####################### #my father ####################### if family2: #We need to add dad to the family if self.inlc_marr: parent = family2_id else: parent = father2_id else: parent = None father1_b = self.add_family( 1, family1, parent ) #All of us gets added to the line start #Add the next no matter what it is. if self.inlc_marr: mother1_b = father1_b.next.next #Mom's Box line = father1_b.next.line_to if line is None: line = Line() line.start = [father1_b, father1_b.next, mother1_b] else: mother1_b = father1_b.next #Mom's Box line = father1_b.line_to if line is None: line = Line() line.start = [father1_b, mother1_b] father1_b.line_to = line #make sure there is at least one child in this family. #if not put in a placeholder if line.end == []: box = PlaceHolderBox((father1_b[0]+1, 0)) self.add_to_col(box) line.end.append(box) ####################### ####################### #Lower half #This will be quite like the first half. #Just on the mothers side... #Mom has already been printed with the family ####################### ####################### ####################### #Initial setup of variables ####################### mother1_h = family1.get_mother_handle() family2_h = mother1 = family2 = None if mother1_h: mother1 = self.database.get_person_from_handle(mother1_h) if self.do_gparents: #b3 - remove grandparents? family2_h = mother1.get_main_parents_family_handle() family2 = self.database.get_family_from_handle(family2_h) mother2_h = father2_h = None if family2: mother2_h = family2.get_mother_handle() mother2 = self.database.get_person_from_handle(mother2_h) father2_h = family2.get_father_handle() father2 = self.database.get_person_from_handle(father2_h) ####################### #don't do my parents family. self.famalies_seen = [family1_h] ##If mom has no other children from other marriages. remove her if self.max_spouses == 0 and not self.has_children(mother1_h): self.famalies_seen.append(mother1_h) if mother1_h: myfams = mother1.get_family_handle_list() if len(myfams) < 2: #If mom didn't have any other families, don't even do her #she is already here with dad and will be added later self.famalies_seen.append(mother1_h) ####################### #my mother other spouses (if no parents) ####################### #if my mother does not have parents (she is the highest) #Then do her OTHER spouses. if family2 == None and mother1: self.recurse_if(mother1_h, 1) ####################### #my mothers parents! ####################### if family2: father2 = self.add_family( 0, family2, None ) if self.inlc_marr: family2 = father2.next #Add mom (made in dad's section) to the list of children (first) tmpfather = father2 if self.inlc_marr: tmpfather = family2 tmpfather.line_to.end.insert(0, mother1_b) #fix me. Moms other siblings have been given an extra space #Because Moms-father is not siblings-father right now. mother1_b.father = tmpfather ####################### #my mother mothers OTHER husbands ####################### #update to only run if she HAD other husbands! if mother2_h: self.recurse_if(mother2_h, 0) ####################### #mother Fathers OTHER wives ####################### #update to only run if he HAD other wives! if father2_h: self.recurse_if(father2_h, 0) #------------------------------------------------------------------------ # # Class MakeReport # #------------------------------------------------------------------------ class MakeReport(object): """ Make a report out of a list of people. The list of people is already made. Use this information to find where people will be placed on the canvas. """ def __init__(self, dbase, canvas, ind_spouse, compress_tree): self.database = dbase self.canvas = canvas gui = GuiConnect() self.do_gparents = gui.get_val('show_gparents') self.inlc_marr = gui.get_val('incmarr') self.max_spouses = gui.get_val('maxspouse') gui = None self.ind_spouse = ind_spouse self.compress_tree = compress_tree self.cols = [[]] #self.max_generations = 0 #already done in recurse, #Some of this code needs to be moved up to RecurseDown.add_to_col() def calc_box(self, box): """ calculate the max_box_width and max_box_height for the report """ width = box.x_cm + box.width if width > self.canvas.doc.report_opts.max_box_width: self.canvas.doc.report_opts.max_box_width = width if box.height > self.canvas.doc.report_opts.max_box_height: self.canvas.doc.report_opts.max_box_height = box.height while len(self.cols) <= box.level[0]: self.cols.append([]) self.cols[box.level[0]].append(box) #tmp = box.level[0] #if tmp > self.max_generations: # self.max_generations = tmp def __move_col_from_here_down(self, box, amount): """Move me and everyone below me in this column only down""" while box is not None: box.y_cm += amount box = box.next def __move_next_cols_from_here_down(self, box, amount): """Move me, everyone below me in this column, and all of our children (and childrens children) down.""" col = [box] while len(col) > 0: if len(col) == 1 and col[0].line_to is not None: col.append(col[0].line_to.end[0]) col[0].y_cm += amount col[0] = col[0].next if col[0] is None: col.pop(0) def __next_family_group(self, box): """ a helper function. Assume box is at the start of a family block. get this family block. """ while box is not None: left_group = [] #Form the parental (left) group. #am I a direct descendant? if box.level[1] == 0: #I am the father/mother. left_group.append(box) box = box.next if box is not None and box.level[1] != 0 and self.inlc_marr: #add/start with the marriage box left_group.append(box) box = box.next if box is not None and box.level[1] != 0 and self.max_spouses > 0: #add/start with the spousal box left_group.append(box) box = box.next right_group = [] #Form the children (right) group. for spouses in left_group: if spouses.line_to is not None: right_group += spouses.line_to.end #will only be one. but in the family report, #Dad and mom point to the same line break #we now have everyone we want if right_group != []: return left_group, right_group #else # no children, so no family. go again until we find one to return. return None, None def __reverse_family_group(self): """ go through the n-1 to 0 cols of boxes looking for famalies (parents with children) that may need to be moved. """ for x_col in range(len(self.cols)-2, -1, -1): box = self.cols[x_col][0] #The first person in this col while box is not None: left_group, right_group = self.__next_family_group(box) if left_group is None: box = None #we found the end of this col else: yield left_group, right_group box = left_group[-1].next def __calc_movements(self, left_group, right_group): """ for a family group, see if parents or children need to be moved down so everyone is the the right/left of each other. return a right y_cm and a left y_cm. these points will be used to move parents/children down. """ left_up = left_group[0].y_cm right_up = right_group[0].y_cm left_center = left_up right_center = right_up if self.compress_tree: #calculate a new left and right move points for left_line in left_group: if left_line.line_to is not None: break left_center = left_line.y_cm + (left_line.height /2) left_down = left_group[-1].y_cm + left_group[-1].height right_down = right_group[-1].y_cm + right_group[-1].height #Lazy. Move down either side only as much as we NEED to. if left_center < right_up: right_center = right_group[0].y_cm elif left_up == right_up: left_center = left_up #Lets keep it. top line. elif left_center > right_down: right_center = right_down else: right_center = left_center return right_center, left_center def Make_report(self): """ Everyone on the page is as far up as they can go. Move them down to where they belong. We are going to go through everyone from right to left top to bottom moving everyone down as needed to make the report. """ for left_group, right_group in self.__reverse_family_group(): right_y_cm, left_y_cm = self.__calc_movements(left_group, right_group) #1. Are my children too high? if so move then down! if right_y_cm < left_y_cm: #we have to push our kids (and their kids) down. #We also need to push down all the kids (under) #these kids (in their column) amt = (left_y_cm - right_y_cm) self.__move_next_cols_from_here_down(right_group[0], amt) #2. Am I (and spouses) too high? if so move us down! elif left_y_cm < right_y_cm: #Ok, I am too high. Move me down amt = (right_y_cm - left_y_cm) self.__move_col_from_here_down(left_group[0], amt) #6. now check to see if we are working with dad. #if so we need to move down mariage information #and mom! left_line = left_group[0].line_to if left_line is None: left_line = left_group[1].line_to left_group = left_line.start if len(left_group) > 1: #So far only Dad and Mom have len(left_group) > 1 left_up = left_group[0].y_cm left_down = left_group[-1].y_cm + left_group[-1].height right_up = right_group[0].y_cm right_down = right_group[-1].y_cm + right_group[-1].height #if the parents height is less than the children height #The 0.2 is to see if this is even worth it. if (left_down-left_up+0.2) < (right_down-right_up): #our children take up more space than us parents. #so space us parents out! #move Dad if self.compress_tree: left_group[0].y_cm += right_group[0].height/2 move_amt = right_group[-1].y_cm + right_group[-1].height/2 move_amt -= (left_group[-1].y_cm + left_group[-1].height/2) #move Mom self.__move_col_from_here_down(left_group[-1], move_amt) #move marriage info if self.inlc_marr: left_group[0].next.y_cm += move_amt/2 def start(self): """Make the report""" #for person in self.persons.depth_first_gen(): for box in self.canvas.boxes: self.calc_box(box) #At this point we know everything we need to make the report. #Width of each column of people - self.rept_opt.box_width #width of each column (or row) of lines - self.rept_opt.col_width if len(self.cols[0]) == 0: #We wanted to print parents of starting person/family but #there were none! #remove column 0 and move everyone back one level self.cols.pop(0) for box in self.canvas.boxes: box.level = (box.level[0]-1, box.level[1]) #go ahead and set it now. width = self.canvas.doc.report_opts.max_box_width for box in self.canvas.boxes: box.width = width - box.x_cm box.x_cm += self.canvas.doc.report_opts.littleoffset box.x_cm += (box.level[0] * \ (self.canvas.doc.report_opts.col_width + self.canvas.doc.report_opts.max_box_width)) box.y_cm += self.canvas.doc.report_opts.littleoffset box.y_cm += self.canvas.title.height self.Make_report() class GuiConnect(): """ This is a BORG object. There is ONLY one. This give some common routines that EVERYONE can use like get the value from a GUI variable """ __shared_state = {} def __init__(self): #We are BORG! self.__dict__ = self.__shared_state def set__opts(self, options, which): self._opts = options self._which_report = which def get_val(self, val): return self._opts.get_option_by_name(val).get_value() def Title_class(self, database, doc): Title_type = self.get_val('report_title') if Title_type == 0: #None return TitleNone(database, doc) if Title_type == 1: #Descendant Chart if self._which_report == _RPT_NAME: if self.get_val('show_gparents'): return TitleDPY(database, doc) else: return TitleDPN(database, doc) else: if self.get_val('show_gparents'): return TitleDFY(database, doc) else: return TitleDFN(database, doc) if Title_type == 2: return TitleF(database, doc) else: #Title_type == 3 return TitleC(database, doc) def Make_Tree(self, database, canvas): if self._which_report == _RPT_NAME: return MakePersonTree(database, canvas) else: return MakeFamilyTree(database, canvas) def calc_lines(self, database): #calculate the printed lines for each box display_repl = self.get_val('replacelist') #str = "" #if self.get_val('miss_val'): # str = "_____" return CalcLines(database, display_repl) def working_lines(self, box): display = self.get_val('dispf') if self.get_val('diffspouse'): display_spou = self.get_val('sdispf') else: display_spou = display display_marr = [self.get_val('dispmarr')] if box.boxstr == "CG2-fam-box": #((((( workinglines = display_marr elif box.level[1] > 0 or (box.level[0] == 0 and box.father is not None): workinglines = display_spou else: workinglines = display return workinglines #------------------------------------------------------------------------ # # DescendTree # #------------------------------------------------------------------------ class Descend2Tree(Report): def __init__(self, database, options_class): """ Create Descend2Tree object that produces the report. The arguments are: database - the GRAMPS database instance options_class - instance of the Options class for this report This report needs the following parameters (class variables) that come in the options class. """ Report.__init__(self, database, options_class) self.Connect = GuiConnect() self.Connect.set__opts(options_class.menu, options_class.name) style_sheet = self.doc.get_style_sheet() font_normal = style_sheet.get_paragraph_style("CG2-Normal").get_font() self.doc.report_opts = ReportOptions(self.doc, font_normal) self.canvas = Canvas(self.doc) center_id = self.Connect.get_val('pid') #make the tree tree = self.Connect.Make_Tree(database, self.canvas) tree.start(center_id) tree = None #Title title = self.Connect.Title_class(database, self.doc) title.calc_title(center_id) self.canvas.add_title(title) #make the report as big as it wants to be. ind_spouse = self.Connect.get_val('indspouce') compress_tree = self.Connect.get_val('compress_tree') report = MakeReport(database, self.canvas, ind_spouse, compress_tree) report.start() report = None def begin_report(self): """ We have a report in its full size and pages to print on scale one or both as needed/desired. """ if self.Connect.get_val('use_note'): note_box = NoteBox(self.doc, "CG2-fam-box", self.Connect.get_val('note_local')) subst = SubstKeywords(self.database, None, None) note_box.text = subst.replace_and_clean( self.Connect.get_val('note_disp')) self.canvas.add_note(note_box) one_page = self.Connect.get_val('onepage') scale_report = self.Connect.get_val('scale_report') scale = self.canvas.scale_report(one_page, scale_report != 0, scale_report == 2) if scale != 1: self.scale_styles(scale) def write_report(self): """ Canvas now has everyone ready to print. Get some misc stuff together and print. """ one_page = self.Connect.get_val('onepage') scale_report = self.Connect.get_val('scale_report') #Inlc_marr = self.Connect.get_val('incmarr') inc_border = self.Connect.get_val('inc_border') incblank = self.Connect.get_val('incblank') prnnum = self.Connect.get_val('prnnum') #ind_spouse = self.Connect.get_val('indspouce') lines = self.Connect.get_val('note_disp') ##################### #Setup page infomation colsperpage = self.doc.get_usable_width() colsperpage += self.doc.report_opts.col_width tmp = self.doc.report_opts.max_box_width tmp += self.doc.report_opts.col_width colsperpage = int( colsperpage / tmp ) if colsperpage == 0: #Is the page really that small? colsperpage = 1 ##################### #Vars #p = self.doc.get_style_sheet().get_paragraph_style("CG2-Normal") #font = p.get_font() if prnnum: page_num_box = PageNumberBox(self.doc, 'CG2-box') ##################### #ok, everyone is now ready to print on the canvas. Paginate? self.canvas.sort_boxes_on_y_cm() self.canvas.paginate(colsperpage, one_page) ##################### #Yeah!!! #lets finally make some pages!!! ##################### for page in self.canvas.page_iter_gen(incblank): self.doc.start_page() #do we need to print a border? if inc_border: page.draw_border('CG2-line') #Do we need to print the page number? if prnnum: page_num_box.display(page) page.display() self.doc.end_page() def scale_styles(self, amount): """ Scale the styles for this report. This must be done in the constructor. """ style_sheet = self.doc.get_style_sheet() graph_style = style_sheet.get_draw_style("CG2-fam-box") graph_style.set_shadow(graph_style.get_shadow(), 0) graph_style.set_line_width(graph_style.get_line_width() * amount) style_sheet.add_draw_style("CG2-fam-box", graph_style) graph_style = style_sheet.get_draw_style("CG2-box") graph_style.set_shadow(graph_style.get_shadow(), self.doc.report_opts.box_shadow) graph_style.set_line_width(graph_style.get_line_width() * amount) style_sheet.add_draw_style("CG2-box", graph_style) graph_style = style_sheet.get_draw_style("CG2b-box") graph_style.set_shadow(graph_style.get_shadow(), self.doc.report_opts.box_shadow) graph_style.set_line_width(graph_style.get_line_width() * amount) style_sheet.add_draw_style("CG2b-box", graph_style) para_style = style_sheet.get_paragraph_style("CG2-Title") font = para_style.get_font() font.set_size(font.get_size() * amount) para_style.set_font(font) style_sheet.add_paragraph_style("CG2-Title", para_style) para_style = style_sheet.get_paragraph_style("CG2-Normal") font = para_style.get_font() font.set_size(font.get_size() * amount) para_style.set_font(font) style_sheet.add_paragraph_style("CG2-Normal", para_style) para_style = style_sheet.get_paragraph_style("CG2-Bold") font = para_style.get_font() font.set_bold(True) font.set_size(font.get_size() * amount) para_style.set_font(font) style_sheet.add_paragraph_style("CG2-Bold", para_style) self.doc.set_style_sheet(style_sheet) #------------------------------------------------------------------------ # # DescendTreeOptions # #------------------------------------------------------------------------ class Descend2TreeOptions(MenuReportOptions): """ Defines options and provides handling interface. """ def __init__(self, name, dbase): self.__pid = None self.__onepage = None self.__inc_title = None self.__title = None self.__blank = None self.scale = None self.__db = dbase self.name = name MenuReportOptions.__init__(self, name, dbase) def add_menu_options(self, menu): """ Add options to the menu for the descendant report. """ category_name = _("Tree Options") if self.name == _RPT_NAME: self.__pid = PersonOption(_("Report for")) self.__pid.set_help(_("The main person for the report")) menu.add_option(category_name, "pid", self.__pid) else: #if self.name == "familial_descend_tree": self.__pid = FamilyOption(_("Report for")) self.__pid.set_help(_("The main family for the report")) menu.add_option(category_name, "pid", self.__pid) self.showparents = BooleanOption( _('Start with the parent(s) of the selected first'), True) self.showparents.set_help( _("Will show the parents, brother and sisters of the" + "selected person.")) menu.add_option(category_name, "show_gparents", self.showparents) max_gen = NumberOption(_("Generations"), 2, 1, 50) max_gen.set_help(_("The number of generations to include in the tree")) menu.add_option(category_name, "maxgen", max_gen) max_spouse = NumberOption(_("Level of Spouses"), 1, 0, 10) max_spouse.set_help(_("0=no Spouses, 1=include Spouses, 2=include" + "Spouses of the spouse, etc")) menu.add_option(category_name, "maxspouse", max_spouse) compresst = BooleanOption(_('Co_mpress tree'), False) compresst.set_help(_("Whether to compress the tree.")) menu.add_option(category_name, "compress_tree", compresst) category_name = _("Display") disp = TextOption(_("Personal\nDisplay Format"), ["$n","%s $b" % _BORN,"%s $d" %_DIED]) disp.set_help(_("Display format for the outputbox.")) menu.add_option(category_name, "dispf", disp) bold = BooleanOption(_('Bold direct descendants'), True) bold.set_help(_("Whether to bold those people that are direct" + "(not step or half) decendants.")) menu.add_option(category_name, "bolddirect", bold) #Will add when libsubstkeyword supports it. #missing = EnumeratedListOption(_("Replace missing\npalces\\dates \ # with"), 0) #missing.add_item( 0, _("Does not display anything")) #missing.add_item( 1, _("Displays '_____'")) #missing.set_help(_("What will print when information is not known")) #menu.add_option(category_name, "miss_val", missing) category_name = _("Secondary") diffspouse = BooleanOption(_('Use seperate display format for ' + 'spouses'), True) diffspouse.set_help(_("Whether spouses can have a different format.")) menu.add_option(category_name, "diffspouse", diffspouse) indspouce = BooleanOption(_('Indent Spouses'), True) indspouce.set_help(_("Whether to indent the spouses in the tree.")) menu.add_option(category_name, "indspouce", indspouce) sdisp = TextOption(_("Spousal\nDisplay Format"), ["$n","%s $b" % _BORN,"%s $d" %_DIED]) sdisp.set_help(_("Display format for the outputbox.")) menu.add_option(category_name, "sdispf", sdisp) incmarr = BooleanOption(_('Include Marriage information'), True) incmarr.set_help(_("Whether to include marriage information in the" + "report.")) menu.add_option(category_name, "incmarr", incmarr) marrdisp = StringOption(_("Marraige\nDisplay Format"), "%s $m" % _MARR) marrdisp.set_help(_("Display format for the outputbox.")) menu.add_option(category_name, "dispmarr", marrdisp) category_name = _("Replace") repldisp = TextOption(_("Replace Display Format:\n'Replace this'/'" + "with this'"), []) repldisp.set_help(_("ie\nUnited States of America/U.S.A")) menu.add_option(category_name, "replacelist", repldisp) category_name = _("Print") self.scale = EnumeratedListOption(_("Scale report to fit"), 0) self.scale.add_item( 0, "Do not scale report") self.scale.add_item( 1, "Scale report to fit page width only") self.scale.add_item( 2, "Scale report to fit the size of the page") self.scale.set_help(_("Wheather to scale the report to fit a " + "specific size")) menu.add_option(category_name, "scale_report", self.scale) self.scale.connect('value-changed', self.__check_blank) self.__onepage = BooleanOption(_('One page report'), True) self.__onepage.set_help(_("Whether to scale the size of the page to " + "the size of the report.")) menu.add_option(category_name, "onepage", self.__onepage) self.__onepage.connect('value-changed', self.__check_blank) self.title = EnumeratedListOption(_("Report Title"), 0) self.title.add_item( 0, "Do not print a title") self.title.set_help(_("Choose a title for the report")) menu.add_option(category_name, "report_title", self.title) self.showparents.connect('value-changed', self.__Title_enum) border = BooleanOption(_('Print a border'), True) border.set_help(_("Whether to make a border around the report.")) menu.add_option(category_name, "inc_border", border) prnnum = BooleanOption(_('Print Page Numbers'), False) prnnum.set_help(_("Whether to print page numbers on each page.")) menu.add_option(category_name, "prnnum", prnnum) self.__blank = BooleanOption(_('Include Blank Pages'), True) self.__blank.set_help(_("Whether to include pages that are blank.")) menu.add_option(category_name, "incblank", self.__blank) category_name = _("Notes") self.usenote = BooleanOption(_('Include a personal note'), False) self.usenote.set_help(_("Whether to include a personalized note on " + "the report.")) menu.add_option(category_name, "use_note", self.usenote) self.notedisp = TextOption(_("Note to add\nto the graph\n\n$T " + "inserts today's date"), []) self.notedisp.set_help(_("Add a personal note")) menu.add_option(category_name, "note_disp", self.notedisp) locals = NoteType(0) notelocal = EnumeratedListOption(_("Note Location"), 0) for num, text in locals.note_locals(): notelocal.add_item( num, text ) notelocal.set_help(_("Where to place a personal note.")) menu.add_option(category_name, "note_local", notelocal) def __check_blank(self): """dis/enables the 'print blank pages' checkbox""" off = not self.__onepage.get_value() and (self.scale.get_value() != 2) self.__blank.set_available( off ) def __Title_enum(self): item_list = [] item_list.append([0, "Do not print a title" ]) item_list.append([1, "Descendant Chart for [selected person(s)]" ]) if self.name != _RPT_NAME: item_list.append([2, "Family Chart for [names of choosen family]" ]) if self.showparents.get_value(): item_list.append([3, "Cousin Chart for [names of children]" ]) self.title.set_items(item_list) def make_default_style(self, default_style): """Make the default output style for the Descendant Tree.""" from gen.plug.docgen import (FontStyle, ParagraphStyle, GraphicsStyle, FONT_SANS_SERIF, PARA_ALIGN_CENTER) ## Paragraph Styles: font = FontStyle() font.set_size(16) font.set_type_face(FONT_SANS_SERIF) para_style = ParagraphStyle() para_style.set_font(font) para_style.set_alignment(PARA_ALIGN_CENTER) para_style.set_description(_('The basic style used for the ' + 'title display.')) default_style.add_paragraph_style("CG2-Title", para_style) font = FontStyle() font.set_size(9) font.set_type_face(FONT_SANS_SERIF) para_style = ParagraphStyle() para_style.set_font(font) para_style.set_description(_('The basic style used for the ' + 'text display.')) default_style.add_paragraph_style("CG2-Normal", para_style) #Set the size of the shadow based on the font size! Much better #will be set later too. tmp = PT2CM(font.get_size()) box_shadow = tmp * .6 font.set_bold(True) para_style = ParagraphStyle() para_style.set_font(font) para_style.set_description(_('The bold style used for the ' + 'text display.')) default_style.add_paragraph_style("CG2-Bold", para_style) graph_style = GraphicsStyle() graph_style.set_paragraph_style("CG2-Title") graph_style.set_color((0, 0, 0)) graph_style.set_fill_color((255, 255, 255)) graph_style.set_line_width(0) default_style.add_draw_style("CG2-Title", graph_style) ## Draw styles graph_style = GraphicsStyle() graph_style.set_paragraph_style("CG2-Normal") graph_style.set_fill_color((255, 255, 255)) default_style.add_draw_style("CG2-fam-box", graph_style) graph_style = GraphicsStyle() graph_style.set_paragraph_style("CG2-Normal") graph_style.set_shadow(1, box_shadow) graph_style.set_fill_color((255, 255, 255)) default_style.add_draw_style("CG2-box", graph_style) graph_style = GraphicsStyle() graph_style.set_paragraph_style("CG2-Bold") graph_style.set_shadow(1, box_shadow) graph_style.set_fill_color((255, 255, 255)) default_style.add_draw_style("CG2b-box", graph_style) graph_style = GraphicsStyle() default_style.add_draw_style("CG2-line", graph_style) #===================================== #So do not fear, for I am with you; do not be dismayed, #for I am your God. I will strengthen you and help you; #I will uphold you with my righteous right hand. #Isaiah 41:10