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