1# encoding:utf-8 2# 3# Gramps - a GTK+/GNOME based genealogy program - Records plugin 4# 5# Copyright (C) 2008-2011 Reinhard Müller 6# Copyright (C) 2010 Jakim Friant 7# Copyright (C) 2012 Brian G. Matherly 8# Copyright (C) 2013-2016 Paul Franklin 9# 10# This program is free software; you can redistribute it and/or modify 11# it under the terms of the GNU General Public License as published by 12# the Free Software Foundation; either version 2 of the License, or 13# (at your option) any later version. 14# 15# This program is distributed in the hope that it will be useful, 16# but WITHOUT ANY WARRANTY; without even the implied warranty of 17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18# GNU General Public License for more details. 19# 20# You should have received a copy of the GNU General Public License 21# along with this program; if not, write to the Free Software 22# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 23# 24 25""" Records Report """ 26 27#------------------------------------------------------------------------ 28# 29# Standard Python modules 30# 31#------------------------------------------------------------------------ 32 33#------------------------------------------------------------------------ 34# 35# Gramps modules 36# 37#------------------------------------------------------------------------ 38from gramps.gen.const import GRAMPS_LOCALE as glocale 39_ = glocale.translation.sgettext 40from gramps.plugins.lib.librecords import (RECORDS, find_records, 41 CALLNAME_DONTUSE, CALLNAME_REPLACE, 42 CALLNAME_UNDERLINE_ADD) 43from gramps.gen.plug.docgen import (FontStyle, ParagraphStyle, 44 FONT_SANS_SERIF, PARA_ALIGN_CENTER, 45 IndexMark, INDEX_TYPE_TOC) 46from gramps.gen.plug.menu import (BooleanOption, EnumeratedListOption, 47 FilterOption, NumberOption, 48 PersonOption, StringOption) 49from gramps.gen.plug.report import Report 50from gramps.gen.plug.report import utils 51from gramps.gen.plug.report import MenuReportOptions 52from gramps.gen.plug.report import stdoptions 53from gramps.gen.lib import Span 54from gramps.gen.errors import ReportError 55from gramps.gen.proxy import LivingProxyDb, CacheProxyDb 56 57#------------------------------------------------------------------------ 58# 59# Records Report 60# 61#------------------------------------------------------------------------ 62class RecordsReport(Report): 63 """ Records Report """ 64 65 def __init__(self, database, options, user): 66 """ 67 This report needs the following parameters (class variables) 68 that come in the options class. 69 70 incl_private - Whether to include private data 71 living_people - How to handle living people 72 years_past_death - Consider as living this many years after death 73 """ 74 75 Report.__init__(self, database, options, user) 76 menu = options.menu 77 78 self.set_locale(options.menu.get_option_by_name('trans').get_value()) 79 80 stdoptions.run_private_data_option(self, menu) 81 living_opt = stdoptions.run_living_people_option(self, menu, 82 self._locale) 83 self.database = CacheProxyDb(self.database) 84 85 self._lv = menu.get_option_by_name('living_people').get_value() 86 for (value, description) in living_opt.get_items(xml_items=True): 87 if value == self._lv: 88 living_desc = self._(description) 89 break 90 self.living_desc = self._( 91 "(Living people: %(option_name)s)") % {'option_name': living_desc} 92 93 filter_option = menu.get_option_by_name('filter') 94 self.filter = filter_option.get_filter() 95 96 self.top_size = menu.get_option_by_name('top_size').get_value() 97 self.callname = menu.get_option_by_name('callname').get_value() 98 99 self.footer = menu.get_option_by_name('footer').get_value() 100 101 self.include = {} 102 for (text, varname, default) in RECORDS: 103 self.include[varname] = menu.get_option_by_name(varname).get_value() 104 105 self._nf = stdoptions.run_name_format_option(self, menu) 106 107 def write_report(self): 108 """ 109 Build the actual report. 110 """ 111 112 records = find_records(self.database, self.filter, 113 self.top_size, self.callname, 114 trans_text=self._, name_format=self._nf, 115 living_mode=self._lv, user=self._user) 116 117 self.doc.start_paragraph('REC-Title') 118 title = self._("Records") 119 mark = IndexMark(title, INDEX_TYPE_TOC, 1) 120 self.doc.write_text(title, mark) 121 self.doc.end_paragraph() 122 123 self.doc.start_paragraph('REC-Subtitle') 124 filter_name = self.filter.get_name(self._locale) 125 self.doc.write_text("(%s)" % filter_name) 126 self.doc.end_paragraph() 127 if self._lv != LivingProxyDb.MODE_INCLUDE_ALL: 128 self.doc.start_paragraph('REC-Subtitle') 129 self.doc.write_text(self.living_desc) 130 self.doc.end_paragraph() 131 132 for (text, varname, top) in records: 133 if not self.include[varname]: 134 continue 135 136 self.doc.start_paragraph('REC-Heading') 137 self.doc.write_text(self._(text)) 138 self.doc.end_paragraph() 139 140 last_value = None 141 rank = 0 142 for (number, 143 (sort, value, name, handletype, handle)) in enumerate(top): 144 mark = None 145 if handletype == 'Person': 146 person = self.database.get_person_from_handle(handle) 147 mark = utils.get_person_mark(self.database, person) 148 elif handletype == 'Family': 149 family = self.database.get_family_from_handle(handle) 150 # librecords.py checks that the family has both 151 # a father and a mother and also that each one is 152 # in the filter if any filter was used, so we don't 153 # have to do any similar checking here, it's been done 154 f_handle = family.get_father_handle() 155 dad = self.database.get_person_from_handle(f_handle) 156 f_mark = utils.get_person_mark(self.database, dad) 157 m_handle = family.get_mother_handle() 158 mom = self.database.get_person_from_handle(m_handle) 159 m_mark = utils.get_person_mark(self.database, mom) 160 else: 161 raise ReportError(_( 162 "Option '%(opt_name)s' is present " 163 "in %(file)s\n but is not known to " 164 "the module. Ignoring...") 165 % {'opt_name': handletype, 166 'file': 'libnarrate.py'}) 167 # since the error is very unlikely I reused the string 168 if value != last_value: 169 last_value = value 170 rank = number 171 self.doc.start_paragraph('REC-Normal') 172 self.doc.write_text( 173 self._("%(number)s. ") % {'number': rank+1}) 174 self.doc.write_markup(str(name), name.get_tags(), mark) 175 if handletype == 'Family': 176 self.doc.write_text('', f_mark) 177 self.doc.write_text('', m_mark) 178 if isinstance(value, Span): 179 tvalue = value.get_repr(dlocale=self._locale) 180 else: 181 tvalue = value 182 self.doc.write_text(" (%s)" % tvalue) 183 self.doc.end_paragraph() 184 185 self.doc.start_paragraph('REC-Footer') 186 self.doc.write_text(self.footer) 187 self.doc.end_paragraph() 188 189 190#------------------------------------------------------------------------ 191# 192# Records Report Options 193# 194#------------------------------------------------------------------------ 195class RecordsReportOptions(MenuReportOptions): 196 """ 197 Defines options and provides handling interface. 198 """ 199 200 def __init__(self, name, dbase): 201 202 self.__pid = None 203 self.__filter = None 204 self.__db = dbase 205 self._nf = None 206 MenuReportOptions.__init__(self, name, dbase) 207 208 209 def get_subject(self): 210 """ Return a string that describes the subject of the report. """ 211 return self.__filter.get_filter().get_name() 212 213 def add_menu_options(self, menu): 214 215 category_name = _("Report Options") 216 217 self.__filter = FilterOption(_("Filter"), 0) 218 self.__filter.set_help( 219 _("Determines what people are included in the report.")) 220 menu.add_option(category_name, "filter", self.__filter) 221 self.__filter.connect('value-changed', self.__filter_changed) 222 223 self.__pid = PersonOption(_("Filter Person")) 224 self.__pid.set_help(_("The center person for the filter")) 225 menu.add_option(category_name, "pid", self.__pid) 226 self.__pid.connect('value-changed', self.__update_filters) 227 228 top_size = NumberOption(_("Number of ranks to display"), 3, 1, 100) 229 menu.add_option(category_name, "top_size", top_size) 230 231 callname = EnumeratedListOption(_("Use call name"), CALLNAME_DONTUSE) 232 callname.set_items([ 233 (CALLNAME_DONTUSE, _("Don't use call name")), 234 (CALLNAME_REPLACE, _("Replace first names with call name")), 235 (CALLNAME_UNDERLINE_ADD, 236 _("Underline call name in first names / " 237 "add call name to first name"))]) 238 menu.add_option(category_name, "callname", callname) 239 240 footer = StringOption(_("Footer text"), "") 241 menu.add_option(category_name, "footer", footer) 242 243 category_name = _("Report Options (2)") 244 245 self._nf = stdoptions.add_name_format_option(menu, category_name) 246 self._nf.connect('value-changed', self.__update_filters) 247 248 self.__update_filters() 249 250 stdoptions.add_private_data_option(menu, category_name) 251 252 stdoptions.add_living_people_option(menu, category_name) 253 254 stdoptions.add_localization_option(menu, category_name) 255 256 p_count = 0 257 for (text, varname, default) in RECORDS: 258 if varname.startswith('person'): 259 p_count += 1 260 p_half = p_count // 2 261 p_idx = 0 262 for (text, varname, default) in RECORDS: 263 option = BooleanOption(_(text), default) 264 if varname.startswith('person'): 265 if p_idx >= p_half: 266 category_name = _("Person 2") 267 else: 268 category_name = _("Person 1") 269 p_idx += 1 270 elif varname.startswith('family'): 271 category_name = _("Family") 272 menu.add_option(category_name, varname, option) 273 274 def __update_filters(self): 275 """ 276 Update the filter list based on the selected person 277 """ 278 gid = self.__pid.get_value() 279 person = self.__db.get_person_from_gramps_id(gid) 280 nfv = self._nf.get_value() 281 filter_list = utils.get_person_filters(person, 282 include_single=False, 283 name_format=nfv) 284 self.__filter.set_filters(filter_list) 285 286 def __filter_changed(self): 287 """ 288 Handle filter change. If the filter is not specific to a person, 289 disable the person option 290 """ 291 filter_value = self.__filter.get_value() 292 if filter_value == 0: # "Entire Database" (as "include_single=False") 293 self.__pid.set_available(False) 294 else: 295 # The other filters need a center person (assume custom ones too) 296 self.__pid.set_available(True) 297 298 def make_default_style(self, default_style): 299 300 #Paragraph Styles 301 font = FontStyle() 302 font.set_type_face(FONT_SANS_SERIF) 303 font.set_size(16) 304 font.set_bold(True) 305 para = ParagraphStyle() 306 para.set_font(font) 307 para.set_alignment(PARA_ALIGN_CENTER) 308 para.set_description(_("The style used for the title.")) 309 default_style.add_paragraph_style('REC-Title', para) 310 311 font = FontStyle() 312 font.set_type_face(FONT_SANS_SERIF) 313 font.set_size(12) 314 font.set_bold(True) 315 para = ParagraphStyle() 316 para.set_font(font) 317 para.set_alignment(PARA_ALIGN_CENTER) 318 para.set_description(_("The style used for the subtitle.")) 319 default_style.add_paragraph_style('REC-Subtitle', para) 320 321 font = FontStyle() 322 font.set_size(12) 323 font.set_bold(True) 324 para = ParagraphStyle() 325 para.set_font(font) 326 para.set_top_margin(utils.pt2cm(6)) 327 para.set_description(_('The style used for the section headers.')) 328 default_style.add_paragraph_style('REC-Heading', para) 329 330 font = FontStyle() 331 font.set_size(10) 332 para = ParagraphStyle() 333 para.set_font(font) 334 para.set_left_margin(0.5) 335 para.set_description(_('The basic style used for the text display.')) 336 default_style.add_paragraph_style('REC-Normal', para) 337 338 font = FontStyle() 339 font.set_size(8) 340 para = ParagraphStyle() 341 para.set_font(font) 342 para.set_alignment(PARA_ALIGN_CENTER) 343 para.set_top_border(True) 344 para.set_top_margin(utils.pt2cm(8)) 345 para.set_description(_('The style used for the footer.')) 346 default_style.add_paragraph_style('REC-Footer', para) 347