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