UserPreferences

GrampsCalendarReport


1. GRAMPS Calendar Report

The calendar report is now a standard part of GRAMPS. You can find out more about gramps at http://gramps-project.org/

Further discussions on this report can be found at http://developers.gramps-project.org/tiki-index.php?page=CalendarEnhancements


Here is a screen image of the options, first page:

http://bubo.brynmawr.edu/~dblank/images/calendar-option1.png

Here is a screen image of the options, second page:

http://bubo.brynmawr.edu/~dblank/images/calendar-option2.png

Here is a screen image of the generated PDF of a month:

http://bubo.brynmawr.edu/~dblank/images/calendar2-month.png

Here is a screen image of the generated PDF, close up:

http://bubo.brynmawr.edu/~dblank/images/calendar2-day.png

Here are the files you'll need:

http://bubo.brynmawr.edu/~dblank/python/Calendar.py

and the holiday file:

http://bubo.brynmawr.edu/~dblank/python/holidays.xml

You can get both together as a ZIP file [WWW]here.

You can put these both in your ~/.gramps/plugins/ directory or your main gramps/plugins/ directory.

You'll might want to use landscape paper mode. I select a relative a couple of generations back and then select the filter "Descendent Families of ...".

See the file for more details, TODO, and other IDEAS

Caveats

This is my first report, and I also undertook a bit of rewriting of the report options interface. It should be completely compatible with the old version as it only builds some infrastructure on top.

Comments welcome.

2. Documentation

This documentation describes a new set of report abstractions and the use of them. This is only related to the calendar program because it uses them. Also, following that describes the Calendar holidays.xml file format so that you can add your own elements to the calendar.

2.1. Report Abstractions

Here is a very simple report example that doesn't even refer to the database:

# ------------------ Version 1
import Report
import ReportOptions
from Widget import StyleWidget
class MyOptions(ReportOptions):
    def enable_options(self):
        self.enable_dict = {}
        self.widgets = [
            StyleWidget(self,
                        label = _('The style of the text.'),
                        bold = 1,
                        name = "textstyle",
                        ),
            ]
class MyReport(Report.Report):
    def write_report(self):
        self.doc.start_page()
        self.doc.write_at("textstyle",
                          _("Hello World!"),
                          1,
                          1)
        self.doc.end_page()
# -------------- end version 1

That's it. As you can see, it is fairly self-explanatory. There is one widget, a StyleWidget, that defines a bold style, taking the other style defaults. The write_at method refers to the name of the style. All of the details, including the GUI and handling of values, is taken care of by the system (just slightly altered standard methods).

Ok, but what about getting a value from the user, and going through the database? Here is a slightly more powerful example:

