1104 lines
41 KiB
Python
1104 lines
41 KiB
Python
# Gramps - a GTK+/GNOME based genealogy program
|
|
#
|
|
# Copyright (C) 2000-2007 Donald N. Allingham
|
|
# Copyright (C) 2007-2012 Brian G. Matherly
|
|
# Copyright (C) 2010 Jakim Friant
|
|
# Copyright (C) 2014 Paul Franklin
|
|
# Copyright (C) 2010-2015 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
#
|
|
|
|
"""Reports/Graphical Reports/Ancestor Tree"""
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# Python modules
|
|
#
|
|
#------------------------------------------------------------------------
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# Gramps modules
|
|
#
|
|
#------------------------------------------------------------------------
|
|
|
|
from gramps.gen.const import GRAMPS_LOCALE as glocale
|
|
_ = glocale.translation.sgettext
|
|
from gramps.gen.errors import ReportError
|
|
from gramps.gen.plug.menu import (TextOption, NumberOption, BooleanOption,
|
|
EnumeratedListOption, StringOption,
|
|
PersonOption)
|
|
from gramps.gen.plug.report import Report, MenuReportOptions, stdoptions
|
|
from gramps.gen.plug.report import utils
|
|
from gramps.gen.plug.docgen import (FontStyle, ParagraphStyle, GraphicsStyle,
|
|
FONT_SANS_SERIF, PARA_ALIGN_CENTER)
|
|
from gramps.plugins.lib.libtreebase import *
|
|
from gramps.plugins.lib.librecurse import AscendPerson
|
|
from gramps.gen.proxy import CacheProxyDb
|
|
from gramps.gen.display.name import displayer as _nd
|
|
|
|
PT2CM = utils.pt2cm
|
|
#cm2pt = utils.cm2pt
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# Constants
|
|
#
|
|
#------------------------------------------------------------------------
|
|
_BORN = _("birth abbreviation|b."),
|
|
_DIED = _("death abbreviation|d."),
|
|
_MARR = _("marriage abbreviation|m."),
|
|
|
|
LVL_GEN, LVL_INDX, LVL_Y = range(3)
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# Box classes
|
|
#
|
|
#------------------------------------------------------------------------
|
|
class PersonBox(BoxBase):
|
|
"""
|
|
Calculates information about the box that will print on a page
|
|
"""
|
|
def __init__(self, level):
|
|
BoxBase.__init__(self)
|
|
self.boxstr = "AC2-box"
|
|
#self.level = (level[0]-1, level[1])
|
|
self.level = level
|
|
|
|
def __lt__(self, other):
|
|
return self.level[LVL_Y] < other.level[LVL_Y]
|
|
|
|
class FamilyBox(BoxBase):
|
|
"""
|
|
Calculates information about the box that will print on a page
|
|
"""
|
|
def __init__(self, level):
|
|
BoxBase.__init__(self)
|
|
self.boxstr = "AC2-fam-box"
|
|
#self.level = (level[0]-1, level[1])
|
|
self.level = level
|
|
|
|
def __lt__(self, other):
|
|
return self.level[LVL_Y] < other.level[LVL_Y]
|
|
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# Titles Class(es)
|
|
#
|
|
#------------------------------------------------------------------------
|
|
class TitleN(TitleNoDisplay):
|
|
"""No Title class for the report """
|
|
|
|
def __init__(self, doc, locale):
|
|
TitleNoDisplay.__init__(self, doc, "AC2-Title-box")
|
|
self._ = locale.translation.sgettext
|
|
|
|
def calc_title(self, center):
|
|
"""Calculate the title of the report"""
|
|
#we want no text, but need a text for the TOC in a book!
|
|
self.mark_text = self._("Ancestor Graph")
|
|
self.text = ''
|
|
|
|
class TitleA(TitleBox):
|
|
"""Title class for the report """
|
|
def __init__(self, doc, locale, name_displayer):
|
|
self._nd = name_displayer
|
|
TitleBox.__init__(self, doc, "AC2-Title-box")
|
|
self._ = locale.translation.sgettext
|
|
|
|
def calc_title(self, center):
|
|
"""Calculate the title of the report"""
|
|
name = ""
|
|
if center is not None:
|
|
name = self._nd.display(center)
|
|
|
|
# feature request 2356: avoid genitive form
|
|
self.text = self._("Ancestor Graph for %s") % name
|
|
self.set_box_height_width()
|
|
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# CalcItems (helper class to calculate text)
|
|
# make_ancestor_tree (main recursive functions)
|
|
#
|
|
#------------------------------------------------------------------------
|
|
class CalcItems:
|
|
""" A helper class to calculate the default box text
|
|
and text for each person / marriage
|
|
"""
|
|
def __init__(self, dbase):
|
|
_gui = GUIConnect()
|
|
self._gui = _gui
|
|
|
|
#calculate the printed lines for each box
|
|
#str = ""
|
|
#if self.get_val('miss_val'):
|
|
# str = "_____"
|
|
display_repl = _gui.get_val("replace_list")
|
|
self.__calc_l = CalcLines(dbase, display_repl, _gui.locale, _gui.n_d)
|
|
|
|
self.__blank_father = None
|
|
self.__blank_mother = None
|
|
|
|
self.__blank_father = \
|
|
self.__calc_l.calc_lines(None, None, _gui.get_val("father_disp"))
|
|
self.__blank_mother = \
|
|
self.__calc_l.calc_lines(None, None, _gui.get_val("mother_disp"))
|
|
|
|
self.center_use = _gui.get_val("center_uses")
|
|
self.disp_father = _gui.get_val("father_disp")
|
|
self.disp_mother = _gui.get_val("mother_disp")
|
|
|
|
self.disp_marr = [_gui.get_val("marr_disp")]
|
|
self.__blank_marriage = \
|
|
self.__calc_l.calc_lines(None, None, self.disp_marr)
|
|
|
|
def calc_person(self, index, indi_handle, fams_handle):
|
|
working_lines = ""
|
|
if index[1] % 2 == 0 or (index[1] == 1 and self.center_use == 0):
|
|
if indi_handle == fams_handle == None:
|
|
working_lines = self.__calc_l.calc_lines(
|
|
None, None, self._gui.get_val("father_disp"))
|
|
else:
|
|
working_lines = self.disp_father
|
|
else:
|
|
if indi_handle == fams_handle == None:
|
|
working_lines = self.__calc_l.calc_lines(
|
|
None, None, self._gui.get_val("mother_disp"))
|
|
else:
|
|
working_lines = self.disp_mother
|
|
|
|
if indi_handle == fams_handle == None:
|
|
return working_lines
|
|
else:
|
|
return self.__calc_l.calc_lines(indi_handle, fams_handle,
|
|
working_lines)
|
|
|
|
def calc_marriage(self, indi_handle, fams_handle):
|
|
if indi_handle == fams_handle == None:
|
|
return self.__blank_marriage
|
|
else:
|
|
return self.__calc_l.calc_lines(indi_handle, fams_handle,
|
|
self.disp_marr)
|
|
|
|
class MakeAncestorTree(AscendPerson):
|
|
"""
|
|
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):
|
|
_gui = GUIConnect()
|
|
|
|
AscendPerson.__init__(self, dbase, _gui.maxgen(), _gui.fill_out())
|
|
self.database = dbase
|
|
self.canvas = canvas
|
|
self.inlc_marr = _gui.inc_marr()
|
|
self.inc_sib = _gui.inc_sib()
|
|
self.compress_tree = _gui.compress_tree()
|
|
self.center_family = None
|
|
self.lines = [None] * (_gui.maxgen() + 1)
|
|
|
|
self.max_generation = 0
|
|
|
|
self.calc_items = CalcItems(self.database)
|
|
|
|
def add_person(self, index, indi_handle, fams_handle):
|
|
""" Makes a person box and add that person into the Canvas. """
|
|
|
|
#print str(index) + " add_person " + str(indi_handle)
|
|
myself = PersonBox((index[0]-1,) + index[1:])
|
|
|
|
if index[LVL_GEN] == 1: #Center Person
|
|
self.center_family = fams_handle
|
|
|
|
if index[LVL_GEN] > self.max_generation:
|
|
self.max_generation = index[LVL_GEN]
|
|
|
|
myself.text = self.calc_items.calc_person(index,
|
|
indi_handle, fams_handle)
|
|
# myself.text[0] = myself.text[0] + ' ' + repr(index) # for debugging
|
|
|
|
if indi_handle is not None: # None is legal for an empty box
|
|
myself.add_mark(self.database,
|
|
self.database.get_person_from_handle(indi_handle))
|
|
|
|
self.canvas.add_box(myself)
|
|
|
|
#make the lines
|
|
indx = index[LVL_GEN]
|
|
self.lines[indx] = myself
|
|
if indx > 1:
|
|
if self.lines[indx-1].line_to is None:
|
|
line = LineBase(self.lines[indx-1])
|
|
self.lines[indx-1].line_to = line
|
|
self.canvas.add_line(line)
|
|
else:
|
|
line = self.lines[indx-1].line_to
|
|
line.add_to(myself)
|
|
|
|
return myself
|
|
|
|
def add_person_again(self, index, indi_handle, fams_handle):
|
|
self.add_person(index, indi_handle, fams_handle)
|
|
|
|
def add_marriage(self, index, indi_handle, fams_handle):
|
|
""" Makes a marriage box and add that person into the Canvas. """
|
|
|
|
if not self.inlc_marr:
|
|
return
|
|
|
|
myself = FamilyBox((index[0]-1,) + index[1:])
|
|
|
|
#calculate the text.
|
|
myself.text = self.calc_items.calc_marriage(indi_handle, fams_handle)
|
|
|
|
self.canvas.add_box(myself)
|
|
|
|
def y_index(self, x_level, index):
|
|
""" Calculate the column or generation that this person is in.
|
|
x_level -> 0 to max_gen-1
|
|
index -> 1 to (self.max_generation**2)-1
|
|
"""
|
|
#Calculate which row in the column of people.
|
|
tmp_y = index - (2**x_level)
|
|
#Calculate which row in the table (yes table) of people.
|
|
delta = (2**self.max_generation) // (2**(x_level))
|
|
return int((delta/2) + (tmp_y*delta)) -1
|
|
|
|
def do_y_indx(self):
|
|
''' Make the y_index for all boxes
|
|
first off of a forumula, then remove blank areas around the edges,
|
|
then compress the tree if desired
|
|
'''
|
|
min_y = self.y_index(self.canvas.boxes[0].level[LVL_GEN],
|
|
self.canvas.boxes[0].level[LVL_INDX])
|
|
for box in self.canvas.boxes:
|
|
if "fam" in box.boxstr:
|
|
box.level = box.level + \
|
|
(self.y_index(box.level[LVL_GEN]-1,
|
|
int(box.level[LVL_INDX]/2)),)
|
|
else:
|
|
box.level = box.level + \
|
|
(self.y_index(box.level[LVL_GEN], box.level[LVL_INDX]),)
|
|
min_y = min(min_y, box.level[LVL_Y])
|
|
#print (str(box.level))
|
|
|
|
#if a last father (of fathers) does not have a father/parents
|
|
#Then there could be a gap. Remove this gap
|
|
if min_y > 0:
|
|
for box in self.canvas.boxes:
|
|
box.level = box.level[:LVL_Y] + (box.level[LVL_Y]-min_y,)
|
|
|
|
#Now that we have y_index, lets see if we need to squish the tree
|
|
self.canvas.boxes.sort() #Sort them on the y_index
|
|
if not self.compress_tree:
|
|
return
|
|
#boxes are already in top down [LVL_Y] form so lets
|
|
#set the box in the correct y level depending on compress_tree
|
|
y_level = 0
|
|
current_y = self.canvas.boxes[0].level[LVL_Y]
|
|
for box in self.canvas.boxes:
|
|
y_index = box.level[LVL_Y]
|
|
if y_index > current_y:
|
|
current_y = y_index
|
|
y_level += 1
|
|
box.level = box.level[:LVL_Y] + (y_level,)
|
|
|
|
def do_sibs(self):
|
|
if not self.inc_sib or self.center_family is None:
|
|
return
|
|
|
|
family = self.database.get_family_from_handle(self.center_family)
|
|
mykids = [kid.ref for kid in family.get_child_ref_list()]
|
|
|
|
if len(mykids) == 1: # No other siblings. Don't do anything.
|
|
return
|
|
|
|
# The first person is the center person had he/she has our information
|
|
center = self.canvas.boxes.pop(self.canvas.boxes.index(self.lines[1]))
|
|
line = center.line_to
|
|
level = center.level[LVL_Y]
|
|
|
|
move = level - (len(mykids)//2) + ((len(mykids)+1)%2)
|
|
|
|
if move < 0:
|
|
# more kids than parents. ran off the page. Move them all down
|
|
for box in self.canvas.boxes:
|
|
box.level = (box.level[0], box.level[1], box.level[2]-move)
|
|
move = 0
|
|
|
|
|
|
line.start = []
|
|
rrr = -1 # if len(mykids)%2 == 1 else 0
|
|
for kid in mykids:
|
|
rrr += 1
|
|
mee = self.add_person((1, 1, move+rrr), kid, self.center_family)
|
|
line.add_from(mee)
|
|
#mee.level = (0, 1, level - (len(mykids)//2)+rrr)
|
|
|
|
|
|
def start(self, person_id):
|
|
""" go ahead and make it happen """
|
|
center = self.database.get_person_from_gramps_id(person_id)
|
|
if center is None:
|
|
raise ReportError(
|
|
_("Person %s is not in the Database") % person_id)
|
|
center_h = center.get_handle()
|
|
|
|
#Step 1. Get the people
|
|
self.recurse(center_h)
|
|
|
|
#Step 2. Calculate the y_index for everyone
|
|
self.do_y_indx()
|
|
|
|
#Step 3. Siblings of the center person
|
|
self.do_sibs()
|
|
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# Transform Classes
|
|
#
|
|
#------------------------------------------------------------------------
|
|
#------------------------------------------------------------------------
|
|
# Class lr_Transform
|
|
#------------------------------------------------------------------------
|
|
class LRTransform:
|
|
"""
|
|
setup all of the boxes on the canvas in for a left/right report
|
|
"""
|
|
def __init__(self, canvas, max_generations):
|
|
self.canvas = canvas
|
|
self.rept_opts = canvas.report_opts
|
|
self.y_offset = self.rept_opts.littleoffset*2 + self.canvas.title.height
|
|
|
|
def _place(self, box):
|
|
""" put the box in it's correct spot """
|
|
#1. cm_x
|
|
box.x_cm = self.rept_opts.littleoffset
|
|
box.x_cm += (box.level[LVL_GEN] *
|
|
(self.rept_opts.col_width + self.rept_opts.max_box_width))
|
|
#2. cm_y
|
|
box.y_cm = self.rept_opts.max_box_height + self.rept_opts.box_pgap
|
|
box.y_cm *= box.level[LVL_Y]
|
|
box.y_cm += self.y_offset
|
|
#if box.height < self.rept_opts.max_box_height:
|
|
# box.y_cm += ((self.rept_opts.max_box_height - box.height) /2)
|
|
|
|
def place(self):
|
|
""" Step through boxes so they can be put in the right spot """
|
|
#prime the pump
|
|
self.__last_y_level = self.canvas.boxes[0].level[LVL_Y]
|
|
#go
|
|
for box in self.canvas.boxes:
|
|
self._place(box)
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# class make_report
|
|
#
|
|
#------------------------------------------------------------------------
|
|
class MakeReport:
|
|
|
|
def __init__(self, dbase, doc, canvas, font_normal):
|
|
|
|
self.database = dbase
|
|
self.doc = doc
|
|
self.canvas = canvas
|
|
self.font_normal = font_normal
|
|
|
|
_gui = GUIConnect()
|
|
self.inlc_marr = _gui.inc_marr()
|
|
self.compress_tree = _gui.compress_tree()
|
|
|
|
self.mother_ht = self.father_ht = 0
|
|
|
|
self.max_generations = 0
|
|
|
|
def get_height_width(self, box):
|
|
"""
|
|
obtain width information for each level (x)
|
|
obtain height information for each item
|
|
"""
|
|
|
|
self.canvas.set_box_height_width(box)
|
|
|
|
if box.width > self.canvas.report_opts.max_box_width:
|
|
self.canvas.report_opts.max_box_width = box.width #+ box.shadow
|
|
|
|
if box.level[LVL_Y] > 0:
|
|
if box.level[LVL_INDX] % 2 == 0 and box.height > self.father_ht:
|
|
self.father_ht = box.height
|
|
elif box.level[LVL_INDX] % 2 == 1 and box.height > self.mother_ht:
|
|
self.mother_ht = box.height
|
|
|
|
if box.level[LVL_GEN] > self.max_generations:
|
|
self.max_generations = box.level[LVL_GEN]
|
|
|
|
def get_generations(self):
|
|
return self.max_generations
|
|
|
|
def start(self):
|
|
## __gui = GUIConnect()
|
|
# 1.
|
|
#set the sizes for each box and get the max_generations.
|
|
self.father_ht = 0.0
|
|
self.mother_ht = 0.0
|
|
for box in self.canvas.boxes:
|
|
self.get_height_width(box)
|
|
|
|
if self.compress_tree and not self.inlc_marr:
|
|
self.canvas.report_opts.max_box_height = \
|
|
min(self.father_ht, self.mother_ht)
|
|
else:
|
|
self.canvas.report_opts.max_box_height = \
|
|
max(self.father_ht, self.mother_ht)
|
|
|
|
#At this point we know everything we need to make the report.
|
|
#Size of each column of people - self.rept_opt.box_width
|
|
#size of each column (or row) of lines - self.rept_opt.col_width
|
|
#size of each row - self.rept_opt.box_height
|
|
#go ahead and set it now.
|
|
for box in self.canvas.boxes:
|
|
box.width = self.canvas.report_opts.max_box_width
|
|
|
|
# 2.
|
|
#setup the transform class to move around the boxes on the canvas
|
|
transform = LRTransform(self.canvas, self.max_generations)
|
|
transform.place()
|
|
|
|
|
|
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, locale, name_displayer):
|
|
""" Set only once as we are BORG. """
|
|
self.__opts = options
|
|
self.locale = locale
|
|
self.n_d = name_displayer
|
|
|
|
def get_val(self, val):
|
|
""" Get a GUI value. """
|
|
value = self.__opts.get_option_by_name(val)
|
|
if value:
|
|
return value.get_value()
|
|
else:
|
|
False
|
|
|
|
def title_class(self, doc):
|
|
""" Return a class that holds the proper title based off of the
|
|
GUI options """
|
|
title_type = self.get_val('report_title')
|
|
if title_type:
|
|
return TitleA(doc, self.locale, self.n_d)
|
|
else:
|
|
return TitleN(doc, self.locale)
|
|
|
|
def inc_marr(self):
|
|
return self.get_val("inc_marr")
|
|
|
|
def inc_sib(self):
|
|
return self.get_val("inc_siblings")
|
|
|
|
def maxgen(self):
|
|
return self.get_val("maxgen")
|
|
|
|
def fill_out(self):
|
|
return self.get_val("fill_out")
|
|
|
|
def compress_tree(self):
|
|
return self.get_val("compress_tree")
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# AncestorTree
|
|
#
|
|
#------------------------------------------------------------------------
|
|
class AncestorTree(Report):
|
|
""" AncestorTree Report """
|
|
|
|
def __init__(self, database, options, user):
|
|
"""
|
|
Create AncestorTree object that produces the report.
|
|
|
|
The arguments are:
|
|
|
|
database - the Gramps database instance
|
|
options - instance of the Options class for this report
|
|
user - a gen.user.User() instance
|
|
"""
|
|
Report.__init__(self, database, options, user)
|
|
|
|
self.options = options
|
|
self._user = user
|
|
|
|
lang = options.menu.get_option_by_name('trans').get_value()
|
|
self._locale = self.set_locale(lang)
|
|
stdoptions.run_private_data_option(self, options.menu)
|
|
stdoptions.run_living_people_option(self, options.menu, self._locale)
|
|
self.database = CacheProxyDb(self.database)
|
|
stdoptions.run_name_format_option(self, options.menu)
|
|
self._nd = self._name_display
|
|
|
|
def begin_report(self):
|
|
"""
|
|
This report needs the following parameters (class variables)
|
|
that come in the options class.
|
|
|
|
max_generations - Maximum number of generations to include.
|
|
pagebbg - Whether to include page breaks between generations.
|
|
dispf - Display format for the output box.
|
|
scale_report - Whether to scale the report to fit the width or all.
|
|
indblank - Whether to include blank pages.
|
|
compress - Whether to compress chart.
|
|
incl_private - Whether to include private data
|
|
living_people - How to handle living people
|
|
years_past_death - Consider as living this many years after death
|
|
|
|
We will
|
|
1. a canvas in its full one-page size
|
|
2. a page that we wish to print on
|
|
scale up/down either or both of the above as needed/desired.
|
|
almost all of this should be moved into Canvas!
|
|
"""
|
|
|
|
database = self.database
|
|
|
|
self.connect = GUIConnect()
|
|
self.connect.set__opts(self.options.menu, self._locale, self._nd)
|
|
|
|
#Set up the canvas that we will print on.
|
|
style_sheet = self.doc.get_style_sheet()
|
|
font_normal = style_sheet.get_paragraph_style("AC2-Normal").get_font()
|
|
|
|
#The canvas that we will put our report on and print off of
|
|
self.canvas = Canvas(self.doc,
|
|
ReportOptions(self.doc, font_normal, 'AC2-line'))
|
|
|
|
self.canvas.report_opts.box_shadow *= \
|
|
self.connect.get_val('shadowscale')
|
|
self.canvas.report_opts.box_pgap *= self.connect.get_val('box_Yscale')
|
|
self.canvas.report_opts.box_mgap *= self.connect.get_val('box_Yscale')
|
|
|
|
with self._user.progress(_('Ancestor Tree'),
|
|
_('Making the Tree...'), 4) as step:
|
|
|
|
#make the tree onto the canvas
|
|
## inlc_marr = self.connect.get_val("inc_marr")
|
|
self.max_generations = self.connect.get_val('maxgen')
|
|
tree = MakeAncestorTree(database, self.canvas)
|
|
tree.start(self.connect.get_val('pid'))
|
|
tree = None
|
|
|
|
step()
|
|
|
|
#Title
|
|
title = self.connect.title_class(self.doc)
|
|
center = self.database.get_person_from_gramps_id(
|
|
self.connect.get_val('pid'))
|
|
title.calc_title(center)
|
|
self.canvas.add_title(title)
|
|
|
|
#make the report as big as it wants to be.
|
|
report = MakeReport(database, self.doc, self.canvas, font_normal)
|
|
report.start()
|
|
self.max_generations = report.get_generations() #already know
|
|
report = None
|
|
|
|
step()
|
|
|
|
#Note?
|
|
if self.connect.get_val("inc_note"):
|
|
note_box = NoteBox(self.doc, "AC2-note-box",
|
|
self.connect.get_val("note_place"))
|
|
subst = SubstKeywords(self.database, self._locale, self._nd,
|
|
None, None)
|
|
note_box.text = subst.replace_and_clean(
|
|
self.connect.get_val('note_disp'))
|
|
self.canvas.add_note(note_box)
|
|
|
|
#Now we have the report in its full size.
|
|
#Do we want to scale the report?
|
|
one_page = self.connect.get_val("resize_page")
|
|
scale_report = self.connect.get_val("scale_tree")
|
|
|
|
scale = self.canvas.scale_report(one_page,
|
|
scale_report != 0,
|
|
scale_report == 2)
|
|
|
|
step()
|
|
|
|
if scale != 1 or self.connect.get_val('shadowscale') != 1.0:
|
|
self.scale_styles(scale)
|
|
|
|
def write_report(self):
|
|
|
|
one_page = self.connect.get_val("resize_page")
|
|
#scale_report = self.connect.get_val("scale_tree")
|
|
|
|
#inlc_marr = self.connect.get_val("inc_marr")
|
|
inc_border = self.connect.get_val('inc_border')
|
|
incblank = self.connect.get_val("inc_blank")
|
|
prnnum = self.connect.get_val("inc_pagenum")
|
|
|
|
#####################
|
|
#Setup page information
|
|
|
|
colsperpage = self.doc.get_usable_width()
|
|
colsperpage += self.canvas.report_opts.col_width
|
|
colsperpage = int(colsperpage / (self.canvas.report_opts.max_box_width +
|
|
self.canvas.report_opts.col_width))
|
|
colsperpage = colsperpage or 1
|
|
|
|
#####################
|
|
#Vars
|
|
if prnnum:
|
|
page_num_box = PageNumberBox(self.doc, 'AC2-box', self._locale)
|
|
|
|
#TODO - Here
|
|
|
|
#####################
|
|
#ok, everyone is now ready to print on the canvas. Paginate?
|
|
self.canvas.paginate(colsperpage, one_page)
|
|
|
|
#####################
|
|
#Yeah!!!
|
|
#lets finally make some pages!!!
|
|
#####################
|
|
pages = self.canvas.page_count(incblank)
|
|
with self._user.progress(_('Ancestor Tree'),
|
|
_('Printing the Tree...'), pages) as step:
|
|
|
|
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('AC2-line')
|
|
|
|
#Do we need to print the page number?
|
|
if prnnum:
|
|
page_num_box.display(page)
|
|
|
|
#Print the individual people and lines
|
|
page.display()
|
|
|
|
step()
|
|
self.doc.end_page()
|
|
|
|
def scale_styles(self, scale):
|
|
"""
|
|
Scale the styles for this report.
|
|
"""
|
|
style_sheet = self.doc.get_style_sheet()
|
|
|
|
graph_style = style_sheet.get_draw_style("AC2-box")
|
|
graph_style.set_shadow(graph_style.get_shadow(),
|
|
self.canvas.report_opts.box_shadow * scale)
|
|
graph_style.set_line_width(graph_style.get_line_width() * scale)
|
|
style_sheet.add_draw_style("AC2-box", graph_style)
|
|
|
|
graph_style = style_sheet.get_draw_style("AC2-fam-box")
|
|
graph_style.set_shadow(graph_style.get_shadow(),
|
|
self.canvas.report_opts.box_shadow * scale)
|
|
graph_style.set_line_width(graph_style.get_line_width() * scale)
|
|
style_sheet.add_draw_style("AC2-fam-box", graph_style)
|
|
|
|
graph_style = style_sheet.get_draw_style("AC2-note-box")
|
|
#graph_style.set_shadow(graph_style.get_shadow(),
|
|
# self.canvas.report_opts.box_shadow * scale)
|
|
graph_style.set_line_width(graph_style.get_line_width() * scale)
|
|
style_sheet.add_draw_style("AC2-note-box", graph_style)
|
|
|
|
para_style = style_sheet.get_paragraph_style("AC2-Normal")
|
|
font = para_style.get_font()
|
|
font.set_size(font.get_size() * scale)
|
|
para_style.set_font(font)
|
|
style_sheet.add_paragraph_style("AC2-Normal", para_style)
|
|
|
|
para_style = style_sheet.get_paragraph_style("AC2-Note")
|
|
font = para_style.get_font()
|
|
font.set_size(font.get_size() * scale)
|
|
para_style.set_font(font)
|
|
style_sheet.add_paragraph_style("AC2-Note", para_style)
|
|
|
|
para_style = style_sheet.get_paragraph_style("AC2-Title")
|
|
font = para_style.get_font()
|
|
font.set_size(font.get_size() * scale)
|
|
para_style.set_font(font)
|
|
style_sheet.add_paragraph_style("AC2-Title", para_style)
|
|
|
|
graph_style = GraphicsStyle()
|
|
width = graph_style.get_line_width()
|
|
width = width * scale
|
|
graph_style.set_line_width(width)
|
|
style_sheet.add_draw_style("AC2-line", graph_style)
|
|
|
|
self.doc.set_style_sheet(style_sheet)
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# AncestorTreeOptions
|
|
#
|
|
#------------------------------------------------------------------------
|
|
class AncestorTreeOptions(MenuReportOptions):
|
|
|
|
"""
|
|
Defines options and provides handling interface.
|
|
"""
|
|
|
|
def __init__(self, name, dbase):
|
|
self.__db = dbase
|
|
self.__pid = None
|
|
self.box_Y_sf = None
|
|
self.box_shadow_sf = None
|
|
MenuReportOptions.__init__(self, name, dbase)
|
|
|
|
def get_subject(self):
|
|
""" Return a string that describes the subject of the report. """
|
|
gid = self.__pid.get_value()
|
|
person = self.__db.get_person_from_gramps_id(gid)
|
|
return _nd.display(person)
|
|
|
|
def add_menu_options(self, menu):
|
|
|
|
##################
|
|
category_name = _("Tree Options")
|
|
|
|
self.__pid = PersonOption(_("Center Person"))
|
|
self.__pid.set_help(_("The center person for the tree"))
|
|
menu.add_option(category_name, "pid", self.__pid)
|
|
|
|
siblings = BooleanOption(
|
|
_('Include siblings of the center person'), False)
|
|
siblings.set_help(
|
|
_("Whether to only display the center person or all "
|
|
"of his/her siblings too"))
|
|
menu.add_option(category_name, "inc_siblings", siblings)
|
|
|
|
self.max_gen = NumberOption(_("Generations"), 10, 1, 50)
|
|
self.max_gen.set_help(_("The number of generations to include "
|
|
"in the tree"))
|
|
menu.add_option(category_name, "maxgen", self.max_gen)
|
|
|
|
self.fillout = EnumeratedListOption(_("Display unknown\ngenerations"),
|
|
0)
|
|
self.fillout.set_help(_("The number of generations of empty "
|
|
"boxes that will be displayed"))
|
|
menu.add_option(category_name, "fill_out", self.fillout)
|
|
|
|
self.max_gen.connect('value-changed', self.__fillout_vals)
|
|
self.__fillout_vals()
|
|
|
|
compress = BooleanOption(_('Compress tree'), True)
|
|
compress.set_help(
|
|
_("Whether to remove any extra blank spaces set "
|
|
"aside for people that are unknown"))
|
|
menu.add_option(category_name, "compress_tree", compress)
|
|
|
|
#better to 'Show siblings of\nthe center person
|
|
#Spouse_disp = EnumeratedListOption(_("Show spouses of\nthe center "
|
|
# "person"), 0)
|
|
#Spouse_disp.add_item(0, _("No. Do not show Spouses"))
|
|
#Spouse_disp.add_item(1, _("Yes, and use the Main Display Format"))
|
|
#Spouse_disp.add_item(2, _("Yes, and use the Secondary "
|
|
# "Display Format"))
|
|
#Spouse_disp.set_help(_("Show spouses of the center person?"))
|
|
#menu.add_option(category_name, "Spouse_disp", Spouse_disp)
|
|
|
|
##################
|
|
category_name = _("Report Options")
|
|
|
|
self.title = EnumeratedListOption(_("Report Title"), 0)
|
|
self.title.add_item(0, _("Do not include a title"))
|
|
self.title.add_item(1, _("Include Report Title"))
|
|
self.title.set_help(_("Choose a title for the report"))
|
|
menu.add_option(category_name, "report_title", self.title)
|
|
|
|
border = BooleanOption(_('Include a border'), False)
|
|
border.set_help(_("Whether to make a border around the report."))
|
|
menu.add_option(category_name, "inc_border", border)
|
|
|
|
prnnum = BooleanOption(_('Include Page Numbers'), False)
|
|
prnnum.set_help(_("Whether to print page numbers on each page."))
|
|
menu.add_option(category_name, "inc_pagenum", prnnum)
|
|
|
|
self.scale = EnumeratedListOption(_("Scale tree to fit"), 0)
|
|
self.scale.add_item(0, _("Do not scale tree"))
|
|
self.scale.add_item(1, _("Scale tree to fit page width only"))
|
|
self.scale.add_item(2, _("Scale tree to fit the size of the page"))
|
|
self.scale.set_help(
|
|
_("Whether to scale the tree to fit a specific paper size")
|
|
)
|
|
menu.add_option(category_name, "scale_tree", self.scale)
|
|
self.scale.connect('value-changed', self.__check_blank)
|
|
|
|
if "BKI" not in self.name.split(","):
|
|
self.__onepage = BooleanOption(
|
|
_("Resize Page to Fit Tree size\n"
|
|
"\n"
|
|
"Note: Overrides options in the 'Paper Option' tab"
|
|
),
|
|
False)
|
|
self.__onepage.set_help(
|
|
_("Whether to resize the page to fit the size \n"
|
|
"of the tree. Note: the page will have a \n"
|
|
"non standard size.\n"
|
|
"\n"
|
|
"With this option selected, the following will happen:\n"
|
|
"\n"
|
|
"With the 'Do not scale tree' option the page\n"
|
|
" is resized to the height/width of the tree\n"
|
|
"\n"
|
|
"With 'Scale tree to fit page width only' the height of\n"
|
|
" the page is resized to the height of the tree\n"
|
|
"\n"
|
|
"With 'Scale tree to fit the size of the page' the page\n"
|
|
" is resized to remove any gap in either height or width"
|
|
))
|
|
menu.add_option(category_name, "resize_page", self.__onepage)
|
|
self.__onepage.connect('value-changed', self.__check_blank)
|
|
else:
|
|
self.__onepage = None
|
|
|
|
self.__blank = BooleanOption(_('Include Blank Pages'), True)
|
|
self.__blank.set_help(_("Whether to include pages that are blank."))
|
|
menu.add_option(category_name, "inc_blank", self.__blank)
|
|
|
|
self.__check_blank()
|
|
|
|
##################
|
|
category_name = _("Report Options (2)")
|
|
|
|
stdoptions.add_name_format_option(menu, category_name)
|
|
|
|
stdoptions.add_living_people_option(menu, category_name)
|
|
|
|
stdoptions.add_private_data_option(menu, category_name)
|
|
|
|
stdoptions.add_localization_option(menu, category_name)
|
|
|
|
##################
|
|
category_name = _("Display")
|
|
|
|
disp = TextOption(_("Father\nDisplay Format"),
|
|
["$n",
|
|
"%s $b" %_BORN,
|
|
"-{%s $d}" %_DIED])
|
|
disp.set_help(_("Display format for the fathers box."))
|
|
menu.add_option(category_name, "father_disp", disp)
|
|
|
|
#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)
|
|
|
|
disp_mom = TextOption(_("Mother\nDisplay Format"),
|
|
["$n",
|
|
"%s $b" %_BORN,
|
|
"%s $m" %_MARR,
|
|
"-{%s $d}" %_DIED])
|
|
disp_mom.set_help(_("Display format for the mothers box."))
|
|
menu.add_option(category_name, "mother_disp", disp_mom)
|
|
|
|
center_disp = EnumeratedListOption(_("Center person uses\n"
|
|
"which format"), 0)
|
|
center_disp.add_item(0, _("Use Fathers Display format"))
|
|
center_disp.add_item(1, _("Use Mothers display format"))
|
|
center_disp.set_help(_("The display format for the center person"))
|
|
menu.add_option(category_name, "center_uses", center_disp)
|
|
|
|
self.incmarr = BooleanOption(_('Include Marriage box'), False)
|
|
self.incmarr.set_help(
|
|
_("Whether to include a separate marital box in the report"))
|
|
menu.add_option(category_name, "inc_marr", self.incmarr)
|
|
self.incmarr.connect('value-changed', self._incmarr_changed)
|
|
|
|
self.marrdisp = StringOption(_("Marriage\nDisplay Format"),
|
|
"%s $m" % _MARR)
|
|
self.marrdisp.set_help(_("Display format for the marital box."))
|
|
menu.add_option(category_name, "marr_disp", self.marrdisp)
|
|
self._incmarr_changed()
|
|
|
|
##################
|
|
category_name = _("Advanced")
|
|
|
|
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, "replace_list", repldisp)
|
|
|
|
# TODO this code is never used and so I conclude it is for future use
|
|
# self.__include_images = BooleanOption(
|
|
# _('Include thumbnail images of people'), False)
|
|
# self.__include_images.set_help(
|
|
# _("Whether to include thumbnails of people."))
|
|
# menu.add_option(category_name, "includeImages", self.__include_images)
|
|
|
|
self.usenote = BooleanOption(_('Include a note'), False)
|
|
self.usenote.set_help(_("Whether to include a note on the report."))
|
|
menu.add_option(category_name, "inc_note", self.usenote)
|
|
self.usenote.connect('value-changed', self._usenote_changed)
|
|
|
|
self.notedisp = TextOption(_("Note"), [])
|
|
self.notedisp.set_help(_("Add a note\n\n"
|
|
"$T inserts today's date"))
|
|
menu.add_option(category_name, "note_disp", self.notedisp)
|
|
|
|
locales = NoteType(0, 1)
|
|
self.notelocal = EnumeratedListOption(_("Note Location"), 0)
|
|
for num, text in locales.note_locals():
|
|
self.notelocal.add_item(num, text)
|
|
self.notelocal.set_help(_("Where to place the note."))
|
|
menu.add_option(category_name, "note_place", self.notelocal)
|
|
self._usenote_changed()
|
|
|
|
self.box_Y_sf = NumberOption(_("inter-box scale factor"),
|
|
1.00, 0.10, 2.00, 0.01)
|
|
self.box_Y_sf.set_help(
|
|
_("Make the inter-box spacing bigger or smaller"))
|
|
menu.add_option(category_name, "box_Yscale", self.box_Y_sf)
|
|
|
|
self.box_shadow_sf = NumberOption(_("box shadow scale factor"),
|
|
1.00, 0.00, 2.00, 0.01) # down to 0
|
|
self.box_shadow_sf.set_help(_("Make the box shadow bigger or smaller"))
|
|
menu.add_option(category_name, "shadowscale", self.box_shadow_sf)
|
|
|
|
def _incmarr_changed(self):
|
|
"""
|
|
If Marriage box is not enabled, disable Marriage Display Format box
|
|
"""
|
|
value = self.incmarr.get_value()
|
|
self.marrdisp.set_available(value)
|
|
|
|
def _usenote_changed(self):
|
|
"""
|
|
If Note box is not enabled, disable Note Location box
|
|
"""
|
|
value = self.usenote.get_value()
|
|
self.notelocal.set_available(value)
|
|
|
|
def __check_blank(self):
|
|
if self.__onepage:
|
|
value = not self.__onepage.get_value()
|
|
else:
|
|
value = True
|
|
off = value and (self.scale.get_value() != 2)
|
|
self.__blank.set_available(off)
|
|
|
|
def __fillout_vals(self):
|
|
max_gen = self.max_gen.get_value()
|
|
old_val = self.fillout.get_value()
|
|
item_list = []
|
|
item_list.append([0, _("No generations of empty boxes "
|
|
"for unknown ancestors")])
|
|
if max_gen > 1:
|
|
item_list.append([1, _("One Generation of empty boxes "
|
|
"for unknown ancestors")])
|
|
|
|
item_list.extend(
|
|
[itr, str(itr) +
|
|
_(" Generations of empty boxes for unknown ancestors")]
|
|
for itr in range(2, max_gen))
|
|
|
|
self.fillout.set_items(item_list)
|
|
if old_val+2 > len(item_list):
|
|
self.fillout.set_value(len(item_list) -2)
|
|
|
|
def make_default_style(self, default_style):
|
|
"""Make the default output style for the Ancestor Tree."""
|
|
|
|
## Paragraph Styles:
|
|
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("AC2-Normal", para_style)
|
|
box_shadow = PT2CM(font.get_size()) * .6
|
|
|
|
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 note display.'))
|
|
default_style.add_paragraph_style("AC2-Note", para_style)
|
|
|
|
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 style used for the title.'))
|
|
default_style.add_paragraph_style("AC2-Title", para_style)
|
|
|
|
## Draw styles
|
|
graph_style = GraphicsStyle()
|
|
graph_style.set_paragraph_style("AC2-Normal")
|
|
graph_style.set_shadow(1, box_shadow) #shadow set by text size
|
|
graph_style.set_fill_color((255, 255, 255))
|
|
default_style.add_draw_style("AC2-box", graph_style)
|
|
|
|
graph_style = GraphicsStyle()
|
|
graph_style.set_paragraph_style("AC2-Normal")
|
|
#graph_style.set_shadow(0, PT2CM(9)) #shadow set by text size
|
|
graph_style.set_fill_color((255, 255, 255))
|
|
default_style.add_draw_style("AC2-fam-box", graph_style)
|
|
|
|
graph_style = GraphicsStyle()
|
|
graph_style.set_paragraph_style("AC2-Note")
|
|
graph_style.set_fill_color((255, 255, 255))
|
|
default_style.add_draw_style("AC2-note-box", graph_style)
|
|
|
|
# TODO this seems meaningless, as only the text is displayed
|
|
graph_style = GraphicsStyle()
|
|
graph_style.set_paragraph_style("AC2-Title")
|
|
graph_style.set_color((0, 0, 0))
|
|
graph_style.set_fill_color((255, 255, 255))
|
|
graph_style.set_line_width(0)
|
|
graph_style.set_description(_("Cannot edit this reference"))
|
|
default_style.add_draw_style("AC2-Title-box", graph_style)
|
|
|
|
graph_style = GraphicsStyle()
|
|
default_style.add_draw_style("AC2-line", graph_style)
|
|
|
|
#=====================================
|
|
#But even if you should suffer for what is right, you are blessed.
|
|
#"Do not fear what they fear ; do not be frightened."
|
|
#Take Courage
|
|
#1 Peter 3:14
|