# # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2007-2008 Stephane Charette # Copyright (C) 2007-2008 Brian G. Matherly # Copyright (C) 2009-2010 Gary Burton # Contribution 2009 by Bob Ham # Copyright (C) 2010 Jakim Friant # # 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$ """ Family Lines, a GraphViz-based plugin for Gramps. """ #------------------------------------------------------------------------ # # python modules # #------------------------------------------------------------------------ from gen.ggettext import gettext as _ from functools import partial #------------------------------------------------------------------------ # # Set up logging # #------------------------------------------------------------------------ import logging log = logging.getLogger(".FamilyLines") #------------------------------------------------------------------------ # # GRAMPS module # #------------------------------------------------------------------------ import gen.lib import Utils import ThumbNails from DateHandler import displayer as _dd from gen.plug.report import Report from gen.plug.report import utils as ReportUtils from gui.plug.report import MenuReportOptions from gen.plug.menu import (NumberOption, ColorOption, BooleanOption, EnumeratedListOption, PersonListOption, SurnameColorOption) from gen.utils import get_birth_or_fallback, get_death_or_fallback from gen.display.name import displayer as name_displayer #------------------------------------------------------------------------ # # Constant options items # #------------------------------------------------------------------------ _COLORS = [ { 'name' : _("B&W outline"), 'value' : "outline" }, { 'name' : _("Coloured outline"), 'value' : "colored" }, { 'name' : _("Colour fill"), 'value' : "filled" }] #------------------------------------------------------------------------ # # A quick overview of the classes we'll be using: # # class FamilyLinesOptions(MenuReportOptions) # - this class is created when the report dialog comes up # - all configuration controls for the report are created here # - see src/ReportBase/_ReportOptions.py for more information # # class FamilyLinesReport(Report) # - this class is created only after the user clicks on "OK" # - the actual report generation is done by this class # - see src/ReportBase/_Report.py for more information # # Likely to be of additional interest is register_report() at the # very bottom of this file. # #------------------------------------------------------------------------ class FamilyLinesOptions(MenuReportOptions): """ Defines all of the controls necessary to configure the FamilyLines reports. """ def __init__(self, name, dbase): self.limit_parents = None self.max_parents = None self.limit_children = None self.max_children = None self.include_images = None self.image_location = None MenuReportOptions.__init__(self, name, dbase) def add_menu_options(self, menu): # -------------------------------- add_option = partial(menu.add_option, _('People of Interest')) # -------------------------------- person_list = PersonListOption(_('People of interest')) person_list.set_help(_('People of interest are used as a starting ' 'point when determining "family lines".')) add_option('gidlist', person_list) followpar = BooleanOption( _('Follow parents to determine family lines'), True) followpar.set_help(_('Parents and their ancestors will be ' 'considered when determining "family lines".')) add_option('followpar', followpar) followchild = BooleanOption(_('Follow children to determine ' '"family lines"'), True) followchild.set_help(_('Children will be considered when ' 'determining "family lines".')) add_option('followchild', followchild) remove_extra_people = BooleanOption( _('Try to remove extra people and families'), True) remove_extra_people.set_help(_('People and families not directly ' 'related to people of interest will ' 'be removed when determining ' '"family lines".')) add_option('removeextra', remove_extra_people) # ---------------------------- add_option = partial(menu.add_option, _('Family Colours')) # ---------------------------- surname_color = SurnameColorOption(_('Family colours')) surname_color.set_help(_('Colours to use for various family lines.')) add_option('surnamecolors', surname_color) # ------------------------- add_option = partial(menu.add_option, _('Individuals')) # ------------------------- color_males = ColorOption(_('Males'), '#e0e0ff') color_males.set_help(_('The colour to use to display men.')) add_option('colormales', color_males) color_females = ColorOption(_('Females'), '#ffe0e0') color_females.set_help(_('The colour to use to display women.')) add_option('colorfemales', color_females) color_unknown = ColorOption(_('Unknown'), '#e0e0e0') color_unknown.set_help( _('The colour to use when the gender is unknown.')) add_option('colorunknown', color_unknown) color_family = ColorOption(_('Families'), '#ffffe0') color_family.set_help(_('The colour to use to display families.')) add_option('colorfamilies', color_family) self.limit_parents = BooleanOption(_('Limit the number of parents'), False) self.limit_parents.set_help( _('The maximum number of ancestors to include.')) add_option('limitparents', self.limit_parents) self.limit_parents.connect('value-changed', self.limit_changed) self.max_parents = NumberOption('', 50, 10, 9999) self.max_parents.set_help( _('The maximum number of ancestors to include.')) add_option('maxparents', self.max_parents) self.limit_children = BooleanOption(_('Limit the number of children'), False) self.limit_children.set_help( _('The maximum number of children to include.')) add_option('limitchildren', self.limit_children) self.limit_children.connect('value-changed', self.limit_changed) self.max_children = NumberOption('', 50, 10, 9999) self.max_children.set_help( _('The maximum number of children to include.')) add_option('maxchildren', self.max_children) # -------------------- add_option = partial(menu.add_option, _('Images')) # -------------------- self.include_images = BooleanOption( _('Include thumbnail images of people'), True) self.include_images.set_help( _('The maximum number of children to include.')) add_option('incimages', self.include_images) self.include_images.connect('value-changed', self.images_changed) self.image_location = EnumeratedListOption(_('Thumbnail location'), 0) self.image_location.add_item(0, _('Above the name')) self.image_location.add_item(1, _('Beside the name')) self.image_location.set_help( _('Where the thumbnail image should appear relative to the name')) add_option('imageonside', self.image_location) # --------------------- add_option = partial(menu.add_option, _('Options')) # --------------------- color = EnumeratedListOption(_("Graph coloring"), "filled") for i in range(len(_COLORS)): color.add_item(_COLORS[i]["value"], _COLORS[i]["name"]) color.set_help(_("Males will be shown with blue, females " "with red, unless otherwise set above for filled." " If the sex of an individual " "is unknown it will be shown with gray.")) add_option("color", color) use_roundedcorners = BooleanOption(_('Use rounded corners'), False) use_roundedcorners.set_help(_('Use rounded corners to differentiate ' 'between women and men.')) add_option("useroundedcorners", use_roundedcorners) self.include_dates = BooleanOption(_('Include dates'), True) self.include_dates.set_help(_('Whether to include dates for people ' 'and families.')) add_option('incdates', self.include_dates) self.include_dates.connect('value-changed', self.include_dates_changed) self.justyears = BooleanOption(_("Limit dates to years only"), False) self.justyears.set_help(_("Prints just dates' year, neither " "month or day nor date approximation " "or interval are shown.")) add_option("justyears", self.justyears) include_places = BooleanOption(_('Include places'), True) include_places.set_help(_('Whether to include placenames for people ' 'and families.')) add_option('incplaces', include_places) include_num_children = BooleanOption( _('Include the number of children'), True) include_num_children.set_help(_('Whether to include the number of ' 'children for families with more ' 'than 1 child.')) add_option('incchildcnt', include_num_children) include_private = BooleanOption(_('Include private records'), False) include_private.set_help(_('Whether to include names, dates, and ' 'families that are marked as private.')) add_option('incprivate', include_private) self.limit_changed() self.images_changed() def limit_changed(self): """ Handle the change of limiting parents and children. """ self.max_parents.set_available(self.limit_parents.get_value()) self.max_children.set_available(self.limit_children.get_value()) def images_changed(self): """ Handle the change of including images. """ self.image_location.set_available(self.include_images.get_value()) def include_dates_changed(self): """ Enable/disable menu items if dates are required """ if self.include_dates.get_value(): self.justyears.set_available(True) else: self.justyears.set_available(False) #------------------------------------------------------------------------ # # FamilyLinesReport -- created once the user presses 'OK' # #------------------------------------------------------------------------ class FamilyLinesReport(Report): def __init__(self, database, options): """ Create FamilyLinesReport object that eventually produces the report. The arguments are: database - the GRAMPS database instance person - currently selected person options - instance of the FamilyLinesOptions class for this report """ Report.__init__(self, database, options) # initialize several convenient variables self._db = database self._people = set() # handle of people we need in the report self._families = set() # handle of families we need in the report self._deleted_people = 0 self._deleted_families = 0 menu = options.menu get_option_by_name = menu.get_option_by_name get_value = lambda name: get_option_by_name(name).get_value() self._followpar = get_value('followpar') self._followchild = get_value('followchild') self._removeextra = get_value('removeextra') self._gidlist = get_value('gidlist') self._colormales = get_value('colormales') self._colorfemales = get_value('colorfemales') self._colorunknown = get_value('colorunknown') self._colorfamilies = get_value('colorfamilies') self._limitparents = get_value('limitparents') self._maxparents = get_value('maxparents') self._limitchildren = get_value('limitchildren') self._maxchildren = get_value('maxchildren') self._incimages = get_value('incimages') self._imageonside = get_value('imageonside') self._useroundedcorners = get_value('useroundedcorners') self._usesubgraphs = get_value('usesubgraphs') self._incdates = get_value('incdates') self._just_years = get_value('justyears') self._incplaces = get_value('incplaces') self._incchildcount = get_value('incchildcnt') self._incprivate = get_value('incprivate') # the gidlist is annoying for us to use since we always have to convert # the GIDs to either Person or to handles, so we may as well convert the # entire list right now and not have to deal with it ever again self._interest_set = set() for gid in self._gidlist.split(): person = self._db.get_person_from_gramps_id(gid) if person: #option can be from another family tree, so person can be None self._interest_set.add(person.get_handle()) # convert the 'surnamecolors' string to a dictionary of names and colors self._surnamecolors = {} tmp = get_value('surnamecolors') if (tmp.find(u'\xb0') >= 0): tmp = tmp.split(u'\xb0') # new style delimiter (see bug report #2162) else: tmp = tmp.split(' ') # old style delimiter while len(tmp) > 1: surname = tmp.pop(0).encode('iso-8859-1', 'xmlcharrefreplace') colour = tmp.pop(0) self._surnamecolors[surname] = colour self._colorize = get_value('color') def begin_report(self): """ Inherited method; called by report() in _ReportDialog.py This is where we'll do all of the work of figuring out who from the database is going to be output into the report """ # starting with the people of interest, we then add parents: self._people.clear() self._families.clear() if self._followpar: self.findParents() if self._removeextra: self.removeUninterestingParents() # ...and/or with the people of interest we add their children: if self._followchild: self.findChildren() # once we get here we have a full list of people # and families that we need to generate a report def write_report(self): """ Inherited method; called by report() in _ReportDialog.py """ # now that begin_report() has done the work, output what we've # obtained into whatever file or format the user expects to use self.doc.add_comment('# Number of people in database: %d' % self._db.get_number_of_people()) self.doc.add_comment('# Number of people of interest: %d' % len(self._people)) self.doc.add_comment('# Number of families in database: %d' % self._db.get_number_of_families()) self.doc.add_comment('# Number of families of interest: %d' % len(self._families)) if self._removeextra: self.doc.add_comment('# Additional people removed: %d' % self._deleted_people) self.doc.add_comment('# Additional families removed: %d' % self._deleted_families) self.doc.add_comment('# Initial list of people of interest:') for handle in self._interest_set: person = self._db.get_person_from_handle(handle) gid = person.get_gramps_id() name = person.get_primary_name().get_regular_name() self.doc.add_comment('# -> %s, %s' % (gid, name)) self.writePeople() self.writeFamilies() def findParents(self): # we need to start with all of our "people of interest" ancestorsNotYetProcessed = set(self._interest_set) # now we find all the immediate ancestors of our people of interest while ancestorsNotYetProcessed: handle = ancestorsNotYetProcessed.pop() # One of 2 things can happen here: # 1) we've already know about this person and he/she is already # in our list # 2) this is someone new, and we need to remember him/her # # In the first case, there isn't anything else to do, so we simply # go back to the top and pop the next person off the list. # # In the second case, we need to add this person to our list, and # then go through all of the parents this person has to find more # people of interest. if handle not in self._people: person = self._db.get_person_from_handle(handle) # if this is a private record, and we're not # including private records, then go back to the # top of the while loop to get the next person if person.private and not self._incprivate: continue # remember this person! self._people.add(handle) # see if a family exists between this person and someone else # we have on our list of people we're going to output -- if # there is a family, then remember it for when it comes time # to link spouses together for family_handle in person.get_family_handle_list(): family = self._db.get_family_from_handle(family_handle) spouse_handle = ReportUtils.find_spouse(person, family) if spouse_handle: if (spouse_handle in self._people or spouse_handle in ancestorsNotYetProcessed): self._families.add(family_handle) # if we have a limit on the number of people, and we've # reached that limit, then don't attempt to find any # more ancestors if self._limitparents and (self._maxparents < len(ancestorsNotYetProcessed) + len(self._people)): # get back to the top of the while loop so we can finish # processing the people queued up in the "not yet # processed" list continue # queue the parents of the person we're processing for family_handle in person.get_parent_family_handle_list(): family = self._db.get_family_from_handle(family_handle) if not family.private or self._incprivate: father = self._db.get_person_from_handle( family.get_father_handle()) mother = self._db.get_person_from_handle( family.get_mother_handle()) if father: if not father.private or self._incprivate: ancestorsNotYetProcessed.add( family.get_father_handle()) self._families.add(family_handle) if mother: if not mother.private or self._incprivate: ancestorsNotYetProcessed.add( family.get_mother_handle()) self._families.add(family_handle) def removeUninterestingParents(self): # start with all the people we've already identified unprocessed_parents = set(self._people) while len(unprocessed_parents) > 0: handle = unprocessed_parents.pop() person = self._db.get_person_from_handle(handle) # There are a few things we're going to need, # so look it all up right now; such as: # - who is the child? # - how many children? # - parents? # - spouse? # - is a person of interest? # - spouse of a person of interest? # - same surname as a person of interest? # - spouse has the same surname as a person of interest? child_handle = None child_count = 0 spouse_handle = None spouse_count = 0 father_handle = None mother_handle = None spouse_father_handle = None spouse_mother_handle = None spouse_surname = "" surname = person.get_primary_name().get_surname() surname = surname.encode('iso-8859-1','xmlcharrefreplace') # first we get the person's father and mother for family_handle in person.get_parent_family_handle_list(): family = self._db.get_family_from_handle(family_handle) handle = family.get_father_handle() if handle in self._people: father_handle = handle handle = family.get_mother_handle() if handle in self._people: mother_handle = handle # now see how many spouses this person has for family_handle in person.get_family_handle_list(): family = self._db.get_family_from_handle(family_handle) handle = ReportUtils.find_spouse(person, family) if handle in self._people: spouse_count += 1 spouse = self._db.get_person_from_handle(handle) spouse_handle = handle spouse_surname = spouse.get_primary_name().get_surname() spouse_surname = spouse_surname.encode( 'iso-8859-1', 'xmlcharrefreplace' ) # see if the spouse has parents if not spouse_father_handle and not spouse_mother_handle: for family_handle in \ spouse.get_parent_family_handle_list(): family = self._db.get_family_from_handle( family_handle) handle = family.get_father_handle() if handle in self._people: spouse_father_handle = handle handle = family.get_mother_handle() if handle in self._people: spouse_mother_handle = handle # get the number of children that we think might be interesting 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(): if child_ref.ref in self._people: child_count += 1 child_handle = child_ref.ref # we now have everything we need -- start looking for reasons # why this is a person we need to keep in our list, and loop # back to the top as soon as a reason is discovered # if this person has many children of interest, then we # automatically keep this person if child_count > 1: continue # if this person has many spouses of interest, then we # automatically keep this person if spouse_count > 1: continue # if this person has parents, then we automatically keep # this person if father_handle is not None or mother_handle is not None: continue # if the spouse has parents, then we automatically keep # this person if spouse_father_handle is not None or spouse_mother_handle is not None: continue # if this is a person of interest, then we automatically keep if person.get_handle() in self._interest_set: continue # if the spouse is a person of interest, then we keep if spouse_handle in self._interest_set: continue # if the surname (or the spouse's surname) matches a person # of interest, then we automatically keep this person bKeepThisPerson = False for personOfInterestHandle in self._interest_set: personOfInterest = self._db.get_person_from_handle(personOfInterestHandle) surnameOfInterest = personOfInterest.get_primary_name().get_surname().encode('iso-8859-1','xmlcharrefreplace') if surnameOfInterest == surname or surnameOfInterest == spouse_surname: bKeepThisPerson = True break if bKeepThisPerson: continue # if we have a special colour to use for this person, # then we automatically keep this person if surname in self._surnamecolors: continue # if we have a special colour to use for the spouse, # then we automatically keep this person if spouse_surname in self._surnamecolors: continue # took us a while, but if we get here, then we can remove this person self._deleted_people += 1 self._people.remove(person.get_handle()) # we can also remove any families to which this person belonged for family_handle in person.get_family_handle_list(): if family_handle in self._families: self._deleted_families += 1 self._families.remove(family_handle) # if we have a spouse, then ensure we queue up the spouse if spouse_handle: if spouse_handle not in unprocessed_parents: unprocessed_parents.add(spouse_handle) # if we have a child, then ensure we queue up the child if child_handle: if child_handle not in unprocessed_parents: unprocessed_parents.add(child_handle) def findChildren(self): # we need to start with all of our "people of interest" childrenNotYetProcessed = set(self._interest_set) childrenToInclude = set() # now we find all the children of our people of interest while len(childrenNotYetProcessed) > 0: handle = childrenNotYetProcessed.pop() if handle not in childrenToInclude: person = self._db.get_person_from_handle(handle) # if this is a private record, and we're not # including private records, then go back to the # top of the while loop to get the next person if person.private and not self._incprivate: continue # remember this person! childrenToInclude.add(handle) # if we have a limit on the number of people, and we've # reached that limit, then don't attempt to find any # more children if self._limitchildren and ( self._maxchildren < ( len(childrenNotYetProcessed) + len(childrenToInclude) ) ): # get back to the top of the while loop so we can finish # processing the people queued up in the "not yet processed" list continue # iterate through this person's families for family_handle in person.get_family_handle_list(): family = self._db.get_family_from_handle(family_handle) if (family.private and self._incprivate) or not family.private: # queue up any children from this person's family for childRef in family.get_child_ref_list(): child = self._db.get_person_from_handle(childRef.ref) if (child.private and self._incprivate) or not child.private: childrenNotYetProcessed.add(child.get_handle()) self._families.add(family_handle) # include the spouse from this person's family spouse_handle = ReportUtils.find_spouse(person, family) if spouse_handle: spouse = self._db.get_person_from_handle(spouse_handle) if (spouse.private and self._incprivate) or not spouse.private: childrenToInclude.add(spouse_handle) self._families.add(family_handle) # we now merge our temp set "childrenToInclude" into our master set self._people.update(childrenToInclude) def writePeople(self): self.doc.add_comment('') # If we're going to attempt to include images, then use the HTML style # of .gv file. bUseHtmlOutput = False if self._incimages: bUseHtmlOutput = True # loop through all the people we need to output for handle in self._people: person = self._db.get_person_from_handle(handle) name = name_displayer.display_name(person.get_primary_name()) # figure out what colour to use gender = person.get_gender() colour = self._colorunknown if gender == gen.lib.Person.MALE: colour = self._colormales elif gender == gen.lib.Person.FEMALE: colour = self._colorfemales # see if we have surname colours that match this person surname = person.get_primary_name().get_surname().encode('iso-8859-1','xmlcharrefreplace') if surname in self._surnamecolors: colour = self._surnamecolors[surname] # see if we have a birth/death or fallback dates we can use if self._incdates or self._incplaces: bth_event = get_birth_or_fallback(self._db, person) dth_event = get_death_or_fallback(self._db, person) else: bth_event = None dth_event = None # output the birth or fallback event birthStr = None if bth_event and self._incdates: if not bth_event.private or self._incprivate: date = bth_event.get_date_object() if self._just_years and date.get_year_valid(): birthStr = '%i' % date.get_year() else: birthStr = _dd.display(date) # get birth place (one of: city, state, or country) we can use birthplace = None if bth_event and self._incplaces: if not bth_event.private or self._incprivate: place = self._db.get_place_from_handle(bth_event.get_place_handle()) if place: location = place.get_main_location() if location.get_city: birthplace = location.get_city() elif location.get_state: birthplace = location.get_state() elif location.get_country: birthplace = location.get_country() # see if we have a deceased date we can use deathStr = None if dth_event and self._incdates: if not dth_event.private or self._incprivate: date = dth_event.get_date_object() if self._just_years and date.get_year_valid(): deathStr = '%i' % date.get_year() else: deathStr = _dd.display(date) # get death place (one of: city, state, or country) we can use deathplace = None if dth_event and self._incplaces: if not dth_event.private or self._incprivate: place = self._db.get_place_from_handle(dth_event.get_place_handle()) if place: location = place.get_main_location() if location.get_city: deathplace = location.get_city() elif location.get_state: deathplace = location.get_state() elif location.get_country: deathplace = location.get_country() # see if we have an image to use for this person imagePath = None if self._incimages: mediaList = person.get_media_list() if len(mediaList) > 0: mediaHandle = mediaList[0].get_reference_handle() media = self._db.get_object_from_handle(mediaHandle) mediaMimeType = media.get_mime_type() if mediaMimeType[0:5] == "image": imagePath = ThumbNails.get_thumbnail_path( Utils.media_path_full(self._db, media.get_path()), rectangle=mediaList[0].get_rectangle()) # put the label together and output this person label = u"" lineDelimiter = '\\n' if bUseHtmlOutput: lineDelimiter = '
' # if we have an image, then start an HTML table; remember to close the table afterwards! if imagePath: label = (u'' % imagePath ) if self._imageonside == 0: label += u'' label += '
' # at the very least, the label must have the person's name label += name if birthStr or deathStr: label += ' %s(' % lineDelimiter if birthStr: label += '%s' % birthStr label += ' - ' if deathStr: label += '%s' % deathStr label += ')' if birthplace or deathplace: if birthplace == deathplace: deathplace = None # no need to print the same name twice label += ' %s' % lineDelimiter if birthplace: label += '%s' % birthplace if birthplace and deathplace: label += ' / ' if deathplace: label += '%s' % deathplace # see if we have a table that needs to be terminated if imagePath: label += '
' shape = "box" style = "solid" border = colour fill = colour # do not use colour if this is B&W outline if self._colorize == 'outline': border = "" fill = "" if gender == person.FEMALE and self._useroundedcorners: style = "rounded" elif gender == person.UNKNOWN: shape = "hexagon" # if we're filling the entire node: if self._colorize == 'filled': style += ",filled" border = "" # we're done -- add the node self.doc.add_node(person.get_gramps_id(), label=label, shape=shape, color=border, style=style, fillcolor=fill, htmloutput=bUseHtmlOutput) def writeFamilies(self): self.doc.add_comment('') # loop through all the families we need to output for family_handle in self._families: family = self._db.get_family_from_handle(family_handle) fgid = family.get_gramps_id() # figure out a wedding date or placename we can use weddingDate = None weddingPlace = None if self._incdates or self._incplaces: for event_ref in family.get_event_ref_list(): event = self._db.get_event_from_handle(event_ref.ref) if event.get_type() == gen.lib.EventType.MARRIAGE and \ (event_ref.get_role() == gen.lib.EventRoleType.FAMILY or event_ref.get_role() == gen.lib.EventRoleType.PRIMARY ): # get the wedding date if (event.private and self._incprivate) or not event.private: if self._incdates: date = event.get_date_object() if self._just_years and date.get_year_valid(): weddingDate = '%i' % date.get_year() else: weddingDate = _dd.display(date) # get the wedding location if self._incplaces: place = self._db.get_place_from_handle(event.get_place_handle()) if place: location = place.get_main_location() if location.get_city: weddingPlace = location.get_city() elif location.get_state: weddingPlace = location.get_state() elif location.get_country: weddingPlace = location.get_country() break # figure out the number of children (if any) childrenStr = None if self._incchildcount: child_count = len(family.get_child_ref_list()) # if child_count == 1: # childrenStr = _('1 child') if child_count > 1: childrenStr = _('%d children') % child_count label = '' if weddingDate: if label != '': label += '\\n' label += '%s' % weddingDate if weddingPlace: if label != '': label += '\\n' label += '%s' % weddingPlace if childrenStr: if label != '': label += '\\n' label += '%s' % childrenStr shape = "ellipse" style = "solid" border = self._colorfamilies fill = self._colorfamilies # do not use colour if this is B&W outline if self._colorize == 'outline': border = "" fill = "" # if we're filling the entire node: if self._colorize == 'filled': style += ",filled" border = "" # we're done -- add the node self.doc.add_node(fgid, label, shape, border, style, fill) # now that we have the families written, go ahead and link the parents and children to the families for family_handle in self._families: # get the parents for this family family = self._db.get_family_from_handle(family_handle) fgid = family.get_gramps_id() father_handle = family.get_father_handle() mother_handle = family.get_mother_handle() self.doc.add_comment('') if self._usesubgraphs and father_handle and mother_handle: self.doc.start_subgraph(fgid) # see if we have a father to link to this family if father_handle: if father_handle in self._people: father = self._db.get_person_from_handle(father_handle) comment = "father: %s" % father.get_primary_name().get_regular_name() self.doc.add_link(father.get_gramps_id(), fgid, comment=comment) # see if we have a mother to link to this family if mother_handle: if mother_handle in self._people: mother = self._db.get_person_from_handle(mother_handle) comment = "mother: %s" % mother.get_primary_name().get_regular_name() self.doc.add_link(mother.get_gramps_id(), fgid, comment=comment) if self._usesubgraphs and father_handle and mother_handle: self.doc.end_subgraph() # link the children to the family for childRef in family.get_child_ref_list(): if childRef.ref in self._people: child = self._db.get_person_from_handle(childRef.ref) comment = "child: %s" % child.get_primary_name().get_regular_name() self.doc.add_link(fgid, child.get_gramps_id(), comment=comment)