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