1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - EventAggregator Macro 4 5 @copyright: 2008, 2009, 2010 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 import EventAggregatorSupport 13 import calendar 14 15 linkToPage = EventAggregatorSupport.linkToPage 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 calendar_start, calendar_end, first, last, category_names, template_name, 27 parent_name, mode, name_usage): 28 29 """ 30 Initialise the view with the current 'page', a 'calendar_name' (which 31 may be None), the 'raw_calendar_start' and 'raw_calendar_end' (which 32 are the actual start and end values provided by the request), the 33 requested, calculated 'calendar_start' and 'calendar_end', and the 34 'first' and 'last' months of event coverage. 35 36 The additional 'category_names', 'template_name', 'parent_name' and 37 'mode' parameters are used to configure the links employed by the view. 38 39 The 'name_usage' parameter controls how names are shown on calendar mode 40 events. 41 """ 42 43 self.page = page 44 self.calendar_name = calendar_name 45 self.raw_calendar_start = raw_calendar_start 46 self.raw_calendar_end = raw_calendar_end 47 self.calendar_start = calendar_start 48 self.calendar_end = calendar_end 49 self.template_name = template_name 50 self.parent_name = parent_name 51 self.mode = mode 52 self.name_usage = name_usage 53 54 self.category_name_parameters = "&".join([("category=%s" % name) for name in category_names]) 55 56 if self.calendar_name is not None: 57 58 # Store the view parameters. 59 60 self.number_of_months = (last - first).months() + 1 61 62 self.previous_month_start = first.previous_month() 63 self.next_month_start = first.next_month() 64 self.previous_month_end = last.previous_month() 65 self.next_month_end = last.next_month() 66 67 self.previous_set_start = first.month_update(-self.number_of_months) 68 self.next_set_start = first.month_update(self.number_of_months) 69 self.previous_set_end = last.month_update(-self.number_of_months) 70 self.next_set_end = last.month_update(self.number_of_months) 71 72 def getQualifiedParameterName(self, argname): 73 74 "Return the 'argname' qualified using the calendar name." 75 76 return EventAggregatorSupport.getQualifiedParameterName(self.calendar_name, argname) 77 78 def getDateQueryString(self, argname, date, prefix=1): 79 80 """ 81 Return a query string fragment for the given 'argname', referring to the 82 month given by the specified 'year_month' object, appropriate for this 83 calendar. 84 85 If 'prefix' is specified and set to a false value, the parameters in the 86 query string will not be calendar-specific, but could be used with the 87 summary action. 88 """ 89 90 suffixes = ["year", "month", "day"] 91 92 if date is not None: 93 args = [] 94 for suffix, value in zip(suffixes, date.as_tuple()): 95 suffixed_argname = "%s-%s" % (argname, suffix) 96 if prefix: 97 suffixed_argname = self.getQualifiedParameterName(suffixed_argname) 98 args.append("%s=%s" % (suffixed_argname, value)) 99 return "&".join(args) 100 else: 101 return "" 102 103 def getRawDateQueryString(self, argname, date, prefix=1): 104 105 """ 106 Return a query string fragment for the given 'argname', referring to the 107 date given by the specified 'date' value, appropriate for this 108 calendar. 109 110 If 'prefix' is specified and set to a false value, the parameters in the 111 query string will not be calendar-specific, but could be used with the 112 summary action. 113 """ 114 115 if date is not None: 116 if prefix: 117 argname = self.getQualifiedParameterName(argname) 118 return "%s=%s" % (argname, date) 119 else: 120 return "" 121 122 def getNavigationLink(self, start, end, mode=None): 123 124 """ 125 Return a query string fragment for navigation to a view showing months 126 from 'start' to 'end' inclusive, with the optional 'mode' indicating the 127 view style. 128 """ 129 130 return "%s&%s&%s=%s" % ( 131 self.getRawDateQueryString("start", start), 132 self.getRawDateQueryString("end", end), 133 self.getQualifiedParameterName("mode"), mode or self.mode 134 ) 135 136 def getFullDateLabel(self, date): 137 page = self.page 138 request = page.request 139 return EventAggregatorSupport.getFullDateLabel(request, date) 140 141 def getFullMonthLabel(self, year_month): 142 page = self.page 143 request = page.request 144 return EventAggregatorSupport.getFullMonthLabel(request, year_month) 145 146 def writeDownloadControls(self): 147 page = self.page 148 request = page.request 149 fmt = page.formatter 150 _ = request.getText 151 152 output = [] 153 154 # Generate the links. 155 156 download_dialogue_link = "action=EventAggregatorSummary&parent=%s&%s" % ( 157 self.parent_name or "", self.category_name_parameters 158 ) 159 download_all_link = download_dialogue_link + "&doit=1" 160 download_link = download_all_link + ("&%s&%s" % ( 161 self.getDateQueryString("start", self.calendar_start, prefix=0), 162 self.getDateQueryString("end", self.calendar_end, prefix=0) 163 )) 164 165 # Subscription links just explicitly select the RSS format. 166 167 subscribe_dialogue_link = download_dialogue_link + "&format=RSS" 168 subscribe_all_link = download_all_link + "&format=RSS" 169 subscribe_link = download_link + "&format=RSS" 170 171 # Adjust the "download all" and "subscribe all" links if the calendar 172 # has an inherent period associated with it. 173 174 period_limits = [] 175 176 if self.raw_calendar_start: 177 period_limits.append("&%s" % 178 self.getRawDateQueryString("start", self.raw_calendar_start, prefix=0) 179 ) 180 if self.raw_calendar_end: 181 period_limits.append("&%s" % 182 self.getRawDateQueryString("end", self.raw_calendar_end, prefix=0) 183 ) 184 185 period_limits = "".join(period_limits) 186 187 download_dialogue_link += period_limits 188 download_all_link += period_limits 189 subscribe_dialogue_link += period_limits 190 subscribe_all_link += period_limits 191 192 # Pop-up descriptions of the downloadable calendars. 193 194 calendar_period = "%s - %s" % ( 195 self.getFullMonthLabel(self.calendar_start), 196 self.getFullMonthLabel(self.calendar_end) 197 ) 198 raw_calendar_period = "%s - %s" % (self.raw_calendar_start, self.raw_calendar_end) 199 200 # Write the controls. 201 202 # Download controls. 203 204 output.append(fmt.div(on=1, css_class="event-download-controls")) 205 output.append(fmt.span(on=1, css_class="event-download")) 206 output.append(linkToPage(request, page, _("Download this view"), download_link)) 207 output.append(fmt.span(on=1, css_class="event-download-popup")) 208 output.append(fmt.text(calendar_period)) 209 output.append(fmt.span(on=0)) 210 output.append(fmt.span(on=0)) 211 212 output.append(fmt.span(on=1, css_class="event-download")) 213 output.append(linkToPage(request, page, _("Download this calendar"), download_all_link)) 214 output.append(fmt.span(on=1, css_class="event-download-popup")) 215 output.append(fmt.span(on=1, css_class="event-download-period")) 216 output.append(fmt.text(calendar_period)) 217 output.append(fmt.span(on=0)) 218 output.append(fmt.span(on=1, css_class="event-download-period-raw")) 219 output.append(fmt.text(raw_calendar_period)) 220 output.append(fmt.span(on=0)) 221 output.append(fmt.span(on=0)) 222 output.append(fmt.span(on=0)) 223 224 output.append(fmt.span(on=1, css_class="event-download")) 225 output.append(linkToPage(request, page, _("Download..."), download_dialogue_link)) 226 output.append(fmt.span(on=1, css_class="event-download-popup")) 227 output.append(fmt.text(_("Edit download options"))) 228 output.append(fmt.span(on=0)) 229 output.append(fmt.span(on=0)) 230 231 # Subscription controls. 232 233 output.append(fmt.span(on=1, css_class="event-download")) 234 output.append(linkToPage(request, page, _("Subscribe to this view"), subscribe_link)) 235 output.append(fmt.span(on=1, css_class="event-download-popup")) 236 output.append(fmt.text(calendar_period)) 237 output.append(fmt.span(on=0)) 238 output.append(fmt.span(on=0)) 239 240 output.append(fmt.span(on=1, css_class="event-download")) 241 output.append(linkToPage(request, page, _("Subscribe to this calendar"), subscribe_all_link)) 242 output.append(fmt.span(on=1, css_class="event-download-popup")) 243 output.append(fmt.span(on=1, css_class="event-download-period")) 244 output.append(fmt.text(calendar_period)) 245 output.append(fmt.span(on=0)) 246 output.append(fmt.span(on=1, css_class="event-download-period-raw")) 247 output.append(fmt.text(raw_calendar_period)) 248 output.append(fmt.span(on=0)) 249 output.append(fmt.span(on=0)) 250 output.append(fmt.span(on=0)) 251 252 output.append(fmt.span(on=1, css_class="event-download")) 253 output.append(linkToPage(request, page, _("Subscribe..."), subscribe_dialogue_link)) 254 output.append(fmt.span(on=1, css_class="event-download-popup")) 255 output.append(fmt.text(_("Edit subscription options"))) 256 output.append(fmt.span(on=0)) 257 output.append(fmt.span(on=0)) 258 output.append(fmt.div(on=0)) 259 260 return "".join(output) 261 262 def writeViewControls(self): 263 page = self.page 264 request = page.request 265 fmt = page.formatter 266 _ = request.getText 267 268 output = [] 269 270 calendar_link = self.getNavigationLink( 271 self.calendar_start, self.calendar_end, "calendar" 272 ) 273 list_link = self.getNavigationLink( 274 self.calendar_start, self.calendar_end, "list" 275 ) 276 table_link = self.getNavigationLink( 277 self.calendar_start, self.calendar_end, "table" 278 ) 279 280 # Write the controls. 281 282 output.append(fmt.div(on=1, css_class="event-view-controls")) 283 output.append(fmt.span(on=1, css_class="event-view")) 284 output.append(linkToPage(request, page, _("View as calendar"), calendar_link)) 285 output.append(fmt.span(on=0)) 286 output.append(fmt.span(on=1, css_class="event-view")) 287 output.append(linkToPage(request, page, _("View as list"), list_link)) 288 output.append(fmt.span(on=0)) 289 output.append(fmt.span(on=1, css_class="event-view")) 290 output.append(linkToPage(request, page, _("View as table"), table_link)) 291 output.append(fmt.span(on=0)) 292 output.append(fmt.div(on=0)) 293 294 return "".join(output) 295 296 def writeMonthHeading(self, year_month): 297 page = self.page 298 request = page.request 299 fmt = page.formatter 300 _ = request.getText 301 full_month_label = self.getFullMonthLabel(year_month) 302 303 output = [] 304 305 # Prepare navigation links. 306 307 if self.calendar_name is not None: 308 calendar_name = self.calendar_name 309 310 # Links to the previous set of months and to a calendar shifted 311 # back one month. 312 313 previous_set_link = self.getNavigationLink( 314 self.previous_set_start, self.previous_set_end 315 ) 316 previous_month_link = self.getNavigationLink( 317 self.previous_month_start, self.previous_month_end 318 ) 319 320 # Links to the next set of months and to a calendar shifted 321 # forward one month. 322 323 next_set_link = self.getNavigationLink( 324 self.next_set_start, self.next_set_end 325 ) 326 next_month_link = self.getNavigationLink( 327 self.next_month_start, self.next_month_end 328 ) 329 330 # A link leading to this month being at the top of the calendar. 331 332 end_month = year_month.month_update(self.number_of_months - 1) 333 334 month_link = self.getNavigationLink(year_month, end_month) 335 336 output.append(fmt.span(on=1, css_class="previous-month")) 337 output.append(linkToPage(request, page, "<<", previous_set_link)) 338 output.append(fmt.text(" ")) 339 output.append(linkToPage(request, page, "<", previous_month_link)) 340 output.append(fmt.span(on=0)) 341 342 output.append(fmt.span(on=1, css_class="next-month")) 343 output.append(linkToPage(request, page, ">", next_month_link)) 344 output.append(fmt.text(" ")) 345 output.append(linkToPage(request, page, ">>", next_set_link)) 346 output.append(fmt.span(on=0)) 347 348 output.append(linkToPage(request, page, full_month_label, month_link)) 349 350 else: 351 output.append(fmt.span(on=1)) 352 output.append(fmt.text(full_month_label)) 353 output.append(fmt.span(on=0)) 354 355 return "".join(output) 356 357 def writeDayNumberLinked(self, date): 358 page = self.page 359 request = page.request 360 fmt = page.formatter 361 _ = request.getText 362 363 year, month, day = date.as_tuple() 364 output = [] 365 366 # Prepare navigation details for the calendar shown with the new event 367 # form. 368 369 navigation_link = self.getNavigationLink( 370 self.calendar_start, self.calendar_end, self.mode 371 ) 372 373 # Prepare the link to the new event form, incorporating the above 374 # calendar parameters. 375 376 new_event_link = "action=EventAggregatorNewEvent&start-day=%d&start-month=%d&start-year=%d" \ 377 "&%s&template=%s&parent=%s&%s" % ( 378 day, month, year, self.category_name_parameters, self.template_name, self.parent_name or "", 379 navigation_link) 380 381 output.append(fmt.div(on=1)) 382 output.append(fmt.span(on=1, css_class="event-day-number")) 383 output.append(linkToPage(request, page, unicode(day), new_event_link)) 384 output.append(fmt.span(on=0)) 385 output.append(fmt.div(on=0)) 386 387 return "".join(output) 388 389 # Calendar layout methods. 390 391 def writeMonthTableHeading(self, year_month): 392 page = self.page 393 fmt = page.formatter 394 395 output = [] 396 output.append(fmt.table_row(on=1)) 397 output.append(fmt.table_cell(on=1, attrs={"class" : "event-month-heading", "colspan" : "21"})) 398 399 output.append(self.writeMonthHeading(year_month)) 400 401 output.append(fmt.table_cell(on=0)) 402 output.append(fmt.table_row(on=0)) 403 404 return "".join(output) 405 406 def writeWeekdayHeadings(self): 407 page = self.page 408 request = page.request 409 fmt = page.formatter 410 _ = request.getText 411 412 output = [] 413 output.append(fmt.table_row(on=1)) 414 415 for weekday in range(0, 7): 416 output.append(fmt.table_cell(on=1, attrs={"class" : "event-weekday-heading", "colspan" : "3"})) 417 output.append(fmt.text(_(EventAggregatorSupport.getDayLabel(weekday)))) 418 output.append(fmt.table_cell(on=0)) 419 420 output.append(fmt.table_row(on=0)) 421 return "".join(output) 422 423 def writeDayNumbers(self, first_day, number_of_days, month, coverage): 424 page = self.page 425 fmt = page.formatter 426 427 output = [] 428 output.append(fmt.table_row(on=1)) 429 430 for weekday in range(0, 7): 431 day = first_day + weekday 432 date = month.as_date(day) 433 434 # Output out-of-month days. 435 436 if day < 1 or day > number_of_days: 437 output.append(fmt.table_cell(on=1, 438 attrs={"class" : "event-day-heading event-day-excluded", "colspan" : "3"})) 439 output.append(fmt.table_cell(on=0)) 440 441 # Output normal days. 442 443 else: 444 if date in coverage: 445 output.append(fmt.table_cell(on=1, 446 attrs={"class" : "event-day-heading event-day-busy", "colspan" : "3"})) 447 else: 448 output.append(fmt.table_cell(on=1, 449 attrs={"class" : "event-day-heading event-day-empty", "colspan" : "3"})) 450 451 # Output the day number, making a link to a new event 452 # action. 453 454 output.append(self.writeDayNumberLinked(date)) 455 456 # End of day. 457 458 output.append(fmt.table_cell(on=0)) 459 460 # End of day numbers. 461 462 output.append(fmt.table_row(on=0)) 463 return "".join(output) 464 465 def writeEmptyWeek(self, first_day, number_of_days): 466 page = self.page 467 fmt = page.formatter 468 469 output = [] 470 output.append(fmt.table_row(on=1)) 471 472 for weekday in range(0, 7): 473 day = first_day + weekday 474 475 # Output out-of-month days. 476 477 if day < 1 or day > number_of_days: 478 output.append(fmt.table_cell(on=1, 479 attrs={"class" : "event-day-content event-day-excluded", "colspan" : "3"})) 480 output.append(fmt.table_cell(on=0)) 481 482 # Output empty days. 483 484 else: 485 output.append(fmt.table_cell(on=1, 486 attrs={"class" : "event-day-content event-day-empty", "colspan" : "3"})) 487 488 output.append(fmt.table_row(on=0)) 489 return "".join(output) 490 491 def writeWeekSlots(self, first_day, number_of_days, month, week_end, week_slots): 492 output = [] 493 494 locations = week_slots.keys() 495 locations.sort(EventAggregatorSupport.sort_none_first) 496 497 # Visit each slot corresponding to a location (or no location). 498 499 for location in locations: 500 501 # Visit each coverage span, presenting the events in the span. 502 503 for events in week_slots[location]: 504 505 # Output each set. 506 507 output.append(self.writeWeekSlot(first_day, number_of_days, month, week_end, events)) 508 509 # Add a spacer. 510 511 output.append(self.writeWeekSpacer(first_day, number_of_days)) 512 513 return "".join(output) 514 515 def writeWeekSlot(self, first_day, number_of_days, month, week_end, events): 516 page = self.page 517 request = page.request 518 fmt = page.formatter 519 520 output = [] 521 output.append(fmt.table_row(on=1)) 522 523 # Then, output day details. 524 525 for weekday in range(0, 7): 526 day = first_day + weekday 527 date = month.as_date(day) 528 529 # Skip out-of-month days. 530 531 if day < 1 or day > number_of_days: 532 output.append(fmt.table_cell(on=1, 533 attrs={"class" : "event-day-content event-day-excluded", "colspan" : "3"})) 534 output.append(fmt.table_cell(on=0)) 535 continue 536 537 # Output the day. 538 539 if date not in events: 540 output.append(fmt.table_cell(on=1, 541 attrs={"class" : "event-day-content event-day-empty", "colspan" : "3"})) 542 543 # Get event details for the current day. 544 545 for event in events: 546 event_page = event.getPage() 547 event_details = event.getDetails() 548 549 if date not in event: 550 continue 551 552 # Get basic properties of the event. 553 554 starts_today = event_details["start"] == date 555 ends_today = event_details["end"] == date 556 event_summary = event.getSummary(self.parent_name) 557 is_ambiguous = event_details["start"].ambiguous() or event_details["end"].ambiguous() 558 559 # Generate a colour for the event. 560 561 bg = getColour(event_summary) 562 fg = getBlackOrWhite(bg) 563 style = ("background-color: rgb(%d, %d, %d); color: rgb(%d, %d, %d);" % (bg + fg)) 564 565 # Determine if the event name should be shown. 566 567 start_of_period = starts_today or weekday == 0 or day == 1 568 569 if self.name_usage == "daily" or start_of_period: 570 hide_text = 0 571 else: 572 hide_text = 1 573 574 # Output start of day gap and determine whether 575 # any event content should be explicitly output 576 # for this day. 577 578 if starts_today: 579 580 # Single day events... 581 582 if ends_today: 583 colspan = 3 584 event_day_type = "event-day-single" 585 586 # Events starting today... 587 588 else: 589 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-start-gap"})) 590 output.append(fmt.table_cell(on=0)) 591 592 # Calculate the span of this cell. 593 # Events whose names appear on every day... 594 595 if self.name_usage == "daily": 596 colspan = 2 597 event_day_type = "event-day-starting" 598 599 # Events whose names appear once per week... 600 601 else: 602 if event_details["end"] <= week_end: 603 event_length = event_details["end"].day() - day + 1 604 colspan = (event_length - 2) * 3 + 4 605 else: 606 event_length = week_end.day() - day + 1 607 colspan = (event_length - 1) * 3 + 2 608 609 event_day_type = "event-day-multiple" 610 611 # Events continuing from a previous week... 612 613 elif start_of_period: 614 615 # End of continuing event... 616 617 if ends_today: 618 colspan = 2 619 event_day_type = "event-day-ending" 620 621 # Events continuing for at least one more day... 622 623 else: 624 625 # Calculate the span of this cell. 626 # Events whose names appear on every day... 627 628 if self.name_usage == "daily": 629 colspan = 3 630 event_day_type = "event-day-full" 631 632 # Events whose names appear once per week... 633 634 else: 635 if event_details["end"] <= week_end: 636 event_length = event_details["end"].day() - day + 1 637 colspan = (event_length - 1) * 3 + 2 638 else: 639 event_length = week_end.day() - day + 1 640 colspan = event_length * 3 641 642 event_day_type = "event-day-multiple" 643 644 # Continuing events whose names appear on every day... 645 646 elif self.name_usage == "daily": 647 if ends_today: 648 colspan = 2 649 event_day_type = "event-day-ending" 650 else: 651 colspan = 3 652 event_day_type = "event-day-full" 653 654 # Continuing events whose names appear once per week... 655 656 else: 657 colspan = None 658 659 # Output the main content only if it is not 660 # continuing from a previous day. 661 662 if colspan is not None: 663 664 # Colour the cell for continuing events. 665 666 attrs={ 667 "class" : "event-day-content event-day-busy %s" % event_day_type, 668 "colspan" : str(colspan) 669 } 670 671 if not (starts_today and ends_today): 672 attrs["style"] = style 673 674 output.append(fmt.table_cell(on=1, attrs=attrs)) 675 676 # Output the event. 677 678 if starts_today and ends_today or not hide_text: 679 680 # The event box contains the summary, alongside 681 # other elements. 682 683 output.append(fmt.div(on=1, css_class="event-summary-box")) 684 output.append(fmt.div(on=1, css_class="event-summary", style=style)) 685 686 if is_ambiguous: 687 output.append(fmt.icon("/!\\")) 688 689 output.append(event_page.linkToPage(request, event_summary)) 690 output.append(fmt.div(on=0)) 691 692 # Add a pop-up element for long summaries. 693 694 output.append(fmt.div(on=1, css_class="event-summary-popup", style=style)) 695 696 if is_ambiguous: 697 output.append(fmt.icon("/!\\")) 698 699 output.append(event_page.linkToPage(request, event_summary)) 700 output.append(fmt.div(on=0)) 701 702 output.append(fmt.div(on=0)) 703 704 # Output end of day content. 705 706 output.append(fmt.div(on=0)) 707 708 # Output end of day gap. 709 710 if ends_today and not starts_today: 711 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-end-gap"})) 712 output.append(fmt.table_cell(on=0)) 713 714 # End of day. 715 716 output.append(fmt.table_cell(on=0)) 717 718 # End of set. 719 720 output.append(fmt.table_row(on=0)) 721 return "".join(output) 722 723 def writeWeekSpacer(self, first_day, number_of_days): 724 page = self.page 725 fmt = page.formatter 726 727 output = [] 728 output.append(fmt.table_row(on=1)) 729 730 for weekday in range(0, 7): 731 day = first_day + weekday 732 css_classes = "event-day-spacer" 733 734 # Skip out-of-month days. 735 736 if day < 1 or day > number_of_days: 737 css_classes += " event-day-excluded" 738 739 output.append(fmt.table_cell(on=1, attrs={"class" : css_classes, "colspan" : "3"})) 740 output.append(fmt.table_cell(on=0)) 741 742 output.append(fmt.table_row(on=0)) 743 return "".join(output) 744 745 # Day layout methods. 746 747 def writeDayHeading(self, date): 748 page = self.page 749 request = page.request 750 fmt = page.formatter 751 _ = request.getText 752 full_date_label = self.getFullDateLabel(date) 753 754 output = [] 755 output.append(fmt.table_row(on=1)) 756 757 output.append(fmt.table_cell(on=1, attrs={"class" : "event-weekday-heading"})) 758 output.append(fmt.text(full_date_label)) 759 output.append(fmt.table_cell(on=0)) 760 761 output.append(fmt.table_row(on=0)) 762 return "".join(output) 763 764 def writeEmptyDay(self, date): 765 page = self.page 766 fmt = page.formatter 767 768 output = [] 769 output.append(fmt.table_row(on=1)) 770 771 output.append(fmt.table_cell(on=1, 772 attrs={"class" : "event-day-content event-day-empty"})) 773 774 output.append(fmt.table_row(on=0)) 775 return "".join(output) 776 777 def writeDaySlots(self, date, full_coverage, day_slots): 778 page = self.page 779 fmt = page.formatter 780 781 output = [] 782 783 locations = day_slots.keys() 784 locations.sort(EventAggregatorSupport.sort_none_first) 785 786 # Traverse the time scale of the full coverage, visiting each slot to 787 # determine whether it provides content for each period. 788 789 scale = EventAggregatorSupport.getCoverageScale(full_coverage) 790 791 for period in scale: 792 start = period.start 793 794 output.append(fmt.table_row(on=1)) 795 output.append(fmt.table_cell(on=1, attrs={"class" : "event-weekday-heading"})) 796 output.append(fmt.text(str(start))) 797 output.append(fmt.table_cell(on=0)) 798 799 # Visit each slot corresponding to a location (or no location). 800 801 #for location in locations: 802 803 # # Visit each coverage span, presenting the events in the span. 804 805 # for events in day_slots[location]: 806 807 # # Output each set. 808 809 # output.append(self.writeDaySlot(day, events)) 810 811 output.append(fmt.table_row(on=0)) 812 813 return "".join(output) 814 815 def writeDaySlot(self, date, events): 816 pass 817 818 # HTML-related functions. 819 820 def getColour(s): 821 colour = [0, 0, 0] 822 digit = 0 823 for c in s: 824 colour[digit] += ord(c) 825 colour[digit] = colour[digit] % 256 826 digit += 1 827 digit = digit % 3 828 return tuple(colour) 829 830 def getBlackOrWhite(colour): 831 if sum(colour) / 3.0 > 127: 832 return (0, 0, 0) 833 else: 834 return (255, 255, 255) 835 836 # Macro functions. 837 838 def execute(macro, args): 839 840 """ 841 Execute the 'macro' with the given 'args': an optional list of selected 842 category names (categories whose pages are to be shown), together with 843 optional named arguments of the following forms: 844 845 start=YYYY-MM shows event details starting from the specified month 846 start=YYYY-MM-DD shows event details starting from the specified day 847 start=current-N shows event details relative to the current month 848 (or relative to the current day in "day" mode) 849 end=YYYY-MM shows event details ending at the specified month 850 end=YYYY-MM-DD shows event details ending on the specified day 851 end=current+N shows event details relative to the current month 852 (or relative to the current day in "day" mode) 853 854 mode=calendar shows a calendar view of events 855 mode=day shows a calendar day view of events 856 mode=list shows a list of events by month 857 mode=table shows a table of events 858 859 names=daily shows the name of an event on every day of that event 860 names=weekly shows the name of an event once per week 861 862 calendar=NAME uses the given NAME to provide request parameters which 863 can be used to control the calendar view 864 865 template=PAGE uses the given PAGE as the default template for new 866 events (or the default template from the configuration 867 if not specified) 868 869 parent=PAGE uses the given PAGE as the parent of any new event page 870 """ 871 872 request = macro.request 873 fmt = macro.formatter 874 page = fmt.page 875 _ = request.getText 876 877 # Interpret the arguments. 878 879 try: 880 parsed_args = args and wikiutil.parse_quoted_separated(args, name_value=False) or [] 881 except AttributeError: 882 parsed_args = args.split(",") 883 884 parsed_args = [arg for arg in parsed_args if arg] 885 886 # Get special arguments. 887 888 category_names = [] 889 raw_calendar_start = None 890 raw_calendar_end = None 891 calendar_start = None 892 calendar_end = None 893 mode = None 894 name_usage = "weekly" 895 calendar_name = None 896 template_name = getattr(request.cfg, "event_aggregator_new_event_template", "EventTemplate") 897 parent_name = None 898 899 for arg in parsed_args: 900 if arg.startswith("start="): 901 raw_calendar_start = arg[6:] 902 903 elif arg.startswith("end="): 904 raw_calendar_end = arg[4:] 905 906 elif arg.startswith("mode="): 907 mode = arg[5:] 908 909 elif arg.startswith("names="): 910 name_usage = arg[6:] 911 912 elif arg.startswith("calendar="): 913 calendar_name = arg[9:] 914 915 elif arg.startswith("template="): 916 template_name = arg[9:] 917 918 elif arg.startswith("parent="): 919 parent_name = arg[7:] 920 921 else: 922 category_names.append(arg) 923 924 # Find request parameters to override settings. 925 926 mode = EventAggregatorSupport.getQualifiedParameter(request, calendar_name, "mode", mode or "calendar") 927 928 if mode == "day": 929 get_date = EventAggregatorSupport.getParameterDate 930 get_form_date = EventAggregatorSupport.getFormDate 931 else: 932 get_date = EventAggregatorSupport.getParameterMonth 933 get_form_date = EventAggregatorSupport.getFormMonth 934 935 # Determine the limits of the calendar. 936 937 calendar_start = get_date(raw_calendar_start) 938 calendar_end = get_date(raw_calendar_end) 939 940 if calendar_name is not None: 941 calendar_start = get_form_date(request, calendar_name, "start") or calendar_start 942 calendar_end = get_form_date(request, calendar_name, "end") or calendar_end 943 944 # Get the events according to the resolution of the calendar. 945 946 events, shown_events, all_shown_events, earliest, latest = \ 947 EventAggregatorSupport.getEvents(request, category_names, calendar_start, calendar_end, 948 mode == "day" and "date" or "month") 949 950 # Get a concrete period of time. 951 952 first, last = EventAggregatorSupport.getConcretePeriod(calendar_start, calendar_end, earliest, latest) 953 954 # Define a view of the calendar, retaining useful navigational information. 955 956 view = View(page, calendar_name, raw_calendar_start, raw_calendar_end, calendar_start, calendar_end, 957 first, last, category_names, template_name, parent_name, mode, name_usage) 958 959 # Make a calendar. 960 961 output = [] 962 963 # Output download controls. 964 965 output.append(fmt.div(on=1, css_class="event-controls")) 966 output.append(view.writeDownloadControls()) 967 output.append(fmt.div(on=0)) 968 969 # Output a table. 970 971 if mode == "table": 972 973 # Start of table view output. 974 975 output.append(fmt.table(on=1, attrs={"tableclass" : "event-table"})) 976 977 output.append(fmt.table_row(on=1)) 978 output.append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) 979 output.append(fmt.text(_("Event dates"))) 980 output.append(fmt.table_cell(on=0)) 981 output.append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) 982 output.append(fmt.text(_("Event location"))) 983 output.append(fmt.table_cell(on=0)) 984 output.append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) 985 output.append(fmt.text(_("Event details"))) 986 output.append(fmt.table_cell(on=0)) 987 output.append(fmt.table_row(on=0)) 988 989 # Get the events in order. 990 991 ordered_events = EventAggregatorSupport.getOrderedEvents(all_shown_events) 992 993 # Show the events in order. 994 995 for event in ordered_events: 996 event_page = event.getPage() 997 event_summary = event.getSummary(parent_name) 998 event_details = event.getDetails() 999 1000 # Prepare CSS classes with category-related styling. 1001 1002 css_classes = ["event-table-details"] 1003 1004 for topic in event_details.get("topics") or event_details.get("categories") or []: 1005 1006 # Filter the category text to avoid illegal characters. 1007 1008 css_classes.append("event-table-category-%s" % "".join(filter(lambda c: c.isalnum(), topic))) 1009 1010 attrs = {"class" : " ".join(css_classes)} 1011 1012 output.append(fmt.table_row(on=1)) 1013 1014 # Start and end dates. 1015 1016 output.append(fmt.table_cell(on=1, attrs=attrs)) 1017 output.append(fmt.span(on=1)) 1018 output.append(fmt.text(str(event_details["start"]))) 1019 output.append(fmt.span(on=0)) 1020 1021 if event_details["start"] != event_details["end"]: 1022 output.append(fmt.text(" - ")) 1023 output.append(fmt.span(on=1)) 1024 output.append(fmt.text(str(event_details["end"]))) 1025 output.append(fmt.span(on=0)) 1026 1027 output.append(fmt.table_cell(on=0)) 1028 1029 # Location. 1030 1031 output.append(fmt.table_cell(on=1, attrs=attrs)) 1032 1033 if event_details.has_key("location"): 1034 output.append(fmt.text(event_details["location"])) 1035 1036 output.append(fmt.table_cell(on=0)) 1037 1038 # Link to the page using the summary. 1039 1040 output.append(fmt.table_cell(on=1, attrs=attrs)) 1041 output.append(event_page.linkToPage(request, event_summary)) 1042 output.append(fmt.table_cell(on=0)) 1043 1044 output.append(fmt.table_row(on=0)) 1045 1046 # End of table view output. 1047 1048 output.append(fmt.table(on=0)) 1049 1050 # Output a list or month calendar. 1051 1052 elif mode in ("list", "calendar"): 1053 1054 # Output top-level information. 1055 1056 # Start of list view output. 1057 1058 if mode == "list": 1059 output.append(fmt.bullet_list(on=1, attr={"class" : "event-listings"})) 1060 1061 # Visit all months in the requested range, or across known events. 1062 1063 for month in first.months_until(last): 1064 1065 # Either output a calendar view... 1066 1067 if mode == "calendar": 1068 1069 # Output a month. 1070 1071 output.append(fmt.table(on=1, attrs={"tableclass" : "event-month"})) 1072 1073 # Either write a month heading or produce links for navigable 1074 # calendars. 1075 1076 output.append(view.writeMonthTableHeading(month)) 1077 1078 # Weekday headings. 1079 1080 output.append(view.writeWeekdayHeadings()) 1081 1082 # Process the days of the month. 1083 1084 start_weekday, number_of_days = month.month_properties() 1085 1086 # The start weekday is the weekday of day number 1. 1087 # Find the first day of the week, counting from below zero, if 1088 # necessary, in order to land on the first day of the month as 1089 # day number 1. 1090 1091 first_day = 1 - start_weekday 1092 1093 while first_day <= number_of_days: 1094 1095 # Find events in this week and determine how to mark them on the 1096 # calendar. 1097 1098 week_start = month.as_date(max(first_day, 1)) 1099 week_end = month.as_date(min(first_day + 6, number_of_days)) 1100 1101 full_coverage, week_slots = EventAggregatorSupport.getCoverage( 1102 week_start, week_end, shown_events.get(month, [])) 1103 1104 # Output a week, starting with the day numbers. 1105 1106 output.append(view.writeDayNumbers(first_day, number_of_days, month, full_coverage)) 1107 1108 # Either generate empty days... 1109 1110 if not week_slots: 1111 output.append(view.writeEmptyWeek(first_day, number_of_days)) 1112 1113 # Or generate each set of scheduled events... 1114 1115 else: 1116 output.append(view.writeWeekSlots(first_day, number_of_days, month, week_end, week_slots)) 1117 1118 # Process the next week... 1119 1120 first_day += 7 1121 1122 # End of month. 1123 1124 output.append(fmt.table(on=0)) 1125 1126 # Or output a summary view... 1127 1128 elif mode == "list": 1129 1130 # Output a list. 1131 1132 output.append(fmt.listitem(on=1, attr={"class" : "event-listings-month"})) 1133 output.append(fmt.div(on=1, attr={"class" : "event-listings-month-heading"})) 1134 1135 # Either write a month heading or produce links for navigable 1136 # calendars. 1137 1138 output.append(view.writeMonthHeading(month)) 1139 1140 output.append(fmt.div(on=0)) 1141 1142 output.append(fmt.bullet_list(on=1, attr={"class" : "event-month-listings"})) 1143 1144 # Get the events in order. 1145 1146 ordered_events = EventAggregatorSupport.getOrderedEvents(shown_events.get(month, [])) 1147 1148 # Show the events in order. 1149 1150 for event in ordered_events: 1151 event_page = event.getPage() 1152 event_details = event.getDetails() 1153 event_summary = event.getSummary(parent_name) 1154 1155 output.append(fmt.listitem(on=1, attr={"class" : "event-listing"})) 1156 1157 # Link to the page using the summary. 1158 1159 output.append(fmt.paragraph(on=1)) 1160 output.append(event_page.linkToPage(request, event_summary)) 1161 output.append(fmt.paragraph(on=0)) 1162 1163 # Start and end dates. 1164 1165 output.append(fmt.paragraph(on=1)) 1166 output.append(fmt.span(on=1)) 1167 output.append(fmt.text(str(event_details["start"]))) 1168 output.append(fmt.span(on=0)) 1169 output.append(fmt.text(" - ")) 1170 output.append(fmt.span(on=1)) 1171 output.append(fmt.text(str(event_details["end"]))) 1172 output.append(fmt.span(on=0)) 1173 output.append(fmt.paragraph(on=0)) 1174 1175 # Location. 1176 1177 if event_details.has_key("location"): 1178 output.append(fmt.paragraph(on=1)) 1179 output.append(fmt.text(event_details["location"])) 1180 output.append(fmt.paragraph(on=1)) 1181 1182 # Topics. 1183 1184 if event_details.has_key("topics") or event_details.has_key("categories"): 1185 output.append(fmt.bullet_list(on=1, attr={"class" : "event-topics"})) 1186 1187 for topic in event_details.get("topics") or event_details.get("categories") or []: 1188 output.append(fmt.listitem(on=1)) 1189 output.append(fmt.text(topic)) 1190 output.append(fmt.listitem(on=0)) 1191 1192 output.append(fmt.bullet_list(on=0)) 1193 1194 output.append(fmt.listitem(on=0)) 1195 1196 output.append(fmt.bullet_list(on=0)) 1197 1198 # Output top-level information. 1199 1200 # End of list view output. 1201 1202 if mode == "list": 1203 output.append(fmt.bullet_list(on=0)) 1204 1205 # Output a day view. 1206 1207 elif mode == "day": 1208 1209 # Visit all days in the requested range, or across known events. 1210 1211 for date in first.days_until(last): 1212 1213 output.append(fmt.table(on=1, attrs={"tableclass" : "event-calendar-day"})) 1214 1215 full_coverage, day_slots = EventAggregatorSupport.getCoverage( 1216 date, date, shown_events.get(date, [])) 1217 1218 output.append(view.writeDayHeading(date)) 1219 1220 # Either generate empty days... 1221 1222 if not day_slots: 1223 output.append(view.writeEmptyDay(date)) 1224 1225 # Or generate each set of scheduled events... 1226 1227 else: 1228 output.append(view.writeDaySlots(date, full_coverage, day_slots)) 1229 1230 # End of day. 1231 1232 output.append(fmt.table(on=0)) 1233 1234 # Output view controls. 1235 1236 output.append(fmt.div(on=1, css_class="event-controls")) 1237 output.append(view.writeViewControls()) 1238 output.append(fmt.div(on=0)) 1239 1240 return ''.join(output) 1241 1242 # vim: tabstop=4 expandtab shiftwidth=4