#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2007          Thom Sturgill
# Copyright (C) 2007-2009 Brian G. Matherly
# Copyright (C) 2008-2009 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2008          Jason Simanek
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

# $Id: WebCal.py 15450 2010-05-23 04:24:49Z robhealey1 $

"""
Web Calendar generator.

Refactoring. This is an ongoing job until this plugin is in a better shape.
"""
from __future__ import with_statement
#------------------------------------------------------------------------
#
# python modules
#
#------------------------------------------------------------------------
import os, codecs, shutil, re
import datetime, calendar
from gen.ggettext import sgettext as _
from gen.ggettext import ngettext
from itertools import imap

#------------------------------------------------------------------------
#
# Set up logging
#
#------------------------------------------------------------------------
import logging
log = logging.getLogger(".WebPage")

#------------------------------------------------------------------------
#
# GRAMPS module
#
#------------------------------------------------------------------------
from gen.lib import date, Date, Name, Person, NameType, EventType
import const
import constfunc
from ReportBase import Report, ReportUtils, MenuReportOptions, CSS_FILES
from gen.plug.menu import BooleanOption, NumberOption, StringOption, \
                          EnumeratedListOption, FilterOption, PersonOption, \
                          DestinationOption, NoteOption
import GrampsLocale
from QuestionDialog import WarningDialog
from Utils import probably_alive, xml_lang, get_researcher
from gui.utils import ProgressMeter
from DateHandler import displayer as _dd

from gen.display.name import displayer as _nd
from DateHandler import displayer as _dd

import libholiday
from libhtml import Html
from libhtmlconst import _CHARACTER_SETS, _CC, _COPY_OPTIONS

# import styled notes from
# src/plugins/lib/libhtmlbackend.py
from libhtmlbackend import HtmlBackend
import Utils
#------------------------------------------------------------------------
#
# constants
#
#------------------------------------------------------------------------
# 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'

