2019-11-08 11:46:40 -06:00

2192 lines
89 KiB
Python

# -*- coding: utf-8 -*-
#!/usr/bin/python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2007 Thom Sturgill
# Copyright (C) 2007-2009 Brian G. Matherly
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2008 Jason Simanek
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2015- Serge Noiraud
#
# 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.
#
"""
Web Calendar generator.
"""
#------------------------------------------------------------------------
# python modules
#------------------------------------------------------------------------
import os
import shutil
import datetime
import time
import calendar # Python module
#------------------------------------------------------------------------
# Set up logging
#------------------------------------------------------------------------
import logging
#------------------------------------------------------------------------
# Gramps module
#------------------------------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.gen.lib import Date, Name, NameType, Person
from gramps.gen.lib.date import Today
from gramps.plugins.webreport.common import html_escape
from gramps.gen.const import PROGRAM_NAME, URL_HOMEPAGE
from gramps.version import VERSION
from gramps.gen.constfunc import win
from gramps.gen.config import config
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.plug.menu import (BooleanOption, NumberOption, StringOption,
EnumeratedListOption, FilterOption,
PersonOption, DestinationOption, NoteOption)
from gramps.gen.utils.config import get_researcher
from gramps.gen.utils.alive import probably_alive
from gramps.gen.utils.db import get_death_or_fallback
from gramps.gen.datehandler import displayer as _dd
from gramps.gen.display.name import displayer as _nd
import gramps.plugins.lib.libholiday as libholiday
from gramps.plugins.lib.libhtml import Html, xml_lang
from gramps.plugins.lib.libhtmlconst import _CHARACTER_SETS, _CC, _COPY_OPTIONS
from gramps.gui.pluginmanager import GuiPluginManager
from gramps.gen.lib.date import gregorian
# import styled notes from
# src/plugins/lib/libhtmlbackend.py
from gramps.plugins.lib.libhtmlbackend import HtmlBackend
#------------------------------------------------------------------------
# constants
#------------------------------------------------------------------------
_ = glocale.translation.sgettext
_LOG = logging.getLogger(".WebPage")
# full clear line for proper styling
FULLCLEAR = Html("div", class_="fullclear", inline=True)
# Web page filename extensions
_WEB_EXT = ['.html', '.htm', '.shtml', '.php', '.php3', '.cgi']
# Calendar stylesheet names
_CALENDARSCREEN = 'calendar-screen.css'
_CALENDARPRINT = 'calendar-print.css'
PLUGMAN = GuiPluginManager.get_instance()
CSS = PLUGMAN.process_plugin_data('WEBSTUFF')
#------------------------------------------------------------------------
#
# WebCalReport
#
#------------------------------------------------------------------------
class WebCalReport(Report):
"""
Create WebCalReport object that produces the report.
"""
def __init__(self, database, options, user):
Report.__init__(self, database, options, user)
self._user = user
stdoptions.run_private_data_option(self, options.menu)
# class to do conversion of styled notes to html markup
self._backend = HtmlBackend()
self.options = options
mgobn = lambda name: options.menu.get_option_by_name(name).get_value()
self.set_locale(options.menu.get_option_by_name('trans').get_value())
stdoptions.run_date_format_option(self, options.menu)
self.rlocale = self._locale
self._ = self.rlocale.translation.sgettext
self.html_dir = mgobn('target')
self.title_text = html_escape(mgobn('title'))
filter_option = options.menu.get_option_by_name('filter')
self.filter = filter_option.get_filter()
self.name_format = mgobn('name_format')
self.ext = mgobn('ext')
self.copy = mgobn('cright')
self.css = mgobn('css')
self.country = mgobn('country')
self.start_dow = mgobn('start_dow')
self.multiyear = mgobn('multiyear')
self.start_year = mgobn('start_year')
self.end_year = mgobn('end_year')
self.after_year = mgobn('after_year')
if not self.multiyear:
self.end_year = self.start_year
if self.end_year < self.start_year:
self.end_year = self.start_year
self.maiden_name = mgobn('maiden_name')
self.alive = mgobn('alive')
self.birthday = mgobn('birthdays')
self.anniv = mgobn('anniversaries')
self.death_anniv = mgobn('death_anniv')
self.home_link = mgobn('home_link')
self.event_list = []
self.month_notes = [mgobn('note_' + month)
for month in ['jan', 'feb', 'mar', 'apr', 'may',
'jun', 'jul', 'aug', 'sep', 'oct',
'nov', 'dec']]
self.encoding = mgobn('encoding')
self.fullyear = True
self.makeoneday = mgobn('makeoneday')
# identify researcher name and e-mail address
# as NarrativeWeb already does
researcher = get_researcher()
self.author = researcher.name
if self.author:
self.author = self.author.replace(',,,', '')
self.email = researcher.email
# set to today's date
self.today = Today()
self.warn_dir = True # Only give warning once.
self.link_to_narweb = mgobn('link_to_narweb')
self.narweb_prefix = mgobn('prefix')
# self.calendar is a dict; key is the month number
# Each entry in the dict is also a dict; key is the day number.
# The day dict is a list of things to display for that day.
# These things are: birthdays and anniversaries
self.calendar = {}
self.holidays = {}
calendar.setfirstweekday(DOW_GRAMPS2ISO[self.start_dow])
def get_note_format(self, note):
"""
will get the note from the database, and will return either the
styled text or plain note
"""
# retrieve the body of the note
note_text = note.get()
# styled notes
htmlnotetext = self.styled_note(note.get_styledtext(),
note.get_format())
text = htmlnotetext or Html("p", note_text)
# return text of the note to its callers
return text
#################################################
# Will produce styled notes for WebCal by using:
# src/plugins/lib/libhtmlbackend.py
#################################################
def styled_note(self, styledtext, format_type):
"""
styledtext : assumed a StyledText object to write
format_type : = 0 : Flowed, = 1 : Preformatted
style_name : name of the style to use for default presentation
"""
text = str(styledtext)
if not text:
return ''
s_tags = styledtext.get_tags()
#FIXME: following split should be regex to match \n\s*\n instead?
markuptext = self._backend.add_markup_from_styled(text, s_tags,
split='\n\n')
htmllist = Html("div", id="grampsstylednote")
if format_type == 1:
#preformatted, retain whitespace.
#so use \n\n for paragraph detection
#FIXME: following split should be regex to match \n\s*\n instead?
htmllist += Html('pre', indent=None, inline=True)
for line in markuptext.split('\n\n'):
htmllist += Html("p")
for realline in line.split('\n'):
htmllist += realline
htmllist += Html('br')
elif format_type == 0:
#flowed
#FIXME: following split should be regex to match \n\s*\n instead?
for line in markuptext.split('\n\n'):
htmllist += Html("p")
htmllist += line
return htmllist
def copy_file(self, from_fname, to_fname, to_dir=''):
"""
Copy a file from a source to a (report) destination.
If to_dir is not present and if the target is not an archive,
then the destination directory will be created.
Normally 'to_fname' will be just a filename, without directory path.
'to_dir' is the relative path name in the destination root. It will
be prepended before 'to_fname'.
"""
dest = os.path.join(self.html_dir, to_dir, to_fname)
destdir = os.path.dirname(dest)
if not os.path.isdir(destdir):
os.makedirs(destdir)
if from_fname != dest:
shutil.copyfile(from_fname, dest)
elif self.warn_dir:
self._user.warn(
_("Possible destination error") + "\n" +
_("You appear to have set your target directory "
"to a directory used for data storage. This "
"could create problems with file management. "
"It is recommended that you consider using "
"a different directory to store your generated "
"web pages."))
self.warn_dir = False
config.set('paths.website-directory',
os.path.dirname(self.html_dir) + os.sep)
def add_day_item(self, text, year, month, day,
event, age_at_death, dead_event_date):
"""
adds birthdays, anniversaries, and holidays to their perspective lists
text -- line to be added
year, month, day -- date to add the text to
event -- one of 'BirthDay', 'Anniversary', 'Death' or 'Holiday'
age_at_death -- The age in text. ie : 68 years, 6 months
dead_event_date -- The date of the event used to calculate
the age_at_death
"""
if year <= self.after_year:
return
# This may happen for certain "about" dates.
# Use first day of the month
if day == 0:
day = 1
# determine which dictionary to use???
if event in ['Birthday', 'Anniversary', 'Death']:
month_dict = self.calendar.get(month, {})
else:
month_dict = self.holidays.get(month, {})
day_list = month_dict.get(day, [])
if month > 0:
try:
event_date = Date(year, month, day)
except ValueError:
event_date = Date.EMPTY
else:
event_date = Date.EMPTY # Incomplete date....
day_list.append((text, event, event_date,
age_at_death, dead_event_date))
month_dict[day] = day_list
# determine which dictionary to add it to???
if event in ['Birthday', 'Anniversary', 'Death']:
self.calendar[month] = month_dict
else:
self.holidays[month] = month_dict
def __get_holidays(self, year):
""" Get the holidays for the specified country and year """
# _('translation')
with self._user.progress(_("Web Calendar Report"),
_('Calculating Holidays for year %04d') % year,
365) as step:
holiday_table = libholiday.HolidayTable()
country = holiday_table.get_countries()[self.country]
holiday_table.load_holidays(year, country)
for month in range(1, 13):
for day in range(1, 32):
holiday_names = holiday_table.get_holidays(month, day)
for holiday_name in holiday_names:
self.add_day_item(holiday_name, year, month,
day, 'Holiday', None, None)
step()
def copy_calendar_files(self):
"""
Copies all the necessary stylesheets and images for these calendars
"""
imgs = []
# copy all screen style sheet
for css_f in CSS:
already_done = False
for css_fn in ("UsEr_", "Basic", "Mainz", "Nebraska", "Vis"):
if css_fn in css_f and not already_done:
already_done = True
fname = CSS[css_f]["filename"]
# add images for this css
imgs += CSS[css_f]["images"]
css_f = css_f.replace("UsEr_", "")
self.copy_file(fname, css_f + ".css", "css")
# Copy the screen stylesheet
if self.css and self.css != 'No style sheet':
fname = CSS[self.css]["filename"]
self.copy_file(fname, _CALENDARSCREEN, "css")
# copy Navigation Menu Layout if Blue or Visually is being used
if CSS[self.css]["navigation"]:
# copy horizontal menus...
fname = CSS["Horizontal-Menus"]["filename"]
self.copy_file(fname, "calendar-menus.css", "css")
# copy print stylesheet
fname = CSS["Print-Default"]["filename"]
self.copy_file(fname, _CALENDARPRINT, "css")
# Mainz stylesheet graphics
# will only be used if Mainz is slected as the stylesheet
imgs += CSS[self.css]["images"]
# copy copyright image
# the proper way would be to call "filename", but it is NOT working...
if 0 < self.copy <= len(_CC):
imgs += [CSS["Copyright"]["filename"]]
# copy Gramps favicon #2
imgs += [CSS["favicon2"]["filename"]]
for from_path in imgs:
dummy_fdir, fname = os.path.split(from_path)
self.copy_file(from_path, fname, "images")
def create_file(self, fname, subdir):
"""
Create a file in the html_dir tree.
If the directory does not exist, create it.
fname -- filename to be created
subdir -- any subdirs to be added
"""
fname = os.path.join(self.html_dir, subdir, fname)
if not _has_webpage_extension(fname):
fname += self.ext
destdir = os.path.dirname(fname)
if not os.path.isdir(destdir):
os.makedirs(destdir)
output_file = open(fname, 'w', encoding=self.encoding,
errors='xmlcharrefreplace')
return output_file
def close_file(self, output_file):
""" will close whatever filename is passed to it """
output_file.close()
def write_header(self, nr_up, title, body_id=None, add_print=True):
"""
This creates the header for the Calendars
'nr_up' - number of directory levels up, started from current page, to
the root of the directory tree (i.e. to self.html_dir).
title -- to be inserted into page header section
add_print -- whether to add printer stylesheet or not
* only webcalendar() and one_day() only!
"""
# number of subdirectories up to reach root
subdirs = ['..'] * nr_up
# Header contants
xmllang = xml_lang()
_meta1 = 'name ="viewport" content="width=device-width, '\
'initial-scale=1.0, maximum-scale=1.0, user-scalable=1"'
_meta2 = 'name ="apple-mobile-web-app-capable" content="yes"'
_meta3 = 'name="generator" content="%s %s %s"' % (
PROGRAM_NAME, VERSION, URL_HOMEPAGE)
_meta4 = 'name="author" content="%s"' % self.author
# create additional meta tags
meta = Html("meta", attr=_meta1) + (
Html("meta", attr=_meta2, indent=False),
Html("meta", attr=_meta3, indent=False),
Html("meta", attr=_meta4, indent=False)
)
# begin each html page...
page, head, body = Html.page(title,
self.encoding,
xmllang)
# add body id tag if not None
if body_id is not None:
body.attr = "id = '%(idtag)s'" % {'idtag' : body_id}
# Gramps favicon
fname1 = "/".join(subdirs + ["images", "favicon2.ico"])
# _CALENDARSCREEN stylesheet
fname2 = "/".join(subdirs + ["css", _CALENDARSCREEN])
# links for Gramps favicon and stylesheets
links = Html("link", rel='shortcut icon',
href=fname1, type="image/x-icon") + (
Html("link", href=fname2, type="text/css",
title=self._("Default"),
media="screen", rel="stylesheet", indent=False))
# create all alternate stylesheets
# Cannot use it on local files (file://)
for css_f in CSS:
already_done = False
for css_fn in ("UsEr_", "Basic", "Mainz", "Nebraska"):
if css_fn in css_f and not already_done:
css_f = css_f.replace("UsEr_", "")
fname = "/".join(subdirs + ["css", css_f + ".css"])
links += Html("link", rel="alternate stylesheet",
title=css_f, indent=False,
media="screen", type="text/css",
href=fname)
# add horizontal menu if css == Blue or Visually because
# there is no menus?
if CSS[self.css]["navigation"]:
fname = "/".join(subdirs + ["css", "calendar-menus.css"])
links.extend(
Html("link", href=fname, type="text/css",
media="screen", rel="stylesheet", indent=False)
)
# add printer stylesheet to webcalendar() and one_day() only
if add_print:
fname = "/".join(subdirs + ["css", _CALENDARPRINT])
links.extend(
Html("link", href=fname, type="text/css",
media="print", rel="stylesheet", indent=False)
)
# add meta tags and links to head section
head += (meta, links)
# start header section and page title...
with Html("div", id="header", role="Title-n-Navigation") as header:
header += Html("h1", title, id="SiteTitle", inline=True)
# Created for ?
msg = None
if self.author and self.email:
bemail = '<a href="mailto:' + self.email + '?subject='
eemail = '">' + self.author + '</a>'
msg = self._('the "WebCal" will be the potential-email Subject|'
'Created for %(html_email_author_start)s'
'WebCal%(html_email_author_end)s') % {
'html_email_author_start' : bemail,
'html_email_author_end' : eemail}
elif self.author:
msg = self._('Created for %(author)s') % {
'author' : self.author}
if msg:
header += Html("p", msg, id="CreatorInfo")
body += header
return page, body
def year_navigation(self, nr_up, currentsection):
"""
This will create the year navigation menu bar for a total of
seventeen (17) years
nr_up = number of directories up to reach root directory
currentsection = proper styling of this navigation bar
"""
# limit number of years to eighteen (18) years and only one row of years
nyears = ((self.end_year - self.start_year) + 1)
num_years = nyears if 0 < nyears < 19 else 18
self.end_year = (self.start_year + 17) if nyears > 18 else self.end_year
# begin year division and begin unordered list
with Html("div", id="subnavigation",
role="subnavigation") as submenu:
unordered = Html("ul")
for cal_year in range(self.start_year,
(self.start_year + num_years)):
url = ''
# begin subdir level
subdirs = ['..'] * nr_up
subdirs.append(str(cal_year))
# each year will link to current month.
# this will always need an extension added
month = int(self.today.get_month())
full_month_name = self.rlocale.date_displayer.long_months[month]
full_month_name = full_month_name.lower()
# Note. We use '/' here because it is a URL, not a OS dependent
# pathname.
url = '/'.join(subdirs + [full_month_name]) + self.ext
hyper = Html("a", self.rlocale.get_date(Date(cal_year)),
href=url, title=str(cal_year))
# Figure out if we need <li class="CurrentSection">
# or just plain <li>
if str(cal_year) == currentsection:
check_cs = 'class = "CurrentSection"'
else:
check_cs = False
if check_cs:
unordered.extend(
Html("li", hyper, attr=check_cs, inline=True)
)
else:
unordered.extend(
Html("li", hyper, inline=True)
)
submenu += unordered
return submenu
def month_navigation(self, nr_up, year, currentsection, add_home):
"""
Will create and display the navigation menu bar
nr_up = number of directories up to reach root directory
year = year being created
currentsection = month name being created for proper CSS styling
add_home = if creating a link to home
-- a link to root directory of website
"""
navs = []
# An optional link to a home page
if self.home_link:
navs.append((self.home_link, self._('Home'), add_home))
navs.extend((self.rlocale.date_displayer.long_months[int(month)],
self.rlocale.date_displayer.short_months[int(month)], True)
for month in range(1, 13))
# Add a link for year_glance() if requested
navs.append(('fullyearlinked', self._('Year Glance'), self.fullyear))
# remove menu items if they are not True
navs = [(u, n) for u, n, c in navs if c]
# begin month subnavigation
with Html("div", class_="wrapper",
id="nav", role="navigation") as navigation:
with Html("div", class_="container") as container:
unordered = Html("ul", class_="menu")
for url_fname, nav_text in navs:
# Note. We use '/' here because it is a URL, not a OS
# dependent pathname need to leave home link alone,
# so look for it ...
if nav_text != _("Home"):
url_fname = url_fname.lower()
url = url_fname
add_subdirs = False
if not (url.startswith('http:') or url.startswith('/')):
add_subdirs = not any(url.endswith(ext)
for ext in _WEB_EXT)
# whether to add subdirs or not???
if add_subdirs:
subdirs = ['..'] * nr_up
subdirs.append(str(year))
url = '/'.join(subdirs + [url_fname])
if not _has_webpage_extension(url):
url += self.ext
# Figure out if we need <li class="CurrentSection"> or
# just plain <li>
if url_fname == currentsection:
check_cs = 'class = "CurrentSection"'
else:
check_cs = False
if url == self.home_link:
mytitle = self._("NarrativeWeb Home")
elif url_fname == 'fullyearlinked':
mytitle = self._('Full year at a Glance')
else:
mytitle = self._(url_fname)
hyper = Html("a", nav_text, href=url,
name=url_fname, title=mytitle)
if check_cs:
unordered.extend(
Html("li", hyper, attr=check_cs, inline=True)
)
else:
unordered.extend(
Html("li", hyper, inline=True)
)
container += unordered
navigation += container
return navigation
def calendar_build(self, cal, year, month, clickable=False):
"""
This does the work of building the calendar
@param: cal - either "yg" year_glance(), or "wc" webcalendar()
@param: year -- year being created
@param: month - month number 1, 2, .., 12
"""
date_displayer = self.rlocale.date_displayer
# define names for long and short month names
full_month_name = date_displayer.long_months[int(month)]
abbr_month_name = date_displayer.short_months[int(month)]
# dow (day-of-week) uses Gramps numbering, sunday => 1, etc
start_dow = self.start_dow
col2day = [(x-1)%7+1 for x in range(start_dow, start_dow + 7)]
def get_class_for_daycol(col):
""" Translate a Gramps day number into a HTMLclass """
day = col2day[col]
if day == 1:
return "weekend sunday"
elif day == 7:
return "weekend saturday"
return "weekday"
def get_name_for_daycol(col):
""" Translate a Gramps day number into a HTMLclass """
day = col2day[col]
return day_names[day]
# Note. gen.datehandler has sunday => 1, monday => 2, etc
# We slice out the first empty element.
day_names = date_displayer.long_days # use self._ldd.long_days when
# set_locale is used ...
def __get_previous_month_day(year, month, day_col):
if month == 1:
prevmonth = calendar.monthcalendar((year - 1), 12)
else:
prevmonth = calendar.monthcalendar(year, (month - 1))
num_weeks = len(prevmonth)
lastweek_prevmonth = prevmonth[(num_weeks - 1)]
previous_month_day = lastweek_prevmonth[day_col]
# return previous month day number based on day_col
# day_col is based on range(0 - 6)
return previous_month_day
def __get_next_month_day(year, month, day_col):
if month == 12:
nextmonth = calendar.monthcalendar((year + 1), 1)
else:
nextmonth = calendar.monthcalendar(year, (month + 1))
firstweek_nextmonth = nextmonth[0]
next_month_day = firstweek_nextmonth[day_col]
# return next month day number based on day_col
# day_col is based on range(0 - 6)
return next_month_day
# Begin calendar head. We'll use the capitalized name, because here it
# seems appropriate for most countries.
month_name = full_month_name.capitalize()
th_txt = month_name
if cal == 'wc': # webcalendar()
if not self.multiyear:
th_txt = '%s %s' % (month_name,
self._get_date(Date(year))) # localized
# begin calendar table and table head
with Html("table", class_="calendar",
id=month_name, role="Calendar-Grid") as table:
thead = Html("thead")
table += thead
if clickable:
name = th_txt + self.ext
url = name.lower()
linkable = Html("a", th_txt, href=url, name=url, title=th_txt)
else:
linkable = th_txt
if not self.multiyear:
self.end_year = self.start_year
if month > 1:
full_month_name = date_displayer.long_months[month-1]
url = full_month_name.lower() + self.ext
prevm = Date(int(year), int(month-1), 0)
my_title = Html("a", html_escape("<"), href=url,
title=date_displayer.display(prevm))
elif self.multiyear and year > self.start_year:
full_month_name = date_displayer.long_months[12]
url = full_month_name.lower() + self.ext
dest = os.path.join("../", str(year-1), url)
prevm = Date(int(year-1), 12, 0)
my_title = Html("a", html_escape("<"), href=dest,
title=date_displayer.display(prevm))
else:
full_month_name = date_displayer.long_months[12]
url = full_month_name.lower() + self.ext
dest = os.path.join("../", str(self.end_year), url)
prevy = Date(self.end_year, 12, 0)
my_title = Html("a", html_escape("<"), href=dest,
title=date_displayer.display(prevy))
my_title += Html("</a>&nbsp;")
if month < 12:
full_month_name = date_displayer.long_months[month+1]
url = full_month_name.lower() + self.ext
nextd = Date(int(year), int(month+1), 0)
my_title += Html("a", html_escape(">"), href=url,
title=date_displayer.display(nextd))
elif self.multiyear and year < self.end_year:
full_month_name = date_displayer.long_months[1]
url = full_month_name.lower() + self.ext
dest = os.path.join("../", str(year+1), url)
nextd = Date(int(year+1), 1, 0)
my_title += Html("a", html_escape(">"), href=dest,
title=date_displayer.display(nextd))
else:
full_month_name = date_displayer.long_months[1]
url = full_month_name.lower() + self.ext
dest = os.path.join("../", str(self.start_year), url)
nexty = Date(self.start_year, 1, 0)
my_title += Html("a", html_escape(">"), href=dest,
title=date_displayer.display(nexty))
my_title += Html("</a>")
trow = Html("tr") + (
Html("th", my_title, class_='monthName',
colspan=7, inline=True)
)
thead += trow
trow = Html("tr") + (
Html("th", linkable, class_='monthName', colspan=7, inline=True)
)
thead += trow
# Calendar weekday names header
trow = Html("tr")
thead += trow
for day_col in range(7):
dayclass = get_class_for_daycol(day_col)
dayname = self._(get_name_for_daycol(day_col))
trow += Html("th", class_=dayclass, inline=True) + (
Html('abbr', dayname[0], title=dayname))
# begin table body
tbody = Html("tbody")
table += tbody
# get first of the month and month information
(dummy_current_date,
current_ord, monthinfo) = get_first_day_of_month(year, month)
# begin calendar table rows, starting week0
nweeks = len(monthinfo)
for week_row in range(0, nweeks):
week = monthinfo[week_row]
# if you look this up in wikipedia, the first week
# is called week0
trow = Html("tr", class_="week%02d" % week_row)
tbody += trow
# begin calendar day column
for day_col in range(0, 7):
dayclass = get_class_for_daycol(day_col)
# day number, can also be a zero -- a day before or
# after month
day = week[day_col]
# start the beginning variable for <td>, table cell
tcell_id = "%s%02d" % (abbr_month_name, day)
# add calendar date division
datediv = Html("div", day, class_="date", inline=True)
### a day in the previous or next month ###
if day == 0:
# day in previous/ next month
specday = __get_previous_month_day(year, month, day_col
) if week_row == 0 \
else __get_next_month_day(year, month, day_col)
specclass = "previous " if week_row == 0 else "next "
specclass += dayclass
# continue table cell, <td>, without id tag
tcell = Html("td", class_=specclass, inline=True) + (
Html("div", specday, class_="date", inline=True))
# normal day number in current month
else:
thisday = datetime.date.fromordinal(current_ord)
# Something this month
if thisday.month == month:
holiday_list = self.holidays.get(month,
{}).get(
thisday.day,
[])
bday_anniv_list = self.calendar.get(month,
{}).get(
thisday.day,
[])
# date is an instance because of subtracting
# abilities in date.py
event_date = Date(thisday.year, thisday.month,
thisday.day)
# get events for this day
day_list = get_day_list(event_date, holiday_list,
bday_anniv_list,
rlocale=self.rlocale)
# is there something this day?
if day_list:
hilightday = 'highlight ' + dayclass
tcell = Html("td", id=tcell_id,
class_=hilightday)
# Year at a Glance
if cal == "yg":
# make one day pages and hyperlink
if self.makeoneday:
# create yyyymmdd date string for
# "One Day" calendar page filename
fname_date = '%04d%02d%02d' % (year,
month,
day)
fname_date += self.ext
# create hyperlink to one_day()
tcell += Html("a", datediv,
href=fname_date,
inline=True)
# only year_glance() needs this to
# create the one_day() pages
self.one_day(event_date, fname_date,
day_list)
# just year_glance(), but no one_day() pages
else:
# continue table cell, <td>,
# without id tag
tcell = Html("td", class_=hilightday,
inline=True) + (
# adds date division
Html("div", day,
class_="date",
inline=True))
# WebCal
else:
# add date to table cell
tcell += datediv
# list the events
unordered = Html("ul")
tcell += unordered
for (dummy_nyears, dummy_date, text,
event, dummy_notused,
dummy_notused) in day_list:
unordered += Html("li", text,
inline=False
if (event ==
'Anniversary')
else True)
# no events for this day
else:
# create empty day with date
tcell = Html("td", class_=dayclass,
inline=True) + (
# adds date division
Html("div", day, class_="date",
inline=True))
# nothing for this month
else:
tcell = Html("td", class_=dayclass) + (
# adds date division
Html("div", day, class_="date", inline=True))
# attach table cell to table row
# close the day column
trow += tcell
# change day number
current_ord += 1
if cal == "yg":
for weeks in range(nweeks, 6):
# each calendar must have six weeks for proper styling
# and alignment
with Html("tr",
class_="week%02d" % (weeks + 1)) as six_weeks:
tbody += six_weeks
for dummy_emptydays in range(7):
six_weeks += Html("td", class_="emptyDays",
inline=True)
# return calendar table to its callers
return table
def webcalendar(self, year):
"""
This method provides information and header/ footer
to the calendar month
year -- year being created
"""
# do some error correcting if needed
if self.multiyear:
if self.end_year < self.start_year:
self.end_year = self.start_year
nr_up = 1 # Number of directory levels up to get to self.html_dir / root
with self._user.progress(_("Web Calendar Report"),
_('Formatting months ...'), 12) as step:
for month in range(1, 13):
cal_fname = self.rlocale.date_displayer.long_months[int(month)]
cal_fname = cal_fname.lower()
open_file = self.create_file(cal_fname, str(year))
# Add xml, doctype, meta and stylesheets
# body has already been added to webcal already once
webcal, body = self.write_header(nr_up, self.title_text)
# create Year Navigation menu
if self.multiyear and ((self.end_year - self.start_year) > 0):
body += self.year_navigation(nr_up, str(year))
# Create Month Navigation Menu
# identify currentsection for proper highlighting
currentsection = _dd.long_months[month]
body += self.month_navigation(nr_up, year, currentsection, True)
# build the calendar
content = Html("div", class_="content", id="WebCal")
body += content
monthly_calendar = self.calendar_build("wc", year, month)
content += monthly_calendar
# create note section for webcalendar()
# One has to be minused because the array starts at zero,
# but January =1
note = self.month_notes[month-1].strip()
if note:
note = self.database.get_note_from_gramps_id(note)
note = self.get_note_format(note)
# table foot section
cal_foot = Html("tfoot")
monthly_calendar += cal_foot
trow = Html("tr") + (
Html("td", note, colspan=7, inline=True)
)
cal_foot += trow
# create blank line for stylesheets
# create footer division section
footer = self.write_footer(nr_up)
body += (FULLCLEAR, footer)
# send calendar page to web output
# and close the file
self.xhtmlwriter(webcal, open_file)
step()
def year_glance(self, year):
"""
This method will create the Full Year At A Glance Page...
year -- year being created
"""
self.event_list = []
prv = None
nxt = None
evdte = None
for month in sorted(self.calendar):
vals = sorted(self.calendar.get(month, {}))
if month == 0: # why ?
continue
for day in vals:
event_date = "%04d%02d%02d" % (year, month, day)
if evdte is None:
evdte = event_date
elif nxt is None:
nxt = event_date
self.event_list.append((evdte, prv, nxt))
else:
prv = evdte
evdte = nxt
nxt = event_date
self.event_list.append((evdte, prv, nxt))
self.event_list.append((nxt, evdte, None))
nr_up = 1 # Number of directory levels up to get to root
# generate progress pass for "Year At A Glance"
with self._user.progress(_("Web Calendar Report"),
_('Creating Year At A Glance calendar'),
12) as step:
open_file = self.create_file('fullyearlinked', str(year))
# page title
title = self._("%(year)d, At A Glance") % {'year' : year}
# Create page header
# body has already been added to yearglance already once
yearglance, body = self.write_header(nr_up, title,
"fullyearlinked", False)
# create Year Navigation menu
if self.multiyear and ((self.end_year - self.start_year) > 0):
body += self.year_navigation(nr_up, str(year))
# Create Month Navigation Menu
# identify currentsection for proper highlighting
body += self.month_navigation(nr_up, year, "fullyearlinked", True)
msg = (self._('This calendar is meant to give you access '
'to all your data at a glance compressed into one '
'page. Clicking on a date will take you to a page '
'that shows all the events for that date, if there '
'are any.\n'))
# page description
content = Html("div", class_="content", id="YearGlance")
body += content
content += Html("p", msg, id='description')
for month in range(1, 13):
# build the calendar
monthly_calendar = self.calendar_build("yg", year, month,
clickable=True)
content += monthly_calendar
# increase progress bar
step()
# create blank line for stylesheets
# write footer section
footer = self.write_footer(nr_up)
body += (FULLCLEAR, footer)
# send calendar page to web output
# and close the file
self.xhtmlwriter(yearglance, open_file)
def one_day(self, event_date, fname_date, day_list):
"""
This method creates the One Day page for "Year At A Glance"
event_date -- date for the listed events
fname_date -- filename date from calendar_build()
day_list -- a combination of both dictionaries to be able
to create one day
nyears, date, text, event -- are necessary for figuring the age
or years married for each year being created...
"""
nr_up = 1 # number of directory levels up to get to root
# get year and month from event_date for use in this section
year = event_date.get_year()
month = event_date.get_month()
one_day_file = self.create_file(fname_date, str(year))
# page title
title = self._('One Day Within A Year')
# create page header
oneday, body = self.write_header(nr_up, title, "OneDay")
# create Year Navigation menu
if self.multiyear and ((self.end_year - self.start_year) > 0):
body += self.year_navigation(nr_up, str(year))
# Create Month Navigation Menu
# identify currentsection for proper highlighting
currentsection = _dd.long_months[month]
body += self.month_navigation(nr_up, year, currentsection, True)
# set date display as in user preferences
content = Html("div", class_="content", id="OneDay")
body += content
evt = fname_date[:8]
found = (evt, None, None)
for event in self.event_list:
if event[0] == evt:
found = event
break
my_title = Html()
url = "#"
if found[1] is not None:
url = event[1] + self.ext
prevd = Date(int(event[1][:4]), int(event[1][4:6]),
int(event[1][6:]))
my_title = Html("a", html_escape("<"), href=url,
title=self.rlocale.get_date(prevd))
else:
my_title = Html('<em>&nbsp;&nbsp;</em>')
my_title += Html("</a>&nbsp;")
if found[2] is not None:
url = event[2] + self.ext
nextd = Date(int(event[2][:4]), int(event[2][4:6]),
int(event[2][6:]))
my_title += Html("a", html_escape(">"), href=url,
title=self.rlocale.get_date(nextd))
else:
my_title += Html('<b>&nbsp;&nbsp;</b>')
my_title += Html("</a>")
content += Html("h3", my_title, inline=True)
my_title = Html("span", " ")
my_title += self.rlocale.date_displayer.display(event_date)
my_title += Html("span", " ")
content += Html("h3", my_title, inline=True)
# list the events
ordered = Html("ol")
content += ordered
for (dummy_nyears, dummy_date, text, event, dummy_age_at_death,
dummy_dead_event_date) in day_list:
ordered += Html("li", text,
inline=False if event == 'Anniversary' else True)
# create blank line for stylesheets
# write footer section
footer = self.write_footer(nr_up)
body += (FULLCLEAR, footer)
# send calendar page to web output
# and close the file
self.xhtmlwriter(oneday, one_day_file)
def build_url_fname_html(self, fname, subdir=None, prefix=None):
"""
build the url for the file name with sub directories and extension
"""
return self.build_url_fname(fname, subdir, prefix) + self.ext
def build_url_fname(self, fname, subdir, prefix=None):
"""
Create part of the URL given the filename and optionally
the subdirectory. If the subdirectory is given, then two extra levels
of subdirectory are inserted between 'subdir' and the filename.
The reason is to prevent directories with too many entries.
If 'prefix' is set, then is inserted in front of the result.
The extension is added to the filename as well.
Notice that we do NOT use os.path.join() because we're creating a URL.
Imagine we run gramps on Windows (heaven forbits), we don't want to
see backslashes in the URL.
"""
if win():
fname = fname.replace('\\', "/")
subdirs = self.build_subdirs(subdir, fname)
return (prefix or '') + "/".join(subdirs + [fname])
def build_subdirs(self, subdir, fname):
"""
If subdir is given, then two extra levels of subdirectory are inserted
between 'subdir' and the filename.
The reason is to prevent directories with too many entries.
For example,
this may return ['ppl', '8', '1'] given 'ppl', "aec934857df74d36618"
"""
subdirs = []
if subdir:
subdirs.append(subdir)
subdirs.append(fname[-1].lower())
subdirs.append(fname[-2].lower())
return subdirs
def get_name(self, person, maiden_name=None):
"""
Return person's name, unless maiden_name given, unless married_name
listed.
person -- person to get short name from
maiden_name -- either a woman's maiden name or man's surname
"""
# Get all of a person's names:
primary_name = person.primary_name
married_name = None
names = [primary_name] + person.get_alternate_names()
for name in names:
if int(name.get_type()) == NameType.MARRIED:
married_name = name
break
# Now, decide which to use:
if maiden_name is not None:
if married_name is not None:
name = Name(married_name)
else:
name = Name(primary_name)
surname_obj = name.get_primary_surname()
surname_obj.set_surname(maiden_name)
else:
name = Name(primary_name)
name.set_display_as(self.name_format)
return _nd.display_name(name)
def collect_data(self, this_year):
"""
This method runs through the data, and collects the relevant dates
and text.
"""
db = self.database
people = db.iter_person_handles()
people = self.filter.apply(db, people, user=self._user)
with self._user.progress(_("Web Calendar Report"),
_("Reading database..."), len(people)) as step:
for person in map(db.get_person_from_handle, people):
step()
family_list = person.get_family_handle_list()
birth_ref = person.get_birth_ref()
birth_date = Date()
if birth_ref:
birth_event = db.get_event_from_handle(birth_ref.ref)
birth_date = birth_event.get_date_object()
death_ref = person.get_death_ref()
person_death = Date()
age_at_death = None
if death_ref and birth_date:
death_event = db.get_event_from_handle(death_ref.ref)
death_date = death_event.get_date_object()
person_death = death_date
if (birth_date != Date() and birth_date.is_valid()
and death_date):
age_at_death = death_date - birth_date
age_at_death = age_at_death.format(dlocale=self.rlocale)
# determine birthday information???
if (self.birthday and birth_date is not Date()
and birth_date.is_valid()):
birth_date = gregorian(birth_date)
year = birth_date.get_year()
month = birth_date.get_month()
day = birth_date.get_day()
# date to figure if someone is still alive
# current year of calendar, month nd day is their birth
# month and birth day
prob_alive_date = Date(this_year, month, day)
# add some things to handle maiden name:
father_surname = None # husband, actually
if person.gender == Person.FEMALE:
# get husband's last name:
if self.maiden_name in ['spouse_first', 'spouse_last']:
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 is not None:
father_surname = _regular_surname(
person.gender,
father.get_primary_name())
short_name = self.get_name(person, father_surname)
alive = probably_alive(person, db, prob_alive_date)
if (self.alive and alive) or not self.alive:
# add link to NarrativeWeb
if self.link_to_narweb:
prfx = self.narweb_prefix
text = str(Html("a", short_name,
href=self.build_url_fname_html(
person.handle,
"ppl",
prefix=prfx)))
else:
text = short_name
if age_at_death is None:
self.add_day_item(text, year, month, day,
'Birthday',
age_at_death, birth_date)
else:
self.add_day_item(text, year, month, day,
'Birthday',
age_at_death, person_death)
death_event = get_death_or_fallback(db, person)
if death_event:
death_date = death_event.get_date_object()
else:
death_date = None
#primary_name = person.primary_name
#name = Name(primary_name)
if self.death_anniv and death_date:
year = death_date.get_year() or this_year
month = death_date.get_month()
day = death_date.get_day()
short_name = self.get_name(person)
prob_alive_date = Date(this_year, month, day)
alive = probably_alive(person, db, prob_alive_date)
if (self.alive and alive) or not self.alive:
# add link to NarrativeWeb
if self.link_to_narweb:
prfx = self.narweb_prefix
navpfx = self.build_url_fname_html(person.handle,
"ppl",
prefix=prfx)
text = str(Html("a", short_name, href=navpfx))
else:
text = short_name
self.add_day_item(text, year, month, day, 'Death',
age_at_death, death_date)
# add anniversary if requested
if self.anniv:
for fhandle in family_list:
fam = db.get_family_from_handle(fhandle)
father_handle = fam.get_father_handle()
mother_handle = fam.get_mother_handle()
if father_handle == person.handle:
spouse_handle = mother_handle
else:
continue # with next person if this was
# the marriage event
if spouse_handle:
spouse = db.get_person_from_handle(spouse_handle)
if spouse:
spouse_name = self.get_name(spouse)
short_name = self.get_name(person)
death_ref = spouse.get_death_ref()
spouse_death = Date()
if death_ref:
death_event = db.get_event_from_handle(
death_ref.ref)
death_date = death_event.get_date_object()
if (death_date != Date() and
death_date.is_valid()):
spouse_death = death_date
first_died = Date()
if person_death == Date():
first_died = spouse_death
elif spouse_death != Date():
first_died = person_death if spouse_death\
> person_death else spouse_death
else:
first_died = person_death
# will return a marriage event or False if not
# married any longer
marriage_event = get_marriage_event(db, fam)
if marriage_event:
event_date = marriage_event.get_date_object()
if (event_date is not Date() and
event_date.is_valid()):
event_date = gregorian(event_date)
year = event_date.get_year()
month = event_date.get_month()
day = event_date.get_day()
# date to figure if someone is still alive
prob_alive_date = Date(this_year,
month, day)
wedding_age = None
if first_died != Date():
wedding_age = first_died - event_date
wedding_age = wedding_age.format(
dlocale=self.rlocale)
divorce_event = get_divorce_event(db, fam)
if divorce_event:
d_date = divorce_event.get_date_object()
if (d_date is not Date() and
d_date.is_valid()):
d_date = gregorian(d_date)
if d_date != Date():
w_age = d_date - event_date
w_age = w_age.format(
dlocale=self.rlocale)
wedding_age = w_age
first_died = d_date
if self.link_to_narweb:
prefx = self.narweb_prefix
reference = self.build_url_fname_html(
spouse_handle, 'ppl',
prefix=prefx)
spouse_name = str(Html("a", spouse_name,
href=reference))
href1 = self.build_url_fname_html(
person.handle, 'ppl', prefix=prefx)
short_name = str(Html("a", short_name,
href=href1))
alive1 = probably_alive(person, db,
prob_alive_date)
alive2 = probably_alive(spouse, db,
prob_alive_date)
if first_died == Date():
first_died = Date(0, 0, 0)
if ((self.alive and (alive1 or alive2))
or not self.alive):
spse = self._('%(spouse)s and'
' %(person)s')
text = spse % {'spouse' : spouse_name,
'person' : short_name}
self.add_day_item(text, year, month,
day, 'Anniversary',
wedding_age,
first_died)
def write_footer(self, nr_up):
"""
Writes the footer section of the pages
'nr_up' - number of directory levels up, started from current page,
to the root of the directory tree (i.e. to self.html_dir).
"""
# begin calendar footer
with Html("div", id="footer", role="Footer-End") as footer:
# Display date as user set in preferences
date = self.rlocale.date_displayer.display(Today())
bhtml = '<a href="' + URL_HOMEPAGE + '">'
msg = self._('Generated by %(gramps_home_html_start)s'
'Gramps%(html_end)s on %(date)s') % {
'gramps_home_html_start' : bhtml,
'html_end' : '</a>',
'date' : date}
footer += Html("p", msg, id='createdate')
copy_nr = self.copy
text = ''
if copy_nr == 0:
if self.author:
text = "&copy; %s %s" % (self.today.get_year(), self.author)
elif 0 < copy_nr < len(_CC):
subdirs = ['..'] * nr_up
# Note. We use '/' here because it is a URL,
# not a OS dependent pathname
fname = '/'.join(subdirs + ['images'] + ['somerights20.gif'])
text = _CC[copy_nr] % {'gif_fname' : fname}
else:
text = "&copy; %s %s" % (self.today.get_year(), self.author)
footer += Html("p", text, id='copyright')
# return footer to its callers
return footer
def xhtmlwriter(self, page, open_file):
"""
This function is simply to make the web page look pretty and readable
It is not for the browser, but for us, humans
"""
# writes the file out from the page variable; Html instance
# This didn't work for some reason, but it does in NarWeb:
#page.write(partial(print, file=of.write))
page.write(lambda line: open_file.write(line + '\n'))
# close the file now...
self.close_file(open_file)
def write_report(self):
"""
The short method that runs through each month and creates a page.
"""
# get data from database for birthdays/ anniversaries
self.collect_data(self.start_year)
# Copy all files for the calendars being created
self.copy_calendar_files()
if self.multiyear:
# limit number of years to eighteen (18) years and only one row
# of years
nyears = ((self.end_year - self.start_year) + 1)
num_years = nyears if 0 < nyears < 19 else 18
for cal_year in range(self.start_year,
(self.start_year + num_years)):
# initialize the holidays dict to fill:
self.holidays = {}
# get the information, zero is equal to None
if self.country != 0:
self.__get_holidays(cal_year)
# create webcalendar() calendar pages
self.webcalendar(cal_year)
# create "Year At A Glance" and
# "One Day" calendar pages
if self.fullyear:
self.year_glance(cal_year)
if self.home_link:
self.create_page_index()
# a single year
else:
cal_year = self.start_year
self.holidays = {}
# get the information, first from holidays:
if self.country != 0:
self.__get_holidays(cal_year)
# create webcalendar() calendar pages
self.webcalendar(cal_year)
# create "Year At A Glance" and
# "One Day" calendar pages
if self.fullyear:
self.year_glance(cal_year)
if self.home_link:
self.create_page_index()
def create_page_index(self):
"""
Create the page index called by the narrativeweb.
"""
output_file = self.create_file('index', "")
# page title
title = self.title_text
nr_up = 0
# Create page header
# body has already been added to yearglance already once
index, body = self.write_header(nr_up, title, "index", False)
# create Year Navigation menu
current_year = time.strftime("%Y", time.gmtime())
body += self.year_navigation(nr_up, str(current_year))
# create blank line for stylesheets
# write footer section
footer = self.write_footer(nr_up)
body += (FULLCLEAR, footer)
# send calendar page to web output
# and close the file
self.xhtmlwriter(index, output_file)
# -----------------------------------------------------------------------------
# WebCalOptions; Creates the Menu
#------------------------------------------------------------------------------
class WebCalOptions(MenuReportOptions):
"""
Defines options and provides handling interface.
"""
def __init__(self, name, dbase):
self.__db = dbase
self.__pid = None
self.__filter = None
self.__links = None
self.__prefix = None
MenuReportOptions.__init__(self, name, dbase)
self.__multiyear = None
self.__start_year = None
self.__end_year = None
self.__after_year = None
def add_menu_options(self, menu):
"""
Add options to the menu for the web calendar.
"""
self.__add_report_options(menu)
self.__add_report2_options(menu)
self.__add_content_options(menu)
self.__add_advanced_options(menu)
self.__add_notes_options(menu)
def __add_report_options(self, menu):
"""
Options on the "Report Options" tab.
"""
category_name = _("Report Options")
dbname = self.__db.get_dbname()
default_dir = dbname + "_WEBCAL"
target = DestinationOption(
_("Destination"),
os.path.join(config.get('paths.website-directory'), default_dir))
target.set_help(_("The destination directory for the web files"))
target.set_directory_entry(True)
menu.add_option(category_name, "target", target)
title = StringOption(_('Calendar Title'), _('My Family Calendar'))
title.set_help(_("The title of the calendar"))
menu.add_option(category_name, "title", title)
self.__filter = FilterOption(_("Filter"), 0)
self.__filter.set_help(
_("Select filter to restrict people that appear on calendar"))
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()
ext = EnumeratedListOption(_("File extension"), ".html")
for etype in _WEB_EXT:
ext.add_item(etype, etype)
ext.set_help(_("The extension to be used for the web files"))
menu.add_option(category_name, "ext", ext)
cright = EnumeratedListOption(_('Copyright'), 0)
for index, copt in enumerate(_COPY_OPTIONS):
cright.add_item(index, copt)
cright.set_help(_("The copyright to be used for the web files"))
menu.add_option(category_name, "cright", cright)
css_list = sorted([(CSS[key]["translation"], CSS[key]["id"])
for key in list(CSS.keys())
if CSS[key]["user"]])
css = EnumeratedListOption(_('StyleSheet'), css_list[0][1])
for css_item in css_list:
css.add_item(css_item[1], css_item[0])
css.set_help(_('The stylesheet to be used for the web pages'))
menu.add_option(category_name, "css", css)
def __add_report2_options(self, menu):
"""
Options on the "Report Options (2)" tab.
"""
category_name = _("Report Options (2)")
# We must figure out the value of the first option before we can
# create the EnumeratedListOption
fmt_list = _nd.get_name_format()
defaultnum = _nd.get_default_format()
default = 0
for ind, val in enumerate(fmt_list):
if val[0] == defaultnum:
default = ind
break
name_format = EnumeratedListOption(_("Name format"),
fmt_list[default][0])
for num, name, dummy_fmt_str, dummy_act in fmt_list:
name_format.add_item(num, name)
name_format.set_help(_("Select the format to display names"))
menu.add_option(category_name, "name_format", name_format)
stdoptions.add_private_data_option(menu, category_name, default=False)
alive = BooleanOption(_("Include only living people"), True)
alive.set_help(_("Include only living people in the calendar"))
menu.add_option(category_name, "alive", alive)
locale_opt = stdoptions.add_localization_option(menu, category_name)
stdoptions.add_date_format_option(menu, category_name, locale_opt)
def __add_content_options(self, menu):
"""
Options on the "Content Options" tab.
"""
category_name = _("Content Options")
# set to today's date for use in menu, etc.
today = Today()
self.__multiyear = BooleanOption(_('Create multiple year calendars'),
False)
self.__multiyear.set_help(_('Whether to create Multiple year '
'calendars or not.'))
menu.add_option(category_name, 'multiyear', self.__multiyear)
self.__multiyear.connect('value-changed', self.__multiyear_changed)
self.__start_year = NumberOption(_('Start Year for the Calendar(s)'),
today.get_year(), 1900, 3000)
self.__start_year.set_help(_('Enter the starting year for the calendars'
' between 1900 - 3000'))
menu.add_option(category_name, 'start_year', self.__start_year)
self.__end_year = NumberOption(_('End Year for the Calendar(s)'),
today.get_year(), 1900, 3000)
self.__end_year.set_help(_('Enter the ending year for the calendars '
'between 1900 - 3000.'))
menu.add_option(category_name, 'end_year', self.__end_year)
self.__multiyear_changed()
country = EnumeratedListOption(_('Country for holidays'), 0)
holiday_table = libholiday.HolidayTable()
countries = holiday_table.get_countries()
countries.sort()
#if (len(countries) == 0 or
# (len(countries) > 0 and countries[0] != '')):
if (not countries or
(countries and countries[0] != '')):
countries.insert(0, '')
count = 0
for cntry in countries:
country.add_item(count, cntry)
count += 1
country.set_help(_("Holidays will be included for the selected "
"country"))
menu.add_option(category_name, "country", country)
# Default selection ????
start_dow = EnumeratedListOption(_("First day of week"), 1)
for count in range(1, 8):
start_dow.add_item(count, _dd.long_days[count].capitalize())
start_dow.set_help(_("Select the first day of the week "
"for the calendar"))
menu.add_option(category_name, "start_dow", start_dow)
maiden_name = EnumeratedListOption(_("Birthday surname"), "own")
maiden_name.add_item('spouse_first', _("Wives use husband's surname "
"(from first family listed)"))
maiden_name.add_item('spouse_last', _("Wives use husband's surname "
"(from last family listed)"))
maiden_name.add_item("own", _("Wives use their own surname"))
maiden_name.set_help(_("Select married women's displayed surname"))
menu.add_option(category_name, "maiden_name", maiden_name)
dbname = self.__db.get_dbname()
default_link = '../../' + dbname + "_NAVWEB/index.html"
home_link = StringOption(_('Home link'), default_link)
home_link.set_help(_("The link to be included to direct the user to "
"the main page of the web site"))
menu.add_option(category_name, "home_link", home_link)
def __add_notes_options(self, menu):
"""
Options on the "Months Notes" tabs.
"""
category_name = _("Jan - Jun Notes")
note_jan = NoteOption(_('January Note'))
note_jan.set_help(_("The note for the month of January"))
menu.add_option(category_name, "note_jan", note_jan)
note_feb = NoteOption(_('February Note'))
note_feb.set_help(_("The note for the month of February"))
menu.add_option(category_name, "note_feb", note_feb)
note_mar = NoteOption(_('March Note'))
note_mar.set_help(_("The note for the month of March"))
menu.add_option(category_name, "note_mar", note_mar)
note_apr = NoteOption(_('April Note'))
note_apr.set_help(_("The note for the month of April"))
menu.add_option(category_name, "note_apr", note_apr)
note_may = NoteOption(_('May Note'))
note_may.set_help(_("The note for the month of May"))
menu.add_option(category_name, "note_may", note_may)
note_jun = NoteOption(_('June Note'))
note_jun.set_help(_("The note for the month of June"))
menu.add_option(category_name, "note_jun", note_jun)
category_name = _("Jul - Dec Notes")
note_jul = NoteOption(_('July Note'))
note_jul.set_help(_("The note for the month of July"))
menu.add_option(category_name, "note_jul", note_jul)
note_aug = NoteOption(_('August Note'))
note_aug.set_help(_("The note for the month of August"))
menu.add_option(category_name, "note_aug", note_aug)
note_sep = NoteOption(_('September Note'))
note_sep.set_help(_("The note for the month of September"))
menu.add_option(category_name, "note_sep", note_sep)
note_oct = NoteOption(_('October Note'))
note_oct.set_help(_("The note for the month of October"))
menu.add_option(category_name, "note_oct", note_oct)
note_nov = NoteOption(_('November Note'))
note_nov.set_help(_("The note for the month of November"))
menu.add_option(category_name, "note_nov", note_nov)
note_dec = NoteOption(_('December Note'))
note_dec.set_help(_("The note for the month of December"))
menu.add_option(category_name, "note_dec", note_dec)
def __add_advanced_options(self, menu):
"""
Options for the advanced menu
"""
category_name = _('Advanced Options')
encoding = EnumeratedListOption(_('Character set encoding'),
_CHARACTER_SETS[0][1])
for eopt in _CHARACTER_SETS:
encoding.add_item(eopt[1], eopt[0])
encoding.set_help(_('The encoding to be used for the web files'))
menu.add_option(category_name, "encoding", encoding)
makeoneday = BooleanOption(_('Create one day event pages for'
' Year At A Glance calendar'), False)
makeoneday.set_help(_('Whether to create one day pages or not'))
menu.add_option(category_name, 'makeoneday', makeoneday)
birthdays = BooleanOption(_("Include birthdays"), True)
birthdays.set_help(_("Include birthdays in the calendar"))
menu.add_option(category_name, "birthdays", birthdays)
anniversaries = BooleanOption(_("Include anniversaries"), True)
anniversaries.set_help(_("Include anniversaries in the calendar"))
menu.add_option(category_name, "anniversaries", anniversaries)
anniversaries = BooleanOption(_('Include death dates'), False)
anniversaries.set_help(_('Include death anniversaries in the calendar'))
menu.add_option(category_name, 'death_anniv', anniversaries)
self.__links = BooleanOption(_('Link to Narrated Web Report'), False)
self.__links.set_help(_('Whether to link data to web report or not'))
menu.add_option(category_name, 'link_to_narweb', self.__links)
self.__links.connect('value-changed', self.__links_changed)
today = Today()
default_before = config.get('behavior.max-age-prob-alive')
self.__after_year = NumberOption(_('Show data only after year'),
(today.get_year() - default_before),
0, today.get_year())
self.__after_year.set_help(_("Show data only after this year."
" Default is current year - "
" 'maximum age probably alive' which is "
"defined in the dates preference tab."))
menu.add_option(category_name, 'after_year', self.__after_year)
dbname = self.__db.get_dbname()
default_prefix = '../../' + dbname + "_NAVWEB/"
self.__prefix = StringOption(_('Link prefix'), default_prefix)
self.__prefix.set_help(_("A Prefix on the links to take you to "
"Narrated Web Report"))
menu.add_option(category_name, "prefix", self.__prefix)
self.__links_changed()
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 = utils.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 1 <= filter_value <= 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 __multiyear_changed(self):
"""
Handles the ability to print multiple year calendars or not?
"""
mgobn = lambda name: self.menu.get_option_by_name(name)
self.__multiyear = mgobn('multiyear')
self.__start_year = mgobn('start_year')
self.__end_year = mgobn('end_year')
if self.__start_year:
self.__start_year.set_available(True)
if self.__multiyear.get_value():
self.__end_year.set_available(True)
else:
self.__end_year.set_available(False)
def __links_changed(self):
"""
Handle checkbox change.
"""
if self.__links.get_value():
self.__prefix.set_available(True)
else:
self.__prefix.set_available(False)
def _regular_surname(sex, name):
"""
Returns a name string built from the components of the Name instance.
"""
dummy_gender = sex
surname = name.get_surname()
suffix = name.get_suffix()
if suffix:
# TODO for Arabic, should the next line's comma be translated?
surname = surname + ", " + suffix
return surname
# Simple utility list to convert Gramps day-of-week numbering
# to calendar.firstweekday numbering
DOW_GRAMPS2ISO = [-1, calendar.SUNDAY, calendar.MONDAY, calendar.TUESDAY,
calendar.WEDNESDAY, calendar.THURSDAY, calendar.FRIDAY,
calendar.SATURDAY]
def get_marriage_event(db, family):
"""
marriage_event will either be the marriage event or False
"""
marriage_event = False
for event_ref in family.get_event_ref_list():
event = db.get_event_from_handle(event_ref.ref)
if event.type.is_marriage():
marriage_event = event
break
# return the marriage event or False to it caller
return marriage_event
def get_divorce_event(db, family):
"""
divorce will either be the divorce event or False
"""
divorce_event = False
for event_ref in family.get_event_ref_list():
event = db.get_event_from_handle(event_ref.ref)
if event.type.is_divorce():
divorce_event = event
break
# return the divorce event or False to it caller
return divorce_event
def get_first_day_of_month(year, month):
"""
Compute the first day to display for this month.
It can also be a day in the previous month.
"""
# first day of the month
current_date = datetime.date(year, month, 1)
# monthinfo is filled using standard Python library
# calendar.monthcalendar. It fills a list of 7-day-lists. The first day
# of the 7-day-list is determined by calendar.firstweekday.
monthinfo = calendar.monthcalendar(year, month)
current_ord = current_date.toordinal() - monthinfo[0].count(0)
return current_date, current_ord, monthinfo
def _has_webpage_extension(url):
"""
determine if a filename has an extension or not...
url = filename to be checked
"""
return any(url.endswith(ext) for ext in _WEB_EXT)
def get_day_list(event_date, holiday_list, bday_anniv_list, rlocale=glocale):
"""
Will fill day_list and return it to its caller: calendar_build()
holiday_list -- list of holidays for event_date
bday_anniv_list -- list of birthdays and anniversaries
for event_date
event_date -- date for this day_list
'day_list' - a combination of both dictionaries to be able
to create one day nyears, date, text, event --- are
necessary for figuring the age or years married for
each day being created...
rlocale -- the locale to use
"""
trans_text = rlocale.translation.sgettext
# initialize day_list
day_list = []
##################################################################
# birthday/ anniversary on this day
# Date.EMPTY signifies an incomplete date for an event. See add_day_item()
bday_anniv_list = [(t, e, d, n, x) for t, e, d, n, x in bday_anniv_list
if d != Date.EMPTY]
# number of years have to be at least zero
bday_anniv_list = [(t, e, d, n, x) for t, e, d, n, x in bday_anniv_list
if (event_date.get_year() - d.get_year()) >= 0]
# a holiday
# zero will force holidays to be first in list
nyears = 0
for text, event, date, notused, notused in holiday_list:
day_list.append((nyears, date, text, event, notused, notused))
# birthday and anniversary list
for text, event, date, age_at_death, dead_event_date in bday_anniv_list:
# number of years married, ex: 10
nyears = (event_date.get_year() - date.get_year())
# number of years for birthday, ex: 10 years
age_str = event_date - date
#age_str.format(precision=1, as_age=False, dlocale=rlocale)
age_str = age_str.format(precision=1, as_age=False, dlocale=rlocale)
# a birthday
if event == 'Birthday':
if age_at_death is not None:
death_symbol = "&#10014;" # latin cross for html code
trans_date = trans_text("Died %(death_date)s.")
translated_date = rlocale.get_date(dead_event_date)
mess = trans_date % {'death_date' : translated_date}
age = ", <font size='+1' ><b>%s</b></font> <em>%s (%s)" % (
death_symbol, mess, age_at_death)
else:
# TRANSLATORS: expands to smth like "12 years old",
# where "12 years" is already localized to your language
age = ', <em>'
date_y = date.get_year()
trans_date = trans_text("Born %(birth_date)s.")
old_date = trans_text('%s old')
translated_date = rlocale.get_date(dead_event_date)
age += old_date % (str(age_str) if (date_y != 0)
else trans_date % {
'birth_date' : translated_date})
txt_str = (text + age + '</em>')
# a death
if event == 'Death':
txt_str = (text + ', <em>'
+ (_('%s since death') % str(age_str) if nyears
else _('death'))
+ '</em>')
# an anniversary
elif event == "Anniversary":
if nyears == 0:
txt_str = trans_text('%(couple)s, <em>wedding</em>') % {
'couple' : text}
else:
if age_at_death is not None:
age = '%s %s' % (trans_text("Married"), age_at_death)
txt_str = "%s, <em>%s" % (text, age)
if isinstance(dead_event_date,
Date) and dead_event_date.get_year() > 0:
txt_str += " (" + trans_text("Until") + " "
txt_str += rlocale.get_date(dead_event_date)
txt_str += ")</em>"
else:
txt_str += "</em>"
else:
age = '<em>%s' % nyears
# translators: leave all/any {...} untranslated
ngettext = rlocale.translation.ngettext
txt_str = ngettext("{couple}, {years} year anniversary",
"{couple}, {years} year anniversary",
nyears).format(couple=text, years=age)
txt_str += "</em>"
txt_str = Html('span', txt_str, class_="yearsmarried")
day_list.append((nyears, date, txt_str, event,
age_at_death, dead_event_date))
# sort them based on number of years
# holidays will always be on top of event list
day_list = sorted(day_list, key=lambda x: (isinstance(x[0], str), x[0]))
# return to its caller calendar_build()
return day_list