#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2007-2008  Stephane Charette
# Copyright (C) 2007  Brian G. Matherly
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Pubilc 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: GVFamilyLines.py 10152 2008-03-01 22:47:48Z pez4brian $

"""
Family Lines, a GraphViz-based plugin for Gramps.
"""

#------------------------------------------------------------------------
#
# python modules
#
#------------------------------------------------------------------------
from gettext import gettext as _

#------------------------------------------------------------------------
#
# 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 ReportBase import Report, ReportUtils, MenuReportOptions, \
    CATEGORY_GRAPHVIZ, MODE_GUI
from PluginUtils import register_report, EnumeratedListOption, BooleanOption, \
    NumberOption, ColourOption, PersonListOption, SurnameColourOption


#------------------------------------------------------------------------
#
# 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):

        # --------------------------------
        category = _('People of Interest')
        # --------------------------------

        personList = PersonListOption(      _('People of interest'))
        personList.set_help(                _('People of interest are used as a starting point when determining \"family lines\".'))
        menu.add_option(category, 'FLgidlist', personList)

        followParents = BooleanOption(      _('Follow parents to determine family lines'), True)
        followParents.set_help(             _('Parents and their ancestors will be considered when determining "family lines".'))
        menu.add_option(category, 'FLfollowParents', followParents)

        followChildren = BooleanOption(     _('Follow children to determine family lines'), True)
        followChildren.set_help(            _('Children will be considered when determining "family lines".'))
        menu.add_option(category, 'FLfollowChildren', followChildren)

        removeExtraPeople = BooleanOption(  _('Try to remove extra people and families'), True)
        removeExtraPeople.set_help(         _('People and families not directly related to people of interest will be removed when determining "family lines".'))
        menu.add_option(category, 'FLremoveExtraPeople', removeExtraPeople)

        # ----------------------------
        category = _('Family Colours')
        # ----------------------------

        surnameColour = SurnameColourOption(_('Family colours'))
        surnameColour.set_help(             _('Colours to use for various family lines.'))
        menu.add_option(category, 'FLsurnameColours', surnameColour)

        # -------------------------
        category = _('Individuals')
        # -------------------------

        colourMales = ColourOption(   _('Males'), '#e0e0ff')
        colourMales.set_help(               _('The colour to use to display men.'))
        menu.add_option(category, 'FLcolourMales', colourMales)

        colourFemales = ColourOption( _('Females'), '#ffe0e0')
        colourFemales.set_help(             _('The colour to use to display women.'))
        menu.add_option(category, 'FLcolourFemales', colourFemales)

        colourUnknown = ColourOption( _('Unknown'), '#e0e0e0')
        colourUnknown.set_help(             _('The colour to use when the gender is unknown.'))
        menu.add_option(category, 'FLcolourUnknown', colourUnknown)

        colourFamily = ColourOption(  _('Families'), '#ffffe0')
        colourFamily.set_help(              _('The colour to use to display families.'))
        menu.add_option(category, 'FLcolourFamilies', colourFamily)

        self.limit_parents = BooleanOption(_('Limit the number of parents'), 
                                           False)
        self.limit_parents.set_help(
                            _('The maximum number of ancestors to include.'))
        menu.add_option(category, 'FLlimitParents', 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.'))
        menu.add_option(category, 'FLmaxParents', 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.'))
        menu.add_option(category, 'FLlimitChildren', 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.'))
        menu.add_option(category, 'FLmaxChildren', self.max_children)

        # --------------------
        category = _('Images')
        # --------------------

        self.include_images = BooleanOption(
                                  _('Include thumbnail images of people'), True)
        self.include_images.set_help(
                                _('The maximum number of children to include.'))
        menu.add_option(category, 'FLincludeImages', 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'))
        menu.add_option(category, 'FLimageOnTheSide', self.image_location)

        # ---------------------
        category = _('Options')
        # ---------------------

        useSubgraphs = BooleanOption(_('Use subgraphs'), False)
        useSubgraphs.set_help(_("Subgraphs can help GraphViz position "
                                "certain linked nodes closer together, "
                                "but with non-trivial graphs will result "
                                "in longer lines and larger graphs."))
        menu.add_option(category, "FLuseSubgraphs", useSubgraphs)

        includeDates = BooleanOption(       _('Include dates'), True)
        includeDates.set_help(              _('Whether to include dates for people and families.'))
        menu.add_option(category, 'FLincludeDates', includeDates)

        includePlaces = BooleanOption(      _('Include places'), True)
        includePlaces.set_help(             _('Whether to include placenames for people and families.'))
        menu.add_option(category, 'FLincludePlaces', includePlaces)

        includeNumChildren = BooleanOption( _('Include the number of children'), True)
        includeNumChildren.set_help(        _('Whether to include the number of children for families with more than 1 child.'))
        menu.add_option(category, 'FLincludeNumChildren', includeNumChildren)

        includeResearcher = BooleanOption(  _('Include researcher and date'), True)
        includeResearcher.set_help(         _('Whether to include at the bottom the researcher''s name, e-mail, and the date the graph was generated.'))
        menu.add_option(category, 'FLincludeResearcher', includeResearcher)

        includePrivate = BooleanOption(     _('Include private records'), False)
        includePrivate.set_help(            _('Whether to include names, dates, and families that are marked as private.'))
        menu.add_option(category, 'FLincludePrivate', includePrivate)
        
        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())

#------------------------------------------------------------------------
#
# 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.options            = options
        self.db                 = database
        self.peopleToOutput     = set() # handle of people we need in the report
        self.familiesToOutput   = set() # handle of families we need in the report
        self.deletedPeople      = 0
        self.deletedFamilies    = 0

        self.followParents      = options.handler.options_dict['FLfollowParents'        ]
        self.followChildren     = options.handler.options_dict['FLfollowChildren'       ]
        self.removeExtraPeople  = options.handler.options_dict['FLremoveExtraPeople'    ]
        self.gidlist            = options.handler.options_dict['FLgidlist'              ]
        self.colourMales        = options.handler.options_dict['FLcolourMales'          ]
        self.colourFemales      = options.handler.options_dict['FLcolourFemales'        ]
        self.colourUnknown      = options.handler.options_dict['FLcolourUnknown'        ]
        self.colourFamilies     = options.handler.options_dict['FLcolourFamilies'       ]
        self.limitParents       = options.handler.options_dict['FLlimitParents'         ]
        self.maxParents         = options.handler.options_dict['FLmaxParents'           ]
        self.limitChildren      = options.handler.options_dict['FLlimitChildren'        ]
        self.maxChildren        = options.handler.options_dict['FLmaxChildren'          ]
        self.includeImages      = options.handler.options_dict['FLincludeImages'        ]
        self.imageOnTheSide     = options.handler.options_dict['FLimageOnTheSide'       ]
        self.useSubgraphs       = options.handler.options_dict['FLuseSubgraphs'         ]   
        self.includeDates       = options.handler.options_dict['FLincludeDates'         ] 
        self.includePlaces      = options.handler.options_dict['FLincludePlaces'        ]
        self.includeNumChildren = options.handler.options_dict['FLincludeNumChildren'   ]
        self.includeResearcher  = options.handler.options_dict['FLincludeResearcher'    ]
        self.includePrivate     = options.handler.options_dict['FLincludePrivate'       ]

        # 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.interestSet = set()
        for gid in self.gidlist.split():
            person = self.db.get_person_from_gramps_id(gid)
            self.interestSet.add(person.get_handle())

        # convert the 'surnameColours' string to a dictionary of names and colours
        self.surnameColours = {}
        tmp = options.handler.options_dict['FLsurnameColours'].split()
        while len(tmp) > 1:
            surname = tmp.pop(0).encode('iso-8859-1','xmlcharrefreplace')
            colour = tmp.pop(0)
            self.surnameColours[surname] = colour


    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

        self.progress = Utils.ProgressMeter(_('Generating Family Lines'),_('Starting'))

        # starting with the people of interest, we then add parents:
        self.peopleToOutput.clear()
        self.familiesToOutput.clear()
        self.progress.set_pass(_('Finding ancestors and children'), self.db.get_number_of_people())
        if self.followParents:
            self.findParents()

            if self.removeExtraPeople:
                self.removeUninterestingParents()

        # ...and/or with the people of interest we add their children:
        if self.followChildren:
            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

        # since we know the exact number of people and families,
        # we can then restart the progress bar with the exact
        # number
        self.progress.set_pass(_('Writing family lines'),
            len(self.peopleToOutput     ) + # every person needs to be written
            len(self.familiesToOutput   ) + # every family needs to be written
            len(self.familiesToOutput   ))  # every family needs people assigned to it

        # 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.peopleToOutput))
        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.familiesToOutput))
        if self.removeExtraPeople:
            self.doc.add_comment('# Additional people removed:       %d' % self.deletedPeople)
            self.doc.add_comment('# Additional families removed:     %d' % self.deletedFamilies)
        self.doc.add_comment('# Initial list of people of interest:')
        for handle in self.interestSet:
            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()
        self.progress.close()


    def findParents(self):
        # we need to start with all of our "people of interest"
        ancestorsNotYetProcessed = set(self.interestSet)

        # now we find all the immediate ancestors of our people of interest

        while len(ancestorsNotYetProcessed) > 0:
            handle = ancestorsNotYetProcessed.pop()
            self.progress.step()

            # 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.peopleToOutput:

                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.includePrivate:
                    continue

                # remember this person!
                self.peopleToOutput.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 familyHandle in person.get_family_handle_list():
                    family = self.db.get_family_from_handle(familyHandle)
                    spouseHandle = ReportUtils.find_spouse(person, family)
                    if spouseHandle:
                        if spouseHandle in self.peopleToOutput or spouseHandle in ancestorsNotYetProcessed:
                            self.familiesToOutput.add(familyHandle)

                # 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.peopleToOutput))):
                    # 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 familyHandle in person.get_parent_family_handle_list():
                    family = self.db.get_family_from_handle(familyHandle)

                    if (family.private and self.includePrivate) or not family.private:

                        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 (father.private and self.includePrivate) or not father.private:
                                ancestorsNotYetProcessed.add(family.get_father_handle())
                                self.familiesToOutput.add(familyHandle)
                        if mother:
                            if (mother.private and self.includePrivate) or not mother.private:
                                ancestorsNotYetProcessed.add(family.get_mother_handle())
                                self.familiesToOutput.add(familyHandle)


    def removeUninterestingParents(self):
        # start with all the people we've already identified
        parentsNotYetProcessed = set(self.peopleToOutput)

        while len(parentsNotYetProcessed) > 0:
            handle = parentsNotYetProcessed.pop()
            self.progress.step()
            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?

            childHandle = None
            numberOfChildren = 0
            spouseHandle = None
            numberOfSpouse = 0
            fatherHandle = None
            motherHandle = None
            spouseFatherHandle = None
            spouseMotherHandle = None
            spouseSurname = ""
            surname = person.get_primary_name().get_surname().encode('iso-8859-1','xmlcharrefreplace')

            # first we get the person's father and mother
            for familyHandle in person.get_parent_family_handle_list():
                family = self.db.get_family_from_handle(familyHandle)
                handle = family.get_father_handle()
                if handle in self.peopleToOutput:
                    fatherHandle = handle
                handle = family.get_mother_handle()
                if handle in self.peopleToOutput:
                    motherHandle = handle

            # now see how many spouses this person has
            for familyHandle in person.get_family_handle_list():
                family = self.db.get_family_from_handle(familyHandle)
                handle = ReportUtils.find_spouse(person, family)
                if handle in self.peopleToOutput:
                    numberOfSpouse += 1
                    spouse = self.db.get_person_from_handle(handle)
                    spouseHandle = handle
                    spouseSurname = spouse.get_primary_name().get_surname().encode('iso-8859-1','xmlcharrefreplace')

                    # see if the spouse has parents
                    if spouseFatherHandle == None and spouseMotherHandle == None:
                        for familyHandle in spouse.get_parent_family_handle_list():
                            family = self.db.get_family_from_handle(familyHandle)
                            handle = family.get_father_handle()
                            if handle in self.peopleToOutput:
                                spouseFatherHandle = handle
                            handle = family.get_mother_handle()
                            if handle in self.peopleToOutput:
                                spouseMotherHandle = handle

            # get the number of children that we think might be interesting
            for familyHandle in person.get_family_handle_list():
                family = self.db.get_family_from_handle(familyHandle)
                for childRef in family.get_child_ref_list():
                    if childRef.ref in self.peopleToOutput:
                        numberOfChildren += 1
                        childHandle = childRef.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 numberOfChildren > 1:
                continue

            # if this person has many spouses of interest, then we
            # automatically keep this person
            if numberOfSpouse > 1:
                continue

            # if this person has parents, then we automatically keep
            # this person
            if fatherHandle != None or motherHandle != None:
                continue

            # if the spouse has parents, then we automatically keep
            # this person
            if spouseFatherHandle != None or spouseMotherHandle != None:
                continue;

            # if this is a person of interest, then we automatically keep
            if person.get_handle() in self.interestSet:
                continue;

            # if the spouse is a person of interest, then we keep
            if spouseHandle in self.interestSet:
                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.interestSet:
                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 == spouseSurname:
                    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.surnameColours:
                continue

            # if we have a special colour to use for the spouse,
            # then we automatically keep this person
            if spouseSurname in self.surnameColours:
                continue

            # took us a while, but if we get here, then we can remove this person
            self.deletedPeople += 1
            self.peopleToOutput.remove(person.get_handle())

            # we can also remove any families to which this person belonged
            for familyHandle in person.get_family_handle_list():
                if familyHandle in self.familiesToOutput:
                    self.deletedFamilies += 1
                    self.familiesToOutput.remove(familyHandle)

            # if we have a spouse, then ensure we queue up the spouse
            if spouseHandle:
                if spouseHandle not in parentsNotYetProcessed:
                    parentsNotYetProcessed.add(spouseHandle)

            # if we have a child, then ensure we queue up the child
            if childHandle:
                if childHandle not in parentsNotYetProcessed:
                    parentsNotYetProcessed.add(childHandle)


    def findChildren(self):
        # we need to start with all of our "people of interest"
        childrenNotYetProcessed = set(self.interestSet)
        childrenToInclude = set()

        # now we find all the children of our people of interest

        while len(childrenNotYetProcessed) > 0:
            handle = childrenNotYetProcessed.pop()
            self.progress.step()

            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.includePrivate:
                    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 familyHandle in person.get_family_handle_list():
                    family = self.db.get_family_from_handle(familyHandle)
                    if (family.private and self.includePrivate) 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.includePrivate) or not child.private:
                                childrenNotYetProcessed.add(child.get_handle())
                                self.familiesToOutput.add(familyHandle)

                        # include the spouse from this person's family
                        spouseHandle = ReportUtils.find_spouse(person, family)
                        if spouseHandle:
                            spouse = self.db.get_person_from_handle(spouseHandle)
                            if (spouse.private and self.includePrivate) or not spouse.private:
                                childrenToInclude.add(spouseHandle)
                                self.familiesToOutput.add(familyHandle)

        # we now merge our temp set "childrenToInclude" into our master set
        self.peopleToOutput.update(childrenToInclude)


    def writePeople(self):

        self.doc.add_comment('')

        # if we're going to attempt to include images, then use the HTML style of .dot file
        bUseHtmlOutput = False
        if self.includeImages:
            bUseHtmlOutput = True

        # loop through all the people we need to output
        for handle in self.peopleToOutput:
            self.progress.step()
            person = self.db.get_person_from_handle(handle)
            name = person.get_primary_name().get_regular_name()

            # figure out what colour to use
            colour = self.colourUnknown
            if person.get_gender() ==  gen.lib.Person.MALE:
                colour = self.colourMales
            if person.get_gender() ==  gen.lib.Person.FEMALE:
                colour = self.colourFemales

            # 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.surnameColours:
                colour = self.surnameColours[surname]

            # see if we have a birth/death or fallback dates we can use
            if self.includeDates or self.includePlaces:
                bth_event = ReportUtils.get_birth_or_fallback(self.db, person)
                dth_event = ReportUtils.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.includeDates:
                if (bth_event.private and self.includePrivate) or \
                        not bth_event.private:
                    date = bth_event.get_date_object()
                    birthStr = _dd.display(date)

            # get birth place (one of:  city, state, or country) we can use
            birthplace = None
            if bth_event and self.includePlaces:
                if (bth_event.private and self.includePrivate) or \
                        not bth_event.private:
                    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.includeDates:
                if (dth_event.private and self.includePrivate) or \
                        not dth_event.private:
                    date = dth_event.get_date_object()
                    deathStr = _dd.display(date)

            # get death place (one of:  city, state, or country) we can use
            deathplace = None
            if dth_event and self.includePlaces:
                if (dth_event.private and self.includePrivate) or \
                        not dth_event.private:
                    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.includeImages:
                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())
                                                                 )

            # put the label together and ouput this person
            label = u""
            lineDelimiter = '\\n'
            if bUseHtmlOutput:
                lineDelimiter = '<BR/>'

            # if we have an image, then start an HTML table; remember to close the table afterwards!
            if imagePath:
                label = u'<TABLE BORDER="0" CELLSPACING="2" CELLPADDING="0" CELLBORDER="0"><TR><TD><IMG SRC="%s"/></TD>'  % imagePath
                if self.imageOnTheSide == 0:
                    label += u'</TR><TR>'
                label += '<TD>'

            # 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 += '</TD></TR></TABLE>'

            self.doc.add_node(
                id=person.get_gramps_id(),
                label=label,
                shape='box',
                fillcolor=colour,
                htmloutput=bUseHtmlOutput)


    def writeFamilies(self):

        self.doc.add_comment('')

        # loop through all the families we need to output
        for familyHandle in self.familiesToOutput:
            self.progress.step()
            family = self.db.get_family_from_handle(familyHandle)
            fgid = family.get_gramps_id()

            # figure out a wedding date or placename we can use
            weddingDate = None
            weddingPlace = None
            if self.includeDates or self.includePlaces:
                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:
                        # get the wedding date
                        if (event.private and self.includePrivate) or not event.private:
                            if self.includeDates:
                                date = event.get_date_object()
                                weddingDate = _dd.display(date)
                            # get the wedding location
                            if self.includePlaces:
                                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.includeNumChildren:
                numberOfChildren = len(family.get_child_ref_list())
