class AncestorTree(Report): """ AncestorTree Report """ def __init__(self, database, options, user): """ Create AncestorTree object that produces the report. The arguments are: database - the GRAMPS database instance options - instance of the Options class for this report user - a gen.user.User() instance """ Report.__init__(self, database, options, user) self.options = options self._user = user lang = options.menu.get_option_by_name('trans').get_value() self._locale = self.set_locale(lang) stdoptions.run_private_data_option(self, options.menu) stdoptions.run_living_people_option(self, options.menu, self._locale) self.database = CacheProxyDb(self.database) stdoptions.run_name_format_option(self, options.menu) self._nd = self._name_display def begin_report(self): """ This report needs the following parameters (class variables) that come in the options class. max_generations - Maximum number of generations to include. pagebbg - Whether to include page breaks between generations. dispf - Display format for the output box. scale_report - Whether to scale the report to fit the width or all. indblank - Whether to include blank pages. compress - Whether to compress chart. incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death We will 1. a canvas in its full one-page size 2. a page that we wish to print on scale up/down either or both of the above as needed/desired. almost all of this should be moved into Canvas! """ database = self.database self.connect = GUIConnect() self.connect.set__opts(self.options.menu, self._locale, self._nd) #Set up the canvas that we will print on. style_sheet = self.doc.get_style_sheet() font_normal = style_sheet.get_paragraph_style("AC2-Normal").get_font() #The canvas that we will put our report on and print off of self.canvas = Canvas(self.doc, ReportOptions(self.doc, font_normal, 'AC2-line')) self.canvas.report_opts.box_shadow *= \ self.connect.get_val('shadowscale') self.canvas.report_opts.box_pgap *= self.connect.get_val('box_Yscale') self.canvas.report_opts.box_mgap *= self.connect.get_val('box_Yscale') with self._user.progress(_('Ancestor Tree'), _('Making the Tree...'), 4) as step: #make the tree onto the canvas ## inlc_marr = self.connect.get_val("inc_marr") self.max_generations = self.connect.get_val('maxgen') tree = MakeAncestorTree(database, self.canvas) tree.start(self.connect.get_val('pid')) tree = None step() #Title title = self.connect.title_class(self.doc) center = self.database.get_person_from_gramps_id( self.connect.get_val('pid')) title.calc_title(center) self.canvas.add_title(title) #make the report as big as it wants to be. report = MakeReport(database, self.doc, self.canvas, font_normal) report.start() self.max_generations = report.get_generations() #already know report = None step() #Note? if self.connect.get_val("inc_note"): note_box = NoteBox(self.doc, "AC2-note-box", self.connect.get_val("note_place")) subst = SubstKeywords(self.database, self._locale, self._nd, None, None) note_box.text = subst.replace_and_clean( self.connect.get_val('note_disp')) self.canvas.add_note(note_box) #Now we have the report in its full size. #Do we want to scale the report? one_page = self.connect.get_val("resize_page") scale_report = self.connect.get_val("scale_tree") scale = self.canvas.scale_report(one_page, scale_report != 0, scale_report == 2) step() if scale != 1 or self.connect.get_val('shadowscale') != 1.0: self.scale_styles(scale) def write_report(self): one_page = self.connect.get_val("resize_page") #scale_report = self.connect.get_val("scale_tree") #inlc_marr = self.connect.get_val("inc_marr") inc_border = self.connect.get_val('inc_border') incblank = self.connect.get_val("inc_blank") prnnum = self.connect.get_val("inc_pagenum") ##################### #Setup page information colsperpage = self.doc.get_usable_width() colsperpage += self.canvas.report_opts.col_width colsperpage = int(colsperpage / (self.canvas.report_opts.max_box_width + self.canvas.report_opts.col_width)) colsperpage = colsperpage or 1 ##################### #Vars if prnnum: page_num_box = PageNumberBox(self.doc, 'AC2-box', self._locale) #TODO - Here ##################### #ok, everyone is now ready to print on the canvas. Paginate? self.canvas.paginate(colsperpage, one_page) ##################### #Yeah!!! #lets finally make some pages!!! ##################### pages = self.canvas.page_count(incblank) with self._user.progress(_('Ancestor Tree'), _('Printing the Tree...'), pages) as step: for page in self.canvas.page_iter_gen(incblank): self.doc.start_page() #do we need to print a border? if inc_border: page.draw_border('AC2-line') #Do we need to print the page number? if prnnum: page_num_box.display(page) #Print the individual people and lines page.display() step() self.doc.end_page() def scale_styles(self, scale): """ Scale the styles for this report. """ style_sheet = self.doc.get_style_sheet() graph_style = style_sheet.get_draw_style("AC2-box") graph_style.set_shadow(graph_style.get_shadow(), self.canvas.report_opts.box_shadow * scale) graph_style.set_line_width(graph_style.get_line_width() * scale) style_sheet.add_draw_style("AC2-box", graph_style) graph_style = style_sheet.get_draw_style("AC2-fam-box") graph_style.set_shadow(graph_style.get_shadow(), self.canvas.report_opts.box_shadow * scale) graph_style.set_line_width(graph_style.get_line_width() * scale) style_sheet.add_draw_style("AC2-fam-box", graph_style) graph_style = style_sheet.get_draw_style("AC2-note-box") #graph_style.set_shadow(graph_style.get_shadow(), # self.canvas.report_opts.box_shadow * scale) graph_style.set_line_width(graph_style.get_line_width() * scale) style_sheet.add_draw_style("AC2-note-box", graph_style) para_style = style_sheet.get_paragraph_style("AC2-Normal") font = para_style.get_font() font.set_size(font.get_size() * scale) para_style.set_font(font) style_sheet.add_paragraph_style("AC2-Normal", para_style) para_style = style_sheet.get_paragraph_style("AC2-Note") font = para_style.get_font() font.set_size(font.get_size() * scale) para_style.set_font(font) style_sheet.add_paragraph_style("AC2-Note", para_style) para_style = style_sheet.get_paragraph_style("AC2-Title") font = para_style.get_font() font.set_size(font.get_size() * scale) para_style.set_font(font) style_sheet.add_paragraph_style("AC2-Title", para_style) graph_style = GraphicsStyle() width = graph_style.get_line_width() width = width * scale graph_style.set_line_width(width) style_sheet.add_draw_style("AC2-line", graph_style) self.doc.set_style_sheet(style_sheet)
class FanChart(Report): def __init__(self, database, options, user): """ Create the FanChart object that produces the report. The arguments are: database - the Gramps database instance options - instance of the Options class for this report user - a gen.user.User instance This report needs the following parameters (class variables) that come in the options class. maxgen - Maximum number of generations to include. circle - Draw a full circle, half circle, or quarter circle. background - Background color is generation dependent or white. radial - Print radial texts roundabout or as upright as possible. draw_empty - draw background when there is no information same_style - use the same style for all generation incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death """ Report.__init__(self, database, options, user) menu = options.menu self.set_locale(options.menu.get_option_by_name('trans').get_value()) stdoptions.run_private_data_option(self, menu) stdoptions.run_living_people_option(self, menu, self._locale) self.database = CacheProxyDb(self.database) self.max_generations = menu.get_option_by_name('maxgen').get_value() self.circle = menu.get_option_by_name('circle').get_value() self.background = menu.get_option_by_name('background').get_value() self.radial = menu.get_option_by_name('radial').get_value() pid = menu.get_option_by_name('pid').get_value() self.draw_empty = menu.get_option_by_name('draw_empty').get_value() self.same_style = menu.get_option_by_name('same_style').get_value() self.center_person = self.database.get_person_from_gramps_id(pid) if self.center_person is None: raise ReportError(_("Person %s is not in the Database") % pid) self.graphic_style = [] self.text_style = [] for i in range(0, self.max_generations): self.graphic_style.append('FC-Graphic' + '%02d' % i) self.text_style.append('FC-Text' + '%02d' % i) self.calendar = 0 self.height = 0 self.map = [None] * 2**self.max_generations self.text = {} 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-1] = person_handle self.text[index-1] = self.get_info(person_handle, log2(index)) 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) def write_report(self): self.doc.start_page() self.apply_filter(self.center_person.get_handle(), 1) p_rn = self.center_person.get_primary_name().get_regular_name() if self.circle == FULL_CIRCLE: max_angle = 360.0 start_angle = 90 max_circular = 5 _x_ = self.doc.get_usable_width() / 2.0 _y_ = self.doc.get_usable_height() / 2.0 min_xy = min(_x_, _y_) elif self.circle == HALF_CIRCLE: max_angle = 180.0 start_angle = 180 max_circular = 3 _x_ = (self.doc.get_usable_width()/2.0) _y_ = self.doc.get_usable_height() min_xy = min(_x_, _y_) else: # quarter circle max_angle = 90.0 start_angle = 270 max_circular = 2 _x_ = 0 _y_ = self.doc.get_usable_height() min_xy = min(self.doc.get_usable_width(), _y_) # choose one line or two lines translation according to the width title = self._("%(generations)d Generation Fan Chart " "for %(person)s") % { 'generations' : self.max_generations, 'person' : p_rn} title_nb_lines = 1 style_sheet = self.doc.get_style_sheet() if style_sheet: p_style = style_sheet.get_paragraph_style('FC-Title') if p_style: font = p_style.get_font() if font: title_width = utils.pt2cm(self.doc.string_width(font, title)) if title_width > self.doc.get_usable_width(): title = self._( "%(generations)d Generation Fan Chart " "for\n%(person)s") % { 'generations' : self.max_generations, 'person' : p_rn} title_nb_lines = 2 if self.circle == FULL_CIRCLE or self.circle == QUAR_CIRCLE: # adjust only if full circle or 1/4 circle in landscape mode if self.doc.get_usable_height() <= self.doc.get_usable_width(): # Should be in Landscape now style_sheet = self.doc.get_style_sheet() p_style = style_sheet.get_paragraph_style('FC-Title') if p_style: font = p_style.get_font() if font: fontsize = utils.pt2cm(font.get_size()) # _y_ is vertical distance to center of circle, # move center down 1 fontsize _y_ += fontsize*title_nb_lines # min_XY is the diameter of the circle, # subtract two fontsize # so we dont draw outside bottom of the paper min_xy = min(min_xy, _y_ - 2*fontsize*title_nb_lines) if self.max_generations > max_circular: block_size = min_xy / (self.max_generations * 2 - max_circular) else: block_size = min_xy / self.max_generations # adaptation of the fonts (title and others) optimized_style_sheet = self.get_optimized_style_sheet( title, max_circular, block_size, self.same_style, not self.same_style, # if same_style, use default generated colors self.background == BACKGROUND_WHITE) if optimized_style_sheet: self.doc.set_style_sheet(optimized_style_sheet) # title mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.center_text('FC-Graphic-title', title, self.doc.get_usable_width() / 2, 0, mark) # wheel for generation in range(0, min(max_circular, self.max_generations)): self.draw_circular(_x_, _y_, start_angle, max_angle, block_size, generation) for generation in range(max_circular, self.max_generations): self.draw_radial(_x_, _y_, start_angle, max_angle, block_size, generation) self.doc.end_page() def get_info(self, person_handle, generation): """ get info about a person """ person = self.database.get_person_from_handle(person_handle) p_pn = person.get_primary_name() self.calendar = config.get('preferences.calendar-format-report') birth = get_birth_or_fallback(self.database, person) bth = "" if birth: bth = birth.get_date_object() bth = str(bth.to_calendar(self.calendar).get_year()) if bth == 0: bth = "" elif birth.get_type() != EventType.BIRTH: bth += '*' death = get_death_or_fallback(self.database, person) dth = "" if death: dth = death.get_date_object() dth = str(dth.to_calendar(self.calendar).get_year()) if dth == 0: dth = "" elif death.get_type() != EventType.DEATH: dth += '*' if bth and dth: val = "%s - %s" % (str(bth), str(dth)) elif bth: val = "* %s" % (str(bth)) elif dth: val = "+ %s" % (str(dth)) else: val = "" if generation > 7: if (p_pn.get_first_name() != "") and (p_pn.get_surname() != ""): name = p_pn.get_first_name() + " " + p_pn.get_surname() else: name = p_pn.get_first_name() + p_pn.get_surname() if (name != "") and (val != ""): string = name + ", " + val else: string = name + val return [string] elif generation == 7: if (p_pn.get_first_name() != "") and (p_pn.get_surname() != ""): name = p_pn.get_first_name() + " " + p_pn.get_surname() else: name = p_pn.get_first_name() + p_pn.get_surname() if self.circle == FULL_CIRCLE: return [name, val] elif self.circle == HALF_CIRCLE: return [name, val] else: if (name != "") and (val != ""): string = name + ", " + val else: string = name + val return [string] elif generation == 6: if self.circle == FULL_CIRCLE: return [p_pn.get_first_name(), p_pn.get_surname(), val] elif self.circle == HALF_CIRCLE: return [p_pn.get_first_name(), p_pn.get_surname(), val] else: if (p_pn.get_first_name() != "") and (p_pn.get_surname() != ""): name = p_pn.get_first_name() + " " + p_pn.get_surname() else: name = p_pn.get_first_name() + p_pn.get_surname() return [name, val] else: return [p_pn.get_first_name(), p_pn.get_surname(), val] def get_max_width_for_circles(self, rad1, rad2, max_centering_proportion): r""" (the "r" in the above line is to keep pylint happy) __ /__\ <- compute the line width which is drawable between 2 circles. / _ \ max_centering_proportion : 0, touching the circle1, 1, | |_| | touching the circle2, 0.5 : middle between the 2 circles | | \ / \__/ basically, max_centering_proportion is max_centering_proportion/nb_lines """ # radius at the center of the 2 circles rmid = rad2 - (rad2-rad1)*max_centering_proportion return sin(acos(rmid/rad2)) * rad2 * 2 def get_max_width_for_circles_line(self, rad1, rad2, line, nb_lines, centering=False): r""" (the "r" in the above line is to keep pylint happy) __ /__\ <- compute the line width which is drawable between 2 circles. / _ \ instead of a max_centering_proportion, you get a | |_| | line/nb_lines position. (we suppose that lines have the | | same heights.) for example, if you've 2 lines to draw, \ / line 2 max width is at the 2/3 between the 2 circles \__/ """ if centering: return self.get_max_width_for_circles(rad1, rad2, 1.0) else: return self.get_max_width_for_circles(rad1, rad2, line/float(nb_lines+1)) def get_optimized_font_size_for_text(self, rad1, rad2, text, font, centering=False): """ a text can be several lines find the font size equals or lower than font.get_size() which fit between rad1 and rad2 to display the text. centering is a special case when you've the full circle available to draw the text in the middle of it """ min_font_size = font.get_size() i = 1 nb_lines = len(text) for line in text: font_size = self.get_optimized_font_size( line, font, self.get_max_width_for_circles_line(rad1, rad2, i, nb_lines, centering)) i += 1 if min_font_size > font_size: min_font_size = font_size return min_font_size def get_optimized_font_size(self, line, font, max_width): """ for a given width, guess the best font size which is equals or smaller than font which make line fit into max_width """ test_font = FontStyle(font) width = utils.pt2cm(self.doc.string_width(test_font, line)) while width > max_width and test_font.get_size() > 1: test_font.set_size(test_font.get_size() -1) width = utils.pt2cm(self.doc.string_width(test_font, line)) return test_font.get_size() def get_optimized_style_sheet(self, title, max_circular, block_size, map_style_from_single, map_paragraphs_colors_to_graphics, make_background_white): """ returns an optimized (modified) style sheet which make fanchart look nicer """ new_style_sheet = self.doc.get_style_sheet() if not new_style_sheet: return self.doc.get_style_sheet() # update title font size pstyle_name = 'FC-Title' p_style = new_style_sheet.get_paragraph_style(pstyle_name) if p_style: title_font = p_style.get_font() if title_font: title_width = utils.pt2cm( self.doc.string_multiline_width(title_font, title)) while (title_width > self.doc.get_usable_width() and title_font.get_size() > 1): title_font.set_size(title_font.get_size()-1) title_width = utils.pt2cm( self.doc.string_multiline_width(title_font, title)) new_style_sheet.add_paragraph_style(pstyle_name, p_style) # biggest font allowed is the one of the fist generation, after, # always lower than the previous one p_style = new_style_sheet.get_paragraph_style(self.text_style[0]) font = None if p_style: font = p_style.get_font() if font: previous_generation_font_size = font.get_size() for generation in range(0, self.max_generations): gstyle_name = self.graphic_style[generation] pstyle_name = self.text_style[generation] g_style = new_style_sheet.get_draw_style(gstyle_name) # p_style is a copy of 'FC-Text' - use different style # to be able to auto change some fonts for some generations if map_style_from_single: p_style = new_style_sheet.get_paragraph_style('FC-Text') else: p_style = new_style_sheet.get_paragraph_style(pstyle_name) if g_style and p_style: # set graphic colors to paragraph colors, # while it's functionnaly # the same for fanchart or make backgrounds white if make_background_white: g_style.set_fill_color((255, 255, 255)) new_style_sheet.add_draw_style(gstyle_name, g_style) elif map_paragraphs_colors_to_graphics: pstyle = new_style_sheet.get_paragraph_style( pstyle_name) if pstyle: g_style.set_fill_color( pstyle.get_background_color()) new_style_sheet.add_draw_style(gstyle_name, g_style) # adapt font size if too big segments = 2**generation if generation < min(max_circular, self.max_generations): # adpatation for circular fonts rad1, rad2 = self.get_circular_radius( block_size, generation, self.circle) font = p_style.get_font() if font: min_font_size = font.get_size() # find the smallest font required for index in range(segments - 1, 2*segments - 1): if self.map[index]: font_size = \ self.get_optimized_font_size_for_text( rad1, rad2, self.text[index], p_style.get_font(), (self.circle == FULL_CIRCLE and generation == 0) ) if font_size < min_font_size: min_font_size = font_size font.set_size(min(previous_generation_font_size, min_font_size)) else: # adaptation for radial fonts # find the largest string for the generation longest_line = "" longest_width = 0 for index in range(segments - 1, 2*segments - 1): if self.map[index]: for line in self.text[index]: width = utils.pt2cm( self.doc.string_multiline_width( p_style.get_font(), line)) if width > longest_width: longest_line = line longest_width = width # determine maximum width allowed for this generation rad1, rad2 = self.get_radial_radius( block_size, generation, self.circle) max_width = rad2 - rad1 # reduce the font so that longest_width # fit into max_width font = p_style.get_font() if font: font.set_size(min(previous_generation_font_size, self.get_optimized_font_size( longest_line, p_style.get_font(), max_width))) # redefine the style new_style_sheet.add_paragraph_style(pstyle_name, p_style) font = p_style.get_font() if font: previous_generation_font_size = font.get_size() # finished return new_style_sheet def draw_circular(self, _x_, _y_, start_angle, max_angle, size, generation): segments = 2**generation delta = max_angle / segments end_angle = start_angle text_angle = start_angle - 270 + (delta / 2.0) rad1, rad2 = self.get_circular_radius(size, generation, self.circle) graphic_style = self.graphic_style[generation] for index in range(segments - 1, 2*segments - 1): start_angle = end_angle end_angle = start_angle + delta (_xc, _yc) = draw_wedge(self.doc, graphic_style, _x_, _y_, rad2, start_angle, end_angle, self.map[index] or self.draw_empty, rad1) if self.map[index]: if (generation == 0) and self.circle == FULL_CIRCLE: _yc = _y_ person = self.database.get_person_from_handle(self.map[index]) mark = utils.get_person_mark(self.database, person) self.doc.rotate_text(graphic_style, self.text[index], _xc, _yc, text_angle, mark) text_angle += delta def get_radial_radius(self, size, generation, circle): """ determine the radius """ if circle == FULL_CIRCLE: rad1 = size * ((generation * 2) - 5) rad2 = size * ((generation * 2) - 3) elif circle == HALF_CIRCLE: rad1 = size * ((generation * 2) - 3) rad2 = size * ((generation * 2) - 1) else: # quarter circle rad1 = size * ((generation * 2) - 2) rad2 = size * (generation * 2) return rad1, rad2 def get_circular_radius(self, size, generation, circle): """ determine the radius """ return size * generation, size * (generation + 1) def draw_radial(self, _x_, _y_, start_angle, max_angle, size, generation): segments = 2**generation delta = max_angle / segments end_angle = start_angle text_angle = start_angle - delta / 2.0 graphic_style = self.graphic_style[generation] rad1, rad2 = self.get_radial_radius(size, generation, self.circle) for index in range(segments - 1, 2*segments - 1): start_angle = end_angle end_angle = start_angle + delta (_xc, _yc) = draw_wedge(self.doc, graphic_style, _x_, _y_, rad2, start_angle, end_angle, self.map[index] or self.draw_empty, rad1) text_angle += delta if self.map[index]: person = self.database.get_person_from_handle(self.map[index]) mark = utils.get_person_mark(self.database, person) if (self.radial == RADIAL_UPRIGHT and (start_angle >= 90) and (start_angle < 270)): self.doc.rotate_text(graphic_style, self.text[index], _xc, _yc, text_angle + 180, mark) else: self.doc.rotate_text(graphic_style, self.text[index], _xc, _yc, text_angle, mark)
class AncestorReport(Report): """ Ancestor Report class """ def __init__(self, database, options, user): """ Create the AncestorReport object that produces the Ahnentafel report. The arguments are: database - the Gramps database instance options - instance of the Options class for this report user - a gen.user.User() instance 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. name_format - Preferred format to display names incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death """ Report.__init__(self, database, options, user) self.map = {} menu = options.menu lang = menu.get_option_by_name('trans').get_value() rlocale = self.set_locale(lang) stdoptions.run_private_data_option(self, menu) stdoptions.run_living_people_option(self, menu, rlocale) self.database = CacheProxyDb(self.database) self.max_generations = menu.get_option_by_name('maxgen').get_value() self.pgbrk = menu.get_option_by_name('pagebbg').get_value() self.opt_namebrk = menu.get_option_by_name('namebrk').get_value() pid = menu.get_option_by_name('pid').get_value() self.center_person = self.database.get_person_from_gramps_id(pid) if (self.center_person == None): raise ReportError(_("Person %s is not in the Database") % pid) stdoptions.run_name_format_option(self, menu) self.__narrator = Narrator(self.database, use_fulldate=True, nlocale=rlocale) def apply_filter(self, person_handle, index, generation=1): """ Recursive function to walk back all parents of the current person. When max_generations are hit, we stop the traversal. """ # check for end of the current recursion level. This happens # if the person handle is None, or if the max_generations is hit if not person_handle or generation > self.max_generations: return # store the person in the map based off their index number # which is passed to the routine. self.map[index] = person_handle # retrieve the Person instance from the database from the # passed person_handle and find the parents from the list. # Since this report is for natural parents (birth parents), # we have to handle that parents may not person = self.database.get_person_from_handle(person_handle) if person is None: return father_handle = None mother_handle = None for family_handle in person.get_parent_family_handle_list(): family = self.database.get_family_from_handle(family_handle) # filter the child_ref_list to find the reference that matches # the passed person. There should be exactly one, but there is # nothing that prevents the same child in the list multiple times. ref = [ c for c in family.get_child_ref_list() if c.get_reference_handle() == person_handle ] if ref: # If the father_handle is not defined and the relationship is # BIRTH, then we have found the birth father. Same applies to # the birth mother. If for some reason, the we have multiple # people defined as the birth parents, we will select based on # priority in the list if not father_handle and \ ref[0].get_father_relation() == ChildRefType.BIRTH: father_handle = family.get_father_handle() if not mother_handle and \ ref[0].get_mother_relation() == ChildRefType.BIRTH: mother_handle = family.get_mother_handle() # Recursively call the function. It is okay if the handle is None, # since routine handles a handle of None self.apply_filter(father_handle, index * 2, generation + 1) self.apply_filter(mother_handle, (index * 2) + 1, generation + 1) def write_report(self): """ The routine the actually creates the report. At this point, the document is opened and ready for writing. """ # Call apply_filter to build the self.map array of people in the # database that match the ancestry. self.apply_filter(self.center_person.get_handle(), 1) # Write the title line. Set in INDEX marker so that this section will be # identified as a major category if this is included in a Book report. name = self._name_display.display_formal(self.center_person) # feature request 2356: avoid genitive form title = self._("Ahnentafel Report for %s") % name mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.start_paragraph("AHN-Title") self.doc.write_text(title, mark) self.doc.end_paragraph() # get the entries out of the map, and sort them. generation = 0 for key in sorted(self.map): # check the index number to see if we need to start a new generation if generation == log2(key): # generate a page break if requested if self.pgbrk and generation > 0: self.doc.page_break() generation += 1 # Create the Generation title, set an index marker gen_text = self._("Generation %d") % generation mark = None # don't need any with no page breaks if self.pgbrk: mark = IndexMark(gen_text, INDEX_TYPE_TOC, 2) self.doc.start_paragraph("AHN-Generation") self.doc.write_text(gen_text, mark) self.doc.end_paragraph() # Build the entry self.doc.start_paragraph("AHN-Entry", "%d." % key) person = self.database.get_person_from_handle(self.map[key]) if person is None: continue name = self._name_display.display(person) mark = utils.get_person_mark(self.database, person) # write the name in bold self.doc.start_bold() self.doc.write_text(name.strip(), mark) self.doc.end_bold() # terminate with a period if it is not already terminated. # This can happen if the person's name ends with something 'Jr.' if name[-1:] == '.': self.doc.write_text(" ") else: self.doc.write_text(". ") # Add a line break if requested (not implemented yet) if self.opt_namebrk: self.doc.write_text('\n') self.__narrator.set_subject(person) self.doc.write_text(self.__narrator.get_born_string()) self.doc.write_text(self.__narrator.get_baptised_string()) self.doc.write_text(self.__narrator.get_christened_string()) self.doc.write_text(self.__narrator.get_died_string()) self.doc.write_text(self.__narrator.get_buried_string()) self.doc.end_paragraph()
class EndOfLineReport(Report): """ EndOfLine Report """ def __init__(self, database, options, user): """ Create the EndOfLineReport object that produces the report. The arguments are: database - the Gramps database instance options - instance of the Options class for this report user - a gen.user.User() instance This report needs the following parameters (class variables) that come in the options class. name_format - Preferred format to display names incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death """ Report.__init__(self, database, options, user) menu = options.menu self.set_locale(menu.get_option_by_name('trans').get_value()) stdoptions.run_date_format_option(self, menu) stdoptions.run_private_data_option(self, menu) stdoptions.run_living_people_option(self, menu, self._locale) self.database = CacheProxyDb(self.database) pid = menu.get_option_by_name('pid').get_value() self.center_person = self.database.get_person_from_gramps_id(pid) if self.center_person is None: raise ReportError(_("Person %s is not in the Database") % pid) stdoptions.run_name_format_option(self, menu) # eol_map is a map whose: # keys are the generations of the people # values are a map whose: # keys are person handles # values are an array whose: # elements are an array of ancestor person handles that link # the eol person handle to the person or interest # eol_map[generation][person_handle][pedigree_idx][ancestor_handle_idx] # # There is an array of pedigrees because one person could show up twice # in one generation (descendants marrying). Most people only have one # pedigree. # # eol_map is populated by get_eol() which calls itself recursively. self.eol_map = {} self.get_eol(self.center_person, 1, []) def get_eol(self, person, gen, pedigree): """ Recursively find the end of the line for each person """ person_handle = person.get_handle() new_pedigree = list(pedigree) + [person_handle] person_is_eol = False families = person.get_parent_family_handle_list() if person_handle in pedigree: # This is a severe error! # It indicates a loop in ancestry: A -> B -> A person_is_eol = True elif not families: person_is_eol = True else: for family_handle in families: family = self.database.get_family_from_handle(family_handle) father_handle = family.get_father_handle() mother_handle = family.get_mother_handle() if father_handle: father = self.database.get_person_from_handle(father_handle) self.get_eol(father, gen+1, new_pedigree) if mother_handle: mother = self.database.get_person_from_handle(mother_handle) self.get_eol(mother, gen+1, new_pedigree) if not father_handle or not mother_handle: person_is_eol = True if person_is_eol: # This person is the end of a line if gen not in self.eol_map: self.eol_map[gen] = {} if person_handle not in self.eol_map[gen]: self.eol_map[gen][person_handle] = [] self.eol_map[gen][person_handle].append(new_pedigree) def write_report(self): """ The routine that actually creates the report. At this point, the document is opened and ready for writing. """ pname = self._name_display.display(self.center_person) self.doc.start_paragraph("EOL-Title") # feature request 2356: avoid genitive form title = self._("End of Line Report for %s") % pname mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.write_text(title, mark) self.doc.end_paragraph() self.doc.start_paragraph("EOL-Subtitle") # feature request 2356: avoid genitive form title = self._("All the ancestors of %s who are missing a parent" ) % pname self.doc.write_text(title) self.doc.end_paragraph() self.doc.start_table('EolTable', 'EOL-Table') for generation, handles in sorted(self.eol_map.items()): self.write_generation_row(generation) for person_handle, pedigrees in handles.items(): self.write_person_row(person_handle) list(map(self.write_pedigree_row, pedigrees)) self.doc.end_table() def write_generation_row(self, generation): """ Write out a row in the table showing the generation. """ self.doc.start_row() self.doc.start_cell('EOL_GenerationCell', 2) self.doc.start_paragraph('EOL-Generation') self.doc.write_text(self._("Generation %d") % generation) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() def write_person_row(self, person_handle): """ Write a row in the table with information about the given person. """ person = self.database.get_person_from_handle(person_handle) name = self._name_display.display(person) mark = utils.get_person_mark(self.database, person) birth_date = "" birth_ref = person.get_birth_ref() if birth_ref: event = self.database.get_event_from_handle(birth_ref.ref) birth_date = self._get_date(event.get_date_object()) death_date = "" death_ref = person.get_death_ref() if death_ref: event = self.database.get_event_from_handle(death_ref.ref) death_date = self._get_date(event.get_date_object()) dates = '' if birth_date or death_date: dates = " (%(birth_date)s - %(death_date)s)" % { 'birth_date' : birth_date, 'death_date' : death_date} self.doc.start_row() self.doc.start_cell('EOL-TableCell', 2) self.doc.start_paragraph('EOL-Normal') self.doc.write_text(name, mark) self.doc.write_text(dates) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() def write_pedigree_row(self, pedigree): """ Write a row in the table with with the person's family line. pedigree is an array containing the names of the people in the pedigree """ names = [] for person_handle in pedigree: person = self.database.get_person_from_handle(person_handle) names.append(self._name_display.display(person)) text = " -- ".join(names) self.doc.start_row() self.doc.start_cell('EOL-TableCell') self.doc.end_cell() self.doc.start_cell('EOL-TableCell') self.doc.start_paragraph('EOL-Pedigree') self.doc.write_text(text) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row()
class KinshipReport(Report): """ Kinship Report """ def __init__(self, database, options, user): """ Create the KinshipReport object that produces the report. The arguments are: database - the GRAMPS database instance options - instance of the Options class for this report user - a gen.user.User() instance This report needs the following parameters (class variables) that come in the options class. maxdescend - Maximum generations of descendants to include. maxascend - Maximum generations of ancestors to include. incspouses - Whether to include spouses. inccousins - Whether to include cousins. incaunts - Whether to include aunts/uncles/nephews/nieces. pid - The Gramps ID of the center person for the report. name_format - Preferred format to display names incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death """ Report.__init__(self, database, options, user) menu = options.menu lang = menu.get_option_by_name('trans').get_value() rlocale = self.set_locale(lang) stdoptions.run_private_data_option(self, menu) stdoptions.run_living_people_option(self, menu, rlocale) self.database = CacheProxyDb(self.database) self.__db = self.database self.max_descend = menu.get_option_by_name('maxdescend').get_value() self.max_ascend = menu.get_option_by_name('maxascend').get_value() self.inc_spouses = menu.get_option_by_name('incspouses').get_value() self.inc_cousins = menu.get_option_by_name('inccousins').get_value() self.inc_aunts = menu.get_option_by_name('incaunts').get_value() pid = menu.get_option_by_name('pid').get_value() self.person = self.database.get_person_from_gramps_id(pid) if self.person is None: raise ReportError(_("Person %s is not in the Database") % pid) stdoptions.run_name_format_option(self, menu) self.rel_calc = get_relationship_calculator(reinit=True, clocale=rlocale) self.kinship_map = {} self.spouse_map = {} def write_report(self): """ The routine the actually creates the report. At this point, the document is opened and ready for writing. """ pname = self._name_display.display(self.person) self.doc.start_paragraph("KIN-Title") # feature request 2356: avoid genitive form title = self._("Kinship Report for %s") % pname mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.write_text(title, mark) self.doc.end_paragraph() if self.inc_spouses: spouse_handles = self.get_spouse_handles(self.person.get_handle()) if spouse_handles: self.write_people(self._("Spouses"), spouse_handles) # Collect all descendants of the person self.traverse_down(self.person.get_handle(), 0, 1) # Collect all ancestors/aunts/uncles/nephews/cousins of the person self.traverse_up(self.person.get_handle(), 1, 0) # Write Kin for Ga, Gbs in self.kinship_map.items(): for Gb in Gbs: # To understand these calculations, see: # http://en.wikipedia.org/wiki/Cousin#Mathematical_definitions _x_ = min(Ga, Gb) _y_ = abs(Ga - Gb) # Skip unrequested people if _x_ == 1 and _y_ > 0 and not self.inc_aunts: continue elif _x_ > 1 and not self.inc_cousins: continue get_rel_str = self.rel_calc.get_plural_relationship_string title = get_rel_str(Ga, Gb, in_law_b=False) self.write_people(self._(title), self.kinship_map[Ga][Gb]) if (self.inc_spouses and Ga in self.spouse_map and Gb in self.spouse_map[Ga]): title = get_rel_str(Ga, Gb, in_law_b=True) self.write_people(self._(title), self.spouse_map[Ga][Gb]) def traverse_down(self, person_handle, Ga, Gb, skip_handle=None): """ Populate a map of arrays containing person handles for the descendants of the passed person. This function calls itself recursively until it reaches max_descend. Parameters: person_handle: the handle of the person to go to next Ga: The number of generations from the main person to the common ancestor. This should be incremented when going up generations, and left alone when going down generations. Gb: The number of generations from this person (person_handle) to the common ancestor. This should be incremented when going down generations and set back to zero when going up generations. skip_handle: an optional handle to skip when going down. This is useful to skip the descendant that brought you this generation in the first place. """ for child_handle in self.get_children_handles(person_handle): if child_handle != skip_handle: self.add_kin(child_handle, Ga, Gb) if self.inc_spouses: for spouse_handle in self.get_spouse_handles(child_handle): self.add_spouse(spouse_handle, Ga, Gb) if Gb < self.max_descend: self.traverse_down(child_handle, Ga, Gb+1) def traverse_up(self, person_handle, Ga, Gb): """ Populate a map of arrays containing person handles for the ancestors of the passed person. This function calls itself recursively until it reaches max_ascend. Parameters: person_handle: the handle of the person to go to next Ga: The number of generations from the main person to the common ancestor. This should be incremented when going up generations, and left alone when going down generations. Gb: The number of generations from this person (person_handle) to the common ancestor. This should be incremented when going down generations and set back to zero when going up generations. """ parent_handles = self.get_parent_handles(person_handle) for parent_handle in parent_handles: self.add_kin(parent_handle, Ga, Gb) self.traverse_down(parent_handle, Ga, Gb+1, person_handle) if Ga < self.max_ascend: self.traverse_up(parent_handle, Ga+1, 0) def add_kin(self, person_handle, Ga, Gb): """ Add a person handle to the kin map. """ if Ga not in self.kinship_map: self.kinship_map[Ga] = {} if Gb not in self.kinship_map[Ga]: self.kinship_map[Ga][Gb] = [] if person_handle not in self.kinship_map[Ga][Gb]: self.kinship_map[Ga][Gb].append(person_handle) def add_spouse(self, spouse_handle, Ga, Gb): """ Add a person handle to the spouse map. """ if Ga not in self.spouse_map: self.spouse_map[Ga] = {} if Gb not in self.spouse_map[Ga]: self.spouse_map[Ga][Gb] = [] if spouse_handle not in self.spouse_map[Ga][Gb]: self.spouse_map[Ga][Gb].append(spouse_handle) def get_parent_handles(self, person_handle): """ Return an array of handles for all the parents of the given person handle. """ parent_handles = [] person = self.__db.get_person_from_handle(person_handle) family_handle = person.get_main_parents_family_handle() if family_handle: family = self.__db.get_family_from_handle(family_handle) father_handle = family.get_father_handle() if father_handle: parent_handles.append(father_handle) mother_handle = family.get_mother_handle() if mother_handle: parent_handles.append(mother_handle) return parent_handles def get_spouse_handles(self, person_handle): """ Return an array of handles for all the spouses of the given person handle. """ spouses = [] person = self.__db.get_person_from_handle(person_handle) for family_handle in person.get_family_handle_list(): family = self.__db.get_family_from_handle(family_handle) father_handle = family.get_father_handle() mother_handle = family.get_mother_handle() spouse_handle = None if mother_handle and father_handle == person_handle: spouse_handle = mother_handle elif father_handle and mother_handle == person_handle: spouse_handle = father_handle if spouse_handle and spouse_handle not in spouses: spouses.append(spouse_handle) return spouses def get_children_handles(self, person_handle): """ Return an array of handles for all the children of the given person handle. """ children = [] person = self.__db.get_person_from_handle(person_handle) for family_handle in person.get_family_handle_list(): family = self.__db.get_family_from_handle(family_handle) for child_ref in family.get_child_ref_list(): children.append(child_ref.get_reference_handle()) return children def write_people(self, title, people_handles): """ Write information about a group of people - including the title. """ cap_title = title[0].upper() + title[1:] subtitle = "%s (%d)" % (cap_title, len(people_handles)) self.doc.start_paragraph("KIN-Subtitle") mark = IndexMark(cap_title, INDEX_TYPE_TOC, 2) self.doc.write_text(subtitle, mark) self.doc.end_paragraph() list(map(self.write_person, people_handles)) def write_person(self, person_handle): """ Write information about the given person. """ person = self.database.get_person_from_handle(person_handle) name = self._name_display.display(person) mark = utils.get_person_mark(self.database, person) birth_date = "" birth = get_birth_or_fallback(self.database, person) if birth: birth_date = self._get_date(birth.get_date_object()) death_date = "" death = get_death_or_fallback(self.database, person) if death: death_date = self._get_date(death.get_date_object()) dates = '' if birth_date or death_date: dates = self._(" (%(birth_date)s - %(death_date)s)" ) % {'birth_date' : birth_date, 'death_date' : death_date} self.doc.start_paragraph('KIN-Normal') self.doc.write_text(name, mark) self.doc.write_text(dates) self.doc.end_paragraph()
class DescendantReport(Report): """ Descendant report """ def __init__(self, database, options, user): """ Create the DescendantReport object that produces the report. The arguments are: database - the Gramps database instance options - instance of the Options class for this report user - a gen.user.User() instance This report needs the following parameters (class variables) that come in the options class. gen - Maximum number of generations to include. name_format - Preferred format to display names dups - Whether to include duplicate descendant trees incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death inc_id - Whether to include Gramps IDs """ Report.__init__(self, database, options, user) menu = options.menu self.set_locale(menu.get_option_by_name('trans').get_value()) stdoptions.run_date_format_option(self, menu) stdoptions.run_private_data_option(self, menu) stdoptions.run_living_people_option(self, menu, self._locale) self.database = CacheProxyDb(self.database) self.max_generations = menu.get_option_by_name('gen').get_value() self.want_ids = menu.get_option_by_name('inc_id').get_value() pid = menu.get_option_by_name('pid').get_value() self.center_person = self.database.get_person_from_gramps_id(pid) if self.center_person is None: raise ReportError(_("Person %s is not in the Database") % pid) #Initialize the Printinfo class self._showdups = menu.get_option_by_name('dups').get_value() numbering = menu.get_option_by_name('numbering').get_value() if numbering == "Simple": obj = PrintSimple(self._showdups) elif numbering == "Henry": obj = PrintHenry() elif numbering == "Modified Henry": obj = PrintHenry(modified=True) elif numbering == "d'Aboville": obj = PrintDAboville() elif numbering == "de Villiers/Pama": obj = PrintVilliers() elif numbering == "Meurgey de Tupigny": obj = PrintMeurgey() else: raise AttributeError("no such numbering: '%s'" % numbering) marrs = menu.get_option_by_name('marrs').get_value() divs = menu.get_option_by_name('divs').get_value() stdoptions.run_name_format_option(self, menu) self.obj_print = Printinfo(self.doc, self.database, obj, marrs, divs, self._name_display, self._locale, self.want_ids) def write_report(self): self.doc.start_paragraph("DR-Title") name = self._name_display.display(self.center_person) # feature request 2356: avoid genitive form title = self._("Descendants of %s") % name mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.write_text(title, mark) self.doc.end_paragraph() recurse = RecurseDown(self.max_generations, self.database, self.obj_print, self._showdups, self._locale) recurse.recurse(1, self.center_person, None)
class KinshipReport(Report): """ Kinship Report """ def __init__(self, database, options, user): """ Create the KinshipReport object that produces the report. The arguments are: database - the Gramps database instance options - instance of the Options class for this report user - a gen.user.User() instance This report needs the following parameters (class variables) that come in the options class. maxdescend - Maximum generations of descendants to include. maxascend - Maximum generations of ancestors to include. incspouses - Whether to include spouses. inccousins - Whether to include cousins. incaunts - Whether to include aunts/uncles/nephews/nieces. pid - The Gramps ID of the center person for the report. name_format - Preferred format to display names incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death """ Report.__init__(self, database, options, user) menu = options.menu lang = menu.get_option_by_name('trans').get_value() rlocale = self.set_locale(lang) stdoptions.run_private_data_option(self, menu) stdoptions.run_living_people_option(self, menu, rlocale) self.database = CacheProxyDb(self.database) self.__db = self.database self.max_descend = menu.get_option_by_name('maxdescend').get_value() self.max_ascend = menu.get_option_by_name('maxascend').get_value() self.inc_spouses = menu.get_option_by_name('incspouses').get_value() self.inc_cousins = menu.get_option_by_name('inccousins').get_value() self.inc_aunts = menu.get_option_by_name('incaunts').get_value() pid = menu.get_option_by_name('pid').get_value() self.person = self.database.get_person_from_gramps_id(pid) if self.person is None: raise ReportError(_("Person %s is not in the Database") % pid) stdoptions.run_name_format_option(self, menu) self.rel_calc = get_relationship_calculator(reinit=True, clocale=rlocale) self.kinship_map = {} self.spouse_map = {} def write_report(self): """ The routine the actually creates the report. At this point, the document is opened and ready for writing. """ pname = self._name_display.display(self.person) self.doc.start_paragraph("KIN-Title") # feature request 2356: avoid genitive form title = self._("Kinship Report for %s") % pname mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.write_text(title, mark) self.doc.end_paragraph() if self.inc_spouses: spouse_handles = self.get_spouse_handles(self.person.get_handle()) if spouse_handles: self.write_people(self._("Spouses"), spouse_handles) # Collect all descendants of the person self.traverse_down(self.person.get_handle(), 0, 1) # Collect all ancestors/aunts/uncles/nephews/cousins of the person self.traverse_up(self.person.get_handle(), 1, 0) # Write Kin for Ga, Gbs in self.kinship_map.items(): for Gb in Gbs: # To understand these calculations, see: # http://en.wikipedia.org/wiki/Cousin#Mathematical_definitions _x_ = min(Ga, Gb) _y_ = abs(Ga - Gb) # Skip unrequested people if _x_ == 1 and _y_ > 0 and not self.inc_aunts: continue elif _x_ > 1 and not self.inc_cousins: continue get_rel_str = self.rel_calc.get_plural_relationship_string title = get_rel_str(Ga, Gb, in_law_b=False) self.write_people(self._(title), self.kinship_map[Ga][Gb]) if (self.inc_spouses and Ga in self.spouse_map and Gb in self.spouse_map[Ga]): title = get_rel_str(Ga, Gb, in_law_b=True) self.write_people(self._(title), self.spouse_map[Ga][Gb]) def traverse_down(self, person_handle, Ga, Gb, skip_handle=None): """ Populate a map of arrays containing person handles for the descendants of the passed person. This function calls itself recursively until it reaches max_descend. Parameters: person_handle: the handle of the person to go to next Ga: The number of generations from the main person to the common ancestor. This should be incremented when going up generations, and left alone when going down generations. Gb: The number of generations from this person (person_handle) to the common ancestor. This should be incremented when going down generations and set back to zero when going up generations. skip_handle: an optional handle to skip when going down. This is useful to skip the descendant that brought you this generation in the first place. """ for child_handle in self.get_children_handles(person_handle): if child_handle != skip_handle: self.add_kin(child_handle, Ga, Gb) if self.inc_spouses: for spouse_handle in self.get_spouse_handles(child_handle): self.add_spouse(spouse_handle, Ga, Gb) if Gb < self.max_descend: self.traverse_down(child_handle, Ga, Gb + 1) def traverse_up(self, person_handle, Ga, Gb): """ Populate a map of arrays containing person handles for the ancestors of the passed person. This function calls itself recursively until it reaches max_ascend. Parameters: person_handle: the handle of the person to go to next Ga: The number of generations from the main person to the common ancestor. This should be incremented when going up generations, and left alone when going down generations. Gb: The number of generations from this person (person_handle) to the common ancestor. This should be incremented when going down generations and set back to zero when going up generations. """ parent_handles = self.get_parent_handles(person_handle) for parent_handle in parent_handles: self.add_kin(parent_handle, Ga, Gb) self.traverse_down(parent_handle, Ga, Gb + 1, person_handle) if Ga < self.max_ascend: self.traverse_up(parent_handle, Ga + 1, 0) def add_kin(self, person_handle, Ga, Gb): """ Add a person handle to the kin map. """ if Ga not in self.kinship_map: self.kinship_map[Ga] = {} if Gb not in self.kinship_map[Ga]: self.kinship_map[Ga][Gb] = [] if person_handle not in self.kinship_map[Ga][Gb]: self.kinship_map[Ga][Gb].append(person_handle) def add_spouse(self, spouse_handle, Ga, Gb): """ Add a person handle to the spouse map. """ if Ga not in self.spouse_map: self.spouse_map[Ga] = {} if Gb not in self.spouse_map[Ga]: self.spouse_map[Ga][Gb] = [] if spouse_handle not in self.spouse_map[Ga][Gb]: self.spouse_map[Ga][Gb].append(spouse_handle) def get_parent_handles(self, person_handle): """ Return an array of handles for all the parents of the given person handle. """ parent_handles = [] person = self.__db.get_person_from_handle(person_handle) family_handle = person.get_main_parents_family_handle() if family_handle: family = self.__db.get_family_from_handle(family_handle) father_handle = family.get_father_handle() if father_handle: parent_handles.append(father_handle) mother_handle = family.get_mother_handle() if mother_handle: parent_handles.append(mother_handle) return parent_handles def get_spouse_handles(self, person_handle): """ Return an array of handles for all the spouses of the given person handle. """ spouses = [] person = self.__db.get_person_from_handle(person_handle) for family_handle in person.get_family_handle_list(): family = self.__db.get_family_from_handle(family_handle) father_handle = family.get_father_handle() mother_handle = family.get_mother_handle() spouse_handle = None if mother_handle and father_handle == person_handle: spouse_handle = mother_handle elif father_handle and mother_handle == person_handle: spouse_handle = father_handle if spouse_handle and spouse_handle not in spouses: spouses.append(spouse_handle) return spouses def get_children_handles(self, person_handle): """ Return an array of handles for all the children of the given person handle. """ children = [] person = self.__db.get_person_from_handle(person_handle) for family_handle in person.get_family_handle_list(): family = self.__db.get_family_from_handle(family_handle) for child_ref in family.get_child_ref_list(): children.append(child_ref.get_reference_handle()) return children def write_people(self, title, people_handles): """ Write information about a group of people - including the title. """ cap_title = title[0].upper() + title[1:] subtitle = "%s (%d)" % (cap_title, len(people_handles)) self.doc.start_paragraph("KIN-Subtitle") mark = IndexMark(cap_title, INDEX_TYPE_TOC, 2) self.doc.write_text(subtitle, mark) self.doc.end_paragraph() list(map(self.write_person, people_handles)) def write_person(self, person_handle): """ Write information about the given person. """ person = self.database.get_person_from_handle(person_handle) name = self._name_display.display(person) mark = utils.get_person_mark(self.database, person) birth_date = "" birth = get_birth_or_fallback(self.database, person) if birth: birth_date = self._get_date(birth.get_date_object()) death_date = "" death = get_death_or_fallback(self.database, person) if death: death_date = self._get_date(death.get_date_object()) dates = '' if birth_date or death_date: dates = " (%(birth_date)s - %(death_date)s)" % { 'birth_date': birth_date, 'death_date': death_date } self.doc.start_paragraph('KIN-Normal') self.doc.write_text(name, mark) self.doc.write_text(dates) self.doc.end_paragraph()
class EndOfLineReport(Report): """ EndOfLine Report """ def __init__(self, database, options, user): """ Create the EndOfLineReport object that produces the report. The arguments are: database - the GRAMPS database instance options - instance of the Options class for this report user - a gen.user.User() instance This report needs the following parameters (class variables) that come in the options class. name_format - Preferred format to display names incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death """ Report.__init__(self, database, options, user) menu = options.menu lang = menu.get_option_by_name('trans').get_value() rlocale = self.set_locale(lang) stdoptions.run_private_data_option(self, menu) stdoptions.run_living_people_option(self, menu, rlocale) self.database = CacheProxyDb(self.database) pid = menu.get_option_by_name('pid').get_value() self.center_person = self.database.get_person_from_gramps_id(pid) if self.center_person is None: raise ReportError(_("Person %s is not in the Database") % pid) stdoptions.run_name_format_option(self, menu) # eol_map is a map whose: # keys are the generations of the people # values are a map whose: # keys are person handles # values are an array whose: # elements are an array of ancestor person handles that link # the eol person handle to the person or interest # eol_map[generation][person_handle][pedigree_idx][ancestor_handle_idx] # # There is an array of pedigrees because one person could show up twice # in one generation (descendants marrying). Most people only have one # pedigree. # # eol_map is populated by get_eol() which calls itself recursively. self.eol_map = {} self.get_eol(self.center_person, 1, []) def get_eol(self, person, gen, pedigree): """ Recursively find the end of the line for each person """ person_handle = person.get_handle() new_pedigree = list(pedigree) + [person_handle] person_is_eol = False families = person.get_parent_family_handle_list() if person_handle in pedigree: # This is a severe error! # It indicates a loop in ancestry: A -> B -> A person_is_eol = True elif not families: person_is_eol = True else: for family_handle in families: family = self.database.get_family_from_handle(family_handle) father_handle = family.get_father_handle() mother_handle = family.get_mother_handle() if father_handle: father = self.database.get_person_from_handle( father_handle) self.get_eol(father, gen + 1, new_pedigree) if mother_handle: mother = self.database.get_person_from_handle( mother_handle) self.get_eol(mother, gen + 1, new_pedigree) if not father_handle or not mother_handle: person_is_eol = True if person_is_eol: # This person is the end of a line if gen not in self.eol_map: self.eol_map[gen] = {} if person_handle not in self.eol_map[gen]: self.eol_map[gen][person_handle] = [] self.eol_map[gen][person_handle].append(new_pedigree) def write_report(self): """ The routine that actually creates the report. At this point, the document is opened and ready for writing. """ pname = self._name_display.display(self.center_person) self.doc.start_paragraph("EOL-Title") # feature request 2356: avoid genitive form title = self._("End of Line Report for %s") % pname mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.write_text(title, mark) self.doc.end_paragraph() self.doc.start_paragraph("EOL-Subtitle") # feature request 2356: avoid genitive form title = self._( "All the ancestors of %s who are missing a parent") % pname self.doc.write_text(title) self.doc.end_paragraph() self.doc.start_table('EolTable', 'EOL-Table') for generation, handles in self.eol_map.items(): self.write_generation_row(generation) for person_handle, pedigrees in handles.items(): self.write_person_row(person_handle) list(map(self.write_pedigree_row, pedigrees)) self.doc.end_table() def write_generation_row(self, generation): """ Write out a row in the table showing the generation. """ self.doc.start_row() self.doc.start_cell('EOL_GenerationCell', 2) self.doc.start_paragraph('EOL-Generation') self.doc.write_text(self._("Generation %d") % generation) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() def write_person_row(self, person_handle): """ Write a row in the table with information about the given person. """ person = self.database.get_person_from_handle(person_handle) name = self._name_display.display(person) mark = utils.get_person_mark(self.database, person) birth_date = "" birth_ref = person.get_birth_ref() if birth_ref: event = self.database.get_event_from_handle(birth_ref.ref) birth_date = self._get_date(event.get_date_object()) death_date = "" death_ref = person.get_death_ref() if death_ref: event = self.database.get_event_from_handle(death_ref.ref) death_date = self._get_date(event.get_date_object()) dates = '' if birth_date or death_date: dates = self._(" (%(birth_date)s - %(death_date)s)") % { 'birth_date': birth_date, 'death_date': death_date } self.doc.start_row() self.doc.start_cell('EOL-TableCell', 2) self.doc.start_paragraph('EOL-Normal') self.doc.write_text(name, mark) self.doc.write_text(dates) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() def write_pedigree_row(self, pedigree): """ Write a row in the table with with the person's family line. pedigree is an array containing the names of the people in the pedigree """ names = [] for person_handle in pedigree: person = self.database.get_person_from_handle(person_handle) names.append(self._name_display.display(person)) text = " -- ".join(names) self.doc.start_row() self.doc.start_cell('EOL-TableCell') self.doc.end_cell() self.doc.start_cell('EOL-TableCell') self.doc.start_paragraph('EOL-Pedigree') self.doc.write_text(text) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row()
class DescendantReport(Report): """ Descendant report """ def __init__(self, database, options, user): """ Create the DescendantReport object that produces the report. The arguments are: database - the Gramps database instance options - instance of the Options class for this report user - a gen.user.User() instance This report needs the following parameters (class variables) that come in the options class. gen - Maximum number of generations to include. name_format - Preferred format to display names dups - Whether to include duplicate descendant trees incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death inc_id - Whether to include Gramps IDs """ Report.__init__(self, database, options, user) menu = options.menu self.set_locale(menu.get_option_by_name('trans').get_value()) stdoptions.run_date_format_option(self, menu) stdoptions.run_private_data_option(self, menu) stdoptions.run_living_people_option(self, menu, self._locale) self.database = CacheProxyDb(self.database) self.max_generations = menu.get_option_by_name('gen').get_value() self.want_ids = menu.get_option_by_name('inc_id').get_value() pid = menu.get_option_by_name('pid').get_value() self.center_person = self.database.get_person_from_gramps_id(pid) if self.center_person is None: raise ReportError(_("Person %s is not in the Database") % pid) #Initialize the Printinfo class self._showdups = menu.get_option_by_name('dups').get_value() numbering = menu.get_option_by_name('numbering').get_value() if numbering == "Simple": obj = PrintSimple(self._showdups) elif numbering == "Henry": obj = PrintHenry() elif numbering == "Modified Henry": obj = PrintHenry(modified=True) elif numbering == "d'Aboville": obj = PrintDAboville() elif numbering == "de Villiers/Pama": obj = PrintVilliers() elif numbering == "Meurgey de Tupigny": obj = PrintMeurgey() else: raise AttributeError("no such numbering: '%s'" % numbering) marrs = menu.get_option_by_name('marrs').get_value() divs = menu.get_option_by_name('divs').get_value() stdoptions.run_name_format_option(self, menu) pformat = menu.get_option_by_name("place_format").get_value() self.obj_print = Printinfo(self.doc, self.database, obj, marrs, divs, self._name_display, self._locale, self.want_ids, pformat) def write_report(self): self.doc.start_paragraph("DR-Title") name = self._name_display.display(self.center_person) # feature request 2356: avoid genitive form title = self._("Descendants of %s") % name mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.write_text(title, mark) self.doc.end_paragraph() recurse = RecurseDown(self.max_generations, self.database, self.obj_print, self._showdups, self._locale) recurse.recurse(1, self.center_person, None)
class AncestorTree(Report): """ AncestorTree Report """ def __init__(self, database, options, user): """ Create AncestorTree object that produces the report. The arguments are: database - the Gramps database instance options - instance of the Options class for this report user - a gen.user.User() instance """ Report.__init__(self, database, options, user) self.options = options self._user = user self.set_locale(options.menu.get_option_by_name('trans').get_value()) stdoptions.run_date_format_option(self, options.menu) stdoptions.run_private_data_option(self, options.menu) stdoptions.run_living_people_option(self, options.menu, self._locale) self.database = CacheProxyDb(self.database) stdoptions.run_name_format_option(self, options.menu) self._nd = self._name_display def begin_report(self): """ This report needs the following parameters (class variables) that come in the options class. max_generations - Maximum number of generations to include. pagebbg - Whether to include page breaks between generations. dispf - Display format for the output box. scale_report - Whether to scale the report to fit the width or all. indblank - Whether to include blank pages. compress - Whether to compress chart. incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death We will 1. a canvas in its full one-page size 2. a page that we wish to print on scale up/down either or both of the above as needed/desired. almost all of this should be moved into Canvas! """ database = self.database self.connect = GUIConnect() self.connect.set__opts(self.options.menu, self._locale, self._nd) #Set up the canvas that we will print on. style_sheet = self.doc.get_style_sheet() font_normal = style_sheet.get_paragraph_style("AC2-Normal").get_font() #The canvas that we will put our report on and print off of self.canvas = Canvas(self.doc, ReportOptions(self.doc, font_normal, 'AC2-line')) self.canvas.report_opts.box_shadow *= \ self.connect.get_val('shadowscale') self.canvas.report_opts.box_pgap *= self.connect.get_val('box_Yscale') self.canvas.report_opts.box_mgap *= self.connect.get_val('box_Yscale') with self._user.progress(_('Ancestor Tree'), _('Making the Tree...'), 4) as step: #make the tree onto the canvas ## inlc_marr = self.connect.get_val("inc_marr") self.max_generations = self.connect.get_val('maxgen') tree = MakeAncestorTree(database, self.canvas) tree.start(self.connect.get_val('pid')) tree = None step() #Title title = self.connect.title_class(self.doc) center = self.database.get_person_from_gramps_id( self.connect.get_val('pid')) title.calc_title(center) self.canvas.add_title(title) #make the report as big as it wants to be. report = MakeReport(database, self.doc, self.canvas, font_normal) report.start() self.max_generations = report.get_generations() #already know report = None step() #Note? if self.connect.get_val("inc_note"): note_box = NoteBox(self.doc, "AC2-note-box", self.connect.get_val("note_place")) subst = SubstKeywords(self.database, self._locale, self._nd, None, None) note_box.text = subst.replace_and_clean( self.connect.get_val('note_disp')) self.canvas.add_note(note_box) #Now we have the report in its full size. #Do we want to scale the report? one_page = self.connect.get_val("resize_page") scale_report = self.connect.get_val("scale_tree") scale = self.canvas.scale_report(one_page, scale_report != 0, scale_report == 2) step() if scale != 1 or self.connect.get_val('shadowscale') != 1.0: self.scale_styles(scale) def write_report(self): one_page = self.connect.get_val("resize_page") #scale_report = self.connect.get_val("scale_tree") #inlc_marr = self.connect.get_val("inc_marr") inc_border = self.connect.get_val('inc_border') incblank = self.connect.get_val("inc_blank") prnnum = self.connect.get_val("inc_pagenum") ##################### #Setup page information colsperpage = self.doc.get_usable_width() colsperpage += self.canvas.report_opts.col_width colsperpage = int(colsperpage / (self.canvas.report_opts.max_box_width + self.canvas.report_opts.col_width)) colsperpage = colsperpage or 1 ##################### #Vars if prnnum: page_num_box = PageNumberBox(self.doc, 'AC2-box', self._locale) #TODO - Here ##################### #ok, everyone is now ready to print on the canvas. Paginate? self.canvas.paginate(colsperpage, one_page) ##################### #Yeah!!! #lets finally make some pages!!! ##################### pages = self.canvas.page_count(incblank) with self._user.progress(_('Ancestor Tree'), _('Printing the Tree...'), pages) as step: for page in self.canvas.page_iter_gen(incblank): self.doc.start_page() #do we need to print a border? if inc_border: page.draw_border('AC2-line') #Do we need to print the page number? if prnnum: page_num_box.display(page) #Print the individual people and lines page.display() step() self.doc.end_page() def scale_styles(self, scale): """ Scale the styles for this report. """ style_sheet = self.doc.get_style_sheet() graph_style = style_sheet.get_draw_style("AC2-box") graph_style.set_shadow(graph_style.get_shadow(), self.canvas.report_opts.box_shadow * scale) graph_style.set_line_width(graph_style.get_line_width() * scale) style_sheet.add_draw_style("AC2-box", graph_style) graph_style = style_sheet.get_draw_style("AC2-fam-box") graph_style.set_shadow(graph_style.get_shadow(), self.canvas.report_opts.box_shadow * scale) graph_style.set_line_width(graph_style.get_line_width() * scale) style_sheet.add_draw_style("AC2-fam-box", graph_style) graph_style = style_sheet.get_draw_style("AC2-note-box") #graph_style.set_shadow(graph_style.get_shadow(), # self.canvas.report_opts.box_shadow * scale) graph_style.set_line_width(graph_style.get_line_width() * scale) style_sheet.add_draw_style("AC2-note-box", graph_style) para_style = style_sheet.get_paragraph_style("AC2-Normal") font = para_style.get_font() font.set_size(font.get_size() * scale) para_style.set_font(font) style_sheet.add_paragraph_style("AC2-Normal", para_style) para_style = style_sheet.get_paragraph_style("AC2-Note") font = para_style.get_font() font.set_size(font.get_size() * scale) para_style.set_font(font) style_sheet.add_paragraph_style("AC2-Note", para_style) para_style = style_sheet.get_paragraph_style("AC2-Title") font = para_style.get_font() font.set_size(font.get_size() * scale) para_style.set_font(font) style_sheet.add_paragraph_style("AC2-Title", para_style) graph_style = GraphicsStyle() width = graph_style.get_line_width() width = width * scale graph_style.set_line_width(width) style_sheet.add_draw_style("AC2-line", graph_style) self.doc.set_style_sheet(style_sheet)
class AncestorReport(Report): """ Ancestor Report class """ def __init__(self, database, options, user): """ Create the AncestorReport object that produces the Ahnentafel report. The arguments are: database - the GRAMPS database instance options - instance of the Options class for this report user - a gen.user.User() instance 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. name_format - Preferred format to display names incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death """ Report.__init__(self, database, options, user) self.map = {} menu = options.menu lang = menu.get_option_by_name('trans').get_value() rlocale = self.set_locale(lang) stdoptions.run_private_data_option(self, menu) stdoptions.run_living_people_option(self, menu, rlocale) self.database = CacheProxyDb(self.database) self.max_generations = menu.get_option_by_name('maxgen').get_value() self.pgbrk = menu.get_option_by_name('pagebbg').get_value() self.opt_namebrk = menu.get_option_by_name('namebrk').get_value() pid = menu.get_option_by_name('pid').get_value() self.center_person = self.database.get_person_from_gramps_id(pid) if (self.center_person == None) : raise ReportError(_("Person %s is not in the Database") % pid ) stdoptions.run_name_format_option(self, menu) self.__narrator = Narrator(self.database, use_fulldate=True, nlocale=rlocale) def apply_filter(self, person_handle, index, generation=1): """ Recursive function to walk back all parents of the current person. When max_generations are hit, we stop the traversal. """ # check for end of the current recursion level. This happens # if the person handle is None, or if the max_generations is hit if not person_handle or generation > self.max_generations: return # store the person in the map based off their index number # which is passed to the routine. self.map[index] = person_handle # retrieve the Person instance from the database from the # passed person_handle and find the parents from the list. # Since this report is for natural parents (birth parents), # we have to handle that parents may not person = self.database.get_person_from_handle(person_handle) if person is None: return father_handle = None mother_handle = None for family_handle in person.get_parent_family_handle_list(): family = self.database.get_family_from_handle(family_handle) # filter the child_ref_list to find the reference that matches # the passed person. There should be exactly one, but there is # nothing that prevents the same child in the list multiple times. ref = [ c for c in family.get_child_ref_list() if c.get_reference_handle() == person_handle] if ref: # If the father_handle is not defined and the relationship is # BIRTH, then we have found the birth father. Same applies to # the birth mother. If for some reason, the we have multiple # people defined as the birth parents, we will select based on # priority in the list if not father_handle and \ ref[0].get_father_relation() == ChildRefType.BIRTH: father_handle = family.get_father_handle() if not mother_handle and \ ref[0].get_mother_relation() == ChildRefType.BIRTH: mother_handle = family.get_mother_handle() # Recursively call the function. It is okay if the handle is None, # since routine handles a handle of None self.apply_filter(father_handle, index*2, generation+1) self.apply_filter(mother_handle, (index*2)+1, generation+1) def write_report(self): """ The routine the actually creates the report. At this point, the document is opened and ready for writing. """ # Call apply_filter to build the self.map array of people in the # database that match the ancestry. self.apply_filter(self.center_person.get_handle(), 1) # Write the title line. Set in INDEX marker so that this section will be # identified as a major category if this is included in a Book report. name = self._name_display.display_formal(self.center_person) # feature request 2356: avoid genitive form title = self._("Ahnentafel Report for %s") % name mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.start_paragraph("AHN-Title") self.doc.write_text(title, mark) self.doc.end_paragraph() # get the entries out of the map, and sort them. generation = 0 for key in sorted(self.map): # check the index number to see if we need to start a new generation if generation == log2(key): # generate a page break if requested if self.pgbrk and generation > 0: self.doc.page_break() generation += 1 # Create the Generation title, set an index marker gen_text = self._("Generation %d") % generation mark = None # don't need any with no page breaks if self.pgbrk: mark = IndexMark(gen_text, INDEX_TYPE_TOC, 2) self.doc.start_paragraph("AHN-Generation") self.doc.write_text(gen_text, mark) self.doc.end_paragraph() # Build the entry self.doc.start_paragraph("AHN-Entry","%d." % key) person = self.database.get_person_from_handle(self.map[key]) if person is None: continue name = self._name_display.display(person) mark = utils.get_person_mark(self.database, person) # write the name in bold self.doc.start_bold() self.doc.write_text(name.strip(), mark) self.doc.end_bold() # terminate with a period if it is not already terminated. # This can happen if the person's name ends with something 'Jr.' if name[-1:] == '.': self.doc.write_text(" ") else: self.doc.write_text(". ") # Add a line break if requested (not implemented yet) if self.opt_namebrk: self.doc.write_text('\n') self.__narrator.set_subject(person) self.doc.write_text(self.__narrator.get_born_string()) self.doc.write_text(self.__narrator.get_baptised_string()) self.doc.write_text(self.__narrator.get_christened_string()) self.doc.write_text(self.__narrator.get_died_string()) self.doc.write_text(self.__narrator.get_buried_string()) self.doc.end_paragraph()