1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - EventAggregator user interface library 4 5 @copyright: 2008, 2009, 2010, 2011, 2012, 2013, 2014 by Paul Boddie <paul@boddie.org.uk> 6 @license: GNU GPL (v2 or later), see COPYING.txt for details. 7 """ 8 9 from DateSupport import Date 10 from EventAggregatorSupport.Filter import getCalendarPeriod, getEventsInPeriod, \ 11 getCoverage, getCoverageScale 12 from EventAggregatorSupport.Locations import getMapsPage, getLocationsPage, Location 13 14 from GeneralSupport import sort_none_first 15 from LocationSupport import getMapReference, getNormalisedLocation, \ 16 getPositionForCentrePoint, getPositionForReference 17 from MoinDateSupport import getFullDateLabel, getFullMonthLabel 18 from MoinSupport import * 19 from ViewSupport import getColour, getBlackOrWhite 20 21 from MoinMoin.Page import Page 22 from MoinMoin.action import AttachFile 23 from MoinMoin import wikiutil 24 25 try: 26 set 27 except NameError: 28 from sets import Set as set 29 30 # Utility functions. 31 32 def to_plain_text(s, request): 33 34 "Convert 's' to plain text." 35 36 fmt = getFormatterClass(request, "plain")(request) 37 fmt.setPage(request.page) 38 return formatText(s, request, fmt) 39 40 def getLocationPosition(location, locations): 41 42 """ 43 Attempt to return the position of the given 'location' using the 'locations' 44 dictionary provided. If no position can be found, return a latitude of None 45 and a longitude of None. 46 """ 47 48 latitude, longitude = None, None 49 50 if location is not None: 51 try: 52 latitude, longitude = map(getMapReference, locations[location].split()) 53 except (KeyError, ValueError): 54 pass 55 56 return latitude, longitude 57 58 # Event sorting. 59 60 def sort_start_first(x, y): 61 x_ts = x.as_limits() 62 if x_ts is not None: 63 x_start, x_end = x_ts 64 y_ts = y.as_limits() 65 if y_ts is not None: 66 y_start, y_end = y_ts 67 start_order = cmp(x_start, y_start) 68 if start_order == 0: 69 return cmp(x_end, y_end) 70 else: 71 return start_order 72 return 0 73 74 # User interface abstractions. 75 76 class View: 77 78 "A view of the event calendar." 79 80 def __init__(self, page, calendar_name, 81 raw_calendar_start, raw_calendar_end, 82 original_calendar_start, original_calendar_end, 83 calendar_start, calendar_end, 84 wider_calendar_start, wider_calendar_end, 85 first, last, category_names, remote_sources, search_pattern, template_name, 86 parent_name, mode, raw_resolution, resolution, name_usage, map_name): 87 88 """ 89 Initialise the view with the current 'page', a 'calendar_name' (which 90 may be None), the 'raw_calendar_start' and 'raw_calendar_end' (which 91 are the actual start and end values provided by the request), the 92 calculated 'original_calendar_start' and 'original_calendar_end' (which 93 are the result of calculating the calendar's limits from the raw start 94 and end values), the requested, calculated 'calendar_start' and 95 'calendar_end' (which may involve different start and end values due to 96 navigation in the user interface), and the requested 97 'wider_calendar_start' and 'wider_calendar_end' (which indicate a wider 98 view used when navigating out of the day view), along with the 'first' 99 and 'last' months of event coverage. 100 101 The additional 'category_names', 'remote_sources', 'search_pattern', 102 'template_name', 'parent_name' and 'mode' parameters are used to 103 configure the links employed by the view. 104 105 The 'raw_resolution' is used to parameterise download links, whereas the 106 'resolution' affects the view for certain modes and is also used to 107 parameterise links. 108 109 The 'name_usage' parameter controls how names are shown on calendar mode 110 events, such as how often labels are repeated. 111 112 The 'map_name' parameter provides the name of a map to be used in the 113 map mode. 114 """ 115 116 self.page = page 117 self.calendar_name = calendar_name 118 self.raw_calendar_start = raw_calendar_start 119 self.raw_calendar_end = raw_calendar_end 120 self.original_calendar_start = original_calendar_start 121 self.original_calendar_end = original_calendar_end 122 self.calendar_start = calendar_start 123 self.calendar_end = calendar_end 124 self.wider_calendar_start = wider_calendar_start 125 self.wider_calendar_end = wider_calendar_end 126 self.template_name = template_name 127 self.parent_name = parent_name 128 self.mode = mode 129 self.raw_resolution = raw_resolution 130 self.resolution = resolution 131 self.name_usage = name_usage 132 self.map_name = map_name 133 134 # Search-related parameters for links. 135 136 self.category_name_parameters = "&".join([("category=%s" % name) for name in category_names]) 137 self.remote_source_parameters = "&".join([("source=%s" % source) for source in remote_sources]) 138 self.search_pattern = search_pattern 139 140 # Calculate the duration in terms of the highest common unit of time. 141 142 self.first = first 143 self.last = last 144 self.initDuration() 145 146 self.showing_everything = not self.calendar_start and not self.calendar_end 147 148 if not self.showing_everything: 149 150 # Store the view parameters. 151 152 self.previous_start = first.previous() 153 self.next_start = first.next() 154 self.previous_end = last.previous() 155 self.next_end = last.next() 156 157 self.previous_set_start = first.update(-self.duration) 158 self.next_set_start = first.update(self.duration) 159 self.previous_set_end = last.update(-self.duration) 160 self.next_set_end = last.update(self.duration) 161 162 def initDuration(self): 163 164 "Limit the duration of the calendar to prevent excessive output." 165 166 request = self.page.request 167 168 self.duration = abs(self.last - self.first) + 1 169 170 # Limit to the specified number of units. 171 172 limit = int(getattr(request.cfg, "event_aggregator_max_duration", 31)) 173 174 if self.duration > limit: 175 if isinstance(self.first, Date): 176 self.last = self.first.day_update(limit - 1) 177 else: 178 self.last = self.first.month_update(limit - 1) 179 180 self.calendar_start = self.calendar_start or self.first 181 self.calendar_end = self.calendar_end or self.last 182 self.duration = limit 183 184 def getIdentifier(self): 185 186 "Return a unique identifier to be used to refer to this view." 187 188 # NOTE: Nasty hack to get a unique identifier if no name is given. 189 190 return self.calendar_name or str(id(self)) 191 192 def getQualifiedParameterName(self, argname): 193 194 "Return the 'argname' qualified using the calendar name." 195 196 if self.calendar_name: 197 return getQualifiedParameterName(self.calendar_name, argname) 198 else: 199 return argname 200 201 def getDateQueryString(self, argname, date, prefix=1): 202 203 """ 204 Return a query string fragment for the given 'argname', referring to the 205 month given by the specified 'year_month' object, appropriate for this 206 calendar. 207 208 If 'prefix' is specified and set to a false value, the parameters in the 209 query string will not be calendar-specific, but could be used with the 210 summary action. 211 """ 212 213 suffixes = ["year", "month", "day"] 214 215 if date is not None: 216 args = [] 217 for suffix, value in zip(suffixes, date.as_tuple()): 218 suffixed_argname = "%s-%s" % (argname, suffix) 219 if prefix: 220 suffixed_argname = self.getQualifiedParameterName(suffixed_argname) 221 args.append("%s=%s" % (suffixed_argname, value)) 222 return "&".join(args) 223 else: 224 return "" 225 226 def getRawDateQueryString(self, argname, date, prefix=1): 227 228 """ 229 Return a query string fragment for the given 'argname', referring to the 230 date given by the specified 'date' value, appropriate for this 231 calendar. 232 233 If 'prefix' is specified and set to a false value, the parameters in the 234 query string will not be calendar-specific, but could be used with the 235 summary action. 236 """ 237 238 if date is not None: 239 if prefix: 240 argname = self.getQualifiedParameterName(argname) 241 return "%s=%s" % (argname, wikiutil.url_quote(date)) 242 else: 243 return "" 244 245 def getNavigationLink(self, start, end, mode=None, resolution=None, wider_start=None, wider_end=None): 246 247 """ 248 Return a query string fragment for navigation to a view showing months 249 from 'start' to 'end' inclusive, with the optional 'mode' indicating the 250 view style and the optional 'resolution' indicating the resolution of a 251 view, if configurable. 252 253 If the 'wider_start' and 'wider_end' arguments are given, parameters 254 indicating a wider calendar view (when returning from a day view, for 255 example) will be included in the link. 256 """ 257 258 return "%s&%s&%s=%s&%s=%s&%s&%s" % ( 259 self.getRawDateQueryString("start", start), 260 self.getRawDateQueryString("end", end), 261 self.getQualifiedParameterName("mode"), mode or self.mode, 262 self.getQualifiedParameterName("resolution"), resolution or self.resolution, 263 self.getRawDateQueryString("wider-start", wider_start), 264 self.getRawDateQueryString("wider-end", wider_end), 265 ) 266 267 def getUpdateLink(self, start, end, mode=None, resolution=None, wider_start=None, wider_end=None): 268 269 """ 270 Return a query string fragment for navigation to a view showing months 271 from 'start' to 'end' inclusive, with the optional 'mode' indicating the 272 view style and the optional 'resolution' indicating the resolution of a 273 view, if configurable. This link differs from the conventional 274 navigation link in that it is sufficient to activate the update action 275 and produce an updated region of the page without needing to locate and 276 process the page or any macro invocation. 277 278 If the 'wider_start' and 'wider_end' arguments are given, parameters 279 indicating a wider calendar view (when returning from a day view, for 280 example) will be included in the link. 281 """ 282 283 parameters = [ 284 self.getRawDateQueryString("start", start, 0), 285 self.getRawDateQueryString("end", end, 0), 286 self.category_name_parameters, 287 self.remote_source_parameters, 288 self.getRawDateQueryString("wider-start", wider_start, 0), 289 self.getRawDateQueryString("wider-end", wider_end, 0), 290 ] 291 292 pairs = [ 293 ("calendar", self.calendar_name or ""), 294 ("calendarstart", self.raw_calendar_start or ""), 295 ("calendarend", self.raw_calendar_end or ""), 296 ("mode", mode or self.mode), 297 ("resolution", resolution or self.resolution), 298 ("raw-resolution", self.raw_resolution), 299 ("parent", self.parent_name or ""), 300 ("template", self.template_name or ""), 301 ("names", self.name_usage), 302 ("map", self.map_name or ""), 303 ("search", self.search_pattern or ""), 304 ] 305 306 url = self.page.url(self.page.request, 307 "action=EventAggregatorUpdate&%s" % ( 308 "&".join([("%s=%s" % (key, wikiutil.url_quote(value))) for (key, value) in pairs] + parameters) 309 ), relative=True) 310 311 return "return replaceCalendar('EventAggregator-%s', '%s')" % (self.getIdentifier(), url) 312 313 def getNewEventLink(self, start): 314 315 """ 316 Return a query string activating the new event form, incorporating the 317 calendar parameters, specialising the form for the given 'start' date or 318 month. 319 """ 320 321 if start is not None: 322 details = start.as_tuple() 323 pairs = zip(["start-year=%d", "start-month=%d", "start-day=%d"], details) 324 args = [(param % value) for (param, value) in pairs] 325 args = "&".join(args) 326 else: 327 args = "" 328 329 # Prepare navigation details for the calendar shown with the new event 330 # form. 331 332 navigation_link = self.getNavigationLink( 333 self.calendar_start, self.calendar_end 334 ) 335 336 return "action=EventAggregatorNewEvent%s%s&template=%s&parent=%s&%s" % ( 337 args and "&%s" % args, 338 self.category_name_parameters and "&%s" % self.category_name_parameters, 339 self.template_name, self.parent_name or "", 340 navigation_link) 341 342 def getFullDateLabel(self, date): 343 return getFullDateLabel(self.page.request, date) 344 345 def getFullMonthLabel(self, year_month): 346 return getFullMonthLabel(self.page.request, year_month) 347 348 def getFullLabel(self, arg, resolution): 349 return resolution == "date" and self.getFullDateLabel(arg) or self.getFullMonthLabel(arg) 350 351 def _getCalendarPeriod(self, start_label, end_label, default_label): 352 353 """ 354 Return a label describing a calendar period in terms of the given 355 'start_label' and 'end_label', with the 'default_label' being used where 356 the supplied start and end labels fail to produce a meaningful label. 357 """ 358 359 output = [] 360 append = output.append 361 362 if start_label: 363 append(start_label) 364 if end_label and start_label != end_label: 365 if output: 366 append(" - ") 367 append(end_label) 368 return "".join(output) or default_label 369 370 def getCalendarPeriod(self): 371 372 "Return the period description for the shown calendar." 373 374 _ = self.page.request.getText 375 return self._getCalendarPeriod( 376 self.calendar_start and self.getFullLabel(self.calendar_start, self.resolution), 377 self.calendar_end and self.getFullLabel(self.calendar_end, self.resolution), 378 _("All events") 379 ) 380 381 def getOriginalCalendarPeriod(self): 382 383 "Return the period description for the originally specified calendar." 384 385 _ = self.page.request.getText 386 return self._getCalendarPeriod( 387 self.original_calendar_start and self.getFullLabel(self.original_calendar_start, self.raw_resolution), 388 self.original_calendar_end and self.getFullLabel(self.original_calendar_end, self.raw_resolution), 389 _("All events") 390 ) 391 392 def getRawCalendarPeriod(self): 393 394 "Return the raw period description for the calendar." 395 396 _ = self.page.request.getText 397 return self._getCalendarPeriod( 398 self.raw_calendar_start, 399 self.raw_calendar_end, 400 _("No period specified") 401 ) 402 403 def writeDownloadControls(self): 404 405 """ 406 Return a representation of the download controls, featuring links for 407 view, calendar and customised downloads and subscriptions. 408 """ 409 410 page = self.page 411 request = page.request 412 fmt = request.formatter 413 _ = request.getText 414 415 output = [] 416 append = output.append 417 418 # The full URL is needed for webcal links. 419 420 full_url = "%s%s" % (request.getBaseURL(), getPathInfo(request)) 421 422 # Generate the links. 423 424 download_dialogue_link = "action=EventAggregatorSummary&parent=%s&search=%s%s%s" % ( 425 self.parent_name or "", 426 self.search_pattern or "", 427 self.category_name_parameters and "&%s" % self.category_name_parameters, 428 self.remote_source_parameters and "&%s" % self.remote_source_parameters 429 ) 430 download_all_link = download_dialogue_link + "&doit=1" 431 download_link = download_all_link + ("&%s&%s" % ( 432 self.getDateQueryString("start", self.calendar_start, prefix=0), 433 self.getDateQueryString("end", self.calendar_end, prefix=0) 434 )) 435 436 # The entire calendar download uses the originally specified resolution 437 # of the calendar as does the dialogue. The other link uses the current 438 # resolution. 439 440 download_dialogue_link += "&resolution=%s" % self.raw_resolution 441 download_all_link += "&resolution=%s" % self.raw_resolution 442 download_link += "&resolution=%s" % self.resolution 443 444 # Subscription links just explicitly select the RSS format. 445 446 subscribe_dialogue_link = download_dialogue_link + "&format=RSS" 447 subscribe_all_link = download_all_link + "&format=RSS" 448 subscribe_link = download_link + "&format=RSS" 449 450 # Adjust the "download all" and "subscribe all" links if the calendar 451 # has an inherent period associated with it. 452 453 period_limits = [] 454 455 if self.raw_calendar_start: 456 period_limits.append("&%s" % 457 self.getRawDateQueryString("start", self.raw_calendar_start, prefix=0) 458 ) 459 if self.raw_calendar_end: 460 period_limits.append("&%s" % 461 self.getRawDateQueryString("end", self.raw_calendar_end, prefix=0) 462 ) 463 464 period_limits = "".join(period_limits) 465 466 download_dialogue_link += period_limits 467 download_all_link += period_limits 468 subscribe_dialogue_link += period_limits 469 subscribe_all_link += period_limits 470 471 # Pop-up descriptions of the downloadable calendars. 472 473 shown_calendar_period = self.getCalendarPeriod() 474 original_calendar_period = self.getOriginalCalendarPeriod() 475 raw_calendar_period = self.getRawCalendarPeriod() 476 477 # Write the controls. 478 479 # Download controls. 480 481 controls_target = "%s-controls" % self.getIdentifier() 482 483 append(fmt.div(on=1, css_class="event-download-controls", id=controls_target)) 484 485 download_target = "%s-download" % self.getIdentifier() 486 487 append(fmt.span(on=1, css_class="event-download", id=download_target)) 488 append(linkToPage(request, page, _("Download..."), anchor=download_target)) 489 append(fmt.div(on=1, css_class="event-download-popup")) 490 491 append(fmt.div(on=1, css_class="event-download-item")) 492 append(fmt.span(on=1, css_class="event-download-types")) 493 append(fmt.span(on=1, css_class="event-download-webcal")) 494 append(linkToResource(full_url.replace("http", "webcal", 1), request, _("webcal"), download_link)) 495 append(fmt.span(on=0)) 496 append(fmt.span(on=1, css_class="event-download-http")) 497 append(linkToPage(request, page, _("http"), download_link, title=_("Download this view in the browser"))) 498 append(fmt.span(on=0)) 499 append(fmt.span(on=0)) # end types 500 append(fmt.span(on=1, css_class="event-download-label")) 501 append(fmt.text(_("Download this view"))) 502 append(fmt.span(on=0)) # end label 503 append(fmt.span(on=1, css_class="event-download-period")) 504 append(fmt.text(shown_calendar_period)) 505 append(fmt.span(on=0)) 506 append(fmt.div(on=0)) 507 508 append(fmt.div(on=1, css_class="event-download-item")) 509 append(fmt.span(on=1, css_class="event-download-types")) 510 append(fmt.span(on=1, css_class="event-download-webcal")) 511 append(linkToResource(full_url.replace("http", "webcal", 1), request, _("webcal"), download_all_link)) 512 append(fmt.span(on=0)) 513 append(fmt.span(on=1, css_class="event-download-http")) 514 append(linkToPage(request, page, _("http"), download_all_link, title=_("Download this calendar in the browser"))) 515 append(fmt.span(on=0)) 516 append(fmt.span(on=0)) # end types 517 append(fmt.span(on=1, css_class="event-download-label")) 518 append(fmt.text(_("Download this calendar"))) 519 append(fmt.span(on=0)) # end label 520 append(fmt.span(on=1, css_class="event-download-period")) 521 append(fmt.text(original_calendar_period)) 522 append(fmt.span(on=0)) 523 append(fmt.span(on=1, css_class="event-download-period-raw")) 524 append(fmt.text(raw_calendar_period)) 525 append(fmt.span(on=0)) 526 append(fmt.div(on=0)) 527 528 append(fmt.div(on=1, css_class="event-download-item")) 529 append(fmt.span(on=1, css_class="event-download-link")) 530 append(linkToPage(request, page, _("Edit download options..."), download_dialogue_link)) 531 append(fmt.span(on=0)) # end label 532 append(fmt.div(on=0)) 533 534 append(fmt.div(on=1, css_class="event-download-item focus-only")) 535 append(fmt.span(on=1, css_class="event-download-link")) 536 append(linkToPage(request, page, _("Cancel"), anchor=controls_target)) 537 append(fmt.span(on=0)) # end label 538 append(fmt.div(on=0)) 539 540 append(fmt.div(on=0)) # end of pop-up 541 append(fmt.span(on=0)) # end of download 542 543 # Subscription controls. 544 545 subscribe_target = "%s-subscribe" % self.getIdentifier() 546 547 append(fmt.span(on=1, css_class="event-download", id=subscribe_target)) 548 append(linkToPage(request, page, _("Subscribe..."), anchor=subscribe_target)) 549 append(fmt.div(on=1, css_class="event-download-popup")) 550 551 append(fmt.div(on=1, css_class="event-download-item")) 552 append(fmt.span(on=1, css_class="event-download-label")) 553 append(linkToPage(request, page, _("Subscribe to this view"), subscribe_link)) 554 append(fmt.span(on=0)) # end label 555 append(fmt.span(on=1, css_class="event-download-period")) 556 append(fmt.text(shown_calendar_period)) 557 append(fmt.span(on=0)) 558 append(fmt.div(on=0)) 559 560 append(fmt.div(on=1, css_class="event-download-item")) 561 append(fmt.span(on=1, css_class="event-download-label")) 562 append(linkToPage(request, page, _("Subscribe to this calendar"), subscribe_all_link)) 563 append(fmt.span(on=0)) # end label 564 append(fmt.span(on=1, css_class="event-download-period")) 565 append(fmt.text(original_calendar_period)) 566 append(fmt.span(on=0)) 567 append(fmt.span(on=1, css_class="event-download-period-raw")) 568 append(fmt.text(raw_calendar_period)) 569 append(fmt.span(on=0)) 570 append(fmt.div(on=0)) 571 572 append(fmt.div(on=1, css_class="event-download-item")) 573 append(fmt.span(on=1, css_class="event-download-link")) 574 append(linkToPage(request, page, _("Edit subscription options..."), subscribe_dialogue_link)) 575 append(fmt.span(on=0)) # end label 576 append(fmt.div(on=0)) 577 578 append(fmt.div(on=1, css_class="event-download-item focus-only")) 579 append(fmt.span(on=1, css_class="event-download-link")) 580 append(linkToPage(request, page, _("Cancel"), anchor=controls_target)) 581 append(fmt.span(on=0)) # end label 582 append(fmt.div(on=0)) 583 584 append(fmt.div(on=0)) # end of pop-up 585 append(fmt.span(on=0)) # end of download 586 587 append(fmt.div(on=0)) # end of controls 588 589 return "".join(output) 590 591 def writeViewControls(self): 592 593 """ 594 Return a representation of the view mode controls, permitting viewing of 595 aggregated events in calendar, list or table form. 596 """ 597 598 page = self.page 599 request = page.request 600 fmt = request.formatter 601 _ = request.getText 602 603 output = [] 604 append = output.append 605 606 # For day view links to other views, the wider view parameters should 607 # be used in order to be able to return to those other views. 608 609 specific_start = self.calendar_start 610 specific_end = self.calendar_end 611 612 multiday = self.resolution == "date" and len(specific_start.days_until(specific_end)) > 1 613 614 start = self.wider_calendar_start or self.original_calendar_start and specific_start 615 end = self.wider_calendar_end or self.original_calendar_end and specific_end 616 617 help_page = Page(request, "HelpOnEventAggregator") 618 619 calendar_link = self.getNavigationLink(start and start.as_month(), end and end.as_month(), "calendar", "month") 620 calendar_update_link = self.getUpdateLink(start and start.as_month(), end and end.as_month(), "calendar", "month") 621 list_link = self.getNavigationLink(start, end, "list", "month") 622 list_update_link = self.getUpdateLink(start, end, "list", "month") 623 table_link = self.getNavigationLink(start, end, "table", "month") 624 table_update_link = self.getUpdateLink(start, end, "table", "month") 625 map_link = self.getNavigationLink(start, end, "map", "month") 626 map_update_link = self.getUpdateLink(start, end, "map", "month") 627 628 # Specific links permit date-level navigation. 629 630 specific_day_link = self.getNavigationLink(specific_start, specific_end, "day", wider_start=start, wider_end=end) 631 specific_day_update_link = self.getUpdateLink(specific_start, specific_end, "day", wider_start=start, wider_end=end) 632 specific_list_link = self.getNavigationLink(specific_start, specific_end, "list", wider_start=start, wider_end=end) 633 specific_list_update_link = self.getUpdateLink(specific_start, specific_end, "list", wider_start=start, wider_end=end) 634 specific_table_link = self.getNavigationLink(specific_start, specific_end, "table", wider_start=start, wider_end=end) 635 specific_table_update_link = self.getUpdateLink(specific_start, specific_end, "table", wider_start=start, wider_end=end) 636 specific_map_link = self.getNavigationLink(specific_start, specific_end, "map", wider_start=start, wider_end=end) 637 specific_map_update_link = self.getUpdateLink(specific_start, specific_end, "map", wider_start=start, wider_end=end) 638 639 new_event_link = self.getNewEventLink(start) 640 641 # Write the controls. 642 643 append(fmt.div(on=1, css_class="event-view-controls")) 644 645 append(fmt.span(on=1, css_class="event-view")) 646 append(linkToPage(request, help_page, _("Help"))) 647 append(fmt.span(on=0)) 648 649 append(fmt.span(on=1, css_class="event-view")) 650 append(linkToPage(request, page, _("New event"), new_event_link)) 651 append(fmt.span(on=0)) 652 653 if self.mode != "calendar": 654 view_label = self.resolution == "date" and \ 655 (multiday and _("View days in calendar") or _("View day in calendar")) or \ 656 _("View as calendar") 657 append(fmt.span(on=1, css_class="event-view")) 658 append(linkToPage(request, page, view_label, calendar_link, onclick=calendar_update_link)) 659 append(fmt.span(on=0)) 660 661 if self.resolution == "date" and self.mode != "day": 662 view_label = multiday and _("View days as calendar") or _("View day as calendar") 663 append(fmt.span(on=1, css_class="event-view")) 664 append(linkToPage(request, page, view_label, specific_day_link, onclick=specific_day_update_link)) 665 append(fmt.span(on=0)) 666 667 if self.resolution != "date" and self.mode != "list" or self.resolution == "date": 668 view_label = self.resolution == "date" and \ 669 (multiday and _("View days in list") or _("View day in list")) or \ 670 _("View as list") 671 append(fmt.span(on=1, css_class="event-view")) 672 append(linkToPage(request, page, view_label, list_link, onclick=list_update_link)) 673 append(fmt.span(on=0)) 674 675 if self.resolution == "date" and self.mode != "list": 676 view_label = multiday and _("View days as list") or _("View day as list") 677 append(fmt.span(on=1, css_class="event-view")) 678 append(linkToPage(request, page, view_label, specific_list_link, onclick=specific_list_update_link)) 679 append(fmt.span(on=0)) 680 681 if self.resolution != "date" and self.mode != "table" or self.resolution == "date": 682 view_label = self.resolution == "date" and \ 683 (multiday and _("View days in table") or _("View day in table")) or \ 684 _("View as table") 685 append(fmt.span(on=1, css_class="event-view")) 686 append(linkToPage(request, page, view_label, table_link, onclick=table_update_link)) 687 append(fmt.span(on=0)) 688 689 if self.resolution == "date" and self.mode != "table": 690 view_label = multiday and _("View days as table") or _("View day as table") 691 append(fmt.span(on=1, css_class="event-view")) 692 append(linkToPage(request, page, view_label, specific_table_link, onclick=specific_table_update_link)) 693 append(fmt.span(on=0)) 694 695 if self.map_name: 696 if self.resolution != "date" and self.mode != "map" or self.resolution == "date": 697 view_label = self.resolution == "date" and \ 698 (multiday and _("View days in map") or _("View day in map")) or \ 699 _("View as map") 700 append(fmt.span(on=1, css_class="event-view")) 701 append(linkToPage(request, page, view_label, map_link, onclick=map_update_link)) 702 append(fmt.span(on=0)) 703 704 if self.resolution == "date" and self.mode != "map": 705 view_label = multiday and _("View days as map") or _("View day as map") 706 append(fmt.span(on=1, css_class="event-view")) 707 append(linkToPage(request, page, view_label, specific_map_link, onclick=specific_map_update_link)) 708 append(fmt.span(on=0)) 709 710 append(fmt.div(on=0)) 711 712 return "".join(output) 713 714 def writeMapHeading(self): 715 716 """ 717 Return the calendar heading for the current calendar, providing links 718 permitting navigation to other periods. 719 """ 720 721 label = self.getCalendarPeriod() 722 723 if self.raw_calendar_start is None or self.raw_calendar_end is None: 724 fmt = self.page.request.formatter 725 output = [] 726 append = output.append 727 append(fmt.span(on=1)) 728 append(fmt.text(label)) 729 append(fmt.span(on=0)) 730 return "".join(output) 731 else: 732 return self._writeCalendarHeading(label, self.calendar_start, self.calendar_end) 733 734 def writeDateHeading(self, date): 735 if isinstance(date, Date): 736 return self.writeDayHeading(date) 737 else: 738 return self.writeMonthHeading(date) 739 740 def writeMonthHeading(self, year_month): 741 742 """ 743 Return the calendar heading for the given 'year_month' (a Month object) 744 providing links permitting navigation to other months. 745 """ 746 747 full_month_label = self.getFullMonthLabel(year_month) 748 end_month = year_month.update(self.duration - 1) 749 return self._writeCalendarHeading(full_month_label, year_month, end_month) 750 751 def writeDayHeading(self, date): 752 753 """ 754 Return the calendar heading for the given 'date' (a Date object) 755 providing links permitting navigation to other dates. 756 """ 757 758 full_date_label = self.getFullDateLabel(date) 759 end_date = date.update(self.duration - 1) 760 return self._writeCalendarHeading(full_date_label, date, end_date) 761 762 def writeCalendarNavigation(self): 763 764 "Return navigation links for a calendar." 765 766 page = self.page 767 request = page.request 768 fmt = request.formatter 769 _ = request.getText 770 771 output = [] 772 append = output.append 773 774 # Links to the previous set of months and to a calendar shifted 775 # back one month. 776 777 previous_set_link = self.getNavigationLink( 778 self.previous_set_start, self.previous_set_end 779 ) 780 previous_link = self.getNavigationLink( 781 self.previous_start, self.previous_end 782 ) 783 previous_set_update_link = self.getUpdateLink( 784 self.previous_set_start, self.previous_set_end 785 ) 786 previous_update_link = self.getUpdateLink( 787 self.previous_start, self.previous_end 788 ) 789 790 # Links to the next set of months and to a calendar shifted 791 # forward one month. 792 793 next_set_link = self.getNavigationLink( 794 self.next_set_start, self.next_set_end 795 ) 796 next_link = self.getNavigationLink( 797 self.next_start, self.next_end 798 ) 799 next_set_update_link = self.getUpdateLink( 800 self.next_set_start, self.next_set_end 801 ) 802 next_update_link = self.getUpdateLink( 803 self.next_start, self.next_end 804 ) 805 806 append(fmt.div(on=1, css_class="event-calendar-navigation")) 807 808 append(fmt.span(on=1, css_class="previous")) 809 append(linkToPage(request, page, "<<", previous_set_link, onclick=previous_set_update_link, title=_("Previous set"))) 810 append(fmt.text(" ")) 811 append(linkToPage(request, page, "<", previous_link, onclick=previous_update_link, title=_("Previous"))) 812 append(fmt.span(on=0)) 813 814 append(fmt.span(on=1, css_class="next")) 815 append(linkToPage(request, page, ">", next_link, onclick=next_update_link, title=_("Next"))) 816 append(fmt.text(" ")) 817 append(linkToPage(request, page, ">>", next_set_link, onclick=next_set_update_link, title=_("Next set"))) 818 append(fmt.span(on=0)) 819 820 append(fmt.div(on=0)) 821 822 return "".join(output) 823 824 def _writeCalendarHeading(self, label, start, end): 825 826 """ 827 Write a calendar heading providing links permitting navigation to other 828 periods, using the given 'label' along with the 'start' and 'end' dates 829 to provide a link to a particular period. 830 """ 831 832 page = self.page 833 request = page.request 834 fmt = request.formatter 835 _ = request.getText 836 837 output = [] 838 append = output.append 839 840 if not self.showing_everything: 841 842 # A link leading to this date being at the top of the calendar. 843 844 date_link = self.getNavigationLink(start, end) 845 date_update_link = self.getUpdateLink(start, end) 846 847 append(linkToPage(request, page, label, date_link, onclick=date_update_link, title=_("Show this period first"))) 848 849 else: 850 append(fmt.text(label)) 851 852 return "".join(output) 853 854 def writeDayNumberHeading(self, date, busy): 855 856 """ 857 Return a link for the given 'date' which will activate the new event 858 action for the given day. If 'busy' is given as a true value, the 859 heading will be marked as busy. 860 """ 861 862 page = self.page 863 request = page.request 864 fmt = request.formatter 865 _ = request.getText 866 867 output = [] 868 append = output.append 869 870 year, month, day = date.as_tuple() 871 new_event_link = self.getNewEventLink(date) 872 873 # Prepare a link to the day view for this day. 874 875 day_view_link = self.getNavigationLink(date, date, "day", "date", self.calendar_start, self.calendar_end) 876 day_view_update_link = self.getUpdateLink(date, date, "day", "date", self.calendar_start, self.calendar_end) 877 878 # Output the heading. 879 880 day_target = "%s-day-%d" % (self.getIdentifier(), day) 881 day_menu_target = "%s-menu" % day_target 882 883 today_attr = date == getCurrentDate() and "event-day-current" or "" 884 885 append(fmt.rawHTML("<th class='event-day-heading event-day-%s %s' colspan='3' axis='day' id='%s'>" % ( 886 busy and "busy" or "empty", escattr(today_attr), escattr(day_target)))) 887 888 # Output the number and pop-up menu. 889 890 append(fmt.div(on=1, css_class="event-day-box", id=day_menu_target)) 891 892 append(fmt.span(on=1, css_class="event-day-number-popup")) 893 append(fmt.span(on=1, css_class="event-day-number-link")) 894 append(linkToPage(request, page, _("View day"), day_view_link, onclick=day_view_update_link)) 895 append(fmt.span(on=0)) 896 append(fmt.span(on=1, css_class="event-day-number-link")) 897 append(linkToPage(request, page, _("New event on this day"), new_event_link)) 898 append(fmt.span(on=0)) 899 append(fmt.span(on=0)) 900 901 # Link the number to the day view. 902 903 append(fmt.span(on=1, css_class="event-day-number")) 904 append(linkToPage(request, page, unicode(day), anchor=day_menu_target, title=_("View day options"))) 905 append(fmt.span(on=0)) 906 907 append(fmt.div(on=0)) 908 909 # End of heading. 910 911 append(fmt.rawHTML("</th>")) 912 913 return "".join(output) 914 915 # Common layout methods. 916 917 def getEventStyle(self, colour_seed): 918 919 "Generate colour style information using the given 'colour_seed'." 920 921 bg = getColour(colour_seed) 922 fg = getBlackOrWhite(bg) 923 return "background-color: rgb(%d, %d, %d); color: rgb(%d, %d, %d);" % (bg + fg) 924 925 def writeEventSummaryBox(self, event): 926 927 "Return an event summary box linking to the given 'event'." 928 929 page = self.page 930 request = page.request 931 fmt = request.formatter 932 933 output = [] 934 append = output.append 935 936 event_details = event.getDetails() 937 event_summary = event.getSummary(self.parent_name) 938 939 is_ambiguous = event.as_timespan().ambiguous() 940 style = self.getEventStyle(event_summary) 941 942 # The event box contains the summary, alongside 943 # other elements. 944 945 append(fmt.div(on=1, css_class="event-summary-box")) 946 append(fmt.div(on=1, css_class="event-summary", style=style)) 947 948 if is_ambiguous: 949 append(fmt.icon("/!\\")) 950 951 append(event.linkToEvent(request, event_summary)) 952 append(fmt.div(on=0)) 953 954 # Add a pop-up element for long summaries. 955 956 append(fmt.div(on=1, css_class="event-summary-popup", style=style)) 957 958 if is_ambiguous: 959 append(fmt.icon("/!\\")) 960 961 append(event.linkToEvent(request, event_summary)) 962 append(fmt.div(on=0)) 963 964 append(fmt.div(on=0)) 965 966 return "".join(output) 967 968 # Calendar layout methods. 969 970 def writeMonthTableHeading(self, year_month): 971 page = self.page 972 fmt = page.request.formatter 973 974 output = [] 975 append = output.append 976 977 # Using a caption for accessibility reasons. 978 979 append(fmt.rawHTML('<caption class="event-month-heading">')) 980 append(self.writeMonthHeading(year_month)) 981 append(fmt.rawHTML("</caption>")) 982 983 return "".join(output) 984 985 def writeWeekdayHeadings(self): 986 page = self.page 987 request = page.request 988 fmt = request.formatter 989 _ = request.getText 990 991 output = [] 992 append = output.append 993 994 append(fmt.table_row(on=1)) 995 996 for weekday in range(0, 7): 997 append(fmt.rawHTML(u"<th class='event-weekday-heading' colspan='3' abbr='%s' scope='row'>" % 998 escattr(_(getVerboseDayLabel(weekday))))) 999 append(fmt.text(_(getDayLabel(weekday)))) 1000 append(fmt.rawHTML("</th>")) 1001 1002 append(fmt.table_row(on=0)) 1003 return "".join(output) 1004 1005 def writeDayNumbers(self, first_day, number_of_days, month, coverage): 1006 page = self.page 1007 fmt = page.request.formatter 1008 1009 output = [] 1010 append = output.append 1011 1012 append(fmt.table_row(on=1)) 1013 1014 for weekday in range(0, 7): 1015 day = first_day + weekday 1016 date = month.as_date(day) 1017 1018 # Output out-of-month days. 1019 1020 if day < 1 or day > number_of_days: 1021 append(fmt.table_cell(on=1, 1022 attrs={"class" : "event-day-heading event-day-excluded", "colspan" : "3"})) 1023 append(fmt.table_cell(on=0)) 1024 1025 # Output normal days. 1026 1027 else: 1028 # Output the day heading, making a link to a new event 1029 # action. 1030 1031 append(self.writeDayNumberHeading(date, date in coverage)) 1032 1033 # End of day numbers. 1034 1035 append(fmt.table_row(on=0)) 1036 return "".join(output) 1037 1038 def writeEmptyWeek(self, first_day, number_of_days, month): 1039 page = self.page 1040 fmt = page.request.formatter 1041 1042 output = [] 1043 append = output.append 1044 1045 append(fmt.table_row(on=1)) 1046 1047 for weekday in range(0, 7): 1048 day = first_day + weekday 1049 date = month.as_date(day) 1050 1051 today_attr = date == getCurrentDate() and "event-day-current" or "" 1052 1053 # Output out-of-month days. 1054 1055 if day < 1 or day > number_of_days: 1056 append(fmt.table_cell(on=1, 1057 attrs={"class" : "event-day-content event-day-excluded %s" % today_attr, "colspan" : "3"})) 1058 append(fmt.table_cell(on=0)) 1059 1060 # Output empty days. 1061 1062 else: 1063 append(fmt.table_cell(on=1, 1064 attrs={"class" : "event-day-content event-day-empty %s" % today_attr, "colspan" : "3"})) 1065 1066 append(fmt.table_row(on=0)) 1067 return "".join(output) 1068 1069 def writeWeekSlots(self, first_day, number_of_days, month, week_end, week_slots): 1070 output = [] 1071 append = output.append 1072 1073 locations = week_slots.keys() 1074 locations.sort(sort_none_first) 1075 1076 # Visit each slot corresponding to a location (or no location). 1077 1078 for location in locations: 1079 1080 # Visit each coverage span, presenting the events in the span. 1081 1082 for events in week_slots[location]: 1083 1084 # Output each set. 1085 1086 append(self.writeWeekSlot(first_day, number_of_days, month, week_end, events)) 1087 1088 # Add a spacer. 1089 1090 append(self.writeWeekSpacer(first_day, number_of_days, month)) 1091 1092 return "".join(output) 1093 1094 def writeWeekSlot(self, first_day, number_of_days, month, week_end, events): 1095 page = self.page 1096 request = page.request 1097 fmt = request.formatter 1098 1099 output = [] 1100 append = output.append 1101 1102 append(fmt.table_row(on=1)) 1103 1104 # Then, output day details. 1105 1106 for weekday in range(0, 7): 1107 day = first_day + weekday 1108 date = month.as_date(day) 1109 1110 # Skip out-of-month days. 1111 1112 if day < 1 or day > number_of_days: 1113 append(fmt.table_cell(on=1, 1114 attrs={"class" : "event-day-content event-day-excluded", "colspan" : "3"})) 1115 append(fmt.table_cell(on=0)) 1116 continue 1117 1118 # Output the day. 1119 # Where a day does not contain an event, a single cell is used. 1120 # Otherwise, multiple cells are used to provide space before, during 1121 # and after events. 1122 1123 day_target = "%s-day-%d" % (self.getIdentifier(), day) 1124 today_attr = date == getCurrentDate() and "event-day-current" or "" 1125 1126 if date not in events: 1127 append(fmt.rawHTML(u"<td class='event-day-content event-day-empty %s' colspan='3' headers='%s'>" % ( 1128 escattr(today_attr), escattr(day_target)))) 1129 1130 # Get event details for the current day. 1131 1132 for event in events: 1133 event_details = event.getDetails() 1134 1135 if date not in event: 1136 continue 1137 1138 # Get basic properties of the event. 1139 1140 starts_today = event_details["start"] == date 1141 ends_today = event_details["end"] == date 1142 event_summary = event.getSummary(self.parent_name) 1143 1144 style = self.getEventStyle(event_summary) 1145 1146 # Determine if the event name should be shown. 1147 1148 start_of_period = starts_today or weekday == 0 or day == 1 1149 1150 if self.name_usage == "daily" or start_of_period: 1151 hide_text = 0 1152 else: 1153 hide_text = 1 1154 1155 # Output start of day gap and determine whether 1156 # any event content should be explicitly output 1157 # for this day. 1158 1159 if starts_today: 1160 1161 # Single day events... 1162 1163 if ends_today: 1164 colspan = 3 1165 event_day_type = "event-day-single" 1166 1167 # Events starting today... 1168 1169 else: 1170 append(fmt.rawHTML(u"<td class='event-day-start-gap %s' headers='%s'>" % ( 1171 escattr(today_attr), escattr(day_target)))) 1172 append(fmt.table_cell(on=0)) 1173 1174 # Calculate the span of this cell. 1175 # Events whose names appear on every day... 1176 1177 if self.name_usage == "daily": 1178 colspan = 2 1179 event_day_type = "event-day-starting" 1180 1181 # Events whose names appear once per week... 1182 1183 else: 1184 if event_details["end"] <= week_end: 1185 event_length = event_details["end"].day() - day + 1 1186 colspan = (event_length - 2) * 3 + 4 1187 else: 1188 event_length = week_end.day() - day + 1 1189 colspan = (event_length - 1) * 3 + 2 1190 1191 event_day_type = "event-day-multiple" 1192 1193 # Events continuing from a previous week... 1194 1195 elif start_of_period: 1196 1197 # End of continuing event... 1198 1199 if ends_today: 1200 colspan = 2 1201 event_day_type = "event-day-ending" 1202 1203 # Events continuing for at least one more day... 1204 1205 else: 1206 1207 # Calculate the span of this cell. 1208 # Events whose names appear on every day... 1209 1210 if self.name_usage == "daily": 1211 colspan = 3 1212 event_day_type = "event-day-full" 1213 1214 # Events whose names appear once per week... 1215 1216 else: 1217 if event_details["end"] <= week_end: 1218 event_length = event_details["end"].day() - day + 1 1219 colspan = (event_length - 1) * 3 + 2 1220 else: 1221 event_length = week_end.day() - day + 1 1222 colspan = event_length * 3 1223 1224 event_day_type = "event-day-multiple" 1225 1226 # Continuing events whose names appear on every day... 1227 1228 elif self.name_usage == "daily": 1229 if ends_today: 1230 colspan = 2 1231 event_day_type = "event-day-ending" 1232 else: 1233 colspan = 3 1234 event_day_type = "event-day-full" 1235 1236 # Continuing events whose names appear once per week... 1237 1238 else: 1239 colspan = None 1240 1241 # Output the main content only if it is not 1242 # continuing from a previous day. 1243 1244 if colspan is not None: 1245 1246 # Colour the cell for continuing events. 1247 1248 attrs={ 1249 "class" : escattr("event-day-content event-day-busy %s %s" % (event_day_type, today_attr)), 1250 "colspan" : str(colspan), 1251 "headers" : escattr(day_target), 1252 } 1253 1254 if not (starts_today and ends_today): 1255 attrs["style"] = style 1256 1257 append(fmt.rawHTML(u"<td class='%(class)s' colspan='%(colspan)s' headers='%(headers)s'>" % attrs)) 1258 1259 # Output the event. 1260 1261 if starts_today and ends_today or not hide_text: 1262 append(self.writeEventSummaryBox(event)) 1263 1264 append(fmt.table_cell(on=0)) 1265 1266 # Output end of day gap. 1267 1268 if ends_today and not starts_today: 1269 append(fmt.rawHTML("<td class='event-day-end-gap %s' headers='%s'>" % (escattr(today_attr), escattr(day_target)))) 1270 append(fmt.table_cell(on=0)) 1271 1272 # End of set. 1273 1274 append(fmt.table_row(on=0)) 1275 return "".join(output) 1276 1277 def writeWeekSpacer(self, first_day, number_of_days, month): 1278 page = self.page 1279 fmt = page.request.formatter 1280 1281 output = [] 1282 append = output.append 1283 1284 append(fmt.table_row(on=1)) 1285 1286 for weekday in range(0, 7): 1287 day = first_day + weekday 1288 date = month.as_date(day) 1289 today_attr = date == getCurrentDate() and "event-day-current" or "" 1290 1291 css_classes = "event-day-spacer %s" % today_attr 1292 1293 # Skip out-of-month days. 1294 1295 if day < 1 or day > number_of_days: 1296 css_classes += " event-day-excluded" 1297 1298 append(fmt.table_cell(on=1, attrs={"class" : css_classes, "colspan" : "3"})) 1299 append(fmt.table_cell(on=0)) 1300 1301 append(fmt.table_row(on=0)) 1302 return "".join(output) 1303 1304 # Day layout methods. 1305 1306 def writeDayTableHeading(self, date, colspan=1): 1307 page = self.page 1308 fmt = page.request.formatter 1309 1310 output = [] 1311 append = output.append 1312 1313 # Using a caption for accessibility reasons. 1314 1315 append(fmt.rawHTML('<caption class="event-full-day-heading">')) 1316 append(self.writeDayHeading(date)) 1317 append(fmt.rawHTML("</caption>")) 1318 1319 return "".join(output) 1320 1321 def writeEmptyDay(self, date): 1322 page = self.page 1323 fmt = page.request.formatter 1324 1325 output = [] 1326 append = output.append 1327 1328 append(fmt.table_row(on=1)) 1329 1330 append(fmt.table_cell(on=1, 1331 attrs={"class" : "event-day-content event-day-empty"})) 1332 1333 append(fmt.table_row(on=0)) 1334 return "".join(output) 1335 1336 def writeDaySlots(self, date, full_coverage, day_slots): 1337 1338 """ 1339 Given a 'date', non-empty 'full_coverage' for the day concerned, and a 1340 non-empty mapping of 'day_slots' (from locations to event collections), 1341 output the day slots for the day. 1342 """ 1343 1344 page = self.page 1345 fmt = page.request.formatter 1346 1347 output = [] 1348 append = output.append 1349 1350 locations = day_slots.keys() 1351 locations.sort(sort_none_first) 1352 1353 # Traverse the time scale of the full coverage, visiting each slot to 1354 # determine whether it provides content for each period. 1355 1356 scale = getCoverageScale(full_coverage) 1357 1358 # Define a mapping of events to rowspans. 1359 1360 rowspans = {} 1361 1362 # Populate each period with event details, recording how many periods 1363 # each event populates. 1364 1365 day_rows = [] 1366 1367 for period, limit, start_times, end_times in scale: 1368 1369 # Ignore timespans before this day. 1370 1371 if period != date: 1372 continue 1373 1374 # Visit each slot corresponding to a location (or no location). 1375 1376 day_row = [] 1377 1378 for location in locations: 1379 1380 # Visit each coverage span, presenting the events in the span. 1381 1382 for events in day_slots[location]: 1383 event = self.getActiveEvent(period, events) 1384 if event is not None: 1385 if not rowspans.has_key(event): 1386 rowspans[event] = 1 1387 else: 1388 rowspans[event] += 1 1389 day_row.append((location, event)) 1390 1391 day_rows.append((period, day_row, start_times, end_times)) 1392 1393 # Output the locations. 1394 1395 append(fmt.table_row(on=1)) 1396 1397 # Add a spacer. 1398 1399 append(self.writeDaySpacer(colspan=2, cls="location")) 1400 1401 for location in locations: 1402 1403 # Add spacers to the column spans. 1404 1405 columns = len(day_slots[location]) * 2 - 1 1406 append(fmt.table_cell(on=1, attrs={"class" : "event-location-heading", "colspan" : str(columns)})) 1407 append(fmt.text(location or "")) 1408 append(fmt.table_cell(on=0)) 1409 1410 # Add a trailing spacer. 1411 1412 append(self.writeDaySpacer(cls="location")) 1413 1414 append(fmt.table_row(on=0)) 1415 1416 # Output the periods with event details. 1417 1418 last_period = period = None 1419 events_written = set() 1420 1421 for period, day_row, start_times, end_times in day_rows: 1422 1423 # Write a heading describing the time. 1424 1425 append(fmt.table_row(on=1)) 1426 1427 # Show times only for distinct periods. 1428 1429 if not last_period or period.start != last_period.start: 1430 append(self.writeDayScaleHeading(start_times)) 1431 else: 1432 append(self.writeDayScaleHeading([])) 1433 1434 append(self.writeDaySpacer()) 1435 1436 # Visit each slot corresponding to a location (or no location). 1437 1438 for location, event in day_row: 1439 1440 # Output each location slot's contribution. 1441 1442 if event is None or event not in events_written: 1443 append(self.writeDaySlot(period, event, event is None and 1 or rowspans[event])) 1444 if event is not None: 1445 events_written.add(event) 1446 1447 # Add a trailing spacer. 1448 1449 append(self.writeDaySpacer()) 1450 1451 append(fmt.table_row(on=0)) 1452 1453 last_period = period 1454 1455 # Write a final time heading if the last period ends in the current day. 1456 1457 if period is not None: 1458 if period.end == date: 1459 append(fmt.table_row(on=1)) 1460 append(self.writeDayScaleHeading(end_times)) 1461 1462 for slot in day_row: 1463 append(self.writeDaySpacer()) 1464 append(self.writeEmptyDaySlot()) 1465 1466 append(fmt.table_row(on=0)) 1467 1468 return "".join(output) 1469 1470 def writeDayScaleHeading(self, times): 1471 page = self.page 1472 fmt = page.request.formatter 1473 1474 output = [] 1475 append = output.append 1476 1477 append(fmt.table_cell(on=1, attrs={"class" : "event-scale-heading"})) 1478 1479 first = 1 1480 for t in times: 1481 if isinstance(t, DateTime): 1482 if not first: 1483 append(fmt.linebreak(0)) 1484 append(fmt.text(t.time_string())) 1485 first = 0 1486 1487 append(fmt.table_cell(on=0)) 1488 1489 return "".join(output) 1490 1491 def getActiveEvent(self, period, events): 1492 for event in events: 1493 if period not in event: 1494 continue 1495 return event 1496 else: 1497 return None 1498 1499 def writeDaySlot(self, period, event, rowspan): 1500 page = self.page 1501 fmt = page.request.formatter 1502 1503 output = [] 1504 append = output.append 1505 1506 if event is not None: 1507 event_summary = event.getSummary(self.parent_name) 1508 style = self.getEventStyle(event_summary) 1509 1510 append(fmt.table_cell(on=1, attrs={ 1511 "class" : "event-timespan-content event-timespan-busy", 1512 "style" : style, 1513 "rowspan" : str(rowspan) 1514 })) 1515 append(self.writeEventSummaryBox(event)) 1516 append(fmt.table_cell(on=0)) 1517 else: 1518 append(self.writeEmptyDaySlot()) 1519 1520 return "".join(output) 1521 1522 def writeEmptyDaySlot(self): 1523 page = self.page 1524 fmt = page.request.formatter 1525 1526 output = [] 1527 append = output.append 1528 1529 append(fmt.table_cell(on=1, 1530 attrs={"class" : "event-timespan-content event-timespan-empty"})) 1531 append(fmt.table_cell(on=0)) 1532 1533 return "".join(output) 1534 1535 def writeDaySpacer(self, colspan=1, cls="timespan"): 1536 page = self.page 1537 fmt = page.request.formatter 1538 1539 output = [] 1540 append = output.append 1541 1542 append(fmt.table_cell(on=1, attrs={ 1543 "class" : "event-%s-spacer" % cls, 1544 "colspan" : str(colspan)})) 1545 append(fmt.table_cell(on=0)) 1546 return "".join(output) 1547 1548 # Map layout methods. 1549 1550 def writeMapTableHeading(self): 1551 page = self.page 1552 fmt = page.request.formatter 1553 1554 output = [] 1555 append = output.append 1556 1557 # Using a caption for accessibility reasons. 1558 1559 append(fmt.rawHTML('<caption class="event-map-heading">')) 1560 append(self.writeMapHeading()) 1561 append(fmt.rawHTML("</caption>")) 1562 1563 return "".join(output) 1564 1565 def showDictError(self, text, pagename): 1566 page = self.page 1567 request = page.request 1568 fmt = request.formatter 1569 1570 output = [] 1571 append = output.append 1572 1573 append(fmt.div(on=1, attrs={"class" : "event-aggregator-error"})) 1574 append(fmt.paragraph(on=1)) 1575 append(fmt.text(text)) 1576 append(fmt.paragraph(on=0)) 1577 append(fmt.paragraph(on=1)) 1578 append(linkToPage(request, Page(request, pagename), pagename)) 1579 append(fmt.paragraph(on=0)) 1580 1581 return "".join(output) 1582 1583 def writeMapMarker(self, marker_x, marker_y, map_x_scale, map_y_scale, location, events): 1584 1585 "Put a marker on the map." 1586 1587 page = self.page 1588 request = page.request 1589 fmt = request.formatter 1590 1591 output = [] 1592 append = output.append 1593 1594 append(fmt.listitem(on=1, css_class="event-map-label")) 1595 1596 # Have a positioned marker for the print mode. 1597 1598 append(fmt.div(on=1, css_class="event-map-label-only", 1599 style="left:%dpx; top:%dpx; min-width:%dpx; min-height:%dpx") % ( 1600 marker_x, marker_y, map_x_scale, map_y_scale)) 1601 append(fmt.div(on=0)) 1602 1603 # Have a marker containing a pop-up when using the screen mode, 1604 # providing a normal block when using the print mode. 1605 1606 location_text = to_plain_text(location, request) 1607 label_target = "%s-maplabel-%s" % (self.getIdentifier(), location_text) 1608 1609 append(fmt.div(on=1, css_class="event-map-label", id=label_target, 1610 style="left:%dpx; top:%dpx; min-width:%dpx; min-height:%dpx") % ( 1611 marker_x, marker_y, map_x_scale, map_y_scale)) 1612 1613 label_target_url = page.url(request, anchor=label_target, relative=True) 1614 append(fmt.url(1, label_target_url, "event-map-label-link")) 1615 append(fmt.span(1)) 1616 append(fmt.text(location_text)) 1617 append(fmt.span(0)) 1618 append(fmt.url(0)) 1619 1620 append(fmt.div(on=1, css_class="event-map-details")) 1621 append(fmt.div(on=1, css_class="event-map-shadow")) 1622 append(fmt.div(on=1, css_class="event-map-location")) 1623 1624 # The location may have been given as formatted text, but this will not 1625 # be usable in a heading, so it must be first converted to plain text. 1626 1627 append(fmt.heading(on=1, depth=2)) 1628 append(fmt.text(location_text)) 1629 append(fmt.heading(on=0, depth=2)) 1630 1631 append(self.writeMapEventSummaries(events)) 1632 1633 append(fmt.div(on=0)) 1634 append(fmt.div(on=0)) 1635 append(fmt.div(on=0)) 1636 append(fmt.div(on=0)) 1637 append(fmt.listitem(on=0)) 1638 1639 return "".join(output) 1640 1641 def writeMapEventSummaries(self, events): 1642 1643 "Write summaries of the given 'events' for the map." 1644 1645 page = self.page 1646 request = page.request 1647 fmt = request.formatter 1648 1649 # Sort the events by date. 1650 1651 events.sort(sort_start_first) 1652 1653 # Write out a self-contained list of events. 1654 1655 output = [] 1656 append = output.append 1657 1658 append(fmt.bullet_list(on=1, attr={"class" : "event-map-location-events"})) 1659 1660 for event in events: 1661 1662 # Get the event details. 1663 1664 event_summary = event.getSummary(self.parent_name) 1665 start, end = event.as_limits() 1666 event_period = self._getCalendarPeriod( 1667 start and self.getFullDateLabel(start), 1668 end and self.getFullDateLabel(end), 1669 "") 1670 1671 append(fmt.listitem(on=1)) 1672 1673 # Link to the page using the summary. 1674 1675 append(event.linkToEvent(request, event_summary)) 1676 1677 # Add the event period. 1678 1679 append(fmt.text(" ")) 1680 append(fmt.span(on=1, css_class="event-map-period")) 1681 append(fmt.text(event_period)) 1682 append(fmt.span(on=0)) 1683 1684 append(fmt.listitem(on=0)) 1685 1686 append(fmt.bullet_list(on=0)) 1687 1688 return "".join(output) 1689 1690 def render(self, all_shown_events): 1691 1692 """ 1693 Render the view, returning the rendered representation as a string. 1694 The view will show a list of 'all_shown_events'. 1695 """ 1696 1697 page = self.page 1698 request = page.request 1699 fmt = request.formatter 1700 _ = request.getText 1701 1702 # Make a calendar. 1703 1704 output = [] 1705 append = output.append 1706 1707 append(fmt.div(on=1, css_class="event-calendar-region", id=("EventAggregator-%s" % self.getIdentifier()))) 1708 1709 # Output download controls. 1710 1711 append(fmt.div(on=1, css_class="event-controls")) 1712 append(self.writeDownloadControls()) 1713 append(fmt.div(on=0)) 1714 1715 append(fmt.div(on=1, css_class="event-display")) 1716 1717 # Output a table. 1718 1719 if self.mode == "table": 1720 1721 # Start of table view output. 1722 1723 append(fmt.table(on=1, attrs={"tableclass" : "event-table", "summary" : _("A table of events")})) 1724 1725 append(fmt.table_row(on=1)) 1726 append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) 1727 append(fmt.text(_("Event dates"))) 1728 append(fmt.table_cell(on=0)) 1729 append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) 1730 append(fmt.text(_("Event location"))) 1731 append(fmt.table_cell(on=0)) 1732 append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) 1733 append(fmt.text(_("Event details"))) 1734 append(fmt.table_cell(on=0)) 1735 append(fmt.table_row(on=0)) 1736 1737 # Show the events in order. 1738 1739 all_shown_events.sort(sort_start_first) 1740 1741 for event in all_shown_events: 1742 event_page = event.getPage() 1743 event_summary = event.getSummary(self.parent_name) 1744 event_details = event.getDetails() 1745 1746 # Prepare CSS classes with category-related styling. 1747 1748 css_classes = ["event-table-details"] 1749 1750 for topic in event_details.get("topics") or event_details.get("categories") or []: 1751 1752 # Filter the category text to avoid illegal characters. 1753 1754 css_classes.append("event-table-category-%s" % "".join(filter(lambda c: c.isalnum(), topic))) 1755 1756 attrs = {"class" : " ".join(css_classes)} 1757 1758 append(fmt.table_row(on=1)) 1759 1760 # Start and end dates. 1761 1762 append(fmt.table_cell(on=1, attrs=attrs)) 1763 append(fmt.span(on=1)) 1764 append(fmt.text(str(event_details["start"]))) 1765 append(fmt.span(on=0)) 1766 1767 if event_details["start"] != event_details["end"]: 1768 append(fmt.text(" - ")) 1769 append(fmt.span(on=1)) 1770 append(fmt.text(str(event_details["end"]))) 1771 append(fmt.span(on=0)) 1772 1773 append(fmt.table_cell(on=0)) 1774 1775 # Location. 1776 1777 append(fmt.table_cell(on=1, attrs=attrs)) 1778 1779 if event_details.has_key("location"): 1780 append(event_page.formatText(event_details["location"], fmt)) 1781 1782 append(fmt.table_cell(on=0)) 1783 1784 # Link to the page using the summary. 1785 1786 append(fmt.table_cell(on=1, attrs=attrs)) 1787 append(event.linkToEvent(request, event_summary)) 1788 append(fmt.table_cell(on=0)) 1789 1790 append(fmt.table_row(on=0)) 1791 1792 # End of table view output. 1793 1794 append(fmt.table(on=0)) 1795 1796 # Output a map view. 1797 1798 elif self.mode == "map": 1799 1800 # Special dictionary pages. 1801 1802 maps_page = getMapsPage(request) 1803 locations_page = getLocationsPage(request) 1804 1805 map_image = None 1806 1807 # Get the maps and locations. 1808 1809 maps = getWikiDict(maps_page, request) 1810 locations = getWikiDict(locations_page, request) 1811 1812 # Get the map image definition. 1813 1814 if maps is not None and self.map_name: 1815 try: 1816 map_details = maps[self.map_name].split() 1817 1818 map_bottom_left_latitude, map_bottom_left_longitude, map_top_right_latitude, map_top_right_longitude = \ 1819 map(getMapReference, map_details[:4]) 1820 map_width, map_height = map(int, map_details[4:6]) 1821 map_image = map_details[6] 1822 1823 map_x_scale = map_width / (map_top_right_longitude - map_bottom_left_longitude).to_degrees() 1824 map_y_scale = map_height / (map_top_right_latitude - map_bottom_left_latitude).to_degrees() 1825 1826 except (KeyError, ValueError): 1827 pass 1828 1829 # Report errors. 1830 1831 if maps is None: 1832 append(self.showDictError( 1833 _("You do not have read access to the maps page:"), 1834 maps_page)) 1835 1836 elif not self.map_name: 1837 append(self.showDictError( 1838 _("Please specify a valid map name corresponding to an entry on the following page:"), 1839 maps_page)) 1840 1841 elif map_image is None: 1842 append(self.showDictError( 1843 _("Please specify a valid entry for %s on the following page:") % self.map_name, 1844 maps_page)) 1845 1846 elif locations is None: 1847 append(self.showDictError( 1848 _("You do not have read access to the locations page:"), 1849 locations_page)) 1850 1851 # Attempt to show the map. 1852 1853 else: 1854 1855 # Get events by position. 1856 1857 events_by_location = {} 1858 event_locations = {} 1859 1860 for event in all_shown_events: 1861 event_details = event.getDetails() 1862 1863 location = event_details.get("location") 1864 geo = event_details.get("geo") 1865 1866 # Make a temporary location if an explicit position is given 1867 # but not a location name. 1868 1869 if not location and geo: 1870 location = "%s %s" % tuple(geo) 1871 1872 # Map the location to a position. 1873 1874 if location is not None and not event_locations.has_key(location): 1875 1876 # Get any explicit position of an event. 1877 1878 if geo: 1879 latitude, longitude = geo 1880 1881 # Or look up the position of a location using the locations 1882 # page. 1883 1884 else: 1885 latitude, longitude = Location(location, locations).getPosition() 1886 1887 # Use a normalised location if necessary. 1888 1889 if latitude is None and longitude is None: 1890 normalised_location = getNormalisedLocation(location) 1891 if normalised_location is not None: 1892 latitude, longitude = getLocationPosition(normalised_location, locations) 1893 if latitude is not None and longitude is not None: 1894 location = normalised_location 1895 1896 # Only remember positioned locations. 1897 1898 if latitude is not None and longitude is not None: 1899 event_locations[location] = latitude, longitude 1900 1901 # Record events according to location. 1902 1903 if not events_by_location.has_key(location): 1904 events_by_location[location] = [] 1905 1906 events_by_location[location].append(event) 1907 1908 # Get the map image URL. 1909 1910 map_image_url = AttachFile.getAttachUrl(maps_page, map_image, request) 1911 1912 # Start of map view output. 1913 1914 map_identifier = "map-%s" % self.getIdentifier() 1915 append(fmt.div(on=1, css_class="event-map", id=map_identifier)) 1916 1917 append(self.writeCalendarNavigation()) 1918 1919 append(fmt.table(on=1, attrs={"summary" : _("A map showing events")})) 1920 1921 append(self.writeMapTableHeading()) 1922 1923 append(fmt.table_row(on=1)) 1924 append(fmt.table_cell(on=1)) 1925 1926 append(fmt.div(on=1, css_class="event-map-container")) 1927 append(fmt.image(map_image_url)) 1928 append(fmt.number_list(on=1)) 1929 1930 # Events with no location are unpositioned. 1931 1932 if events_by_location.has_key(None): 1933 unpositioned_events = events_by_location[None] 1934 del events_by_location[None] 1935 else: 1936 unpositioned_events = [] 1937 1938 # Events whose location is unpositioned are themselves considered 1939 # unpositioned. 1940 1941 for location in set(events_by_location.keys()).difference(event_locations.keys()): 1942 unpositioned_events += events_by_location[location] 1943 1944 # Sort the locations before traversing them. 1945 1946 event_locations = event_locations.items() 1947 event_locations.sort() 1948 1949 # Show the events in the map. 1950 1951 for location, (latitude, longitude) in event_locations: 1952 events = events_by_location[location] 1953 1954 # Skip unpositioned locations and locations outside the map. 1955 1956 if latitude is None or longitude is None or \ 1957 latitude < map_bottom_left_latitude or \ 1958 longitude < map_bottom_left_longitude or \ 1959 latitude > map_top_right_latitude or \ 1960 longitude > map_top_right_longitude: 1961 1962 unpositioned_events += events 1963 continue 1964 1965 # Get the position and dimensions of the map marker. 1966 # NOTE: Use one degree as the marker size. 1967 1968 marker_x, marker_y = getPositionForCentrePoint( 1969 getPositionForReference(map_top_right_latitude, longitude, latitude, map_bottom_left_longitude, 1970 map_x_scale, map_y_scale), 1971 map_x_scale, map_y_scale) 1972 1973 # Add the map marker. 1974 1975 append(self.writeMapMarker(marker_x, marker_y, map_x_scale, map_y_scale, location, events)) 1976 1977 append(fmt.number_list(on=0)) 1978 append(fmt.div(on=0)) 1979 append(fmt.table_cell(on=0)) 1980 append(fmt.table_row(on=0)) 1981 1982 # Write unpositioned events. 1983 1984 if unpositioned_events: 1985 unpositioned_identifier = "unpositioned-%s" % self.getIdentifier() 1986 1987 append(fmt.table_row(on=1, css_class="event-map-unpositioned", 1988 id=unpositioned_identifier)) 1989 append(fmt.table_cell(on=1)) 1990 1991 append(fmt.heading(on=1, depth=2)) 1992 append(fmt.text(_("Events not shown on the map"))) 1993 append(fmt.heading(on=0, depth=2)) 1994 1995 # Show and hide controls. 1996 1997 append(fmt.div(on=1, css_class="event-map-show-control")) 1998 append(fmt.anchorlink(on=1, name=unpositioned_identifier)) 1999 append(fmt.text(_("Show unpositioned events"))) 2000 append(fmt.anchorlink(on=0)) 2001 append(fmt.div(on=0)) 2002 2003 append(fmt.div(on=1, css_class="event-map-hide-control")) 2004 append(fmt.anchorlink(on=1, name=map_identifier)) 2005 append(fmt.text(_("Hide unpositioned events"))) 2006 append(fmt.anchorlink(on=0)) 2007 append(fmt.div(on=0)) 2008 2009 append(self.writeMapEventSummaries(unpositioned_events)) 2010 2011 # End of map view output. 2012 2013 append(fmt.table_cell(on=0)) 2014 append(fmt.table_row(on=0)) 2015 append(fmt.table(on=0)) 2016 append(fmt.div(on=0)) 2017 2018 # Output a list. 2019 2020 elif self.mode == "list": 2021 2022 # Start of list view output. 2023 2024 append(fmt.bullet_list(on=1, attr={"class" : "event-listings"})) 2025 2026 # Output a list. 2027 # NOTE: Make the heading depth configurable. 2028 2029 for period in self.first.until(self.last): 2030 2031 append(fmt.listitem(on=1, attr={"class" : "event-listings-period"})) 2032 append(fmt.heading(on=1, depth=2, attr={"class" : "event-listings-heading"})) 2033 2034 # Either write a date heading or produce links for navigable 2035 # calendars. 2036 2037 append(self.writeDateHeading(period)) 2038 2039 append(fmt.heading(on=0, depth=2)) 2040 2041 append(fmt.bullet_list(on=1, attr={"class" : "event-period-listings"})) 2042 2043 # Show the events in order. 2044 2045 events_in_period = getEventsInPeriod(all_shown_events, getCalendarPeriod(period, period)) 2046 events_in_period.sort(sort_start_first) 2047 2048 for event in events_in_period: 2049 event_page = event.getPage() 2050 event_details = event.getDetails() 2051 event_summary = event.getSummary(self.parent_name) 2052 2053 append(fmt.listitem(on=1, attr={"class" : "event-listing"})) 2054 2055 # Link to the page using the summary. 2056 2057 append(fmt.paragraph(on=1)) 2058 append(event.linkToEvent(request, event_summary)) 2059 append(fmt.paragraph(on=0)) 2060 2061 # Start and end dates. 2062 2063 append(fmt.paragraph(on=1)) 2064 append(fmt.span(on=1)) 2065 append(fmt.text(str(event_details["start"]))) 2066 append(fmt.span(on=0)) 2067 append(fmt.text(" - ")) 2068 append(fmt.span(on=1)) 2069 append(fmt.text(str(event_details["end"]))) 2070 append(fmt.span(on=0)) 2071 append(fmt.paragraph(on=0)) 2072 2073 # Location. 2074 2075 if event_details.has_key("location"): 2076 append(fmt.paragraph(on=1)) 2077 append(event_page.formatText(event_details["location"], fmt)) 2078 append(fmt.paragraph(on=1)) 2079 2080 # Topics. 2081 2082 if event_details.has_key("topics") or event_details.has_key("categories"): 2083 append(fmt.bullet_list(on=1, attr={"class" : "event-topics"})) 2084 2085 for topic in event_details.get("topics") or event_details.get("categories") or []: 2086 append(fmt.listitem(on=1)) 2087 append(event_page.formatText(topic, fmt)) 2088 append(fmt.listitem(on=0)) 2089 2090 append(fmt.bullet_list(on=0)) 2091 2092 append(fmt.listitem(on=0)) 2093 2094 append(fmt.bullet_list(on=0)) 2095 2096 # End of list view output. 2097 2098 append(fmt.bullet_list(on=0)) 2099 2100 # Output a month calendar. This shows month-by-month data. 2101 2102 elif self.mode == "calendar": 2103 2104 # Visit all months in the requested range, or across known events. 2105 2106 for month in self.first.months_until(self.last): 2107 2108 append(fmt.div(on=1, css_class="event-calendar")) 2109 append(self.writeCalendarNavigation()) 2110 2111 # Output a month. 2112 2113 append(fmt.table(on=1, attrs={"tableclass" : "event-month", "summary" : _("A table showing a calendar month")})) 2114 2115 # Either write a month heading or produce links for navigable 2116 # calendars. 2117 2118 append(self.writeMonthTableHeading(month)) 2119 2120 # Weekday headings. 2121 2122 append(self.writeWeekdayHeadings()) 2123 2124 # Process the days of the month. 2125 2126 start_weekday, number_of_days = month.month_properties() 2127 2128 # The start weekday is the weekday of day number 1. 2129 # Find the first day of the week, counting from below zero, if 2130 # necessary, in order to land on the first day of the month as 2131 # day number 1. 2132 2133 first_day = 1 - start_weekday 2134 2135 while first_day <= number_of_days: 2136 2137 # Find events in this week and determine how to mark them on the 2138 # calendar. 2139 2140 week_start = month.as_date(max(first_day, 1)) 2141 week_end = month.as_date(min(first_day + 6, number_of_days)) 2142 2143 full_coverage, week_slots = getCoverage( 2144 getEventsInPeriod(all_shown_events, getCalendarPeriod(week_start, week_end))) 2145 2146 # Make a new table region. 2147 # NOTE: Moin opens a "tbody" element in the table method. 2148 2149 append(fmt.rawHTML("</tbody>")) 2150 append(fmt.rawHTML("<tbody>")) 2151 2152 # Output a week, starting with the day numbers. 2153 2154 append(self.writeDayNumbers(first_day, number_of_days, month, full_coverage)) 2155 2156 # Either generate empty days... 2157 2158 if not week_slots: 2159 append(self.writeEmptyWeek(first_day, number_of_days, month)) 2160 2161 # Or generate each set of scheduled events... 2162 2163 else: 2164 append(self.writeWeekSlots(first_day, number_of_days, month, week_end, week_slots)) 2165 2166 # Process the next week... 2167 2168 first_day += 7 2169 2170 # End of month. 2171 # NOTE: Moin closes a "tbody" element in the table method. 2172 2173 append(fmt.table(on=0)) 2174 append(fmt.div(on=0)) 2175 2176 # Output a day view. 2177 2178 elif self.mode == "day": 2179 2180 # Visit all days in the requested range, or across known events. 2181 2182 for date in self.first.days_until(self.last): 2183 2184 append(fmt.div(on=1, css_class="event-calendar")) 2185 append(self.writeCalendarNavigation()) 2186 2187 append(fmt.table(on=1, attrs={"tableclass" : "event-calendar-day", "summary" : _("A table showing a calendar day")})) 2188 2189 full_coverage, day_slots = getCoverage( 2190 getEventsInPeriod(all_shown_events, getCalendarPeriod(date, date)), "datetime") 2191 2192 # Work out how many columns the day title will need. 2193 # Include spacers after the scale and each event column. 2194 2195 colspan = sum(map(len, day_slots.values())) * 2 + 2 2196 2197 append(self.writeDayTableHeading(date, colspan)) 2198 2199 # Either generate empty days... 2200 2201 if not day_slots: 2202 append(self.writeEmptyDay(date)) 2203 2204 # Or generate each set of scheduled events... 2205 2206 else: 2207 append(self.writeDaySlots(date, full_coverage, day_slots)) 2208 2209 # End of day. 2210 2211 append(fmt.table(on=0)) 2212 append(fmt.div(on=0)) 2213 2214 append(fmt.div(on=0)) # end of event-display 2215 2216 # Output view controls. 2217 2218 append(fmt.div(on=1, css_class="event-controls")) 2219 append(self.writeViewControls()) 2220 append(fmt.div(on=0)) 2221 2222 # Close the calendar region. 2223 2224 append(fmt.div(on=0)) 2225 2226 # Add any scripts. 2227 2228 if isinstance(fmt, request.html_formatter.__class__): 2229 append(self.update_script) 2230 2231 return ''.join(output) 2232 2233 update_script = """\ 2234 <script type="text/javascript"> 2235 function replaceCalendar(name, url) { 2236 var calendar = document.getElementById(name); 2237 2238 if (calendar == null) { 2239 return true; 2240 } 2241 2242 var xmlhttp = new XMLHttpRequest(); 2243 xmlhttp.open("GET", url, false); 2244 xmlhttp.send(null); 2245 2246 var newCalendar = xmlhttp.responseText; 2247 2248 if (newCalendar != null) { 2249 calendar.innerHTML = newCalendar; 2250 return false; 2251 } 2252 2253 return true; 2254 } 2255 </script> 2256 """ 2257 2258 # vim: tabstop=4 expandtab shiftwidth=4