#------------------------------------------------------------------------
#
# WebCalReport
#
#------------------------------------------------------------------------
class WebCalReport(Report):
    """
    Create WebCalReport object that produces the report.
    """
    def __init__(self, database, options):
        Report.__init__(self, database, options)
        mgobn = lambda name:options.menu.get_option_by_name(name).get_value()

        # class to do conversion of styled notes to html markup
        self._backend = HtmlBackend()

        self.database = database
        self.options = options

        self.html_dir = mgobn('target')
        self.title_text  = 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.maiden_name = mgobn('maiden_name')

        self.alive = mgobn('alive')
        self.birthday = mgobn('birthdays')
        self.anniv = mgobn('anniversaries')
        self.home_link = mgobn('home_link')

        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 = mgobn('fullyear')
        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 = date.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 = {}

        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):
        """
         styledtext : assumed a StyledText object to write
        format : = 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 == 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 == 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:
            WarningDialog(
                _("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

# ---------------------------------------------------------------------------------------
#                Adds Birthdays, Anniversaries, and holidays
# ---------------------------------------------------------------------------------------
    def add_day_item(self, text, year, month, day, event):
        """
        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', or 'Holiday'
        """

        # 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']:
            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))
        month_dict[day] = day_list

        # determine which dictionary to add it to???
        if event in ['Birthday', 'Anniversary']:
            self.calendar[month] = month_dict
        else:
            self.holidays[month] = month_dict

# ---------------------------------------------------------------------------------------
#         Retrieves Holidays from the Holiday file, 
#         src/plugins/lib/holidays.xml
# ---------------------------------------------------------------------------------------
    def __get_holidays(self, year):

        # _('translation')
        self.progress.set_pass((_('Calculating Holidays for year %04d') % year), 365)

        """ Get the holidays for the specified country and year """
        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')

                # increment progress bar
                self.progress.step() 

# ---------------------------------------------------------------------------------------
#          Copies all of the calendar files for all calendars
# ---------------------------------------------------------------------------------------
    def copy_calendar_files(self):
        """
        Copies all the necessary stylesheets and images for these calendars
        """
        # Copy the screen stylesheet
        if self.css:
            fname = os.path.join(const.DATA_DIR, self.css)
            self.copy_file(fname, _CALENDARSCREEN, "styles")

        # add horizontal menus if using Web_Basic-Blue or Web_Visually style sheets
        # menus were removed out of them so that the user can choose between horizontal 
        # or vertical menus...
        if self.css in ["Web_Basic-Blue.css", "Web_Visually.css"]:
            fname = os.path.join(const.DATA_DIR, "Web_Navigation-Horizontal.css")
            self.copy_file(fname, "Web_Navigation-Menus.css", "styles")

        # copy print stylesheet
        fname = os.path.join(const.DATA_DIR, "Web_Print-Default.css")
        self.copy_file(fname, _CALENDARPRINT, "styles")

        # set imgs to empty
        imgs = []

        if self.css == "Web_Mainz.css":

            # Mainz stylesheet graphics
            # will only be used if Mainz is slected as the stylesheet
            imgs += ["Web_Mainz_Bkgd.png", "Web_Mainz_Header.png", 
                     "Web_Mainz_Mid.png", "Web_Mainz_MidLight.png"]

        # Copy GRAMPS favicon
        imgs += ['favicon.ico']

        # copy copyright image
        if 0 < self.copy <= len(_CC):
            imgs += ['somerights20.gif']

        for fname in imgs:
            from_path = os.path.join(const.IMAGE_DIR, fname)
            self.copy_file(from_path, fname, "images")

# ---------------------------------------------------------------------------------------
#                     Creates file name passed to it
# ---------------------------------------------------------------------------------------
    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)

        of = codecs.EncodedFile(open(fname, "w"), 'utf-8', self.encoding, 'xmlcharrefreplace')
        return of

# ---------------------------------------------------------------------------------------
#                             Closes all file name passed to it
# ---------------------------------------------------------------------------------------
    def close_file(self, of):
        """ will close whatever filename is passed to it """
        of.close()

# ---------------------------------------------------------------------------------------
#                       Beginning of Calendar Creation
# ---------------------------------------------------------------------------------------
    def write_header(self, nr_up, body_id, title, 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="generator" content="%s %s %s"' % \
                    (const.PROGRAM_NAME, const.VERSION, const.URL_HOMEPAGE)
        _META2 = 'name="author" content="%s"' % self.author

        page, head, body = Html.page(title, self.encoding, xmllang)

        # GRAMPS favicon
        fname1 = os.path.join(subdirs, "images", "favicon.ico")

        # _CALENDARSCREEN stylesheet
        fname2 = os.path.join(subdirs, "styles", _CALENDARSCREEN)

        # create additional meta tags
        meta = Html("meta", attr = _META1) + (
            Html("meta", attr = _META2, indent=False)
            )

        # links for GRAMPS favicon and stylesheets
        links = Html("link", rel='shortcut icon', href=fname1, type = "image/x-icon") + (
            Html("link",rel="stylesheet", href=fname2, type="text/css", media= "screen", indent = False)
            )

        # add horizontal menus if using Web_Basic-Blue or Web_Visually style sheets
        # menus were removed out of them so that the user can choose between horizontal 
        # or vertical menus...
        if self.css in ["Web_Basic-Blue.css", "Web_Visually.css"]:
            fname = os.path.join(subdirs, "styles", "Web_Navigation-Menus.css")
            links.extend(
                Html("link", rel = "stylesheet", href = fname, type = "text/css", media = "screen", indent = False)
                )

        # add printer stylesheet to webcalendar() and one_day() only
        if add_print:
            fname = os.path.join(subdirs, "styles", _CALENDARPRINT)
            links += Html("link",rel="stylesheet", href=fname,type="text/css", media="print", 
                indent = False)

        # add meta tags and links to head section
        head += (meta, links)

        # replace standard body element with custom one
        body.attr = 'id="%s"' % body_id    

        # start header division section
        header = Html("div", id="header") + (

            # page title 
            Html("h1", title, id = "SiteTitle", inline = True)
            )
        body += header

        # Created for ?
        msg = None
        if self.author and self.email:  
            msg = _('Created for <a href="mailto:%(email)s?'
                    'subject=WebCal">%(author)s</a>') % {'email'  : self.email,
                                                         'author' : self.author}
        elif self.author:
            msg = _('Created for %(author)s') % {'author' : self.author}

        if msg is not None:
            header += Html("p", msg, id="CreatorInfo")

        # return to its callers; either webcalendar(), year_glance(), or one_day()
        return page, body

# ---------------------------------------------------------------------------------------
#                        Creates year navigation, if multiyear
# ---------------------------------------------------------------------------------------
    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

        # begin year division and begin unordered list
        with Html("div", id = "subnavigation") as section: 
            unordered = Html("ul")
            section += unordered

            for cal_year in xrange(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
                full_month_name = get_full_month_name(self.today.get_month())

                # Note. We use '/' here because it is a URL, not a OS dependent 
                # pathname.
                url = '/'.join(subdirs + [full_month_name]) + self.ext

                # Figure out if we need <li class="CurrentSection"> or just plain <li>
                cs = str(cal_year) == currentsection and 'class="CurrentSection"' or ''
                unordered += Html("li", attr=cs, inline = True) + (

                    # create hyperlink
                    Html("a", str(cal_year), href = url, title = "Year %04d" % cal_year, inline = True)
                    )

        # return yearnav to its caller
        return section

# ---------------------------------------------------------------------------------------
#                     Creates month navigation for all Calendars
# ---------------------------------------------------------------------------------------
    def month_navigation(self, nr_up, year, currentsection, add_home):
        """
        Will create and display the navigation menu bar

        of = calendar filename being created
        nr_up = number of directories up to reach root directory
        year = year being created
        currentsection = month name being created for proper CSS styling
        use_home = if creating a link to home 
            -- a link to root directory of website
        """

        navs = []

        # An optional link to a home page
        navs.append((self.home_link,  _('html|Home'),  add_home))
        navs.extend((month, month, True) for month in range(1,13))

        # Add a link for year_glance() if requested
        navs.append(('fullyearlinked', _('Year Glance'), self.fullyear))

        # begin month subnavigation
        with Html("div", id = "navigation") as section:

            unordered = Html("ul")
            section += unordered 

            navs = [(u, n) for u, n, c in navs if c]
            for url_fname, nav_text in navs:

                if type(url_fname) == int:
                    url_fname = get_full_month_name(url_fname)

                if type(nav_text) == int:
                    nav_text = get_short_month_name(nav_text)

                # Note. We use '/' here because it is a URL, not a OS dependent pathname
                # need to leave home link alone, so look for it ...
                url = url_fname
                add_subdirs = True
                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>
                cs = url_fname == currentsection and 'class="CurrentSection"' or ''
                unordered += Html("li", attr = cs, inline = True) + (

                    # create hyperlink
                    Html("a", nav_text, href = url, title=url_fname, inline = True)
                    )

        # return monthnav to its caller
        return section

# ---------------------------------------------------------------------------------------
#                            Creates the Calendar Table
# ---------------------------------------------------------------------------------------
    def calendar_build(self, cal, year, month):
        """
        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
        """

        # define names for long and short month names
        full_month_name = get_full_month_name(month)
        abbr_month_name = get_short_month_name(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. GrampsLocale has sunday => 1, monday => 2, etc
        # We slice out the first empty element.
        day_names = GrampsLocale.long_days

        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 %d' % (month_name, year)

        # begin calendar table and table head
        with Html("table", class_ = "calendar", id = month_name) as table:

            thead = Html("thead")
            table += thead 

            trow = Html("tr") + (
                Html("th", th_txt, 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 = 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 
            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 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) + (

                            # adds date for previous and next month days
                            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) 

                            # 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) + self.ext

                                        # create hyperlink to one_day()
                                        fname_date = full_month_name + '/' + fname_date 
                                        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 nyears, date, text, event 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 xrange(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 emptydays in range(7):
                            six_weeks += Html("td", class_ = "emptyDays", inline = True)

        # return calendar table to its callers
        return table

# ---------------------------------------------------------------------------------------
#   Creates the Web Calendars; the primary part of this plugin
# ---------------------------------------------------------------------------------------
    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

        # generate progress pass for "WebCal"
        self.progress.set_pass(_('Formatting months ...'), 12)

        for month in range(1, 13):

            # Name the file, and create it
            cal_fname = get_full_month_name(month)
            of = 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, 'WebCal', 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 = get_full_month_name(month)
            body += self.month_navigation(nr_up, year, currentsection, True)

            # build the calendar
            monthly_calendar = self.calendar_build("wc", year, month)
            body += monthly_calendar

            # create note section for webcalendar()
            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, of)

            # increase progress bar
            self.progress.step()

# ---------------------------------------------------------------------------------------
#                    Creates Year At A Glance Calendar
# ---------------------------------------------------------------------------------------
    def year_glance(self, year):
        """
        This method will create the Full Year At A Glance Page...
        year -- year being created
        """

        nr_up = 1                       # Number of directory levels up to get to root

        # generate progress pass for "Year At A Glance"
        self.progress.set_pass(_('Creating Year At A Glance calendar'), 12)

        # Name the file, and create it
        of = self.create_file('fullyearlinked', str(year))

        # page title
        title = _("%(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, 'fullyearlinked', title, 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 = (_('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 
        body += Html("div", class_ = "content") + (

            # message line
            Html("p", msg, id='description')
            )

        for month in range(1, 13):

            # build the calendar
            monthly_calendar = self.calendar_build("yg", year, month)
            body += monthly_calendar  

            # increase progress bar
            self.progress.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, of)

# ---------------------------------------------------------------------------------------
#               Creates the individual days for year_glance()
# ---------------------------------------------------------------------------------------
    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 = 2                    # 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()

        # Name the file, and crate it (see code in calendar_build)
        od = self.create_file(fname_date, str(year))

        # page title
        title =  _('One Day Within A Year')

        # create page header
        oneday, body = self.write_header(nr_up, 'OneDay', title)

        # 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 = get_full_month_name(month)
        body += self.month_navigation(nr_up, year, currentsection, True)

        # set date display as in user prevferences 
        body += Html("h3", _dd.display(event_date), inline = True)

        # list the events
        ordered = Html("ol")
        body += ordered  
        for nyears, date, text, event 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, od)

######################################################################################
#                  Support Functions for linking to Narratted Web Site
######################################################################################
    def build_url_fname_html(self, fname, subdir=None, prefix=None):
        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 (constfunc.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

# ---------------------------------------------------------------------------------------
#                             Get person's short name
# ---------------------------------------------------------------------------------------
    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)
                name.set_surname(maiden_name)
        else:
            name = Name(primary_name)
        name.set_display_as(self.name_format)
        return _nd.display_name(name)

# ---------------------------------------------------------------------------------------
#        The database slave; Gathers information for calendars
# ---------------------------------------------------------------------------------------
    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()
        self.progress.set_pass(_('Applying Filter...'), db.get_number_of_people())
        people = self.filter.apply(db, people, self.progress)

        self.progress.set_pass(_("Reading database..."), len(people))
        for person in imap(db.get_person_from_handle, people):
            self.progress.step()

            family_list = person.get_family_handle_list()
            birth_ref = person.get_birth_ref()
            birth_date = Date.EMPTY
            if birth_ref:
                birth_event = db.get_event_from_handle(birth_ref.ref)
                birth_date = birth_event.get_date_object()

            # determine birthday information???
            if (self.birthday and birth_date is not Date.EMPTY
                and birth_date.is_valid()):

                year = birth_date.get_year() or this_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 = _get_regular_surname(person.gender, father.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:
                        text = str(Html("a", short_name, 
                                    href = self.build_url_fname_html(person.handle, "ppl", 
                                                                   prefix = self.narweb_prefix)))
                    else:
                        text = short_name
                    self.add_day_item(text, year, month, day, 'Birthday')

            # 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)

                        # 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.EMPTY and event_date.is_valid():
                                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)

                                if self.link_to_narweb:
                                    spouse_name = str(Html("a", spouse_name,
                                                  href = self.build_url_fname_html(spouse_handle, 'ppl', 
                                                  prefix = self.narweb_prefix)))
                                    short_name = str(Html("a", short_name,
                                                      href = self.build_url_fname_html(person.handle, 'ppl', 
                                                       prefix = self.narweb_prefix)))
                                
                                alive1 = probably_alive(person, db, prob_alive_date)
                                alive2 = probably_alive(spouse, db, prob_alive_date)
                                if ((self.alive and alive1 and alive2) or not self.alive):

                                    text = _('%(spouse)s and %(person)s') % {
                                        'spouse' : spouse_name,
                                        'person' : short_name}

                                    self.add_day_item(text, year, month, day, 'Anniversary')

# ---------------------------------------------------------------------------------------
#                                Closes the Calendars; the end
# ---------------------------------------------------------------------------------------
    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") as footer:

            # Display date as user set in preferences
            msg = _('Generated by <a href="http://gramps-project.org">'
                    'Gramps</a> on %(date)s') % {'date' : _dd.display(date.Today())}
            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

# ---------------------------------------------------------------------------------------
#              # Web Page Fortmatter and writer                   
# ---------------------------------------------------------------------------------------
    def XHTMLWriter(self, page, of):
        """
        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
        page.write(lambda line: of.write(line + '\n'))

        # close the file now...
        self.close_file(of)

