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