1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - EventAggregator Macro 4 5 @copyright: 2008, 2009 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 try: 18 set 19 except NameError: 20 from sets import Set as set 21 22 Dependencies = ['pages'] 23 24 # HTML-related functions. 25 26 def getColour(s): 27 colour = [0, 0, 0] 28 digit = 0 29 for c in s: 30 colour[digit] += ord(c) 31 colour[digit] = colour[digit] % 256 32 digit += 1 33 digit = digit % 3 34 return tuple(colour) 35 36 def getBlackOrWhite(colour): 37 if sum(colour) / 3.0 > 127: 38 return (0, 0, 0) 39 else: 40 return (255, 255, 255) 41 42 def getMonthActionQueryString(argname, month): 43 if month is not None: 44 return "%s=%04d-%02d" % ((argname,) + month) 45 else: 46 return "" 47 48 def getMonthQueryString(calendar_name, argname, month): 49 if month is not None: 50 return "%s-%s=%04d-%02d" % ((calendar_name, argname) + month) 51 else: 52 return "" 53 54 # Macro functions. 55 56 def execute(macro, args): 57 58 """ 59 Execute the 'macro' with the given 'args': an optional list of selected 60 category names (categories whose pages are to be shown), together with 61 optional named arguments of the following forms: 62 63 start=YYYY-MM shows event details starting from the specified month 64 start=current-N shows event details relative to the current month 65 end=YYYY-MM shows event details ending at the specified month 66 end=current+N shows event details relative to the current month 67 68 mode=calendar shows a calendar view of events 69 mode=list shows a list of events by month 70 mode=ics provides iCalendar data for the events 71 72 names=daily shows the name of an event on every day of that event 73 names=weekly shows the name of an event once per week 74 75 calendar=NAME uses the given NAME to provide request parameters which 76 can be used to control the calendar view 77 """ 78 79 request = macro.request 80 fmt = macro.formatter 81 page = fmt.page 82 _ = request.getText 83 84 # Interpret the arguments. 85 86 try: 87 parsed_args = args and wikiutil.parse_quoted_separated(args, name_value=False) or [] 88 except AttributeError: 89 parsed_args = args.split(",") 90 91 parsed_args = [arg for arg in parsed_args if arg] 92 93 # Get special arguments. 94 95 category_names = [] 96 calendar_start = None 97 calendar_end = None 98 mode = "calendar" 99 name_usage = "daily" 100 calendar_name = None 101 102 for arg in parsed_args: 103 if arg.startswith("start="): 104 calendar_start = EventAggregatorSupport.getParameterMonth(arg[6:]) 105 106 elif arg.startswith("end="): 107 calendar_end = EventAggregatorSupport.getParameterMonth(arg[4:]) 108 109 elif arg.startswith("mode="): 110 mode = arg[5:] 111 112 elif arg.startswith("names="): 113 name_usage = arg[6:] 114 115 elif arg.startswith("calendar="): 116 calendar_name = arg[9:] 117 118 else: 119 category_names.append(arg) 120 121 # Find request parameters to override settings. 122 123 if calendar_name is not None: 124 calendar_start = EventAggregatorSupport.getFormMonth(request, calendar_name, "start") or calendar_start 125 calendar_end = EventAggregatorSupport.getFormMonth(request, calendar_name, "end") or calendar_end 126 127 # Get the events. 128 129 events, shown_events, all_shown_events, earliest, latest = \ 130 EventAggregatorSupport.getEvents(request, category_names, calendar_start, calendar_end) 131 132 # Get a concrete period of time. 133 134 first, last = EventAggregatorSupport.getConcretePeriod(calendar_start, calendar_end, earliest, latest) 135 136 # Define some useful navigation months. 137 138 if calendar_name is not None: 139 span = EventAggregatorSupport.span(first, last) 140 number_of_months = span[0] * 12 + span[1] + 1 141 142 previous_month_start = EventAggregatorSupport.prevmonth(first) 143 next_month_start = EventAggregatorSupport.nextmonth(first) 144 previous_month_end = EventAggregatorSupport.prevmonth(last) 145 next_month_end = EventAggregatorSupport.nextmonth(last) 146 147 previous_set_start = EventAggregatorSupport.monthupdate(first, -number_of_months) 148 next_set_start = EventAggregatorSupport.monthupdate(first, number_of_months) 149 previous_set_end = EventAggregatorSupport.monthupdate(last, -number_of_months) 150 next_set_end = EventAggregatorSupport.monthupdate(last, number_of_months) 151 152 # Make a calendar. 153 154 output = [] 155 156 # Output download controls. 157 158 download_all_link = "action=EventAggregatorSummary&doit=1&%s" % ( 159 "&".join([("category=%s" % name) for name in category_names]) 160 ) 161 download_link = download_all_link + ("&%s&%s" % ( 162 getMonthActionQueryString("start", calendar_start), 163 getMonthActionQueryString("end", calendar_end) 164 )) 165 166 output.append(fmt.div(on=1, css_class="event-controls")) 167 output.append(fmt.span(on=1, css_class="event-download-calendar")) 168 output.append(linkToPage(request, page, _("Download this calendar in iCalendar format"), download_link)) 169 output.append(fmt.span(on=0)) 170 output.append(fmt.span(on=1, css_class="event-download-all")) 171 output.append(linkToPage(request, page, _("Download all events in iCalendar format"), download_all_link)) 172 output.append(fmt.span(on=0)) 173 output.append(fmt.div(on=0)) 174 175 # Output top-level information. 176 177 if mode == "list": 178 output.append(fmt.bullet_list(on=1, attr={"class" : "event-listings"})) 179 180 # Visit all months in the requested range, or across known events. 181 182 for year, month in EventAggregatorSupport.daterange(first, last): 183 184 # Either output a calendar view... 185 186 if mode == "calendar": 187 188 # Output a month. 189 190 output.append(fmt.table(on=1, attrs={"tableclass" : "event-month"})) 191 192 output.append(fmt.table_row(on=1)) 193 output.append(fmt.table_cell(on=1, attrs={"class" : "event-month-heading", "colspan" : "7"})) 194 195 # Either write a month heading or produce a link for navigable 196 # calendars. 197 198 month_label = _(EventAggregatorSupport.getMonthLabel(month)) 199 200 if calendar_name is not None: 201 202 # Links to the previous set of months and to a calendar shifted 203 # back one month. 204 205 previous_set_link = "%s&%s" % ( 206 getMonthQueryString(calendar_name, "start", previous_set_start), 207 getMonthQueryString(calendar_name, "end", previous_set_end) 208 ) 209 previous_month_link = "%s&%s" % ( 210 getMonthQueryString(calendar_name, "start", previous_month_start), 211 getMonthQueryString(calendar_name, "end", previous_month_end) 212 ) 213 214 output.append(fmt.span(on=1, css_class="previous-month")) 215 output.append(linkToPage(request, page, "<<", previous_set_link)) 216 output.append(fmt.text(" ")) 217 output.append(linkToPage(request, page, "<", previous_month_link)) 218 output.append(fmt.span(on=0)) 219 220 # Links to the next set of months and to a calendar shifted 221 # forward one month. 222 223 next_set_link = "%s&%s" % ( 224 getMonthQueryString(calendar_name, "start", next_set_start), 225 getMonthQueryString(calendar_name, "end", next_set_end) 226 ) 227 next_month_link = "%s&%s" % ( 228 getMonthQueryString(calendar_name, "start", next_month_start), 229 getMonthQueryString(calendar_name, "end", next_month_end) 230 ) 231 232 output.append(fmt.span(on=1, css_class="next-month")) 233 output.append(linkToPage(request, page, ">", next_month_link)) 234 output.append(fmt.text(" ")) 235 output.append(linkToPage(request, page, ">>", next_set_link)) 236 output.append(fmt.span(on=0)) 237 238 # A link leading to this month being at the top of the calendar. 239 240 full_month_label = "%s %s" % (month_label, year) 241 end_month = EventAggregatorSupport.monthupdate((year, month), number_of_months - 1) 242 243 month_link = "%s&%s" % ( 244 getMonthQueryString(calendar_name, "start", (year, month)), 245 getMonthQueryString(calendar_name, "end", end_month) 246 ) 247 output.append(linkToPage(request, page, full_month_label, month_link)) 248 249 else: 250 output.append(fmt.span(on=1)) 251 output.append(fmt.text(month_label)) 252 output.append(fmt.span(on=0)) 253 output.append(fmt.text(" ")) 254 output.append(fmt.span(on=1)) 255 output.append(fmt.text(unicode(year))) 256 output.append(fmt.span(on=0)) 257 258 output.append(fmt.table_cell(on=0)) 259 output.append(fmt.table_row(on=0)) 260 261 # Weekday headings. 262 263 output.append(fmt.table_row(on=1)) 264 265 for weekday in range(0, 7): 266 output.append(fmt.table_cell(on=1, attrs={"class" : "event-weekday-heading"})) 267 output.append(fmt.text(_(EventAggregatorSupport.getDayLabel(weekday)))) 268 output.append(fmt.table_cell(on=0)) 269 270 output.append(fmt.table_row(on=0)) 271 272 # Process the days of the month. 273 274 start_weekday, number_of_days = calendar.monthrange(year, month) 275 276 # The start weekday is the weekday of day number 1. 277 # Find the first day of the week, counting from below zero, if 278 # necessary, in order to land on the first day of the month as 279 # day number 1. 280 281 first_day = 1 - start_weekday 282 283 while first_day <= number_of_days: 284 285 # Find events in this week and determine how to mark them on the 286 # calendar. 287 288 week_start = (year, month, max(first_day, 1)) 289 week_end = (year, month, min(first_day + 6, number_of_days)) 290 291 week_coverage, week_events = EventAggregatorSupport.getCoverage( 292 week_start, week_end, shown_events.get((year, month), [])) 293 294 # Output a week, starting with the day numbers. 295 296 output.append(fmt.table_row(on=1)) 297 298 for weekday in range(0, 7): 299 day = first_day + weekday 300 date = (year, month, day) 301 302 # Output out-of-month days. 303 304 if day < 1 or day > number_of_days: 305 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-heading event-day-excluded"})) 306 output.append(fmt.table_cell(on=0)) 307 308 # Output normal days. 309 310 else: 311 if date in week_coverage: 312 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-heading event-day-busy"})) 313 else: 314 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-heading event-day-empty"})) 315 316 output.append(fmt.div(on=1)) 317 output.append(fmt.span(on=1, css_class="event-day-number")) 318 output.append(fmt.text(unicode(day))) 319 output.append(fmt.span(on=0)) 320 output.append(fmt.div(on=0)) 321 322 # End of day. 323 324 output.append(fmt.table_cell(on=0)) 325 326 # End of day numbers. 327 328 output.append(fmt.table_row(on=0)) 329 330 # Either generate empty days... 331 332 if not week_events: 333 output.append(fmt.table_row(on=1)) 334 335 for weekday in range(0, 7): 336 day = first_day + weekday 337 date = (year, month, day) 338 339 # Output out-of-month days. 340 341 if day < 1 or day > number_of_days: 342 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-excluded"})) 343 output.append(fmt.table_cell(on=0)) 344 345 # Output empty days. 346 347 else: 348 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-empty"})) 349 350 output.append(fmt.table_row(on=0)) 351 352 # Or visit each set of scheduled events... 353 354 else: 355 for coverage, events in week_events: 356 357 # Output each set. 358 359 output.append(fmt.table_row(on=1)) 360 361 # Then, output day details. 362 363 for weekday in range(0, 7): 364 day = first_day + weekday 365 date = (year, month, day) 366 367 # Skip out-of-month days. 368 369 if day < 1 or day > number_of_days: 370 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-excluded"})) 371 output.append(fmt.table_cell(on=0)) 372 continue 373 374 # Output the day. 375 376 if date in coverage: 377 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-busy"})) 378 else: 379 output.append(fmt.table_cell(on=1, attrs={"class" : "event-day event-day-empty"})) 380 381 # Get event details for the current day. 382 383 for event_page, event_details in events: 384 if not (event_details["start"] <= date <= event_details["end"]): 385 continue 386 387 event_summary = EventAggregatorSupport.getEventSummary(event_page, event_details) 388 389 # Try to work out if the summary will cause sizing issues. 390 391 summary_parts = event_summary.split() 392 largest_part = reduce(lambda x, y: max(x, len(y)), summary_parts, 0) 393 394 if largest_part > 12: 395 sizing = "font-size: smaller;" 396 else: 397 sizing = "" 398 399 # Generate a colour for the event. 400 401 bg = getColour(event_page.page_name) 402 fg = getBlackOrWhite(bg) 403 style = ("background-color: rgb(%d, %d, %d); color: rgb(%d, %d, %d);" % (bg + fg)) 404 hidden_style = ("background-color: rgb(%d, %d, %d); color: rgb(%d, %d, %d);" % (bg + bg)) 405 406 css_classes = ["event-summary"] 407 408 if event_details["start"] == date: 409 css_classes.append("event-starts") 410 start_of_event = 1 411 else: 412 start_of_event = 0 413 414 if event_details["end"] == date: 415 css_classes.append("event-ends") 416 417 # Output the event. 418 419 if name_usage == "daily" or start_of_event or weekday == 0 or day == 1: 420 hide_text = 0 421 else: 422 hide_text = 1 423 424 output.append(fmt.div(on=1, css_class="event-summary-box")) 425 426 if not hide_text: 427 output.append(fmt.div(on=1, css_class=(" ".join(css_classes)), style=style+" "+sizing)) 428 output.append(linkToPage(request, event_page, event_summary)) 429 output.append(fmt.div(on=0)) 430 431 # Add a pop-up element for long summaries. 432 433 if sizing: 434 output.append(fmt.div(on=1, css_class="event-summary-popup", style=style+" font-size: larger;")) 435 output.append(linkToPage(request, event_page, event_summary)) 436 output.append(fmt.div(on=0)) 437 438 else: 439 output.append(fmt.div(on=1, css_class=(" ".join(css_classes)), style=hidden_style+" "+sizing)) 440 output.append(fmt.text(event_summary)) 441 output.append(fmt.div(on=0)) 442 443 output.append(fmt.div(on=0)) 444 445 # End of day. 446 447 output.append(fmt.table_cell(on=0)) 448 449 # End of set. 450 451 output.append(fmt.table_row(on=0)) 452 453 # Process the next week... 454 455 first_day += 7 456 457 # End of month. 458 459 output.append(fmt.table(on=0)) 460 461 # Or output a summary view... 462 463 elif mode == "list": 464 465 month_label = _(EventAggregatorSupport.getMonthLabel(month)) 466 467 output.append(fmt.listitem(on=1, attr={"class" : "event-listings-month"})) 468 output.append(fmt.div(on=1, attr={"class" : "event-listings-month-heading"})) 469 output.append(fmt.span(on=1)) 470 output.append(fmt.text(month_label)) 471 output.append(fmt.span(on=0)) 472 output.append(fmt.text(" ")) 473 output.append(fmt.span(on=1)) 474 output.append(fmt.text(unicode(year))) 475 output.append(fmt.span(on=0)) 476 output.append(fmt.div(on=0)) 477 478 output.append(fmt.bullet_list(on=1, attr={"class" : "event-month-listings"})) 479 480 # Get the events in order. 481 482 ordered_events = EventAggregatorSummary.getOrderedEvents(shown_events.get((year, month), [])) 483 484 # Show the events in order. 485 486 for event_page, event_details in ordered_events: 487 event_summary = EventAggregatorSupport.getEventSummary(event_page, event_details) 488 489 output.append(fmt.listitem(on=1, attr={"class" : "event-listing"})) 490 491 # Link to the page using the summary. 492 493 output.append(fmt.paragraph(on=1)) 494 output.append(linkToPage(request, event_page, event_summary)) 495 output.append(fmt.paragraph(on=0)) 496 497 # Start and end dates. 498 499 output.append(fmt.paragraph(on=1)) 500 output.append(fmt.span(on=1)) 501 output.append(fmt.text("%04d-%02d-%02d" % event_details["start"])) 502 output.append(fmt.span(on=0)) 503 output.append(fmt.text(" - ")) 504 output.append(fmt.span(on=1)) 505 output.append(fmt.text("%04d-%02d-%02d" % event_details["end"])) 506 output.append(fmt.span(on=0)) 507 output.append(fmt.paragraph(on=0)) 508 509 # Location. 510 511 if event_details.has_key("location"): 512 output.append(fmt.paragraph(on=1)) 513 output.append(fmt.text(event_details["location"])) 514 output.append(fmt.paragraph(on=1)) 515 516 # Topics. 517 518 if event_details.has_key("topics") or event_details.has_key("categories"): 519 output.append(fmt.bullet_list(on=1, attr={"class" : "event-topics"})) 520 521 for topic in event_details.get("topics") or event_details.get("categories"): 522 output.append(fmt.listitem(on=1)) 523 output.append(fmt.text(topic)) 524 output.append(fmt.listitem(on=0)) 525 526 output.append(fmt.bullet_list(on=0)) 527 528 output.append(fmt.listitem(on=0)) 529 530 output.append(fmt.bullet_list(on=0)) 531 532 # Output top-level information. 533 534 # End of list view output. 535 536 if mode == "list": 537 output.append(fmt.bullet_list(on=0)) 538 539 return ''.join(output) 540 541 # vim: tabstop=4 expandtab shiftwidth=4