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 self.calendar_end 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 # HTML-related functions. 1178 1179 def getColour(s): 1180 colour = [0, 0, 0] 1181 digit = 0 1182 for c in s: 1183 colour[digit] += ord(c) 1184 colour[digit] = colour[digit] % 256 1185 digit += 1 1186 digit = digit % 3 1187 return tuple(colour) 1188 1189 def getBlackOrWhite(colour): 1190 if sum(colour) / 3.0 > 127: 1191 return (0, 0, 0) 1192 else: 1193 return (255, 255, 255) 1194 1195 # Macro functions. 1196 1197 def execute(macro, args): 1198 1199 """ 1200 Execute the 'macro' with the given 'args': an optional list of selected 1201 category names (categories whose pages are to be shown), together with 1202 optional named arguments of the following forms: 1203 1204 start=YYYY-MM shows event details starting from the specified month 1205 start=YYYY-MM-DD shows event details starting from the specified day 1206 start=current-N shows event details relative to the current month 1207 (or relative to the current day in "day" mode) 1208 end=YYYY-MM shows event details ending at the specified month 1209 end=YYYY-MM-DD shows event details ending on the specified day 1210 end=current+N shows event details relative to the current month 1211 (or relative to the current day in "day" mode) 1212 1213 mode=calendar shows a calendar view of events 1214 mode=day shows a calendar day view of events 1215 mode=list shows a list of events by month 1216 mode=table shows a table of events 1217 mode=map shows a map of events 1218 1219 calendar=NAME uses the given NAME to provide request parameters which 1220 can be used to control the calendar view 1221 1222 template=PAGE uses the given PAGE as the default template for new 1223 events (or the default template from the configuration 1224 if not specified) 1225 1226 parent=PAGE uses the given PAGE as the parent of any new event page 1227 1228 Calendar view options: 1229 1230 names=daily shows the name of an event on every day of that event 1231 names=weekly shows the name of an event once per week 1232 1233 Map view options: 1234 1235 map=NAME uses the given NAME as the map image, where an entry for 1236 the map must be found in the EventMaps page (or another 1237 page specified in the configuration by the 1238 'event_aggregator_maps_page' setting) along with an 1239 attached map image 1240 """ 1241 1242 request = macro.request 1243 fmt = macro.formatter 1244 page = fmt.page 1245 _ = request.getText 1246 1247 parser_cls = getParserClass(request, page.pi["format"]) 1248 1249 # Interpret the arguments. 1250 1251 try: 1252 parsed_args = args and wikiutil.parse_quoted_separated(args, name_value=False) or [] 1253 except AttributeError: 1254 parsed_args = args.split(",") 1255 1256 parsed_args = [arg for arg in parsed_args if arg] 1257 1258 # Get special arguments. 1259 1260 category_names = [] 1261 raw_calendar_start = None 1262 raw_calendar_end = None 1263 calendar_start = None 1264 calendar_end = None 1265 mode = None 1266 name_usage = "weekly" 1267 calendar_name = None 1268 template_name = getattr(request.cfg, "event_aggregator_new_event_template", "EventTemplate") 1269 parent_name = None 1270 map_name = None 1271 1272 for arg in parsed_args: 1273 if arg.startswith("start="): 1274 raw_calendar_start = arg[6:] 1275 1276 elif arg.startswith("end="): 1277 raw_calendar_end = arg[4:] 1278 1279 elif arg.startswith("mode="): 1280 mode = arg[5:] 1281 1282 elif arg.startswith("names="): 1283 name_usage = arg[6:] 1284 1285 elif arg.startswith("calendar="): 1286 calendar_name = arg[9:] 1287 1288 elif arg.startswith("template="): 1289 template_name = arg[9:] 1290 1291 elif arg.startswith("parent="): 1292 parent_name = arg[7:] 1293 1294 elif arg.startswith("map="): 1295 map_name = arg[4:] 1296 1297 else: 1298 category_names.append(arg) 1299 1300 # Find request parameters to override settings. 1301 1302 mode = getQualifiedParameter(request, calendar_name, "mode", mode or "calendar") 1303 1304 # Different modes require different levels of precision by default. 1305 1306 resolution = getQualifiedParameter(request, calendar_name, "resolution", mode == "day" and "date" or "month") 1307 resolution = mode == "calendar" and "month" or resolution 1308 1309 if resolution == "date": 1310 get_date = getParameterDate 1311 get_form_date = getFormDate 1312 else: 1313 get_date = getParameterMonth 1314 get_form_date = getFormMonth 1315 1316 # Determine the limits of the calendar. 1317 1318 original_calendar_start = calendar_start = get_date(raw_calendar_start) 1319 original_calendar_end = calendar_end = get_date(raw_calendar_end) 1320 1321 calendar_start = get_form_date(request, calendar_name, "start") or calendar_start 1322 calendar_end = get_form_date(request, calendar_name, "end") or calendar_end 1323 1324 # Get the events according to the resolution of the calendar. 1325 1326 event_pages = getPagesFromResults(getAllCategoryPages(category_names, request), request) 1327 events = getEventsFromPages(event_pages) 1328 all_shown_events = getEventsInPeriod(events, getCalendarPeriod(calendar_start, calendar_end)) 1329 earliest, latest = getEventLimits(all_shown_events) 1330 1331 # Get a concrete period of time. 1332 1333 first, last = getConcretePeriod(calendar_start, calendar_end, earliest, latest, resolution) 1334 1335 # Define a view of the calendar, retaining useful navigational information. 1336 1337 view = View(page, calendar_name, raw_calendar_start, raw_calendar_end, 1338 original_calendar_start, original_calendar_end, calendar_start, calendar_end, 1339 first, last, category_names, template_name, parent_name, mode, resolution, 1340 name_usage, map_name) 1341 1342 # Make a calendar. 1343 1344 output = [] 1345 1346 output.append(fmt.div(on=1, css_class="event-calendar")) 1347 1348 # Output download controls. 1349 1350 output.append(fmt.div(on=1, css_class="event-controls")) 1351 output.append(view.writeDownloadControls()) 1352 output.append(fmt.div(on=0)) 1353 1354 # Output a table. 1355 1356 if mode == "table": 1357 1358 # Start of table view output. 1359 1360 output.append(fmt.table(on=1, attrs={"tableclass" : "event-table"})) 1361 1362 output.append(fmt.table_row(on=1)) 1363 output.append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) 1364 output.append(fmt.text(_("Event dates"))) 1365 output.append(fmt.table_cell(on=0)) 1366 output.append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) 1367 output.append(fmt.text(_("Event location"))) 1368 output.append(fmt.table_cell(on=0)) 1369 output.append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) 1370 output.append(fmt.text(_("Event details"))) 1371 output.append(fmt.table_cell(on=0)) 1372 output.append(fmt.table_row(on=0)) 1373 1374 # Show the events in order. 1375 1376 for event in all_shown_events: 1377 event_page = event.getPage() 1378 event_summary = event.getSummary(parent_name) 1379 event_details = event.getDetails() 1380 1381 # Prepare CSS classes with category-related styling. 1382 1383 css_classes = ["event-table-details"] 1384 1385 for topic in event_details.get("topics") or event_details.get("categories") or []: 1386 1387 # Filter the category text to avoid illegal characters. 1388 1389 css_classes.append("event-table-category-%s" % "".join(filter(lambda c: c.isalnum(), topic))) 1390 1391 attrs = {"class" : " ".join(css_classes)} 1392 1393 output.append(fmt.table_row(on=1)) 1394 1395 # Start and end dates. 1396 1397 output.append(fmt.table_cell(on=1, attrs=attrs)) 1398 output.append(fmt.span(on=1)) 1399 output.append(fmt.text(str(event_details["start"]))) 1400 output.append(fmt.span(on=0)) 1401 1402 if event_details["start"] != event_details["end"]: 1403 output.append(fmt.text(" - ")) 1404 output.append(fmt.span(on=1)) 1405 output.append(fmt.text(str(event_details["end"]))) 1406 output.append(fmt.span(on=0)) 1407 1408 output.append(fmt.table_cell(on=0)) 1409 1410 # Location. 1411 1412 output.append(fmt.table_cell(on=1, attrs=attrs)) 1413 1414 if event_details.has_key("location"): 1415 output.append(formatText(event_details["location"], request, fmt, parser_cls)) 1416 1417 output.append(fmt.table_cell(on=0)) 1418 1419 # Link to the page using the summary. 1420 1421 output.append(fmt.table_cell(on=1, attrs=attrs)) 1422 output.append(event_page.linkToPage(request, event_summary)) 1423 output.append(fmt.table_cell(on=0)) 1424 1425 output.append(fmt.table_row(on=0)) 1426 1427 # End of table view output. 1428 1429 output.append(fmt.table(on=0)) 1430 1431 # Output a map view. 1432 1433 elif mode == "map": 1434 1435 # Special dictionary pages. 1436 1437 maps_page = getattr(request.cfg, "event_aggregator_maps_page", "EventMapsDict") 1438 locations_page = getattr(request.cfg, "event_aggregator_locations_page", "EventLocationsDict") 1439 1440 map_image = None 1441 1442 # Get the maps and locations. 1443 1444 if request.user.may.read(maps_page): 1445 maps = request.dicts.dict(maps_page) 1446 else: 1447 maps = None 1448 1449 if request.user.may.read(locations_page): 1450 locations = request.dicts.dict(locations_page) 1451 else: 1452 locations = None 1453 1454 # Get the map image definition. 1455 1456 if maps is not None and map_name is not None: 1457 try: 1458 map_details = maps[map_name].split() 1459 1460 map_bottom_left_latitude, map_bottom_left_longitude, map_top_right_latitude, map_top_right_longitude = \ 1461 map(getMapReference, map_details[:4]) 1462 map_width, map_height = map(int, map_details[4:6]) 1463 map_image = map_details[6] 1464 1465 map_x_scale = map_width / (map_top_right_longitude - map_bottom_left_longitude).to_degrees() 1466 map_y_scale = map_height / (map_top_right_latitude - map_bottom_left_latitude).to_degrees() 1467 1468 except (KeyError, ValueError): 1469 pass 1470 1471 # Report errors. 1472 1473 if maps is None: 1474 output.append(view.showDictError( 1475 _("You do not have read access to the maps page:"), 1476 maps_page)) 1477 1478 elif map_name is None: 1479 output.append(view.showDictError( 1480 _("Please specify a valid map name corresponding to an entry on the following page:"), 1481 maps_page)) 1482 1483 elif map_image is None: 1484 output.append(view.showDictError( 1485 _("Please specify a valid entry for %s on the following page:") % map_name, 1486 maps_page)) 1487 1488 elif locations is None: 1489 output.append(view.showDictError( 1490 _("You do not have read access to the locations page:"), 1491 locations_page)) 1492 1493 # Attempt to show the map. 1494 1495 else: 1496 1497 # Get events by position. 1498 1499 events_by_location = {} 1500 1501 for event in all_shown_events: 1502 event_details = event.getDetails() 1503 1504 location = event_details.get("location") 1505 1506 # Use a normalised location if possible. 1507 1508 location = location and getNormalisedLocation(location) or location 1509 1510 if not events_by_location.has_key(location): 1511 events_by_location[location] = [] 1512 events_by_location[location].append(event) 1513 1514 # Get the map image URL. 1515 1516 map_image_url = AttachFile.getAttachUrl(maps_page, map_image, request) 1517 1518 # Start of map view output. 1519 1520 output.append(fmt.div(on=1, css_class="event-map")) 1521 1522 output.append(fmt.table(on=1)) 1523 1524 output.append(fmt.table_row(on=1)) 1525 output.append(view.writeMapTableHeading()) 1526 output.append(fmt.table_row(on=0)) 1527 1528 output.append(fmt.table_row(on=1)) 1529 output.append(fmt.table_cell(on=1)) 1530 1531 output.append(fmt.div(on=1, css_class="event-map-container")) 1532 output.append(fmt.image(map_image_url)) 1533 output.append(fmt.number_list(on=1)) 1534 1535 # Show the events in the map. 1536 1537 for location, events in events_by_location.items(): 1538 1539 # Look up the position of a location using the locations page. 1540 1541 latitude, longitude = None, None 1542 1543 if location is not None: 1544 try: 1545 latitude, longitude = map(getMapReference, locations[location].split()) 1546 except (KeyError, ValueError): 1547 pass 1548 1549 # Skip unpositioned locations and locations outside the map. 1550 1551 if latitude is None or longitude is None or \ 1552 latitude < map_bottom_left_latitude or \ 1553 longitude < map_bottom_left_longitude or \ 1554 latitude > map_top_right_latitude or \ 1555 longitude > map_top_right_longitude: 1556 continue 1557 1558 # Get the position and dimensions of the map marker. 1559 # NOTE: Use one degree as the marker size. 1560 1561 marker_x, marker_y = getPositionForCentrePoint( 1562 getPositionForReference(map_top_right_latitude, longitude, latitude, map_bottom_left_longitude, 1563 map_x_scale, map_y_scale), 1564 map_x_scale, map_y_scale) 1565 1566 # Put a marker on the map. 1567 1568 output.append(fmt.listitem(on=1, css_class="event-map-label")) 1569 1570 # Have a positioned marker for the print mode. 1571 1572 output.append(fmt.div(on=1, css_class="event-map-label-only", 1573 style="left:%dpx; top:%dpx; min-width:%dpx; min-height:%dpx") % ( 1574 marker_x, marker_y, map_x_scale, map_y_scale)) 1575 output.append(fmt.div(on=0)) 1576 1577 # Have a marker containing a pop-up when using the screen mode, 1578 # providing a normal block when using the print mode. 1579 1580 output.append(fmt.div(on=1, css_class="event-map-label", 1581 style="left:%dpx; top:%dpx; min-width:%dpx; min-height:%dpx") % ( 1582 marker_x, marker_y, map_x_scale, map_y_scale)) 1583 output.append(fmt.div(on=1, css_class="event-map-details")) 1584 output.append(fmt.div(on=1, css_class="event-map-shadow")) 1585 output.append(fmt.div(on=1, css_class="event-map-description")) 1586 1587 output.append(fmt.paragraph(on=1)) 1588 output.append(fmt.text(location)) 1589 output.append(fmt.paragraph(on=0)) 1590 1591 output.append(fmt.bullet_list(on=1, attr={"class" : "event-map-description-events"})) 1592 1593 for event in events: 1594 event_page = event.getPage() 1595 event_summary = event.getSummary(parent_name) 1596 1597 # Link to the page using the summary. 1598 1599 output.append(fmt.listitem(on=1)) 1600 output.append(event_page.linkToPage(request, event_summary)) 1601 output.append(fmt.listitem(on=0)) 1602 1603 output.append(fmt.bullet_list(on=0)) 1604 1605 output.append(fmt.div(on=0)) 1606 output.append(fmt.div(on=0)) 1607 output.append(fmt.div(on=0)) 1608 output.append(fmt.div(on=0)) 1609 output.append(fmt.listitem(on=0)) 1610 1611 # End of map view output. 1612 1613 output.append(fmt.number_list(on=0)) 1614 output.append(fmt.div(on=0)) 1615 output.append(fmt.table_cell(on=0)) 1616 output.append(fmt.table_row(on=0)) 1617 output.append(fmt.table(on=0)) 1618 output.append(fmt.div(on=0)) 1619 1620 # Output a list. 1621 1622 elif mode == "list": 1623 1624 # Start of list view output. 1625 1626 output.append(fmt.bullet_list(on=1, attr={"class" : "event-listings"})) 1627 1628 # Output a list. 1629 1630 for period in first.until(last): 1631 1632 output.append(fmt.listitem(on=1, attr={"class" : "event-listings-period"})) 1633 output.append(fmt.div(on=1, attr={"class" : "event-listings-heading"})) 1634 1635 # Either write a date heading or produce links for navigable 1636 # calendars. 1637 1638 output.append(view.writeDateHeading(period)) 1639 1640 output.append(fmt.div(on=0)) 1641 1642 output.append(fmt.bullet_list(on=1, attr={"class" : "event-period-listings"})) 1643 1644 # Show the events in order. 1645 1646 for event in getEventsInPeriod(all_shown_events, getCalendarPeriod(period, period)): 1647 event_page = event.getPage() 1648 event_details = event.getDetails() 1649 event_summary = event.getSummary(parent_name) 1650 1651 output.append(fmt.listitem(on=1, attr={"class" : "event-listing"})) 1652 1653 # Link to the page using the summary. 1654 1655 output.append(fmt.paragraph(on=1)) 1656 output.append(event_page.linkToPage(request, event_summary)) 1657 output.append(fmt.paragraph(on=0)) 1658 1659 # Start and end dates. 1660 1661 output.append(fmt.paragraph(on=1)) 1662 output.append(fmt.span(on=1)) 1663 output.append(fmt.text(str(event_details["start"]))) 1664 output.append(fmt.span(on=0)) 1665 output.append(fmt.text(" - ")) 1666 output.append(fmt.span(on=1)) 1667 output.append(fmt.text(str(event_details["end"]))) 1668 output.append(fmt.span(on=0)) 1669 output.append(fmt.paragraph(on=0)) 1670 1671 # Location. 1672 1673 if event_details.has_key("location"): 1674 output.append(fmt.paragraph(on=1)) 1675 output.append(formatText(event_details["location"], request, fmt, parser_cls)) 1676 output.append(fmt.paragraph(on=1)) 1677 1678 # Topics. 1679 1680 if event_details.has_key("topics") or event_details.has_key("categories"): 1681 output.append(fmt.bullet_list(on=1, attr={"class" : "event-topics"})) 1682 1683 for topic in event_details.get("topics") or event_details.get("categories") or []: 1684 output.append(fmt.listitem(on=1)) 1685 output.append(formatText(topic, request, fmt, parser_cls)) 1686 output.append(fmt.listitem(on=0)) 1687 1688 output.append(fmt.bullet_list(on=0)) 1689 1690 output.append(fmt.listitem(on=0)) 1691 1692 output.append(fmt.bullet_list(on=0)) 1693 1694 # End of list view output. 1695 1696 output.append(fmt.bullet_list(on=0)) 1697 1698 # Output a month calendar. This shows month-by-month data. 1699 1700 elif mode == "calendar": 1701 1702 # Visit all months in the requested range, or across known events. 1703 1704 for month in first.months_until(last): 1705 1706 # Output a month. 1707 1708 output.append(fmt.table(on=1, attrs={"tableclass" : "event-month"})) 1709 1710 # Either write a month heading or produce links for navigable 1711 # calendars. 1712 1713 output.append(view.writeMonthTableHeading(month)) 1714 1715 # Weekday headings. 1716 1717 output.append(view.writeWeekdayHeadings()) 1718 1719 # Process the days of the month. 1720 1721 start_weekday, number_of_days = month.month_properties() 1722 1723 # The start weekday is the weekday of day number 1. 1724 # Find the first day of the week, counting from below zero, if 1725 # necessary, in order to land on the first day of the month as 1726 # day number 1. 1727 1728 first_day = 1 - start_weekday 1729 1730 while first_day <= number_of_days: 1731 1732 # Find events in this week and determine how to mark them on the 1733 # calendar. 1734 1735 week_start = month.as_date(max(first_day, 1)) 1736 week_end = month.as_date(min(first_day + 6, number_of_days)) 1737 1738 full_coverage, week_slots = getCoverage( 1739 getEventsInPeriod(all_shown_events, getCalendarPeriod(week_start, week_end))) 1740 1741 # Output a week, starting with the day numbers. 1742 1743 output.append(view.writeDayNumbers(first_day, number_of_days, month, full_coverage)) 1744 1745 # Either generate empty days... 1746 1747 if not week_slots: 1748 output.append(view.writeEmptyWeek(first_day, number_of_days)) 1749 1750 # Or generate each set of scheduled events... 1751 1752 else: 1753 output.append(view.writeWeekSlots(first_day, number_of_days, month, week_end, week_slots)) 1754 1755 # Process the next week... 1756 1757 first_day += 7 1758 1759 # End of month. 1760 1761 output.append(fmt.table(on=0)) 1762 1763 # Output a day view. 1764 1765 elif mode == "day": 1766 1767 # Visit all days in the requested range, or across known events. 1768 1769 for date in first.days_until(last): 1770 1771 output.append(fmt.table(on=1, attrs={"tableclass" : "event-calendar-day"})) 1772 1773 full_coverage, day_slots = getCoverage( 1774 getEventsInPeriod(all_shown_events, getCalendarPeriod(date, date)), "datetime") 1775 1776 # Work out how many columns the day title will need. 1777 # Include spacers after the scale and each event column. 1778 1779 colspan = sum(map(len, day_slots.values())) * 2 + 2 1780 1781 output.append(view.writeDayTableHeading(date, colspan)) 1782 1783 # Either generate empty days... 1784 1785 if not day_slots: 1786 output.append(view.writeEmptyDay(date)) 1787 1788 # Or generate each set of scheduled events... 1789 1790 else: 1791 output.append(view.writeDaySlots(date, full_coverage, day_slots)) 1792 1793 # End of day. 1794 1795 output.append(fmt.table(on=0)) 1796 1797 # Output view controls. 1798 1799 output.append(fmt.div(on=1, css_class="event-controls")) 1800 output.append(view.writeViewControls()) 1801 output.append(fmt.div(on=0)) 1802 1803 # Close the calendar region. 1804 1805 output.append(fmt.div(on=0)) 1806 1807 return ''.join(output) 1808 1809 # vim: tabstop=4 expandtab shiftwidth=4