1.1 --- a/EventAggregatorSupport.py Sun Jan 16 19:30:01 2011 +0100
1.2 +++ b/EventAggregatorSupport.py Mon Jan 17 02:26:17 2011 +0100
1.3 @@ -631,6 +631,9 @@
1.4 self.page = page
1.5 self.details = details
1.6
1.7 + def __hash__(self):
1.8 + return hash(self.getSummary())
1.9 +
1.10 def getPage(self):
1.11
1.12 "Return the page describing this event."
1.13 @@ -701,8 +704,8 @@
1.14 else:
1.15 return None
1.16
1.17 - def as_times(self):
1.18 - return self.as_timespan().as_times()
1.19 + def as_limits(self):
1.20 + return self.as_timespan().as_limits()
1.21
1.22 def getEvents(request, category_names, calendar_start=None, calendar_end=None, resolution="month"):
1.23
1.24 @@ -786,7 +789,7 @@
1.25 # Test for the suitability of the event.
1.26
1.27 if event.as_timespan() is not None:
1.28 - start, end = map(convert, event.as_timespan().as_times())
1.29 + start, end = map(convert, event.as_timespan().as_limits())
1.30
1.31 # Compare the dates to the requested calendar window, if any.
1.32
1.33 @@ -963,9 +966,19 @@
1.34
1.35 times = set()
1.36 for timespan in coverage:
1.37 - start, end = timespan.as_times()
1.38 - times.add(start)
1.39 - times.add(end)
1.40 + start, end = timespan.as_limits()
1.41 +
1.42 + # Add either genuine times or dates converted to times.
1.43 +
1.44 + if isinstance(start, DateTime):
1.45 + times.add(start)
1.46 +
1.47 + if isinstance(end, DateTime):
1.48 + if end.has_time():
1.49 + times.add(end)
1.50 + else:
1.51 + times.add(end.as_date().next_day())
1.52 +
1.53 times = list(times)
1.54 times.sort()
1.55
1.56 @@ -1194,6 +1207,9 @@
1.57 self.data[3:6] = hour, minute, second
1.58
1.59 def __str__(self):
1.60 + return Date.__str__(self) + self.time_string()
1.61 +
1.62 + def time_string(self):
1.63 if self.has_time():
1.64 data = self.as_tuple()
1.65 time_str = " %02d:%02d" % data[3:5]
1.66 @@ -1201,10 +1217,9 @@
1.67 time_str += ":%02d" % data[5]
1.68 if data[6] is not None:
1.69 time_str += " %s" % data[6]
1.70 + return time_str
1.71 else:
1.72 - time_str = ""
1.73 -
1.74 - return Date.__str__(self) + time_str
1.75 + return ""
1.76
1.77 def as_datetime(self):
1.78 return self
1.79 @@ -1212,17 +1227,37 @@
1.80 def as_date(self):
1.81 return Date(self.data[:3])
1.82
1.83 + def as_datetime_or_date(self):
1.84 +
1.85 + """
1.86 + Return a date for this datetime if fields are missing. Otherwise, return
1.87 + this datetime itself.
1.88 + """
1.89 +
1.90 + if not self.has_time():
1.91 + return self.as_date()
1.92 + else:
1.93 + return self
1.94 +
1.95 def __cmp__(self, other):
1.96 - if isinstance(other, DateTime):
1.97 - self_utc = self.to_utc()
1.98 - other_utc = other.to_utc()
1.99 - if self_utc is not None and other_utc is not None:
1.100 - return cmp(self_utc.as_tuple(), other_utc.as_tuple())
1.101 - return Month.__cmp__(self, other)
1.102 + this = self.as_datetime_or_date()
1.103 +
1.104 + if isinstance(this, DateTime) and isinstance(other, DateTime):
1.105 + other = other.as_datetime_or_date()
1.106 + if isinstance(other, DateTime):
1.107 + this_utc = this.to_utc()
1.108 + other_utc = other.to_utc()
1.109 + if this_utc is not None and other_utc is not None:
1.110 + return cmp(this_utc.as_tuple(), other_utc.as_tuple())
1.111 +
1.112 + return Date.__cmp__(this, other)
1.113
1.114 def has_time(self):
1.115 return self.data[3] is not None and self.data[4] is not None
1.116
1.117 + def time(self):
1.118 + return self.data[3:]
1.119 +
1.120 def seconds(self):
1.121 return self.data[5]
1.122
1.123 @@ -1246,6 +1281,9 @@
1.124 defined.
1.125 """
1.126
1.127 + if not self.has_time():
1.128 + return None
1.129 +
1.130 offset = self.utc_offset()
1.131 if offset:
1.132 hours, minutes = offset
1.133 @@ -1256,7 +1294,7 @@
1.134
1.135 # Get the components.
1.136
1.137 - hour, minute, second, zone = self.as_tuple()[3:]
1.138 + hour, minute, second, zone = self.time()
1.139 date = self.as_date()
1.140
1.141 # Add the minutes and hours.
1.142 @@ -1418,17 +1456,17 @@
1.143 def __hash__(self):
1.144 return hash((self.start, self.end))
1.145
1.146 - def as_times(self):
1.147 + def as_limits(self):
1.148 return self.start, self.end
1.149
1.150 def is_before(self, a, b):
1.151 - if isinstance(a, DateTime) and isinstance(b, DateTime):
1.152 + if isinstance(a, DateTime) and a.has_time() and isinstance(b, DateTime) and b.has_time():
1.153 return a <= b
1.154 else:
1.155 return a < b
1.156
1.157 def is_after_or_during(self, a, b):
1.158 - if isinstance(a, DateTime) and isinstance(b, DateTime):
1.159 + if isinstance(a, DateTime) and a.has_time() and isinstance(b, DateTime) and b.has_time():
1.160 return a > b
1.161 else:
1.162 return a >= b
1.163 @@ -1482,7 +1520,7 @@
1.164 value = value.as_timespan()
1.165
1.166 if isinstance(value, Timespan):
1.167 - start, end = map(self.convert_time, value.as_times())
1.168 + start, end = map(self.convert_time, value.as_limits())
1.169 return Timespan(start, end)
1.170 else:
1.171 return self.convert_time(value)
1.172 @@ -1634,7 +1672,10 @@
1.173
1.174 n = None
1.175
1.176 - if arg.startswith("current"):
1.177 + if arg is None:
1.178 + return None
1.179 +
1.180 + elif arg.startswith("current"):
1.181 date = getCurrentDate()
1.182 if len(arg) > 8:
1.183 n = int(arg[7:])
1.184 @@ -1663,7 +1704,10 @@
1.185
1.186 n = None
1.187
1.188 - if arg.startswith("current"):
1.189 + if arg is None:
1.190 + return None
1.191 +
1.192 + elif arg.startswith("current"):
1.193 date = getCurrentMonth()
1.194 if len(arg) > 8:
1.195 n = int(arg[7:])
1.196 @@ -1694,10 +1738,7 @@
1.197 """
1.198
1.199 arg = getQualifiedParameter(request, calendar_name, argname)
1.200 - if arg is not None:
1.201 - return getParameterDate(arg)
1.202 - else:
1.203 - return None
1.204 + return getParameterDate(arg)
1.205
1.206 def getFormMonth(request, calendar_name, argname):
1.207
1.208 @@ -1707,10 +1748,7 @@
1.209 """
1.210
1.211 arg = getQualifiedParameter(request, calendar_name, argname)
1.212 - if arg is not None:
1.213 - return getParameterMonth(arg)
1.214 - else:
1.215 - return None
1.216 + return getParameterMonth(arg)
1.217
1.218 def getFormDateTriple(request, yeararg, montharg, dayarg):
1.219
1.220 @@ -1750,6 +1788,9 @@
1.221 'year_month'.
1.222 """
1.223
1.224 + if not date:
1.225 + return ""
1.226 +
1.227 _ = request.getText
1.228 year, month, day = date.as_tuple()[:3]
1.229 start_weekday, number_of_days = date.month_properties()
1.230 @@ -1765,6 +1806,9 @@
1.231 'year_month'.
1.232 """
1.233
1.234 + if not year_month:
1.235 + return ""
1.236 +
1.237 _ = request.getText
1.238 year, month = year_month.as_tuple()[:2]
1.239 month_label = _(getMonthLabel(month))
2.1 --- a/actions/EventAggregatorSummary.py Sun Jan 16 19:30:01 2011 +0100
2.2 +++ b/actions/EventAggregatorSummary.py Mon Jan 17 02:26:17 2011 +0100
2.3 @@ -25,22 +25,13 @@
2.4
2.5 "A summary dialogue requesting various parameters."
2.6
2.7 - def get_evaluated_label(self, evaluated):
2.8 - _ = self._
2.9 - request = self.request
2.10 -
2.11 - if isinstance(evaluated, EventAggregatorSupport.Date):
2.12 - return EventAggregatorSupport.getFullDateLabel(request, evaluated)
2.13 - elif isinstance(evaluated, EventAggregatorSupport.Month):
2.14 - return EventAggregatorSupport.getFullMonthLabel(request, evaluated)
2.15 - else:
2.16 - return ""
2.17 -
2.18 def get_form_html(self, buttons_html):
2.19 _ = self._
2.20 request = self.request
2.21 form = self.get_form()
2.22
2.23 + resolution = form.get("resolution", ["month"])[0]
2.24 +
2.25 category_list = []
2.26 category_pagenames = form.get("category", [])
2.27
2.28 @@ -64,13 +55,18 @@
2.29 start_criteria_default = form.get("start", [""])[0]
2.30 end_criteria_default = form.get("end", [""])[0]
2.31
2.32 - start_criteria_evaluated = EventAggregatorSupport.getParameterDate(start_criteria_default) or \
2.33 - EventAggregatorSupport.getParameterMonth(start_criteria_default)
2.34 - end_criteria_evaluated = EventAggregatorSupport.getParameterDate(end_criteria_default) or \
2.35 - EventAggregatorSupport.getParameterMonth(end_criteria_default)
2.36 + if resolution == "date":
2.37 + get_parameter = EventAggregatorSupport.getParameterDate
2.38 + get_label = EventAggregatorSupport.getFullDateLabel
2.39 + else:
2.40 + get_parameter = EventAggregatorSupport.getParameterMonth
2.41 + get_label = EventAggregatorSupport.getFullMonthLabel
2.42
2.43 - start_criteria_evaluated = self.get_evaluated_label(start_criteria_evaluated)
2.44 - end_criteria_evaluated = self.get_evaluated_label(end_criteria_evaluated)
2.45 + start_criteria_evaluated = get_parameter(start_criteria_default)
2.46 + end_criteria_evaluated = get_parameter(end_criteria_default)
2.47 +
2.48 + start_criteria_evaluated = get_label(request, start_criteria_evaluated)
2.49 + end_criteria_evaluated = get_label(request, end_criteria_evaluated)
2.50
2.51 # Descriptions.
2.52
2.53 @@ -267,18 +263,18 @@
2.54
2.55 if isinstance(calendar_start, EventAggregatorSupport.Date):
2.56 if isinstance(calendar_end, EventAggregatorSupport.Date):
2.57 - mode = "day"
2.58 + resolution = "date"
2.59 else:
2.60 calendar_start = calendar_start.as_month()
2.61 - mode = "month"
2.62 + resolution = "month"
2.63 else:
2.64 - mode = "month"
2.65 + resolution = "month"
2.66 if isinstance(calendar_end, EventAggregatorSupport.Date):
2.67 calendar_end = calendar_end.as_month()
2.68
2.69 events, shown_events, all_shown_events, earliest, latest = \
2.70 EventAggregatorSupport.getEvents(request, category_names, calendar_start, calendar_end,
2.71 - mode == "day" and "day" or "month")
2.72 + resolution)
2.73
2.74 latest_timestamp = EventAggregatorSupport.setEventTimestamps(request, all_shown_events)
2.75
3.1 --- a/css/event-aggregator.css Sun Jan 16 19:30:01 2011 +0100
3.2 +++ b/css/event-aggregator.css Mon Jan 17 02:26:17 2011 +0100
3.3 @@ -267,14 +267,13 @@
3.4 border-bottom: 1px solid #dddddd;
3.5 border-left: 0;
3.6 border-right: 0;
3.7 + padding-bottom: 2em;
3.8 }
3.9
3.10 .event-timespan-content {
3.11 + vertical-align: top;
3.12 border-left: 0;
3.13 border-right: 0;
3.14 -}
3.15 -
3.16 -.event-timespan-empty {
3.17 border-top: 1px solid #dddddd;
3.18 border-bottom: 1px solid #dddddd;
3.19 }
4.1 --- a/macros/EventAggregator.py Sun Jan 16 19:30:01 2011 +0100
4.2 +++ b/macros/EventAggregator.py Mon Jan 17 02:26:17 2011 +0100
4.3 @@ -23,15 +23,19 @@
4.4 "A view of the event calendar."
4.5
4.6 def __init__(self, page, calendar_name, raw_calendar_start, raw_calendar_end,
4.7 - calendar_start, calendar_end, first, last, category_names, template_name,
4.8 - parent_name, mode, name_usage):
4.9 + original_calendar_start, original_calendar_end, calendar_start, calendar_end,
4.10 + first, last, category_names, template_name, parent_name, mode, name_usage):
4.11
4.12 """
4.13 Initialise the view with the current 'page', a 'calendar_name' (which
4.14 may be None), the 'raw_calendar_start' and 'raw_calendar_end' (which
4.15 are the actual start and end values provided by the request), the
4.16 - requested, calculated 'calendar_start' and 'calendar_end', and the
4.17 - 'first' and 'last' months of event coverage.
4.18 + calculated 'original_calendar_start' and 'original_calendar_end' (which
4.19 + are the result of calculating the calendar's limits from the raw start
4.20 + and end values), and the requested, calculated 'calendar_start' and
4.21 + 'calendar_end' (which may involve different start and end values due to
4.22 + navigation in the user interface), along with the 'first' and 'last'
4.23 + months of event coverage.
4.24
4.25 The additional 'category_names', 'template_name', 'parent_name' and
4.26 'mode' parameters are used to configure the links employed by the view.
4.27 @@ -44,6 +48,8 @@
4.28 self.calendar_name = calendar_name
4.29 self.raw_calendar_start = raw_calendar_start
4.30 self.raw_calendar_end = raw_calendar_end
4.31 + self.original_calendar_start = original_calendar_start
4.32 + self.original_calendar_end = original_calendar_end
4.33 self.calendar_start = calendar_start
4.34 self.calendar_end = calendar_end
4.35 self.template_name = template_name
4.36 @@ -153,8 +159,10 @@
4.37
4.38 # Generate the links.
4.39
4.40 - download_dialogue_link = "action=EventAggregatorSummary&parent=%s&%s" % (
4.41 - self.parent_name or "", self.category_name_parameters
4.42 + download_dialogue_link = "action=EventAggregatorSummary&parent=%s&resolution=%s&%s" % (
4.43 + self.parent_name or "",
4.44 + self.mode == "day" and "date" or "month",
4.45 + self.category_name_parameters
4.46 )
4.47 download_all_link = download_dialogue_link + "&doit=1"
4.48 download_link = download_all_link + ("&%s&%s" % (
4.49 @@ -197,6 +205,10 @@
4.50 get_label(self.calendar_start),
4.51 get_label(self.calendar_end)
4.52 )
4.53 + original_calendar_period = "%s - %s" % (
4.54 + get_label(self.original_calendar_start),
4.55 + get_label(self.original_calendar_end)
4.56 + )
4.57 raw_calendar_period = "%s - %s" % (self.raw_calendar_start, self.raw_calendar_end)
4.58
4.59 # Write the controls.
4.60 @@ -215,7 +227,7 @@
4.61 output.append(linkToPage(request, page, _("Download this calendar"), download_all_link))
4.62 output.append(fmt.span(on=1, css_class="event-download-popup"))
4.63 output.append(fmt.span(on=1, css_class="event-download-period"))
4.64 - output.append(fmt.text(calendar_period))
4.65 + output.append(fmt.text(original_calendar_period))
4.66 output.append(fmt.span(on=0))
4.67 output.append(fmt.span(on=1, css_class="event-download-period-raw"))
4.68 output.append(fmt.text(raw_calendar_period))
4.69 @@ -243,7 +255,7 @@
4.70 output.append(linkToPage(request, page, _("Subscribe to this calendar"), subscribe_all_link))
4.71 output.append(fmt.span(on=1, css_class="event-download-popup"))
4.72 output.append(fmt.span(on=1, css_class="event-download-period"))
4.73 - output.append(fmt.text(calendar_period))
4.74 + output.append(fmt.text(original_calendar_period))
4.75 output.append(fmt.span(on=0))
4.76 output.append(fmt.span(on=1, css_class="event-download-period-raw"))
4.77 output.append(fmt.text(raw_calendar_period))
4.78 @@ -808,15 +820,49 @@
4.79 # determine whether it provides content for each period.
4.80
4.81 scale = EventAggregatorSupport.getCoverageScale(full_coverage)
4.82 - period = None
4.83 +
4.84 + # Define a mapping of events to rowspans.
4.85 +
4.86 + rowspans = {}
4.87 +
4.88 + # Populate each period with event details, recording how many periods
4.89 + # each event populates.
4.90 +
4.91 + day_rows = []
4.92
4.93 for period in scale:
4.94
4.95 # Ignore timespans before this day.
4.96
4.97 - if not date in period:
4.98 + if period != date:
4.99 continue
4.100
4.101 + # Visit each slot corresponding to a location (or no location).
4.102 +
4.103 + day_row = []
4.104 +
4.105 + for location in locations:
4.106 +
4.107 + # Visit each coverage span, presenting the events in the span.
4.108 +
4.109 + for events in day_slots[location]:
4.110 + event = self.getActiveEvent(period, events)
4.111 + if event is not None:
4.112 + if not rowspans.has_key(event):
4.113 + rowspans[event] = 1
4.114 + else:
4.115 + rowspans[event] += 1
4.116 + day_row.append((location, event))
4.117 +
4.118 + day_rows.append((period, day_row))
4.119 +
4.120 + # Output the periods with event details.
4.121 +
4.122 + period = None
4.123 + events_written = set()
4.124 +
4.125 + for period, day_row in day_rows:
4.126 +
4.127 # Write an empty heading for the start of the day where the first
4.128 # applicable timespan starts before this day.
4.129
4.130 @@ -828,23 +874,22 @@
4.131
4.132 else:
4.133 output.append(fmt.table_row(on=1))
4.134 - output.append(self.writeDayScaleHeading(str(period.start)))
4.135 + output.append(self.writeDayScaleHeading(period.start.time_string()))
4.136
4.137 # Visit each slot corresponding to a location (or no location).
4.138
4.139 - for location in locations:
4.140 + for location, event in day_row:
4.141
4.142 - # Visit each coverage span, presenting the events in the span.
4.143 + # Add a spacer.
4.144
4.145 - for events in day_slots[location]:
4.146 + output.append(self.writeDaySpacer())
4.147
4.148 - # Add a spacer.
4.149 -
4.150 - output.append(self.writeDaySpacer())
4.151 + # Output each location slot's contribution.
4.152
4.153 - # Output each set's contribution to this period.
4.154 -
4.155 - output.append(self.writeDaySlot(period, events))
4.156 + if event is None or event not in events_written:
4.157 + output.append(self.writeDaySlot(period, event, event is None and 1 or rowspans[event]))
4.158 + if event is not None:
4.159 + events_written.add(event)
4.160
4.161 output.append(fmt.table_row(on=0))
4.162
4.163 @@ -853,12 +898,11 @@
4.164 if period is not None:
4.165 if period.end == date:
4.166 output.append(fmt.table_row(on=1))
4.167 - output.append(self.writeDayScaleHeading(str(period.end)))
4.168 + output.append(self.writeDayScaleHeading(period.end.time_string()))
4.169
4.170 - for location in locations:
4.171 - for events in day_slots[location]:
4.172 - output.append(self.writeDaySpacer())
4.173 - output.append(self.writeEmptyDaySlot())
4.174 + for slot in day_row:
4.175 + output.append(self.writeDaySpacer())
4.176 + output.append(self.writeEmptyDaySlot())
4.177
4.178 output.append(fmt.table_row(on=0))
4.179
4.180 @@ -875,26 +919,31 @@
4.181
4.182 return "".join(output)
4.183
4.184 - def writeDaySlot(self, period, events):
4.185 + def getActiveEvent(self, period, events):
4.186 + for event in events:
4.187 + if period not in event:
4.188 + continue
4.189 + return event
4.190 + else:
4.191 + return None
4.192 +
4.193 + def writeDaySlot(self, period, event, rowspan):
4.194 page = self.page
4.195 fmt = page.formatter
4.196
4.197 output = []
4.198
4.199 - for event in events:
4.200 - if period not in event:
4.201 - continue
4.202 -
4.203 + if event is not None:
4.204 event_summary = event.getSummary(self.parent_name)
4.205 style = self.getEventStyle(event_summary)
4.206
4.207 output.append(fmt.table_cell(on=1, attrs={
4.208 "class" : "event-timespan-content event-timespan-busy",
4.209 - "style" : style}))
4.210 + "style" : style,
4.211 + "rowspan" : str(rowspan)
4.212 + }))
4.213 output.append(self.writeEventSummaryBox(event))
4.214 output.append(fmt.table_cell(on=0))
4.215 - break
4.216 -
4.217 else:
4.218 output.append(self.writeEmptyDaySlot())
4.219
4.220 @@ -1042,8 +1091,8 @@
4.221
4.222 # Determine the limits of the calendar.
4.223
4.224 - calendar_start = get_date(raw_calendar_start)
4.225 - calendar_end = get_date(raw_calendar_end)
4.226 + original_calendar_start = calendar_start = get_date(raw_calendar_start)
4.227 + original_calendar_end = calendar_end = get_date(raw_calendar_end)
4.228
4.229 if calendar_name is not None:
4.230 calendar_start = get_form_date(request, calendar_name, "start") or calendar_start
4.231 @@ -1061,7 +1110,8 @@
4.232
4.233 # Define a view of the calendar, retaining useful navigational information.
4.234
4.235 - view = View(page, calendar_name, raw_calendar_start, raw_calendar_end, calendar_start, calendar_end,
4.236 + view = View(page, calendar_name, raw_calendar_start, raw_calendar_end,
4.237 + original_calendar_start, original_calendar_end, calendar_start, calendar_end,
4.238 first, last, category_names, template_name, parent_name, mode, name_usage)
4.239
4.240 # Make a calendar.