# HG changeset patch # User Paul Boddie # Date 1295143814 -3600 # Node ID fa33d60bf552aaaa2d249461f230b7f2fb00afeb # Parent 750bec8ee3ad200244cf50f036978c9a562b44d2 Introduced events to day views. Fixed the markup around calendar events. Fixed the controls to show date-based periods for day views. Introduced common methods for making event summary boxes and defining style information. Introduced UTC-based datetime comparisons. diff -r 750bec8ee3ad -r fa33d60bf552 EventAggregatorSupport.py --- a/EventAggregatorSupport.py Mon Dec 20 01:18:48 2010 +0100 +++ b/EventAggregatorSupport.py Sun Jan 16 03:10:14 2011 +0100 @@ -2,7 +2,7 @@ """ MoinMoin - EventAggregator library - @copyright: 2008, 2009, 2010 by Paul Boddie + @copyright: 2008, 2009, 2010, 2011 by Paul Boddie @copyright: 2000-2004 Juergen Hermann , 2005-2008 MoinMoin:ThomasWaldmann. @license: GNU GPL (v2 or later), see COPYING.txt for details. @@ -943,6 +943,13 @@ return full_coverage, all_events def getCoverageScale(coverage): + + """ + Return a scale for the given coverage so that the times involved are + exposed. The scale consists of a list of non-overlapping timespans forming + a contiguous period of time. + """ + times = set() for timespan in coverage: start, end = timespan.as_times() @@ -960,6 +967,7 @@ else: first = 0 start = time + return scale # Date-related functions. @@ -1193,6 +1201,14 @@ def as_date(self): return Date(self.data[:3]) + def __cmp__(self, other): + if isinstance(other, DateTime): + self_utc = self.to_utc() + other_utc = other.to_utc() + if self_utc is not None and other_utc is not None: + return cmp(self_utc.as_tuple(), other_utc.as_tuple()) + return Month.__cmp__(self, other) + def has_time(self): return self.data[3] is not None and self.data[4] is not None diff -r 750bec8ee3ad -r fa33d60bf552 css/event-aggregator.css --- a/css/event-aggregator.css Mon Dec 20 01:18:48 2010 +0100 +++ b/css/event-aggregator.css Sun Jan 16 03:10:14 2011 +0100 @@ -6,7 +6,7 @@ ...before any rules. -Copyright (c) 2009, 2010 by Paul Boddie +Copyright (c) 2009, 2010, 2011 by Paul Boddie Licensed under the GNU GPL (v2 or later), see COPYING.txt for details. */ @@ -258,6 +258,39 @@ .event-calendar-day { width: 98%; + border-bottom: 1px solid #dddddd; +} + +.event-scale-heading { + vertical-align: top; + border-top: 1px solid #dddddd; + border-bottom: 1px solid #dddddd; + border-left: 0; + border-right: 0; +} + +.event-timespan-content { + border-left: 0; + border-right: 0; +} + +.event-timespan-empty { + border-top: 1px solid #dddddd; + border-bottom: 1px solid #dddddd; +} + +.event-timespan-content a:link, +.event-timespan-content a:hover, +.event-timespan-content a:visited { + color: inherit !important; +} + +.event-timespan-spacer { + width: 2%; + border-left: 0; + border-right: 0; + border-top: 1px solid #dddddd; + border-bottom: 1px solid #dddddd; } /* List/summary view. */ diff -r 750bec8ee3ad -r fa33d60bf552 macros/EventAggregator.py --- a/macros/EventAggregator.py Mon Dec 20 01:18:48 2010 +0100 +++ b/macros/EventAggregator.py Sun Jan 16 03:10:14 2011 +0100 @@ -2,7 +2,7 @@ """ MoinMoin - EventAggregator Macro - @copyright: 2008, 2009, 2010 by Paul Boddie + @copyright: 2008, 2009, 2010, 2011 by Paul Boddie @copyright: 2000-2004 Juergen Hermann , 2005-2008 MoinMoin:ThomasWaldmann. @license: GNU GPL (v2 or later), see COPYING.txt for details. @@ -37,7 +37,7 @@ 'mode' parameters are used to configure the links employed by the view. The 'name_usage' parameter controls how names are shown on calendar mode - events. + events, such as how often labels are repeated. """ self.page = page @@ -191,9 +191,11 @@ # Pop-up descriptions of the downloadable calendars. + get_label = self.mode == "day" and self.getFullDateLabel or self.getFullMonthLabel + calendar_period = "%s - %s" % ( - self.getFullMonthLabel(self.calendar_start), - self.getFullMonthLabel(self.calendar_end) + get_label(self.calendar_start), + get_label(self.calendar_end) ) raw_calendar_period = "%s - %s" % (self.raw_calendar_start, self.raw_calendar_end) @@ -386,6 +388,59 @@ 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_page = event.getPage() + event_details = event.getDetails() + event_summary = event.getSummary(self.parent_name) + + is_ambiguous = event_details["start"].ambiguous() or event_details["end"].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_page.linkToPage(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_page.linkToPage(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): @@ -554,13 +609,8 @@ starts_today = event_details["start"] == date ends_today = event_details["end"] == date event_summary = event.getSummary(self.parent_name) - is_ambiguous = event_details["start"].ambiguous() or event_details["end"].ambiguous() - # Generate a colour for the event. - - bg = getColour(event_summary) - fg = getBlackOrWhite(bg) - style = ("background-color: rgb(%d, %d, %d); color: rgb(%d, %d, %d);" % (bg + fg)) + style = self.getEventStyle(event_summary) # Determine if the event name should be shown. @@ -676,34 +726,9 @@ # Output the event. if starts_today and ends_today or not hide_text: - - # 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_page.linkToPage(request, event_summary)) - output.append(fmt.div(on=0)) + output.append(self.writeEventSummaryBox(event)) - # 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_page.linkToPage(request, event_summary)) - output.append(fmt.div(on=0)) - - output.append(fmt.div(on=0)) - - # Output end of day content. - - output.append(fmt.div(on=0)) + output.append(fmt.table_cell(on=0)) # Output end of day gap. @@ -711,10 +736,6 @@ output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-end-gap"})) output.append(fmt.table_cell(on=0)) - # End of day. - - output.append(fmt.table_cell(on=0)) - # End of set. output.append(fmt.table_row(on=0)) @@ -744,7 +765,7 @@ # Day layout methods. - def writeDayHeading(self, date): + def writeDayHeading(self, date, colspan=1): page = self.page request = page.request fmt = page.formatter @@ -754,7 +775,7 @@ output = [] output.append(fmt.table_row(on=1)) - output.append(fmt.table_cell(on=1, attrs={"class" : "event-weekday-heading"})) + output.append(fmt.table_cell(on=1, attrs={"class" : "event-weekday-heading", "colspan" : str(colspan)})) output.append(fmt.text(full_date_label)) output.append(fmt.table_cell(on=0)) @@ -787,33 +808,118 @@ # determine whether it provides content for each period. scale = EventAggregatorSupport.getCoverageScale(full_coverage) + period = None for period in scale: - start = period.start + + # Ignore timespans before this day. + + if not date in period: + continue + + # Write an empty heading for the start of the day where the first + # applicable timespan starts before this day. - output.append(fmt.table_row(on=1)) - output.append(fmt.table_cell(on=1, attrs={"class" : "event-weekday-heading"})) - output.append(fmt.text(str(start))) - output.append(fmt.table_cell(on=0)) + 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(str(period.start))) # Visit each slot corresponding to a location (or no location). - #for location in locations: + for location in locations: - # # Visit each coverage span, presenting the events in the span. + # Visit each coverage span, presenting the events in the span. + + for events in day_slots[location]: - # for events in day_slots[location]: + # Add a spacer. + + output.append(self.writeDaySpacer()) - # # Output each set. + # Output each set's contribution to this period. - # output.append(self.writeDaySlot(day, events)) + output.append(self.writeDaySlot(period, events)) 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(str(period.end))) + + for location in locations: + for events in day_slots[location]: + 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 writeDaySlot(self, date, events): - pass + def writeDaySlot(self, period, events): + page = self.page + fmt = page.formatter + + output = [] + + for event in events: + if period not in event: + continue + + 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})) + output.append(self.writeEventSummaryBox(event)) + output.append(fmt.table_cell(on=0)) + break + + 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): + page = self.page + fmt = page.formatter + + output = [] + output.append(fmt.table_cell(on=1, attrs={"class" : "event-timespan-spacer"})) + output.append(fmt.table_cell(on=0)) + return "".join(output) # HTML-related functions. @@ -925,6 +1031,8 @@ mode = EventAggregatorSupport.getQualifiedParameter(request, calendar_name, "mode", mode or "calendar") + # Different modes require different levels of precision. + if mode == "day": get_date = EventAggregatorSupport.getParameterDate get_form_date = EventAggregatorSupport.getFormDate @@ -1215,7 +1323,12 @@ full_coverage, day_slots = EventAggregatorSupport.getCoverage( date, date, shown_events.get(date, [])) - output.append(view.writeDayHeading(date)) + # Work out how many columns the day title will need. + # Includer spacers before each event column. + + colspan = sum(map(len, day_slots.values())) * 2 + 1 + + output.append(view.writeDayHeading(date, colspan)) # Either generate empty days...