#                if numberOfChildren == 1:
#                    childrenStr = _('1 child')
                if numberOfChildren > 1:
                    childrenStr = _('%d children') % numberOfChildren

            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
            self.doc.add_node(fgid, label, "ellipse", "", "filled", self.colourFamilies)
            

        # now that we have the families written, go ahead and link the parents and children to the families
        for familyHandle in self.familiesToOutput:
            self.progress.step()

            # get the parents for this family
            family = self.db.get_family_from_handle(familyHandle)
            fgid = family.get_gramps_id()
            fatherHandle = family.get_father_handle()
            motherHandle = family.get_mother_handle()

            if self.useSubgraphs and fatherHandle and motherHandle:
                self.doc.start_subgraph(fgid)

            self.doc.add_comment('')

            # see if we have a father to link to this family
            if fatherHandle:
                if fatherHandle in self.peopleToOutput:
                    father = self.db.get_person_from_handle(fatherHandle)
                    comment = "father: %s" % father.get_primary_name().get_regular_name()
                    self.doc.add_link(fgid, father.get_gramps_id(), comment=comment)

            # see if we have a mother to link to this family
            if motherHandle:
                if motherHandle in self.peopleToOutput:
                    mother = self.db.get_person_from_handle(motherHandle)
                    comment = "mother: %s" % mother.get_primary_name().get_regular_name()
                    self.doc.add_link(fgid, mother.get_gramps_id(), comment=comment)

            if self.useSubgraphs and fatherHandle and motherHandle:
                self.doc.end_subgraph()

            # link the children to the family
            for childRef in family.get_child_ref_list():
                if childRef.ref in self.peopleToOutput:
                    child = self.db.get_person_from_handle(childRef.ref)
                    comment = "child:  %s" % child.get_primary_name().get_regular_name()
                    self.doc.add_link(child.get_gramps_id(), fgid, comment=comment)


#------------------------------------------------------------------------
#
# register_report() is defined in _PluginMgr.py and
# is used to hook the plugin into GRAMPS so that it
# appears in the "Reports" menu options
#
#------------------------------------------------------------------------
register_report(
    name            = 'familylines_graph',
    translated_name = _("Family Lines Graph"),
    category        = CATEGORY_GRAPHVIZ,
    report_class    = FamilyLinesReport,
    options_class   = FamilyLinesOptions,
    modes           = MODE_GUI,
    status          = _("Stable"),
    author_name     = "Stephane Charette",
    author_email    = "stephanecharette@gmail.com",
    description     = _("Produces family line graphs using GraphViz"),
    )

