609 lines
24 KiB
Python
609 lines
24 KiB
Python
#
|
|
# Gramps - a GTK+/GNOME based genealogy program
|
|
#
|
|
# Copyright (C) 2008,2011 Gary Burton
|
|
# Copyright (C) 2010 Jakim Friant
|
|
# Copyright (C) 2011 Heinz Brinker
|
|
# Copyright (C) 2013-2016 Paul Franklin
|
|
#
|
|
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
#
|
|
|
|
"""Place Report"""
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# python modules
|
|
#
|
|
#------------------------------------------------------------------------
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# gramps modules
|
|
#
|
|
#------------------------------------------------------------------------
|
|
from gramps.gen.const import GRAMPS_LOCALE as glocale
|
|
_ = glocale.translation.sgettext
|
|
from gramps.gen.plug.menu import (FilterOption, PlaceListOption,
|
|
EnumeratedListOption)
|
|
from gramps.gen.plug.report import Report
|
|
from gramps.gen.plug.report import MenuReportOptions
|
|
from gramps.gen.plug.report import stdoptions
|
|
from gramps.gen.plug.docgen import (IndexMark, FontStyle, ParagraphStyle,
|
|
TableStyle, TableCellStyle,
|
|
FONT_SANS_SERIF, FONT_SERIF,
|
|
INDEX_TYPE_TOC, PARA_ALIGN_CENTER)
|
|
from gramps.gen.sort import Sort
|
|
from gramps.gen.utils.location import get_location_list
|
|
from gramps.gen.display.place import displayer as _pd
|
|
from gramps.gen.errors import ReportError
|
|
from gramps.gen.proxy import LivingProxyDb, CacheProxyDb
|
|
|
|
class PlaceReport(Report):
|
|
"""
|
|
Place Report class
|
|
"""
|
|
def __init__(self, database, options, user):
|
|
"""
|
|
Create the PlaceReport object produces the Place report.
|
|
|
|
The arguments are:
|
|
|
|
database - the Gramps database instance
|
|
options - instance of the Options class for this report
|
|
user - instance of a gen.user.User class
|
|
|
|
This report needs the following parameters (class variables)
|
|
that come in the options class.
|
|
|
|
places - List of places to report on.
|
|
center - Center of report, person or event
|
|
incl_private - Whether to include private data
|
|
name_format - Preferred format to display names
|
|
living_people - How to handle living people
|
|
years_past_death - Consider as living this many years after death
|
|
"""
|
|
|
|
Report.__init__(self, database, options, user)
|
|
|
|
self._user = user
|
|
menu = options.menu
|
|
|
|
lang = menu.get_option_by_name('trans').get_value()
|
|
rlocale = self.set_locale(lang)
|
|
|
|
stdoptions.run_private_data_option(self, menu)
|
|
living_opt = stdoptions.run_living_people_option(self, menu, rlocale)
|
|
self.database = CacheProxyDb(self.database)
|
|
self._db = self.database
|
|
|
|
self._lv = menu.get_option_by_name('living_people').get_value()
|
|
for (value, description) in living_opt.get_items(xml_items=True):
|
|
if value == self._lv:
|
|
living_desc = self._(description)
|
|
break
|
|
self.living_desc = self._("(Living people: %(option_name)s)"
|
|
) % {'option_name': living_desc}
|
|
|
|
places = menu.get_option_by_name('places').get_value()
|
|
self.center = menu.get_option_by_name('center').get_value()
|
|
|
|
stdoptions.run_name_format_option(self, menu)
|
|
self._nd = self._name_display
|
|
|
|
filter_option = menu.get_option_by_name('filter')
|
|
self.filter = filter_option.get_filter()
|
|
|
|
self.sort = Sort(self._db)
|
|
|
|
self.place_handles = []
|
|
if self.filter.get_name() != '':
|
|
# Use the selected filter to provide a list of place handles
|
|
plist = self._db.iter_place_handles()
|
|
self.place_handles = self.filter.apply(self._db, plist,
|
|
user=self._user)
|
|
|
|
if places:
|
|
# Add places selected individually
|
|
self.place_handles += self.__get_place_handles(places)
|
|
|
|
if not self.place_handles:
|
|
raise ReportError(
|
|
_('Place Report'),
|
|
_('Please select at least one place before running this.'))
|
|
|
|
self.place_handles.sort(key=self.sort.by_place_title_key)
|
|
|
|
def write_report(self):
|
|
"""
|
|
The routine that actually creates the report.
|
|
At this point, the document is opened and ready for writing.
|
|
"""
|
|
|
|
# Write the title line. Set in INDEX marker so that this section will be
|
|
# identified as a major category if this is included in a Book report.
|
|
|
|
title = self._("Place Report")
|
|
mark = IndexMark(title, INDEX_TYPE_TOC, 1)
|
|
self.doc.start_paragraph("PLC-ReportTitle")
|
|
self.doc.write_text(title, mark)
|
|
self.doc.end_paragraph()
|
|
if self._lv != LivingProxyDb.MODE_INCLUDE_ALL:
|
|
self.doc.start_paragraph("PLC-ReportSubtitle")
|
|
self.doc.write_text(self.living_desc)
|
|
self.doc.end_paragraph()
|
|
self.__write_all_places()
|
|
|
|
def __write_all_places(self):
|
|
"""
|
|
This procedure writes out each of the selected places.
|
|
"""
|
|
place_nbr = 1
|
|
|
|
with self._user.progress(_("Place Report"),
|
|
_("Generating report"),
|
|
len(self.place_handles)) as step:
|
|
|
|
for handle in self.place_handles:
|
|
self.__write_place(handle, place_nbr)
|
|
if self.center == "Event":
|
|
self.__write_referenced_events(handle)
|
|
elif self.center == "Person":
|
|
self.__write_referenced_persons(handle)
|
|
else:
|
|
raise AttributeError("no such center: '%s'" % self.center)
|
|
place_nbr += 1
|
|
# increment progress bar
|
|
step()
|
|
|
|
|
|
def __write_place(self, handle, place_nbr):
|
|
"""
|
|
This procedure writes out the details of a single place
|
|
"""
|
|
place = self._db.get_place_from_handle(handle)
|
|
|
|
place_details = [self._("Gramps ID: %s ") % place.get_gramps_id()]
|
|
for level in get_location_list(self._db, place):
|
|
# translators: needed for French, ignore otherwise
|
|
place_details.append(self._("%(str1)s: %(str2)s"
|
|
) % {'str1': self._(level[1].xml_str()),
|
|
'str2': level[0]})
|
|
|
|
place_names = ''
|
|
all_names = place.get_all_names()
|
|
if len(all_names) > 1 or __debug__:
|
|
for place_name in all_names:
|
|
if place_names != '':
|
|
# translators: needed for Arabic, ignore otherwise
|
|
place_names += self._(", ")
|
|
place_names += '%s' % place_name.get_value()
|
|
if place_name.get_language() != '' or __debug__:
|
|
place_names += ' (%s)' % place_name.get_language()
|
|
place_details += [self._("places|All Names: %s") % place_names,]
|
|
self.doc.start_paragraph("PLC-PlaceTitle")
|
|
place_title = _pd.display(self._db, place)
|
|
self.doc.write_text(("%(nbr)s. %(place)s") % {'nbr' : place_nbr,
|
|
'place' : place_title})
|
|
self.doc.end_paragraph()
|
|
|
|
for item in place_details:
|
|
self.doc.start_paragraph("PLC-PlaceDetails")
|
|
self.doc.write_text(item)
|
|
self.doc.end_paragraph()
|
|
|
|
def __write_referenced_events(self, handle):
|
|
"""
|
|
This procedure writes out each of the events related to the place
|
|
"""
|
|
event_handles = [event_handle for (object_type, event_handle) in
|
|
self._db.find_backlink_handles(handle, ['Event'])]
|
|
event_handles.sort(key=self.sort.by_date_key)
|
|
|
|
if event_handles:
|
|
self.doc.start_paragraph("PLC-Section")
|
|
title = self._("Events that happened at this place")
|
|
self.doc.write_text(title)
|
|
self.doc.end_paragraph()
|
|
self.doc.start_table("EventTable", "PLC-EventTable")
|
|
column_titles = [self._("Date"), self._("Type of Event"),
|
|
self._("Person"), self._("Description")]
|
|
self.doc.start_row()
|
|
for title in column_titles:
|
|
self.doc.start_cell("PLC-TableColumn")
|
|
self.doc.start_paragraph("PLC-ColumnTitle")
|
|
self.doc.write_text(title)
|
|
self.doc.end_paragraph()
|
|
self.doc.end_cell()
|
|
self.doc.end_row()
|
|
|
|
for evt_handle in event_handles:
|
|
event = self._db.get_event_from_handle(evt_handle)
|
|
if event: # will be None if marked private
|
|
date = self._get_date(event.get_date_object())
|
|
descr = event.get_description()
|
|
event_type = self._(self._get_type(event.get_type()))
|
|
|
|
person_list = []
|
|
ref_handles = [x for x in
|
|
self._db.find_backlink_handles(evt_handle)]
|
|
if not ref_handles: # since the backlink may point to private
|
|
continue # data, ignore an event with no backlinks
|
|
for (ref_type, ref_handle) in ref_handles:
|
|
if ref_type == 'Person':
|
|
person_list.append(ref_handle)
|
|
else:
|
|
family = self._db.get_family_from_handle(ref_handle)
|
|
father = family.get_father_handle()
|
|
if father:
|
|
person_list.append(father)
|
|
mother = family.get_mother_handle()
|
|
if mother:
|
|
person_list.append(mother)
|
|
|
|
people = ""
|
|
person_list = list(set(person_list))
|
|
for p_handle in person_list:
|
|
person = self._db.get_person_from_handle(p_handle)
|
|
if person:
|
|
person_name = self._nd.display(person)
|
|
if people == "":
|
|
people = "%(name)s (%(id)s)" % {
|
|
'name' : person_name,
|
|
'id' : person.get_gramps_id()}
|
|
else:
|
|
people = self._("%(persons)s and %(name)s (%(id)s)"
|
|
) % {'persons' : people,
|
|
'name' : person_name,
|
|
'id' : person.get_gramps_id()}
|
|
|
|
event_details = [date, event_type, people, descr]
|
|
self.doc.start_row()
|
|
for detail in event_details:
|
|
self.doc.start_cell("PLC-Cell")
|
|
self.doc.start_paragraph("PLC-Details")
|
|
self.doc.write_text("%s " % detail)
|
|
self.doc.end_paragraph()
|
|
self.doc.end_cell()
|
|
self.doc.end_row()
|
|
|
|
if event_handles:
|
|
self.doc.end_table()
|
|
|
|
def __write_referenced_persons(self, handle):
|
|
"""
|
|
This procedure writes out each of the people related to the place
|
|
"""
|
|
event_handles = [event_handle for (object_type, event_handle) in
|
|
self._db.find_backlink_handles(handle, ['Event'])]
|
|
|
|
if event_handles:
|
|
self.doc.start_paragraph("PLC-Section")
|
|
title = self._("People associated with this place")
|
|
self.doc.write_text(title)
|
|
self.doc.end_paragraph()
|
|
self.doc.start_table("EventTable", "PLC-PersonTable")
|
|
column_titles = [self._("Person"), self._("Type of Event"), \
|
|
self._("Description"), self._("Date")]
|
|
self.doc.start_row()
|
|
for title in column_titles:
|
|
self.doc.start_cell("PLC-TableColumn")
|
|
self.doc.start_paragraph("PLC-ColumnTitle")
|
|
self.doc.write_text(title)
|
|
self.doc.end_paragraph()
|
|
self.doc.end_cell()
|
|
self.doc.end_row()
|
|
|
|
person_dict = {}
|
|
for evt_handle in event_handles:
|
|
ref_handles = [x for x in
|
|
self._db.find_backlink_handles(evt_handle)]
|
|
for (ref_type, ref_handle) in ref_handles:
|
|
if ref_type == 'Person':
|
|
person = self._db.get_person_from_handle(ref_handle)
|
|
name_entry = "%s (%s)" % (self._nd.display(person),
|
|
person.get_gramps_id())
|
|
else:
|
|
family = self._db.get_family_from_handle(ref_handle)
|
|
f_handle = family.get_father_handle()
|
|
m_handle = family.get_mother_handle()
|
|
if f_handle and m_handle:
|
|
father = self._db.get_person_from_handle(f_handle)
|
|
mother = self._db.get_person_from_handle(m_handle)
|
|
father_name = self._nd.display(father)
|
|
mother_name = self._nd.display(mother)
|
|
father_id = father.get_gramps_id()
|
|
mother_id = mother.get_gramps_id()
|
|
name_entry = self._("%(father)s (%(father_id)s) and "
|
|
"%(mother)s (%(mother_id)s)"
|
|
) % {'father' : father_name,
|
|
'father_id' : father_id,
|
|
'mother' : mother_name,
|
|
'mother_id' : mother_id}
|
|
elif f_handle or m_handle:
|
|
if f_handle:
|
|
p_handle = f_handle
|
|
else:
|
|
p_handle = m_handle
|
|
person = self._db.get_person_from_handle(p_handle)
|
|
|
|
name_entry = "%s (%s)" % (self._nd.display(person),
|
|
person.get_gramps_id())
|
|
else:
|
|
# No parents - bug #7299
|
|
continue
|
|
|
|
if name_entry in person_dict:
|
|
person_dict[name_entry].append(evt_handle)
|
|
else:
|
|
person_dict[name_entry] = []
|
|
person_dict[name_entry].append(evt_handle)
|
|
|
|
keys = list(person_dict.keys())
|
|
keys.sort()
|
|
|
|
for entry in keys:
|
|
people = entry
|
|
person_dict[entry].sort(key=self.sort.by_date_key)
|
|
for evt_handle in person_dict[entry]:
|
|
event = self._db.get_event_from_handle(evt_handle)
|
|
if event:
|
|
date = self._get_date(event.get_date_object())
|
|
descr = event.get_description()
|
|
event_type = self._(self._get_type(event.get_type()))
|
|
else:
|
|
date = ''
|
|
descr = ''
|
|
event_type = ''
|
|
event_details = [people, event_type, descr, date]
|
|
self.doc.start_row()
|
|
for detail in event_details:
|
|
self.doc.start_cell("PLC-Cell")
|
|
self.doc.start_paragraph("PLC-Details")
|
|
self.doc.write_text("%s " % detail)
|
|
self.doc.end_paragraph()
|
|
self.doc.end_cell()
|
|
people = "" # do not repeat the name on the next event
|
|
self.doc.end_row()
|
|
|
|
if event_handles:
|
|
self.doc.end_table()
|
|
|
|
def __get_place_handles(self, places):
|
|
"""
|
|
This procedure converts a string of place GIDs to a list of handles
|
|
"""
|
|
place_handles = []
|
|
for place_gid in places.split():
|
|
place = self._db.get_place_from_gramps_id(place_gid)
|
|
if place is not None:
|
|
#place can be None if option is gid of other fam tree
|
|
place_handles.append(place.get_handle())
|
|
|
|
return place_handles
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# PlaceOptions
|
|
#
|
|
#------------------------------------------------------------------------
|
|
class PlaceOptions(MenuReportOptions):
|
|
|
|
"""
|
|
Defines options and provides handling interface.
|
|
"""
|
|
|
|
def __init__(self, name, dbase):
|
|
self.__db = dbase
|
|
self.__filter = None
|
|
self.__places = None
|
|
MenuReportOptions.__init__(self, name, dbase)
|
|
|
|
def get_subject(self):
|
|
""" Return a string that describes the subject of the report. """
|
|
subject = ""
|
|
if self.__filter.get_filter().get_name():
|
|
# Use the selected filter's name, if any
|
|
subject += self.__filter.get_filter().get_name()
|
|
if self.__places.get_value():
|
|
# Add places selected individually, if any
|
|
for place_id in self.__places.get_value().split():
|
|
if subject:
|
|
subject += " + "
|
|
place = self.__db.get_place_from_gramps_id(place_id)
|
|
subject += _pd.display(self.__db, place)
|
|
return subject
|
|
|
|
def add_menu_options(self, menu):
|
|
"""
|
|
Add options to the menu for the place report.
|
|
"""
|
|
category_name = _("Report Options")
|
|
|
|
# Reload filters to pick any new ones
|
|
CustomFilters = None
|
|
from gramps.gen.filters import CustomFilters, GenericFilter
|
|
|
|
self.__filter = FilterOption(_("Select using filter"), 0)
|
|
self.__filter.set_help(_("Select places using a filter"))
|
|
filter_list = []
|
|
filter_list.append(GenericFilter())
|
|
filter_list.extend(CustomFilters.get_filters('Place'))
|
|
self.__filter.set_filters(filter_list)
|
|
menu.add_option(category_name, "filter", self.__filter)
|
|
|
|
self.__places = PlaceListOption(_("Select places individually"))
|
|
self.__places.set_help(_("List of places to report on"))
|
|
menu.add_option(category_name, "places", self.__places)
|
|
|
|
center = EnumeratedListOption(_("Center on"), "Event")
|
|
center.set_items([("Event", _("Event")), ("Person", _("Person"))])
|
|
center.set_help(_("If report is event or person centered"))
|
|
menu.add_option(category_name, "center", center)
|
|
|
|
category_name = _("Report Options (2)")
|
|
|
|
stdoptions.add_name_format_option(menu, category_name)
|
|
|
|
stdoptions.add_private_data_option(menu, category_name)
|
|
|
|
stdoptions.add_living_people_option(menu, category_name)
|
|
|
|
stdoptions.add_localization_option(menu, category_name)
|
|
|
|
def make_default_style(self, default_style):
|
|
"""
|
|
Make the default output style for the Place report.
|
|
"""
|
|
self.default_style = default_style
|
|
self.__report_title_style()
|
|
self.__report_subtitle_style()
|
|
self.__place_title_style()
|
|
self.__place_details_style()
|
|
self.__column_title_style()
|
|
self.__section_style()
|
|
self.__event_table_style()
|
|
self.__details_style()
|
|
self.__cell_style()
|
|
self.__table_column_style()
|
|
|
|
def __report_title_style(self):
|
|
"""
|
|
Define the style used for the report title
|
|
"""
|
|
font = FontStyle()
|
|
font.set(face=FONT_SANS_SERIF, size=16, bold=1)
|
|
para = ParagraphStyle()
|
|
para.set_font(font)
|
|
para.set_header_level(1)
|
|
para.set_top_margin(0.25)
|
|
para.set_bottom_margin(0.25)
|
|
para.set_alignment(PARA_ALIGN_CENTER)
|
|
para.set_description(_('The style used for the title of the report.'))
|
|
self.default_style.add_paragraph_style("PLC-ReportTitle", para)
|
|
|
|
def __report_subtitle_style(self):
|
|
"""
|
|
Define the style used for the report subtitle
|
|
"""
|
|
font = FontStyle()
|
|
font.set(face=FONT_SANS_SERIF, size=12, bold=1)
|
|
para = ParagraphStyle()
|
|
para.set_font(font)
|
|
para.set_header_level(1)
|
|
para.set_top_margin(0.25)
|
|
para.set_bottom_margin(0.25)
|
|
para.set_alignment(PARA_ALIGN_CENTER)
|
|
para.set_description(_('The style used for the subtitle.'))
|
|
self.default_style.add_paragraph_style("PLC-ReportSubtitle", para)
|
|
|
|
def __place_title_style(self):
|
|
"""
|
|
Define the style used for the place title
|
|
"""
|
|
font = FontStyle()
|
|
font.set(face=FONT_SERIF, size=12, italic=0, bold=1)
|
|
para = ParagraphStyle()
|
|
para.set_font(font)
|
|
para.set(first_indent=-1.5, lmargin=1.5)
|
|
para.set_top_margin(0.75)
|
|
para.set_bottom_margin(0.25)
|
|
para.set_description(_('The style used for place title.'))
|
|
self.default_style.add_paragraph_style("PLC-PlaceTitle", para)
|
|
|
|
def __place_details_style(self):
|
|
"""
|
|
Define the style used for the place details
|
|
"""
|
|
font = FontStyle()
|
|
font.set(face=FONT_SERIF, size=10)
|
|
para = ParagraphStyle()
|
|
para.set_font(font)
|
|
para.set(first_indent=0.0, lmargin=1.5)
|
|
para.set_description(_('The style used for place details.'))
|
|
self.default_style.add_paragraph_style("PLC-PlaceDetails", para)
|
|
|
|
def __column_title_style(self):
|
|
"""
|
|
Define the style used for the event table column title
|
|
"""
|
|
font = FontStyle()
|
|
font.set(face=FONT_SERIF, size=10, bold=1)
|
|
para = ParagraphStyle()
|
|
para.set_font(font)
|
|
para.set(first_indent=0.0, lmargin=0.0)
|
|
para.set_description(_('The style used for a column title.'))
|
|
self.default_style.add_paragraph_style("PLC-ColumnTitle", para)
|
|
|
|
def __section_style(self):
|
|
"""
|
|
Define the style used for each section
|
|
"""
|
|
font = FontStyle()
|
|
font.set(face=FONT_SERIF, size=10, italic=0, bold=0)
|
|
para = ParagraphStyle()
|
|
para.set_font(font)
|
|
para.set(first_indent=-1.5, lmargin=1.5)
|
|
para.set_top_margin(0.5)
|
|
para.set_bottom_margin(0.25)
|
|
para.set_description(_('The style used for each section.'))
|
|
self.default_style.add_paragraph_style("PLC-Section", para)
|
|
|
|
def __event_table_style(self):
|
|
"""
|
|
Define the style used for event table
|
|
"""
|
|
table = TableStyle()
|
|
table.set_width(100)
|
|
table.set_columns(4)
|
|
table.set_column_width(0, 25)
|
|
table.set_column_width(1, 15)
|
|
table.set_column_width(2, 35)
|
|
table.set_column_width(3, 25)
|
|
self.default_style.add_table_style("PLC-EventTable", table)
|
|
table.set_width(100)
|
|
table.set_columns(4)
|
|
table.set_column_width(0, 35)
|
|
table.set_column_width(1, 15)
|
|
table.set_column_width(2, 25)
|
|
table.set_column_width(3, 25)
|
|
self.default_style.add_table_style("PLC-PersonTable", table)
|
|
|
|
def __details_style(self):
|
|
"""
|
|
Define the style used for person and event details
|
|
"""
|
|
font = FontStyle()
|
|
font.set(face=FONT_SERIF, size=10)
|
|
para = ParagraphStyle()
|
|
para.set_font(font)
|
|
para.set_description(_('The style used for event and person details.'))
|
|
self.default_style.add_paragraph_style("PLC-Details", para)
|
|
|
|
def __cell_style(self):
|
|
"""
|
|
Define the style used for cells in the event table
|
|
"""
|
|
cell = TableCellStyle()
|
|
self.default_style.add_cell_style("PLC-Cell", cell)
|
|
|
|
def __table_column_style(self):
|
|
"""
|
|
Define the style used for event table columns
|
|
"""
|
|
cell = TableCellStyle()
|
|
cell.set_bottom_border(1)
|
|
self.default_style.add_cell_style('PLC-TableColumn', cell)
|