# HG changeset patch # User Paul Boddie # Date 1367591856 -7200 # Node ID 75db413c0a386d65419bcf51872a9f41ae7c70d2 # Parent 92c9070fd5f6c5969abe78c0102fa6ae1dee83bf Added support for showing events that occupy instants in time in day views, consolidating times in the timescale used for each day and showing the times specified for the events concerned for each common point in time. Adjusted labels of navigation links to indicate multiple days where a day view shows many days. diff -r 92c9070fd5f6 -r 75db413c0a38 EventAggregatorSupport/Filter.py --- a/EventAggregatorSupport/Filter.py Wed May 01 17:38:06 2013 +0200 +++ b/EventAggregatorSupport/Filter.py Fri May 03 16:37:36 2013 +0200 @@ -7,8 +7,16 @@ """ from DateSupport import DateTime, Timespan, TimespanCollection, \ - getCurrentDate, getCurrentMonth -from MoinSupport import * + getCurrentDate, getCurrentMonth, cmp_dates_as_day_start + +try: + set +except NameError: + from sets import Set as set + +# Sortable values representing start and end limits of timespans/events. + +START, END = 0, 1 # Event filtering and limits. @@ -189,38 +197,73 @@ """ 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. + a contiguous period of time, where each timespan is accompanied in a tuple + by a limit and a list of original time details. Thus, the scale consists of + (timespan, limit, set-of-times) tuples. """ - times = set() + times = {} + for timespan in coverage: start, end = timespan.as_limits() # Add either genuine times or dates converted to times. if isinstance(start, DateTime): - times.add(start) + value = start + key = value.to_utc(), START else: - times.add(start.as_start_of_day()) + value = start.as_start_of_day() + key = value, START + + if not times.has_key(key): + times[key] = set() + times[key].add(value) if isinstance(end, DateTime): - times.add(end) + value = end + key = value.to_utc(), END else: - times.add(end.as_date().next_day()) + value = end.as_date().next_day() + key = value, END - times = list(times) - times.sort(cmp_dates_as_day_start) + if not times.has_key(key): + times[key] = set() + times[key].add(value) + + keys = times.keys() + keys.sort(cmp_tuples_with_dates_as_day_start) scale = [] first = 1 - start = None - for time in times: + start, start_limit = None, None + + for time, limit in keys: if not first: - scale.append(Timespan(start, time)) + scale.append((Timespan(start, time), limit, times[(start, start_limit)])) else: first = 0 - start = time + start, start_limit = time, limit return scale +def cmp_tuples_with_dates_as_day_start(a, b): + + """ + Compare (datetime, limit) tuples, where identical datetimes are + distinguished by the limit associated with them. + """ + + a_date, a_limit = a + b_date, b_limit = b + result = cmp_dates_as_day_start(a_date, b_date) + + if result == 0: + if a_limit < b_limit: + return -1 + else: + return 1 + + return result + # vim: tabstop=4 expandtab shiftwidth=4 diff -r 92c9070fd5f6 -r 75db413c0a38 EventAggregatorSupport/View.py --- a/EventAggregatorSupport/View.py Wed May 01 17:38:06 2013 +0200 +++ b/EventAggregatorSupport/View.py Fri May 03 16:37:36 2013 +0200 @@ -563,6 +563,8 @@ specific_start = self.calendar_start specific_end = self.calendar_end + multiday = self.resolution == "date" and len(specific_start.days_until(specific_end)) > 1 + start = self.wider_calendar_start or self.original_calendar_start and specific_start end = self.wider_calendar_end or self.original_calendar_end and specific_end @@ -603,54 +605,60 @@ append(fmt.span(on=0)) if self.mode != "calendar": - view_label = self.resolution == "date" and _("View day in calendar") or _("View as calendar") + view_label = self.resolution == "date" and \ + (multiday and _("View days in calendar") or _("View day in calendar")) or \ + _("View as calendar") append(fmt.span(on=1, css_class="event-view")) append(linkToPage(request, page, view_label, calendar_link, onclick=calendar_update_link)) append(fmt.span(on=0)) if self.resolution == "date" and self.mode != "day": + view_label = multiday and _("View days as calendar") or _("View day as calendar") append(fmt.span(on=1, css_class="event-view")) - append(linkToPage(request, page, _("View day as calendar"), specific_day_link, onclick=specific_day_update_link)) + append(linkToPage(request, page, view_label, specific_day_link, onclick=specific_day_update_link)) append(fmt.span(on=0)) if self.resolution != "date" and self.mode != "list" or self.resolution == "date": - view_label = self.resolution == "date" and _("View day in list") or _("View as list") + view_label = self.resolution == "date" and \ + (multiday and _("View days in list") or _("View day in list")) or \ + _("View as list") append(fmt.span(on=1, css_class="event-view")) append(linkToPage(request, page, view_label, list_link, onclick=list_update_link)) append(fmt.span(on=0)) if self.resolution == "date" and self.mode != "list": + view_label = multiday and _("View days as list") or _("View day as list") append(fmt.span(on=1, css_class="event-view")) - append(linkToPage(request, page, _("View day as list"), - specific_list_link, onclick=specific_list_update_link - )) + append(linkToPage(request, page, view_label, specific_list_link, onclick=specific_list_update_link)) append(fmt.span(on=0)) if self.resolution != "date" and self.mode != "table" or self.resolution == "date": - view_label = self.resolution == "date" and _("View day in table") or _("View as table") + view_label = self.resolution == "date" and \ + (multiday and _("View days in table") or _("View day in table")) or \ + _("View as table") append(fmt.span(on=1, css_class="event-view")) append(linkToPage(request, page, view_label, table_link, onclick=table_update_link)) append(fmt.span(on=0)) if self.resolution == "date" and self.mode != "table": + view_label = multiday and _("View days as table") or _("View day as table") append(fmt.span(on=1, css_class="event-view")) - append(linkToPage(request, page, _("View day as table"), - specific_table_link, onclick=specific_table_update_link - )) + append(linkToPage(request, page, view_label, specific_table_link, onclick=specific_table_update_link)) append(fmt.span(on=0)) if self.map_name: if self.resolution != "date" and self.mode != "map" or self.resolution == "date": - view_label = self.resolution == "date" and _("View day in map") or _("View as map") + view_label = self.resolution == "date" and \ + (multiday and _("View days in map") or _("View day in map")) or \ + _("View as map") append(fmt.span(on=1, css_class="event-view")) append(linkToPage(request, page, view_label, map_link, onclick=map_update_link)) append(fmt.span(on=0)) if self.resolution == "date" and self.mode != "map": + view_label = multiday and _("View days as map") or _("View day as map") append(fmt.span(on=1, css_class="event-view")) - append(linkToPage(request, page, _("View day as map"), - specific_map_link, onclick=specific_map_update_link - )) + append(linkToPage(request, page, view_label, specific_map_link, onclick=specific_map_update_link)) append(fmt.span(on=0)) append(fmt.div(on=0)) @@ -1294,7 +1302,7 @@ day_rows = [] - for period in scale: + for period, limit, times in scale: # Ignore timespans before this day. @@ -1318,7 +1326,7 @@ rowspans[event] += 1 day_row.append((location, event)) - day_rows.append((period, day_row)) + day_rows.append((period, day_row, times)) # Output the locations. @@ -1345,23 +1353,21 @@ # Output the periods with event details. - period = None + last_period = 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: - append(fmt.table_row(on=1)) - append(self.writeDayScaleHeading("")) - - # Otherwise, write a heading describing the time. - + for period, day_row, times in day_rows: + + # Write a heading describing the time. + + append(fmt.table_row(on=1)) + + # Show times only for distinct periods. + + if not last_period or period.start != last_period.start: + append(self.writeDayScaleHeading(times)) else: - append(fmt.table_row(on=1)) - append(self.writeDayScaleHeading(period.start.time_string())) + append(self.writeDayScaleHeading([])) append(self.writeDaySpacer()) @@ -1382,12 +1388,14 @@ append(fmt.table_row(on=0)) + last_period = period + # Write a final time heading if the last period ends in the current day. if period is not None: if period.end == date: append(fmt.table_row(on=1)) - append(self.writeDayScaleHeading(period.end.time_string())) + append(self.writeDayScaleHeading(times)) for slot in day_row: append(self.writeDaySpacer()) @@ -1397,7 +1405,7 @@ return "".join(output) - def writeDayScaleHeading(self, heading): + def writeDayScaleHeading(self, times): page = self.page fmt = page.request.formatter @@ -1405,7 +1413,15 @@ append = output.append append(fmt.table_cell(on=1, attrs={"class" : "event-scale-heading"})) - append(fmt.text(heading)) + + first = 1 + for t in times: + if isinstance(t, DateTime): + if not first: + append(fmt.linebreak(0)) + append(fmt.text(t.time_string())) + first = 0 + append(fmt.table_cell(on=0)) return "".join(output) diff -r 92c9070fd5f6 -r 75db413c0a38 README.txt --- a/README.txt Wed May 01 17:38:06 2013 +0200 +++ b/README.txt Fri May 03 16:37:36 2013 +0200 @@ -361,6 +361,12 @@ dialogue URLs. * Fixed the resolution of whole calendar download/subscription links in day views. + * Added support for showing events that occupy instants in time in day + views, consolidating times in the timescale used for each day and showing + the times specified for the events concerned for each common point in + time. + * Adjusted labels of navigation links to indicate multiple days where a day + view shows many days. * Changed iCalendar serialisation to use the vContent library, making vContent a requirement of this software. * Refactored the library, replacing the support module with a package diff -r 92c9070fd5f6 -r 75db413c0a38 TO_DO.txt --- a/TO_DO.txt Wed May 01 17:38:06 2013 +0200 +++ b/TO_DO.txt Fri May 03 16:37:36 2013 +0200 @@ -7,17 +7,6 @@ Points in Time -------------- -Make events with identical start and end times appear in the day view, -potentially using the approach given below. - -Events which have identical start and end times might be represented by -building a calendar scale that distinguishes between times acting as start -times and times acting as end times. (The iCalendar specification appears to -state that events without end dates/times are actually points in time, but -this potentially conflicts with the expectation that merely specifying a start -date or time produces an event with an undefined end point or a "common sense" -end point.) - Consider making dates convertible to timespans of the form (start of day, start of next day).