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