gramps/src/plugins/drawreport/DescendTree.py

1696 lines
62 KiB
Python
Raw Normal View History

2005-12-06 12:08:09 +05:30
#
# 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
2005-12-06 12:08:09 +05:30
#
# 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
2005-12-06 12:08:09 +05:30
"""
Reports/Graphical Reports/Familial Tree
Reports/Graphical Reports/Personal Tree
"""
2005-12-06 12:08:09 +05:30
#------------------------------------------------------------------------
#
# GRAMPS modules
2005-12-06 12:08:09 +05:30
#
#------------------------------------------------------------------------
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
2005-12-06 12:08:09 +05:30
#------------------------------------------------------------------------
#
# Constants
#
#------------------------------------------------------------------------
_BORN = _('short for born|b.')
_DIED = _('short for died|d.')
_MARR = _('short for married|m.')
2005-12-06 12:08:09 +05:30
_RPT_NAME = 'descend_chart'
2005-12-06 12:08:09 +05:30
from libtreebase import *
2005-12-06 12:08:09 +05:30
#------------------------------------------------------------------------
#
# Box classes
2005-12-06 12:08:09 +05:30
#
#------------------------------------------------------------------------
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()
2005-12-06 12:08:09 +05:30
#------------------------------------------------------------------------
#
# Class RecurseDown
2005-12-06 12:08:09 +05:30
#
#------------------------------------------------------------------------
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
2005-12-06 12:08:09 +05:30
self.famalies_seen = set()
self.cols = []
self.__last_direct = []
2005-12-06 12:08:09 +05:30
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
2005-12-06 12:08:09 +05:30
def add_to_col(self, box):
2005-12-06 12:08:09 +05:30
"""
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()
2005-12-06 12:08:09 +05:30
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)
2005-12-06 12:08:09 +05:30
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
2005-12-06 12:08:09 +05:30
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)
2005-12-06 12:08:09 +05:30
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)
2005-12-06 12:08:09 +05:30
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()]
2005-12-06 12:08:09 +05:30
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
2005-12-06 12:08:09 +05:30
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)
2005-12-06 12:08:09 +05:30
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)
2005-12-06 12:08:09 +05:30
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
"""
2005-12-06 12:08:09 +05:30
if not person_handle or person_handle in self.famalies_seen:
return False
2005-12-06 12:08:09 +05:30
person = self.database.get_person_from_handle(person_handle)
2005-12-06 12:08:09 +05:30
for family_handle in person.get_family_handle_list():
if family_handle not in self.famalies_seen:
2005-12-06 12:08:09 +05:30
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
2005-12-06 12:08:09 +05:30
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):
2005-12-06 12:08:09 +05:30
"""
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.
2005-12-06 12:08:09 +05:30
"""
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
2005-12-06 12:08:09 +05:30
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)
2005-12-06 12:08:09 +05:30
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')
2005-12-06 12:08:09 +05:30
#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)
2005-12-06 12:08:09 +05:30
2005-12-06 12:08:09 +05:30
#------------------------------------------------------------------------
#
# DescendTreeOptions
2005-12-06 12:08:09 +05:30
#
#------------------------------------------------------------------------
class Descend2TreeOptions(MenuReportOptions):
2005-12-06 12:08:09 +05:30
"""
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