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