# HG changeset patch # User Paul Boddie # Date 1328401785 -3600 # Node ID a2297e8dd36cd4f5035fc27f0178b0d0e462effb # Parent 421d98ae50d203c22625621ce78c40fb72d5ac0a Moved the user interface construction code into the common library so that it can be used by actions. diff -r 421d98ae50d2 -r a2297e8dd36c EventAggregatorSupport.py --- a/EventAggregatorSupport.py Sun Jan 22 00:45:28 2012 +0100 +++ b/EventAggregatorSupport.py Sun Feb 05 01:29:45 2012 +0100 @@ -13,7 +13,7 @@ from MoinSupport import * from MoinMoin.Page import Page -from MoinMoin.action import cache +from MoinMoin.action import AttachFile, cache from MoinMoin import caching from MoinMoin import search from MoinMoin import wikiutil @@ -1362,4 +1362,1851 @@ return new_event_page.getBody() +# Colour-related functions. + +def getColour(s): + colour = [0, 0, 0] + digit = 0 + for c in s: + colour[digit] += ord(c) + colour[digit] = colour[digit] % 256 + digit += 1 + digit = digit % 3 + return tuple(colour) + +def getBlackOrWhite(colour): + if sum(colour) / 3.0 > 127: + return (0, 0, 0) + else: + return (255, 255, 255) + +# User interface abstractions. + +class View: + + "A view of the event calendar." + + def __init__(self, page, calendar_name, raw_calendar_start, raw_calendar_end, + original_calendar_start, original_calendar_end, calendar_start, calendar_end, + first, last, category_names, remote_sources, template_name, parent_name, mode, + resolution, name_usage, map_name): + + """ + Initialise the view with the current 'page', a 'calendar_name' (which + may be None), the 'raw_calendar_start' and 'raw_calendar_end' (which + are the actual start and end values provided by the request), the + calculated 'original_calendar_start' and 'original_calendar_end' (which + are the result of calculating the calendar's limits from the raw start + and end values), and the requested, calculated 'calendar_start' and + 'calendar_end' (which may involve different start and end values due to + navigation in the user interface), along with the 'first' and 'last' + months of event coverage. + + The additional 'category_names', 'remote_sources', 'template_name', + 'parent_name' and 'mode' parameters are used to configure the links + employed by the view. + + The 'resolution' affects the view for certain modes and is also used to + parameterise links. + + The 'name_usage' parameter controls how names are shown on calendar mode + events, such as how often labels are repeated. + + The 'map_name' parameter provides the name of a map to be used in the + map mode. + """ + + self.page = page + self.calendar_name = calendar_name + self.raw_calendar_start = raw_calendar_start + self.raw_calendar_end = raw_calendar_end + self.original_calendar_start = original_calendar_start + self.original_calendar_end = original_calendar_end + self.calendar_start = calendar_start + self.calendar_end = calendar_end + self.template_name = template_name + self.parent_name = parent_name + self.mode = mode + self.resolution = resolution + self.name_usage = name_usage + self.map_name = map_name + + self.category_name_parameters = "&".join([("category=%s" % name) for name in category_names]) + self.remote_source_parameters = "&".join([("source=%s" % source) for source in remote_sources]) + + # Calculate the duration in terms of the highest common unit of time. + + self.first = first + self.last = last + self.duration = last - first + + if self.calendar_name is not None: + + # Store the view parameters. + + self.previous_start = first.previous() + self.next_start = first.next() + self.previous_end = last.previous() + self.next_end = last.next() + + self.previous_set_start = first.update(-self.duration) + self.next_set_start = first.update(self.duration) + self.previous_set_end = last.update(-self.duration) + self.next_set_end = last.update(self.duration) + + def getIdentifier(self): + + "Return a unique identifier to be used to refer to this view." + + # NOTE: Nasty hack to get a unique identifier if no name is given. + + return self.calendar_name or str(id(self)) + + def getQualifiedParameterName(self, argname): + + "Return the 'argname' qualified using the calendar name." + + return getQualifiedParameterName(self.calendar_name, argname) + + def getDateQueryString(self, argname, date, prefix=1): + + """ + Return a query string fragment for the given 'argname', referring to the + month given by the specified 'year_month' object, appropriate for this + calendar. + + If 'prefix' is specified and set to a false value, the parameters in the + query string will not be calendar-specific, but could be used with the + summary action. + """ + + suffixes = ["year", "month", "day"] + + if date is not None: + args = [] + for suffix, value in zip(suffixes, date.as_tuple()): + suffixed_argname = "%s-%s" % (argname, suffix) + if prefix: + suffixed_argname = self.getQualifiedParameterName(suffixed_argname) + args.append("%s=%s" % (suffixed_argname, value)) + return "&".join(args) + else: + return "" + + def getRawDateQueryString(self, argname, date, prefix=1): + + """ + Return a query string fragment for the given 'argname', referring to the + date given by the specified 'date' value, appropriate for this + calendar. + + If 'prefix' is specified and set to a false value, the parameters in the + query string will not be calendar-specific, but could be used with the + summary action. + """ + + if date is not None: + if prefix: + argname = self.getQualifiedParameterName(argname) + return "%s=%s" % (argname, wikiutil.url_quote_plus(date)) + else: + return "" + + def getNavigationLink(self, start, end, mode=None, resolution=None): + + """ + Return a query string fragment for navigation to a view showing months + from 'start' to 'end' inclusive, with the optional 'mode' indicating the + view style and the optional 'resolution' indicating the resolution of a + view, if configurable. + """ + + return "%s&%s&%s=%s&%s=%s" % ( + self.getRawDateQueryString("start", start), + self.getRawDateQueryString("end", end), + self.getQualifiedParameterName("mode"), mode or self.mode, + self.getQualifiedParameterName("resolution"), resolution or self.resolution + ) + + def getUpdateLink(self, start, end, mode=None, resolution=None): + + """ + Return a query string fragment for navigation to a view showing months + from 'start' to 'end' inclusive, with the optional 'mode' indicating the + view style and the optional 'resolution' indicating the resolution of a + view, if configurable. This link differs from the conventional + navigation link in that it is sufficient to activate the update action + and produce an updated region of the page without needing to locate and + process the page or any macro invocation. + """ + + return "calendar=%s&%s&%s&%s=%s&%s=%s" % ( + self.calendar_name, + self.getRawDateQueryString("start", start), + self.getRawDateQueryString("end", end), + self.getQualifiedParameterName("mode"), mode or self.mode, + self.getQualifiedParameterName("resolution"), resolution or self.resolution + ) + + def getNewEventLink(self, start): + + """ + Return a query string activating the new event form, incorporating the + calendar parameters, specialising the form for the given 'start' date or + month. + """ + + if start is not None: + details = start.as_tuple() + pairs = zip(["start-year=%d", "start-month=%d", "start-day=%d"], details) + args = [(param % value) for (param, value) in pairs] + args = "&".join(args) + else: + args = "" + + # Prepare navigation details for the calendar shown with the new event + # form. + + navigation_link = self.getNavigationLink( + self.calendar_start, self.calendar_end + ) + + return "action=EventAggregatorNewEvent&%s&%s&template=%s&parent=%s&%s" % ( + args, self.category_name_parameters, self.template_name, self.parent_name or "", + navigation_link) + + def getFullDateLabel(self, date): + page = self.page + request = page.request + return getFullDateLabel(request, date) + + def getFullMonthLabel(self, year_month): + page = self.page + request = page.request + return getFullMonthLabel(request, year_month) + + def getFullLabel(self, arg): + return self.resolution == "date" and self.getFullDateLabel(arg) or self.getFullMonthLabel(arg) + + def _getCalendarPeriod(self, start_label, end_label, default_label): + output = [] + if start_label: + output.append(start_label) + if end_label and start_label != end_label: + if output: + output.append(" - ") + output.append(end_label) + return "".join(output) or default_label + + def getCalendarPeriod(self): + _ = self.page.request.getText + return self._getCalendarPeriod( + self.calendar_start and self.getFullLabel(self.calendar_start), + self.calendar_end and self.getFullLabel(self.calendar_end), + _("All events") + ) + + def getOriginalCalendarPeriod(self): + _ = self.page.request.getText + return self._getCalendarPeriod( + self.original_calendar_start and self.getFullLabel(self.original_calendar_start), + self.original_calendar_end and self.getFullLabel(self.original_calendar_end), + _("All events") + ) + + def getRawCalendarPeriod(self): + _ = self.page.request.getText + return self._getCalendarPeriod( + self.raw_calendar_start, + self.raw_calendar_end, + _("No period specified") + ) + + def writeDownloadControls(self): + + """ + Return a representation of the download controls, featuring links for + view, calendar and customised downloads and subscriptions. + """ + + page = self.page + request = page.request + fmt = page.formatter + _ = request.getText + + output = [] + + # The full URL is needed for webcal links. + + full_url = "%s%s" % (request.getBaseURL(), getPathInfo(request)) + + # Generate the links. + + download_dialogue_link = "action=EventAggregatorSummary&parent=%s&resolution=%s&%s&%s" % ( + self.parent_name or "", + self.resolution, + self.category_name_parameters, + self.remote_source_parameters + ) + download_all_link = download_dialogue_link + "&doit=1" + download_link = download_all_link + ("&%s&%s" % ( + self.getDateQueryString("start", self.calendar_start, prefix=0), + self.getDateQueryString("end", self.calendar_end, prefix=0) + )) + + # Subscription links just explicitly select the RSS format. + + subscribe_dialogue_link = download_dialogue_link + "&format=RSS" + subscribe_all_link = download_all_link + "&format=RSS" + subscribe_link = download_link + "&format=RSS" + + # Adjust the "download all" and "subscribe all" links if the calendar + # has an inherent period associated with it. + + period_limits = [] + + if self.raw_calendar_start: + period_limits.append("&%s" % + self.getRawDateQueryString("start", self.raw_calendar_start, prefix=0) + ) + if self.raw_calendar_end: + period_limits.append("&%s" % + self.getRawDateQueryString("end", self.raw_calendar_end, prefix=0) + ) + + period_limits = "".join(period_limits) + + download_dialogue_link += period_limits + download_all_link += period_limits + subscribe_dialogue_link += period_limits + subscribe_all_link += period_limits + + # Pop-up descriptions of the downloadable calendars. + + calendar_period = self.getCalendarPeriod() + original_calendar_period = self.getOriginalCalendarPeriod() + raw_calendar_period = self.getRawCalendarPeriod() + + # Write the controls. + + # Download controls. + + output.append(fmt.div(on=1, css_class="event-download-controls")) + + output.append(fmt.span(on=1, css_class="event-download")) + output.append(fmt.text(_("Download..."))) + output.append(fmt.div(on=1, css_class="event-download-popup")) + + output.append(fmt.div(on=1, css_class="event-download-item")) + output.append(fmt.span(on=1, css_class="event-download-types")) + output.append(fmt.span(on=1, css_class="event-download-webcal")) + output.append(linkToResource(full_url.replace("http", "webcal", 1), request, _("webcal"), download_link)) + output.append(fmt.span(on=0)) + output.append(fmt.span(on=1, css_class="event-download-http")) + output.append(linkToPage(request, page, _("http"), download_link)) + output.append(fmt.span(on=0)) + output.append(fmt.span(on=0)) # end types + output.append(fmt.span(on=1, css_class="event-download-label")) + output.append(fmt.text(_("Download this view"))) + output.append(fmt.span(on=0)) # end label + output.append(fmt.span(on=1, css_class="event-download-period")) + output.append(fmt.text(calendar_period)) + output.append(fmt.span(on=0)) + output.append(fmt.div(on=0)) + + output.append(fmt.div(on=1, css_class="event-download-item")) + output.append(fmt.span(on=1, css_class="event-download-types")) + output.append(fmt.span(on=1, css_class="event-download-webcal")) + output.append(linkToResource(full_url.replace("http", "webcal", 1), request, _("webcal"), download_all_link)) + output.append(fmt.span(on=0)) + output.append(fmt.span(on=1, css_class="event-download-http")) + output.append(linkToPage(request, page, _("http"), download_all_link)) + output.append(fmt.span(on=0)) + output.append(fmt.span(on=0)) # end types + output.append(fmt.span(on=1, css_class="event-download-label")) + output.append(fmt.text(_("Download this calendar"))) + output.append(fmt.span(on=0)) # end label + output.append(fmt.span(on=1, css_class="event-download-period")) + output.append(fmt.text(original_calendar_period)) + output.append(fmt.span(on=0)) + output.append(fmt.span(on=1, css_class="event-download-period-raw")) + output.append(fmt.text(raw_calendar_period)) + output.append(fmt.span(on=0)) + output.append(fmt.div(on=0)) + + output.append(fmt.div(on=1, css_class="event-download-item")) + output.append(fmt.span(on=1, css_class="event-download-link")) + output.append(linkToPage(request, page, _("Edit download options..."), download_dialogue_link)) + output.append(fmt.span(on=0)) # end label + output.append(fmt.div(on=0)) + + output.append(fmt.div(on=0)) # end of pop-up + output.append(fmt.span(on=0)) # end of download + + # Subscription controls. + + output.append(fmt.span(on=1, css_class="event-download")) + output.append(fmt.text(_("Subscribe..."))) + output.append(fmt.div(on=1, css_class="event-download-popup")) + + output.append(fmt.div(on=1, css_class="event-download-item")) + output.append(fmt.span(on=1, css_class="event-download-label")) + output.append(linkToPage(request, page, _("Subscribe to this view"), subscribe_link)) + output.append(fmt.span(on=0)) # end label + output.append(fmt.span(on=1, css_class="event-download-period")) + output.append(fmt.text(calendar_period)) + output.append(fmt.span(on=0)) + output.append(fmt.div(on=0)) + + output.append(fmt.div(on=1, css_class="event-download-item")) + output.append(fmt.span(on=1, css_class="event-download-label")) + output.append(linkToPage(request, page, _("Subscribe to this calendar"), subscribe_all_link)) + output.append(fmt.span(on=0)) # end label + output.append(fmt.span(on=1, css_class="event-download-period")) + output.append(fmt.text(original_calendar_period)) + output.append(fmt.span(on=0)) + output.append(fmt.span(on=1, css_class="event-download-period-raw")) + output.append(fmt.text(raw_calendar_period)) + output.append(fmt.span(on=0)) + output.append(fmt.div(on=0)) + + output.append(fmt.div(on=1, css_class="event-download-item")) + output.append(fmt.span(on=1, css_class="event-download-link")) + output.append(linkToPage(request, page, _("Edit subscription options..."), subscribe_dialogue_link)) + output.append(fmt.span(on=0)) # end label + output.append(fmt.div(on=0)) + + output.append(fmt.div(on=0)) # end of pop-up + output.append(fmt.span(on=0)) # end of download + + output.append(fmt.div(on=0)) # end of controls + + return "".join(output) + + def writeViewControls(self): + + """ + Return a representation of the view mode controls, permitting viewing of + aggregated events in calendar, list or table form. + """ + + page = self.page + request = page.request + fmt = page.formatter + _ = request.getText + + output = [] + + start = self.calendar_start + end = self.calendar_end + + help_page = Page(request, "HelpOnEventAggregator") + calendar_link = self.getNavigationLink(start and start.as_month(), end and end.as_month(), "calendar", "month") + list_link = self.getNavigationLink(start, end, "list") + table_link = self.getNavigationLink(start, end, "table") + map_link = self.getNavigationLink(start, end, "map") + new_event_link = self.getNewEventLink(start) + + # Write the controls. + + output.append(fmt.div(on=1, css_class="event-view-controls")) + + output.append(fmt.span(on=1, css_class="event-view")) + output.append(linkToPage(request, help_page, _("Help"))) + output.append(fmt.span(on=0)) + + output.append(fmt.span(on=1, css_class="event-view")) + output.append(linkToPage(request, page, _("New event"), new_event_link)) + output.append(fmt.span(on=0)) + + if self.mode != "calendar": + output.append(fmt.span(on=1, css_class="event-view")) + output.append(linkToPage(request, page, _("View as calendar"), calendar_link)) + output.append(fmt.span(on=0)) + + if self.mode != "list": + output.append(fmt.span(on=1, css_class="event-view")) + output.append(linkToPage(request, page, _("View as list"), list_link)) + output.append(fmt.span(on=0)) + + if self.mode != "table": + output.append(fmt.span(on=1, css_class="event-view")) + output.append(linkToPage(request, page, _("View as table"), table_link)) + output.append(fmt.span(on=0)) + + if self.mode != "map" and self.map_name is not None: + output.append(fmt.span(on=1, css_class="event-view")) + output.append(linkToPage(request, page, _("View as map"), map_link)) + output.append(fmt.span(on=0)) + + output.append(fmt.div(on=0)) + + return "".join(output) + + def writeMapHeading(self): + + """ + Return the calendar heading for the current calendar, providing links + permitting navigation to other periods. + """ + + label = self.getCalendarPeriod() + + if self.raw_calendar_start is None or self.raw_calendar_end is None: + fmt = self.page.formatter + output = [] + output.append(fmt.span(on=1)) + output.append(fmt.text(label)) + output.append(fmt.span(on=0)) + return "".join(output) + else: + return self._writeCalendarHeading(label, self.calendar_start, self.calendar_end) + + def writeDateHeading(self, date): + if isinstance(date, Date): + return self.writeDayHeading(date) + else: + return self.writeMonthHeading(date) + + def writeMonthHeading(self, year_month): + + """ + Return the calendar heading for the given 'year_month' (a Month object) + providing links permitting navigation to other months. + """ + + full_month_label = self.getFullMonthLabel(year_month) + end_month = year_month.update(self.duration - 1) + return self._writeCalendarHeading(full_month_label, year_month, end_month) + + def writeDayHeading(self, date): + + """ + Return the calendar heading for the given 'date' (a Date object) + providing links permitting navigation to other dates. + """ + + full_date_label = self.getFullDateLabel(date) + end_date = date.update(self.duration - 1) + return self._writeCalendarHeading(full_date_label, date, end_date) + + def _writeCalendarHeading(self, label, start, end): + + """ + Write a calendar heading providing links permitting navigation to other + periods, using the given 'label' along with the 'start' and 'end' dates + to provide a link to a particular period. + """ + + page = self.page + request = page.request + fmt = page.formatter + _ = request.getText + + output = [] + + # Prepare navigation links. + + if self.calendar_name is not None: + calendar_name = self.calendar_name + + # Links to the previous set of months and to a calendar shifted + # back one month. + + previous_set_link = self.getNavigationLink( + self.previous_set_start, self.previous_set_end + ) + previous_link = self.getNavigationLink( + self.previous_start, self.previous_end + ) + + # Links to the next set of months and to a calendar shifted + # forward one month. + + next_set_link = self.getNavigationLink( + self.next_set_start, self.next_set_end + ) + next_link = self.getNavigationLink( + self.next_start, self.next_end + ) + + # A link leading to this date being at the top of the calendar. + + date_link = self.getNavigationLink(start, end) + + output.append(fmt.span(on=1, css_class="previous")) + output.append(linkToPage(request, page, "<<", previous_set_link)) + output.append(fmt.text(" ")) + output.append(linkToPage(request, page, "<", previous_link)) + output.append(fmt.span(on=0)) + + output.append(fmt.span(on=1, css_class="next")) + output.append(linkToPage(request, page, ">", next_link)) + output.append(fmt.text(" ")) + output.append(linkToPage(request, page, ">>", next_set_link)) + output.append(fmt.span(on=0)) + + output.append(linkToPage(request, page, label, date_link)) + + else: + output.append(fmt.span(on=1)) + output.append(fmt.text(label)) + output.append(fmt.span(on=0)) + + return "".join(output) + + def writeDayNumberHeading(self, date, busy): + + """ + Return a link for the given 'date' which will activate the new event + action for the given day. If 'busy' is given as a true value, the + heading will be marked as busy. + """ + + page = self.page + request = page.request + fmt = page.formatter + _ = request.getText + + output = [] + + year, month, day = date.as_tuple() + new_event_link = self.getNewEventLink(date) + + # Prepare a link to the day view for this day. + + day_view_link = self.getNavigationLink(date, date, "day", "date") + + # Output the heading class. + + output.append( + fmt.table_cell(on=1, attrs={ + "class" : "event-day-heading event-day-%s" % (busy and "busy" or "empty"), + "colspan" : "3" + })) + + # Output the number and pop-up menu. + + output.append(fmt.div(on=1, css_class="event-day-box")) + + output.append(fmt.span(on=1, css_class="event-day-number-popup")) + output.append(fmt.span(on=1, css_class="event-day-number-link")) + output.append(linkToPage(request, page, _("View day"), day_view_link)) + output.append(fmt.span(on=0)) + output.append(fmt.span(on=1, css_class="event-day-number-link")) + output.append(linkToPage(request, page, _("New event"), new_event_link)) + output.append(fmt.span(on=0)) + output.append(fmt.span(on=0)) + + output.append(fmt.span(on=1, css_class="event-day-number")) + output.append(fmt.text(unicode(day))) + output.append(fmt.span(on=0)) + + output.append(fmt.div(on=0)) + + # End of heading. + + output.append(fmt.table_cell(on=0)) + + return "".join(output) + + # Common layout methods. + + def getEventStyle(self, colour_seed): + + "Generate colour style information using the given 'colour_seed'." + + bg = getColour(colour_seed) + fg = getBlackOrWhite(bg) + return "background-color: rgb(%d, %d, %d); color: rgb(%d, %d, %d);" % (bg + fg) + + def writeEventSummaryBox(self, event): + + "Return an event summary box linking to the given 'event'." + + page = self.page + request = page.request + fmt = page.formatter + + output = [] + + event_details = event.getDetails() + event_summary = event.getSummary(self.parent_name) + + is_ambiguous = event.as_timespan().ambiguous() + style = self.getEventStyle(event_summary) + + # The event box contains the summary, alongside + # other elements. + + output.append(fmt.div(on=1, css_class="event-summary-box")) + output.append(fmt.div(on=1, css_class="event-summary", style=style)) + + if is_ambiguous: + output.append(fmt.icon("/!\\")) + + output.append(event.linkToEvent(request, event_summary)) + output.append(fmt.div(on=0)) + + # Add a pop-up element for long summaries. + + output.append(fmt.div(on=1, css_class="event-summary-popup", style=style)) + + if is_ambiguous: + output.append(fmt.icon("/!\\")) + + output.append(event.linkToEvent(request, event_summary)) + output.append(fmt.div(on=0)) + + output.append(fmt.div(on=0)) + + return "".join(output) + + # Calendar layout methods. + + def writeMonthTableHeading(self, year_month): + page = self.page + fmt = page.formatter + + output = [] + output.append(fmt.table_row(on=1)) + output.append(fmt.table_cell(on=1, attrs={"class" : "event-month-heading", "colspan" : "21"})) + + output.append(self.writeMonthHeading(year_month)) + + output.append(fmt.table_cell(on=0)) + output.append(fmt.table_row(on=0)) + + return "".join(output) + + def writeWeekdayHeadings(self): + page = self.page + request = page.request + fmt = page.formatter + _ = request.getText + + output = [] + output.append(fmt.table_row(on=1)) + + for weekday in range(0, 7): + output.append(fmt.table_cell(on=1, attrs={"class" : "event-weekday-heading", "colspan" : "3"})) + output.append(fmt.text(_(getDayLabel(weekday)))) + output.append(fmt.table_cell(on=0)) + + output.append(fmt.table_row(on=0)) + return "".join(output) + + def writeDayNumbers(self, first_day, number_of_days, month, coverage): + page = self.page + fmt = page.formatter + + output = [] + output.append(fmt.table_row(on=1)) + + for weekday in range(0, 7): + day = first_day + weekday + date = month.as_date(day) + + # Output out-of-month days. + + if day < 1 or day > number_of_days: + output.append(fmt.table_cell(on=1, + attrs={"class" : "event-day-heading event-day-excluded", "colspan" : "3"})) + output.append(fmt.table_cell(on=0)) + + # Output normal days. + + else: + # Output the day heading, making a link to a new event + # action. + + output.append(self.writeDayNumberHeading(date, date in coverage)) + + # End of day numbers. + + output.append(fmt.table_row(on=0)) + return "".join(output) + + def writeEmptyWeek(self, first_day, number_of_days): + page = self.page + fmt = page.formatter + + output = [] + output.append(fmt.table_row(on=1)) + + for weekday in range(0, 7): + day = first_day + weekday + + # Output out-of-month days. + + if day < 1 or day > number_of_days: + output.append(fmt.table_cell(on=1, + attrs={"class" : "event-day-content event-day-excluded", "colspan" : "3"})) + output.append(fmt.table_cell(on=0)) + + # Output empty days. + + else: + output.append(fmt.table_cell(on=1, + attrs={"class" : "event-day-content event-day-empty", "colspan" : "3"})) + + output.append(fmt.table_row(on=0)) + return "".join(output) + + def writeWeekSlots(self, first_day, number_of_days, month, week_end, week_slots): + output = [] + + locations = week_slots.keys() + locations.sort(sort_none_first) + + # Visit each slot corresponding to a location (or no location). + + for location in locations: + + # Visit each coverage span, presenting the events in the span. + + for events in week_slots[location]: + + # Output each set. + + output.append(self.writeWeekSlot(first_day, number_of_days, month, week_end, events)) + + # Add a spacer. + + output.append(self.writeWeekSpacer(first_day, number_of_days)) + + return "".join(output) + + def writeWeekSlot(self, first_day, number_of_days, month, week_end, events): + page = self.page + request = page.request + fmt = page.formatter + + output = [] + output.append(fmt.table_row(on=1)) + + # Then, output day details. + + for weekday in range(0, 7): + day = first_day + weekday + date = month.as_date(day) + + # Skip out-of-month days. + + if day < 1 or day > number_of_days: + output.append(fmt.table_cell(on=1, + attrs={"class" : "event-day-content event-day-excluded", "colspan" : "3"})) + output.append(fmt.table_cell(on=0)) + continue + + # Output the day. + + if date not in events: + output.append(fmt.table_cell(on=1, + attrs={"class" : "event-day-content event-day-empty", "colspan" : "3"})) + + # Get event details for the current day. + + for event in events: + event_details = event.getDetails() + + if date not in event: + continue + + # Get basic properties of the event. + + starts_today = event_details["start"] == date + ends_today = event_details["end"] == date + event_summary = event.getSummary(self.parent_name) + + style = self.getEventStyle(event_summary) + + # Determine if the event name should be shown. + + start_of_period = starts_today or weekday == 0 or day == 1 + + if self.name_usage == "daily" or start_of_period: + hide_text = 0 + else: + hide_text = 1 + + # Output start of day gap and determine whether + # any event content should be explicitly output + # for this day. + + if starts_today: + + # Single day events... + + if ends_today: + colspan = 3 + event_day_type = "event-day-single" + + # Events starting today... + + else: + output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-start-gap"})) + output.append(fmt.table_cell(on=0)) + + # Calculate the span of this cell. + # Events whose names appear on every day... + + if self.name_usage == "daily": + colspan = 2 + event_day_type = "event-day-starting" + + # Events whose names appear once per week... + + else: + if event_details["end"] <= week_end: + event_length = event_details["end"].day() - day + 1 + colspan = (event_length - 2) * 3 + 4 + else: + event_length = week_end.day() - day + 1 + colspan = (event_length - 1) * 3 + 2 + + event_day_type = "event-day-multiple" + + # Events continuing from a previous week... + + elif start_of_period: + + # End of continuing event... + + if ends_today: + colspan = 2 + event_day_type = "event-day-ending" + + # Events continuing for at least one more day... + + else: + + # Calculate the span of this cell. + # Events whose names appear on every day... + + if self.name_usage == "daily": + colspan = 3 + event_day_type = "event-day-full" + + # Events whose names appear once per week... + + else: + if event_details["end"] <= week_end: + event_length = event_details["end"].day() - day + 1 + colspan = (event_length - 1) * 3 + 2 + else: + event_length = week_end.day() - day + 1 + colspan = event_length * 3 + + event_day_type = "event-day-multiple" + + # Continuing events whose names appear on every day... + + elif self.name_usage == "daily": + if ends_today: + colspan = 2 + event_day_type = "event-day-ending" + else: + colspan = 3 + event_day_type = "event-day-full" + + # Continuing events whose names appear once per week... + + else: + colspan = None + + # Output the main content only if it is not + # continuing from a previous day. + + if colspan is not None: + + # Colour the cell for continuing events. + + attrs={ + "class" : "event-day-content event-day-busy %s" % event_day_type, + "colspan" : str(colspan) + } + + if not (starts_today and ends_today): + attrs["style"] = style + + output.append(fmt.table_cell(on=1, attrs=attrs)) + + # Output the event. + + if starts_today and ends_today or not hide_text: + output.append(self.writeEventSummaryBox(event)) + + output.append(fmt.table_cell(on=0)) + + # Output end of day gap. + + if ends_today and not starts_today: + output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-end-gap"})) + output.append(fmt.table_cell(on=0)) + + # End of set. + + output.append(fmt.table_row(on=0)) + return "".join(output) + + def writeWeekSpacer(self, first_day, number_of_days): + page = self.page + fmt = page.formatter + + output = [] + output.append(fmt.table_row(on=1)) + + for weekday in range(0, 7): + day = first_day + weekday + css_classes = "event-day-spacer" + + # Skip out-of-month days. + + if day < 1 or day > number_of_days: + css_classes += " event-day-excluded" + + output.append(fmt.table_cell(on=1, attrs={"class" : css_classes, "colspan" : "3"})) + output.append(fmt.table_cell(on=0)) + + output.append(fmt.table_row(on=0)) + return "".join(output) + + # Day layout methods. + + def writeDayTableHeading(self, date, colspan=1): + page = self.page + fmt = page.formatter + + output = [] + output.append(fmt.table_row(on=1)) + + output.append(fmt.table_cell(on=1, attrs={"class" : "event-full-day-heading", "colspan" : str(colspan)})) + output.append(self.writeDayHeading(date)) + output.append(fmt.table_cell(on=0)) + + output.append(fmt.table_row(on=0)) + return "".join(output) + + def writeEmptyDay(self, date): + page = self.page + fmt = page.formatter + + output = [] + output.append(fmt.table_row(on=1)) + + output.append(fmt.table_cell(on=1, + attrs={"class" : "event-day-content event-day-empty"})) + + output.append(fmt.table_row(on=0)) + return "".join(output) + + def writeDaySlots(self, date, full_coverage, day_slots): + + """ + Given a 'date', non-empty 'full_coverage' for the day concerned, and a + non-empty mapping of 'day_slots' (from locations to event collections), + output the day slots for the day. + """ + + page = self.page + fmt = page.formatter + + output = [] + + locations = day_slots.keys() + locations.sort(sort_none_first) + + # Traverse the time scale of the full coverage, visiting each slot to + # determine whether it provides content for each period. + + scale = getCoverageScale(full_coverage) + + # Define a mapping of events to rowspans. + + rowspans = {} + + # Populate each period with event details, recording how many periods + # each event populates. + + day_rows = [] + + for period in scale: + + # Ignore timespans before this day. + + if period != date: + continue + + # Visit each slot corresponding to a location (or no location). + + day_row = [] + + for location in locations: + + # Visit each coverage span, presenting the events in the span. + + for events in day_slots[location]: + event = self.getActiveEvent(period, events) + if event is not None: + if not rowspans.has_key(event): + rowspans[event] = 1 + else: + rowspans[event] += 1 + day_row.append((location, event)) + + day_rows.append((period, day_row)) + + # Output the locations. + + output.append(fmt.table_row(on=1)) + + # Add a spacer. + + output.append(self.writeDaySpacer(colspan=2, cls="location")) + + for location in locations: + + # Add spacers to the column spans. + + columns = len(day_slots[location]) * 2 - 1 + output.append(fmt.table_cell(on=1, attrs={"class" : "event-location-heading", "colspan" : str(columns)})) + output.append(fmt.text(location or "")) + output.append(fmt.table_cell(on=0)) + + # Add a trailing spacer. + + output.append(self.writeDaySpacer(cls="location")) + + output.append(fmt.table_row(on=0)) + + # Output the periods with event details. + + period = None + events_written = set() + + for period, day_row in day_rows: + + # Write an empty heading for the start of the day where the first + # applicable timespan starts before this day. + + if period.start < date: + output.append(fmt.table_row(on=1)) + output.append(self.writeDayScaleHeading("")) + + # Otherwise, write a heading describing the time. + + else: + output.append(fmt.table_row(on=1)) + output.append(self.writeDayScaleHeading(period.start.time_string())) + + output.append(self.writeDaySpacer()) + + # Visit each slot corresponding to a location (or no location). + + for location, event in day_row: + + # Output each location slot's contribution. + + if event is None or event not in events_written: + output.append(self.writeDaySlot(period, event, event is None and 1 or rowspans[event])) + if event is not None: + events_written.add(event) + + # Add a trailing spacer. + + output.append(self.writeDaySpacer()) + + output.append(fmt.table_row(on=0)) + + # Write a final time heading if the last period ends in the current day. + + if period is not None: + if period.end == date: + output.append(fmt.table_row(on=1)) + output.append(self.writeDayScaleHeading(period.end.time_string())) + + for slot in day_row: + output.append(self.writeDaySpacer()) + output.append(self.writeEmptyDaySlot()) + + output.append(fmt.table_row(on=0)) + + return "".join(output) + + def writeDayScaleHeading(self, heading): + page = self.page + fmt = page.formatter + + output = [] + output.append(fmt.table_cell(on=1, attrs={"class" : "event-scale-heading"})) + output.append(fmt.text(heading)) + output.append(fmt.table_cell(on=0)) + + return "".join(output) + + def getActiveEvent(self, period, events): + for event in events: + if period not in event: + continue + return event + else: + return None + + def writeDaySlot(self, period, event, rowspan): + page = self.page + fmt = page.formatter + + output = [] + + if event is not None: + event_summary = event.getSummary(self.parent_name) + style = self.getEventStyle(event_summary) + + output.append(fmt.table_cell(on=1, attrs={ + "class" : "event-timespan-content event-timespan-busy", + "style" : style, + "rowspan" : str(rowspan) + })) + output.append(self.writeEventSummaryBox(event)) + output.append(fmt.table_cell(on=0)) + else: + output.append(self.writeEmptyDaySlot()) + + return "".join(output) + + def writeEmptyDaySlot(self): + page = self.page + fmt = page.formatter + + output = [] + + output.append(fmt.table_cell(on=1, + attrs={"class" : "event-timespan-content event-timespan-empty"})) + output.append(fmt.table_cell(on=0)) + + return "".join(output) + + def writeDaySpacer(self, colspan=1, cls="timespan"): + page = self.page + fmt = page.formatter + + output = [] + output.append(fmt.table_cell(on=1, attrs={ + "class" : "event-%s-spacer" % cls, + "colspan" : str(colspan)})) + output.append(fmt.table_cell(on=0)) + return "".join(output) + + # Map layout methods. + + def writeMapTableHeading(self): + page = self.page + fmt = page.formatter + + output = [] + output.append(fmt.table_cell(on=1, attrs={"class" : "event-map-heading"})) + output.append(self.writeMapHeading()) + output.append(fmt.table_cell(on=0)) + + return "".join(output) + + def showDictError(self, text, pagename): + page = self.page + fmt = page.formatter + request = page.request + + output = [] + + output.append(fmt.div(on=1, attrs={"class" : "event-aggregator-error"})) + output.append(fmt.paragraph(on=1)) + output.append(fmt.text(text)) + output.append(fmt.paragraph(on=0)) + output.append(fmt.paragraph(on=1)) + output.append(linkToPage(request, Page(request, pagename), pagename)) + output.append(fmt.paragraph(on=0)) + + return "".join(output) + + def writeMapEventSummaries(self, events): + page = self.page + fmt = page.formatter + request = page.request + + # Sort the events by date. + + events.sort(sort_start_first) + + # Write out a self-contained list of events. + + output = [] + output.append(fmt.bullet_list(on=1, attr={"class" : "event-map-location-events"})) + + for event in events: + + # Get the event details. + + event_summary = event.getSummary(self.parent_name) + start, end = event.as_limits() + event_period = self._getCalendarPeriod( + start and self.getFullDateLabel(start), + end and self.getFullDateLabel(end), + "") + + output.append(fmt.listitem(on=1)) + + # Link to the page using the summary. + + output.append(event.linkToEvent(request, event_summary)) + + # Add the event period. + + output.append(fmt.text(" ")) + output.append(fmt.span(on=1, css_class="event-map-period")) + output.append(fmt.text(event_period)) + output.append(fmt.span(on=0)) + + output.append(fmt.listitem(on=0)) + + output.append(fmt.bullet_list(on=0)) + + return "".join(output) + + def render(self, all_shown_events): + + """ + Render the view, returning the rendered representation as a string. + The view will show a list of 'all_shown_events'. + """ + + page = self.page + fmt = page.formatter + request = page.request + _ = request.getText + + # Make a calendar. + + output = [] + + output.append(fmt.div(on=1, css_class="event-calendar")) + + # Output download controls. + + output.append(fmt.div(on=1, css_class="event-controls")) + output.append(self.writeDownloadControls()) + output.append(fmt.div(on=0)) + + # Output a table. + + if self.mode == "table": + + # Start of table view output. + + output.append(fmt.table(on=1, attrs={"tableclass" : "event-table"})) + + output.append(fmt.table_row(on=1)) + output.append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) + output.append(fmt.text(_("Event dates"))) + output.append(fmt.table_cell(on=0)) + output.append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) + output.append(fmt.text(_("Event location"))) + output.append(fmt.table_cell(on=0)) + output.append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) + output.append(fmt.text(_("Event details"))) + output.append(fmt.table_cell(on=0)) + output.append(fmt.table_row(on=0)) + + # Show the events in order. + + all_shown_events.sort(sort_start_first) + + for event in all_shown_events: + event_page = event.getPage() + event_summary = event.getSummary(self.parent_name) + event_details = event.getDetails() + + # Prepare CSS classes with category-related styling. + + css_classes = ["event-table-details"] + + for topic in event_details.get("topics") or event_details.get("categories") or []: + + # Filter the category text to avoid illegal characters. + + css_classes.append("event-table-category-%s" % "".join(filter(lambda c: c.isalnum(), topic))) + + attrs = {"class" : " ".join(css_classes)} + + output.append(fmt.table_row(on=1)) + + # Start and end dates. + + output.append(fmt.table_cell(on=1, attrs=attrs)) + output.append(fmt.span(on=1)) + output.append(fmt.text(str(event_details["start"]))) + output.append(fmt.span(on=0)) + + if event_details["start"] != event_details["end"]: + output.append(fmt.text(" - ")) + output.append(fmt.span(on=1)) + output.append(fmt.text(str(event_details["end"]))) + output.append(fmt.span(on=0)) + + output.append(fmt.table_cell(on=0)) + + # Location. + + output.append(fmt.table_cell(on=1, attrs=attrs)) + + if event_details.has_key("location"): + output.append(event_page.formatText(event_details["location"], request, fmt)) + + output.append(fmt.table_cell(on=0)) + + # Link to the page using the summary. + + output.append(fmt.table_cell(on=1, attrs=attrs)) + output.append(event.linkToEvent(request, event_summary)) + output.append(fmt.table_cell(on=0)) + + output.append(fmt.table_row(on=0)) + + # End of table view output. + + output.append(fmt.table(on=0)) + + # Output a map view. + + elif self.mode == "map": + + # Special dictionary pages. + + maps_page = getattr(request.cfg, "event_aggregator_maps_page", "EventMapsDict") + locations_page = getattr(request.cfg, "event_aggregator_locations_page", "EventLocationsDict") + + map_image = None + + # Get the maps and locations. + + maps = getWikiDict(maps_page, request) + locations = getWikiDict(locations_page, request) + + # Get the map image definition. + + if maps is not None and self.map_name is not None: + try: + map_details = maps[self.map_name].split() + + map_bottom_left_latitude, map_bottom_left_longitude, map_top_right_latitude, map_top_right_longitude = \ + map(getMapReference, map_details[:4]) + map_width, map_height = map(int, map_details[4:6]) + map_image = map_details[6] + + map_x_scale = map_width / (map_top_right_longitude - map_bottom_left_longitude).to_degrees() + map_y_scale = map_height / (map_top_right_latitude - map_bottom_left_latitude).to_degrees() + + except (KeyError, ValueError): + pass + + # Report errors. + + if maps is None: + output.append(self.showDictError( + _("You do not have read access to the maps page:"), + maps_page)) + + elif self.map_name is None: + output.append(self.showDictError( + _("Please specify a valid map name corresponding to an entry on the following page:"), + maps_page)) + + elif map_image is None: + output.append(self.showDictError( + _("Please specify a valid entry for %s on the following page:") % self.map_name, + maps_page)) + + elif locations is None: + output.append(self.showDictError( + _("You do not have read access to the locations page:"), + locations_page)) + + # Attempt to show the map. + + else: + + # Get events by position. + + events_by_location = {} + event_locations = {} + + for event in all_shown_events: + event_details = event.getDetails() + + location = event_details.get("location") + + if location is not None and not event_locations.has_key(location): + + # Get any explicit position of an event. + + if event_details.has_key("geo"): + latitude, longitude = event_details["geo"] + + # Or look up the position of a location using the locations + # page. + + else: + latitude, longitude = getLocationPosition(location, locations) + + # Use a normalised location if necessary. + + if latitude is None and longitude is None: + normalised_location = getNormalisedLocation(location) + if normalised_location is not None: + latitude, longitude = getLocationPosition(normalised_location, locations) + if latitude is not None and longitude is not None: + location = normalised_location + + # Only remember positioned locations. + + if latitude is not None and longitude is not None: + event_locations[location] = latitude, longitude + + # Record events according to location. + + if not events_by_location.has_key(location): + events_by_location[location] = [] + + events_by_location[location].append(event) + + # Get the map image URL. + + map_image_url = AttachFile.getAttachUrl(maps_page, map_image, request) + + # Start of map view output. + + map_identifier = "map-%s" % self.getIdentifier() + output.append(fmt.div(on=1, css_class="event-map", id=map_identifier)) + + output.append(fmt.table(on=1)) + + output.append(fmt.table_row(on=1)) + output.append(self.writeMapTableHeading()) + output.append(fmt.table_row(on=0)) + + output.append(fmt.table_row(on=1)) + output.append(fmt.table_cell(on=1)) + + output.append(fmt.div(on=1, css_class="event-map-container")) + output.append(fmt.image(map_image_url)) + output.append(fmt.number_list(on=1)) + + # Events with no location are unpositioned. + + if events_by_location.has_key(None): + unpositioned_events = events_by_location[None] + del events_by_location[None] + else: + unpositioned_events = [] + + # Events whose location is unpositioned are themselves considered + # unpositioned. + + for location in set(events_by_location.keys()).difference(event_locations.keys()): + unpositioned_events += events_by_location[location] + + # Sort the locations before traversing them. + + event_locations = event_locations.items() + event_locations.sort() + + # Show the events in the map. + + for location, (latitude, longitude) in event_locations: + events = events_by_location[location] + + # Skip unpositioned locations and locations outside the map. + + if latitude is None or longitude is None or \ + latitude < map_bottom_left_latitude or \ + longitude < map_bottom_left_longitude or \ + latitude > map_top_right_latitude or \ + longitude > map_top_right_longitude: + + unpositioned_events += events + continue + + # Get the position and dimensions of the map marker. + # NOTE: Use one degree as the marker size. + + marker_x, marker_y = getPositionForCentrePoint( + getPositionForReference(map_top_right_latitude, longitude, latitude, map_bottom_left_longitude, + map_x_scale, map_y_scale), + map_x_scale, map_y_scale) + + # Put a marker on the map. + + output.append(fmt.listitem(on=1, css_class="event-map-label")) + + # Have a positioned marker for the print mode. + + output.append(fmt.div(on=1, css_class="event-map-label-only", + style="left:%dpx; top:%dpx; min-width:%dpx; min-height:%dpx") % ( + marker_x, marker_y, map_x_scale, map_y_scale)) + output.append(fmt.div(on=0)) + + # Have a marker containing a pop-up when using the screen mode, + # providing a normal block when using the print mode. + + output.append(fmt.div(on=1, css_class="event-map-label", + style="left:%dpx; top:%dpx; min-width:%dpx; min-height:%dpx") % ( + marker_x, marker_y, map_x_scale, map_y_scale)) + output.append(fmt.div(on=1, css_class="event-map-details")) + output.append(fmt.div(on=1, css_class="event-map-shadow")) + output.append(fmt.div(on=1, css_class="event-map-location")) + + output.append(fmt.heading(on=1, depth=2)) + output.append(fmt.text(location)) + output.append(fmt.heading(on=0, depth=2)) + + output.append(self.writeMapEventSummaries(events)) + + output.append(fmt.div(on=0)) + output.append(fmt.div(on=0)) + output.append(fmt.div(on=0)) + output.append(fmt.div(on=0)) + output.append(fmt.listitem(on=0)) + + output.append(fmt.number_list(on=0)) + output.append(fmt.div(on=0)) + output.append(fmt.table_cell(on=0)) + output.append(fmt.table_row(on=0)) + + # Write unpositioned events. + + if unpositioned_events: + unpositioned_identifier = "unpositioned-%s" % self.getIdentifier() + + output.append(fmt.table_row(on=1, css_class="event-map-unpositioned", + id=unpositioned_identifier)) + output.append(fmt.table_cell(on=1)) + + output.append(fmt.heading(on=1, depth=2)) + output.append(fmt.text(_("Events not shown on the map"))) + output.append(fmt.heading(on=0, depth=2)) + + # Show and hide controls. + + output.append(fmt.div(on=1, css_class="event-map-show-control")) + output.append(fmt.anchorlink(on=1, name=unpositioned_identifier)) + output.append(fmt.text(_("Show unpositioned events"))) + output.append(fmt.anchorlink(on=0)) + output.append(fmt.div(on=0)) + + output.append(fmt.div(on=1, css_class="event-map-hide-control")) + output.append(fmt.anchorlink(on=1, name=map_identifier)) + output.append(fmt.text(_("Hide unpositioned events"))) + output.append(fmt.anchorlink(on=0)) + output.append(fmt.div(on=0)) + + output.append(self.writeMapEventSummaries(unpositioned_events)) + + # End of map view output. + + output.append(fmt.table_cell(on=0)) + output.append(fmt.table_row(on=0)) + output.append(fmt.table(on=0)) + output.append(fmt.div(on=0)) + + # Output a list. + + elif self.mode == "list": + + # Start of list view output. + + output.append(fmt.bullet_list(on=1, attr={"class" : "event-listings"})) + + # Output a list. + + for period in self.first.until(self.last): + + output.append(fmt.listitem(on=1, attr={"class" : "event-listings-period"})) + output.append(fmt.div(on=1, attr={"class" : "event-listings-heading"})) + + # Either write a date heading or produce links for navigable + # calendars. + + output.append(self.writeDateHeading(period)) + + output.append(fmt.div(on=0)) + + output.append(fmt.bullet_list(on=1, attr={"class" : "event-period-listings"})) + + # Show the events in order. + + events_in_period = getEventsInPeriod(all_shown_events, getCalendarPeriod(period, period)) + events_in_period.sort(sort_start_first) + + for event in events_in_period: + event_page = event.getPage() + event_details = event.getDetails() + event_summary = event.getSummary(self.parent_name) + + output.append(fmt.listitem(on=1, attr={"class" : "event-listing"})) + + # Link to the page using the summary. + + output.append(fmt.paragraph(on=1)) + output.append(event.linkToEvent(request, event_summary)) + output.append(fmt.paragraph(on=0)) + + # Start and end dates. + + output.append(fmt.paragraph(on=1)) + output.append(fmt.span(on=1)) + output.append(fmt.text(str(event_details["start"]))) + output.append(fmt.span(on=0)) + output.append(fmt.text(" - ")) + output.append(fmt.span(on=1)) + output.append(fmt.text(str(event_details["end"]))) + output.append(fmt.span(on=0)) + output.append(fmt.paragraph(on=0)) + + # Location. + + if event_details.has_key("location"): + output.append(fmt.paragraph(on=1)) + output.append(event_page.formatText(event_details["location"], request, fmt)) + output.append(fmt.paragraph(on=1)) + + # Topics. + + if event_details.has_key("topics") or event_details.has_key("categories"): + output.append(fmt.bullet_list(on=1, attr={"class" : "event-topics"})) + + for topic in event_details.get("topics") or event_details.get("categories") or []: + output.append(fmt.listitem(on=1)) + output.append(event_page.formatText(topic, request, fmt)) + output.append(fmt.listitem(on=0)) + + output.append(fmt.bullet_list(on=0)) + + output.append(fmt.listitem(on=0)) + + output.append(fmt.bullet_list(on=0)) + + # End of list view output. + + output.append(fmt.bullet_list(on=0)) + + # Output a month calendar. This shows month-by-month data. + + elif self.mode == "calendar": + + # Visit all months in the requested range, or across known events. + + for month in self.first.months_until(self.last): + + # Output a month. + + output.append(fmt.table(on=1, attrs={"tableclass" : "event-month"})) + + # Either write a month heading or produce links for navigable + # calendars. + + output.append(self.writeMonthTableHeading(month)) + + # Weekday headings. + + output.append(self.writeWeekdayHeadings()) + + # Process the days of the month. + + start_weekday, number_of_days = month.month_properties() + + # The start weekday is the weekday of day number 1. + # Find the first day of the week, counting from below zero, if + # necessary, in order to land on the first day of the month as + # day number 1. + + first_day = 1 - start_weekday + + while first_day <= number_of_days: + + # Find events in this week and determine how to mark them on the + # calendar. + + week_start = month.as_date(max(first_day, 1)) + week_end = month.as_date(min(first_day + 6, number_of_days)) + + full_coverage, week_slots = getCoverage( + getEventsInPeriod(all_shown_events, getCalendarPeriod(week_start, week_end))) + + # Output a week, starting with the day numbers. + + output.append(self.writeDayNumbers(first_day, number_of_days, month, full_coverage)) + + # Either generate empty days... + + if not week_slots: + output.append(self.writeEmptyWeek(first_day, number_of_days)) + + # Or generate each set of scheduled events... + + else: + output.append(self.writeWeekSlots(first_day, number_of_days, month, week_end, week_slots)) + + # Process the next week... + + first_day += 7 + + # End of month. + + output.append(fmt.table(on=0)) + + # Output a day view. + + elif self.mode == "day": + + # Visit all days in the requested range, or across known events. + + for date in self.first.days_until(self.last): + + output.append(fmt.table(on=1, attrs={"tableclass" : "event-calendar-day"})) + + full_coverage, day_slots = getCoverage( + getEventsInPeriod(all_shown_events, getCalendarPeriod(date, date)), "datetime") + + # Work out how many columns the day title will need. + # Include spacers after the scale and each event column. + + colspan = sum(map(len, day_slots.values())) * 2 + 2 + + output.append(self.writeDayTableHeading(date, colspan)) + + # Either generate empty days... + + if not day_slots: + output.append(self.writeEmptyDay(date)) + + # Or generate each set of scheduled events... + + else: + output.append(self.writeDaySlots(date, full_coverage, day_slots)) + + # End of day. + + output.append(fmt.table(on=0)) + + # Output view controls. + + output.append(fmt.div(on=1, css_class="event-controls")) + output.append(self.writeViewControls()) + output.append(fmt.div(on=0)) + + # Close the calendar region. + + output.append(fmt.div(on=0)) + + return ''.join(output) + # vim: tabstop=4 expandtab shiftwidth=4 diff -r 421d98ae50d2 -r a2297e8dd36c macros/EventAggregator.py --- a/macros/EventAggregator.py Sun Jan 22 00:45:28 2012 +0100 +++ b/macros/EventAggregator.py Sun Feb 05 01:29:45 2012 +0100 @@ -9,1301 +9,10 @@ """ from MoinMoin import wikiutil -from MoinMoin.action import AttachFile -from MoinMoin.Page import Page from EventAggregatorSupport import * -import calendar Dependencies = ['pages'] -# Abstractions. - -class View: - - "A view of the event calendar." - - def __init__(self, page, calendar_name, raw_calendar_start, raw_calendar_end, - original_calendar_start, original_calendar_end, calendar_start, calendar_end, - first, last, category_names, remote_sources, template_name, parent_name, mode, - resolution, name_usage, map_name): - - """ - Initialise the view with the current 'page', a 'calendar_name' (which - may be None), the 'raw_calendar_start' and 'raw_calendar_end' (which - are the actual start and end values provided by the request), the - calculated 'original_calendar_start' and 'original_calendar_end' (which - are the result of calculating the calendar's limits from the raw start - and end values), and the requested, calculated 'calendar_start' and - 'calendar_end' (which may involve different start and end values due to - navigation in the user interface), along with the 'first' and 'last' - months of event coverage. - - The additional 'category_names', 'remote_sources', 'template_name', - 'parent_name' and 'mode' parameters are used to configure the links - employed by the view. - - The 'resolution' affects the view for certain modes and is also used to - parameterise links. - - The 'name_usage' parameter controls how names are shown on calendar mode - events, such as how often labels are repeated. - - The 'map_name' parameter provides the name of a map to be used in the - map mode. - """ - - self.page = page - self.calendar_name = calendar_name - self.raw_calendar_start = raw_calendar_start - self.raw_calendar_end = raw_calendar_end - self.original_calendar_start = original_calendar_start - self.original_calendar_end = original_calendar_end - self.calendar_start = calendar_start - self.calendar_end = calendar_end - self.template_name = template_name - self.parent_name = parent_name - self.mode = mode - self.resolution = resolution - self.name_usage = name_usage - self.map_name = map_name - - self.category_name_parameters = "&".join([("category=%s" % name) for name in category_names]) - self.remote_source_parameters = "&".join([("source=%s" % source) for source in remote_sources]) - - # Calculate the duration in terms of the highest common unit of time. - - self.duration = last - first - - if self.calendar_name is not None: - - # Store the view parameters. - - self.previous_start = first.previous() - self.next_start = first.next() - self.previous_end = last.previous() - self.next_end = last.next() - - self.previous_set_start = first.update(-self.duration) - self.next_set_start = first.update(self.duration) - self.previous_set_end = last.update(-self.duration) - self.next_set_end = last.update(self.duration) - - def getIdentifier(self): - - "Return a unique identifier to be used to refer to this view." - - # NOTE: Nasty hack to get a unique identifier if no name is given. - - return self.calendar_name or str(id(self)) - - def getQualifiedParameterName(self, argname): - - "Return the 'argname' qualified using the calendar name." - - return getQualifiedParameterName(self.calendar_name, argname) - - def getDateQueryString(self, argname, date, prefix=1): - - """ - Return a query string fragment for the given 'argname', referring to the - month given by the specified 'year_month' object, appropriate for this - calendar. - - If 'prefix' is specified and set to a false value, the parameters in the - query string will not be calendar-specific, but could be used with the - summary action. - """ - - suffixes = ["year", "month", "day"] - - if date is not None: - args = [] - for suffix, value in zip(suffixes, date.as_tuple()): - suffixed_argname = "%s-%s" % (argname, suffix) - if prefix: - suffixed_argname = self.getQualifiedParameterName(suffixed_argname) - args.append("%s=%s" % (suffixed_argname, value)) - return "&".join(args) - else: - return "" - - def getRawDateQueryString(self, argname, date, prefix=1): - - """ - Return a query string fragment for the given 'argname', referring to the - date given by the specified 'date' value, appropriate for this - calendar. - - If 'prefix' is specified and set to a false value, the parameters in the - query string will not be calendar-specific, but could be used with the - summary action. - """ - - if date is not None: - if prefix: - argname = self.getQualifiedParameterName(argname) - return "%s=%s" % (argname, wikiutil.url_quote_plus(date)) - else: - return "" - - def getNavigationLink(self, start, end, mode=None, resolution=None): - - """ - Return a query string fragment for navigation to a view showing months - from 'start' to 'end' inclusive, with the optional 'mode' indicating the - view style and the optional 'resolution' indicating the resolution of a - view, if configurable. - """ - - return "%s&%s&%s=%s&%s=%s" % ( - self.getRawDateQueryString("start", start), - self.getRawDateQueryString("end", end), - self.getQualifiedParameterName("mode"), mode or self.mode, - self.getQualifiedParameterName("resolution"), resolution or self.resolution - ) - - def getNewEventLink(self, start): - - """ - Return a query string activating the new event form, incorporating the - calendar parameters, specialising the form for the given 'start' date or - month. - """ - - if start is not None: - details = start.as_tuple() - pairs = zip(["start-year=%d", "start-month=%d", "start-day=%d"], details) - args = [(param % value) for (param, value) in pairs] - args = "&".join(args) - else: - args = "" - - # Prepare navigation details for the calendar shown with the new event - # form. - - navigation_link = self.getNavigationLink( - self.calendar_start, self.calendar_end - ) - - return "action=EventAggregatorNewEvent&%s&%s&template=%s&parent=%s&%s" % ( - args, self.category_name_parameters, self.template_name, self.parent_name or "", - navigation_link) - - def getFullDateLabel(self, date): - page = self.page - request = page.request - return getFullDateLabel(request, date) - - def getFullMonthLabel(self, year_month): - page = self.page - request = page.request - return getFullMonthLabel(request, year_month) - - def getFullLabel(self, arg): - return self.resolution == "date" and self.getFullDateLabel(arg) or self.getFullMonthLabel(arg) - - def _getCalendarPeriod(self, start_label, end_label, default_label): - output = [] - if start_label: - output.append(start_label) - if end_label and start_label != end_label: - if output: - output.append(" - ") - output.append(end_label) - return "".join(output) or default_label - - def getCalendarPeriod(self): - _ = self.page.request.getText - return self._getCalendarPeriod( - self.calendar_start and self.getFullLabel(self.calendar_start), - self.calendar_end and self.getFullLabel(self.calendar_end), - _("All events") - ) - - def getOriginalCalendarPeriod(self): - _ = self.page.request.getText - return self._getCalendarPeriod( - self.original_calendar_start and self.getFullLabel(self.original_calendar_start), - self.original_calendar_end and self.getFullLabel(self.original_calendar_end), - _("All events") - ) - - def getRawCalendarPeriod(self): - _ = self.page.request.getText - return self._getCalendarPeriod( - self.raw_calendar_start, - self.raw_calendar_end, - _("No period specified") - ) - - def writeDownloadControls(self): - - """ - Return a representation of the download controls, featuring links for - view, calendar and customised downloads and subscriptions. - """ - - page = self.page - request = page.request - fmt = page.formatter - _ = request.getText - - output = [] - - # The full URL is needed for webcal links. - - full_url = "%s%s" % (request.getBaseURL(), getPathInfo(request)) - - # Generate the links. - - download_dialogue_link = "action=EventAggregatorSummary&parent=%s&resolution=%s&%s&%s" % ( - self.parent_name or "", - self.resolution, - self.category_name_parameters, - self.remote_source_parameters - ) - download_all_link = download_dialogue_link + "&doit=1" - download_link = download_all_link + ("&%s&%s" % ( - self.getDateQueryString("start", self.calendar_start, prefix=0), - self.getDateQueryString("end", self.calendar_end, prefix=0) - )) - - # Subscription links just explicitly select the RSS format. - - subscribe_dialogue_link = download_dialogue_link + "&format=RSS" - subscribe_all_link = download_all_link + "&format=RSS" - subscribe_link = download_link + "&format=RSS" - - # Adjust the "download all" and "subscribe all" links if the calendar - # has an inherent period associated with it. - - period_limits = [] - - if self.raw_calendar_start: - period_limits.append("&%s" % - self.getRawDateQueryString("start", self.raw_calendar_start, prefix=0) - ) - if self.raw_calendar_end: - period_limits.append("&%s" % - self.getRawDateQueryString("end", self.raw_calendar_end, prefix=0) - ) - - period_limits = "".join(period_limits) - - download_dialogue_link += period_limits - download_all_link += period_limits - subscribe_dialogue_link += period_limits - subscribe_all_link += period_limits - - # Pop-up descriptions of the downloadable calendars. - - calendar_period = self.getCalendarPeriod() - original_calendar_period = self.getOriginalCalendarPeriod() - raw_calendar_period = self.getRawCalendarPeriod() - - # Write the controls. - - # Download controls. - - output.append(fmt.div(on=1, css_class="event-download-controls")) - - output.append(fmt.span(on=1, css_class="event-download")) - output.append(fmt.text(_("Download..."))) - output.append(fmt.div(on=1, css_class="event-download-popup")) - - output.append(fmt.div(on=1, css_class="event-download-item")) - output.append(fmt.span(on=1, css_class="event-download-types")) - output.append(fmt.span(on=1, css_class="event-download-webcal")) - output.append(linkToResource(full_url.replace("http", "webcal", 1), request, _("webcal"), download_link)) - output.append(fmt.span(on=0)) - output.append(fmt.span(on=1, css_class="event-download-http")) - output.append(linkToPage(request, page, _("http"), download_link)) - output.append(fmt.span(on=0)) - output.append(fmt.span(on=0)) # end types - output.append(fmt.span(on=1, css_class="event-download-label")) - output.append(fmt.text(_("Download this view"))) - output.append(fmt.span(on=0)) # end label - output.append(fmt.span(on=1, css_class="event-download-period")) - output.append(fmt.text(calendar_period)) - output.append(fmt.span(on=0)) - output.append(fmt.div(on=0)) - - output.append(fmt.div(on=1, css_class="event-download-item")) - output.append(fmt.span(on=1, css_class="event-download-types")) - output.append(fmt.span(on=1, css_class="event-download-webcal")) - output.append(linkToResource(full_url.replace("http", "webcal", 1), request, _("webcal"), download_all_link)) - output.append(fmt.span(on=0)) - output.append(fmt.span(on=1, css_class="event-download-http")) - output.append(linkToPage(request, page, _("http"), download_all_link)) - output.append(fmt.span(on=0)) - output.append(fmt.span(on=0)) # end types - output.append(fmt.span(on=1, css_class="event-download-label")) - output.append(fmt.text(_("Download this calendar"))) - output.append(fmt.span(on=0)) # end label - output.append(fmt.span(on=1, css_class="event-download-period")) - output.append(fmt.text(original_calendar_period)) - output.append(fmt.span(on=0)) - output.append(fmt.span(on=1, css_class="event-download-period-raw")) - output.append(fmt.text(raw_calendar_period)) - output.append(fmt.span(on=0)) - output.append(fmt.div(on=0)) - - output.append(fmt.div(on=1, css_class="event-download-item")) - output.append(fmt.span(on=1, css_class="event-download-link")) - output.append(linkToPage(request, page, _("Edit download options..."), download_dialogue_link)) - output.append(fmt.span(on=0)) # end label - output.append(fmt.div(on=0)) - - output.append(fmt.div(on=0)) # end of pop-up - output.append(fmt.span(on=0)) # end of download - - # Subscription controls. - - output.append(fmt.span(on=1, css_class="event-download")) - output.append(fmt.text(_("Subscribe..."))) - output.append(fmt.div(on=1, css_class="event-download-popup")) - - output.append(fmt.div(on=1, css_class="event-download-item")) - output.append(fmt.span(on=1, css_class="event-download-label")) - output.append(linkToPage(request, page, _("Subscribe to this view"), subscribe_link)) - output.append(fmt.span(on=0)) # end label - output.append(fmt.span(on=1, css_class="event-download-period")) - output.append(fmt.text(calendar_period)) - output.append(fmt.span(on=0)) - output.append(fmt.div(on=0)) - - output.append(fmt.div(on=1, css_class="event-download-item")) - output.append(fmt.span(on=1, css_class="event-download-label")) - output.append(linkToPage(request, page, _("Subscribe to this calendar"), subscribe_all_link)) - output.append(fmt.span(on=0)) # end label - output.append(fmt.span(on=1, css_class="event-download-period")) - output.append(fmt.text(original_calendar_period)) - output.append(fmt.span(on=0)) - output.append(fmt.span(on=1, css_class="event-download-period-raw")) - output.append(fmt.text(raw_calendar_period)) - output.append(fmt.span(on=0)) - output.append(fmt.div(on=0)) - - output.append(fmt.div(on=1, css_class="event-download-item")) - output.append(fmt.span(on=1, css_class="event-download-link")) - output.append(linkToPage(request, page, _("Edit subscription options..."), subscribe_dialogue_link)) - output.append(fmt.span(on=0)) # end label - output.append(fmt.div(on=0)) - - output.append(fmt.div(on=0)) # end of pop-up - output.append(fmt.span(on=0)) # end of download - - output.append(fmt.div(on=0)) # end of controls - - return "".join(output) - - def writeViewControls(self): - - """ - Return a representation of the view mode controls, permitting viewing of - aggregated events in calendar, list or table form. - """ - - page = self.page - request = page.request - fmt = page.formatter - _ = request.getText - - output = [] - - start = self.calendar_start - end = self.calendar_end - - help_page = Page(request, "HelpOnEventAggregator") - calendar_link = self.getNavigationLink(start and start.as_month(), end and end.as_month(), "calendar", "month") - list_link = self.getNavigationLink(start, end, "list") - table_link = self.getNavigationLink(start, end, "table") - map_link = self.getNavigationLink(start, end, "map") - new_event_link = self.getNewEventLink(start) - - # Write the controls. - - output.append(fmt.div(on=1, css_class="event-view-controls")) - - output.append(fmt.span(on=1, css_class="event-view")) - output.append(linkToPage(request, help_page, _("Help"))) - output.append(fmt.span(on=0)) - - output.append(fmt.span(on=1, css_class="event-view")) - output.append(linkToPage(request, page, _("New event"), new_event_link)) - output.append(fmt.span(on=0)) - - if self.mode != "calendar": - output.append(fmt.span(on=1, css_class="event-view")) - output.append(linkToPage(request, page, _("View as calendar"), calendar_link)) - output.append(fmt.span(on=0)) - - if self.mode != "list": - output.append(fmt.span(on=1, css_class="event-view")) - output.append(linkToPage(request, page, _("View as list"), list_link)) - output.append(fmt.span(on=0)) - - if self.mode != "table": - output.append(fmt.span(on=1, css_class="event-view")) - output.append(linkToPage(request, page, _("View as table"), table_link)) - output.append(fmt.span(on=0)) - - if self.mode != "map" and self.map_name is not None: - output.append(fmt.span(on=1, css_class="event-view")) - output.append(linkToPage(request, page, _("View as map"), map_link)) - output.append(fmt.span(on=0)) - - output.append(fmt.div(on=0)) - - return "".join(output) - - def writeMapHeading(self): - - """ - Return the calendar heading for the current calendar, providing links - permitting navigation to other periods. - """ - - label = self.getCalendarPeriod() - - if self.raw_calendar_start is None or self.raw_calendar_end is None: - fmt = self.page.formatter - output = [] - output.append(fmt.span(on=1)) - output.append(fmt.text(label)) - output.append(fmt.span(on=0)) - return "".join(output) - else: - return self._writeCalendarHeading(label, self.calendar_start, self.calendar_end) - - def writeDateHeading(self, date): - if isinstance(date, Date): - return self.writeDayHeading(date) - else: - return self.writeMonthHeading(date) - - def writeMonthHeading(self, year_month): - - """ - Return the calendar heading for the given 'year_month' (a Month object) - providing links permitting navigation to other months. - """ - - full_month_label = self.getFullMonthLabel(year_month) - end_month = year_month.update(self.duration - 1) - return self._writeCalendarHeading(full_month_label, year_month, end_month) - - def writeDayHeading(self, date): - - """ - Return the calendar heading for the given 'date' (a Date object) - providing links permitting navigation to other dates. - """ - - full_date_label = self.getFullDateLabel(date) - end_date = date.update(self.duration - 1) - return self._writeCalendarHeading(full_date_label, date, end_date) - - def _writeCalendarHeading(self, label, start, end): - - """ - Write a calendar heading providing links permitting navigation to other - periods, using the given 'label' along with the 'start' and 'end' dates - to provide a link to a particular period. - """ - - page = self.page - request = page.request - fmt = page.formatter - _ = request.getText - - output = [] - - # Prepare navigation links. - - if self.calendar_name is not None: - calendar_name = self.calendar_name - - # Links to the previous set of months and to a calendar shifted - # back one month. - - previous_set_link = self.getNavigationLink( - self.previous_set_start, self.previous_set_end - ) - previous_link = self.getNavigationLink( - self.previous_start, self.previous_end - ) - - # Links to the next set of months and to a calendar shifted - # forward one month. - - next_set_link = self.getNavigationLink( - self.next_set_start, self.next_set_end - ) - next_link = self.getNavigationLink( - self.next_start, self.next_end - ) - - # A link leading to this date being at the top of the calendar. - - date_link = self.getNavigationLink(start, end) - - output.append(fmt.span(on=1, css_class="previous")) - output.append(linkToPage(request, page, "<<", previous_set_link)) - output.append(fmt.text(" ")) - output.append(linkToPage(request, page, "<", previous_link)) - output.append(fmt.span(on=0)) - - output.append(fmt.span(on=1, css_class="next")) - output.append(linkToPage(request, page, ">", next_link)) - output.append(fmt.text(" ")) - output.append(linkToPage(request, page, ">>", next_set_link)) - output.append(fmt.span(on=0)) - - output.append(linkToPage(request, page, label, date_link)) - - else: - output.append(fmt.span(on=1)) - output.append(fmt.text(label)) - output.append(fmt.span(on=0)) - - return "".join(output) - - def writeDayNumberHeading(self, date, busy): - - """ - Return a link for the given 'date' which will activate the new event - action for the given day. If 'busy' is given as a true value, the - heading will be marked as busy. - """ - - page = self.page - request = page.request - fmt = page.formatter - _ = request.getText - - output = [] - - year, month, day = date.as_tuple() - new_event_link = self.getNewEventLink(date) - - # Prepare a link to the day view for this day. - - day_view_link = self.getNavigationLink(date, date, "day", "date") - - # Output the heading class. - - output.append( - fmt.table_cell(on=1, attrs={ - "class" : "event-day-heading event-day-%s" % (busy and "busy" or "empty"), - "colspan" : "3" - })) - - # Output the number and pop-up menu. - - output.append(fmt.div(on=1, css_class="event-day-box")) - - output.append(fmt.span(on=1, css_class="event-day-number-popup")) - output.append(fmt.span(on=1, css_class="event-day-number-link")) - output.append(linkToPage(request, page, _("View day"), day_view_link)) - output.append(fmt.span(on=0)) - output.append(fmt.span(on=1, css_class="event-day-number-link")) - output.append(linkToPage(request, page, _("New event"), new_event_link)) - output.append(fmt.span(on=0)) - output.append(fmt.span(on=0)) - - output.append(fmt.span(on=1, css_class="event-day-number")) - output.append(fmt.text(unicode(day))) - output.append(fmt.span(on=0)) - - output.append(fmt.div(on=0)) - - # End of heading. - - output.append(fmt.table_cell(on=0)) - - return "".join(output) - - # Common layout methods. - - def getEventStyle(self, colour_seed): - - "Generate colour style information using the given 'colour_seed'." - - bg = getColour(colour_seed) - fg = getBlackOrWhite(bg) - return "background-color: rgb(%d, %d, %d); color: rgb(%d, %d, %d);" % (bg + fg) - - def writeEventSummaryBox(self, event): - - "Return an event summary box linking to the given 'event'." - - page = self.page - request = page.request - fmt = page.formatter - - output = [] - - event_details = event.getDetails() - event_summary = event.getSummary(self.parent_name) - - is_ambiguous = event.as_timespan().ambiguous() - style = self.getEventStyle(event_summary) - - # The event box contains the summary, alongside - # other elements. - - output.append(fmt.div(on=1, css_class="event-summary-box")) - output.append(fmt.div(on=1, css_class="event-summary", style=style)) - - if is_ambiguous: - output.append(fmt.icon("/!\\")) - - output.append(event.linkToEvent(request, event_summary)) - output.append(fmt.div(on=0)) - - # Add a pop-up element for long summaries. - - output.append(fmt.div(on=1, css_class="event-summary-popup", style=style)) - - if is_ambiguous: - output.append(fmt.icon("/!\\")) - - output.append(event.linkToEvent(request, event_summary)) - output.append(fmt.div(on=0)) - - output.append(fmt.div(on=0)) - - return "".join(output) - - # Calendar layout methods. - - def writeMonthTableHeading(self, year_month): - page = self.page - fmt = page.formatter - - output = [] - output.append(fmt.table_row(on=1)) - output.append(fmt.table_cell(on=1, attrs={"class" : "event-month-heading", "colspan" : "21"})) - - output.append(self.writeMonthHeading(year_month)) - - output.append(fmt.table_cell(on=0)) - output.append(fmt.table_row(on=0)) - - return "".join(output) - - def writeWeekdayHeadings(self): - page = self.page - request = page.request - fmt = page.formatter - _ = request.getText - - output = [] - output.append(fmt.table_row(on=1)) - - for weekday in range(0, 7): - output.append(fmt.table_cell(on=1, attrs={"class" : "event-weekday-heading", "colspan" : "3"})) - output.append(fmt.text(_(getDayLabel(weekday)))) - output.append(fmt.table_cell(on=0)) - - output.append(fmt.table_row(on=0)) - return "".join(output) - - def writeDayNumbers(self, first_day, number_of_days, month, coverage): - page = self.page - fmt = page.formatter - - output = [] - output.append(fmt.table_row(on=1)) - - for weekday in range(0, 7): - day = first_day + weekday - date = month.as_date(day) - - # Output out-of-month days. - - if day < 1 or day > number_of_days: - output.append(fmt.table_cell(on=1, - attrs={"class" : "event-day-heading event-day-excluded", "colspan" : "3"})) - output.append(fmt.table_cell(on=0)) - - # Output normal days. - - else: - # Output the day heading, making a link to a new event - # action. - - output.append(self.writeDayNumberHeading(date, date in coverage)) - - # End of day numbers. - - output.append(fmt.table_row(on=0)) - return "".join(output) - - def writeEmptyWeek(self, first_day, number_of_days): - page = self.page - fmt = page.formatter - - output = [] - output.append(fmt.table_row(on=1)) - - for weekday in range(0, 7): - day = first_day + weekday - - # Output out-of-month days. - - if day < 1 or day > number_of_days: - output.append(fmt.table_cell(on=1, - attrs={"class" : "event-day-content event-day-excluded", "colspan" : "3"})) - output.append(fmt.table_cell(on=0)) - - # Output empty days. - - else: - output.append(fmt.table_cell(on=1, - attrs={"class" : "event-day-content event-day-empty", "colspan" : "3"})) - - output.append(fmt.table_row(on=0)) - return "".join(output) - - def writeWeekSlots(self, first_day, number_of_days, month, week_end, week_slots): - output = [] - - locations = week_slots.keys() - locations.sort(sort_none_first) - - # Visit each slot corresponding to a location (or no location). - - for location in locations: - - # Visit each coverage span, presenting the events in the span. - - for events in week_slots[location]: - - # Output each set. - - output.append(self.writeWeekSlot(first_day, number_of_days, month, week_end, events)) - - # Add a spacer. - - output.append(self.writeWeekSpacer(first_day, number_of_days)) - - return "".join(output) - - def writeWeekSlot(self, first_day, number_of_days, month, week_end, events): - page = self.page - request = page.request - fmt = page.formatter - - output = [] - output.append(fmt.table_row(on=1)) - - # Then, output day details. - - for weekday in range(0, 7): - day = first_day + weekday - date = month.as_date(day) - - # Skip out-of-month days. - - if day < 1 or day > number_of_days: - output.append(fmt.table_cell(on=1, - attrs={"class" : "event-day-content event-day-excluded", "colspan" : "3"})) - output.append(fmt.table_cell(on=0)) - continue - - # Output the day. - - if date not in events: - output.append(fmt.table_cell(on=1, - attrs={"class" : "event-day-content event-day-empty", "colspan" : "3"})) - - # Get event details for the current day. - - for event in events: - event_details = event.getDetails() - - if date not in event: - continue - - # Get basic properties of the event. - - starts_today = event_details["start"] == date - ends_today = event_details["end"] == date - event_summary = event.getSummary(self.parent_name) - - style = self.getEventStyle(event_summary) - - # Determine if the event name should be shown. - - start_of_period = starts_today or weekday == 0 or day == 1 - - if self.name_usage == "daily" or start_of_period: - hide_text = 0 - else: - hide_text = 1 - - # Output start of day gap and determine whether - # any event content should be explicitly output - # for this day. - - if starts_today: - - # Single day events... - - if ends_today: - colspan = 3 - event_day_type = "event-day-single" - - # Events starting today... - - else: - output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-start-gap"})) - output.append(fmt.table_cell(on=0)) - - # Calculate the span of this cell. - # Events whose names appear on every day... - - if self.name_usage == "daily": - colspan = 2 - event_day_type = "event-day-starting" - - # Events whose names appear once per week... - - else: - if event_details["end"] <= week_end: - event_length = event_details["end"].day() - day + 1 - colspan = (event_length - 2) * 3 + 4 - else: - event_length = week_end.day() - day + 1 - colspan = (event_length - 1) * 3 + 2 - - event_day_type = "event-day-multiple" - - # Events continuing from a previous week... - - elif start_of_period: - - # End of continuing event... - - if ends_today: - colspan = 2 - event_day_type = "event-day-ending" - - # Events continuing for at least one more day... - - else: - - # Calculate the span of this cell. - # Events whose names appear on every day... - - if self.name_usage == "daily": - colspan = 3 - event_day_type = "event-day-full" - - # Events whose names appear once per week... - - else: - if event_details["end"] <= week_end: - event_length = event_details["end"].day() - day + 1 - colspan = (event_length - 1) * 3 + 2 - else: - event_length = week_end.day() - day + 1 - colspan = event_length * 3 - - event_day_type = "event-day-multiple" - - # Continuing events whose names appear on every day... - - elif self.name_usage == "daily": - if ends_today: - colspan = 2 - event_day_type = "event-day-ending" - else: - colspan = 3 - event_day_type = "event-day-full" - - # Continuing events whose names appear once per week... - - else: - colspan = None - - # Output the main content only if it is not - # continuing from a previous day. - - if colspan is not None: - - # Colour the cell for continuing events. - - attrs={ - "class" : "event-day-content event-day-busy %s" % event_day_type, - "colspan" : str(colspan) - } - - if not (starts_today and ends_today): - attrs["style"] = style - - output.append(fmt.table_cell(on=1, attrs=attrs)) - - # Output the event. - - if starts_today and ends_today or not hide_text: - output.append(self.writeEventSummaryBox(event)) - - output.append(fmt.table_cell(on=0)) - - # Output end of day gap. - - if ends_today and not starts_today: - output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-end-gap"})) - output.append(fmt.table_cell(on=0)) - - # End of set. - - output.append(fmt.table_row(on=0)) - return "".join(output) - - def writeWeekSpacer(self, first_day, number_of_days): - page = self.page - fmt = page.formatter - - output = [] - output.append(fmt.table_row(on=1)) - - for weekday in range(0, 7): - day = first_day + weekday - css_classes = "event-day-spacer" - - # Skip out-of-month days. - - if day < 1 or day > number_of_days: - css_classes += " event-day-excluded" - - output.append(fmt.table_cell(on=1, attrs={"class" : css_classes, "colspan" : "3"})) - output.append(fmt.table_cell(on=0)) - - output.append(fmt.table_row(on=0)) - return "".join(output) - - # Day layout methods. - - def writeDayTableHeading(self, date, colspan=1): - page = self.page - fmt = page.formatter - - output = [] - output.append(fmt.table_row(on=1)) - - output.append(fmt.table_cell(on=1, attrs={"class" : "event-full-day-heading", "colspan" : str(colspan)})) - output.append(self.writeDayHeading(date)) - output.append(fmt.table_cell(on=0)) - - output.append(fmt.table_row(on=0)) - return "".join(output) - - def writeEmptyDay(self, date): - page = self.page - fmt = page.formatter - - output = [] - output.append(fmt.table_row(on=1)) - - output.append(fmt.table_cell(on=1, - attrs={"class" : "event-day-content event-day-empty"})) - - output.append(fmt.table_row(on=0)) - return "".join(output) - - def writeDaySlots(self, date, full_coverage, day_slots): - - """ - Given a 'date', non-empty 'full_coverage' for the day concerned, and a - non-empty mapping of 'day_slots' (from locations to event collections), - output the day slots for the day. - """ - - page = self.page - fmt = page.formatter - - output = [] - - locations = day_slots.keys() - locations.sort(sort_none_first) - - # Traverse the time scale of the full coverage, visiting each slot to - # determine whether it provides content for each period. - - scale = getCoverageScale(full_coverage) - - # Define a mapping of events to rowspans. - - rowspans = {} - - # Populate each period with event details, recording how many periods - # each event populates. - - day_rows = [] - - for period in scale: - - # Ignore timespans before this day. - - if period != date: - continue - - # Visit each slot corresponding to a location (or no location). - - day_row = [] - - for location in locations: - - # Visit each coverage span, presenting the events in the span. - - for events in day_slots[location]: - event = self.getActiveEvent(period, events) - if event is not None: - if not rowspans.has_key(event): - rowspans[event] = 1 - else: - rowspans[event] += 1 - day_row.append((location, event)) - - day_rows.append((period, day_row)) - - # Output the locations. - - output.append(fmt.table_row(on=1)) - - # Add a spacer. - - output.append(self.writeDaySpacer(colspan=2, cls="location")) - - for location in locations: - - # Add spacers to the column spans. - - columns = len(day_slots[location]) * 2 - 1 - output.append(fmt.table_cell(on=1, attrs={"class" : "event-location-heading", "colspan" : str(columns)})) - output.append(fmt.text(location or "")) - output.append(fmt.table_cell(on=0)) - - # Add a trailing spacer. - - output.append(self.writeDaySpacer(cls="location")) - - output.append(fmt.table_row(on=0)) - - # Output the periods with event details. - - period = None - events_written = set() - - for period, day_row in day_rows: - - # Write an empty heading for the start of the day where the first - # applicable timespan starts before this day. - - if period.start < date: - output.append(fmt.table_row(on=1)) - output.append(self.writeDayScaleHeading("")) - - # Otherwise, write a heading describing the time. - - else: - output.append(fmt.table_row(on=1)) - output.append(self.writeDayScaleHeading(period.start.time_string())) - - output.append(self.writeDaySpacer()) - - # Visit each slot corresponding to a location (or no location). - - for location, event in day_row: - - # Output each location slot's contribution. - - if event is None or event not in events_written: - output.append(self.writeDaySlot(period, event, event is None and 1 or rowspans[event])) - if event is not None: - events_written.add(event) - - # Add a trailing spacer. - - output.append(self.writeDaySpacer()) - - output.append(fmt.table_row(on=0)) - - # Write a final time heading if the last period ends in the current day. - - if period is not None: - if period.end == date: - output.append(fmt.table_row(on=1)) - output.append(self.writeDayScaleHeading(period.end.time_string())) - - for slot in day_row: - output.append(self.writeDaySpacer()) - output.append(self.writeEmptyDaySlot()) - - output.append(fmt.table_row(on=0)) - - return "".join(output) - - def writeDayScaleHeading(self, heading): - page = self.page - fmt = page.formatter - - output = [] - output.append(fmt.table_cell(on=1, attrs={"class" : "event-scale-heading"})) - output.append(fmt.text(heading)) - output.append(fmt.table_cell(on=0)) - - return "".join(output) - - def getActiveEvent(self, period, events): - for event in events: - if period not in event: - continue - return event - else: - return None - - def writeDaySlot(self, period, event, rowspan): - page = self.page - fmt = page.formatter - - output = [] - - if event is not None: - event_summary = event.getSummary(self.parent_name) - style = self.getEventStyle(event_summary) - - output.append(fmt.table_cell(on=1, attrs={ - "class" : "event-timespan-content event-timespan-busy", - "style" : style, - "rowspan" : str(rowspan) - })) - output.append(self.writeEventSummaryBox(event)) - output.append(fmt.table_cell(on=0)) - else: - output.append(self.writeEmptyDaySlot()) - - return "".join(output) - - def writeEmptyDaySlot(self): - page = self.page - fmt = page.formatter - - output = [] - - output.append(fmt.table_cell(on=1, - attrs={"class" : "event-timespan-content event-timespan-empty"})) - output.append(fmt.table_cell(on=0)) - - return "".join(output) - - def writeDaySpacer(self, colspan=1, cls="timespan"): - page = self.page - fmt = page.formatter - - output = [] - output.append(fmt.table_cell(on=1, attrs={ - "class" : "event-%s-spacer" % cls, - "colspan" : str(colspan)})) - output.append(fmt.table_cell(on=0)) - return "".join(output) - - # Map layout methods. - - def writeMapTableHeading(self): - page = self.page - fmt = page.formatter - - output = [] - output.append(fmt.table_cell(on=1, attrs={"class" : "event-map-heading"})) - output.append(self.writeMapHeading()) - output.append(fmt.table_cell(on=0)) - - return "".join(output) - - def showDictError(self, text, pagename): - page = self.page - fmt = page.formatter - request = page.request - - output = [] - - output.append(fmt.div(on=1, attrs={"class" : "event-aggregator-error"})) - output.append(fmt.paragraph(on=1)) - output.append(fmt.text(text)) - output.append(fmt.paragraph(on=0)) - output.append(fmt.paragraph(on=1)) - output.append(linkToPage(request, Page(request, pagename), pagename)) - output.append(fmt.paragraph(on=0)) - - return "".join(output) - - def writeMapEventSummaries(self, events): - page = self.page - fmt = page.formatter - request = page.request - - # Sort the events by date. - - events.sort(sort_start_first) - - # Write out a self-contained list of events. - - output = [] - output.append(fmt.bullet_list(on=1, attr={"class" : "event-map-location-events"})) - - for event in events: - - # Get the event details. - - event_summary = event.getSummary(self.parent_name) - start, end = event.as_limits() - event_period = self._getCalendarPeriod( - start and self.getFullDateLabel(start), - end and self.getFullDateLabel(end), - "") - - output.append(fmt.listitem(on=1)) - - # Link to the page using the summary. - - output.append(event.linkToEvent(request, event_summary)) - - # Add the event period. - - output.append(fmt.text(" ")) - output.append(fmt.span(on=1, css_class="event-map-period")) - output.append(fmt.text(event_period)) - output.append(fmt.span(on=0)) - - output.append(fmt.listitem(on=0)) - - output.append(fmt.bullet_list(on=0)) - - return "".join(output) - -# HTML-related functions. - -def getColour(s): - colour = [0, 0, 0] - digit = 0 - for c in s: - colour[digit] += ord(c) - colour[digit] = colour[digit] % 256 - digit += 1 - digit = digit % 3 - return tuple(colour) - -def getBlackOrWhite(colour): - if sum(colour) / 3.0 > 127: - return (0, 0, 0) - else: - return (255, 255, 255) - # Macro functions. def execute(macro, args): @@ -1354,7 +63,6 @@ request = macro.request fmt = macro.formatter page = fmt.page - _ = request.getText # Interpret the arguments. @@ -1454,529 +162,6 @@ first, last, category_names, remote_sources, template_name, parent_name, mode, resolution, name_usage, map_name) - # Make a calendar. - - output = [] - - output.append(fmt.div(on=1, css_class="event-calendar")) - - # Output download controls. - - output.append(fmt.div(on=1, css_class="event-controls")) - output.append(view.writeDownloadControls()) - output.append(fmt.div(on=0)) - - # Output a table. - - if mode == "table": - - # Start of table view output. - - output.append(fmt.table(on=1, attrs={"tableclass" : "event-table"})) - - output.append(fmt.table_row(on=1)) - output.append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) - output.append(fmt.text(_("Event dates"))) - output.append(fmt.table_cell(on=0)) - output.append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) - output.append(fmt.text(_("Event location"))) - output.append(fmt.table_cell(on=0)) - output.append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) - output.append(fmt.text(_("Event details"))) - output.append(fmt.table_cell(on=0)) - output.append(fmt.table_row(on=0)) - - # Show the events in order. - - all_shown_events.sort(sort_start_first) - - for event in all_shown_events: - event_page = event.getPage() - event_summary = event.getSummary(parent_name) - event_details = event.getDetails() - - # Prepare CSS classes with category-related styling. - - css_classes = ["event-table-details"] - - for topic in event_details.get("topics") or event_details.get("categories") or []: - - # Filter the category text to avoid illegal characters. - - css_classes.append("event-table-category-%s" % "".join(filter(lambda c: c.isalnum(), topic))) - - attrs = {"class" : " ".join(css_classes)} - - output.append(fmt.table_row(on=1)) - - # Start and end dates. - - output.append(fmt.table_cell(on=1, attrs=attrs)) - output.append(fmt.span(on=1)) - output.append(fmt.text(str(event_details["start"]))) - output.append(fmt.span(on=0)) - - if event_details["start"] != event_details["end"]: - output.append(fmt.text(" - ")) - output.append(fmt.span(on=1)) - output.append(fmt.text(str(event_details["end"]))) - output.append(fmt.span(on=0)) - - output.append(fmt.table_cell(on=0)) - - # Location. - - output.append(fmt.table_cell(on=1, attrs=attrs)) - - if event_details.has_key("location"): - output.append(event_page.formatText(event_details["location"], request, fmt)) - - output.append(fmt.table_cell(on=0)) - - # Link to the page using the summary. - - output.append(fmt.table_cell(on=1, attrs=attrs)) - output.append(event.linkToEvent(request, event_summary)) - output.append(fmt.table_cell(on=0)) - - output.append(fmt.table_row(on=0)) - - # End of table view output. - - output.append(fmt.table(on=0)) - - # Output a map view. - - elif mode == "map": - - # Special dictionary pages. - - maps_page = getattr(request.cfg, "event_aggregator_maps_page", "EventMapsDict") - locations_page = getattr(request.cfg, "event_aggregator_locations_page", "EventLocationsDict") - - map_image = None - - # Get the maps and locations. - - maps = getWikiDict(maps_page, request) - locations = getWikiDict(locations_page, request) - - # Get the map image definition. - - if maps is not None and map_name is not None: - try: - map_details = maps[map_name].split() - - map_bottom_left_latitude, map_bottom_left_longitude, map_top_right_latitude, map_top_right_longitude = \ - map(getMapReference, map_details[:4]) - map_width, map_height = map(int, map_details[4:6]) - map_image = map_details[6] - - map_x_scale = map_width / (map_top_right_longitude - map_bottom_left_longitude).to_degrees() - map_y_scale = map_height / (map_top_right_latitude - map_bottom_left_latitude).to_degrees() - - except (KeyError, ValueError): - pass - - # Report errors. - - if maps is None: - output.append(view.showDictError( - _("You do not have read access to the maps page:"), - maps_page)) - - elif map_name is None: - output.append(view.showDictError( - _("Please specify a valid map name corresponding to an entry on the following page:"), - maps_page)) - - elif map_image is None: - output.append(view.showDictError( - _("Please specify a valid entry for %s on the following page:") % map_name, - maps_page)) - - elif locations is None: - output.append(view.showDictError( - _("You do not have read access to the locations page:"), - locations_page)) - - # Attempt to show the map. - - else: - - # Get events by position. - - events_by_location = {} - event_locations = {} - - for event in all_shown_events: - event_details = event.getDetails() - - location = event_details.get("location") - - if location is not None and not event_locations.has_key(location): - - # Get any explicit position of an event. - - if event_details.has_key("geo"): - latitude, longitude = event_details["geo"] - - # Or look up the position of a location using the locations - # page. - - else: - latitude, longitude = getLocationPosition(location, locations) - - # Use a normalised location if necessary. - - if latitude is None and longitude is None: - normalised_location = getNormalisedLocation(location) - if normalised_location is not None: - latitude, longitude = getLocationPosition(normalised_location, locations) - if latitude is not None and longitude is not None: - location = normalised_location - - # Only remember positioned locations. - - if latitude is not None and longitude is not None: - event_locations[location] = latitude, longitude - - # Record events according to location. - - if not events_by_location.has_key(location): - events_by_location[location] = [] - - events_by_location[location].append(event) - - # Get the map image URL. - - map_image_url = AttachFile.getAttachUrl(maps_page, map_image, request) - - # Start of map view output. - - map_identifier = "map-%s" % view.getIdentifier() - output.append(fmt.div(on=1, css_class="event-map", id=map_identifier)) - - output.append(fmt.table(on=1)) - - output.append(fmt.table_row(on=1)) - output.append(view.writeMapTableHeading()) - output.append(fmt.table_row(on=0)) - - output.append(fmt.table_row(on=1)) - output.append(fmt.table_cell(on=1)) - - output.append(fmt.div(on=1, css_class="event-map-container")) - output.append(fmt.image(map_image_url)) - output.append(fmt.number_list(on=1)) - - # Events with no location are unpositioned. - - if events_by_location.has_key(None): - unpositioned_events = events_by_location[None] - del events_by_location[None] - else: - unpositioned_events = [] - - # Events whose location is unpositioned are themselves considered - # unpositioned. - - for location in set(events_by_location.keys()).difference(event_locations.keys()): - unpositioned_events += events_by_location[location] - - # Sort the locations before traversing them. - - event_locations = event_locations.items() - event_locations.sort() - - # Show the events in the map. - - for location, (latitude, longitude) in event_locations: - events = events_by_location[location] - - # Skip unpositioned locations and locations outside the map. - - if latitude is None or longitude is None or \ - latitude < map_bottom_left_latitude or \ - longitude < map_bottom_left_longitude or \ - latitude > map_top_right_latitude or \ - longitude > map_top_right_longitude: - - unpositioned_events += events - continue - - # Get the position and dimensions of the map marker. - # NOTE: Use one degree as the marker size. - - marker_x, marker_y = getPositionForCentrePoint( - getPositionForReference(map_top_right_latitude, longitude, latitude, map_bottom_left_longitude, - map_x_scale, map_y_scale), - map_x_scale, map_y_scale) - - # Put a marker on the map. - - output.append(fmt.listitem(on=1, css_class="event-map-label")) - - # Have a positioned marker for the print mode. - - output.append(fmt.div(on=1, css_class="event-map-label-only", - style="left:%dpx; top:%dpx; min-width:%dpx; min-height:%dpx") % ( - marker_x, marker_y, map_x_scale, map_y_scale)) - output.append(fmt.div(on=0)) - - # Have a marker containing a pop-up when using the screen mode, - # providing a normal block when using the print mode. - - output.append(fmt.div(on=1, css_class="event-map-label", - style="left:%dpx; top:%dpx; min-width:%dpx; min-height:%dpx") % ( - marker_x, marker_y, map_x_scale, map_y_scale)) - output.append(fmt.div(on=1, css_class="event-map-details")) - output.append(fmt.div(on=1, css_class="event-map-shadow")) - output.append(fmt.div(on=1, css_class="event-map-location")) - - output.append(fmt.heading(on=1, depth=2)) - output.append(fmt.text(location)) - output.append(fmt.heading(on=0, depth=2)) - - output.append(view.writeMapEventSummaries(events)) - - output.append(fmt.div(on=0)) - output.append(fmt.div(on=0)) - output.append(fmt.div(on=0)) - output.append(fmt.div(on=0)) - output.append(fmt.listitem(on=0)) - - output.append(fmt.number_list(on=0)) - output.append(fmt.div(on=0)) - output.append(fmt.table_cell(on=0)) - output.append(fmt.table_row(on=0)) - - # Write unpositioned events. - - if unpositioned_events: - unpositioned_identifier = "unpositioned-%s" % view.getIdentifier() - - output.append(fmt.table_row(on=1, css_class="event-map-unpositioned", - id=unpositioned_identifier)) - output.append(fmt.table_cell(on=1)) - - output.append(fmt.heading(on=1, depth=2)) - output.append(fmt.text(_("Events not shown on the map"))) - output.append(fmt.heading(on=0, depth=2)) - - # Show and hide controls. - - output.append(fmt.div(on=1, css_class="event-map-show-control")) - output.append(fmt.anchorlink(on=1, name=unpositioned_identifier)) - output.append(fmt.text(_("Show unpositioned events"))) - output.append(fmt.anchorlink(on=0)) - output.append(fmt.div(on=0)) - - output.append(fmt.div(on=1, css_class="event-map-hide-control")) - output.append(fmt.anchorlink(on=1, name=map_identifier)) - output.append(fmt.text(_("Hide unpositioned events"))) - output.append(fmt.anchorlink(on=0)) - output.append(fmt.div(on=0)) - - output.append(view.writeMapEventSummaries(unpositioned_events)) - - # End of map view output. - - output.append(fmt.table_cell(on=0)) - output.append(fmt.table_row(on=0)) - output.append(fmt.table(on=0)) - output.append(fmt.div(on=0)) - - # Output a list. - - elif mode == "list": - - # Start of list view output. - - output.append(fmt.bullet_list(on=1, attr={"class" : "event-listings"})) - - # Output a list. - - for period in first.until(last): - - output.append(fmt.listitem(on=1, attr={"class" : "event-listings-period"})) - output.append(fmt.div(on=1, attr={"class" : "event-listings-heading"})) - - # Either write a date heading or produce links for navigable - # calendars. - - output.append(view.writeDateHeading(period)) - - output.append(fmt.div(on=0)) - - output.append(fmt.bullet_list(on=1, attr={"class" : "event-period-listings"})) - - # Show the events in order. - - events_in_period = getEventsInPeriod(all_shown_events, getCalendarPeriod(period, period)) - events_in_period.sort(sort_start_first) - - for event in events_in_period: - event_page = event.getPage() - event_details = event.getDetails() - event_summary = event.getSummary(parent_name) - - output.append(fmt.listitem(on=1, attr={"class" : "event-listing"})) - - # Link to the page using the summary. - - output.append(fmt.paragraph(on=1)) - output.append(event.linkToEvent(request, event_summary)) - output.append(fmt.paragraph(on=0)) - - # Start and end dates. - - output.append(fmt.paragraph(on=1)) - output.append(fmt.span(on=1)) - output.append(fmt.text(str(event_details["start"]))) - output.append(fmt.span(on=0)) - output.append(fmt.text(" - ")) - output.append(fmt.span(on=1)) - output.append(fmt.text(str(event_details["end"]))) - output.append(fmt.span(on=0)) - output.append(fmt.paragraph(on=0)) - - # Location. - - if event_details.has_key("location"): - output.append(fmt.paragraph(on=1)) - output.append(event_page.formatText(event_details["location"], request, fmt)) - output.append(fmt.paragraph(on=1)) - - # Topics. - - if event_details.has_key("topics") or event_details.has_key("categories"): - output.append(fmt.bullet_list(on=1, attr={"class" : "event-topics"})) - - for topic in event_details.get("topics") or event_details.get("categories") or []: - output.append(fmt.listitem(on=1)) - output.append(event_page.formatText(topic, request, fmt)) - output.append(fmt.listitem(on=0)) - - output.append(fmt.bullet_list(on=0)) - - output.append(fmt.listitem(on=0)) - - output.append(fmt.bullet_list(on=0)) - - # End of list view output. - - output.append(fmt.bullet_list(on=0)) - - # Output a month calendar. This shows month-by-month data. - - elif mode == "calendar": - - # Visit all months in the requested range, or across known events. - - for month in first.months_until(last): - - # Output a month. - - output.append(fmt.table(on=1, attrs={"tableclass" : "event-month"})) - - # Either write a month heading or produce links for navigable - # calendars. - - output.append(view.writeMonthTableHeading(month)) - - # Weekday headings. - - output.append(view.writeWeekdayHeadings()) - - # Process the days of the month. - - start_weekday, number_of_days = month.month_properties() - - # The start weekday is the weekday of day number 1. - # Find the first day of the week, counting from below zero, if - # necessary, in order to land on the first day of the month as - # day number 1. - - first_day = 1 - start_weekday - - while first_day <= number_of_days: - - # Find events in this week and determine how to mark them on the - # calendar. - - week_start = month.as_date(max(first_day, 1)) - week_end = month.as_date(min(first_day + 6, number_of_days)) - - full_coverage, week_slots = getCoverage( - getEventsInPeriod(all_shown_events, getCalendarPeriod(week_start, week_end))) - - # Output a week, starting with the day numbers. - - output.append(view.writeDayNumbers(first_day, number_of_days, month, full_coverage)) - - # Either generate empty days... - - if not week_slots: - output.append(view.writeEmptyWeek(first_day, number_of_days)) - - # Or generate each set of scheduled events... - - else: - output.append(view.writeWeekSlots(first_day, number_of_days, month, week_end, week_slots)) - - # Process the next week... - - first_day += 7 - - # End of month. - - output.append(fmt.table(on=0)) - - # Output a day view. - - elif mode == "day": - - # Visit all days in the requested range, or across known events. - - for date in first.days_until(last): - - output.append(fmt.table(on=1, attrs={"tableclass" : "event-calendar-day"})) - - full_coverage, day_slots = getCoverage( - getEventsInPeriod(all_shown_events, getCalendarPeriod(date, date)), "datetime") - - # Work out how many columns the day title will need. - # Include spacers after the scale and each event column. - - colspan = sum(map(len, day_slots.values())) * 2 + 2 - - output.append(view.writeDayTableHeading(date, colspan)) - - # Either generate empty days... - - if not day_slots: - output.append(view.writeEmptyDay(date)) - - # Or generate each set of scheduled events... - - else: - output.append(view.writeDaySlots(date, full_coverage, day_slots)) - - # End of day. - - output.append(fmt.table(on=0)) - - # Output view controls. - - output.append(fmt.div(on=1, css_class="event-controls")) - output.append(view.writeViewControls()) - output.append(fmt.div(on=0)) - - # Close the calendar region. - - output.append(fmt.div(on=0)) - - return ''.join(output) + return view.render(all_shown_events) # vim: tabstop=4 expandtab shiftwidth=4