diff --git a/src/plugins/WebCal.py b/src/plugins/WebCal.py index d4f4e420b..0ed83a0ec 100644 --- a/src/plugins/WebCal.py +++ b/src/plugins/WebCal.py @@ -3,6 +3,7 @@ # # Copyright (C) 2007 Thom Sturgill # Copyright (C) 2007-2008 Brian G. Matherly +# Copyright (C) 2008 Rob G. Healey # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Pubilc License as published by @@ -27,6 +28,15 @@ Web Calendar generator. Created 4/22/07 by Thom Sturgill based on Calendar.py (with patches) by Doug Blank with input dialog based on NarrativeWeb.py by Don Allingham. +2008-05-11 Jason Simanek +Improving markup for optimal separation of content and presentation. + +2008-June-22 Rob G. Healey +*** Remove StyleEditor, make it css based as is NarrativeWeb, +move title to first tab, re-word note tabs, complete re-write of +calendar build, added year glance, and blank year, added easter and +dst start/stop from Calendar.py, etc. + Reports/Web Page/Web Calendar """ @@ -38,7 +48,8 @@ Reports/Web Page/Web Calendar import os import time import datetime -#import const +import calendar +import math import codecs import shutil from gettext import gettext as _ @@ -71,18 +82,32 @@ from PluginUtils import PluginManager from ReportBase import (Report, ReportUtils, MenuReportOptions, CATEGORY_WEB, MODE_GUI) from PluginUtils import FilterOption, EnumeratedListOption, PersonOption, \ - BooleanOption, NumberOption, StringOption, DestinationOption, StyleOption + BooleanOption, NumberOption, StringOption, DestinationOption import Utils import GrampsLocale from QuestionDialog import ErrorDialog from Utils import probably_alive +from DateHandler import displayer as _dd +from DateHandler import parser as _dp #------------------------------------------------------------------------ # # constants # #------------------------------------------------------------------------ -_CALENDAR = "calendar.css" +_CALENDARSCREEN = "calendar-screen.css" +_CALENDARPRINT = "calendar-print.css" + +# This information defines the list of styles in the Web calendar +# options dialog as well as the location of the corresponding SCREEN +# stylesheets. +_CSS_FILES = [ + # First is used as default selection. + [_("Evergreen"), 'Web_Evergreen.css'], + [_("Nebraska"), 'Web_Nebraska.css'], + [_("Simply Red"), 'Web_Simply-Red.css'], + [_("No style sheet"), ''], + ] _CHARACTER_SETS = [ [_('Unicode (recommended)'), 'utf-8'], @@ -105,32 +130,29 @@ _CHARACTER_SETS = [ _CC = [ '' 'Creative Commons License - By attribution', + 'Commons License - By attribution" ', '' 'Creative Commons License - By attribution, No derivations', + 'title="Creative Commons License - By attribution, No derivations" ', '' 'Creative Commons License - By attribution, Share-alike', + 'title="Creative Commons License - By attribution, Share-alike" ', '' 'Creative Commons License - By attribution, Non-commercial', + 'title="Creative Commons License - By attribution, Non-commercial" ', '' 'Creative Commons License - By attribution, Non-commercial, No '
     'derivations', + 'Non-commercial, No derivations" ', '' 'Creative Commons License - By attribution, Non-commerical, '
     'Share-alike' + 'Non-commerical, Share-alike" ' ] _COPY_OPTIONS = [ @@ -174,18 +196,16 @@ class WebCalReport(Report): self.ext = menu.get_option_by_name('ext').get_value() self.copy = menu.get_option_by_name('cright').get_value() self.encoding = menu.get_option_by_name('encoding').get_value() + self.css = menu.get_option_by_name('css').get_value() self.Country = menu.get_option_by_name('country').get_value() self.Year = menu.get_option_by_name('year').get_value() + self.fullyear = menu.get_option_by_name('fullyear').get_value() + self.blankyear = menu.get_option_by_name('blankyear').get_value() self.Surname = menu.get_option_by_name('surname').get_value() self.Alive = menu.get_option_by_name('alive').get_value() self.Birthday = menu.get_option_by_name('birthdays').get_value() self.Anniv = menu.get_option_by_name('anniversaries').get_value() self.Title_text = menu.get_option_by_name('title').get_value() - self.Month_image = menu.get_option_by_name('background').get_value() - self.Month_repeat = menu.get_option_by_name('repeat').get_value() - self.Serif_fonts = menu.get_option_by_name('serif_fonts').get_value() - self.SanSerif_fonts = \ - menu.get_option_by_name('sanserif_fonts').get_value() self.Home_link = menu.get_option_by_name('home_link').get_value() self.Note = [ menu.get_option_by_name('note_jan').get_value(), @@ -201,7 +221,69 @@ class WebCalReport(Report): menu.get_option_by_name('note_nov').get_value(), menu.get_option_by_name('note_dec').get_value()] - self.__style = menu.get_option_by_name("style").get_style() + def store_file(self, from_path, to_path): + """ + Store the file in the destination. + """ + dest = os.path.join(self.html_dir, to_path) + dirname = os.path.dirname(dest) + if not os.path.isdir(dirname): + os.makedirs(dirname) + if from_path != dest: + shutil.copyfile(from_path, 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 + + # code snippets for east and Daylight saving start/ stop + # are borrowed from Calendar.py + + def easter(self): + """ + Computes the year/month/day of easter. Based on work by + J.-M. Oudin (1940) and is reprinted in the "Explanatory Supplement + to the Astronomical Almanac", ed. P. K. Seidelmann (1992). Note: + Ash Wednesday is 46 days before Easter Sunday. + """ + year = self.Year + c = year / 100 + n = year - 19 * (year / 19) + k = (c - 17) / 25 + i = c - c / 4 - (c - k) / 3 + 19 * n + 15 + i = i - 30 * (i / 30) + i = i - (i / 28) * (1 - (i / 28) * (29 / (i + 1)) + * ((21 - n) / 11)) + j = year + year / 4 + i + 2 - c + c / 4 + j = j - 7 * (j / 7) + l = i - j + month = 3 + (l + 40) / 44 + day = l + 28 - 31 * (month / 4) + return (year, month, day) + + def dst(self, area="us"): + """ + Return Daylight Saving Time start/stop in a given area ("us", "eu"). + US calculation valid 1976-2099; EU 1996-2099 + """ + year = self.Year + if area == "us": + if year > 2006: + start = (year, 3, 14 - (math.floor(1 + year * 5 / 4) % 7)) # March + stop = (year, 11, 7 - (math.floor(1 + year * 5 / 4) % 7)) # November + else: + start = (year, 4, (2 + 6 * year - math.floor(year / 4)) % 7 + 1) # April + stop = (year, 10, (31 - (math.floor(year * 5 / 4) + 1) % 7)) # October + elif area == "eu": + start = (year, 3, (31 - (math.floor(year * 5 / 4) + 4) % 7)) # March + stop = (year, 10, (31 - (math.floor(year * 5 / 4) + 1) % 7)) # Oct + return (start, stop) def get_short_name(self, person, maiden_name = None): """ Return person's name, unless maiden_name given, unless married_name listed. """ @@ -239,17 +321,30 @@ class WebCalReport(Report): month_dict[day] = day_list self.calendar[month] = month_dict - def get_holidays(self, year, country = "United States"): - """ Looks in multiple places for holidays.xml files """ - locations = [const.PLUGINS_DIR, const.USER_PLUGINS] - holiday_file = 'holidays.xml' - for dir in locations: - holiday_full_path = os.path.join(dir, holiday_file) - if os.path.exists(holiday_full_path): - self.process_holiday_file(holiday_full_path, year, country) + def get_holidays(self, country = "United States"): + """ Looks in multiple places for holidays.xml files + the holidays file will be used first if it exists in user's plugins, else + the GRAMPS plugins will be checked. No more of having duel holidays files being read. - def process_holiday_file(self, filename, year, country): + User directory is first choice if it exists, and does not use both holiday files any longer + """ + + year = self.Year + holiday_file = 'holidays.xml' + holiday_full_path = "" + fname1 = os.path.join(const.USER_PLUGINS, holiday_file) + fname2 = os.path.join(const.PLUGINS_DIR, holiday_file) + if os.path.exists(fname1): + holiday_full_path = fname1 + elif os.path.exists(fname2): + holiday_full_path = fname2 + if holiday_full_path != "": + self.process_holiday_file(holiday_full_path, country) + + def process_holiday_file(self, filename, country): """ This will process a holiday file """ + + year = self.Year parser = Xml2Obj() element = parser.Parse(filename) calendar = Holidays(element, country) @@ -257,186 +352,322 @@ class WebCalReport(Report): while date.year == year: holidays = calendar.check_date( date ) for text in holidays: + if text == "Easter": + date1 = self.easter() + self.add_day_item(text, date1[0], date1[1], date1[2]) + elif text == "Daylight Saving begins": + if Utils.xml_lang() == "en-US": + date2 = self.dst("us") + else: + date2 = self.dst("eu") + dst_start = date2[0] + dst_stop = date2[1] + self.add_day_item(text, dst_start[0], dst_start[1], dst_start[2]) + self.add_day_item("Daylight Saving ends", dst_stop[0], dst_stop[1], dst_stop[2]) + elif text == "Daylight Saving ends": + pass self.add_day_item(text, date.year, date.month, date.day) date = date.fromordinal( date.toordinal() + 1) - def write_css(self): + def copy_css(self): """ - Create the CSS file. + Copies all the necessary files... """ - # simplify the style and weight printing - font_style = ['normal','italic'] - font_weight = ['normal','bold'] - - # use user defined font families - font_family = [self.SanSerif_fonts,self.Serif_fonts] + # Copy the _CALENDARSCREEN css + if self.css != "": + from_file = os.path.join(const.DATA_DIR, self.css) + to_file = os.path.join("styles/", _CALENDARSCREEN) + self.store_file(from_file, to_file) - # - # NAVIGATION BLOCK - # - of = self.create_file(_CALENDAR) - of.write('ul#navlist { padding: 0;\n\tmargin: 0;\n\tlist-style-type: none;') - of.write('\n\tfloat: left;\n\twidth: 100%;') - of.write('\n\tcolor: #FFFFFF;\n\tbackground-color: #003366;\n\t}\n') - of.write('ul#navlist li { display: inline; }\n') - of.write('ul#navlist li a { float: left;\n\twidth: 2.8em;') - of.write('\n\tcolor: #FFFFFF;\n\tbackground-color: #003366;') - of.write('\n\tpadding: 0.2em 1em;\n\ttext-decoration: none;') - of.write('\n\tborder-right: 1px solid #FFFFFF;\n\t}\n') - of.write('ul#navlist li a:hover { background-color: #336699;') - of.write('\n\tcolor: #FFFFFF;\n\t}\n') - # - # HEADER / BODY BACKGROUND - # - of.write('h1 {') - style = self.__style.get_paragraph_style("WC-Title") - font = style.get_font() - italic = font_style[font.get_italic()] - bold = font_weight[font.get_bold()] - family = font_family[font.get_type_face()] - color = "#%02X%02X%02X" % font.get_color() - of.write('\tfont-family: %s;\n\tfont-size: %dpt;\n' - '\tfont-style: %s;\n\tfont-weight: %s;\n' - '\tcolor: %s;\n\ttext-align: %s;\n\t}\n' - % (family, font.get_size(), italic, bold, - color, style.get_alignment_text())) - of.write('body { background-color: #%02X%02X%02X;\n}\n' % style.get_background_color() ) - # - # CALENDAR TABLE - # - of.write('.calendar { ') - style = self.__style.get_paragraph_style("WC-Table") - font = style.get_font() - italic = font_style[font.get_italic()] - bold = font_weight[font.get_bold()] - family = font_family[font.get_type_face()] - color = "#%02X%02X%02X" % font.get_color() - of.write('font-family: %s;\n\tfont-size: %dpt;\n' - '\tfont-style: %s;\n\tfont-weight: %s;\n' - '\tcolor: %s;\n\ttext-align: %s;\n' - % (family, font.get_size(), italic, bold, - color, style.get_alignment_text())) - of.write('\tbackground-color: #%02X%02X%02X;\n}\n' % style.get_background_color() ) - # - # MONTH NAME - # - style = self.__style.get_paragraph_style("WC-Month") - of.write('.cal_month { border-bottom-width: 0;\n') - font = style.get_font() - italic = font_style[font.get_italic()] - bold = font_weight[font.get_bold()] - family = font_family[font.get_type_face()] - color = "#%02X%02X%02X" % font.get_color() - mon_backcolor = "#%02X%02X%02X" % style.get_background_color() - of.write('\tfont-family:%s;\n\tfont-size: %dpt;\n' - '\tfont-style: %s;\n\tfont-weight: %s;\n' - '\tcolor: %s;\n\ttext-align: %s;\n' - % (family, font.get_size(), italic, bold, color, style.get_alignment_text())) - if self.Month_image.strip() != "": - of.write('\tbackground-image: URL( %s );\n' % self.Month_image) - of.write('\tbackground-repeat: %s;\n' % self.options.repeat_options[self.Month_repeat] ) - of.write('\tbackground-color: %s;\n}\n' % mon_backcolor ) - # - # WEEKDAY NAMES - # - of.write('.cal_sun { border-top-width: 0;\n\tborder-right-width: 0;') - of.write('\n\tborder-style: solid; ') - of.write('background-color: %s }\n' % mon_backcolor ) - of.write('.cal_weekday { border-top-width: 0;\n\tborder-left-width: 0;\n\tborder-right-width: 0; ') - of.write('\n\tborder-style: solid;\n\tbackground-color: %s }\n' % mon_backcolor ) - of.write('.cal_sat { border-top-width: 0;\n\tborder-left-width: 0;\n') - of.write('\tborder-right-width: 0;\n\tborder-style: solid;') - of.write('\n\tbackground-color: %s }\n' % mon_backcolor ) - #of.write('.cal_day_num { text-align: right;\n\tfont-size: x-large;\n\tfont-weight: bold;}\n') - # - # CALENDAR ENTRY TEXT - # - style = self.__style.get_paragraph_style("WC-Text") - of.write('.cal_text { vertical-align:bottom;\n') - font = style.get_font() - italic = font_style[font.get_italic()] - bold = font_weight[font.get_bold()] - family = font_family[font.get_type_face()] - color = "#%02X%02X%02X" % font.get_color() - msg_backcolor = "#%02X%02X%02X" % style.get_background_color() - of.write('\tfont-family:%s;\n\tfont-size: %dpt;\n' - '\tfont-style: %s;\n\tfont-weight: %s;\n' - '\tcolor: %s;\n\ttext-align: %s;\n\t}\n' - % (family, font.get_size(), italic, bold, color, style.get_alignment_text())) - of.write('.cal_row { height: 70px;\n\tborder-style: solid;\n\t}\n') - of.write('.cal_cell_hilite {background-color: %s;}\n' % msg_backcolor ) - # - # CALENDAR NOTE TEXT - # - style = self.__style.get_paragraph_style("WC-Note") - font = style.get_font() - italic = font_style[font.get_italic()] - bold = font_weight[font.get_bold()] - family = font_family[font.get_type_face()] - color = "#%02X%02X%02X" % font.get_color() - backcolor = "#%02X%02X%02X" % style.get_background_color() - of.write('.cal_cell { background-color: %s;}\n' % backcolor ) - of.write('.cal_note {\n') - of.write('\tfont-family:%s;\n\tfont-size: %dpt;\n' - '\tfont-style: %s;\n\tfont-weight: %s;\n' - '\tcolor: %s;\n\ttext-align: %s;\n' - '\tbackground-color: %s;\n\t}\n' - % (family, font.get_size(), italic, bold, color, style.get_alignment_text(), backcolor)) - # - # FOOTER AND DONE - # - of.write('.footer { text-align: center;\n\tfont-size:small; }\n') - of.write('img { border: 0; }\n') - of.close() + # copy calendar-print stylesheet + from_file = os.path.join(const.DATA_DIR, "Web_Print-Default.css") + to_file = os.path.join("styles/", _CALENDARPRINT) + self.store_file(from_file, to_file) + # Copy GRAMPS favicon to target + from_file = os.path.join(const.IMAGE_DIR, "favicon.ico") + to_file = os.path.join("images/", "favicon.ico") + self.store_file(from_file, to_file) - def write_footer(self, of): + # Copy arrow image if "Year At A Glance" is requested, + # and if the file exists + if self.fullyear: + from_file = os.path.join(const.IMAGE_DIR, "arrow102.gif") + if os.path.exists(from_file): + to_file = os.path.join("images/", "arrow102.gif") + self.store_file(from_file, to_file) + + def display_nav_links(self, of, currentsection, cal): + + # Check to see if home_link will be used??? + navs = [ + ('January', _('Jan'), True), + ('February', _('Feb'), True), + ('March', _('Mar'), True), + ('April', _('Apr'), True), + ('May', _('May'), True), + ('June', _('Jun'), True), + ('July', _('Jul'), True), + ('August', _('Aug'), True), + ('September', _('Sep'), True), + ('October', _('Oct'), True), + ('November', _('Nov'), True), + ('December', _('Dec'), True), + ('fullyear', _('Year Glance'), self.fullyear), + ('blankyear', _('Blank Calendar'), self.blankyear) + ] + for url_fname, nav_text, cond in navs: + if cond: + new_dir = str(self.Year) + '/' + if ((cal == "yg") or (cal == "by")): + url = self.subdirs("yg", new_dir, url_fname) + elif cal == "ip": + url = self.subdirs("ip", new_dir, url_fname) + else: + url = self.subdirs("wc", new_dir, url_fname) + url += self.ext + self.display_nav_link(of, url, nav_text, currentsection) + + def display_nav_link(self, of, url, title, currentsection): + # Figure out if we need
  • of just plain
  • + cs = False + if title == currentsection: + cs = True + + cs = cs and ' id="CurrentSection"' or '' + of.write(' %s
  • \n' + % (cs, url, title)) + + def calendar_build(self, of, cal, month): + """ + This does the work of building the calendar + """ + + year = self.Year + + # Begin calendar head + title = GrampsLocale.long_months[month] + of.write('\n' % title) + of.write(' \n' % title) + of.write(' \n') + of.write(' \n') + of.write(' \n' % title) + of.write(' \n') + + # Calendar weekday names header + of.write(' \n') + of.write(' \n') + for day_col in range(5): + of.write(' \n') + of.write(' \n') + of.write(' \n') + of.write(' \n') + + of.write(' \n') + monthinfo = calendar.monthcalendar(year, month) + nweeks = len(monthinfo) + current_date = datetime.date(year, month, 1) # first day of the month + if current_date.isoweekday() != 7: # start dow here is 7, sunday + current_ord = current_date.toordinal() - current_date.isoweekday() + else: + current_ord = current_date.toordinal() + + # get last month's last week for previous days in the month + if month == 1: + prevmonth = calendar.monthcalendar(year-1, 12) + else: + prevmonth = calendar.monthcalendar(year, month-1) + num_weeks = len(prevmonth) + lastweek = prevmonth[num_weeks - 1] + + # get next month's first week for next days in the month + if month == 12: + nextmonth = calendar.monthcalendar(year+1, 1) + else: + nextmonth = calendar.monthcalendar(year, month + 1) + nextweek = nextmonth[0] + + # Begin calendar + for week_row in range(0, nweeks): + week = monthinfo[week_row] + of.write(' \n' % week_row) + for days_row in range(0, 7): + if days_row == 0: + dayclass = "weekend sunday" + elif days_row == 6: + dayclass = "weekend saturday" + else: + dayclass = "weekday" + day = week[days_row] + if day == 0: # previous or next month day + if week_row == 0: # previous day + specday = lastweek[days_row] + specclass = "previous " + dayclass + elif week_row == nweeks-1: + specday = nextweek[days_row] + specclass = "next " + dayclass + of.write(' \n') + else: # day number + if cal == "by": + of.write(' \n') + else: + of.write(' \n') + current_ord += 1 + of.write(' \n') + + # Complete six weeks for proper styling + of = self.six_weeks(of, nweeks) + + return of + + def write_header(self, of, title, cal, mystyle): + """ + This creates the header for the Calendars iincluding style embedded for special purpose + """ + + of.write('\n ') + of.write('\n' % (xmllang, xmllang)) + of.write('\n') + of.write(' %s\n' % title) + of.write(' \n' + % self.encoding) + of.write(' \n') + of.write(' \n') author = get_researcher().get_name() - value = unicode(time.strftime('%x',time.localtime(time.time())), - GrampsLocale.codeset) - msg = _('Generated by ' - 'GRAMPS on %(date)s') % { 'date' : value } - of.write('
    ') + of.write('%s
    ') + if cal == "yg": + of.write(GrampsLocale.short_days[1]) + else: + of.write(GrampsLocale.long_days[1]) + of.write('') + if cal == "yg": + of.write(GrampsLocale.short_days[day_col+2]) + else: + of.write(GrampsLocale.long_days[day_col+2]) + of.write('') + if cal == "yg": + of.write(GrampsLocale.short_days[7]) + else: + of.write(GrampsLocale.long_days[7]) + of.write('
    \n' + % (specday, specclass)) + of.write('
    %d
    \n' + % specday) + of.write('
    \n' + % (day, dayclass)) + of.write('
    %d
    \n' % day) + of.write('
    [])): + specclass = "highlight " + dayclass + of.write('class="%s">\n' % specclass) + if cal == "yg": # Year at a Glance + lng_month = GrampsLocale.long_months[month] + shrt_month = GrampsLocale.short_months[month] + of.write(' \n' + % (lng_month, shrt_month, day, self.ext)) + of.write('
    %d' + '
    \n' % day) + self.indiv_date(month, day, list) + else: # WebCal + of.write('
    %d' + '
    \n' % day) + of.write('
      \n') + for p in list: + lines = p.count("\n") + 1 # lines in the text + for line in p.split("\n"): + of.write('
    • ') + of.write(line) + of.write('
    • \n') + of.write('
    \n') + else: + of.write('class="%s">\n' % dayclass) + of.write('
    %d
    \n' + % day) + else: + of.write('class="%s">\n' % dayclass) + of.write('
    %d
    \n' % day) + of.write('
    \n') - of.write(' \n') + of.write('\n') of.write('\n') - def write_header(self, of): - author = get_researcher().get_name() - of.write('\n') - #xmllang = Utils.xml_lang() - of.write('\n') - of.write('\n %s\n' % self.Title_text) - of.write(' \n' % self.encoding) - of.write(' \n') - of.write(' \n') - of.write(' \n' % author) - of.write(' \n') - of.write(' \n') - #of.write('\n' % ('$','$')) - of.write('\n') - of.write('\n') - of.write(' \n') - def create_file(self, name): page_name = os.path.join(self.html_dir, name) of = codecs.EncodedFile(open(page_name, "w"),'utf-8',self.encoding,'xmlcharrefreplace') @@ -445,6 +676,304 @@ class WebCalReport(Report): def close_file(self, of): of.close() + def indiv_date(self, month, day_num, list): + """ + This method creates the indiv pages for "Year At A Glance" + """ + + year = self.Year + dest_dir = self.html_dir + "/images" + arrow = os.path.join(dest_dir, "arrow102.gif") + + # Create names for long and short month names + lng_month = GrampsLocale.long_months[month] + shrt_month = GrampsLocale.short_months[month] + + new_dir = self.html_dir + "/%d/%s" % (year, lng_month) + if not os.path.isdir(new_dir): + os.mkdir(new_dir) + + # Name the file, and create it + cal_file = '%d/%s/%s%d%s' % (year, lng_month, shrt_month, day_num, self.ext) + ip = self.create_file(cal_file) + + mystyle = """ + + """ + + # Add Header to calendar + title = "%d %s %d" % (day_num, lng_month, year) + (ip, author) = self.write_header(ip, title, "ip", mystyle) + + ip.write('\n' % (shrt_month, day_num)) + ip.write(' \n') + + # Create navigation menu + ip.write(' \n') + + ip.write('

    %s %d, %d

    \n' + % (lng_month, day_num, year)) + + # if arrow file exists in IMAGE_DIR, use it + arrow = os.path.join(const.IMAGE_DIR, "arrow102.gif") + if os.path.isfile(arrow): + ip.write('