diff --git a/src/plugins/Records.py b/src/plugins/Records.py
new file mode 100644
index 000000000..f6e5210c2
--- /dev/null
+++ b/src/plugins/Records.py
@@ -0,0 +1,654 @@
+# encoding:utf-8
+#
+# Gramps - a GTK+/GNOME based genealogy program - Records plugin
+#
+# Copyright (C) 2008 Reinhard Müller
+#
+# 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: $
+
+#------------------------------------------------------------------------
+#
+# Standard Python modules
+#
+#------------------------------------------------------------------------
+import datetime
+
+#------------------------------------------------------------------------
+#
+# GRAMPS modules
+#
+#------------------------------------------------------------------------
+from gen.lib import Date, EventType, Name
+import BaseDoc
+from BasicUtils import name_displayer
+from DataViews import register, Gramplet
+from gen.plug.menu import (BooleanOption, EnumeratedListOption,
+ FilterOption, PersonOption)
+from ReportBase import Report, ReportUtils, MenuReportOptions, \
+ CATEGORY_TEXT
+from gen.plug import PluginManager
+
+MODE_GUI = PluginManager.REPORT_MODE_GUI
+MODE_BKI = PluginManager.REPORT_MODE_BKI
+MODE_CLI = PluginManager.REPORT_MODE_CLI
+
+# from TransUtils import sgettext as _
+
+
+#------------------------------------------------------------------------
+#
+# Global functions
+#
+#------------------------------------------------------------------------
+def _find_records(db, filter, callname):
+
+ today = datetime.date.today()
+ today_date = Date(today.year, today.month, today.day)
+
+ # Person records
+ person_youngestliving = []
+ person_oldestliving = []
+ person_youngestdied = []
+ person_oldestdied = []
+ person_youngestmarried = []
+ person_oldestmarried = []
+ person_youngestdivorced = []
+ person_oldestdivorced = []
+ person_youngestfather = []
+ person_youngestmother = []
+ person_oldestfather = []
+ person_oldestmother = []
+
+ person_handle_list = db.get_person_handles(sort_handles=False)
+
+ if filter:
+ person_handle_list = filter.apply(db, person_handle_list)
+
+ for person_handle in person_handle_list:
+ person = db.get_person_from_handle(person_handle)
+
+ birth_ref = person.get_birth_ref()
+
+ if not birth_ref:
+ # No birth event, so we can't calculate any age.
+ continue
+
+ birth = db.get_event_from_handle(birth_ref.ref)
+ birth_date = birth.get_date_object()
+
+ death_ref = person.get_death_ref()
+ if death_ref:
+ death = db.get_event_from_handle(death_ref.ref)
+ death_date = death.get_date_object()
+ else:
+ death_date = None
+
+ if not birth_date.is_regular():
+ # Birth date unknown or incomplete, so we can't calculate any age.
+ continue
+
+ name = _person_get_display_name(person, callname)
+
+ if death_ref is None:
+ # Still living, look for age records
+ _record(person_youngestliving, person_oldestliving,
+ today_date - birth_date, name, 'Person', person_handle)
+ elif death_date.is_regular():
+ # Already died, look for age records
+ _record(person_youngestdied, person_oldestdied,
+ death_date - birth_date, name, 'Person', person_handle)
+
+ for family_handle in person.get_family_handle_list():
+ family = db.get_family_from_handle(family_handle)
+
+ marriage_date = None
+ divorce_date = None
+ for event_ref in family.get_event_ref_list():
+ event = db.get_event_from_handle(event_ref.ref)
+ if event.get_type() == EventType.MARRIAGE:
+ marriage_date = event.get_date_object()
+ elif event.get_type() == EventType.DIVORCE:
+ divorce_date = event.get_date_object()
+
+ if marriage_date is not None and marriage_date.is_regular():
+ _record(person_youngestmarried, person_oldestmarried,
+ marriage_date - birth_date,
+ name, 'Person', person_handle)
+
+ if divorce_date is not None and divorce_date.is_regular():
+ _record(person_youngestdivorced, person_oldestdivorced,
+ divorce_date - birth_date,
+ name, 'Person', person_handle)
+
+ for child_ref in family.get_child_ref_list():
+ child = db.get_person_from_handle(child_ref.ref)
+
+ child_birth_ref = child.get_birth_ref()
+ if not child_birth_ref:
+ continue
+
+ child_birth = db.get_event_from_handle(child_birth_ref.ref)
+ child_birth_date = child_birth.get_date_object()
+
+ if not child_birth_date.is_regular():
+ continue
+
+ if person.get_gender() == person.MALE:
+ _record(person_youngestfather, person_oldestfather,
+ child_birth_date - birth_date,
+ name, 'Person', person_handle)
+ elif person.get_gender() == person.FEMALE:
+ _record(person_youngestmother, person_oldestmother,
+ child_birth_date - birth_date,
+ name, 'Person', person_handle)
+
+
+ # Family records
+ family_mostchildren = []
+ family_youngestmarried = []
+ family_oldestmarried = []
+ family_shortest = []
+ family_longest = []
+
+ for family_handle in db.get_family_handles():
+ family = db.get_family_from_handle(family_handle)
+
+ father_handle = family.get_father_handle()
+ if not father_handle:
+ continue
+ mother_handle = family.get_mother_handle()
+ if not mother_handle:
+ continue
+
+ # Test if either father or mother are in filter
+ if filter:
+ if not filter.apply(db, [father_handle, mother_handle]):
+ continue
+
+ father = db.get_person_from_handle(father_handle)
+ mother = db.get_person_from_handle(mother_handle)
+
+ name = _("%s and %s") % (
+ _person_get_display_name(father, callname),
+ _person_get_display_name(mother, callname))
+
+ _record(None, family_mostchildren,
+ len(family.get_child_ref_list()),
+ name, 'Family', family_handle)
+
+ marriage = None
+ divorce = None
+ marriage_date = None
+ divorce_date = None
+ for event_ref in family.get_event_ref_list():
+ event = db.get_event_from_handle(event_ref.ref)
+ if event.get_type() == EventType.MARRIAGE:
+ marriage = event
+ marriage_date = event.get_date_object()
+ elif event.get_type() == EventType.DIVORCE:
+ divorce = event
+ divorce_date = event.get_date_object()
+
+ father_death = None
+ father_death_date = None
+ father_death_ref = father.get_death_ref()
+ if father_death_ref:
+ father_death = db.get_event_from_handle(father_death_ref.ref)
+ father_death_date = father_death.get_date_object()
+
+ mother_death = None
+ mother_death_date = None
+ mother_death_ref = mother.get_death_ref()
+ if mother_death_ref:
+ mother_death = db.get_event_from_handle(mother_death_ref.ref)
+ mother_death_date = mother_death.get_date_object()
+
+ if not marriage or not marriage_date.is_regular():
+ # Not married or marriage date unknown
+ continue
+
+ if divorce and not divorce_date.is_regular():
+ # Divorced, but divorce date unknown
+ continue
+
+ if father_death and (not father_death_date or not father_death_date.is_regular()):
+ # Father dead, but death date unknown
+ continue
+
+ if mother_death and (not mother_death_date or not mother_death_date.is_regular()):
+ # Mother dead, but death date unknown
+ continue
+
+ if not divorce and not father_death and not mother_death:
+ # Still married and alive
+ _record(family_youngestmarried, family_oldestmarried,
+ today_date - marriage_date,
+ name, 'Family', family_handle)
+ else:
+ end = None
+ if father_death and mother_death:
+ end = min(father_death_date, mother_death_date)
+ elif father_death:
+ end = father_death_date
+ elif mother_death:
+ end = mother_death_date
+ if divorce:
+ if end:
+ end = min(end, divorce_date)
+ else:
+ end = divorce_date
+ duration = end - marriage_date
+
+ _record(family_shortest, family_longest,
+ end - marriage_date, name, 'Family', family_handle)
+
+ return [(text, varname, locals()[varname]) for (text, varname, default) in RECORDS]
+
+
+def _person_get_display_name(person, callname):
+
+ # Make a copy of the name object so we don't mess around with the real
+ # data.
+ n = Name(source=person.get_primary_name())
+
+ if n.call:
+ if callname == RecordsReportOptions.CALLNAME_REPLACE:
+ n.first_name = n.call
+ elif callname == RecordsReportOptions.CALLNAME_UNDERLINE_ADD:
+ if n.call in n.first_name:
+ (before, after) = n.first_name.split(n.call)
+ n.first_name = "%(before)s%(call)s%(after)s" % {
+ 'before': before,
+ 'call': n.call,
+ 'after': after}
+ else:
+ n.first_name = "\"%(call)s\" (%(first)s)" % {
+ 'call': n.call,
+ 'first': n.first_name}
+
+ return name_displayer.display_name(n)
+
+
+def _record(lowest, highest, value, text, handle_type, handle):
+
+ if lowest is not None:
+ lowest.append((value, text, handle_type, handle))
+ lowest.sort(lambda a,b: cmp(a[0], b[0]))
+ for i in range(3, len(lowest)):
+ if lowest[i-1][0] < lowest[i][0]:
+ del lowest[i:]
+ break
+
+ if highest is not None:
+ highest.append((value, text, handle_type, handle))
+ highest.sort(reverse=True)
+ for i in range(3, len(highest)):
+ if highest[i-1][0] > highest[i][0]:
+ del highest[i:]
+ break
+
+
+def _output(value):
+
+ if isinstance(value, tuple) and len(value) == 3:
+ # time span as years, months, days
+ (years, months, days) = value
+ result = []
+ if years == 1:
+ result.append(_("1 year"))
+ elif years != 0:
+ result.append(_("%s years") % years)
+ if months == 1:
+ result.append(_("1 month"))
+ elif months != 0:
+ result.append(_("%s months") % months)
+ if days == 1:
+ result.append(_("1 day"))
+ elif days != 0:
+ result.append(_("%s days") % days)
+ if not result:
+ result.append(_("0 days"))
+ return ", ".join(result)
+ else:
+ return str(value)
+
+
+#------------------------------------------------------------------------
+#
+# The Gramplet
+#
+#------------------------------------------------------------------------
+class RecordsGramplet(Gramplet):
+
+ def init(self):
+ self.set_use_markup(True)
+ self.tooltip = _("Double-click name for details")
+ self.set_text(_("No Family Tree loaded."))
+
+
+ def db_changed(self):
+
+ self.dbstate.db.connect('person-add', self.update)
+ self.dbstate.db.connect('person-delete', self.update)
+ self.dbstate.db.connect('person-update', self.update)
+ self.dbstate.db.connect('family-add', self.update)
+ self.dbstate.db.connect('family-delete', self.update)
+ self.dbstate.db.connect('family-update', self.update)
+
+
+ def main(self):
+
+ self.set_text(_("Processing...") + "\n")
+ records = _find_records(self.dbstate.db, None,
+ RecordsReportOptions.CALLNAME_DONTUSE)
+ self.set_text("")
+ for (text, varname, top3) in records:
+ self.render_text("%s" % text)
+ last_value = None
+ rank = 0
+ for (number, (value, name, handletype, handle)) in enumerate(top3):
+ if value != last_value:
+ last_value = value
+ rank = number
+ self.append_text("\n %s. " % (rank+1))
+ # TODO: When linktype 'Family' is introduced, use this:
+ # self.link(name, handletype, handle)
+ # TODO: instead of this:
+ if handletype == 'Family':
+ family = self.dbstate.db.get_family_from_handle(handle)
+ father_handle = family.get_father_handle()
+ father = self.dbstate.db.get_person_from_handle(father_handle)
+ father_name = _person_get_display_name(father, RecordsReportOptions.CALLNAME_DONTUSE)
+ self.link(father_name, 'Person', father_handle)
+ self.append_text(_(" and "))
+ mother_handle = family.get_mother_handle()
+ mother = self.dbstate.db.get_person_from_handle(mother_handle)
+ mother_name = _person_get_display_name(mother, RecordsReportOptions.CALLNAME_DONTUSE)
+ self.link(mother_name, 'Person', mother_handle)
+ else:
+ self.link(name, handletype, handle)
+ # TODO: end.
+ self.append_text(" (%s)" % _output(value))
+ self.append_text("\n")
+ self.append_text("", scroll_to='begin')
+
+
+#------------------------------------------------------------------------
+#
+# The Report
+#
+#------------------------------------------------------------------------
+class RecordsReport(Report):
+
+ def __init__(self, database, options_class):
+
+ Report.__init__(self, database, options_class)
+ menu = options_class.menu
+
+ self.filter_option = menu.get_option_by_name('filter')
+ self.filter = self.filter_option.get_filter()
+
+ self.callname = menu.get_option_by_name('callname').get_value()
+
+ self.include = {}
+ for (text, varname, default) in RECORDS:
+ self.include[varname] = menu.get_option_by_name(varname).get_value()
+
+
+ def write_report(self):
+ """
+ Build the actual report.
+ """
+
+ records = _find_records(self.database, self.filter, self.callname)
+
+ self.doc.start_paragraph('REC-Title')
+ self.doc.write_text(_("Records"))
+ self.doc.end_paragraph()
+
+ for (text, varname, top3) in records:
+ if not self.include[varname]:
+ continue
+
+ self.doc.start_paragraph('REC-Heading')
+ self.doc.write_text(text)
+ self.doc.end_paragraph()
+
+ last_value = None
+ rank = 0
+ for (number, (value, name, handletype, handle)) in enumerate(top3):
+ if value != last_value:
+ last_value = value
+ rank = number
+ self.doc.start_paragraph('REC-Normal')
+ self.doc.write_text(_("%(number)s. %(name)s (%(value)s)") % {
+ 'number': rank+1,
+ 'name': name,
+ 'value': _output(value)})
+ self.doc.end_paragraph()
+
+
+#------------------------------------------------------------------------
+#
+# MenuReportOptions
+#
+#------------------------------------------------------------------------
+class RecordsReportOptions(MenuReportOptions):
+ """
+ Defines options and provides handling interface.
+ """
+
+ CALLNAME_DONTUSE = 0
+ CALLNAME_REPLACE = 1
+ CALLNAME_UNDERLINE_ADD = 2
+
+ def __init__(self, name, dbase):
+
+ self.__pid = None
+ self.__filter = None
+ self.__db = dbase
+ MenuReportOptions.__init__(self, name, dbase)
+
+
+ def add_menu_options(self, menu):
+
+ category_name = _("Report Options")
+
+ self.__filter = FilterOption(_("Filter"), 0)
+ self.__filter.set_help(
+ _("Determines what people are included in the report"))
+ menu.add_option(category_name, "filter", self.__filter)
+ self.__filter.connect('value-changed', self.__filter_changed)
+
+ self.__pid = PersonOption(_("Filter Person"))
+ self.__pid.set_help(_("The center person for the filter"))
+ menu.add_option(category_name, "pid", self.__pid)
+ self.__pid.connect('value-changed', self.__update_filters)
+
+ self.__update_filters()
+
+ callname = EnumeratedListOption(_("Use call name"), self.CALLNAME_DONTUSE)
+ callname.set_items([
+ (self.CALLNAME_DONTUSE, _("Don't use call name")),
+ (self.CALLNAME_REPLACE, _("Replace first name with call name")),
+ (self.CALLNAME_UNDERLINE_ADD, _("Underline call name in first name / add call name to first name"))])
+ menu.add_option(category_name, "callname", callname)
+
+ for (text, varname, default) in RECORDS:
+ option = BooleanOption(text, default)
+ if varname.startswith('person'):
+ category_name = _("Person Records")
+ elif varname.startswith('family'):
+ category_name = _("Family Records")
+ menu.add_option(category_name, varname, option)
+
+
+ def __update_filters(self):
+ """
+ Update the filter list based on the selected person
+ """
+ gid = self.__pid.get_value()
+ person = self.__db.get_person_from_gramps_id(gid)
+ filter_list = ReportUtils.get_person_filters(person, False)
+ self.__filter.set_filters(filter_list)
+
+
+ def __filter_changed(self):
+ """
+ Handle filter change. If the filter is not specific to a person,
+ disable the person option
+ """
+ filter_value = self.__filter.get_value()
+ if filter_value in [1, 2, 3, 4]:
+ # Filters 1, 2, 3 and 4 rely on the center person
+ self.__pid.set_available(True)
+ else:
+ # The rest don't
+ self.__pid.set_available(False)
+
+
+ def make_default_style(self, default_style):
+
+ #Paragraph Styles
+ font = BaseDoc.FontStyle()
+ font.set_type_face(BaseDoc.FONT_SANS_SERIF)
+ font.set_size(10)
+ font.set_bold(0)
+ para = BaseDoc.ParagraphStyle()
+ para.set_font(font)
+ para.set_description(_('The basic style used for the text display.'))
+ default_style.add_paragraph_style('REC-Normal', para)
+
+ font = BaseDoc.FontStyle()
+ font.set_type_face(BaseDoc.FONT_SANS_SERIF)
+ font.set_size(10)
+ font.set_bold(1)
+ para = BaseDoc.ParagraphStyle()
+ para.set_font(font)
+ para.set_description(_('The style used for headings.'))
+ default_style.add_paragraph_style('REC-Heading', para)
+
+ font = BaseDoc.FontStyle()
+ font.set_type_face(BaseDoc.FONT_SANS_SERIF)
+ font.set_size(12)
+ font.set_bold(1)
+ para = BaseDoc.ParagraphStyle()
+ para.set_font(font)
+ para.set_description(_("The style used for the report title"))
+ default_style.add_paragraph_style('REC-Title', para)
+
+
+#------------------------------------------------------------------------
+#
+# Translation hack
+#
+#------------------------------------------------------------------------
+mytranslation = {
+ u"Records" : u"Rekorde",
+ u"%s and %s" : u"%s und %s",
+ u" and " : u" und ",
+ u"1 year" : u"1 Jahr",
+ u"%s years" : u"%s Jahre",
+ u"1 month" : u"1 Monat",
+ u"%s months" : u"%s Monate",
+ u"1 day" : u"1 Tag",
+ u"%s days" : u"%s Tage",
+ u"0 days" : u"0 Tage",
+ u"Youngest living person" : u"Nesthäkchen",
+ u"Oldest living person" : u"Älteste lebende Person",
+ u"Person died at youngest age" : u"Am jüngsten gestorbene Person",
+ u"Person died at oldest age" : u"Im höchsten Alter gestorbene Person",
+ u"Person married at youngest age" : u"Am jüngsten geheiratete Person",
+ u"Person married at oldest age" : u"Am ältesten geheiratete Person",
+ u"Person divorced at youngest age": u"Am jüngsten geschiedene Person",
+ u"Person divorced at oldest age" : u"Am ältesten geschiedene Person",
+ u"Youngest father" : u"Jüngster Vater",
+ u"Youngest mother" : u"Jüngste Mutter",
+ u"Oldest father" : u"Ältester Vater",
+ u"Oldest mother" : u"Älteste Mutter",
+ u"Couple with most children" : u"Familie mit den meisten Kindern",
+ u"Couple married most recently" : u"Zuletzt geheiratetes Paar",
+ u"Couple married most long ago" : u"Am längsten verheiratetes Paar",
+ u"Shortest marriage" : u"Kürzeste Ehe",
+ u"Longest marriage" : u"Längste Ehe"}
+
+from gettext import gettext
+import locale
+lang = locale.getdefaultlocale()[0]
+if lang:
+ lang = lang.split('_')[0]
+def _(string):
+ if lang == 'de':
+ print string
+ return mytranslation.get(string, gettext(string))
+ else:
+ return gettext(string)
+
+
+#------------------------------------------------------------------------
+#
+# List of records (must be defined after declaration of _())
+#
+#------------------------------------------------------------------------
+RECORDS = [
+ (_("Youngest living person"), 'person_youngestliving', True),
+ (_("Oldest living person"), 'person_oldestliving', True),
+ (_("Person died at youngest age"), 'person_youngestdied', False),
+ (_("Person died at oldest age"), 'person_oldestdied', True),
+ (_("Person married at youngest age"), 'person_youngestmarried', True),
+ (_("Person married at oldest age"), 'person_oldestmarried', True),
+ (_("Person divorced at youngest age"), 'person_youngestdivorced', False),
+ (_("Person divorced at oldest age"), 'person_oldestdivorced', False),
+ (_("Youngest father"), 'person_youngestfather', True),
+ (_("Youngest mother"), 'person_youngestmother', True),
+ (_("Oldest father"), 'person_oldestfather', True),
+ (_("Oldest mother"), 'person_oldestmother', True),
+ (_("Couple with most children"), 'family_mostchildren', True),
+ (_("Couple married most recently"), 'family_youngestmarried', True),
+ (_("Couple married most long ago"), 'family_oldestmarried', True),
+ (_("Shortest marriage"), 'family_shortest', False),
+ (_("Longest marriage"), 'family_longest', True)]
+
+
+#------------------------------------------------------------------------
+#
+# Register the gramplet and the report
+#
+#------------------------------------------------------------------------
+pmgr = PluginManager.get_instance()
+register(
+ type="gramplet",
+ name= "Records Gramplet",
+ tname=_("Records Gramplet"),
+ height=230,
+ expand=True,
+ content = RecordsGramplet,
+ title=_("Records"))
+
+pmgr.register_report(
+ name = 'records',
+ category = CATEGORY_TEXT,
+ report_class = RecordsReport,
+ options_class = RecordsReportOptions,
+ modes = MODE_GUI | MODE_BKI | MODE_CLI,
+ translated_name = _("Records Report"),
+ status = _("Stable"),
+ author_name = u"Reinhard Müller",
+ author_email = "reinhard.mueller@bytewise.at",
+ description = _(
+ "Shows some interesting records about people and families"))
diff --git a/src/plugins/WhatsNext.py b/src/plugins/WhatsNext.py
new file mode 100644
index 000000000..364fe9d93
--- /dev/null
+++ b/src/plugins/WhatsNext.py
@@ -0,0 +1,475 @@
+# encoding: utf-8
+#
+# Gramps - a GTK+/GNOME based genealogy program - What Next Gramplet plugin
+#
+# Copyright (C) 2008 Reinhard Mueller
+#
+# 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: $
+
+#------------------------------------------------------------------------
+#
+# GRAMPS modules
+#
+#------------------------------------------------------------------------
+from gen.lib import EventType, FamilyRelType
+from BasicUtils import name_displayer
+from DataViews import register, Gramplet
+from ReportBase import ReportUtils
+#from TransUtils import sgettext as _
+
+#------------------------------------------------------------------------
+#
+# The Gramplet
+#
+#------------------------------------------------------------------------
+class WhatNextGramplet(Gramplet):
+
+ # Minimum number of lines we want to see. Further lines with the same
+ # distance to the main person will be added on top of this.
+ TODOS_WANTED = 10
+
+ # How many generations of descendants to process before we go up to the
+ # next level of ancestors.
+ DOWNS_PER_UP = 2
+
+ # After an ancestor was processed, how many extra rounds to delay until the
+ # descendants of this ancestor are processed.
+ ANCESTOR_DELAY = 1
+
+ # After a spouse was processed, how many extra rounds to delay until the
+ # ancestors of this spouse are processed.
+ SPOUSE_DELAY = 1
+
+ def init(self):
+
+ self.tooltip = _("Double-click name for details")
+ self.set_text(_("No Family Tree loaded."))
+
+
+ def db_changed(self):
+
+ self.dbstate.db.connect('person-add', self.update)
+ self.dbstate.db.connect('person-delete', self.update)
+ self.dbstate.db.connect('person-update', self.update)
+ self.dbstate.db.connect('family-add', self.update)
+ self.dbstate.db.connect('family-delete', self.update)
+ self.dbstate.db.connect('family-update', self.update)
+
+
+ def main(self):
+
+ default_person = self.dbstate.db.get_default_person()
+ if default_person == None:
+ self.set_text(_("No Home Person set."))
+ return
+
+ self.__counter = 0
+ self.__level = 1
+
+ self.set_text("")
+
+
+ # List of already processed persons and families, to avoid recursing
+ # back down to ourselves or meeting the same person through different
+ # paths.
+ self.__processed_persons = {default_person.get_handle(): True}
+ self.__processed_families = {}
+
+ # List of lists of ancestors in currently processed generation. We go
+ # up one generation in each round.
+ # The lists are separated into my own ancestors, the ancestors of my
+ # spouses, the ancestors of my children's spouses, the ancestors of my
+ # parent's other spouses, the ancestors of my grandchildren's spouses,
+ # the ancestors of my sibling's spouses etc.
+ ancestors = [[default_person]]
+ ancestors_queue = [[[default_person]]] + [[]] * self.ANCESTOR_DELAY
+
+ # List of lists of families of relatives in currently processed
+ # distance. We go up one level of distance in each round.
+ # For example, at the end of the third round, this is (potentially) a
+ # list of 4 lists:
+ # 1. my own great-grandchildren
+ # 2. grandchildren of my parents (= my nephews and nieces)
+ # 3. children of my grandparents (= my uncles and aunts)
+ # 4. my great-grandparents
+ # At the beginning of the fourth round, the other families of my
+ # great-grandparents are added (if they were married more than once).
+ # The separation into these levels is done to allow the spouses of the
+ # earlier level to be listed before the kins of the later level, e.g.
+ # the spouses of my nephews and nieces are listed before my uncles and
+ # aunts.
+ # Not that this may slightly vary with the parameters given at the
+ # beginning of this class definition, but the principle remains the
+ # same.
+ families = []
+ families_queue = [[]] * self.ANCESTOR_DELAY
+
+ # List of spouses to add to ancestors list so we track ancestors of
+ # spouses, too, but delayed as defined by the parameter.
+ spouses = []
+ spouses_queue = [[]] * self.SPOUSE_DELAY
+
+ while (ancestors or families):
+ # (Other) families of parents
+ for ancestor_group in ancestors_queue.pop(0):
+ new_family_group = []
+ new_spouses_group = []
+ for person in ancestor_group:
+ for family in self.__get_families(person):
+ spouse = self.__get_spouse(person, family)
+ if spouse:
+ self.__process_person(spouse)
+ new_spouses_group.append(spouse)
+ elif family.get_relationship() == FamilyRelType.MARRIED:
+ self.__missing_spouse(person)
+ self.__process_family(family, person, spouse)
+ new_family_group.append(family)
+ if new_family_group:
+ families.append(new_family_group)
+ if new_spouses_group:
+ spouses.append(new_spouses_group)
+ if self.__counter >= self.TODOS_WANTED:
+ break
+
+ # Now add the spouses of last round to the list
+ spouses_queue.append(spouses)
+ ancestors += spouses_queue.pop(0)
+
+ # Separator between rounds
+ if self.__counter > 0:
+ self.append_text("\n")
+ self.__level += 1
+
+ # Next generation of children
+ spouses = []
+ for down in range(self.DOWNS_PER_UP):
+ new_families = []
+ for family_group in families:
+ children = []
+ for family in family_group:
+ for child in self.__get_children(family):
+ self.__process_person(child)
+ children.append(child)
+ if self.__counter >= self.TODOS_WANTED:
+ break
+
+ # Families of children
+ new_family_group = []
+ new_spouses_group = []
+ for person in children:
+ for family in self.__get_families(person):
+ spouse = self.__get_spouse(person, family)
+ if spouse:
+ self.__process_person(spouse)
+ new_spouses_group.append(spouse)
+ elif family.get_relationship() == FamilyRelType.MARRIED:
+ self.__missing_spouse(person)
+ self.__process_family(family, person, spouse)
+ new_family_group.append(family)
+ if new_family_group:
+ new_families.append(new_family_group)
+ if new_spouses_group:
+ spouses.append(new_spouses_group)
+ if self.__counter >= self.TODOS_WANTED:
+ break
+ families = new_families
+ if self.__counter >= self.TODOS_WANTED:
+ break
+ if self.__counter >= self.TODOS_WANTED:
+ break
+
+ # Parents
+ new_ancestors = []
+ new_families = []
+ for ancestor_group in ancestors:
+ new_ancestor_group = []
+ new_family_group = []
+ for person in ancestor_group:
+ (father, mother, family) = self.__get_parents(person)
+ if family:
+ if father:
+ self.__process_person(father)
+ new_ancestor_group.append(father)
+ elif family.get_relationship() == FamilyRelType.MARRIED:
+ self.__missing_father(person)
+ if mother:
+ self.__process_person(mother)
+ new_ancestor_group.append(mother)
+ else:
+ self.__missing_mother(person)
+ self.__process_family(family, father, mother)
+ new_family_group.append(family)
+ else:
+ self.__missing_parents(person)
+ if new_ancestor_group:
+ new_ancestors.append(new_ancestor_group)
+ if new_family_group:
+ new_families.append(new_family_group)
+ if self.__counter >= self.TODOS_WANTED:
+ break
+ ancestors = new_ancestors
+ ancestors_queue.append(ancestors)
+ families_queue.append(new_families)
+ families += families_queue.pop(0)
+ if self.__counter >= self.TODOS_WANTED:
+ break
+
+ self.append_text("", scroll_to='begin')
+
+
+ def __process_person(self, person):
+
+ self.__processed_persons[person.get_handle()] = True
+
+ missingbits = []
+
+ primary_name = person.get_primary_name()
+
+ if not primary_name.get_first_name():
+ missingbits.append(_("first name unknown"))
+
+ if not primary_name.get_surname():
+ missingbits.append(_("surname unknown"))
+
+ name = name_displayer.display_name(primary_name)
+ if not name:
+ name = _("(person with unknown name)")
+
+ has_birth = False
+
+ for event_ref in person.get_primary_event_ref_list():
+ event = self.dbstate.db.get_event_from_handle(event_ref.ref)
+ if event.get_type() not in [EventType.BIRTH, EventType.DEATH]:
+ continue
+ missingbits.extend(self.__process_event(event))
+ if event.get_type() == EventType.BIRTH:
+ has_birth = True
+
+ if not has_birth:
+ missingbits.append(_("birth event missing"))
+
+ if missingbits:
+ self.link(name, 'Person', person.get_handle())
+ self.append_text(_(": %(list)s\n") % {
+ 'list': _(", ").join(missingbits)})
+ self.__counter += 1
+
+
+ def __process_family(self, family, person1, person2):
+
+ self.__processed_families[family.get_handle()] = True
+
+ missingbits = []
+
+ if person1:
+ name1 = name_displayer.display(person1)
+ if not name1:
+ name1 = _("(person with unknown name)")
+ else:
+ name1 = _("(unknown person)")
+
+ if person2:
+ name2 = name_displayer.display(person2)
+ if not name2:
+ name2 = _("(person with unknown name)")
+ else:
+ name2 = _("(unknown person)")
+
+ name = _("%(name1)s and %(name2)s") % {
+ 'name1': name1,
+ 'name2': name2}
+
+ has_marriage = False
+
+ for event_ref in family.get_event_ref_list():
+ event = self.dbstate.db.get_event_from_handle(event_ref.ref)
+ if event.get_type() not in [EventType.MARRIAGE, EventType.DIVORCE]:
+ continue
+ missingbits.extend(self.__process_event(event))
+ if event.get_type() == EventType.MARRIAGE:
+ has_marriage = True
+
+ if family.get_relationship() == FamilyRelType.MARRIED:
+ if not has_marriage:
+ missingbits.append(_("marriage event missing"))
+ elif family.get_relationship() == FamilyRelType.UNKNOWN:
+ missingbits.append(_("relation type unknown"))
+
+ if missingbits:
+ # TODO: When linktype 'Family' is introduced, use this:
+ # self.link(name, 'Family', family.get_handle())
+ # TODO: instead of this:
+ if person1:
+ self.link(name1, 'Person', person1.get_handle())
+ else:
+ self.append_text(name1)
+ self.append_text(_(" and "))
+ if person2:
+ self.link(name2, 'Person', person2.get_handle())
+ else:
+ self.append_text(name2)
+ # TODO: end.
+ self.append_text(_(": %(list)s\n") % {
+ 'list': _(", ").join(missingbits)})
+ self.__counter += 1
+
+
+ def __process_event(self, event):
+
+ missingbits = []
+
+ date = event.get_date_object()
+ if date.is_empty():
+ missingbits.append(_("date unknown"))
+ elif not date.is_regular():
+ missingbits.append(_("date incomplete"))
+
+ place_handle = event.get_place_handle()
+ if not place_handle:
+ missingbits.append(_("place unknown"))
+
+ if missingbits:
+ return [_("%(type)s: %(list)s") % {
+ 'type': event.get_type(),
+ 'list': _(", ").join(missingbits)}]
+ else:
+ return []
+
+
+ def __missing_spouse(self, person):
+ self.__missing_link(person, _("spouse missing"))
+
+
+ def __missing_father(self, person):
+ self.__missing_link(person, _("father missing"))
+
+
+ def __missing_mother(self, person):
+ self.__missing_link(person, _("mother missing"))
+
+
+ def __missing_parents(self, person):
+ self.__missing_link(person, _("parents missing"))
+
+
+ def __missing_link(self, person, text):
+
+ name = name_displayer.display(person)
+ self.link(name, 'Person', person.get_handle())
+ self.append_text(_(": %s\n") % text)
+ self.__counter += 1
+
+
+ def __get_spouse(self, person, family):
+
+ spouse_handle = ReportUtils.find_spouse(person, family)
+ if not spouse_handle:
+ return None
+ if spouse_handle in self.__processed_persons:
+ return None
+ return self.dbstate.db.get_person_from_handle(spouse_handle)
+
+
+ def __get_children(self, family):
+
+ for child_ref in family.get_child_ref_list():
+ if child_ref.ref in self.__processed_persons:
+ continue
+ yield self.dbstate.db.get_person_from_handle(child_ref.ref)
+
+
+ def __get_families(self, person):
+
+ for family_handle in person.get_family_handle_list():
+ if family_handle in self.__processed_families:
+ continue
+ yield self.dbstate.db.get_family_from_handle(family_handle)
+
+
+ def __get_parents(self, person):
+
+ family_handle = person.get_main_parents_family_handle()
+ if not family_handle or family_handle in self.__processed_families:
+ return (None, None, None)
+
+ family = self.dbstate.db.get_family_from_handle(family_handle)
+
+ father_handle = family.get_father_handle()
+ if father_handle and father_handle not in self.__processed_persons:
+ father = self.dbstate.db.get_person_from_handle(father_handle)
+ else:
+ father = None
+
+ mother_handle = family.get_mother_handle()
+ if mother_handle and mother_handle not in self.__processed_persons:
+ mother = self.dbstate.db.get_person_from_handle(mother_handle)
+ else:
+ mother = None
+
+ return (father, mother, family)
+
+
+#------------------------------------------------------------------------
+#
+# Translation hack
+#
+#------------------------------------------------------------------------
+mytranslation = {
+ u"first name unknown" : u"Vorname unbekannt",
+ u"surname unknown" : u"Familienname unbekannt",
+ u"(person with unknown name)": u"(Person mit unbekanntem Namen)",
+ u"birth event missing" : u"Geburtsereignis fehlt",
+ u"(unknown person)" : u"(unbekannte Person)",
+ u"%(name1)s and %(name2)s" : u"%(name1)s und %(name2)s",
+ u" and " : u" und ",
+ u"marriage event missing" : u"Hochzeitsereignis fehlt",
+ u"relation type unknown" : u"Beziehungstyp unbekannt",
+ u"date unknown" : u"Datum unbekannt",
+ u"date incomplete" : u"Datum unvollständig",
+ u"place unknown" : u"Ort unbekannt",
+ u"spouse missing" : u"Partner fehlt",
+ u"father missing" : u"Vater fehlt",
+ u"mother missing" : u"Mutter fehlt",
+ u"parents missing" : u"Eltern fehlen"}
+
+from gettext import gettext
+import locale
+lang = locale.getdefaultlocale()[0]
+if lang:
+ lang = lang.split('_')[0]
+def _(string):
+ if lang == 'de':
+ print string
+ return mytranslation.get(string, gettext(string))
+ else:
+ return gettext(string)
+
+
+#------------------------------------------------------------------------
+#
+# Register the gramplet
+#
+#------------------------------------------------------------------------
+register(
+ type = "gramplet",
+ name = "What's Next Gramplet",
+ tname =_("What's Next Gramplet"),
+ height = 230,
+ expand = True,
+ content = WhatNextGramplet,
+ title = _("What's Next?"))