# # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham # Copyright (C) 2007-2008 Brian G. Matherly # Copyright (C) 2010 Jakim Friant # Copyright (C) 2009-2010 Craig J. 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: 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 from gen.plug.menu import NumberOption from gen.plug.menu import EnumeratedListOption from gen.plug.menu import StringOption from gen.plug.menu import BooleanOption from gen.plug.menu import PersonOption from gen.plug.menu import 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.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 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 self.line_to = None self.next = None 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 = []): """ calculate the Descendant title Person_list will always be passed If in the Family reports and there are two families, person_list2 will be used. """ if len(person_list) == len(person_list2) == 1: person_list = person_list + person_list2 person_list2 = [] names = self._get_names(person_list) if person_list2: names2 = self._get_names(person_list2) if len(names) + len(names2) == 3: if len(names) == 1: title = _("Descendant Chart for %(person)s and " "%(father1)s, %(mother1)s") % \ {'person': names[0], 'father1': names2[0], 'mother1': names2[1], } else: # Should be 2 items in names list title = _("Descendant Chart for %(person)s, %(father1)s " "and %(mother1)s") % \ {'father1': names[0], 'mother1': names[1], 'person': names2[0], } else: # Should be 2 items in both names and names2 lists 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], } else: # No person_list2: Just one family if len(names) == 1: title = _("Descendant Chart for %(person)s") % \ {'person': names[0]} else: # Should be two items in names list title = _("Descendant Chart for %(father)s and %(mother)s") % \ {'father': names[0], 'mother': names[1], } return title def get_parents(self, family_id): """ For a family_id, return the father and mother """ family1 = self.database.get_family_from_gramps_id(family_id) father_h = family1.get_father_handle() mother_h = family1.get_mother_handle() parents = [self.database.get_person_from_handle(handle) for handle in [father_h, mother_h] if handle] 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 = None if family2: father2_h = family2.get_father_handle() mother2_h = family2.get_mother_handle() person_list = [self.database.get_person_from_handle(handle) for handle in [father2_h, mother2_h] if handle] if not person_list: person_list = [center] 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 not person: return None parent_list = None family_h = person.get_main_parents_family_handle() family = self.database.get_family_from_handle(family_h) if family: #family = fathers parents father_h = family.get_father_handle() mother_h = family.get_mother_handle() parent_list = [self.database.get_person_from_handle(handle) for handle in [father_h, mother_h] if handle] return parent_list or [person] 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 not dad_parents: 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 = [self.database.get_person_from_handle(kid.ref) for kid in family.get_child_ref_list()] #ok we have the children. Make a title off of them tmp = self._get_names(kids) self.text = _("Cousin Chart for " + ", ".join(self._get_names(kids))) 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 = set() self.cols = [] self.__last_direct = [] 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 not self.max_spouses: 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 attrib 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) self.__last_direct.append(None) if self.cols[level]: #if (not the first box in this column) last_box = self.cols[level] last_box.next = box #calculate the .y_cm for this box. box.y_cm = last_box.y_cm box.y_cm += last_box.height if last_box.boxstr in ["CG2-box", "CG2b-box"]: box.y_cm += self.canvas.doc.report_opts.box_shadow if box.boxstr in ["CG2-box", "CG2b-box"]: box.y_cm += self.canvas.doc.report_opts.box_pgap else: box.y_cm += self.canvas.doc.report_opts.box_mgap if box.level[1] == 0 and self.__last_direct[level]: #ok, a new direct descendant. #print level, box.father is not None, self.__last_direct[level].father is not None, box.text[0], \ # self.__last_direct[level].text[0] if box.father != self.__last_direct[level].father and \ box.father != self.__last_direct[level]: box.y_cm += self.canvas.doc.report_opts.box_pgap self.cols[level] = box if box.level[1] == 0: self.__last_direct[level] = box box.x_cm = self.canvas.doc.report_opts.spouse_offset * box.level[1] self.canvas.set_box_height_width(box) 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) myself.father = father if myself.level[1] == 0 and self.bold_direct and self.bold_now: if self.bold_now == 1: self.bold_now = 0 myself.set_bold() if level[1] == 0 and father and myself.level[0] != father.level[0]: #I am a child if father.line_to: line = father.line_to else: line = LineBase(father) father.line_to = line #self.canvas.add_line(line) line.end.append(myself) #calculate the text. myself.calc_text(self.database, indi_handle, fams_handle) self.add_to_col(myself) 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) 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 not person_handle: return if x_level > self.max_generations: return if s_level > 0 and s_level == self.max_spouses: 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: val = family_handles[0] if family_handles else None 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.add(family_handle) family = self.database.get_family_from_handle(family_handle) #Marriage box if the option is there. if self.inlc_marr and self.max_spouses > 0: marr = self.add_marriage_box((x_level, s_level+1), person_handle, family_handle, father if s_level else myself) spouse_handle = ReportUtils.find_spouse(person, family) if self.max_spouses > s_level and \ spouse_handle not in self.famalies_seen: def _spouse_box(who): return self.add_person_box((x_level, s_level+1), spouse_handle, family_handle, who) if s_level > 0: spouse = _spouse_box(father) elif self.inlc_marr: spouse = _spouse_box(marr) else: spouse = _spouse_box(myself) mykids = [kid.ref for kid in family.get_child_ref_list()] def _child_recurse(who): self.recurse(child_ref, x_level+1, 0, who) for child_ref in mykids: if self.inlc_marr and self.max_spouses > 0: _child_recurse(marr) elif first == 1 and s_level == 0: _child_recurse(myself) elif spouse: _child_recurse(spouse) else: _child_recurse(myself) first = 0 if self.max_spouses > s_level and \ spouse_handle not in self.famalies_seen: #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: father_b = self.add_person_box( (level, 0), father_h, family_h, father2) else: father_b = self.add_person_box( (level, 0), None, None, father2) retrn = [father_b] if self.inlc_marr: family_b = self.add_marriage_box( (level, 1), father_h, family_h, father_b) retrn.append(family_b) self.famalies_seen.add(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) retrn.append(mother_b) family_line = family_b if self.inlc_marr else father_b for child_ref in family.get_child_ref_list(): self.recurse(child_ref.ref, level+1, 0, family_line) self.bold_now = 0 #Set up the lines for the family if not family_line.line_to: #no children. family_line.line_to = LineBase(family_line) if self.inlc_marr: family_line.line_to.start.append(father_b) family_line.line_to.start.append(mother_b) return retrn def has_children(self, person_handle): """ Quickly check to see if this person has children still we want to respect the FamaliesSeen list """ if not person_handle or 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.add(person_handle) # show = False if show: 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 = [] center1 = self.database.get_person_from_gramps_id(person_id) if center1 is None: raise ReportError(_("Person %s is not in the Database") % person_id) center1_h = center1.get_handle() #could be mom too. family2 = family2_h = None if self.do_gparents: family2_h = center1.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.add(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: self.add_family( 0, family2, None ) else: self.bold_now = 2 self.recurse(center1_h, 0, 0, None) 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) if family1 is None: raise ReportError(_("Family %s is not in the Database") % 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.add(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.add(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.add(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 not show: self.famalies_seen.add(father1_h) family2_l = self.add_family( 0, family2, None ) elif father1: ####################### #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. self.recurse_if(father1_h, 1) ####################### #my father, marriage info, mother, siblings, me ####################### if family2: #We need to add dad to the family family2_line = family2_l[1] if self.inlc_marr else family2_l[0] else: family2_line = None family1_l = self.add_family(1, family1, family2_line) mother1_b = family1_l[-1] #Mom's Box #make sure there is at least one child in this family. #if not put in a placeholder family1_line = family1_l[1] if self.inlc_marr else family1_l[0] if family1_line.line_to.end == []: box = PlaceHolderBox((mother1_b.level[0]+1, 0)) box.father = family1_l[0] self.add_to_col(box) family1_line.line_to.end = [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 = set([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.add(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.add(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 not family2 and mother1: self.recurse_if(mother1_h, 1) ####################### #my mothers parents! ####################### if family2: family2_l = self.add_family( 0, family2, None ) family2_line = family2_l[1] if self.inlc_marr else family2_l[0] family2_line = family2_line.line_to if family2_line.end != []: family2_line.end.insert(0, mother1_b) else: family2_line.end = [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 = family2_line ####################### #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: 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 col: if len(col) == 1 and col[0].line_to: 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: left_group = [] line = None #Form the parental (left) group. #am I a direct descendant? if box.level[1] == 0: #I am the father/mother. left_group.append(box) if box.line_to: line = box.line_to box = box.next if box and box.level[1] != 0 and self.inlc_marr: #add/start with the marriage box left_group.append(box) if box.line_to: line = box.line_to box = box.next if box and box.level[1] != 0 and self.max_spouses > 0: #add/start with the spousal box left_group.append(box) if box.line_to: line = box.line_to box = box.next if line: if len(line.start) > 1 and line.start[-1].level[1] == 0: #a dad and mom family from RecurseDown.add_family. add mom left_group.append(line.start[-1]) box = box.next #we now have everyone we want return left_group, line.end #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)-1, -1, -1): box = self.cols[x_col][0] #The first person in this col while box: left_group, right_group = self.__next_family_group(box) if not left_group: 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: 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. """ seen_parents = False 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 and mom. #if so we need to move down mariage information #and mom! left_line = left_group[0].line_to if not left_line: left_line = left_group[1].line_to #left_line = left_line.start if len(left_line.start) > 1 and not seen_parents: #only do Dad and Mom. len(left_line) > 1 seen_parents = True mom_cm = left_group[-1].y_cm + left_group[-1].height/2 last_child_cm = right_group[-1].y_cm if not self.compress_tree: last_child_cm += right_group[-1].height/2 move_amt = last_child_cm - mom_cm #if the moms height is less than the last childs height #The 0.2 is to see if this is even worth it. if move_amt > 0.2: #our children take up more space than us parents. #so space mom out! self.__move_col_from_here_down(left_group[-1], move_amt) #move marriage info if self.inlc_marr: left_group[1].y_cm += move_amt/2 if left_line.end[0].boxstr == 'None': left_line.end = [] 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 not self.cols[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): 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, "CG2-line") 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) colsperpage = colsperpage or 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 output box.")) 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) descendants.") ) menu.add_option(category_name, "bolddirect", bold) #Will add when libsubstkeyword supports it. #missing = EnumeratedListOption(_("Replace missing\nplaces\\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 separate 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 output box.")) 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(_("Marriage\nDisplay Format"), "%s $m" % _MARR) marrdisp.set_help(_("Display format for the output box.")) menu.add_option(category_name, "dispmarr", marrdisp) category_name = _("Replace") repldisp = TextOption( _("Replace Display Format:\n'Replace this'/' with this'"), []) repldisp.set_help(_("i.e.\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( _("Whether 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 = [ [0, "Do not print a title" ], [1, "Descendant Chart for [selected person(s)]" ], ] if self.name != _RPT_NAME: item_list.append( [2, "Family Chart for [names of chosen 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. box_shadow = PT2CM(font.get_size()) * .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