1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - EventAggregator Macro 4 5 @copyright: 2008, 2009, 2010, 2011 by Paul Boddie <paul@boddie.org.uk> 6 @copyright: 2000-2004 Juergen Hermann <jh@web.de>, 7 2005-2008 MoinMoin:ThomasWaldmann 8 @license: GNU GPL (v2 or later), see COPYING.txt for details. 9 """ 10 11 from MoinMoin import wikiutil 12 from MoinMoin.action import AttachFile 13 from EventAggregatorSupport import * 14 import calendar 15 16 Dependencies = ['pages'] 17 18 # Abstractions. 19 20 class View: 21 22 "A view of the event calendar." 23 24 def __init__(self, page, calendar_name, raw_calendar_start, raw_calendar_end, 25 original_calendar_start, original_calendar_end, calendar_start, calendar_end, 26 first, last, category_names, template_name, parent_name, mode, resolution, 27 name_usage, map_name): 28 29 """ 30 Initialise the view with the current 'page', a 'calendar_name' (which 31 may be None), the 'raw_calendar_start' and 'raw_calendar_end' (which 32 are the actual start and end values provided by the request), the 33 calculated 'original_calendar_start' and 'original_calendar_end' (which 34 are the result of calculating the calendar's limits from the raw start 35 and end values), and the requested, calculated 'calendar_start' and 36 'calendar_end' (which may involve different start and end values due to 37 navigation in the user interface), along with the 'first' and 'last' 38 months of event coverage. 39 40 The additional 'category_names', 'template_name', 'parent_name' and 41 'mode' parameters are used to configure the links employed by the view. 42 43 The 'resolution' affects the view for certain modes and is also used to 44 parameterise links. 45 46 The 'name_usage' parameter controls how names are shown on calendar mode 47 events, such as how often labels are repeated. 48 49 The 'map_name' parameter provides the name of a map to be used in the 50 map mode. 51 """ 52 53 self.page = page 54 self.calendar_name = calendar_name 55 self.raw_calendar_start = raw_calendar_start 56 self.raw_calendar_end = raw_calendar_end 57 self.original_calendar_start = original_calendar_start 58 self.original_calendar_end = original_calendar_end 59 self.calendar_start = calendar_start 60 self.calendar_end = calendar_end 61 self.template_name = template_name 62 self.parent_name = parent_name 63 self.mode = mode 64 self.resolution = resolution 65 self.name_usage = name_usage 66 self.map_name = map_name 67 68 self.category_name_parameters = "&".join([("category=%s" % name) for name in category_names]) 69 70 if self.calendar_name is not None: 71 72 # Store the view parameters. 73 74 self.duration = (last - first).count() + 1 75 76 self.previous_start = first.previous() 77 self.next_start = first.next() 78 self.previous_end = last.previous() 79 self.next_end = last.next() 80 81 self.previous_set_start = first.update(-self.duration) 82 self.next_set_start = first.update(self.duration) 83 self.previous_set_end = last.update(-self.duration) 84 self.next_set_end = last.update(self.duration) 85 86 def getQualifiedParameterName(self, argname): 87 88 "Return the 'argname' qualified using the calendar name." 89 90 return getQualifiedParameterName(self.calendar_name, argname) 91 92 def getDateQueryString(self, argname, date, prefix=1): 93 94 """ 95 Return a query string fragment for the given 'argname', referring to the 96 month given by the specified 'year_month' object, appropriate for this 97 calendar. 98 99 If 'prefix' is specified and set to a false value, the parameters in the 100 query string will not be calendar-specific, but could be used with the 101 summary action. 102 """ 103 104 suffixes = ["year", "month", "day"] 105 106 if date is not None: 107 args = [] 108 for suffix, value in zip(suffixes, date.as_tuple()): 109 suffixed_argname = "%s-%s" % (argname, suffix) 110 if prefix: 111 suffixed_argname = self.getQualifiedParameterName(suffixed_argname) 112 args.append("%s=%s" % (suffixed_argname, value)) 113 return "&".join(args) 114 else: 115 return "" 116 117 def getRawDateQueryString(self, argname, date, prefix=1): 118 119 """ 120 Return a query string fragment for the given 'argname', referring to the 121 date given by the specified 'date' value, appropriate for this 122 calendar. 123 124 If 'prefix' is specified and set to a false value, the parameters in the 125 query string will not be calendar-specific, but could be used with the 126 summary action. 127 """ 128 129 if date is not None: 130 if prefix: 131 argname = self.getQualifiedParameterName(argname) 132 return "%s=%s" % (argname, date) 133 else: 134 return "" 135 136 def getNavigationLink(self, start, end, mode=None, resolution=None): 137 138 """ 139 Return a query string fragment for navigation to a view showing months 140 from 'start' to 'end' inclusive, with the optional 'mode' indicating the 141 view style and the optional 'resolution' indicating the resolution of a 142 view, if configurable. 143 """ 144 145 return "%s&%s&%s=%s&%s=%s" % ( 146 self.getRawDateQueryString("start", start), 147 self.getRawDateQueryString("end", end), 148 self.getQualifiedParameterName("mode"), mode or self.mode, 149 self.getQualifiedParameterName("resolution"), resolution or self.resolution 150 ) 151 152 def getFullDateLabel(self, date): 153 page = self.page 154 request = page.request 155 return getFullDateLabel(request, date) 156 157 def getFullMonthLabel(self, year_month): 158 page = self.page 159 request = page.request 160 return getFullMonthLabel(request, year_month) 161 162 def getFullLabel(self, arg): 163 return self.resolution == "date" and self.getFullDateLabel(arg) or self.getFullMonthLabel(arg) 164 165 def _getCalendarPeriod(self, start_label, end_label, default_label): 166 output = [] 167 if start_label: 168 output.append(start_label) 169 if self.calendar_end and start_label != end_label: 170 if output: 171 output.append(" - ") 172 output.append(end_label) 173 return "".join(output) or default_label 174 175 def getCalendarPeriod(self): 176 _ = self.page.request.getText 177 return self._getCalendarPeriod( 178 self.calendar_start and self.getFullLabel(self.calendar_start), 179 self.calendar_end and self.getFullLabel(self.calendar_end), 180 _("All events") 181 ) 182 183 def getOriginalCalendarPeriod(self): 184 _ = self.page.request.getText 185 return self._getCalendarPeriod( 186 self.original_calendar_start and self.getFullLabel(self.original_calendar_start), 187 self.original_calendar_end and self.getFullLabel(self.original_calendar_end), 188 _("All events") 189 ) 190 191 def getRawCalendarPeriod(self): 192 _ = self.page.request.getText 193 return self._getCalendarPeriod( 194 self.raw_calendar_start, 195 self.raw_calendar_end, 196 _("No period specified") 197 ) 198 199 def writeDownloadControls(self): 200 201 """ 202 Return a representation of the download controls, featuring links for 203 view, calendar and customised downloads and subscriptions. 204 """ 205 206 page = self.page 207 request = page.request 208 fmt = page.formatter 209 _ = request.getText 210 211 output = [] 212 213 # Generate the links. 214 215 download_dialogue_link = "action=EventAggregatorSummary&parent=%s&resolution=%s&%s" % ( 216 self.parent_name or "", 217 self.resolution, 218 self.category_name_parameters 219 ) 220 download_all_link = download_dialogue_link + "&doit=1" 221 download_link = download_all_link + ("&%s&%s" % ( 222 self.getDateQueryString("start", self.calendar_start, prefix=0), 223 self.getDateQueryString("end", self.calendar_end, prefix=0) 224 )) 225 226 # Subscription links just explicitly select the RSS format. 227 228 subscribe_dialogue_link = download_dialogue_link + "&format=RSS" 229 subscribe_all_link = download_all_link + "&format=RSS" 230 subscribe_link = download_link + "&format=RSS" 231 232 # Adjust the "download all" and "subscribe all" links if the calendar 233 # has an inherent period associated with it. 234 235 period_limits = [] 236 237 if self.raw_calendar_start: 238 period_limits.append("&%s" % 239 self.getRawDateQueryString("start", self.raw_calendar_start, prefix=0) 240 ) 241 if self.raw_calendar_end: 242 period_limits.append("&%s" % 243 self.getRawDateQueryString("end", self.raw_calendar_end, prefix=0) 244 ) 245 246 period_limits = "".join(period_limits) 247 248 download_dialogue_link += period_limits 249 download_all_link += period_limits 250 subscribe_dialogue_link += period_limits 251 subscribe_all_link += period_limits 252 253 # Pop-up descriptions of the downloadable calendars. 254 255 calendar_period = self.getCalendarPeriod() 256 original_calendar_period = self.getOriginalCalendarPeriod() 257 raw_calendar_period = self.getRawCalendarPeriod() 258 259 # Write the controls. 260 261 # Download controls. 262 263 output.append(fmt.div(on=1, css_class="event-download-controls")) 264 output.append(fmt.span(on=1, css_class="event-download")) 265 output.append(linkToPage(request, page, _("Download this view"), download_link)) 266 output.append(fmt.span(on=1, css_class="event-download-popup")) 267 output.append(fmt.text(calendar_period)) 268 output.append(fmt.span(on=0)) 269 output.append(fmt.span(on=0)) 270 271 output.append(fmt.span(on=1, css_class="event-download")) 272 output.append(linkToPage(request, page, _("Download this calendar"), download_all_link)) 273 output.append(fmt.span(on=1, css_class="event-download-popup")) 274 output.append(fmt.span(on=1, css_class="event-download-period")) 275 output.append(fmt.text(original_calendar_period)) 276 output.append(fmt.span(on=0)) 277 output.append(fmt.span(on=1, css_class="event-download-period-raw")) 278 output.append(fmt.text(raw_calendar_period)) 279 output.append(fmt.span(on=0)) 280 output.append(fmt.span(on=0)) 281 output.append(fmt.span(on=0)) 282 283 output.append(fmt.span(on=1, css_class="event-download")) 284 output.append(linkToPage(request, page, _("Download..."), download_dialogue_link)) 285 output.append(fmt.span(on=1, css_class="event-download-popup")) 286 output.append(fmt.text(_("Edit download options"))) 287 output.append(fmt.span(on=0)) 288 output.append(fmt.span(on=0)) 289 290 # Subscription controls. 291 292 output.append(fmt.span(on=1, css_class="event-download")) 293 output.append(linkToPage(request, page, _("Subscribe to this view"), subscribe_link)) 294 output.append(fmt.span(on=1, css_class="event-download-popup")) 295 output.append(fmt.text(calendar_period)) 296 output.append(fmt.span(on=0)) 297 output.append(fmt.span(on=0)) 298 299 output.append(fmt.span(on=1, css_class="event-download")) 300 output.append(linkToPage(request, page, _("Subscribe to this calendar"), subscribe_all_link)) 301 output.append(fmt.span(on=1, css_class="event-download-popup")) 302 output.append(fmt.span(on=1, css_class="event-download-period")) 303 output.append(fmt.text(original_calendar_period)) 304 output.append(fmt.span(on=0)) 305 output.append(fmt.span(on=1, css_class="event-download-period-raw")) 306 output.append(fmt.text(raw_calendar_period)) 307 output.append(fmt.span(on=0)) 308 output.append(fmt.span(on=0)) 309 output.append(fmt.span(on=0)) 310 311 output.append(fmt.span(on=1, css_class="event-download")) 312 output.append(linkToPage(request, page, _("Subscribe..."), subscribe_dialogue_link)) 313 output.append(fmt.span(on=1, css_class="event-download-popup")) 314 output.append(fmt.text(_("Edit subscription options"))) 315 output.append(fmt.span(on=0)) 316 output.append(fmt.span(on=0)) 317 output.append(fmt.div(on=0)) 318 319 return "".join(output) 320 321 def writeViewControls(self): 322 323 """ 324 Return a representation of the view mode controls, permitting viewing of 325 aggregated events in calendar, list or table form. 326 """ 327 328 page = self.page 329 request = page.request 330 fmt = page.formatter 331 _ = request.getText 332 333 output = [] 334 335 start = self.calendar_start 336 end = self.calendar_end 337 338 calendar_link = self.getNavigationLink(start and start.as_month(), end and end.as_month(), "calendar", "month") 339 list_link = self.getNavigationLink(start, end, "list") 340 table_link = self.getNavigationLink(start, end, "table") 341 map_link = self.getNavigationLink(start, end, "map") 342 343 # Write the controls. 344 345 output.append(fmt.div(on=1, css_class="event-view-controls")) 346 347 if self.mode != "calendar": 348 output.append(fmt.span(on=1, css_class="event-view")) 349 output.append(linkToPage(request, page, _("View as calendar"), calendar_link)) 350 output.append(fmt.span(on=0)) 351 352 if self.mode != "list": 353 output.append(fmt.span(on=1, css_class="event-view")) 354 output.append(linkToPage(request, page, _("View as list"), list_link)) 355 output.append(fmt.span(on=0)) 356 357 if self.mode != "table": 358 output.append(fmt.span(on=1, css_class="event-view")) 359 output.append(linkToPage(request, page, _("View as table"), table_link)) 360 output.append(fmt.span(on=0)) 361 362 if self.mode != "map" and self.map_name is not None: 363 output.append(fmt.span(on=1, css_class="event-view")) 364 output.append(linkToPage(request, page, _("View as map"), map_link)) 365 output.append(fmt.span(on=0)) 366 367 output.append(fmt.div(on=0)) 368 369 return "".join(output) 370 371 def writeMapHeading(self): 372 373 """ 374 Return the calendar heading for the current calendar, providing links 375 permitting navigation to other periods. 376 """ 377 378 label = self.getCalendarPeriod() 379 380 if self.raw_calendar_start is None or self.raw_calendar_end is None: 381 fmt = self.page.formatter 382 output = [] 383 output.append(fmt.span(on=1)) 384 output.append(fmt.text(label)) 385 output.append(fmt.span(on=0)) 386 return "".join(output) 387 else: 388 return self._writeCalendarHeading(label, self.calendar_start, self.calendar_end) 389 390 def writeDateHeading(self, date): 391 if isinstance(date, Date): 392 return self.writeDayHeading(date) 393 else: 394 return self.writeMonthHeading(date) 395 396 def writeMonthHeading(self, year_month): 397 398 """ 399 Return the calendar heading for the given 'year_month' (a Month object) 400 providing links permitting navigation to other months. 401 """ 402 403 full_month_label = self.getFullMonthLabel(year_month) 404 end_month = year_month.update(self.duration - 1) 405 return self._writeCalendarHeading(full_month_label, year_month, end_month) 406 407 def writeDayHeading(self, date): 408 409 """ 410 Return the calendar heading for the given 'date' (a Date object) 411 providing links permitting navigation to other dates. 412 """ 413 414 full_date_label = self.getFullDateLabel(date) 415 end_date = date.update(self.duration - 1) 416 return self._writeCalendarHeading(full_date_label, date, end_date) 417 418 def _writeCalendarHeading(self, label, start, end): 419 420 """ 421 Write a calendar heading providing links permitting navigation to other 422 periods, using the given 'label' along with the 'start' and 'end' dates 423 to provide a link to a particular period. 424 """ 425 426 page = self.page 427 request = page.request 428 fmt = page.formatter 429 _ = request.getText 430 431 output = [] 432 433 # Prepare navigation links. 434 435 if self.calendar_name is not None: 436 calendar_name = self.calendar_name 437 438 # Links to the previous set of months and to a calendar shifted 439 # back one month. 440 441 previous_set_link = self.getNavigationLink( 442 self.previous_set_start, self.previous_set_end 443 ) 444 previous_link = self.getNavigationLink( 445 self.previous_start, self.previous_end 446 ) 447 448 # Links to the next set of months and to a calendar shifted 449 # forward one month. 450 451 next_set_link = self.getNavigationLink( 452 self.next_set_start, self.next_set_end 453 ) 454 next_link = self.getNavigationLink( 455 self.next_start, self.next_end 456 ) 457 458 # A link leading to this date being at the top of the calendar. 459 460 date_link = self.getNavigationLink(start, end) 461 462 output.append(fmt.span(on=1, css_class="previous")) 463 output.append(linkToPage(request, page, "<<", previous_set_link)) 464 output.append(fmt.text(" ")) 465 output.append(linkToPage(request, page, "<", previous_link)) 466 output.append(fmt.span(on=0)) 467 468 output.append(fmt.span(on=1, css_class="next")) 469 output.append(linkToPage(request, page, ">", next_link)) 470 output.append(fmt.text(" ")) 471 output.append(linkToPage(request, page, ">>", next_set_link)) 472 output.append(fmt.span(on=0)) 473 474 output.append(linkToPage(request, page, label, date_link)) 475 476 else: 477 output.append(fmt.span(on=1)) 478 output.append(fmt.text(label)) 479 output.append(fmt.span(on=0)) 480 481 return "".join(output) 482 483 def writeDayNumberHeading(self, date, busy): 484 485 """ 486 Return a link for the given 'date' which will activate the new event 487 action for the given day. If 'busy' is given as a true value, the 488 heading will be marked as busy. 489 """ 490 491 page = self.page 492 request = page.request 493 fmt = page.formatter 494 _ = request.getText 495 496 year, month, day = date.as_tuple() 497 output = [] 498 499 # Prepare navigation details for the calendar shown with the new event 500 # form. 501 502 navigation_link = self.getNavigationLink( 503 self.calendar_start, self.calendar_end 504 ) 505 506 # Prepare the link to the new event form, incorporating the above 507 # calendar parameters. 508 509 new_event_link = "action=EventAggregatorNewEvent&start-day=%d&start-month=%d&start-year=%d" \ 510 "&%s&template=%s&parent=%s&%s" % ( 511 day, month, year, self.category_name_parameters, self.template_name, self.parent_name or "", 512 navigation_link) 513 514 # Prepare a link to the day view for this day. 515 516 day_view_link = self.getNavigationLink(date, date, "day", "date") 517 518 # Output the heading class. 519 520 output.append( 521 fmt.table_cell(on=1, attrs={ 522 "class" : "event-day-heading event-day-%s" % (busy and "busy" or "empty"), 523 "colspan" : "3" 524 })) 525 526 # Output the number and pop-up menu. 527 528 output.append(fmt.div(on=1, css_class="event-day-box")) 529 530 output.append(fmt.span(on=1, css_class="event-day-number-popup")) 531 output.append(fmt.span(on=1, css_class="event-day-number-link")) 532 output.append(linkToPage(request, page, _("View day"), day_view_link)) 533 output.append(fmt.span(on=0)) 534 output.append(fmt.span(on=1, css_class="event-day-number-link")) 535 output.append(linkToPage(request, page, _("New event"), new_event_link)) 536 output.append(fmt.span(on=0)) 537 output.append(fmt.span(on=0)) 538 539 output.append(fmt.span(on=1, css_class="event-day-number")) 540 output.append(fmt.text(unicode(day))) 541 output.append(fmt.span(on=0)) 542 543 output.append(fmt.div(on=0)) 544 545 # End of heading. 546 547 output.append(fmt.table_cell(on=0)) 548 549 return "".join(output) 550 551 # Common layout methods. 552 553 def getEventStyle(self, colour_seed): 554 555 "Generate colour style information using the given 'colour_seed'." 556 557 bg = getColour(colour_seed) 558 fg = getBlackOrWhite(bg) 559 return "background-color: rgb(%d, %d, %d); color: rgb(%d, %d, %d);" % (bg + fg) 560 561 def writeEventSummaryBox(self, event): 562 563 "Return an event summary box linking to the given 'event'." 564 565 page = self.page 566 request = page.request 567 fmt = page.formatter 568 569 output = [] 570 571 event_page = event.getPage() 572 event_details = event.getDetails() 573 event_summary = event.getSummary(self.parent_name) 574 575 is_ambiguous = event.as_timespan().ambiguous() 576 style = self.getEventStyle(event_summary) 577 578 # The event box contains the summary, alongside 579 # other elements. 580 581 output.append(fmt.div(on=1, css_class="event-summary-box")) 582 output.append(fmt.div(on=1, css_class="event-summary", style=style)) 583 584 if is_ambiguous: 585 output.append(fmt.icon("/!\\")) 586 587 output.append(event_page.linkToPage(request, event_summary)) 588 output.append(fmt.div(on=0)) 589 590 # Add a pop-up element for long summaries. 591 592 output.append(fmt.div(on=1, css_class="event-summary-popup", style=style)) 593 594 if is_ambiguous: 595 output.append(fmt.icon("/!\\")) 596 597 output.append(event_page.linkToPage(request, event_summary)) 598 output.append(fmt.div(on=0)) 599 600 output.append(fmt.div(on=0)) 601 602 return "".join(output) 603 604 # Calendar layout methods. 605 606 def writeMonthTableHeading(self, year_month): 607 page = self.page 608 fmt = page.formatter 609 610 output = [] 611 output.append(fmt.table_row(on=1)) 612 output.append(fmt.table_cell(on=1, attrs={"class" : "event-month-heading", "colspan" : "21"})) 613 614 output.append(self.writeMonthHeading(year_month)) 615 616 output.append(fmt.table_cell(on=0)) 617 output.append(fmt.table_row(on=0)) 618 619 return "".join(output) 620 621 def writeWeekdayHeadings(self): 622 page = self.page 623 request = page.request 624 fmt = page.formatter 625 _ = request.getText 626 627 output = [] 628 output.append(fmt.table_row(on=1)) 629 630 for weekday in range(0, 7): 631 output.append(fmt.table_cell(on=1, attrs={"class" : "event-weekday-heading", "colspan" : "3"})) 632 output.append(fmt.text(_(getDayLabel(weekday)))) 633 output.append(fmt.table_cell(on=0)) 634 635 output.append(fmt.table_row(on=0)) 636 return "".join(output) 637 638 def writeDayNumbers(self, first_day, number_of_days, month, coverage): 639 page = self.page 640 fmt = page.formatter 641 642 output = [] 643 output.append(fmt.table_row(on=1)) 644 645 for weekday in range(0, 7): 646 day = first_day + weekday 647 date = month.as_date(day) 648 649 # Output out-of-month days. 650 651 if day < 1 or day > number_of_days: 652 output.append(fmt.table_cell(on=1, 653 attrs={"class" : "event-day-heading event-day-excluded", "colspan" : "3"})) 654 output.append(fmt.table_cell(on=0)) 655 656 # Output normal days. 657 658 else: 659 # Output the day heading, making a link to a new event 660 # action. 661 662 output.append(self.writeDayNumberHeading(date, date in coverage)) 663 664 # End of day numbers. 665 666 output.append(fmt.table_row(on=0)) 667 return "".join(output) 668 669 def writeEmptyWeek(self, first_day, number_of_days): 670 page = self.page 671 fmt = page.formatter 672 673 output = [] 674 output.append(fmt.table_row(on=1)) 675 676 for weekday in range(0, 7): 677 day = first_day + weekday 678 679 # Output out-of-month days. 680 681 if day < 1 or day > number_of_days: 682 output.append(fmt.table_cell(on=1, 683 attrs={"class" : "event-day-content event-day-excluded", "colspan" : "3"})) 684 output.append(fmt.table_cell(on=0)) 685 686 # Output empty days. 687 688 else: 689 output.append(fmt.table_cell(on=1, 690 attrs={"class" : "event-day-content event-day-empty", "colspan" : "3"})) 691 692 output.append(fmt.table_row(on=0)) 693 return "".join(output) 694 695 def writeWeekSlots(self, first_day, number_of_days, month, week_end, week_slots): 696 output = [] 697 698 locations = week_slots.keys() 699 locations.sort(sort_none_first) 700 701 # Visit each slot corresponding to a location (or no location). 702 703 for location in locations: 704 705 # Visit each coverage span, presenting the events in the span. 706 707 for events in week_slots[location]: 708 709 # Output each set. 710 711 output.append(self.writeWeekSlot(first_day, number_of_days, month, week_end, events)) 712 713 # Add a spacer. 714 715 output.append(self.writeWeekSpacer(first_day, number_of_days)) 716 717 return "".join(output) 718 719 def writeWeekSlot(self, first_day, number_of_days, month, week_end, events): 720 page = self.page 721 request = page.request 722 fmt = page.formatter 723 724 output = [] 725 output.append(fmt.table_row(on=1)) 726 727 # Then, output day details. 728 729 for weekday in range(0, 7): 730 day = first_day + weekday 731 date = month.as_date(day) 732 733 # Skip out-of-month days. 734 735 if day < 1 or day > number_of_days: 736 output.append(fmt.table_cell(on=1, 737 attrs={"class" : "event-day-content event-day-excluded", "colspan" : "3"})) 738 output.append(fmt.table_cell(on=0)) 739 continue 740 741 # Output the day. 742 743 if date not in events: 744 output.append(fmt.table_cell(on=1, 745 attrs={"class" : "event-day-content event-day-empty", "colspan" : "3"})) 746 747 # Get event details for the current day. 748 749 for event in events: 750 event_page = event.getPage() 751 event_details = event.getDetails() 752 753 if date not in event: 754 continue 755 756 # Get basic properties of the event. 757 758 starts_today = event_details["start"] == date 759 ends_today = event_details["end"] == date 760 event_summary = event.getSummary(self.parent_name) 761 762 style = self.getEventStyle(event_summary) 763 764 # Determine if the event name should be shown. 765 766 start_of_period = starts_today or weekday == 0 or day == 1 767 768 if self.name_usage == "daily" or start_of_period: 769 hide_text = 0 770 else: 771 hide_text = 1 772 773 # Output start of day gap and determine whether 774 # any event content should be explicitly output 775 # for this day. 776 777 if starts_today: 778 779 # Single day events... 780 781 if ends_today: 782 colspan = 3 783 event_day_type = "event-day-single" 784 785 # Events starting today... 786 787 else: 788 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-start-gap"})) 789 output.append(fmt.table_cell(on=0)) 790 791 # Calculate the span of this cell. 792 # Events whose names appear on every day... 793 794 if self.name_usage == "daily": 795 colspan = 2 796 event_day_type = "event-day-starting" 797 798 # Events whose names appear once per week... 799 800 else: 801 if event_details["end"] <= week_end: 802 event_length = event_details["end"].day() - day + 1 803 colspan = (event_length - 2) * 3 + 4 804 else: 805 event_length = week_end.day() - day + 1 806 colspan = (event_length - 1) * 3 + 2 807 808 event_day_type = "event-day-multiple" 809 810 # Events continuing from a previous week... 811 812 elif start_of_period: 813 814 # End of continuing event... 815 816 if ends_today: 817 colspan = 2 818 event_day_type = "event-day-ending" 819 820 # Events continuing for at least one more day... 821 822 else: 823 824 # Calculate the span of this cell. 825 # Events whose names appear on every day... 826 827 if self.name_usage == "daily": 828 colspan = 3 829 event_day_type = "event-day-full" 830 831 # Events whose names appear once per week... 832 833 else: 834 if event_details["end"] <= week_end: 835 event_length = event_details["end"].day() - day + 1 836 colspan = (event_length - 1) * 3 + 2 837 else: 838 event_length = week_end.day() - day + 1 839 colspan = event_length * 3 840 841 event_day_type = "event-day-multiple" 842 843 # Continuing events whose names appear on every day... 844 845 elif self.name_usage == "daily": 846 if ends_today: 847 colspan = 2 848 event_day_type = "event-day-ending" 849 else: 850 colspan = 3 851 event_day_type = "event-day-full" 852 853 # Continuing events whose names appear once per week... 854 855 else: 856 colspan = None 857 858 # Output the main content only if it is not 859 # continuing from a previous day. 860 861 if colspan is not None: 862 863 # Colour the cell for continuing events. 864 865 attrs={ 866 "class" : "event-day-content event-day-busy %s" % event_day_type, 867 "colspan" : str(colspan) 868 } 869 870 if not (starts_today and ends_today): 871 attrs["style"] = style 872 873 output.append(fmt.table_cell(on=1, attrs=attrs)) 874 875 # Output the event. 876 877 if starts_today and ends_today or not hide_text: 878 output.append(self.writeEventSummaryBox(event)) 879 880 output.append(fmt.table_cell(on=0)) 881 882 # Output end of day gap. 883 884 if ends_today and not starts_today: 885 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-end-gap"})) 886 output.append(fmt.table_cell(on=0)) 887 888 # End of set. 889 890 output.append(fmt.table_row(on=0)) 891 return "".join(output) 892 893 def writeWeekSpacer(self, first_day, number_of_days): 894 page = self.page 895 fmt = page.formatter 896 897 output = [] 898 output.append(fmt.table_row(on=1)) 899 900 for weekday in range(0, 7): 901 day = first_day + weekday 902 css_classes = "event-day-spacer" 903 904 # Skip out-of-month days. 905 906 if day < 1 or day > number_of_days: 907 css_classes += " event-day-excluded" 908 909 output.append(fmt.table_cell(on=1, attrs={"class" : css_classes, "colspan" : "3"})) 910 output.append(fmt.table_cell(on=0)) 911 912 output.append(fmt.table_row(on=0)) 913 return "".join(output) 914 915 # Day layout methods. 916 917 def writeDayTableHeading(self, date, colspan=1): 918 page = self.page 919 fmt = page.formatter 920 921 output = [] 922 output.append(fmt.table_row(on=1)) 923 924 output.append(fmt.table_cell(on=1, attrs={"class" : "event-full-day-heading", "colspan" : str(colspan)})) 925 output.append(self.writeDayHeading(date)) 926 output.append(fmt.table_cell(on=0)) 927 928 output.append(fmt.table_row(on=0)) 929 return "".join(output) 930 931 def writeEmptyDay(self, date): 932 page = self.page 933 fmt = page.formatter 934 935 output = [] 936 output.append(fmt.table_row(on=1)) 937 938 output.append(fmt.table_cell(on=1, 939 attrs={"class" : "event-day-content event-day-empty"})) 940 941 output.append(fmt.table_row(on=0)) 942 return "".join(output) 943 944 def writeDaySlots(self, date, full_coverage, day_slots): 945 946 """ 947 Given a 'date', non-empty 'full_coverage' for the day concerned, and a 948 non-empty mapping of 'day_slots' (from locations to event collections), 949 output the day slots for the day. 950 """ 951 952 page = self.page 953 fmt = page.formatter 954 955 output = [] 956 957 locations = day_slots.keys() 958 locations.sort(sort_none_first) 959 960 # Traverse the time scale of the full coverage, visiting each slot to 961 # determine whether it provides content for each period. 962 963 scale = getCoverageScale(full_coverage) 964 965 # Define a mapping of events to rowspans. 966 967 rowspans = {} 968 969 # Populate each period with event details, recording how many periods 970 # each event populates. 971 972 day_rows = [] 973 974 for period in scale: 975 976 # Ignore timespans before this day. 977 978 if period != date: 979 continue 980 981 # Visit each slot corresponding to a location (or no location). 982 983 day_row = [] 984 985 for location in locations: 986 987 # Visit each coverage span, presenting the events in the span. 988 989 for events in day_slots[location]: 990 event = self.getActiveEvent(period, events) 991 if event is not None: 992 if not rowspans.has_key(event): 993 rowspans[event] = 1 994 else: 995 rowspans[event] += 1 996 day_row.append((location, event)) 997 998 day_rows.append((period, day_row)) 999 1000 # Output the locations. 1001 1002 output.append(fmt.table_row(on=1)) 1003 1004 # Add a spacer. 1005 1006 output.append(self.writeDaySpacer(colspan=2, cls="location")) 1007 1008 for location in locations: 1009 1010 # Add spacers to the column spans. 1011 1012 columns = len(day_slots[location]) * 2 - 1 1013 output.append(fmt.table_cell(on=1, attrs={"class" : "event-location-heading", "colspan" : str(columns)})) 1014 output.append(fmt.text(location or "")) 1015 output.append(fmt.table_cell(on=0)) 1016 1017 # Add a trailing spacer. 1018 1019 output.append(self.writeDaySpacer(cls="location")) 1020 1021 output.append(fmt.table_row(on=0)) 1022 1023 # Output the periods with event details. 1024 1025 period = None 1026 events_written = set() 1027 1028 for period, day_row in day_rows: 1029 1030 # Write an empty heading for the start of the day where the first 1031 # applicable timespan starts before this day. 1032 1033 if period.start < date: 1034 output.append(fmt.table_row(on=1)) 1035 output.append(self.writeDayScaleHeading("")) 1036 1037 # Otherwise, write a heading describing the time. 1038 1039 else: 1040 output.append(fmt.table_row(on=1)) 1041 output.append(self.writeDayScaleHeading(period.start.time_string())) 1042 1043 output.append(self.writeDaySpacer()) 1044 1045 # Visit each slot corresponding to a location (or no location). 1046 1047 for location, event in day_row: 1048 1049 # Output each location slot's contribution. 1050 1051 if event is None or event not in events_written: 1052 output.append(self.writeDaySlot(period, event, event is None and 1 or rowspans[event])) 1053 if event is not None: 1054 events_written.add(event) 1055 1056 # Add a trailing spacer. 1057 1058 output.append(self.writeDaySpacer()) 1059 1060 output.append(fmt.table_row(on=0)) 1061 1062 # Write a final time heading if the last period ends in the current day. 1063 1064 if period is not None: 1065 if period.end == date: 1066 output.append(fmt.table_row(on=1)) 1067 output.append(self.writeDayScaleHeading(period.end.time_string())) 1068 1069 for slot in day_row: 1070 output.append(self.writeDaySpacer()) 1071 output.append(self.writeEmptyDaySlot()) 1072 1073 output.append(fmt.table_row(on=0)) 1074 1075 return "".join(output) 1076 1077 def writeDayScaleHeading(self, heading): 1078 page = self.page 1079 fmt = page.formatter 1080 1081 output = [] 1082 output.append(fmt.table_cell(on=1, attrs={"class" : "event-scale-heading"})) 1083 output.append(fmt.text(heading)) 1084 output.append(fmt.table_cell(on=0)) 1085 1086 return "".join(output) 1087 1088 def getActiveEvent(self, period, events): 1089 for event in events: 1090 if period not in event: 1091 continue 1092 return event 1093 else: 1094 return None 1095 1096 def writeDaySlot(self, period, event, rowspan): 1097 page = self.page 1098 fmt = page.formatter 1099 1100 output = [] 1101 1102 if event is not None: 1103 event_summary = event.getSummary(self.parent_name) 1104 style = self.getEventStyle(event_summary) 1105 1106 output.append(fmt.table_cell(on=1, attrs={ 1107 "class" : "event-timespan-content event-timespan-busy", 1108 "style" : style, 1109 "rowspan" : str(rowspan) 1110 })) 1111 output.append(self.writeEventSummaryBox(event)) 1112 output.append(fmt.table_cell(on=0)) 1113 else: 1114 output.append(self.writeEmptyDaySlot()) 1115 1116 return "".join(output) 1117 1118 def writeEmptyDaySlot(self): 1119 page = self.page 1120 fmt = page.formatter 1121 1122 output = [] 1123 1124 output.append(fmt.table_cell(on=1, 1125 attrs={"class" : "event-timespan-content event-timespan-empty"})) 1126 output.append(fmt.table_cell(on=0)) 1127 1128 return "".join(output) 1129 1130 def writeDaySpacer(self, colspan=1, cls="timespan"): 1131 page = self.page 1132 fmt = page.formatter 1133 1134 output = [] 1135 output.append(fmt.table_cell(on=1, attrs={ 1136 "class" : "event-%s-spacer" % cls, 1137 "colspan" : str(colspan)})) 1138 output.append(fmt.table_cell(on=0)) 1139 return "".join(output) 1140 1141 # Map layout methods. 1142 1143 def writeMapTableHeading(self): 1144 page = self.page 1145 fmt = page.formatter 1146 1147 output = [] 1148 output.append(fmt.table_cell(on=1, attrs={"class" : "event-map-heading"})) 1149 output.append(self.writeMapHeading()) 1150 output.append(fmt.table_cell(on=0)) 1151 1152 return "".join(output) 1153 1154 def showDictError(self, text, pagename): 1155 page = self.page 1156 fmt = page.formatter 1157 request = page.request 1158 1159 output = [] 1160 1161 output.append(fmt.div(on=1, attrs={"class" : "event-aggregator-error"})) 1162 output.append(fmt.paragraph(on=1)) 1163 output.append(fmt.text(text)) 1164 output.append(fmt.paragraph(on=0)) 1165 output.append(fmt.paragraph(on=1)) 1166 output.append(linkToPage(request, Page(request, pagename), pagename)) 1167 output.append(fmt.paragraph(on=0)) 1168 1169 return "".join(output) 1170 1171 # HTML-related functions. 1172 1173 def getColour(s): 1174 colour = [0, 0, 0] 1175 digit = 0 1176 for c in s: 1177 colour[digit] += ord(c) 1178 colour[digit] = colour[digit] % 256 1179 digit += 1 1180 digit = digit % 3 1181 return tuple(colour) 1182 1183 def getBlackOrWhite(colour): 1184 if sum(colour) / 3.0 > 127: 1185 return (0, 0, 0) 1186 else: 1187 return (255, 255, 255) 1188 1189 # Macro functions. 1190 1191 def execute(macro, args): 1192 1193 """ 1194 Execute the 'macro' with the given 'args': an optional list of selected 1195 category names (categories whose pages are to be shown), together with 1196 optional named arguments of the following forms: 1197 1198 start=YYYY-MM shows event details starting from the specified month 1199 start=YYYY-MM-DD shows event details starting from the specified day 1200 start=current-N shows event details relative to the current month 1201 (or relative to the current day in "day" mode) 1202 end=YYYY-MM shows event details ending at the specified month 1203 end=YYYY-MM-DD shows event details ending on the specified day 1204 end=current+N shows event details relative to the current month 1205 (or relative to the current day in "day" mode) 1206 1207 mode=calendar shows a calendar view of events 1208 mode=day shows a calendar day view of events 1209 mode=list shows a list of events by month 1210 mode=table shows a table of events 1211 mode=map shows a map of events 1212 1213 calendar=NAME uses the given NAME to provide request parameters which 1214 can be used to control the calendar view 1215 1216 template=PAGE uses the given PAGE as the default template for new 1217 events (or the default template from the configuration 1218 if not specified) 1219 1220 parent=PAGE uses the given PAGE as the parent of any new event page 1221 1222 Calendar view options: 1223 1224 names=daily shows the name of an event on every day of that event 1225 names=weekly shows the name of an event once per week 1226 1227 Map view options: 1228 1229 map=NAME uses the given NAME as the map image, where an entry for 1230 the map must be found in the EventMaps page (or another 1231 page specified in the configuration by the 1232 'event_aggregator_maps_page' setting) along with an 1233 attached map image 1234 """ 1235 1236 request = macro.request 1237 fmt = macro.formatter 1238 page = fmt.page 1239 _ = request.getText 1240 1241 parser_cls = getParserClass(request, page.pi["format"]) 1242 1243 # Interpret the arguments. 1244 1245 try: 1246 parsed_args = args and wikiutil.parse_quoted_separated(args, name_value=False) or [] 1247 except AttributeError: 1248 parsed_args = args.split(",") 1249 1250 parsed_args = [arg for arg in parsed_args if arg] 1251 1252 # Get special arguments. 1253 1254 category_names = [] 1255 raw_calendar_start = None 1256 raw_calendar_end = None 1257 calendar_start = None 1258 calendar_end = None 1259 mode = None 1260 name_usage = "weekly" 1261 calendar_name = None 1262 template_name = getattr(request.cfg, "event_aggregator_new_event_template", "EventTemplate") 1263 parent_name = None 1264 map_name = None 1265 1266 for arg in parsed_args: 1267 if arg.startswith("start="): 1268 raw_calendar_start = arg[6:] 1269 1270 elif arg.startswith("end="): 1271 raw_calendar_end = arg[4:] 1272 1273 elif arg.startswith("mode="): 1274 mode = arg[5:] 1275 1276 elif arg.startswith("names="): 1277 name_usage = arg[6:] 1278 1279 elif arg.startswith("calendar="): 1280 calendar_name = arg[9:] 1281 1282 elif arg.startswith("template="): 1283 template_name = arg[9:] 1284 1285 elif arg.startswith("parent="): 1286 parent_name = arg[7:] 1287 1288 elif arg.startswith("map="): 1289 map_name = arg[4:] 1290 1291 else: 1292 category_names.append(arg) 1293 1294 # Find request parameters to override settings. 1295 1296 mode = getQualifiedParameter(request, calendar_name, "mode", mode or "calendar") 1297 1298 # Different modes require different levels of precision by default. 1299 1300 resolution = getQualifiedParameter(request, calendar_name, "resolution", mode == "day" and "date" or "month") 1301 resolution = mode == "calendar" and "month" or resolution 1302 1303 if resolution == "date": 1304 get_date = getParameterDate 1305 get_form_date = getFormDate 1306 else: 1307 get_date = getParameterMonth 1308 get_form_date = getFormMonth 1309 1310 # Determine the limits of the calendar. 1311 1312 original_calendar_start = calendar_start = get_date(raw_calendar_start) 1313 original_calendar_end = calendar_end = get_date(raw_calendar_end) 1314 1315 calendar_start = get_form_date(request, calendar_name, "start") or calendar_start 1316 calendar_end = get_form_date(request, calendar_name, "end") or calendar_end 1317 1318 # Get the events according to the resolution of the calendar. 1319 1320 event_pages = getPagesFromResults(getAllCategoryPages(category_names, request), request) 1321 events = getEventsFromPages(event_pages) 1322 all_shown_events = getEventsInPeriod(events, getCalendarPeriod(calendar_start, calendar_end)) 1323 earliest, latest = getEventLimits(all_shown_events) 1324 1325 # Get a concrete period of time. 1326 1327 first, last = getConcretePeriod(calendar_start, calendar_end, earliest, latest, resolution) 1328 1329 # Define a view of the calendar, retaining useful navigational information. 1330 1331 view = View(page, calendar_name, raw_calendar_start, raw_calendar_end, 1332 original_calendar_start, original_calendar_end, calendar_start, calendar_end, 1333 first, last, category_names, template_name, parent_name, mode, resolution, 1334 name_usage, map_name) 1335 1336 # Make a calendar. 1337 1338 output = [] 1339 1340 output.append(fmt.div(on=1, css_class="event-calendar")) 1341 1342 # Output download controls. 1343 1344 output.append(fmt.div(on=1, css_class="event-controls")) 1345 output.append(view.writeDownloadControls()) 1346 output.append(fmt.div(on=0)) 1347 1348 # Output a table. 1349 1350 if mode == "table": 1351 1352 # Start of table view output. 1353 1354 output.append(fmt.table(on=1, attrs={"tableclass" : "event-table"})) 1355 1356 output.append(fmt.table_row(on=1)) 1357 output.append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) 1358 output.append(fmt.text(_("Event dates"))) 1359 output.append(fmt.table_cell(on=0)) 1360 output.append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) 1361 output.append(fmt.text(_("Event location"))) 1362 output.append(fmt.table_cell(on=0)) 1363 output.append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) 1364 output.append(fmt.text(_("Event details"))) 1365 output.append(fmt.table_cell(on=0)) 1366 output.append(fmt.table_row(on=0)) 1367 1368 # Show the events in order. 1369 1370 for event in all_shown_events: 1371 event_page = event.getPage() 1372 event_summary = event.getSummary(parent_name) 1373 event_details = event.getDetails() 1374 1375 # Prepare CSS classes with category-related styling. 1376 1377 css_classes = ["event-table-details"] 1378 1379 for topic in event_details.get("topics") or event_details.get("categories") or []: 1380 1381 # Filter the category text to avoid illegal characters. 1382 1383 css_classes.append("event-table-category-%s" % "".join(filter(lambda c: c.isalnum(), topic))) 1384 1385 attrs = {"class" : " ".join(css_classes)} 1386 1387 output.append(fmt.table_row(on=1)) 1388 1389 # Start and end dates. 1390 1391 output.append(fmt.table_cell(on=1, attrs=attrs)) 1392 output.append(fmt.span(on=1)) 1393 output.append(fmt.text(str(event_details["start"]))) 1394 output.append(fmt.span(on=0)) 1395 1396 if event_details["start"] != event_details["end"]: 1397 output.append(fmt.text(" - ")) 1398 output.append(fmt.span(on=1)) 1399 output.append(fmt.text(str(event_details["end"]))) 1400 output.append(fmt.span(on=0)) 1401 1402 output.append(fmt.table_cell(on=0)) 1403 1404 # Location. 1405 1406 output.append(fmt.table_cell(on=1, attrs=attrs)) 1407 1408 if event_details.has_key("location"): 1409 output.append(formatText(event_details["location"], request, fmt, parser_cls)) 1410 1411 output.append(fmt.table_cell(on=0)) 1412 1413 # Link to the page using the summary. 1414 1415 output.append(fmt.table_cell(on=1, attrs=attrs)) 1416 output.append(event_page.linkToPage(request, event_summary)) 1417 output.append(fmt.table_cell(on=0)) 1418 1419 output.append(fmt.table_row(on=0)) 1420 1421 # End of table view output. 1422 1423 output.append(fmt.table(on=0)) 1424 1425 # Output a map view. 1426 1427 elif mode == "map": 1428 1429 # Special dictionary pages. 1430 1431 maps_page = getattr(request.cfg, "event_aggregator_maps_page", "EventMapsDict") 1432 locations_page = getattr(request.cfg, "event_aggregator_locations_page", "EventLocationsDict") 1433 1434 map_image = None 1435 1436 # Get the maps and locations. 1437 1438 if request.user.may.read(maps_page): 1439 maps = request.dicts.dict(maps_page) 1440 else: 1441 maps = None 1442 1443 if request.user.may.read(locations_page): 1444 locations = request.dicts.dict(locations_page) 1445 else: 1446 locations = None 1447 1448 # Get the map image definition. 1449 1450 if maps is not None and map_name is not None: 1451 try: 1452 map_details = maps[map_name].split() 1453 1454 map_bottom_left_latitude, map_bottom_left_longitude, map_top_right_latitude, map_top_right_longitude = \ 1455 map(getMapReference, map_details[:4]) 1456 map_width, map_height = map(int, map_details[4:6]) 1457 map_image = map_details[6] 1458 1459 map_x_scale = map_width / (map_top_right_longitude - map_bottom_left_longitude).to_degrees() 1460 map_y_scale = map_height / (map_top_right_latitude - map_bottom_left_latitude).to_degrees() 1461 1462 except (KeyError, ValueError): 1463 pass 1464 1465 # Report errors. 1466 1467 if maps is None: 1468 output.append(view.showDictError( 1469 _("You do not have read access to the maps page:"), 1470 maps_page)) 1471 1472 elif map_name is None: 1473 output.append(view.showDictError( 1474 _("Please specify a valid map name corresponding to an entry on the following page:"), 1475 maps_page)) 1476 1477 elif map_image is None: 1478 output.append(view.showDictError( 1479 _("Please specify a valid entry for %s on the following page:") % map_name, 1480 maps_page)) 1481 1482 elif locations is None: 1483 output.append(view.showDictError( 1484 _("You do not have read access to the locations page:"), 1485 locations_page)) 1486 1487 # Attempt to show the map. 1488 1489 else: 1490 1491 # Get events by position. 1492 1493 events_by_location = {} 1494 1495 for event in all_shown_events: 1496 event_details = event.getDetails() 1497 1498 location = event_details.get("location") 1499 1500 # Use a normalised location if possible. 1501 1502 location = location and getNormalisedLocation(location) or location 1503 1504 if not events_by_location.has_key(location): 1505 events_by_location[location] = [] 1506 events_by_location[location].append(event) 1507 1508 # Get the map image URL. 1509 1510 map_image_url = AttachFile.getAttachUrl(maps_page, map_image, request) 1511 1512 # Start of map view output. 1513 1514 output.append(fmt.div(on=1, css_class="event-map")) 1515 1516 output.append(fmt.table(on=1)) 1517 1518 output.append(fmt.table_row(on=1)) 1519 output.append(view.writeMapTableHeading()) 1520 output.append(fmt.table_row(on=0)) 1521 1522 output.append(fmt.table_row(on=1)) 1523 output.append(fmt.table_cell(on=1)) 1524 1525 output.append(fmt.div(on=1, css_class="event-map-container")) 1526 output.append(fmt.image(map_image_url)) 1527 output.append(fmt.number_list(on=1)) 1528 1529 # Show the events in the map. 1530 1531 for location, events in events_by_location.items(): 1532 1533 # Look up the position of a location using the locations page. 1534 1535 latitude, longitude = None, None 1536 1537 if location is not None: 1538 try: 1539 latitude, longitude = map(getMapReference, locations[location].split()) 1540 except (KeyError, ValueError): 1541 pass 1542 1543 # Skip unpositioned locations and locations outside the map. 1544 1545 if latitude is None or longitude is None or \ 1546 latitude < map_bottom_left_latitude or \ 1547 longitude < map_bottom_left_longitude or \ 1548 latitude > map_top_right_latitude or \ 1549 longitude > map_top_right_longitude: 1550 continue 1551 1552 # Get the position and dimensions of the map marker. 1553 # NOTE: Use one degree as the marker size. 1554 1555 marker_x, marker_y = getPositionForCentrePoint( 1556 getPositionForReference(map_top_right_latitude, longitude, latitude, map_bottom_left_longitude, 1557 map_x_scale, map_y_scale), 1558 map_x_scale, map_y_scale) 1559 1560 # Put a marker on the map. 1561 1562 output.append(fmt.listitem(on=1, css_class="event-map-label")) 1563 1564 # Have a positioned marker for the print mode. 1565 1566 output.append(fmt.div(on=1, css_class="event-map-label-only", 1567 style="left:%dpx; top:%dpx; min-width:%dpx; min-height:%dpx") % ( 1568 marker_x, marker_y, map_x_scale, map_y_scale)) 1569 output.append(fmt.div(on=0)) 1570 1571 # Have a marker containing a pop-up when using the screen mode, 1572 # providing a normal block when using the print mode. 1573 1574 output.append(fmt.div(on=1, css_class="event-map-label", 1575 style="left:%dpx; top:%dpx; min-width:%dpx; min-height:%dpx") % ( 1576 marker_x, marker_y, map_x_scale, map_y_scale)) 1577 output.append(fmt.div(on=1, css_class="event-map-details")) 1578 output.append(fmt.div(on=1, css_class="event-map-shadow")) 1579 output.append(fmt.div(on=1, css_class="event-map-description")) 1580 1581 output.append(fmt.paragraph(on=1)) 1582 output.append(fmt.text(location)) 1583 output.append(fmt.paragraph(on=0)) 1584 1585 output.append(fmt.bullet_list(on=1, attr={"class" : "event-map-description-events"})) 1586 1587 for event in events: 1588 event_page = event.getPage() 1589 event_summary = event.getSummary(parent_name) 1590 1591 # Link to the page using the summary. 1592 1593 output.append(fmt.listitem(on=1)) 1594 output.append(event_page.linkToPage(request, event_summary)) 1595 output.append(fmt.listitem(on=0)) 1596 1597 output.append(fmt.bullet_list(on=0)) 1598 1599 output.append(fmt.div(on=0)) 1600 output.append(fmt.div(on=0)) 1601 output.append(fmt.div(on=0)) 1602 output.append(fmt.div(on=0)) 1603 output.append(fmt.listitem(on=0)) 1604 1605 # End of map view output. 1606 1607 output.append(fmt.number_list(on=0)) 1608 output.append(fmt.div(on=0)) 1609 output.append(fmt.table_cell(on=0)) 1610 output.append(fmt.table_row(on=0)) 1611 output.append(fmt.table(on=0)) 1612 output.append(fmt.div(on=0)) 1613 1614 # Output a list. 1615 1616 elif mode == "list": 1617 1618 # Start of list view output. 1619 1620 output.append(fmt.bullet_list(on=1, attr={"class" : "event-listings"})) 1621 1622 # Output a list. 1623 1624 for period in first.until(last): 1625 1626 output.append(fmt.listitem(on=1, attr={"class" : "event-listings-period"})) 1627 output.append(fmt.div(on=1, attr={"class" : "event-listings-heading"})) 1628 1629 # Either write a date heading or produce links for navigable 1630 # calendars. 1631 1632 output.append(view.writeDateHeading(period)) 1633 1634 output.append(fmt.div(on=0)) 1635 1636 output.append(fmt.bullet_list(on=1, attr={"class" : "event-period-listings"})) 1637 1638 # Show the events in order. 1639 1640 for event in getEventsInPeriod(all_shown_events, getCalendarPeriod(period, period)): 1641 event_page = event.getPage() 1642 event_details = event.getDetails() 1643 event_summary = event.getSummary(parent_name) 1644 1645 output.append(fmt.listitem(on=1, attr={"class" : "event-listing"})) 1646 1647 # Link to the page using the summary. 1648 1649 output.append(fmt.paragraph(on=1)) 1650 output.append(event_page.linkToPage(request, event_summary)) 1651 output.append(fmt.paragraph(on=0)) 1652 1653 # Start and end dates. 1654 1655 output.append(fmt.paragraph(on=1)) 1656 output.append(fmt.span(on=1)) 1657 output.append(fmt.text(str(event_details["start"]))) 1658 output.append(fmt.span(on=0)) 1659 output.append(fmt.text(" - ")) 1660 output.append(fmt.span(on=1)) 1661 output.append(fmt.text(str(event_details["end"]))) 1662 output.append(fmt.span(on=0)) 1663 output.append(fmt.paragraph(on=0)) 1664 1665 # Location. 1666 1667 if event_details.has_key("location"): 1668 output.append(fmt.paragraph(on=1)) 1669 output.append(formatText(event_details["location"], request, fmt, parser_cls)) 1670 output.append(fmt.paragraph(on=1)) 1671 1672 # Topics. 1673 1674 if event_details.has_key("topics") or event_details.has_key("categories"): 1675 output.append(fmt.bullet_list(on=1, attr={"class" : "event-topics"})) 1676 1677 for topic in event_details.get("topics") or event_details.get("categories") or []: 1678 output.append(fmt.listitem(on=1)) 1679 output.append(formatText(topic, request, fmt, parser_cls)) 1680 output.append(fmt.listitem(on=0)) 1681 1682 output.append(fmt.bullet_list(on=0)) 1683 1684 output.append(fmt.listitem(on=0)) 1685 1686 output.append(fmt.bullet_list(on=0)) 1687 1688 # End of list view output. 1689 1690 output.append(fmt.bullet_list(on=0)) 1691 1692 # Output a month calendar. This shows month-by-month data. 1693 1694 elif mode == "calendar": 1695 1696 # Visit all months in the requested range, or across known events. 1697 1698 for month in first.months_until(last): 1699 1700 # Output a month. 1701 1702 output.append(fmt.table(on=1, attrs={"tableclass" : "event-month"})) 1703 1704 # Either write a month heading or produce links for navigable 1705 # calendars. 1706 1707 output.append(view.writeMonthTableHeading(month)) 1708 1709 # Weekday headings. 1710 1711 output.append(view.writeWeekdayHeadings()) 1712 1713 # Process the days of the month. 1714 1715 start_weekday, number_of_days = month.month_properties() 1716 1717 # The start weekday is the weekday of day number 1. 1718 # Find the first day of the week, counting from below zero, if 1719 # necessary, in order to land on the first day of the month as 1720 # day number 1. 1721 1722 first_day = 1 - start_weekday 1723 1724 while first_day <= number_of_days: 1725 1726 # Find events in this week and determine how to mark them on the 1727 # calendar. 1728 1729 week_start = month.as_date(max(first_day, 1)) 1730 week_end = month.as_date(min(first_day + 6, number_of_days)) 1731 1732 full_coverage, week_slots = getCoverage( 1733 getEventsInPeriod(all_shown_events, getCalendarPeriod(week_start, week_end))) 1734 1735 # Output a week, starting with the day numbers. 1736 1737 output.append(view.writeDayNumbers(first_day, number_of_days, month, full_coverage)) 1738 1739 # Either generate empty days... 1740 1741 if not week_slots: 1742 output.append(view.writeEmptyWeek(first_day, number_of_days)) 1743 1744 # Or generate each set of scheduled events... 1745 1746 else: 1747 output.append(view.writeWeekSlots(first_day, number_of_days, month, week_end, week_slots)) 1748 1749 # Process the next week... 1750 1751 first_day += 7 1752 1753 # End of month. 1754 1755 output.append(fmt.table(on=0)) 1756 1757 # Output a day view. 1758 1759 elif mode == "day": 1760 1761 # Visit all days in the requested range, or across known events. 1762 1763 for date in first.days_until(last): 1764 1765 output.append(fmt.table(on=1, attrs={"tableclass" : "event-calendar-day"})) 1766 1767 full_coverage, day_slots = getCoverage( 1768 getEventsInPeriod(all_shown_events, getCalendarPeriod(date, date)), "datetime") 1769 1770 # Work out how many columns the day title will need. 1771 # Include spacers after the scale and each event column. 1772 1773 colspan = sum(map(len, day_slots.values())) * 2 + 2 1774 1775 output.append(view.writeDayTableHeading(date, colspan)) 1776 1777 # Either generate empty days... 1778 1779 if not day_slots: 1780 output.append(view.writeEmptyDay(date)) 1781 1782 # Or generate each set of scheduled events... 1783 1784 else: 1785 output.append(view.writeDaySlots(date, full_coverage, day_slots)) 1786 1787 # End of day. 1788 1789 output.append(fmt.table(on=0)) 1790 1791 # Output view controls. 1792 1793 output.append(fmt.div(on=1, css_class="event-controls")) 1794 output.append(view.writeViewControls()) 1795 output.append(fmt.div(on=0)) 1796 1797 # Close the calendar region. 1798 1799 output.append(fmt.div(on=0)) 1800 1801 return ''.join(output) 1802 1803 # vim: tabstop=4 expandtab shiftwidth=4