# ------------------ Version 2
class MyOptions(ReportOptions):
    def enable_options(self):
        self.enable_dict = {}
        self.widgets = [
            FilterWidget(self,
                         label = _("Filter"),
                         name = "filter",
                         filters = ["descendants", "ancestors"],
            CheckWidget(self,
                        label = _("Only include living people"),
                        name  = "alive",
                        value = 1,
                        ),
            StyleWidget(self,
                        label = _('The style of the text.'),
                        name = "textstyle",
                        ),
            ]
class MyReport(Report.Report):
    def write_report(self):
        self.doc.start_page()
        filter_num = self.options_class.get_filter_number()
        filters = self.options_class.get_report_filters(self.start_person)
        filters.extend(GenericFilter.CustomFilters.get_filters())
        self.filter = filters[filter_num]
        people = self.filter.apply(self.database, \
          self.database.get_person_handles(sort_handles=False))
        count = 0
        for person_handle in people:
            person = self.database.get_person_from_handle(person_handle)
            death_handle = person.get_death_handle()
            if not self["alive"] or death_handle != None:
                self.doc.write_at("textstyle",
                                  person.get_primary_name().get_name(),
                                  1,
                                  count)
                if count % 10 == 0:
                    self.doc.end_page()
                    self.doc.start_page()
                    count = 0
                else:
                    count += 1
        self.doc.end_page()
# -------------- end version 2

The FilterWidget takes a list of special names of filters, the common ones used in all existing reports. This example allows the selection from two filters.

2.2. Widgets

2.2.1. Widget

This is the base virtual class.

Keywords and Defaults:

        "wtype"      : None,
        "name"       : None,
        "label"      : None,
        "help"       : None,
        "wtype"      : None,
        "valid_text" : None,
        "frame"      : None,
        "value"      : None,

If "frame" is anything other than None, then the widget will appear on a new frame of that name.

"wtype" are the codes like "=0/1", "=str", etc. in GRAMPS.

2.2.2. StyleWidget

Keywords and Defaults:

        "size"      : 8,
        "bold"      : 0,
        "italics"   : 0,
        "type_face" : BaseDoc.FONT_SERIF,
        "fill_color": None,

2.2.3. EntryWidget

Keywords and Defaults:

        "wtype"     : "=str",
        "help"      : _("Enter text in the area"),
        "valid_text": _("Enter any text data"),

2.2.4. NumberWidget

Keywords and Defaults:

        "wtype"     : "=num",
        "help"      : _("Enter a number in the area"),
        "valid_text": _("Enter any numeric data"),

2.2.5. SpinWidget

Keywords and Defaults:

        "wtype"     : "=num",
        "help"      : _("Type a number or click the spinner"),
        "valid_text": _("Any number"),

2.2.6. CheckWidget

Keywords and Defaults:

        "wtype"     : "=0/1",
        "help"      : _("Click the checkbox"),
        "valid_text": _("Check means this option is active"),

2.2.7. FilterWidget

Keywords and Defaults:

        "filters"     : [],

"filters" is a list of zero or more of the following keywords:

    "everyone" - all people in table
    "descendents" - direct descendents
    "descendent familes" - direct descendents and their familes
    "ancestors" - all ancestors of person
    "common ancestors" - all common ancestors
    "calendar attribute" - experimental filter for tagging people
    or
    "all filters" - all of them

2.3. Examples

            FilterWidget(self, label = _("Filter"),
                         name = "filter",
                         filters = ["all filters"]),
            EntryWidget(self, label = _("Text 1"),
                        name  = "text1",
                        value = "My Calendar",
                        help  = _("Large text area"),
                        valid_text = _("Any text"),
                        frame = _("Text Options")
                        ),
            SpinWidget(self, label = _("Year of calendar"),
                       name  = "year",
                       value = 2005,
                       help  = _("Year of calendar"),
                       valid_text = _("Any year"),
                       ),
            CheckWidget(self, label = _("Use maiden names"),
                        name = "maiden_name",
                        value = 1,
                        help = _("Use married women's maiden name."),
                        valid_text = _("Select to use married women's maiden name."),
                        ),
            StyleWidget(self, label = _('Title text and background color.'),
                        name = "title",
                        size = 20,
                        italics = 1,
                        bold = 1,
                        fill_color = (0xEA,0xEA,0xEA),
                        type_face = BaseDoc.FONT_SERIF,
                        ),

The first argumnmet is the Report instance. The rest are the keywords and values from above.

2.4. XML Holidays File Format

The file holidays.xml is a method for users to put items on the calendar, such as holidays, major events, or family-specific items.

The format is generally:

<?xml version="1.0" encoding="iso-8859-1"?>
<calendar>
  <country name=COUNTRYCODE>
    <date name=NAME value=VALUE type=TYPE offset=OFFSET if=EXPRESSION />
    ...
  </country>
  <country name=COUNTRYCODE>
    <date name=NAME value=VALUE type=TYPE offset=OFFSET if=EXPRESSION />
    ...
  </country>
  ...
</calendar>

where each of the capitalized words would be filled in with appropriate values, as follows. All but NAME and VALUE are optional.

COUNTRYCODE => "US", "CN", etc.

NAME => the text to appear on the calendar

TYPE => "secular", "religious", "personal", "informatinal" (This isn't currently used)

EXPRESSION => a python expression that must be true for this to appear on the calendar

VALUE => "YEAR/MONTH/DAY" or "YEAR/NUMBER/DAYCODE/MONCODE"

YEAR => "*" or an actual year. "*" means any year.

MONTH => "*" or actual month number. "*" means any month.

DAY => "*" or actual day number in month. "*" means any day.

NUMBER => number of weekday in month. For example, "every 3rd Tuesday in April" would be "*/3/tue/apr". NUMBER can also be negative, which means it is counted from the end of the month. "last Tuesday in April 2007" could be "2007/-1/tue/4"

DAYCODE => "mon", "tue", "wed", "thu", "fri", "sat", or "sun". Not internationalized, all lowercase.

MONCODE => MONTH, or 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', or 'dec'. String version is not internationalized, all lowercase.

OFFSET => DAYCODE or a number (either may be negative) that offsets the day determined above. If it is a DAYCODE, then it will find the next DAYCODE that is either the same as this day, or the next(+)/prev(-) one.

2.5. Examples

<?xml version="1.0" encoding="iso-8859-1"?>
<calendar>
  <country name="US">
    <date name="New Year's Day" value="*/1/1" type="national" />
    <date name="Labor Day" value="*/1/mon/sep" type="national" />
    <date name="Thanksgiving" value="*/4/thu/nov" type="national" />
    <date name="Inauguration Day" value="*/1/20" if="(y - 1980) % 4 == 0" type="national" />
    <date name="Washington's Birthday" value="*/3/mon/feb" type="secular" />
    <date name="St. Patrick's Day" value="*/3/17" type="secular" />
    <date name="Assistants' Day" value="*/-1/sat/apr" offset="-3" type="secular" />
    <date name="Arbor Day" value="*/-1/fri/apr" type="secular" />
    <date name="Mothers' Day" value="*/2/sun/may" type="secular" />
    <date name="Fathers' Day" value="*/3/sun/jun" type="secular" />
    <date name="ML Kings's Birthday" value="*/3/mon/jan" type="secular" />
    <date name="Memorial Day" value="*/-1/mon/may" type="secular" />
    <date name="Easter" value="2006/4/16" type="religious" />
  </country>
  <country name="CN">
    <date name="Chinese New Year" value="2006/1/29" type="national" />
  </country>
  <country name="FI">
    <date name="Card Night" value="*/-1/thu/*" type="personal" />
  </country>
</calendar>

The last set will only load if your country is Finland. It says that Card Night is the last Thursday of every month, every year. Let's play!