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