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?"))