# ---------------------------------------------------------------------------------------
#             The work horse of this plugin; stages everything
# ---------------------------------------------------------------------------------------
    def write_report(self):
        """
        The short method that runs through each month and creates a page. 
        """

        # Create progress meter bar
        self.progress = ProgressMeter(_("Web Calendar Report"), '')

        # 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 xrange(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)

        # 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)

        # Close the progress meter
        self.progress.close()

# ---------------------------------------------------------------------------------------
#                             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)

    def add_menu_options(self, menu):
        """
        Add options to the menu for the web calendar.
        """
        self.__add_report_options(menu)
        self.__add_content_options(menu)
        self.__add_notes_options(menu)
        self.__add_advanced_options(menu)

    def __add_report_options(self, menu):
        """
        Options on the "Report Options" tab.
        """
        category_name = _("Report Options")

        target = DestinationOption( _("Destination"),
                                    os.path.join(const.USER_HOME, "WEBCAL"))
        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()

        # We must figure out the value of the first option before we can
        # create the EnumeratedListOption
        fmt_list = _nd.get_name_format()
        name_format = EnumeratedListOption(_("Name format"), fmt_list[0][0])
        for num, name, fmt_str, 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)

        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 = EnumeratedListOption(_('StyleSheet'), CSS_FILES[0][1])
        for style in CSS_FILES:
            css.add_item(style[1], style[0])
        css.set_help( _('The Style Sheet to be used for the web page'))
        menu.add_option(category_name, "css", css)

    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 = date.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] != '')):
            countries.insert(0, '')
        count = 0
        for c in countries:
            country.add_item(count, c)
            count += 1
        country.set_help(_("Holidays will be included for the selected "
                            "country"))
        menu.add_option(category_name, "country", country)

        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)

        # Default selection ????
        start_dow = EnumeratedListOption(_("First day of week"), 1)
        for count in range(1, 8):
            start_dow.add_item(count, GrampsLocale.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)

        home_link = StringOption(_('Home link'), '../../NAVWEB/index.html')
        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)

        alive = BooleanOption(_("Include only living people"), True)
        alive.set_help(_("Include only living people in the calendar"))
        menu.add_option(category_name, "alive", alive)

        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)

    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)

        fullyear = BooleanOption(_('Create "Year At A Glance" Calendar'), False)
        fullyear.set_help(_('Whether to create A one-page mini calendar '
                            'with dates highlighted'))
        menu.add_option(category_name, 'fullyear', fullyear)

        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)  

        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)

        self.__prefix = StringOption(_('Link prefix'), "../../NAVWEB/")
        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 = ReportUtils.get_person_filters(person, False)
        self.__filter.set_filters(filter_list)

    def __filter_changed(self):
        """
        Handle filter change. If the filter is not specific to a person,
        disable the person option
        """
        filter_value = self.__filter.get_value()
        if 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?
        """

        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)

# ---------------------------------------------------------------------------------------
#                        Support Functions for this plugin
# ---------------------------------------------------------------------------------------
def _get_regular_surname(sex, name):
    """
    Returns a name string built from the components of the Name instance.
    """

    surname = name.get_surname()
    prefix = name.get_surname_prefix()
    if prefix:
        surname = prefix + " " + surname
    if sex is not Person.FEMALE:
        suffix = name.get_suffix()
        if suffix:
            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]

# define names for full and abbreviated month names in GrampsLocale
full_month_name = _dd.long_months
abbr_month_name = _dd.short_months

def get_full_month_name(month):
    """ returns full or long month name """
    return full_month_name[month]

def get_short_month_name(month):
    """ return short or abbreviated month name """
    return abbr_month_name[month]   

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 in [EventType.MARRIAGE, EventType.MARR_ALT]:
            marriage_event = event

        elif event.type in [EventType.DIVORCE, EventType.ANNULMENT, EventType.DIV_FILING]:
            break

    # return the marriage event or False to it caller
    return marriage_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)

# ---------------------------------------------------------------------------------------
#                   Gets individual events for each day
# ---------------------------------------------------------------------------------------
def get_day_list(event_date, holiday_list, bday_anniv_list):
    """
    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...
    """

    # 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) for t, e, d in bday_anniv_list
                       if d != Date.EMPTY]

    # number of years have to be at least zero
    bday_anniv_list = [(t, e, d) for t, e, d 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 in holiday_list:
        day_list.append((nyears, date, text, event))

    # birthday and anniversary list
    for text, 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)

        # a birthday
        if event == 'Birthday':

            txt_str = (text + ', <em>'
            + (_('%s old') % str(age_str) if nyears else _('birth'))
            + '</em>')

        # an anniversary
        elif event == "Anniversary":

            if nyears == 0:
                txt_str = _('%(couple)s, <em>wedding</em>') % {
                            'couple' : text}
            else: 
                txt_str = (ngettext('%(couple)s, <em>%(years)d'
                                    '</em> year anniversary',
                                    '%(couple)s, <em>%(years)d'
                                    '</em> year anniversary', nyears)
                           % {'couple' : text, 'years'  : nyears})
            txt_str = Html('span', txt_str, class_ = "yearsmarried")

        day_list.append((nyears, date, txt_str, event))

    # sort them based on number of years
    # holidays will always be on top of event list
    day_list.sort()
 
    # return to its caller calendar_build()
    return day_list
