diff --git a/src/plugins/drawreport/AncestorTree.py b/src/plugins/drawreport/AncestorTree.py index bcbc81c03..5743dbdb7 100644 --- a/src/plugins/drawreport/AncestorTree.py +++ b/src/plugins/drawreport/AncestorTree.py @@ -1,9 +1,7 @@ # # 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) 2008-2010 Craig J. Anderson ander882@hotmail.com # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,7 +18,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # -# $Id$ +# $Id: Ancestor2.py """Reports/Graphical Reports/Ancestor Tree""" @@ -30,23 +28,33 @@ # #------------------------------------------------------------------------ import math +def log2(val): + """ + Calculate the log base 2 of a value. + """ + return int(math.log10(val)/math.log10(2)) + from gen.ggettext import sgettext as _ + #------------------------------------------------------------------------ # # GRAMPS modules # #------------------------------------------------------------------------ -from gen.display.name import displayer as name_displayer -from Errors import ReportError -from gen.plug.docgen import (FontStyle, ParagraphStyle, GraphicsStyle, - FONT_SANS_SERIF, PARA_ALIGN_CENTER) -from gen.plug.menu import BooleanOption, NumberOption, TextOption, PersonOption + +#from Errors import ReportError + +from gen.plug.menu import BooleanOption, NumberOption, StringOption, \ + EnumeratedListOption, TextOption, PersonOption + from gen.plug.report import Report from gen.plug.report import utils as ReportUtils from gui.plug.report import MenuReportOptions -from libsubstkeyword import SubstKeywords -pt2cm = ReportUtils.pt2cm +from gen.display.name import displayer as name_displayer + +PT2CM = ReportUtils.pt2cm +#cm2pt = ReportUtils.cm2pt #------------------------------------------------------------------------ # @@ -55,111 +63,443 @@ pt2cm = ReportUtils.pt2cm #------------------------------------------------------------------------ _BORN = _('short for born|b.') _DIED = _('short for died|d.') +_MARR = _('short for married|m.') + +from libtreebase import * #------------------------------------------------------------------------ # -# log2val +# Box classes # #------------------------------------------------------------------------ -def log2(val): - """ - Calculate the log base 2 of a value. - """ - return int(math.log10(val)/math.log10(2)) - -#------------------------------------------------------------------------ -# -# Layout class -# -#------------------------------------------------------------------------ -class GenChart(object): - - def __init__(self, generations): - self.generations = generations - self.size = (2**(generations)) - self.array = {} - self.map = {} - self.compress_map = {} - - self.max_x = 0 - self.ad = (self.size, generations) - - def set(self, index, value): - x = log2(index) - y = index - (2**x) - delta = int((self.size/(2**(x)))) - new_y = int((delta/2) + (y)*delta) - if not new_y in self.array: - self.array[new_y] = {} - self.array[new_y][x] = (value, index) - self.max_x = max(x, self.max_x) - self.map[value] = (new_y, x) - - def index_to_xy(self, index): - if index: - x = log2(index) - ty = index - (2**x) - delta = int(self.size/(2**x)) - y = int(delta/2 + ty*delta) - else: - x = 0 - y = self.size/2 - - if len(self.compress_map) > 0: - return (x, self.compress_map[y]) - else: - return (x, y) +class AncestorBoxBase(BoxBase): + """ The base class for the two boxes used in this report """ - def get(self, index): - (x,y) = self.index_to_xy(index) - return self.get_xy(x, y) + def __init__(self, boxstr): + BoxBase.__init__(self) + self.boxstr = boxstr + + def x_index(self, level): + """ calculate the row that this person is in """ + return log2(level) + + def y_index(self, max_gen): + """ Calculate the column or generation that this person is in. """ + x_level = self.level[0] + #Calculate which row in the column of people. + tmp_y = self.level[1] - (2**x_level) + #Calculate which row in the table (yes table) of people. + delta = int((2**max_gen)/(2**(x_level))) + return int((delta/2) + (tmp_y*delta)) - def get_xy(self, x, y): - value = 0 - if y in self.array: - if x in self.array[y]: - value = self.array[y][x] - return value +class PersonBox(AncestorBoxBase): + """ + Calculates information about the box that will print on a page + """ + def __init__(self, level): + AncestorBoxBase.__init__(self, "AC2-box") + self.level = (self.x_index(level), level) - def set_xy(self, x, y, value): - if not y in self.array: - self.array[y] = {} - self.array[y][x] = value + def calc_text(self, database, person, family): + """ Calculate the text for this box """ + gui = GUIConnect() + calc = gui.calc_lines(database) + self.text = calc.calc_lines(person, family, + gui.working_lines(self.level[1])) - def dimensions(self): - return (max(self.array)+1, self.max_x+1) +class FamilyBox(AncestorBoxBase): + """ + Calculates information about the box that will print on a page + """ + def __init__(self, level): + AncestorBoxBase.__init__(self, "AC2-fam-box") + self.level = (self.x_index(level)+1, level) + + def calc_text(self, database, person, family): + """ Calculate the text for this box """ + gui = GUIConnect() + calc = gui.calc_lines(database) + self.text = calc.calc_lines(person, family, [gui.get_val('dispmarr')]) - def compress(self): - new_map = {} - new_array = {} - old_y = 0 - new_y = 0 - for key, i in self.array.iteritems(): - old_y = key - if self.not_blank(i.itervalues()): - self.compress_map[old_y] = new_y - new_array[new_y] = i - x = 0 - for entry in i: - new_map[entry] = (new_y, x) - x += 1 - new_y += 1 - self.array = new_array - self.map = new_map - self.ad = (new_y, self.ad[1]) + #def x_index(self): + # """ calculate the row that this person is in """ + # return log2(self.level[0]) +1 + + def y_index(self, max_gen): + """ Calculate the column or generation that this person is in. """ + x_level = self.level[0] -1 + #Calculate which row in the column of people. + tmp_y = self.level[1] - (2**x_level) + #Calculate which row in the table (yes table) of people. + delta = int((2**max_gen)/(2**(x_level))) + return int((delta/2) + (tmp_y*delta)) + + +#------------------------------------------------------------------------ +# +# Line class +# +#------------------------------------------------------------------------ +class Line(LineBase): + """ Our line class.""" + def __init__(self, start, level): + LineBase.__init__(self, start) + self.linestr = 'AC2-line' + #self.level = level + + +#------------------------------------------------------------------------ +# +# Titles Class(es) +# +#------------------------------------------------------------------------ +class TitleN(TitleBox): + """No Title class for the report """ + def __init__(self, doc): + TitleBox.__init__(self, doc, "None") + + def calc_title(self, center): + """Calculate the title of the report""" + return + +class TitleA(TitleBox): + """Title class for the report """ + def __init__(self, doc): + TitleBox.__init__(self, doc, "AC2-Title") + + def calc_title(self, center): + """Calculate the title of the report""" + name = "" + if center is not None: + name = name_displayer.display(center) + + self.text = _("Ancestor Graph for %s") % name + self.set_box_height_width() + + +#------------------------------------------------------------------------ +# +# make_ancestor_tree +# +#------------------------------------------------------------------------ +class MakeAncestorTree(): + """ + 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, max_gen, inc_marr, inc_spouses, fill_out): + self.database = dbase + self.canvas = canvas + self.inlc_marr = inc_marr + self.inc_spouses = inc_spouses + self.max_generations = max_gen + self.fill_out = fill_out + + def add_person_box(self, index, indi_handle, fams_handle): + """ Makes a person box and add that person into the Canvas. """ + myself = PersonBox(index) + + #calculate the text. + myself.calc_text(self.database, indi_handle, fams_handle) + + self.canvas.add_box(myself) + + return myself + + def add_marriage_box(self, index, indi_handle, fams_handle): + """ Makes a marriage box and add that person into the Canvas. """ + myself = FamilyBox(index) + + #calculate the text. + myself.calc_text(self.database, indi_handle, fams_handle) + + self.canvas.add_box(myself) + + def add_line(self, person, index, father, marriage, mother): + """ Adds the line connecting the boxes """ + if father is None and mother is None: + return + + line = Line(person, index) + if father is not None: + line.add_to(father) + if marriage is not None: + line.add_to(marriage) + if mother is not None: + line.add_to(mother) + + self.canvas.add_line(line) + + def recurse(self, person_handle, family_handle, index): + """ traverse the ancestors recursively until either the end + of a line is found, or until we reach the maximum number of + generations that the user wants """ + + if log2(index) == self.max_generations: + return None + + person = self.database.get_person_from_handle(person_handle) + if person is None: + return self.__fill(index, None, self.fill_out) #??? +1 ??? + + + parents_handle = person.get_main_parents_family_handle() + father = marrbox = mother = None + if parents_handle is not None: + #note depth first + family = self.database.get_family_from_handle(parents_handle) + father = self.recurse(family.get_father_handle(), parents_handle, + index*2) + mybox = self.add_person_box(index, person_handle, family_handle) + if self.inlc_marr and (log2(index) + 2) <= self.max_generations: + marrbox = self.add_marriage_box(index, + family.get_father_handle(), + parents_handle) + + mother = self.recurse(family.get_mother_handle(), parents_handle, + (index*2)+1) + + self.add_line(mybox, index, father, marrbox, mother) + else: + mybox = self.__fill(index, person_handle, self.fill_out+1) + #father = self.__fill(index *2, self.fill_out) + #mybox = self.add_person_box(index, person_handle, family_handle) + #if self.fill_out and self.inlc_marr and (log2(index) + 2) < + # self.max_generations: + # marrbox = self.add_marriage_box(index*2, None, None) + #mother = self.__fill(index *2+1, self.fill_out) + + return mybox + + def __fill(self, index, person_handle, levels): + """ Fills out the Ancestor tree if desired/needed """ + if log2(index) == self.max_generations: + return None + if levels == 0: + return None + + father = self.__fill(index *2, None, levels-1) + + mybox = self.add_person_box(index, person_handle, None) + + marrbox = None + if self.inlc_marr and levels > 1 and \ + (log2(index) + 2) <= self.max_generations: + marrbox = self.add_marriage_box(index, None, None) + + mother = self.__fill(index *2+1, None, levels-1) + + self.add_line(mybox, index, father, marrbox, mother) + + return mybox + + def start(self, person_id): + """ go ahead and make it happen """ + center = self.database.get_person_from_gramps_id(person_id) + center_h = center.get_handle() + + self.recurse(center_h, None, 1) + + if self.inc_spouses and False: #future version + family_handles = center.get_family_handle_list() + for family_handle in family_handles: + family = self.database.get_family_from_handle(family_handle) + spouse_handle = ReportUtils.find_spouse(center, family) + self.add_person_box(1, spouse_handle, family_handle) + + +#------------------------------------------------------------------------ +# +# Transform Classes +# +#------------------------------------------------------------------------ +#------------------------------------------------------------------------ +# Class rl_Transform +#------------------------------------------------------------------------ +class RLTransform(): + """ + setup all of the boxes on the canvas in for a left/right report + """ + def __init__(self, canvas, opts, max_generations, compress_tree): + self.canvas = canvas + self.rept_opts = opts + self.max_generations = max_generations + self.compress_tree = compress_tree + self.y_offset = self.rept_opts.littleoffset*2 + self.canvas.title.height + self.__last_y_level = 0 + self.__y_level = 0 + + def __next_y(self, box): + """ boxes are already in top down (.y_cm) form so if we + set the box in the correct y level depending on compress_tree + """ + y_index = box.y_index(self.max_generations+1) -1 + + if self.compress_tree: + current_y = self.__y_level + if y_index > self.__last_y_level: + self.__last_y_level = y_index + self.__y_level += 1 + current_y = self.__y_level + return current_y + else: + return y_index + + 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[0] * \ + (self.rept_opts.col_width + self.rept_opts.max_box_width)) + #2. cm_y + box.y_cm = self.__next_y(box) * self.rept_opts.max_box_height + 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].y_index(self.max_generations+1) -1 + #go + for box in self.canvas.boxes: + self._place(box) + + +#------------------------------------------------------------------------ +# +# class make_report +# +#------------------------------------------------------------------------ +class MakeReport(): + + def __init__(self, dbase, doc, canvas, \ + font_normal, inlc_marr, compress_tree): + + self.database = dbase + self.doc = doc + self.canvas = canvas + self.font_normal = font_normal + self.inlc_marr = inlc_marr + self.compress_tree = 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.doc.report_opts.max_box_width: + self.doc.report_opts.max_box_width = box.width + box.shadow + + if box.level[1] > 0: + if box.level[1] % 2 == 0 and box.height > self.father_ht: + self.father_ht = box.height + elif box.level[1] % 2 == 1 and box.height > self.mother_ht: + self.mother_ht = box.height + + tmp = log2(box.level[1]) + if tmp > self.max_generations: + self.max_generations = tmp + + def get_generations(self): + return self.max_generations + + def start(self): + # 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.doc.report_opts.max_box_height = \ + min(self.father_ht, self.mother_ht) + else: + self.doc.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.doc.report_opts.max_box_width + + # 2. + #setup the transform class to move around the boxes on the canvas + transform = RLTransform(self.canvas, self.doc.report_opts, + self.max_generations, self.compress_tree) + 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): + """ Set only once as we are BORG. """ + self.__opts = options + + def get_val(self, val): + """ Get a GUI value. """ + return self.__opts.get_option_by_name(val).get_value() + + 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 == 0: + return TitleN(doc) + else: + return TitleA(doc) + + #2 helper functions to calculate the box.text + def calc_lines(self, database): + #calculate the printed lines for each box + display_repl = [] #Not used in this report + #str = "" + #if self.get_val('miss_val'): + # str = "_____" + return CalcLines(database, display_repl) + + def working_lines(self, index): + disp_father = self.get_val('dispf') + if index == 1: #The center person always uses main + return disp_father + disp_mother = self.get_val('dispf') + if self.get_val('dif_sec') == 1: + disp_father = self.get_val('disp_sec') + elif self.get_val('dif_sec') == 2: + disp_mother = self.get_val('disp_sec') + + if index % 2 == 0: + return disp_father + else: + return disp_mother - def not_blank(self, line): - for i in line: - if i and isinstance(i, tuple): - return 1 - return 0 #------------------------------------------------------------------------ # # AncestorTree # #------------------------------------------------------------------------ -class AncestorTree(Report): +class AncestorTree2(Report): def __init__(self, database, options_class): """ @@ -174,284 +514,172 @@ class AncestorTree(Report): This report needs the following parameters (class variables) that come in the options class. - gen - Maximum number of generations to include. - pagebbg - Whether to include page breaks between generations. - dispf - Display format for the output box. - singlep - Whether to scale to fit on a single page. - indblank - Whether to include blank pages. - compress - Whether to compress chart. + 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. """ Report.__init__(self, database, options_class) - - menu = options_class.menu - self.display = menu.get_option_by_name('dispf').get_value() - self.max_generations = menu.get_option_by_name('maxgen').get_value() - self.force_fit = menu.get_option_by_name('singlep').get_value() - self.incblank = menu.get_option_by_name('incblank').get_value() - self.compress = menu.get_option_by_name('compress').get_value() - - pid = menu.get_option_by_name('pid').get_value() - center_person = database.get_person_from_gramps_id(pid) - if (center_person == None) : - raise ReportError(_("Person %s is not in the Database") % pid ) - - name = name_displayer.display_formal(center_person) - self.title = _("Ancestor Graph for %s") % name - self.map = {} - self.text = {} + self.connect = GUIConnect() + self.connect.set__opts(options_class.menu) - self.box_width = 0 - self.box_height = 0 - self.lines = 0 - self.scale = 1 - - self.apply_filter(center_person.get_handle(), 1) - keys = sorted(self.map) - max_key = log2(keys[-1]) - - self.genchart = GenChart(max_key+1) - for key, chart in self.map.iteritems(): - self.genchart.set(key, chart) - self.calc() - - if self.force_fit: - self.scale_styles() - - def apply_filter(self, person_handle, index): - """traverse the ancestors recursively until either the end - of a line is found, or until we reach the maximum number of - generations that we want to deal with""" - - if (not person_handle) or (index >= 2**self.max_generations): - return - self.map[index] = person_handle - - self.text[index] = [] - style_sheet = self.doc.get_style_sheet() - pstyle = style_sheet.get_paragraph_style("AC2-Normal") - font = pstyle.get_font() + font_normal = style_sheet.get_paragraph_style("AC2-Normal").get_font() + self.doc.report_opts = ReportOptions(self.doc, font_normal) + + self.canvas = Canvas(self.doc) - em = self.doc.string_width(font,"m") + #make the tree into self.canvas + inlc_marr = self.connect.get_val('incmarr') + self.max_generations = self.connect.get_val('maxgen') + show_spouse = 0 + fillout = self.connect.get_val('fillout') + tree = MakeAncestorTree(database, self.canvas, self.max_generations, + inlc_marr, (show_spouse > 0), fillout) + tree.start(self.connect.get_val('pid')) + tree = None - subst = SubstKeywords(self.database, person_handle) - self.text[index] = subst.replace_and_clean(self.display) + #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) - for line in self.text[index]: - this_box_width = self.doc.string_width(font,line) + (2 * em) - self.box_width = max(self.box_width, this_box_width) + #make the report as big as it wants to be. + compress = self.connect.get_val('compress') + report = MakeReport(database, self.doc, self.canvas, font_normal, + inlc_marr, compress) + report.start() + self.max_generations = report.get_generations() #already know + report = None - self.lines = max(self.lines, len(self.text[index])) + def begin_report(self): + """ + We have + 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! + """ - person = self.database.get_person_from_handle(person_handle) - family_handle = person.get_main_parents_family_handle() - if family_handle: - family = self.database.get_family_from_handle(family_handle) - self.apply_filter(family.get_father_handle(), index*2) - self.apply_filter(family.get_mother_handle(), (index*2)+1) + if self.connect.get_val('use_note'): + note_box = NoteBox(self.doc, "AC2-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): - - (maxy, maxx) = self.genchart.dimensions() - maxh = int(self.uh/self.box_height) - if self.force_fit: - self.print_page(0, maxx, 0, maxy, 0, 0) - else: - starty = 0 - coly = 0 - while starty < maxy: - startx = 0 - colx = 0 - while startx < maxx: - stopx = min(maxx, startx + self.generations_per_page) - stopy = min(maxy, starty + maxh) - self.print_page(startx, stopx, starty, stopy, colx, coly) - colx += 1 - startx += self.generations_per_page - coly += 1 - starty += maxh - - def calc(self): - """ - calc - calculate the maximum width that a box needs to be. From - that and the page dimensions, calculate the proper place to put - the elements on a page. - """ - style_sheet = self.doc.get_style_sheet() + one_page = self.connect.get_val('onepage') + #scale_report = self.connect.get_val('scale_report') - self.add_lines() - if self.compress: - self.genchart.compress() + #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') - self.box_pad_pts = 10 - if self.title and self.force_fit: - pstyle = style_sheet.get_paragraph_style("AC2-Title") - tfont = pstyle.get_font() - self.offset = pt2cm(1.25 * tfont.get_size()) - - gstyle = style_sheet.get_draw_style("AC2-box") - shadow_height = gstyle.get_shadow_space() - else: - # Make space for the page number labels at the bottom. - p = style_sheet.get_paragraph_style("AC2-Normal") - font = p.get_font() - lheight = pt2cm(1.2*font.get_size()) - lwidth = pt2cm(1.1*self.doc.string_width(font, "(00,00)")) - self.page_label_x_offset = self.doc.get_usable_width() - lwidth - self.page_label_y_offset = self.doc.get_usable_height() - lheight + ##################### + #Setup page infomation - self.offset = pt2cm(1.25 * font.get_size()) - shadow_height = 0 - self.uh = self.doc.get_usable_height() - self.offset - shadow_height - uw = self.doc.get_usable_width() - pt2cm(self.box_pad_pts) + colsperpage = self.doc.get_usable_width() + colsperpage += self.doc.report_opts.col_width + colsperpage = int(colsperpage / (self.doc.report_opts.max_box_width + + self.doc.report_opts.col_width)) + if colsperpage == 0: #Is the page really that small? + colsperpage = 1 - calc_width = pt2cm(self.box_width + self.box_pad_pts) + 0.2 - self.box_width = pt2cm(self.box_width) - pstyle = style_sheet.get_paragraph_style("AC2-Normal") - font = pstyle.get_font() - self.box_height = self.lines*pt2cm(1.25*font.get_size()) - if self.force_fit: - (maxy, maxx) = self.genchart.dimensions() - - bw = calc_width / (uw/maxx) - bh = self.box_height / (self.uh/maxy) - - self.scale = max(bw ,bh) - self.box_width /= self.scale - self.box_height /= self.scale - self.box_pad_pts /= self.scale - - maxh = int(self.uh / self.box_height) - maxw = int(uw / calc_width) - - if log2(maxh) < maxw: - self.generations_per_page = int(log2(maxh)) - else: - self.generations_per_page = maxw - - # build array of x indices - - self.delta = pt2cm(self.box_pad_pts) + self.box_width + 0.2 - if not self.force_fit: - calc_width = self.box_width + 0.2 + pt2cm(self.box_pad_pts) - remain = self.doc.get_usable_width() - \ - ((self.generations_per_page)*calc_width) - self.delta += remain / (self.generations_per_page) - - def scale_styles(self): - """ - Scale the styles for this report. This must be done in the constructor. - """ - style_sheet = self.doc.get_style_sheet() + ##################### + #Vars + if prnnum: + page_num_box = PageNumberBox(self.doc, 'AC2-box') - g = style_sheet.get_draw_style("AC2-box") - g.set_shadow(g.get_shadow(), g.get_shadow_space() / self.scale) - g.set_line_width(g.get_line_width() / self.scale) - style_sheet.add_draw_style("AC2-box", g) - - p = style_sheet.get_paragraph_style("AC2-Normal") - font = p.get_font() - font.set_size(font.get_size() / self.scale) - p.set_font(font) - style_sheet.add_paragraph_style("AC2-Normal", p) - - self.doc.set_style_sheet(style_sheet) - - def print_page(self, startx, stopx, starty, stopy, colx, coly): + ##################### + #ok, everyone is now ready to print on the canvas. Paginate? + self.canvas.paginate(colsperpage, one_page) - if not self.incblank: - blank = True - for y in range(starty, stopy): - for x in range(startx, stopx): - if self.genchart.get_xy(x, y) != 0: - blank = False - break - if not blank: - break - if blank: - return + ##################### + #Yeah!!! + #lets finally make some pages!!! + ##################### + for page in self.canvas.page_iter_gen(incblank): - self.doc.start_page() - if self.title and self.force_fit: - self.doc.center_text('AC2-title', self.title, - self.doc.get_usable_width() / 2, 0) - phys_y = 0 - for y in range(starty, stopy): - phys_x = 0 - for x in range(startx, stopx): - value = self.genchart.get_xy(x, y) - if value: - if isinstance(value, tuple): - (person, index) = value - text = '\n'.join(self.text[index]) - self.doc.draw_box("AC2-box", - text, - phys_x*self.delta, - phys_y*self.box_height+self.offset, - self.box_width, - self.box_height ) - elif value == 2: - self.doc.draw_line("AC2-line", - phys_x * self.delta+self.box_width * 0.5, - phys_y * self.box_height + self.offset, - phys_x * self.delta+self.box_width * 0.5, - (phys_y + 1) * self.box_height + self.offset) - elif value == 1: - x1 = phys_x * self.delta + self.box_width * 0.5 - x2 = (phys_x + 1) * self.delta - y1 = phys_y * self.box_height + self.offset + self.box_height / 2 - y2 = (phys_y + 1) * self.box_height + self.offset - self.doc.draw_line("AC2-line", x1, y1, x1, y2) - self.doc.draw_line("AC2-line", x1, y1, x2, y1) - elif value == 3: - x1 = phys_x * self.delta + self.box_width * 0.5 - x2 = (phys_x + 1) * self.delta - y1 = (phys_y) * self.box_height + self.offset + self.box_height / 2 - y2 = (phys_y) * self.box_height + self.offset - self.doc.draw_line("AC2-line", x1, y1, x1, y2) - self.doc.draw_line("AC2-line", x1, y1, x2, y1) - - phys_x +=1 - phys_y += 1 + 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() - if not self.force_fit: - self.doc.draw_text('AC2-box', - '(%d,%d)' % (colx + 1, coly + 1), - self.page_label_x_offset, - self.page_label_y_offset) - self.doc.end_page() + self.doc.end_page() - def add_lines(self): - - (my , mx) = self.genchart.dimensions() + def scale_styles(self, scale): + """ + Scale the styles for this report. + """ + style_sheet = self.doc.get_style_sheet() - for y in range(0, my): - for x in range(0, mx): - value = self.genchart.get_xy(x, y) - if not value: - continue - if isinstance(value, tuple): - (person, index) = value - if self.genchart.get(index * 2): - (px, py) = self.genchart.index_to_xy(index * 2) - self.genchart.set_xy(x, py, 1) - for ty in range(py + 1, y): - self.genchart.set_xy(x, ty, 2) - if self.genchart.get(index * 2 + 1): - (px, py) = self.genchart.index_to_xy(index * 2 + 1) - self.genchart.set_xy(px - 1, py, 3) - for ty in range(y + 1, py): - self.genchart.set_xy(x, ty, 2) + from gen.plug.docgen import GraphicsStyle + + graph_style = style_sheet.get_draw_style("AC2-box") + graph_style.set_shadow(graph_style.get_shadow(), + graph_style.get_shadow_space() * 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(), + graph_style.get_shadow_space() * scale) + graph_style.set_line_width(graph_style.get_line_width() * scale) + style_sheet.add_draw_style("AC2-fam-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-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): +class AncestorTree2Options(MenuReportOptions): """ Defines options and provides handling interface. @@ -468,61 +696,203 @@ class AncestorTreeOptions(MenuReportOptions): pid.set_help(_("The center person for the tree")) menu.add_option(category_name, "pid", pid) - max_gen = NumberOption(_("Generations"), 10, 1, 50) - max_gen.set_help(_("The number of generations to include in the tree")) - menu.add_option(category_name, "maxgen", max_gen) + 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) - disp = TextOption(_("Display Format"), + self.fillout = EnumeratedListOption(_("Display unknown\ngenerations"), + 0) + menu.add_option(category_name, "fillout", self.fillout) + + self.max_gen.connect('value-changed', self.__fillout_vals) + self.__fillout_vals() + + compress = BooleanOption(_('Co_mpress tree'), True) + compress.set_help(_("Whether to compress the tree.")) + menu.add_option(category_name, "compress", 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 the Main Display Format")) + #Spouse_disp.add_item( 2, _("Yes, and use the 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 = _("Display") + + disp = TextOption(_("Main\nDisplay Format"), ["$n","%s $b" % _BORN,"%s $d" %_DIED] ) disp.set_help(_("Display format for the outputbox.")) menu.add_option(category_name, "dispf", disp) - scale = BooleanOption(_('Sc_ale to fit on a single page'), True) - scale.set_help(_("Whether to scale to fit on a single page.")) - menu.add_option(category_name, "singlep", scale) + difMom = EnumeratedListOption(_("Use Main/Secondary\nDisplay " + + "Format for"), 0) + difMom.add_item( 0, _("Everyone uses the Main Display format")) + difMom.add_item( 1, _("Mothers use Main, and Fathers use the " + + "Secondary")) + difMom.add_item( 2, _("Fathers use Main, and Mothers use the " + + "Secondary")) + difMom.set_help(_("Which Display format to use for Fathers and " + + "Mothers")) + menu.add_option(category_name, "dif_sec", difMom) + + #Will add when libsubstkeyword supports it. + #missing = EnumeratedListOption(_("Replace missing\npalces\\dates \ + # with"), 0) + #missing.add_item( 0, _("Does not display anything")) + #missing.add_item( 1, _("Displays '_____'")) + #missing.set_help(_("What will print when information is not known")) + #menu.add_option(category_name, "miss_val", missing) + + category_name = _("Secondary") + + dispMom = TextOption(_("Secondary\nDisplay Format"), + ["$n","%s $b" % _BORN,"%s $m" %_MARR,"%s $d" \ + %_DIED] ) + dispMom.set_help(_("Display format for the outputbox.")) + menu.add_option(category_name, "disp_sec", dispMom) + + incmarr = BooleanOption(_('Include Marriage information'), False) + incmarr.set_help(_("Whether to include marriage information in the " + + "report.")) + menu.add_option(category_name, "incmarr", incmarr) + + marrdisp = StringOption(_("Marraige\nDisplay Format"), "%s $m" % _MARR) + marrdisp.set_help(_("Display format for the outputbox.")) + menu.add_option(category_name, "dispmarr", marrdisp) + + category_name = _("Print") + + self.scale = EnumeratedListOption(_("Scale report to fit"), 0) + self.scale.add_item( 0, _("Do not scale report")) + self.scale.add_item( 1, _("Scale report to fit page width only")) + self.scale.add_item( 2, _("Scale report to fit the size of the page")) + self.scale.set_help(_("Wheather to scale the report to fit a " + + "specific size")) + menu.add_option(category_name, "scale_report", self.scale) + self.scale.connect('value-changed', self.__check_blank) + + self.__onepage = BooleanOption(_('One page report'), True) + self.__onepage.set_help(_("Whether to scale the size of the page to" + + "the size of the report.")) + menu.add_option(category_name, "onepage", self.__onepage) + self.__onepage.connect('value-changed', self.__check_blank) + + self.title = EnumeratedListOption(_("Report Title"), 0) + self.title.add_item( 0, "Do not print a Title") + self.title.add_item( 1, "Include Report Title") + menu.add_option(category_name, "report_title", self.title) + + 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) - blank = BooleanOption(_('Include Blank Pages'), True) - blank.set_help(_("Whether to include pages that are blank.")) - menu.add_option(category_name, "incblank", blank) + self.__check_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) - compress = BooleanOption(_('Co_mpress tree'), True) - compress.set_help(_("Whether to compress the tree.")) - menu.add_option(category_name, "compress", compress) + self.notedisp = TextOption(_("Note to add\nto the graph\n\n$T " + + "inserts todays date"), []) + self.notedisp.set_help(_("Add a personal note")) + menu.add_option(category_name, "note_disp", self.notedisp) + + locals = NoteType(0, 1) + 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): + off = not self.__onepage.get_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") ]) + for itr in range(2, max_gen): + item_list.append([itr, str(itr) + _(" Generations of empty boxes " + + "for unknown ancestors") ]) + + 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.""" + from gen.plug.docgen import (FontStyle, ParagraphStyle, GraphicsStyle, + FONT_SANS_SERIF, PARA_ALIGN_CENTER) + ## Paragraph Styles: - f = FontStyle() - f.set_size(9) - f.set_type_face(FONT_SANS_SERIF) - p = ParagraphStyle() - p.set_font(f) - p.set_description(_('The basic style used for the text display.')) - default_style.add_paragraph_style("AC2-Normal", p) + 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) + + 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("AC2-Title", para_style) - f = FontStyle() - f.set_size(16) - f.set_type_face(FONT_SANS_SERIF) - p = ParagraphStyle() - p.set_font(f) - p.set_alignment(PARA_ALIGN_CENTER) - p.set_description(_('The basic style used for the title display.')) - default_style.add_paragraph_style("AC2-Title", p) - ## Draw styles - g = GraphicsStyle() - g.set_paragraph_style("AC2-Normal") - g.set_shadow(1, 0.2) - g.set_fill_color((255, 255, 255)) - default_style.add_draw_style("AC2-box", g) + graph_style = GraphicsStyle() + graph_style.set_paragraph_style("AC2-Normal") + graph_style.set_shadow(1, PT2CM(9)) #shadow set by text size + graph_style.set_fill_color((255, 255, 255)) + default_style.add_draw_style("AC2-box", graph_style) - g = GraphicsStyle() - g.set_paragraph_style("AC2-Title") - g.set_color((0, 0, 0)) - g.set_fill_color((255, 255, 255)) - g.set_line_width(0) - default_style.add_draw_style("AC2-title", g) + 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) - g = GraphicsStyle() - default_style.add_draw_style("AC2-line", g) + 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) + default_style.add_draw_style("AC2-Title", 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 diff --git a/src/plugins/drawreport/DescendTree.py b/src/plugins/drawreport/DescendTree.py index 39cf6a6ab..7afa8f0f2 100644 --- a/src/plugins/drawreport/DescendTree.py +++ b/src/plugins/drawreport/DescendTree.py @@ -1,10 +1,7 @@ # # Gramps - a GTK+/GNOME based genealogy program # -# Copyright (C) 2000-2007 Donald N. Allingham -# Copyright (C) 2007-2008 Brian G. Matherly -# Copyright (C) 2009 Craig J. Anderson -# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2008-2010 Craig J. Anderson ander882@hotmail.com # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,25 +18,35 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # -# $Id$ +# $Id: Descend2.py -"""Reports/Graphical Reports/Descendant Tree""" +""" +Reports/Graphical Reports/Familial Tree +Reports/Graphical Reports/Personal Tree +""" #------------------------------------------------------------------------ # # GRAMPS modules # -#------------------------------------------------------------------------ -from gen.display.name import displayer as name_displayer +#------------------------------------------------------------------------ +try: + from TransUtils import get_addon_translator + _ = get_addon_translator(__file__).gettext +except: + import gettext + _ = gettext.gettext + from Errors import ReportError -from gen.plug.docgen import (GraphicsStyle, FontStyle, ParagraphStyle, - FONT_SANS_SERIF, PARA_ALIGN_CENTER) -from gen.plug.menu import TextOption, NumberOption, BooleanOption, PersonOption + +from gen.plug.menu import TextOption, NumberOption, EnumeratedListOption, \ + StringOption, BooleanOption, PersonOption, FamilyOption + from gen.plug.report import Report from gen.plug.report import utils as ReportUtils from gui.plug.report import MenuReportOptions -from libsubstkeyword import SubstKeywords -from gen.ggettext import sgettext as _ + +PT2CM = ReportUtils.pt2cm #------------------------------------------------------------------------ # @@ -47,442 +54,1421 @@ from gen.ggettext import sgettext as _ # #------------------------------------------------------------------------ _BORN = _('short for born|b.') -_MARR = _('short for married|m.') _DIED = _('short for died|d.') +_MARR = _('short for married|m.') -_LINE_HORIZONTAL = 1 -_LINE_VERTICAL = 2 -_LINE_ANGLE = 3 +_RPT_NAME = 'descend_chart' -_PERSON_DIRECT = 1 -_PERSON_SPOUSE = 2 +from libtreebase import * #------------------------------------------------------------------------ # -# Layout class +# Box classes # #------------------------------------------------------------------------ -class GenChart(object): +class DescendantBoxBase(BoxBase): + """ Base for all descendant boxes. + Set the boxstr and some new attributes that are needed """ + def __init__(self, boxstr): + BoxBase.__init__(self) + self.boxstr = boxstr + self.next = None + self.prev = None + self.line_to = None + self.father = None - def __init__(self, generations): - self.generations = generations - self.map = {} + def calc_text(self, database, person, family): + """ A sinble place to calculate box text """ + gui = GuiConnect() + calc = gui.calc_lines(database) + self.text = calc.calc_lines(person, family, + gui.working_lines(self)) + +class PersonBox(DescendantBoxBase): + """ + Calculates information about the box that will print on a page + """ + def __init__(self, level, boldable = 0): + DescendantBoxBase.__init__(self, "CG2-box") + self.level = level + self.shadow = PT2CM(9) * .6 + + def set_bold(self): + """ update me to a bolded box """ + self.boxstr = "CG2b-box" + +class FamilyBox(DescendantBoxBase): + """ + Calculates information about the box that will print on a page + """ + def __init__(self, level): + DescendantBoxBase.__init__(self, "CG2-fam-box") + self.level = level + +class PlaceHolderBox(BoxBase): + """ I am a box that does not print. I am used to make sure information + does not run over areas that we don't wnat information (boxes) """ + def __init__(self, level): + BoxBase.__init__(self) + self.boxstr = "None" + self.level = level + + def calc_text(self, database, person, family): + """ move along. Nothing to see here """ + return + + +#------------------------------------------------------------------------ +# +# Line class +# +#------------------------------------------------------------------------ +class Line(LineBase): + """ Our line class.""" + def __init__(self, start): + LineBase.__init__(self, start) + self.linestr = "CG2-line" + + +#------------------------------------------------------------------------ +# +# Titles Class(es) +# +#------------------------------------------------------------------------ +class DescendantTitleBase(TitleBox): + def __init__(self, dbase, doc, boxstr = "CG2-Title"): + TitleBox.__init__(self, doc, boxstr) + self.database = dbase + + def descendant_print(self, person_list, person_list2 = []): + names = self._get_names(person_list) + if person_list2 != []: + names2 = self._get_names(person_list2) - self.array = {} - self.sparray = {} - self.max_x = 0 - self.max_y = 0 + if person_list2 == []: + if len(names) == 1: + title = _("Descendant Chart for %(person)s") % \ + {'person': names[0] } + elif len(names) == 2: + title = _("Descendant Chart for %(father)s and %(mother)s") % \ + {'father': names[0], 'mother': names[1] } + else: + if len(person_list + person_list2) == 3: + if len(person_list) == 1: + title = _("Descendant Chart for %(person)s and" + \ + " %(father1)s, %(mother1)s") % \ + {'person': names[0], \ + 'father1': names2[0], \ + 'mother1': names2[1], + } + else: + title = _("Descendant Chart for %(person)s, %(father1)s " + \ + " and %(mother1)s") % \ + {'father1': names[0], \ + 'mother1': names[1], \ + 'person': names2[0] } + else: #if len(person_list + person_list2) == 4: + title = _("Descendant Chart for %(father1)s, %(father2)s " + \ + "and %(mother1)s, %(mother2)s") % \ + {'father1': names[0], \ + 'mother1': names[1], \ + 'father2': names2[0], \ + 'mother2': names2[1] } + return title + + def get_parents(self, family_id): + family1 = self.database.get_family_from_gramps_id(family_id) + father_h = family1.get_father_handle() + mother_h = family1.get_mother_handle() - def get_xy(self, x, y): - if y not in self.array: - return 0 - return self.array[y].get(x, 0) + parents = [] + + if father_h: + parents.append(self.database.get_person_from_handle(father_h)) + if mother_h: + parents.append(self.database.get_person_from_handle(mother_h)) + + return parents - def set_xy(self, x, y, value): - self.max_x = max(self.max_x, x) - self.max_y = max(self.max_y, y) +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 - if y not in self.array: - self.array[y] = {} - self.array[y][x] = value +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""" - def get_sp(self, col_x, row_y): - """gets whether person at x,y - is a direct descendent or a spouse""" - if (col_x, row_y) not in self.sparray: + center = self.database.get_person_from_gramps_id(person_id) + family2_h = center.get_main_parents_family_handle() + family2 = self.database.get_family_from_handle(family2_h) + + person_list = [] + mother2_h = father2_h = None + if family2: + father2_h = family2.get_father_handle() + if father2_h: + person_list.append( + self.database.get_person_from_handle(father2_h)) + mother2_h = family2.get_mother_handle() + if mother2_h: + person_list.append( + self.database.get_person_from_handle(mother2_h)) + + if person_list == []: + person_list.append(center) + + #else: + # title = str(tmp) + " " + str(len(tmp)) + self.text = self.descendant_print(person_list) + self.set_box_height_width() + +class TitleDPN(DescendantTitleBase): + """Descendant (Person no start with parents) Chart + Title class for the report """ + def __init__(self, dbase, doc): + DescendantTitleBase.__init__(self, dbase, doc) + + def calc_title(self, person_id): + """Calculate the title of the report""" + + center = self.database.get_person_from_gramps_id(person_id) + + title = self.descendant_print([center]) + self.text = title + self.set_box_height_width() + +class TitleDFY(DescendantTitleBase): + """Descendant (Family yes start with parents) Chart + Title class for the report """ + def __init__(self, dbase, doc): + DescendantTitleBase.__init__(self, dbase, doc) + + def get_parent_list(self, person): + """ return a list of my parents. If none, return me """ + if person is None: return None - return self.sparray[col_x, row_y] + parent_list = [] + family_h = person.get_main_parents_family_handle() + family = self.database.get_family_from_handle(family_h) + if family is not None: #family = fathers parents + father_h = family.get_father_handle() + if father_h is not None: + parent_list.append( + self.database.get_person_from_handle(father_h)) + mother_h = family.get_mother_handle() + if mother_h is not None: + parent_list.append( + self.database.get_person_from_handle(mother_h)) + else: + return [person] + + if parent_list == []: + return [person] + else: + return parent_list + + def calc_title(self, family_id): + """Calculate the title of the report""" + + my_parents = self.get_parents(family_id) + + dad_parents = self.get_parent_list(my_parents[0]) + + mom_parents = [] + if len(my_parents) > 1: + if dad_parents is None: + dad_parents = self.get_parent_list(my_parents[1]) + else: + mom_parents = self.get_parent_list(my_parents[1]) + + self.text = self.descendant_print(dad_parents, mom_parents) + self.set_box_height_width() - def set_sp(self, col_x, row_y, value): - """sets whether person at x,y - is a direct descendent or a spouse""" - self.sparray[col_x, row_y] = value +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""" - def dimensions(self): + self.text = self.descendant_print( + self.get_parents(family_id) + ) + self.set_box_height_width() + +class TitleF(DescendantTitleBase): + """Family Chart Title class for the report """ + def __init__(self, dbase, doc): + DescendantTitleBase.__init__(self, dbase, doc) + + def calc_title(self, family_id): + """Calculate the title of the report""" + parents = self.get_parents(family_id) + + names = self._get_names(parents) + + if len(parents) == 1: + title = _("Family Chart for %(person)s") % {'person': names[0] } + elif len(parents) == 2: + title = _("Family Chart for %(father1)s and %(mother1)s") % \ + {'father1': names[0], 'mother1': names[1] } + #else: + # title = str(tmp) + " " + str(len(tmp)) + self.text = title + self.set_box_height_width() + +class TitleC(DescendantTitleBase): + """Cousin Chart Title class for the report """ + def __init__(self, dbase, doc): + DescendantTitleBase.__init__(self, dbase, doc) + + def calc_title(self, family_id): + """Calculate the title of the report""" + + family = self.database.get_family_from_gramps_id(family_id) + + kids = [] + for kid in family.get_child_ref_list(): + kids.append(self.database.get_person_from_handle(kid.ref)) + + #ok we have the children. Make a title off of them + tmp = self._get_names(kids) + + title = "" + while tmp != []: + if title != "": + title += ", " + title += tmp.pop(0) + + title = "Cousin Chart for " + title + + self.text = title + self.set_box_height_width() + + +#------------------------------------------------------------------------ +# +# Class RecurseDown +# +#------------------------------------------------------------------------ +class RecurseDown: + """ + The main recursive functions that will use add_person to make + the tree of people (Descendants) to be included within the report. + """ + def __init__(self, dbase, canvas): + self.database = dbase + self.canvas = canvas + + self.famalies_seen = [] + self.cols = [] + + gui = GuiConnect() + self.do_gparents = gui.get_val('show_gparents') + self.max_generations = gui.get_val('maxgen') + self.max_spouses = gui.get_val('maxspouse') + self.inlc_marr = gui.get_val('incmarr') + if self.max_spouses == 0: + self.inlc_marr = False + + #is the option even available? + self.bold_direct = gui.get_val('bolddirect') + #can we bold direct descendants? + #bold_now will have only three values + #0 - no bolding + #1 - Only bold the first person + #2 - Bold all direct descendants + self.bold_now = 0 + gui = None + + def add_to_col(self, box): """ - Returns the dimensions of the chart. + Add the box to a column on the canvas. we will do these things: + set the .next .prev attribs for the boxs in this col + get the height and width of this box and set it no the column + also we set the .x_cm to any s_level (indentation) here + we will calculate the real .x_cm later (with indentation) """ - return (self.max_y+1, self.max_x+1) + + level = box.level[0] + #make the column list of people + while len(self.cols) <= level: + self.cols.append(None) + + if self.cols[level] is not None: + self.cols[level].next = box + box.prev = self.cols[level] + + self.cols[level] = box + + box.x_cm = self.canvas.doc.report_opts.spouse_offset * box.level[1] + + self.canvas.set_box_height_width(box) + + tmp = box.prev + if tmp is not None: + #set my y_cm. + box.y_cm = tmp.y_cm + tmp.height + tmp.shadow + + + def add_person_box(self, level, indi_handle, fams_handle, father): + """ Makes a person box and add that person into the Canvas. """ + myself = PersonBox(level) + + if myself.level[1] == 0 and self.bold_direct and self.bold_now != 0: + if self.bold_now == 1: + self.bold_now = 0 + myself.set_bold() + + if level[1] == 0 and father is not None and \ + myself.level[0] != father.level[0]: + #I am a child + if father.line_to is None: + line = Line(father) + father.line_to = line + self.canvas.add_line(father.line_to) + else: + line = father.line_to + + father.line_to.end.append(myself) + myself.father = father + + #calculate the text. + myself.calc_text(self.database, indi_handle, fams_handle) + + self.add_to_col(myself) + if myself.y_cm != 0.0: + myself.y_cm += self.canvas.doc.report_opts.box_pgap + + #check to see if the direct decendant above me is a sibling or other + if myself.level[1] == 0: + sibling = True + this_box = myself.prev + while this_box is not None: + if this_box.level[1] == 0: + if this_box.father != myself.father: + sibling = False + break + this_box = this_box.prev + if sibling == False: + myself.y_cm += self.canvas.doc.report_opts.box_pgap + + self.canvas.add_box(myself) + + return myself + + def add_marriage_box(self, level, indi_handle, fams_handle, father): + """ Makes a marriage box and add that person into the Canvas. """ + myself = FamilyBox(level) + + #if father is not None: + # myself.father = father + #calculate the text. + myself.calc_text(self.database, indi_handle, fams_handle) + + self.add_to_col(myself) + if myself.y_cm != 0.0: + myself.y_cm += self.canvas.doc.report_opts.box_mgap + + self.canvas.add_box(myself) + + return myself + + def recurse(self, person_handle, x_level, s_level, father): + """traverse the ancestors recursively until + either the end of a line is found, + or until we reach the maximum number of generations + or we reach the max number of spouses + that we want to deal with""" + + if x_level > self.max_generations: return + if s_level > 0 and s_level == self.max_spouses: return + if person_handle == None: return + if person_handle in self.famalies_seen: return + + myself = None + person = self.database.get_person_from_handle(person_handle) + family_handles = person.get_family_handle_list() + if s_level == 0: + if family_handles == []: + val = None + else: + val = family_handles[0] + myself = self.add_person_box( (x_level, s_level), + person_handle, val, father) + + marr = None + spouse = None + first = 1 + + for family_handle in family_handles: + if family_handle not in self.famalies_seen: + self.famalies_seen.append(family_handle) + + family = self.database.get_family_from_handle(family_handle) + + if self.inlc_marr and self.max_spouses > 0: + if s_level == 0: + marr = self.add_marriage_box((x_level, s_level+1), + person_handle, family_handle, + myself) + else: + marr = self.add_marriage_box((x_level, s_level+1), + person_handle, family_handle, + father) + + spouse_handle = ReportUtils.find_spouse(person, family) + if self.max_spouses > s_level and \ + spouse_handle not in self.famalies_seen: + if s_level > 0: + spouse = self.add_person_box((x_level, s_level+1), + spouse_handle, family_handle, + father) + elif first == 1: + spouse = self.add_person_box((x_level, s_level+1), + spouse_handle, family_handle, + myself) + elif self.inlc_marr: + spouse = self.add_person_box((x_level, s_level+1), + spouse_handle, family_handle, + marr) + else: + spouse = self.add_person_box((x_level, s_level+1), + spouse_handle, family_handle, + myself) + + mykids = [] + for kid in family.get_child_ref_list(): + mykids.append(kid.ref) + + for child_ref in mykids: + if self.inlc_marr and self.max_spouses > 0: + self.recurse(child_ref, x_level+1, 0, marr) + elif spouse is not None: + self.recurse(child_ref, x_level+1, 0, spouse) + else: + self.recurse(child_ref, x_level+1, 0, myself) + first = 0 + + if self.max_spouses > s_level and spouse_handle != None and \ + self.famalies_seen.count(spouse_handle) == 0: + #spouse_handle = ReportUtils.find_spouse(person,family) + self.recurse(spouse_handle, x_level, s_level+1, spouse) + + def add_family(self, level, family, father2): + """ + Adds a family into the canvas. + only will be used for my direct grandparents, and my parents only. + """ + family_h = family.get_handle() + father_h = family.get_father_handle() + mother_h = family.get_mother_handle() + + self.bold_now = 2 + if father_h == None: + father_b = self.add_person_box( (level, 0), None, None, father2) + else: + father_b = self.add_person_box( (level, 0), father_h, + family_h, father2) + + if self.inlc_marr: + family_b = self.add_marriage_box( (level, 1), father_h, + family_h, father_b) + self.famalies_seen.append(family_h) + + if mother_h: + mother_b = self.add_person_box( (level, 0), mother_h, + family_h, father_b) + else: + mother_b = self.add_person_box( (level, 0), None, None, father_b) + + #child_refs = [] + #for child_ref in family.get_child_ref_list(): + # child_refs.append(child_ref) + + for child_ref in family.get_child_ref_list(): + if self.inlc_marr: + self.recurse(child_ref.ref, level+1, 0, family_b) + else: + self.recurse(child_ref.ref, level+1, 0, father_b) + + #Future code. + #if self.inlc_marr: + # family_b.line_to.start.append(father_b) + # family_b.line_to.start.append(mother_b) + #else: + # father_b.line_to.start.append(mother_b) + + self.bold_now = 0 + return father_b + + def has_children(self, person_handle): + """ + Quickly check to see if this person has children + still we want to respect the FamaliesSeen list + """ + + if person_handle == None: + return False + if person_handle in self.famalies_seen: + return False + + person = self.database.get_person_from_handle(person_handle) + + for family_handle in person.get_family_handle_list(): + if family_handle not in self.famalies_seen: + + family = self.database.get_family_from_handle(family_handle) + + if family.get_child_ref_list() != []: + return True + return False + + def recurse_if(self, person_handle, level): + """ + Quickly check to see if we want to continue recursion + still we want to respect the FamaliesSeen list + """ + person = self.database.get_person_from_handle(person_handle) + + show = False + myfams = person.get_family_handle_list() + if len(myfams) > 1: #and self.max_spouses > 0 + show = True + if not self.inlc_marr: + #if the condition is true, we only want to show + #this parent again IF s/he has other children + show = self.has_children(person_handle) + + #if self.max_spouses == 0 and not self.has_children(person_handle): + # self.famalies_seen.append(person_handle) + # show = False + + if show == True: + self.bold_now = 1 + self.recurse(person_handle, level, 0, None) + + +#------------------------------------------------------------------------ +# +# Class MakePersonTree (Personal Descendant Tree option) +# +#------------------------------------------------------------------------ +class MakePersonTree(RecurseDown): + """ + The main procedure to use recursion to make the tree based off of a person. + order of people inserted into Persons is important. + makes sure that order is done correctly. + """ + def __init__(self, dbase, canvas): + RecurseDown.__init__(self, dbase, canvas) + self.max_generations -= 1 + + def start(self, person_id): + """follow the steps to make a tree off of a person""" + persons = [] + + father1 = self.database.get_person_from_gramps_id(person_id) + father1_h = father1.get_handle() #could be mom too. + + family2 = family2_h = None + if self.do_gparents: + family2_h = father1.get_main_parents_family_handle() + family2 = self.database.get_family_from_handle(family2_h) + + mother2_h = father2_h = None + if family2: + father2_h = family2.get_father_handle() + mother2_h = family2.get_mother_handle() + + + ####################### + #don't do center person's parents family. + if family2_h: + self.famalies_seen.append(family2_h) + + ####################### + #Center person's Fathers OTHER wives + ####################### + #update to only run if he HAD other wives! + if father2_h: + self.recurse_if(father2_h, 0) + + ####################### + #Center persons parents only! + ####################### + #now it will ONLY be my fathers parents + if family2: + father2_id = self.add_family( 0, family2, None ) + #if father2_h is not None: + # persons.append(self.database.get_person_from_handle(father2_h)) + #if mother2_h is not None: + # persons.append(self.database.get_person_from_handle(mother2_h)) + else: + self.bold_now = 2 + self.recurse(father1_h, 0, 0, None) + #persons.append(self.database.get_person_from_handle(father1_h)) + self.bold_now = 0 + + ####################### + #Center person's mothers OTHER husbands + ####################### + #update to only run if she HAD other husbands! + if mother2_h: + self.recurse_if(mother2_h, 0) + + return persons + +#------------------------------------------------------------------------ +# +# Class MakeFamilyTree (Familial Descendant Tree option) +# +#------------------------------------------------------------------------ +class MakeFamilyTree(RecurseDown): + """ + The main procedure to use recursion to make the tree based off of a family. + order of people inserted into Persons is important. + makes sure that order is done correctly. + """ + def __init__(self, dbase, canvas): + RecurseDown.__init__(self, dbase, canvas) + + def start(self, family_id): + """follow the steps to make a tree off of a family""" + ## (my) referes to the children of family_id + # Step 1 print out my fathers, fathers, + # other wives families first (if needed) + family1 = self.database.get_family_from_gramps_id(family_id) + family1_h = family1.get_handle() + + ####################### + #Initial setup of variables + ####################### + father1_h = family1.get_father_handle() + mother1_h = family1.get_mother_handle() + + father1 = mother1 = family2 = family2_h = None + if father1_h: + father1 = self.database.get_person_from_handle(father1_h) + if self.do_gparents: #b3 - remove grandparents? + family2_h = father1.get_main_parents_family_handle() + family2 = self.database.get_family_from_handle(family2_h) + if mother1_h: + mother1 = self.database.get_person_from_handle(mother1_h) + + mother2_h = father2_h = None + if family2: #family2 = fathers parents + mother2_h = family2.get_mother_handle() + mother2 = self.database.get_person_from_handle(mother2_h) + father2_h = family2.get_father_handle() + father2 = self.database.get_person_from_handle(father2_h) + + #Helper variables. Assigned in one section, used in another. + father2_id = family2_id = None + mother1_id = None + + ####################### + #don't do my fathers parents family. will be done later + if family2_h: + self.famalies_seen.append(family2_h) + + ####################### + #my father mothers OTHER husbands + ####################### + #update to only run if she HAD other husbands! + if mother2_h: + self.recurse_if(mother2_h, 0) + + ####################### + #father Fathers OTHER wives + ####################### + #update to only run if he HAD other wives! + if father2_h: + self.recurse_if(father2_h, 0) + + ####################### + #don't do my parents family in recurse. will be done later + self.famalies_seen.append(family1_h) + ##If dad has no other children from other marriages. remove him + if self.max_spouses == 0 and not self.has_children(father1_h): + self.famalies_seen.append(father1_h) + + ####################### + #my fathers parents! + ####################### + #now it will ONLY be my fathers parents + #will print dads parents. dad's other wifes will also print + if family2: + myfams = father1.get_family_handle_list() + show = False + if len(myfams) > 1: + show = True + if not self.inlc_marr and self.max_spouses == 0: + #if the condition is true, we only want to show + #this parent again IF s/he has children + show = self.has_children(father1_h) + if show == False: + self.famalies_seen.append(father1_h) + + father2_id = self.add_family( 0, family2, None ) + + if self.inlc_marr: + family2_id = father2_id.next + + + ####################### + #my father other wives (if all of the above does nothing) + #if my father does not have parents (he is the highest) + ####################### + #do his OTHER wives first. + if family2 == None and father1: + self.recurse_if(father1_h, 1) + + + ####################### + #my father + ####################### + if family2: + #We need to add dad to the family + if self.inlc_marr: + parent = family2_id + else: + parent = father2_id + else: + parent = None + + father1_b = self.add_family( 1, family1, parent ) + + #All of us gets added to the line start + #Add the next no matter what it is. + if self.inlc_marr: + mother1_b = father1_b.next.next #Mom's Box + line = father1_b.next.line_to + if line is None: + line = Line() + line.start = [father1_b, father1_b.next, mother1_b] + else: + mother1_b = father1_b.next #Mom's Box + line = father1_b.line_to + if line is None: + line = Line() + line.start = [father1_b, mother1_b] + father1_b.line_to = line + + #make sure there is at least one child in this family. + #if not put in a placeholder + if line.end == []: + box = PlaceHolderBox((father1_b[0]+1, 0)) + self.add_to_col(box) + line.end.append(box) + + ####################### + ####################### + #Lower half + #This will be quite like the first half. + #Just on the mothers side... + #Mom has already been printed with the family + ####################### + ####################### + + ####################### + #Initial setup of variables + ####################### + mother1_h = family1.get_mother_handle() + family2_h = mother1 = family2 = None + if mother1_h: + mother1 = self.database.get_person_from_handle(mother1_h) + if self.do_gparents: #b3 - remove grandparents? + family2_h = mother1.get_main_parents_family_handle() + family2 = self.database.get_family_from_handle(family2_h) + + mother2_h = father2_h = None + if family2: + mother2_h = family2.get_mother_handle() + mother2 = self.database.get_person_from_handle(mother2_h) + father2_h = family2.get_father_handle() + father2 = self.database.get_person_from_handle(father2_h) + + ####################### + #don't do my parents family. + self.famalies_seen = [family1_h] + ##If mom has no other children from other marriages. remove her + if self.max_spouses == 0 and not self.has_children(mother1_h): + self.famalies_seen.append(mother1_h) + + if mother1_h: + myfams = mother1.get_family_handle_list() + if len(myfams) < 2: + #If mom didn't have any other families, don't even do her + #she is already here with dad and will be added later + self.famalies_seen.append(mother1_h) + + ####################### + #my mother other spouses (if no parents) + ####################### + #if my mother does not have parents (she is the highest) + #Then do her OTHER spouses. + if family2 == None and mother1: + self.recurse_if(mother1_h, 1) + + ####################### + #my mothers parents! + ####################### + if family2: + father2 = self.add_family( 0, family2, None ) + if self.inlc_marr: + family2 = father2.next + + #Add mom (made in dad's section) to the list of children (first) + tmpfather = father2 + if self.inlc_marr: + tmpfather = family2 + tmpfather.line_to.end.insert(0, mother1_b) + + #fix me. Moms other siblings have been given an extra space + #Because Moms-father is not siblings-father right now. + + mother1_b.father = tmpfather + + ####################### + #my mother mothers OTHER husbands + ####################### + #update to only run if she HAD other husbands! + if mother2_h: + self.recurse_if(mother2_h, 0) + + ####################### + #mother Fathers OTHER wives + ####################### + #update to only run if he HAD other wives! + if father2_h: + self.recurse_if(father2_h, 0) + + +#------------------------------------------------------------------------ +# +# Class MakeReport +# +#------------------------------------------------------------------------ +class MakeReport(object): + """ + Make a report out of a list of people. + The list of people is already made. Use this information to find where + people will be placed on the canvas. + """ + + def __init__(self, dbase, canvas, ind_spouse, compress_tree): + self.database = dbase + self.canvas = canvas + + gui = GuiConnect() + self.do_gparents = gui.get_val('show_gparents') + self.inlc_marr = gui.get_val('incmarr') + self.max_spouses = gui.get_val('maxspouse') + gui = None + + self.ind_spouse = ind_spouse + self.compress_tree = compress_tree + self.cols = [[]] + #self.max_generations = 0 + + #already done in recurse, + #Some of this code needs to be moved up to RecurseDown.add_to_col() + def calc_box(self, box): + """ calculate the max_box_width and max_box_height for the report """ + width = box.x_cm + box.width + if width > self.canvas.doc.report_opts.max_box_width: + self.canvas.doc.report_opts.max_box_width = width + + if box.height > self.canvas.doc.report_opts.max_box_height: + self.canvas.doc.report_opts.max_box_height = box.height + + while len(self.cols) <= box.level[0]: + self.cols.append([]) + + self.cols[box.level[0]].append(box) + + #tmp = box.level[0] + #if tmp > self.max_generations: + # self.max_generations = tmp + + def __move_col_from_here_down(self, box, amount): + """Move me and everyone below me in this column only down""" + while box is not None: + box.y_cm += amount + box = box.next + + def __move_next_cols_from_here_down(self, box, amount): + """Move me, everyone below me in this column, + and all of our children (and childrens children) down.""" + col = [box] + while len(col) > 0: + if len(col) == 1 and col[0].line_to is not None: + col.append(col[0].line_to.end[0]) + + col[0].y_cm += amount + + col[0] = col[0].next + if col[0] is None: + col.pop(0) + + def __next_family_group(self, box): + """ a helper function. Assume box is at the start of a family block. + get this family block. """ + while box is not None: + left_group = [] + + #Form the parental (left) group. + #am I a direct descendant? + if box.level[1] == 0: + #I am the father/mother. + left_group.append(box) + box = box.next + + if box is not None and box.level[1] != 0 and self.inlc_marr: + #add/start with the marriage box + left_group.append(box) + box = box.next + + if box is not None and box.level[1] != 0 and self.max_spouses > 0: + #add/start with the spousal box + left_group.append(box) + box = box.next + + right_group = [] + #Form the children (right) group. + for spouses in left_group: + if spouses.line_to is not None: + right_group += spouses.line_to.end + #will only be one. but in the family report, + #Dad and mom point to the same line + break + + #we now have everyone we want + if right_group != []: + return left_group, right_group + #else + # no children, so no family. go again until we find one to return. + + return None, None + + def __reverse_family_group(self): + """ go through the n-1 to 0 cols of boxes looking for famalies + (parents with children) that may need to be moved. """ + for x_col in range(len(self.cols)-2, -1, -1): + box = self.cols[x_col][0] #The first person in this col + while box is not None: + left_group, right_group = self.__next_family_group(box) + if left_group is None: + box = None #we found the end of this col + else: + yield left_group, right_group + box = left_group[-1].next + + def __calc_movements(self, left_group, right_group): + """ for a family group, see if parents or children need to be + moved down so everyone is the the right/left of each other. + + return a right y_cm and a left y_cm. these points will be used + to move parents/children down. + """ + left_up = left_group[0].y_cm + right_up = right_group[0].y_cm + + left_center = left_up + right_center = right_up + + if self.compress_tree: + #calculate a new left and right move points + for left_line in left_group: + if left_line.line_to is not None: + break + left_center = left_line.y_cm + (left_line.height /2) + + left_down = left_group[-1].y_cm + left_group[-1].height + right_down = right_group[-1].y_cm + right_group[-1].height + + #Lazy. Move down either side only as much as we NEED to. + if left_center < right_up: + right_center = right_group[0].y_cm + elif left_up == right_up: + left_center = left_up #Lets keep it. top line. + elif left_center > right_down: + right_center = right_down + else: + right_center = left_center + + return right_center, left_center + + def Make_report(self): + """ + Everyone on the page is as far up as they can go. + Move them down to where they belong. + + We are going to go through everyone from right to left + top to bottom moving everyone down as needed to make the report. + """ + for left_group, right_group in self.__reverse_family_group(): + right_y_cm, left_y_cm = self.__calc_movements(left_group, + right_group) + + #1. Are my children too high? if so move then down! + if right_y_cm < left_y_cm: + #we have to push our kids (and their kids) down. + #We also need to push down all the kids (under) + #these kids (in their column) + amt = (left_y_cm - right_y_cm) + self.__move_next_cols_from_here_down(right_group[0], amt) + + + #2. Am I (and spouses) too high? if so move us down! + elif left_y_cm < right_y_cm: + #Ok, I am too high. Move me down + amt = (right_y_cm - left_y_cm) + self.__move_col_from_here_down(left_group[0], amt) + + #6. now check to see if we are working with dad. + #if so we need to move down mariage information + #and mom! + left_line = left_group[0].line_to + if left_line is None: + left_line = left_group[1].line_to + left_group = left_line.start + + if len(left_group) > 1: + #So far only Dad and Mom have len(left_group) > 1 + left_up = left_group[0].y_cm + left_down = left_group[-1].y_cm + left_group[-1].height + right_up = right_group[0].y_cm + right_down = right_group[-1].y_cm + right_group[-1].height + + #if the parents height is less than the children height + #The 0.2 is to see if this is even worth it. + if (left_down-left_up+0.2) < (right_down-right_up): + #our children take up more space than us parents. + #so space us parents out! + + #move Dad + if self.compress_tree: + left_group[0].y_cm += right_group[0].height/2 + + move_amt = right_group[-1].y_cm + right_group[-1].height/2 + move_amt -= (left_group[-1].y_cm + left_group[-1].height/2) + #move Mom + self.__move_col_from_here_down(left_group[-1], move_amt) + + #move marriage info + if self.inlc_marr: + left_group[0].next.y_cm += move_amt/2 + + def start(self): + """Make the report""" + #for person in self.persons.depth_first_gen(): + for box in self.canvas.boxes: + self.calc_box(box) + #At this point we know everything we need to make the report. + #Width of each column of people - self.rept_opt.box_width + #width of each column (or row) of lines - self.rept_opt.col_width + + if len(self.cols[0]) == 0: + #We wanted to print parents of starting person/family but + #there were none! + #remove column 0 and move everyone back one level + self.cols.pop(0) + for box in self.canvas.boxes: + box.level = (box.level[0]-1, box.level[1]) + + #go ahead and set it now. + width = self.canvas.doc.report_opts.max_box_width + for box in self.canvas.boxes: + box.width = width - box.x_cm + box.x_cm += self.canvas.doc.report_opts.littleoffset + box.x_cm += (box.level[0] * \ + (self.canvas.doc.report_opts.col_width + + self.canvas.doc.report_opts.max_box_width)) + + box.y_cm += self.canvas.doc.report_opts.littleoffset + box.y_cm += self.canvas.title.height + + self.Make_report() + + +class GuiConnect(): + """ This is a BORG object. There is ONLY one. + This give some common routines that EVERYONE can use like + get the value from a GUI variable + """ + + __shared_state = {} + def __init__(self): #We are BORG! + self.__dict__ = self.__shared_state + + def set__opts(self, options, which): + self._opts = options + self._which_report = which + + def get_val(self, val): + return self._opts.get_option_by_name(val).get_value() + + def Title_class(self, database, doc): + Title_type = self.get_val('report_title') + if Title_type == 0: #None + return TitleNone(database, doc) + + if Title_type == 1: #Descendant Chart + if self._which_report == _RPT_NAME: + if self.get_val('show_gparents'): + return TitleDPY(database, doc) + else: + return TitleDPN(database, doc) + else: + if self.get_val('show_gparents'): + return TitleDFY(database, doc) + else: + return TitleDFN(database, doc) + + if Title_type == 2: + return TitleF(database, doc) + else: #Title_type == 3 + return TitleC(database, doc) + + def Make_Tree(self, database, canvas): + if self._which_report == _RPT_NAME: + return MakePersonTree(database, canvas) + else: + return MakeFamilyTree(database, canvas) + + def calc_lines(self, database): + #calculate the printed lines for each box + display_repl = self.get_val('replacelist') + #str = "" + #if self.get_val('miss_val'): + # str = "_____" + return CalcLines(database, display_repl) + + def working_lines(self, box): + display = self.get_val('dispf') + if self.get_val('diffspouse'): + display_spou = self.get_val('sdispf') + else: + display_spou = display + display_marr = [self.get_val('dispmarr')] + + if box.boxstr == "CG2-fam-box": #((((( + workinglines = display_marr + elif box.level[1] > 0 or (box.level[0] == 0 and + box.father is not None): + workinglines = display_spou + else: + workinglines = display + return workinglines + #------------------------------------------------------------------------ # # DescendTree # #------------------------------------------------------------------------ -class DescendTree(Report): - """ - Report class that generates a descendant tree. - """ +class Descend2Tree(Report): def __init__(self, database, options_class): """ - Create DescendTree object that produces the report. - + Create Descend2Tree object that produces the report. The arguments are: database - the GRAMPS database instance - person - currently selected person options_class - instance of the Options class for this report This report needs the following parameters (class variables) that come in the options class. - - dispf - Display format for the output box. - singlep - Whether to scale to fit on a single page. - maxgen - Maximum number of generations to include. """ Report.__init__(self, database, options_class) - menu = options_class.menu - self.display = menu.get_option_by_name('dispf').get_value() - self.max_generations = menu.get_option_by_name('maxgen').get_value() - self.force_fit = menu.get_option_by_name('singlep').get_value() - self.incblank = menu.get_option_by_name('incblank').get_value() - pid = menu.get_option_by_name('pid').get_value() - center_person = database.get_person_from_gramps_id(pid) - if (center_person == None) : - raise ReportError(_("Person %s is not in the Database") % pid ) - - self.showspouse = menu.get_option_by_name('shows').get_value() - - name = name_displayer.display_formal(center_person) - self.title = _("Descendant Chart for %s") % name - - self.map = {} - self.text = {} - - self.box_width = 0 - self.box_height = 0 - self.lines = 0 - self.scale = 1 - self.box_gap = 0.2 - self.box_pad_pts = 0 - self.offset = 0 - self.page_label_x_offset = 0 - self.page_label_y_offset = 0 - self.usable_height = 0 - self.usable_width = 0 - self.generations_per_page = 0 - self.delta = 0 - - self.genchart = GenChart(32) - - self.apply_filter(center_person.get_handle(), 0, 0) - - self.calc() - - if self.force_fit: - self.scale_styles() - - def add_person(self, person_handle, col_x, row_y, spouse_level): - """Add a new person into the x,y position - also sets wether the person is: - - a direct descendent or a spouse - - the max length of the text/box, and number of lines""" - self.genchart.set_sp(col_x, row_y, spouse_level) - if person_handle is not None: - self.genchart.set_xy(col_x, row_y, person_handle) - else: - #make sure that a box prints - self.genchart.set_xy(col_x, row_y, ".") - #make a blank box. - self.text[(col_x, row_y)] = [""] - return + self.Connect = GuiConnect() + self.Connect.set__opts(options_class.menu, options_class.name) style_sheet = self.doc.get_style_sheet() - pstyle = style_sheet.get_paragraph_style("DC2-Normal") - font = pstyle.get_font() + font_normal = style_sheet.get_paragraph_style("CG2-Normal").get_font() + self.doc.report_opts = ReportOptions(self.doc, font_normal) + + self.canvas = Canvas(self.doc) + + center_id = self.Connect.get_val('pid') - em = self.doc.string_width(font, "m") + #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) - subst = SubstKeywords(self.database, person_handle) - self.text[(col_x, row_y)] = subst.replace_and_clean(self.display) - for line in self.text[(col_x, row_y)]: - this_box_width = self.doc.string_width(font, line) + (2 * em) - self.box_width = max(self.box_width, this_box_width) - - self.lines = max(self.lines, len(self.text[(col_x, row_y)])) - - def apply_filter(self, person_handle, col_x, row_y): - """traverse the ancestors recursively until either the end - of a line is found, or until we reach the maximum number of - generations that we want to deal with""" - - if (col_x / 2) >= self.max_generations: - return 0 - - person = self.database.get_person_from_handle(person_handle) - self.add_person(person_handle, col_x, row_y, _PERSON_DIRECT) - - working_col = 1 - next_col = 0 - for family_handle in person.get_family_handle_list(): - - family = self.database.get_family_from_handle(family_handle) - - if self.showspouse: - spouse_handle = ReportUtils.find_spouse(person, family) - self.add_person(spouse_handle, col_x, row_y + working_col, - _PERSON_SPOUSE) - working_col += 1 - - for child_ref in family.get_child_ref_list(): - next_col += self.apply_filter(child_ref.ref, col_x + 2, - row_y + next_col) - - working_col = next_col = max(working_col, next_col) - - return working_col - - - def add_lines(self): + #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. """ - Add the lines that connect the boxes in the chart. - """ - (maxy, maxx) = self.genchart.dimensions() - for y in range(0, maxy + 1): - for x in range(0, maxx + 1): - # skip columns reserved for rows - no data here - if x % 2: - continue - - # if we have a direct child to the right of a person - # check to see if the child is a descendant of the person - if self.genchart.get_sp(x + 2, y) == _PERSON_DIRECT: - if self.genchart.get_sp(x, y) == _PERSON_DIRECT: - self.genchart.set_xy(x + 1, y , _LINE_HORIZONTAL) - continue - elif self.genchart.get_sp(x, y) == _PERSON_SPOUSE and \ - self.genchart.get_sp(x, y - 1) != _PERSON_DIRECT: - self.genchart.set_xy(x + 1, y , _LINE_HORIZONTAL) - continue - else: - continue - - self.genchart.set_xy(x + 1, y, _LINE_ANGLE) - - # look through the entries ABOVE this one. All direct people - # in the next column are descendants until we hit the first - # direct person (marked with _LINE_HORIZONTAL) - last = y - 1 - while last > 0: - if self.genchart.get_xy(x + 1, last) == 0: - self.genchart.set_xy(x + 1, last, _LINE_VERTICAL) - else: - break - last -= 1 + 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): - """ - Write the report to the document. - """ - (maxy, maxx) = self.genchart.dimensions() - if maxx != 1: - maxx = (maxx - 1) * 2 - else: - #no descendants - maxx = 1 - maxh = int((self.usable_height - 0.75) / (self.box_height * 1.25)) - - if self.force_fit: - self.print_page(0, maxx, 0, maxy, 0, 0) - else: - starty = 0 - coly = 0 - while starty < maxy: - startx = 0 - colx = 0 - while startx < maxx: - stopx = min(maxx, startx + (self.generations_per_page * 2)) - stopy = min(maxy, starty + maxh) - self.print_page(startx, stopx, starty, stopy, colx, coly) - colx += 1 - startx += self.generations_per_page * 2 - coly += 1 - starty += maxh - - def calc(self): - """ - calc - calculate the maximum width that a box needs to be. From - that and the page dimensions, calculate the proper place to put - the elements on a page. - """ - style_sheet = self.doc.get_style_sheet() - - self.add_lines() - - self.box_pad_pts = 10 - if self.title and self.force_fit: - pstyle = style_sheet.get_paragraph_style("DC2-Title") - tfont = pstyle.get_font() - self.offset = ReportUtils.pt2cm(1.25 * tfont.get_size()) - - gstyle = style_sheet.get_draw_style("DC2-box") - shadow_height = gstyle.get_shadow_space() - else: - # Make space for the page number labels at the bottom. - p = style_sheet.get_paragraph_style("DC2-Normal") - font = p.get_font() - lheight = ReportUtils.pt2cm(1.2 * font.get_size()) - lwidth = ReportUtils.pt2cm(1.1 * - self.doc.string_width(font, "(00,00)")) - self.page_label_x_offset = self.doc.get_usable_width() - lwidth - self.page_label_y_offset = self.doc.get_usable_height() - lheight - - self.offset = ReportUtils.pt2cm(1.25 * font.get_size()) - shadow_height = 0 - self.usable_height = self.doc.get_usable_height() \ - - self.offset \ - - shadow_height - self.usable_width = self.doc.get_usable_width() \ - - ReportUtils.pt2cm(self.box_pad_pts) - - calc_width = ReportUtils.pt2cm(self.box_width + self.box_pad_pts) \ - + self.box_gap - self.box_width = ReportUtils.pt2cm(self.box_width) - pstyle = style_sheet.get_paragraph_style("DC2-Normal") - font = pstyle.get_font() - self.box_height = self.lines*ReportUtils.pt2cm(1.25 * font.get_size()) - - self.scale = 1 + """ Canvas now has everyone ready to print. Get some misc stuff + together and print. """ - if self.force_fit: - (maxy, maxx) = self.genchart.dimensions() + one_page = self.Connect.get_val('onepage') + scale_report = self.Connect.get_val('scale_report') - bw = (calc_width / (self.usable_width / (maxx + 1))) - bh = ((self.box_height * 1.25) + self.box_gap) \ - / (self.usable_height / maxy) + #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') - self.scale = max(bw / 2, bh) - self.box_width /= self.scale - self.box_height /= self.scale - self.box_pad_pts /= self.scale - self.box_gap /= self.scale + ##################### + #Setup page infomation - maxw = int(self.usable_width / calc_width) - - self.generations_per_page = maxw + colsperpage = self.doc.get_usable_width() + colsperpage += self.doc.report_opts.col_width + tmp = self.doc.report_opts.max_box_width + tmp += self.doc.report_opts.col_width + colsperpage = int( colsperpage / tmp ) + if colsperpage == 0: #Is the page really that small? + colsperpage = 1 - self.delta = ReportUtils.pt2cm(self.box_pad_pts) \ - + self.box_width \ - + self.box_gap - if not self.force_fit: - calc_width = self.box_width + ReportUtils.pt2cm(self.box_pad_pts) - remain = self.doc.get_usable_width() \ - - (self.generations_per_page * calc_width) - self.delta += remain / float(self.generations_per_page) + ##################### + #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): - def scale_styles(self): + 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() - box_style = style_sheet.get_draw_style("DC2-box") - box_style.set_shadow(box_style.get_shadow(), - box_style.get_shadow_space() / self.scale) - box_style.set_line_width(box_style.get_line_width() / self.scale) - style_sheet.add_draw_style("DC2-box", box_style) - - para_style = style_sheet.get_paragraph_style("DC2-Normal") + 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() / self.scale) + font.set_size(font.get_size() * amount) para_style.set_font(font) - style_sheet.add_paragraph_style("DC2-Normal", para_style) - + 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) - def print_page(self, startx, stopx, starty, stopy, colx, coly): - if not self.incblank: - blank = True - for y in range(starty, stopy): - for x in range(startx, stopx): - if self.genchart.get_xy(x, y) != 0: - blank = False - break - if not blank: - break - if blank: - return - self.doc.start_page() - if self.title and self.force_fit: - self.doc.center_text('DC2-title', self.title, - self.doc.get_usable_width() / 2, 0) - phys_y = 1 - bh = self.box_height * 1.25 - for y in range(starty, stopy): - phys_x = 0 - for x in range(startx, stopx): - value = self.genchart.get_xy(x, y) - if isinstance(value, basestring): - text = '\n'.join(self.text[(x, y)]) - xbegin = phys_x * self.delta - yend = (phys_y * bh) + self.offset - self.doc.draw_box("DC2-box", - text, - xbegin, - yend, - self.box_width, - self.box_height) - elif value == _LINE_HORIZONTAL: - xbegin = phys_x * self.delta - ystart = ((phys_y * bh) + self.box_height / 2.0) \ - + self.offset - xstart = xbegin + self.box_width - xstop = (phys_x + 1) * self.delta - self.doc.draw_line('DC2-line', xstart, ystart, xstop, - ystart) - elif value == _LINE_VERTICAL: - ystart = ((phys_y - 1) * bh) \ - + (self.box_height / 2.0) \ - + self.offset - ystop = (phys_y * bh) \ - + (self.box_height / 2.0) \ - + self.offset - xlast = (phys_x * self.delta) \ - + self.box_width \ - + self.box_gap - self.doc.draw_line('DC2-line', xlast, ystart, xlast, ystop) - elif value == _LINE_ANGLE: - ystart = ((phys_y - 1) * bh) \ - + (self.box_height / 2.0) \ - + self.offset - ystop = (phys_y * bh) \ - + (self.box_height / 2.0) \ - + self.offset - xlast = (phys_x * self.delta) \ - + self.box_width \ - + self.box_gap - xnext = (phys_x + 1) * self.delta - self.doc.draw_line('DC2-line', xlast, ystart, xlast, ystop) - self.doc.draw_line('DC2-line', xlast, ystop, xnext, ystop) - - if x % 2: - phys_x += 1 - phys_y += 1 - - if not self.force_fit: - self.doc.draw_text('DC2-box', - '(%d,%d)' % (colx + 1, coly + 1), - self.page_label_x_offset, - self.page_label_y_offset) - self.doc.end_page() - #------------------------------------------------------------------------ # # DescendTreeOptions # #------------------------------------------------------------------------ -class DescendTreeOptions(MenuReportOptions): +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): @@ -490,65 +1476,225 @@ class DescendTreeOptions(MenuReportOptions): Add options to the menu for the descendant report. """ category_name = _("Tree Options") - - pid = PersonOption(_("Center Person")) - pid.set_help(_("The center person for the tree")) - menu.add_option(category_name, "pid", pid) - - max_gen = NumberOption(_("Generations"), 10, 1, 50) + + 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) - - disp = TextOption( _("Display Format"), - ["$n","%s $b" % _BORN,"%s $d" %_DIED] ) + + max_spouse = NumberOption(_("Level of Spouses"), 1, 0, 10) + max_spouse.set_help(_("0=no Spouses, 1=include Spouses, 2=include" + + "Spouses of the spouse, etc")) + menu.add_option(category_name, "maxspouse", max_spouse) + + compresst = BooleanOption(_('Co_mpress tree'), False) + compresst.set_help(_("Whether to compress the tree.")) + menu.add_option(category_name, "compress_tree", compresst) + + category_name = _("Display") + + disp = TextOption(_("Personal\nDisplay Format"), + ["$n","%s $b" % _BORN,"%s $d" %_DIED]) disp.set_help(_("Display format for the outputbox.")) menu.add_option(category_name, "dispf", disp) + + bold = BooleanOption(_('Bold direct descendants'), True) + bold.set_help(_("Whether to bold those people that are direct" + + "(not step or half) decendants.")) + menu.add_option(category_name, "bolddirect", bold) + + #Will add when libsubstkeyword supports it. + #missing = EnumeratedListOption(_("Replace missing\npalces\\dates \ + # with"), 0) + #missing.add_item( 0, _("Does not display anything")) + #missing.add_item( 1, _("Displays '_____'")) + #missing.set_help(_("What will print when information is not known")) + #menu.add_option(category_name, "miss_val", missing) + + category_name = _("Secondary") + + diffspouse = BooleanOption(_('Use seperate display format for ' + + 'spouses'), True) + diffspouse.set_help(_("Whether spouses can have a different format.")) + menu.add_option(category_name, "diffspouse", diffspouse) + + indspouce = BooleanOption(_('Indent Spouses'), True) + indspouce.set_help(_("Whether to indent the spouses in the tree.")) + menu.add_option(category_name, "indspouce", indspouce) + + sdisp = TextOption(_("Spousal\nDisplay Format"), + ["$n","%s $b" % _BORN,"%s $d" %_DIED]) + sdisp.set_help(_("Display format for the outputbox.")) + menu.add_option(category_name, "sdispf", sdisp) + + incmarr = BooleanOption(_('Include Marriage information'), True) + incmarr.set_help(_("Whether to include marriage information in the" + + "report.")) + menu.add_option(category_name, "incmarr", incmarr) + + marrdisp = StringOption(_("Marraige\nDisplay Format"), "%s $m" % _MARR) + marrdisp.set_help(_("Display format for the outputbox.")) + menu.add_option(category_name, "dispmarr", marrdisp) + + category_name = _("Replace") + + repldisp = TextOption(_("Replace Display Format:\n'Replace this'/'" + + "with this'"), []) + repldisp.set_help(_("ie\nUnited States of America/U.S.A")) + menu.add_option(category_name, "replacelist", repldisp) + + category_name = _("Print") + + self.scale = EnumeratedListOption(_("Scale report to fit"), 0) + self.scale.add_item( 0, "Do not scale report") + self.scale.add_item( 1, "Scale report to fit page width only") + self.scale.add_item( 2, "Scale report to fit the size of the page") + self.scale.set_help(_("Wheather to scale the report to fit a " + + "specific size")) + menu.add_option(category_name, "scale_report", self.scale) + self.scale.connect('value-changed', self.__check_blank) + + self.__onepage = BooleanOption(_('One page report'), True) + self.__onepage.set_help(_("Whether to scale the size of the page to " + + "the size of the report.")) + menu.add_option(category_name, "onepage", self.__onepage) + self.__onepage.connect('value-changed', self.__check_blank) + + self.title = EnumeratedListOption(_("Report Title"), 0) + self.title.add_item( 0, "Do not print a title") + self.title.set_help(_("Choose a title for the report")) + menu.add_option(category_name, "report_title", self.title) + self.showparents.connect('value-changed', self.__Title_enum) + + border = BooleanOption(_('Print a border'), True) + border.set_help(_("Whether to make a border around the report.")) + menu.add_option(category_name, "inc_border", border) + + prnnum = BooleanOption(_('Print Page Numbers'), False) + prnnum.set_help(_("Whether to print page numbers on each page.")) + menu.add_option(category_name, "prnnum", prnnum) + + self.__blank = BooleanOption(_('Include Blank Pages'), True) + self.__blank.set_help(_("Whether to include pages that are blank.")) + menu.add_option(category_name, "incblank", self.__blank) - scale = BooleanOption(_('Sc_ale to fit on a single page'), True) - scale.set_help(_("Whether to scale to fit on a single page.")) - menu.add_option(category_name, "singlep", scale) + 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 todays 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 ) - blank = BooleanOption(_('Include Blank Pages'), True) - blank.set_help(_("Whether to include pages that are blank.")) - menu.add_option(category_name, "incblank", blank) - - shows = BooleanOption(_('Show Sp_ouses'), True) - shows.set_help(_("Whether to show spouses in the tree.")) - menu.add_option(category_name, "shows", shows) + def __Title_enum(self): + item_list = [] + item_list.append([0, "Do not print a title" ]) + item_list.append([1, "Descendant Chart for [selected person(s)]" ]) + if self.name != _RPT_NAME: + item_list.append([2, "Family Chart for [names of choosen family]" ]) + if self.showparents.get_value(): + item_list.append([3, "Cousin Chart for [names of children]" ]) + self.title.set_items(item_list) def make_default_style(self, default_style): - """Make the default output style for the Ancestor Tree.""" - ## Paragraph Styles: - font = FontStyle() - font.set_size(9) - font.set_type_face(FONT_SANS_SERIF) - p_style = ParagraphStyle() - p_style.set_font(font) - p_style.set_description(_('The basic style used for the text display.')) - default_style.add_paragraph_style("DC2-Normal", p_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) - p_style = ParagraphStyle() - p_style.set_font(font) - p_style.set_alignment(PARA_ALIGN_CENTER) - p_style.set_description(_('The basic style used for the title display.')) - default_style.add_paragraph_style("DC2-Title", p_style) - + para_style = ParagraphStyle() + para_style.set_font(font) + para_style.set_alignment(PARA_ALIGN_CENTER) + para_style.set_description(_('The basic style used for the ' + + 'title display.')) + default_style.add_paragraph_style("CG2-Title", para_style) + + font = FontStyle() + font.set_size(9) + font.set_type_face(FONT_SANS_SERIF) + para_style = ParagraphStyle() + para_style.set_font(font) + para_style.set_description(_('The basic style used for the ' + + 'text display.')) + default_style.add_paragraph_style("CG2-Normal", para_style) + + #Set the size of the shadow based on the font size! Much better + #will be set later too. + tmp = PT2CM(font.get_size()) + box_shadow = tmp * .6 + + font.set_bold(True) + para_style = ParagraphStyle() + para_style.set_font(font) + para_style.set_description(_('The bold style used for the ' + + 'text display.')) + default_style.add_paragraph_style("CG2-Bold", para_style) + + graph_style = GraphicsStyle() + graph_style.set_paragraph_style("CG2-Title") + graph_style.set_color((0, 0, 0)) + graph_style.set_fill_color((255, 255, 255)) + graph_style.set_line_width(0) + default_style.add_draw_style("CG2-Title", graph_style) + ## Draw styles - g_style = GraphicsStyle() - g_style.set_paragraph_style("DC2-Normal") - g_style.set_shadow(1, 0.2) - g_style.set_fill_color((255, 255, 255)) - default_style.add_draw_style("DC2-box", g_style) + 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) - g_style = GraphicsStyle() - g_style.set_paragraph_style("DC2-Title") - g_style.set_color((0, 0, 0)) - g_style.set_fill_color((255, 255, 255)) - g_style.set_line_width(0) - default_style.add_draw_style("DC2-title", g_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) - g_style = GraphicsStyle() - default_style.add_draw_style("DC2-line", g_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 diff --git a/src/plugins/drawreport/drawplugins.gpr.py b/src/plugins/drawreport/drawplugins.gpr.py index a812f774c..3d8179df4 100644 --- a/src/plugins/drawreport/drawplugins.gpr.py +++ b/src/plugins/drawreport/drawplugins.gpr.py @@ -36,11 +36,11 @@ plg.gramps_target_version = '3.3' plg.status = STABLE plg.fname = 'AncestorTree.py' plg.ptype = REPORT -plg.authors = ["Donald N. Allingham"] -plg.authors_email = ["don@gramps-project.org"] +plg.authors = ["Craig J. Anderson"] +plg.authors_email = ["ander882@gramps-project.org"] plg.category = CATEGORY_DRAW -plg.reportclass = 'AncestorTree' -plg.optionclass = 'AncestorTreeOptions' +plg.reportclass = 'AncestorTree2' +plg.optionclass = 'AncestorTree2Options' plg.report_modes = [REPORT_MODE_GUI, REPORT_MODE_BKI, REPORT_MODE_CLI] #------------------------------------------------------------------------ @@ -80,11 +80,34 @@ plg.gramps_target_version = '3.3' plg.status = STABLE plg.fname = 'DescendTree.py' plg.ptype = REPORT -plg.authors = ["Donald N. Allingham"] -plg.authors_email = ["don@gramps-project.org"] +plg.authors = ["Craig J. Anderson"] +plg.authors_email = ["ander882@gramps-project.org"] plg.category = CATEGORY_DRAW -plg.reportclass = 'DescendTree' -plg.optionclass = 'DescendTreeOptions' +plg.reportclass = 'Descend2Tree' +plg.optionclass = 'Descend2TreeOptions' +plg.report_modes = [REPORT_MODE_GUI, REPORT_MODE_BKI, REPORT_MODE_CLI] + +#------------------------------------------------------------------------ +# +# Family Descendant Tree +# +#------------------------------------------------------------------------ + +plg = newplugin() +plg.id = 'family_descend_chart' +plg.name = _("Family Descendant Tree") +plg.description = _("Produces a graphical descendant tree around a family") +plg.version = '1.0' +plg.status = STABLE +plg.fname = 'DescendTree.py' +plg.ptype = REPORT +plg.category = CATEGORY_DRAW +plg.gramps_target_version = '3.3' +plg.authors = ["Craig J. Anderson"] +plg.authors_email = ["ander882@gramps-project.org"] +plg.require_active = True +plg.reportclass = 'Descend2Tree' +plg.optionclass = 'Descend2TreeOptions' plg.report_modes = [REPORT_MODE_GUI, REPORT_MODE_BKI, REPORT_MODE_CLI] #------------------------------------------------------------------------ diff --git a/src/plugins/lib/Makefile.am b/src/plugins/lib/Makefile.am index d55c92463..d3f554dfe 100644 --- a/src/plugins/lib/Makefile.am +++ b/src/plugins/lib/Makefile.am @@ -26,13 +26,14 @@ pkgdata_PYTHON = \ libholiday.py\ libmapservice.py\ libmixin.py\ - libnarrate.py\ + libnarrate.py\ libodfbackend.py\ libpersonview.py\ libplaceview.py\ libplugins.gpr.py\ libsubstkeyword.py\ - libtranslate.py + libtranslate.py\ + libtreebase.py pkgpyexecdir = @pkgpyexecdir@/plugins/lib pkgpythondir = @pkgpythondir@/plugins/lib diff --git a/src/plugins/lib/libplugins.gpr.py b/src/plugins/lib/libplugins.gpr.py index 4dc2d60f5..873872223 100644 --- a/src/plugins/lib/libplugins.gpr.py +++ b/src/plugins/lib/libplugins.gpr.py @@ -302,4 +302,21 @@ fname = 'libsubstkeyword.py', authors = ["The Gramps project"], authors_email = ["http://gramps-project.org"], ) +#------------------------------------------------------------------------ +# +# libtreebase +# +#------------------------------------------------------------------------ +register(GENERAL, +id = 'libtreebase', +name = "Graphical report lib", +description = _("Provides the base needed for the ancestor and " + + "descendant graphical reports.") , +version = '1.0', +gramps_target_version = '3.3', +status = STABLE, +fname = 'libtreebase.py', +authors = ["The Gramps project"], +authors_email = ["http://gramps-project.org"], +) diff --git a/src/plugins/lib/libtreebase.py b/src/plugins/lib/libtreebase.py new file mode 100644 index 000000000..63a7ee082 --- /dev/null +++ b/src/plugins/lib/libtreebase.py @@ -0,0 +1,874 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2008-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: Descendant.py .... ander882 $ + +"""Reports/Graphical Reports/Tree_Base""" + +#------------------------------------------------------------------------ +# +# python modules +# +#------------------------------------------------------------------------ +from gen.ggettext import sgettext as _ + +from gen.plug.report import utils as ReportUtils + +from gen.display.name import displayer as name_displayer + +from libsubstkeyword import SubstKeywords + +PT2CM = ReportUtils.pt2cm + +#------------------------------------------------------------------------ +# +# Class Calc_Lines +# +#------------------------------------------------------------------------ +class CalcLines(object): + """ wrapper for libsubstkeyword and added functionality for + replacements. + + Receive: Individual and family handle, and display format [string] + return: [Text] ready for a box. + """ + def __init__(self, dbase, repl): + self.database = dbase + self.display_repl = repl + #self.default_string = default_str + + def calc_lines(self, _indi_handle, _fams_handle, workinglines): + """ + In this pass we will: + 1. make our text and do our replacements + 2. remove any extra (unwanted) lines with the compres option + """ + + #################### + #1.1 Get our line information here + subst = SubstKeywords(self.database, _indi_handle, _fams_handle) + lines = subst.replace_and_clean(workinglines) + + #################### + #1.2 do our replacements + lns = [] + for line in lines: + for pair in self.display_repl: + if pair.count("/") == 1: + repl = pair.split("/", 1) + line = line.replace(repl[0], repl[1]) + lns.append(line) + + return lns + + +#------------------------------------------------------------------------ +# +# Class Canvas/Pages +# +#------------------------------------------------------------------------ +class Page(object): + """ This class is a printable page. + Offsets from the canvas, Page numbers + boxes and lines + """ + def __init__(self, doc, canvas): + #parts from canvas + self.doc = doc + self.canvas = canvas + + #parts about the page + self.page_x_offset = 0 + self.page_y_offset = 0 + self.x_page_num = 0 + self.y_page_num = 0 + self.boxes = [] #All object must derive from BoxBase + self.lines = [] #All must derive form Linebase + self.note = None + + def is_blank(self): + """ Am I a blank page? Notes and Titles are boxes too """ + return self.boxes == [] and self.lines == [] + + def add_box(self, box): + """ The box must derive from class Box_Base(Object): """ + self.boxes.append(box) + box.doc = self.doc + box.page = self + + def add_line(self, line): + """ Add a line onto this page """ + self.lines.append(line) + + def draw_border(self, line_name): + if self.y_page_num == 0: + self.doc.draw_line(line_name, 0, 0, + self.doc.get_usable_width(), 0) + if self.x_page_num == 0: + self.doc.draw_line(line_name, 0, 0, 0, + self.doc.get_usable_height()) + if self.y_page_num == self.canvas.y_pages-1: + self.doc.draw_line(line_name, 0, + self.doc.get_usable_height(), + self.doc.get_usable_width(), + self.doc.get_usable_height()) + if self.x_page_num == self.canvas.x_pages-1: + self.doc.draw_line(line_name, self.doc.get_usable_width(), + 0, self.doc.get_usable_width(), + self.doc.get_usable_height()) + + def display(self): + """ Display all boxes and lines that are on this page """ + for box in self.boxes: + box.display() + for line in self.lines: + line.display(self) + + +class Canvas(Page): + """ The Canvas is two things. + The all in one canvas. a canvas is a page of unlimited size + a group of pages. each page is set is size and shows only a + part of what is on the entire canvas + """ + def __init__(self, doc): + Page.__init__(self, doc, self) + self.doc = doc + self.report_opts = None + + #How many pages are there in the report. one more than real. + self.x_pages = 1 + self.y_pages = 1 + self.__pages = {(0, 0): self} #set page 0,0 to me. + self.__fonts = {} #keep a list of fonts so we don't have to lookup. + self.title = None + self.note = None + + def __new_page(self, x_page, y_page, x_offset, y_offset): + """ Make a new page. This will only happen if we are + paginating (making new pages to hold parts of the canvas) """ + if x_page >= self.x_pages: + self.x_pages = x_page + 1 + new_page = Page(self.doc, self) + new_page.x_page_num = x_page + new_page.y_page_num = y_page + new_page.page_x_offset = x_offset + new_page.page_y_offset = y_offset + self.__pages[x_page, y_page] = new_page + return new_page + + def sort_boxes_on_y_cm(self): + """ sorts the list of boxes on the canvas by .y_cm (top down) """ + self.boxes.sort( key=lambda box: box.y_cm) + + def add_title(self, title): + """ The title must derive from class TitleBox(BoxBase): """ + self.title = title + + def add_note(self, note): + """ The note must derive from class NoteBox(BoxBase, NoteType) """ + self.note = note + self.set_box_height_width(self.note) + + def __get_font(self, box): + """ returns the font used by a box. makes a list of all seen fonts + to be faster. If a new is found, run through the process to get it """ + if not self.__fonts.has_key(box.boxstr): + style_sheet = self.doc.get_style_sheet() + style_name = style_sheet.get_draw_style(box.boxstr) + style_name = style_name.get_paragraph_style() + self.__fonts[box.boxstr] = \ + style_sheet.get_paragraph_style(style_name).get_font() + + return self.__fonts[box.boxstr] + + def get_report_height_width(self): + """ returns the (max width, max height) of the report + This does not take into account any shadows """ + max_width = 0 + max_height = 0 + for box in self.boxes: + tmp = box.x_cm + box.width + box.shadow + if tmp > max_width: + max_width = tmp + tmp = box.y_cm + box.height + box.shadow + if tmp > max_height: + max_height = tmp + return (max_width, max_height) + + def __scale_canvas(self, scale_amount): + """ scales everything up/down depending upon scale_amount """ + self.doc.report_opts.scale_everything(scale_amount) + self.title.scale(scale_amount) + if self.note is not None: + self.note.scale(scale_amount) + #scale down everyone! + for box in self.boxes: + box.scale(scale_amount) + + def set_box_height_width(self, box): + """ Sets the .width .height and .shadow of a box. """ + if box.boxstr == "None": + box.height = box.width = 0 + return + + font = self.__get_font(box) + ##################### + #Get the width + for line in box.text: + width = self.doc.string_width(font, line) + width = PT2CM(width) + if width > box.width: + box.width = width + + ##################### + #Get the height + height = len(box.text) * font.get_size() * 1.5 + height += 1.0/2.0 * font.get_size() #funny number(s) based upon font. + box.height = PT2CM(height) + + style_sheet = self.doc.get_style_sheet() + style = style_sheet.get_draw_style(box.boxstr) + if style.get_shadow(): + box.shadow = style.get_shadow_space() + + def page_iter_gen(self, incblank): + """ generate the pages of the report. do so in a left to right + up down approach. incblank asks to include blank pages """ + blank = Page(self.doc, self) + for y_p in range(self.y_pages): + for x_p in range(self.x_pages): + if self.__pages.has_key((x_p, y_p)): + yield self.__pages[(x_p, y_p)] + else: + if incblank: + blank.x_page_num = x_p + blank.y_page_num = y_p + yield blank + + def __add_box_to_page(self, x_page, y_page, x_offset, y_offset, box): + """ adds a box to a page. If the page is not there, make it first """ + if not self.__pages.has_key((x_page, y_page)): + #Add the new page into the dictionary + self.__new_page(x_page, y_page, x_offset, y_offset) + + #Add the box into the page + self.__pages[x_page, self.y_pages-1].add_box(box) + + def scale_report(self, one_page, scale_to_width, scale_to_height): + """ We have a report in its full size (on the canvas + and pages to print on. scale one or both as needed/desired. + + - one_page, boolean. Whether to make the page(or parts of) the size + of the report + - scale_to_width, boolean. Scale the report width to the page size? + - scale_to_height, boolean. Scale the report height to page size? + """ + + if scale_to_width or scale_to_height: + max_width, max_height = self.canvas.get_report_height_width() + max_width += self.doc.report_opts.littleoffset + max_height += self.doc.report_opts.littleoffset + + """ + calc - Calculate the scale amount (if any). + <1 everything is smaller to fit on the page + 1 == no scaling + >1 make everything bigger to fill out the page + """ + scale = 1 + scaled_report_to = None + + ##################### + #scale the report option - width + if scale_to_width: + #Check the width of the title + title_width = self.title.width + title_width += self.doc.report_opts.littleoffset * 2 + + max_width = max(title_width, max_width) + + #This will be our base amount and + #then we will decrease only as needed from here. + + scale = self.doc.get_usable_width() / max_width + scaled_report_to = "width" + + ##################### + #scale the report option - height + if scale_to_height: + tmp = self.doc.get_usable_height() / max_height + if not scale_to_width or tmp < scale: + scale = tmp + scaled_report_to = "height" + + #Now I have the scale amount + if scale != 1: #scale everything on the canvas + self.__scale_canvas(scale) + + ##################### + #Scale the page option + if one_page: + + #user wants PAGE to be the size of the report. + size = self.doc.paper.get_size() + + max_width, max_height = \ + self.canvas.get_report_height_width() + + if scaled_report_to != "width": + #calculate the width of the report + max_width += self.doc.report_opts.littleoffset + max_width += self.doc.paper.get_left_margin() + max_width += self.doc.paper.get_right_margin() + + #calculate the width of the title + title_width = self.canvas.title.width + title_width += self.doc.paper.get_left_margin() + title_width += self.doc.paper.get_right_margin() + title_width += self.doc.report_opts.littleoffset + max_width = max(title_width, max_width) + + size.set_width(max_width) + + if scaled_report_to != "height": + #calculate the height of the report + max_height += self.doc.paper.get_top_margin() + max_height += self.doc.paper.get_bottom_margin() + max_height += self.doc.report_opts.littleoffset + size.set_height(max_height) + + return scale + + + def __paginate_x_offsets(self, colsperpage): + """ Go through the boxes and get the x page offsets """ + #fix soon. should not use .level + liloffset = self.doc.report_opts.littleoffset + x_page_offsets = {0:0} #change me to [] ??? + for box in self.boxes: + x_index = box.level[0] + x_page = x_index / colsperpage + if x_page not in x_page_offsets and x_index % colsperpage == 0: + x_page_offsets[x_page] = box.x_cm - liloffset + if x_page >= self.x_pages: + self.x_pages = x_page+1 + return x_page_offsets + + def __paginate_y_pages(self, colsperpage, x_page_offsets): + """ Go through the boxes and put each one in a page + note that the self.boxes needs to be sorted by .y_cm """ + page_y_top = [0] + page_y_height = [self.doc.get_usable_height()] + liloffset = self.doc.report_opts.littleoffset + + for box in self.boxes: + #check to see if this box cross over to the next (y) page + height = box.y_cm + liloffset + box.height + box.shadow/2 + + if height > page_y_height[-1]: + #we went off the end + page_y_height.append(box.y_cm - liloffset + page_y_height[0]) + page_y_top.append(box.y_cm - liloffset) + self.y_pages = len(page_y_height) + + #Calculate my (x) page + #fix soon. should not use .level + x_page = box.level[0] / colsperpage + + self.__add_box_to_page(x_page, self.y_pages-1, + x_page_offsets[x_page], + page_y_top[self.y_pages-1], + box) + #if not self.__pages.has_key((x_page, self.y_pages-1)): + # #Add the new page into the dictionary + # self.__new_page(x_page, self.y_pages-1, + # ) + # + ##Add the box into the page + #self.__pages[x_page, self.y_pages-1].add_box(box) + return page_y_top + + def __paginate_note(self, x_page_offsets, page_y_top): + """ Put the note on first. it can be overwritten by other + boxes but it CAN NOT overwrite a box. """ + x_page, y_page = self.note.set_on_page(self) + if not self.__pages.has_key((x_page, y_page)): + #Add the new page into the dictionary + self.__new_page(x_page, y_page, + x_page_offsets[x_page], page_y_top[y_page]) + #Add the box into the page + self.__pages[x_page, y_page].boxes.insert(0, self.note) + self.note.doc = self.doc + self.note.page = self + + def __paginate_lines(self, x_page_offsets, page_y_top): + """ Step three go through the lines and put each in page(s) """ + for line in self.lines: + pages = [] + #if type(line.start) == type([]): + pages = [] + end = line.start + line.end + #else: + # end = [line.start] + line.end + # pages = [] + + start_x_page = end[0].page.x_page_num + start_y_page = end[0].page.y_page_num + end_y_page = end[0].page.y_page_num + + for box in end: + x_page = box.page.x_page_num + y_page = box.page.y_page_num + if (x_page, y_page) not in pages: + if not self.__pages.has_key((x_page, y_page)): + #Add the new page into the dictionary + self.__new_page(x_page, y_page, + x_page_offsets[x_page], + page_y_top[y_page]) + self.__pages[x_page, y_page].add_line(line) + pages.append((x_page, y_page)) + + if y_page < start_y_page: + start_y_page = y_page + if y_page > end_y_page: + end_y_page = y_page + + #if len(end) = 2 & end[0].y_page = 0 & end[1].y_page = 4 + #the line will not print on y_pages 1,2,3. Fix that here. + x_page = start_x_page + for y_page in range(start_y_page, end_y_page+1): + if (x_page, y_page) not in pages: + if not self.__pages.has_key((x_page, y_page)): + #Add the new page into the dictionary + self.__new_page(x_page, y_page, + x_page_offsets[x_page], + page_y_top[y_page]) + self.__pages[x_page, y_page].add_line(line) + + def __paginate_title(self, x_page_offsets): + #step four work with the title + if self.title.boxstr == "None": + return + #x_page_offsets[page] tells me the widths I can use + if len(x_page_offsets) > 1: + title_list = self.title.text.split(" ") + title_font = self.__get_font(self.title) + #space_width = PT2CM(self.doc.string_width(title_font," ")) + + list_title = [title_list.pop(0)] + while len(title_list): + tmp = list_title[-1] + " " + title_list[0] + if PT2CM(self.doc.string_width(title_font, tmp)) > \ + x_page_offsets[1]: + list_title.append("") + if list_title[-1] != "": + list_title[-1] += " " + list_title[-1] += title_list.pop(0) + + start_page = (len(x_page_offsets) - len(list_title)) / 2 + for tmp in range(start_page): + list_title.insert(0, "") + list_title.append("") + list_title.append("") #one extra for security. doesn't hurt. + + x_page = 0 + for title in list_title: + if title == "": + x_page += 1 + continue + if not self.__pages.has_key((x_page, 0)): + #Add the new page into the dictionary + self.__new_page(x_page, 0, x_page_offsets[1], 0) + + title_part = TitleBox(self.doc, self.title.boxstr) + title_part.text = list_title[x_page] + title_part.width = x_page_offsets[1] + + #Add the box into the page + self.__pages[x_page, 0].add_box(title_part) + x_page = x_page + 1 + else: + self.title.width = self.doc.get_usable_width() + self.__pages[0, 0].add_box(self.title) + + def __paginate(self, colsperpage): + """ take the boxes on the canvas and put them into separate pages. + The boxes need to be sorted by y_cm """ + liloffset = self.doc.report_opts.littleoffset + self.__pages = {} + x_page_offsets = self.__paginate_x_offsets(colsperpage) + page_y_top = self.__paginate_y_pages(colsperpage, x_page_offsets) + + if self.note is not None: + self.__paginate_note(x_page_offsets, page_y_top) + self.__paginate_lines(x_page_offsets, page_y_top) + self.__paginate_title(x_page_offsets) + + + def paginate(self, colsperpage, one_page_report): + """ self.boxes must be sorted by box.y_cm for this to work. """ + if one_page_report: + #self.canvas.add_box(self.canvas.title) + title_part = TitleBox(self.doc, self.title.boxstr) + title_part.text = self.title.text + title_part.width = self.doc.get_usable_width() + self.add_box(title_part) + + if self.note is not None: + self.note.set_on_page(self) + self.boxes.insert(0, self.note) + self.note.doc = self.doc + self.note.page = self + else: + self.__paginate(colsperpage) + + +#------------------------------------------------------------------------ +# +# Class Box_Base +# +#------------------------------------------------------------------------ +class BoxBase(object): + """ boxes are always in/on a Page + Needed to print are: boxstr, text, x_cm, y_cm, width, height + """ + def __init__(self): + self.page = None + + #'None' will cause an error. Sub-classes will init + self.boxstr = "None" + self.text = "" + self.level = (0,) #which column/level am I in? int zero based. + self.x_cm = 0.0 + self.y_cm = 0.0 + self.width = 0.0 + self.height = 0.0 + self.shadow = 0.0 + + def scale(self, scale_amount): + """ Scale the amounts """ + self.x_cm *= scale_amount + self.y_cm *= scale_amount + self.width *= scale_amount + self.height *= scale_amount + self.shadow *= scale_amount + + def display(self): + """ display the box accounting for page x, y offsets + Ignore any box with 'None' is boxstr """ + if self.boxstr != "None": + text = '\n'.join(self.text) + xbegin = self.x_cm - self.page.page_x_offset + ybegin = self.y_cm - self.page.page_y_offset + + self.page.doc.draw_box(self.boxstr, + text, + xbegin, ybegin, + self.width, self.height) + +class TitleBox(BoxBase): + """ + Holds information about the Title that will print on a page + """ + def __init__(self, doc, boxstr): + """ initalize the title box """ + BoxBase.__init__(self) + self.doc = doc + self.boxstr = boxstr + if boxstr == "None": + return + self.cm_y = self.doc.report_opts.littleoffset + + style_sheet = self.doc.get_style_sheet() + style_name = style_sheet.get_draw_style(self.boxstr) + style_name = style_name.get_paragraph_style() + self.font = style_sheet.get_paragraph_style(style_name).get_font() + + def set_box_height_width(self): + if self.boxstr == "None": + return + #fix me. width should be the printable area + self.width = PT2CM(self.doc.string_width(self.font, self.text)) + self.height = PT2CM(self.font.get_size() * 1.2) + + def _get_names(self, persons): + """ A helper function that receives a list of persons and + returns their names in a list """ + tmp = [] + for person in persons: + tmp.append(name_displayer.display(person)) + return tmp + + def display(self): + """ display the title box. """ + if self.page.y_page_num != 0 or self.boxstr == "None": + return + if self.text != "": + self.doc.center_text(self.boxstr, self.text, + self.width/2, self.y_cm) + +class PageNumberBox(BoxBase): + """ + Calculates information about the page numbers that will print on a page + do not put in a value for PageNumberBox.text. this will be calculated for + each page """ + + def __init__(self, doc, boxstr): + """ initalize the page number box """ + BoxBase.__init__(self) + self.doc = doc + self.boxstr = boxstr + + def __calc_position(self, page): + """ calculate where I am to print on the page(s) """ + self.text = "(%d,%d)" + + style_sheet = self.doc.get_style_sheet() + style_name = style_sheet.get_draw_style(self.boxstr) + style_name = style_name.get_paragraph_style() + font = style_sheet.get_paragraph_style(style_name).get_font() + + #calcualate how much space is needed + if page.canvas.x_pages > 10: + tmp = "00" + else: + tmp = "0" + if page.canvas.y_pages > 10: + tmp += "00" + else: + tmp += "0" + + width = self.doc.string_width(font, '(,)'+tmp) + width = PT2CM(width) + self.width = width + + height = font.get_size() * 1.4 + height += 0.5/2.0 * font.get_size() #funny number(s) based upon font. + self.height = PT2CM(height) + + self.x_cm = self.doc.get_usable_width() - self.width + self.y_cm = self.doc.get_usable_height() - self.height + + + def display(self, page): + """ If this is the first time I am ran, get my position + then display the page number """ + if self.text == "": + self.__calc_position(page) + + self.doc.draw_text(self.boxstr, + self.text % (page.x_page_num+1,page.y_page_num+1), + self.x_cm, self.y_cm) + +class NoteType(object): + """ Provide the different options (corners) to place the note """ + + TOPLEFT = 0 + TOPRIGHT = 1 + BOTTOMLEFT = 2 + BOTTOMRIGHT = 3 + + _DEFAULT = BOTTOMRIGHT + + _DATAMAP = [ + (TOPLEFT, _("Top Left"), "Top Left"), + (TOPRIGHT, _("Top Right"), "Top Right"), + (BOTTOMLEFT, _("Bottom Left"), "Bottom Left"), + (BOTTOMRIGHT, _("Bottom Right"), "Bottom Right"), + ] + + def __init__(self, value, exclude=None): + """ initalize GrampsType """ + self.value = value + self.exclude = exclude + #GrampsType.__init__(self, value) + + def note_locals(self, start=0): + """ generates an int of all the options """ + for tuple in self._DATAMAP: + if tuple[0] != self.exclude: + yield tuple[0], tuple[1] + +class NoteBox(BoxBase, NoteType): + """ Box that will hold the note to display on the page """ + + def __init__(self, doc, boxstr, locale, exclude=None): + """ initalize the NoteBox """ + BoxBase.__init__(self) + NoteType.__init__(self, locale, exclude) + self.doc = doc + self.boxstr = boxstr + + def set_on_page(self, canvas): + """ set the x_cm and y_cm given + self.doc, leloffset, and title_height """ + + liloffset = self.doc.report_opts.littleoffset + #left or right side + if self.value == NoteType.BOTTOMLEFT or \ + self.value == NoteType.TOPLEFT: + self.x_cm = liloffset + else: + self.x_cm = self.doc.get_usable_width() - self.width - liloffset + #top or bottom + if self.value == NoteType.TOPRIGHT or \ + self.value == NoteType.TOPLEFT: + self.y_cm = canvas.title.height + liloffset*2 + else: + self.y_cm = self.doc.get_usable_height() - self.height - liloffset + + """ helper function for canvas.paginate(). + return the (x, y) page I want to print on """ + if self.value == NoteType.TOPLEFT: + return (0, 0) + elif self.value == NoteType.TOPRIGHT: + return (canvas.x_pages-1, 0) + elif self.value == NoteType.BOTTOMLEFT: + return (0, canvas.y_pages-1) + elif self.value == NoteType.BOTTOMRIGHT: + return (canvas.x_pages-1, canvas.y_pages-1) + + def display(self): + """ position the box and display """ + title = self.page.canvas.title + title_height = 0 + if title is not None: + title_height = title.height + text = '\n'.join(self.text) + self.doc.draw_box(self.boxstr, text, + self.x_cm, self.y_cm, + self.width, self.height) + + +#------------------------------------------------------------------------ +# +# Class Line_base +# +#------------------------------------------------------------------------ +class LineBase(object): + """ A simple line class. + self.start is the box that we are drawing a line from + self.end are the boxes that we are drawing lines to. + """ + def __init__(self, start): + self.linestr = "None" + self.start = [start] + self.end = [] + + def add_to(self, person): + """ add destination boxes to draw this line to """ + self.end.append(person) + + def display(self, page): + """ display the line. left to right line. one start, multiple end. + page will tell us what parts of the line we can print """ + if self.end == []: + return + + # y_cm and x_cm start points - take into account page offsets + #yme = self.start.y_cm + self.start.height/2 - page.page_y_offset + #if type(self.start) != type([]): + # self.start = [self.start] + start = self.start[0] + + xbegin = start.x_cm + start.width - page.page_x_offset + # out 3/4 of the way and x_cm end point(s) + x34 = xbegin + (start.doc.report_opts.col_width * 3/4) + xend = xbegin + start.doc.report_opts.col_width + + if x34 > 0: # > 0 tell us we are printing on this page. + usable_height = start.doc.get_usable_height() + #1 - Line from start box out + for box in self.start: + yme = box.y_cm + box.height/2 - page.page_y_offset + if box.page.y_page_num == page.y_page_num: + # and 0 < yme < usable_height and \ + start.doc.draw_line(self.linestr, xbegin, yme, x34, yme) + + #2 - veritcal line + mid = [] + for box in self.start + self.end: + tmp = box.y_cm + box.height/2 + mid.append(tmp) + mid.sort() + mid = [mid[0]-page.page_y_offset, mid[-1]-page.page_y_offset] + if mid[0] < 0: + mid[0] = 0 + if mid[1] > usable_height: + mid[1] = usable_height + #draw the connecting vertical line. + start.doc.draw_line(self.linestr, x34, mid[0], x34, mid[1]) + else: + x34 = 0 + + #3 - horizontal line(s) + for box in self.end: + if box.page.y_page_num == page.y_page_num: + yme = box.y_cm + box.height/2 - box.page.page_y_offset + start.doc.draw_line(self.linestr, x34, yme, xend, yme) + + +#------------------------------------------------------------------------ +# +# Class report_options +# +#------------------------------------------------------------------------ +class ReportOptions(object): + """ + A simple class to hold various report information + Calculates + the gap between persons, + the column width, for lines, + the left hand spacing for spouses (Descendant report only) + """ + + def __init__(self, doc, normal_font): + """ initalize various report variables that are used """ + self.box_pgap = PT2CM(1.25*normal_font.get_size()) #gap between persons + self.box_mgap = self.box_pgap /2 #gap between marriage information + self.box_shadow = PT2CM(9) #size of normal text + self.spouse_offset = PT2CM(doc.string_width(normal_font, "0")) + + self.col_width = PT2CM(doc.string_width(normal_font, "(000,0)")) + self.littleoffset = PT2CM(1) + + #Things that will get added later + self.max_box_width = 0 + self.max_box_height = 0 + + self.scale = 1 + + def scale_everything(self, amount): + """ Scale the amounts that are needed to generate a report """ + self.scale = amount + + self.col_width *= amount + self.littleoffset *= amount + + self.max_box_width *= amount #box_width + self.spouse_offset *= amount + self.box_shadow *= amount + +#===================================== +#"And Jesus said unto them ... , "If ye have faith as a grain of mustard +#seed, ye shall say unto this mountain, Remove hence to younder place; and +#it shall remove; and nothing shall be impossible to you." +#Romans 1:17