Calendar report enhancements

* Added symbols for birth, marriage and death
* Added symbols for dead spouse in anniversaries
* Optionally include death dates
* Refactored method `collect_data`
This commit is contained in:
Matthias Balk 2020-12-31 14:49:05 +01:00 committed by Nick Hall
parent b23a2138b0
commit d830918289

View File

@ -4,6 +4,7 @@
# Copyright (C) 2008-2012 Brian G. Matherly # Copyright (C) 2008-2012 Brian G. Matherly
# Copyright (C) 2010 Jakim Friant # Copyright (C) 2010 Jakim Friant
# Copyright (C) 2012-2014 Paul Franklin # Copyright (C) 2012-2014 Paul Franklin
# Copyright (C) 2020,2021 Matthias Balk
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@ -24,41 +25,40 @@
# python modules # python modules
# #
#------------------------------------------------------------------------ #------------------------------------------------------------------------
from functools import partial
import datetime import datetime
import time import time
from functools import partial
#------------------------------------------------------------------------ #------------------------------------------------------------------------
# #
# Gramps modules # Gramps modules
# #
#------------------------------------------------------------------------ #------------------------------------------------------------------------
import gramps.plugins.lib.libholiday as libholiday
from gramps.gen.config import config
from gramps.gen.const import GRAMPS_LOCALE as glocale from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext
from gramps.gen.const import URL_HOMEPAGE from gramps.gen.const import URL_HOMEPAGE
from gramps.gen.datehandler import displayer as date_displayer
from gramps.gen.display.name import displayer as _nd from gramps.gen.display.name import displayer as _nd
from gramps.gen.errors import ReportError from gramps.gen.errors import ReportError
from gramps.gen.plug.docgen import (FontStyle, ParagraphStyle, GraphicsStyle,
FONT_SERIF, PARA_ALIGN_CENTER,
PARA_ALIGN_LEFT, PARA_ALIGN_RIGHT,
IndexMark, INDEX_TYPE_TOC)
from gramps.gen.plug.docgen.fontscale import string_trim
from gramps.gen.plug.menu import (BooleanOption, StringOption, NumberOption,
EnumeratedListOption, FilterOption,
PersonOption)
from gramps.gen.plug.report import Report
from gramps.gen.plug.report import utils
from gramps.gen.plug.report import MenuReportOptions
from gramps.gen.plug.report import stdoptions
from gramps.gen.utils.alive import probably_alive
from gramps.gen.datehandler import displayer as date_displayer
from gramps.gen.lib import (Date, EventRoleType, EventType, Name, NameType, from gramps.gen.lib import (Date, EventRoleType, EventType, Name, NameType,
Person, Surname) Person, Surname)
from gramps.gen.lib.date import gregorian from gramps.gen.lib.date import gregorian
from gramps.gen.plug.docgen import (FONT_SERIF, INDEX_TYPE_TOC,
import gramps.plugins.lib.libholiday as libholiday PARA_ALIGN_CENTER, PARA_ALIGN_LEFT,
PARA_ALIGN_RIGHT, FontStyle, GraphicsStyle,
IndexMark, ParagraphStyle)
from gramps.gen.plug.docgen.fontscale import string_trim
from gramps.gen.plug.menu import (BooleanOption, EnumeratedListOption,
FilterOption, NumberOption, PersonOption,
StringOption)
from gramps.gen.plug.report import MenuReportOptions, Report, stdoptions, utils
from gramps.gen.utils.alive import probably_alive
from gramps.gen.utils.symbols import Symbols
from gramps.plugins.lib.libholiday import g2iso from gramps.plugins.lib.libholiday import g2iso
_ = glocale.translation.gettext
#------------------------------------------------------------------------ #------------------------------------------------------------------------
# #
# Constants # Constants
@ -101,6 +101,7 @@ class Calendar(Report):
self.start_dow = get_value('start_dow') self.start_dow = get_value('start_dow')
self.maiden_name = get_value('maiden_name') self.maiden_name = get_value('maiden_name')
self.alive = get_value('alive') self.alive = get_value('alive')
self.include_death_date = get_value('include_death_date')
self.birthdays = get_value('birthdays') self.birthdays = get_value('birthdays')
self.text1 = get_value('text1') self.text1 = get_value('text1')
self.text2 = get_value('text2') self.text2 = get_value('text2')
@ -115,6 +116,15 @@ class Calendar(Report):
self.set_locale(get_value('trans')) self.set_locale(get_value('trans'))
self.__setup_symbols()
def __setup_symbols(self):
symbols = Symbols()
self.symb_bth = symbols.get_symbol_for_string(symbols.SYMBOL_BIRTH)
self.symb_mrg = symbols.get_symbol_for_string(symbols.SYMBOL_MARRIAGE)
death_symbol_idx = config.get('utf8.death-symbol')
self.symb_dth = symbols.get_death_symbol_for_char(death_symbol_idx)
def get_name(self, person, maiden_name = None): def get_name(self, person, maiden_name = None):
""" Return person's name, unless maiden_name given, """ Return person's name, unless maiden_name given,
unless married_name listed. unless married_name listed.
@ -307,137 +317,212 @@ class Calendar(Report):
This method runs through the data, and collects the relevant dates This method runs through the data, and collects the relevant dates
and text. and text.
""" """
db = self.database people = self.database.iter_person_handles()
people = db.iter_person_handles()
people = self.filter.apply(self.database, people, user=self._user) people = self.filter.apply(self.database, people, user=self._user)
ngettext = self._locale.translation.ngettext # to see "nearby" comments
with self._user.progress(_('Calendar Report'), with self._user.progress(_('Calendar Report'),
_('Reading database...'), _('Reading database...'),
len(people)) as step: len(people)) as step:
for person_handle in people: for person_handle in people:
step() step()
person = db.get_person_from_handle(person_handle) person = self.database.get_person_from_handle(person_handle)
mark = utils.get_person_mark(db, person)
birth_ref = person.get_birth_ref()
birth_date = None
if birth_ref:
birth_event = db.get_event_from_handle(birth_ref.ref)
birth_date = birth_event.get_date_object()
if (self.birthdays and birth_date is not None and birth_date.is_valid()): self._add_birthday(person)
birth_date = gregorian(birth_date) self._add_anniversaries(person)
self._add_death_date(person)
year = birth_date.get_year() def _add_birthday(self, person):
month = birth_date.get_month() if not self.birthdays:
day = birth_date.get_day() return
prob_alive_date = Date(self.year, month, day) birth_ref = person.get_birth_ref()
if not birth_ref:
return
nyears = self.year - year def get_surname_of_husband():
# add some things to handle maiden name: if self.maiden_name not in ['spouse_first', 'spouse_last']:
father_lastname = None # husband, actually return None
if self.maiden_name in ['spouse_first', 'spouse_last']: # get husband's last name:
if person.get_gender() == Person.FEMALE:
family_list = person.get_family_handle_list()
if family_list:
if self.maiden_name == 'spouse_first':
fhandle = family_list[0]
else:
fhandle = family_list[-1]
fam = db.get_family_from_handle(fhandle)
father_handle = fam.get_father_handle()
mother_handle = fam.get_mother_handle()
if mother_handle == person_handle:
if father_handle:
father = db.get_person_from_handle(father_handle)
if father:
father_lastname = father.get_primary_name().get_surname()
short_name = self.get_name(person, father_lastname)
alive = probably_alive(person, db, prob_alive_date)
if not self.alive or alive: if person.get_gender() == Person.FEMALE:
if nyears == 0: family_list = person.get_family_handle_list()
text = self._('%(person)s, birth') % { if family_list:
'person' : short_name } if self.maiden_name == 'spouse_first':
else: family_handle = family_list[0]
# Translators: leave all/any {...} untranslated else:
text = ngettext('{person}, {age}', family_handle = family_list[-1]
'{person}, {age}', family = self.database.get_family_from_handle(
nyears).format(person=short_name, family_handle)
age=nyears) husband_handle = family.get_father_handle()
self.add_day_item(text, month, day, marks=[mark]) wife_handle = family.get_mother_handle()
if self.anniversaries: if wife_handle == person.get_handle():
family_list = person.get_family_handle_list() if husband_handle:
for fhandle in family_list: father = self.database.get_person_from_handle(
fam = db.get_family_from_handle(fhandle) husband_handle)
father_handle = fam.get_father_handle() if father:
mother_handle = fam.get_mother_handle() return father.get_primary_name().get_surname()
if father_handle == person.get_handle():
spouse_handle = mother_handle
else:
continue # with next person if the father is not "person"
# this will keep from duplicating the anniversary
if spouse_handle:
spouse = db.get_person_from_handle(spouse_handle)
if spouse:
s_m = utils.get_person_mark(db, spouse)
spouse_name = self.get_name(spouse)
short_name = self.get_name(person)
# TEMP: this will handle ordered events
# Gramps 3.0 will have a new mechanism for start/stop events
are_married = None
for event_ref in fam.get_event_ref_list():
event = db.get_event_from_handle(event_ref.ref)
et = EventType
rt = EventRoleType
if event.type in [et.MARRIAGE,
et.MARR_ALT] and \
(event_ref.get_role() == rt.FAMILY or
event_ref.get_role() == rt.PRIMARY ):
are_married = event
elif event.type in [et.DIVORCE,
et.ANNULMENT,
et.DIV_FILING] and \
(event_ref.get_role() == rt.FAMILY or
event_ref.get_role() == rt.PRIMARY ):
are_married = None
if are_married is not None:
for event_ref in fam.get_event_ref_list():
event = db.get_event_from_handle(event_ref.ref)
event_obj = event.get_date_object()
if event_obj.is_valid(): return None
event_obj = gregorian(event_obj)
year = event_obj.get_year() birth_event = self.database.get_event_from_handle(birth_ref.ref)
month = event_obj.get_month() birth_date = birth_event.get_date_object()
day = event_obj.get_day()
prob_alive_date = Date(self.year, month, day) if birth_date is not None and birth_date.is_valid():
birth_date = gregorian(birth_date)
nyears = self.year - year year = birth_date.get_year()
if nyears == 0: month = birth_date.get_month()
text = self._('%(spouse)s and\n %(person)s, wedding') % { day = birth_date.get_day()
'spouse' : spouse_name,
'person' : short_name,
}
else:
# Translators: leave all/any {...} untranslated
text = ngettext("{spouse} and\n {person}, {nyears}",
"{spouse} and\n {person}, {nyears}",
nyears).format(spouse=spouse_name, person=short_name, nyears=nyears)
alive1 = probably_alive(person, prob_alive_date = Date(self.year, month, day)
self.database,
prob_alive_date) nyears = self.year - year
alive2 = probably_alive(spouse, short_name = self.get_name(
self.database, person,
prob_alive_date) get_surname_of_husband())
if ((self.alive and alive1 and alive2) or not self.alive): alive = probably_alive(person, self.database, prob_alive_date)
self.add_day_item(text, month, day,
marks=[mark,s_m]) if not self.alive or alive:
if nyears == 0:
text = self._('%(person)s, birth') % {
'person' : short_name }
else:
# to see "nearby" comments
ngettext = self._locale.translation.ngettext
# Translators: leave all/any {...} untranslated
text = ngettext('{person}, {age}',
'{person}, {age}',
nyears).format(person=short_name,
age=nyears)
dth = ' (%s)' % (self.symb_dth) if not alive else ''
text = '%s %s%s' % (self.symb_bth, text, dth)
self.add_day_item(text,
month,
day,
marks=[utils.get_person_mark(self.database,
person)])
def _add_anniversaries(self, person):
if not self.anniversaries:
return
def is_father(family):
return person.get_handle() == family.get_father_handle()
def marriage_still_active(family):
for event_ref in family.get_event_ref_list():
if event_ref.get_role() not in [EventRoleType.FAMILY,
EventRoleType.PRIMARY]:
continue
event = self.database.get_event_from_handle(event_ref.ref)
if event.type in [EventType.DIVORCE,
EventType.ANNULMENT,
EventType.DIV_FILING]:
return False
return True
families = filter(marriage_still_active, filter(is_father,
map(self.database.get_family_from_handle,
person.get_family_handle_list())))
def get_marriage_event(family):
for event_ref in family.get_event_ref_list():
if event_ref.get_role() not in [EventRoleType.FAMILY,
EventRoleType.PRIMARY]:
continue
event = self.database.get_event_from_handle(event_ref.ref)
if event.type in [EventType.MARRIAGE, EventType.MARR_ALT]:
return event
return None
def get_wife(family):
mother_handle = family.get_mother_handle()
if mother_handle:
return self.database.get_person_from_handle(mother_handle)
else:
return None
short_name = self.get_name(person)
for family in families:
wife = get_wife(family)
event = get_marriage_event(family)
if event is None or wife is None:
continue
event_date = event.get_date_object()
if event_date.is_valid():
event_date = gregorian(event_date)
year = event_date.get_year()
month = event_date.get_month()
day = event_date.get_day()
prob_alive_date = Date(self.year, month, day)
alive1 = probably_alive(person, self.database, prob_alive_date)
alive2 = probably_alive(wife, self.database, prob_alive_date)
nyears = self.year - year
if nyears == 0:
text = self._('%(wife)s and\n %(person)s, wedding') % {
'wife' : self.get_name(wife),
'person' : short_name,
}
else:
p_dead = ' (%s)' % (self.symb_dth) if not alive1 else ''
w_dead = ' (%s)' % (self.symb_dth) if not alive2 else ''
# to see "nearby" comments
ngettext = self._locale.translation.ngettext
# Translators: leave all/any {...} untranslated
text = ngettext("{wife} and\n {person}, {nyears}",
"{wife} and\n {person}, {nyears}",
nyears).format(wife=self.get_name(wife) +
w_dead,
person=short_name + p_dead,
nyears=nyears)
text = '%s %s' % (self.symb_mrg, text)
if ((self.alive and alive1 and alive2) or not self.alive):
mark = utils.get_person_mark(self.database, person)
wife_mark = utils.get_person_mark(self.database, wife)
self.add_day_item(
text, month, day,
marks=[mark, wife_mark])
def _add_death_date(self, person):
if not self.include_death_date:
return
death_ref = person.get_death_ref()
if not death_ref:
return
death_event = self.database.get_event_from_handle(death_ref.ref)
death_date = death_event.get_date_object()
if death_date is not None and death_date.is_valid():
death_date = gregorian(death_date)
text = '{symbol} {name}, {nyears}'.format(
symbol=self.symb_dth,
name=self.get_name(person),
nyears=self.year - death_date.get_year())
self.add_day_item(text,
death_date.get_month(),
death_date.get_day(),
marks=[utils.get_person_mark(self.database,
person)])
#------------------------------------------------------------------------ #------------------------------------------------------------------------
# #
@ -559,6 +644,11 @@ class CalendarOptions(MenuReportOptions):
anniversaries.set_help(_("Whether to include anniversaries")) anniversaries.set_help(_("Whether to include anniversaries"))
add_option("anniversaries", anniversaries) add_option("anniversaries", anniversaries)
include_death_date = BooleanOption(_("Include death dates"), False)
include_death_date.set_help(
_("Include death anniversaries in the calendar"))
add_option("include_death_date", include_death_date)
def __update_filters(self): def __update_filters(self):
""" """
Update the filter list based on the selected person Update the filter list based on the selected person