Steal timeline view from cubicweb

Closes #1279572

authorJulien Cristau <julien.cristau@logilab.fr>
changeset61d60efaf1d6
branchdefault
phasepublic
hiddenno
parent revision#dba2efcd9579 initial cube skeleton
child revision#9921b3aeb9e4 Remove empty files
files modified by this revision
__pkginfo__.py
data/cubes.timeline-bundle.js
data/cubes.timeline-ext.js
data/cubes.timeline.js
data/timeline-bundle.css
data/timeline/blue-circle.png
data/timeline/bubble-arrows.png
data/timeline/bubble-body-and-arrows.png
data/timeline/bubble-body.png
data/timeline/bubble-bottom-arrow.png
data/timeline/bubble-bottom-left.png
data/timeline/bubble-bottom-right.png
data/timeline/bubble-bottom.png
data/timeline/bubble-left-arrow.png
data/timeline/bubble-left.png
data/timeline/bubble-right-arrow.png
data/timeline/bubble-right.png
data/timeline/bubble-top-arrow.png
data/timeline/bubble-top-left.png
data/timeline/bubble-top-right.png
data/timeline/bubble-top.png
data/timeline/close-button.png
data/timeline/copyright-vertical.png
data/timeline/copyright.png
data/timeline/dark-blue-circle.png
data/timeline/dark-green-circle.png
data/timeline/dark-red-circle.png
data/timeline/dull-blue-circle.png
data/timeline/dull-green-circle.png
data/timeline/dull-red-circle.png
data/timeline/gray-circle.png
data/timeline/green-circle.png
data/timeline/message-bottom-left.png
data/timeline/message-bottom-right.png
data/timeline/message-left.png
data/timeline/message-right.png
data/timeline/message-top-left.png
data/timeline/message-top-right.png
data/timeline/message.png
data/timeline/progress-running.gif
data/timeline/red-circle.png
data/timeline/sundial.png
data/timeline/top-bubble.png
views.py
# HG changeset patch
# User Julien Cristau <julien.cristau@logilab.fr>
# Date 1417432759 -3600
# Mon Dec 01 12:19:19 2014 +0100
# Node ID 61d60efaf1d6af62a11d3411b81d40c5ab1be342
# Parent dba2efcd957928b91e2fd832d312cdb424af905e
Steal timeline view from cubicweb

Closes #1279572

diff --git a/__pkginfo__.py b/__pkginfo__.py
@@ -8,11 +8,11 @@
1  version = '.'.join(str(num) for num in numversion)
2 
3  license = 'LGPL'
4  author = 'LOGILAB S.A. (Paris, FRANCE)'
5  author_email = 'contact@logilab.fr'
6 -description = 'basic support for SIMILE's timeline widgets'
7 +description = "basic support for SIMILE's timeline widgets"
8  web = 'http://www.cubicweb.org/project/%s' % distname
9 
10  __depends__ =  {'cubicweb': '>= 3.20.0'}
11  __recommends__ = {}
12 
@@ -38,11 +38,12 @@
13  data_files = [
14      # common files
15      [THIS_CUBE_DIR, [fname for fname in glob('*.py') if fname != 'setup.py']],
16      ]
17  # check for possible extended cube layout
18 -for dname in ('entities', 'views', 'sobjects', 'hooks', 'schema', 'data', 'wdoc', 'i18n', 'migration'):
19 +for dname in ('entities', 'views', 'sobjects', 'hooks', 'schema', 'data',
20 +              join('data', 'timeline'), 'wdoc', 'i18n', 'migration'):
21      if isdir(dname):
22          data_files.append([join(THIS_CUBE_DIR, dname), listdir(dname)])
23  # Note: here, you'll need to add subdirectories if you want
24  # them to be included in the debian package
25 
diff --git a/data/cubes.timeline-bundle.js b/data/cubes.timeline-bundle.js
@@ -0,0 +1,10129 @@
26 +/**
27 + *  This file contains timeline utilities
28 + *  :organization: Logilab
29 + */
30 +
31 +var SimileAjax_urlPrefix = BASE_URL + 'data/';
32 +var Timeline_urlPrefix = BASE_URL + 'data/';
33 +
34 +/*
35 + *  Simile Ajax API
36 + *
37 + *  Include this file in your HTML file as follows::
38 + *
39 + *    <script src="http://simile.mit.edu/ajax/api/simile-ajax-api.js" type="text/javascript"></script>
40 + *
41 + *
42 + */
43 +
44 +if (typeof SimileAjax == "undefined") {
45 +    var SimileAjax = {
46 +        loaded:                 false,
47 +        loadingScriptsCount:    0,
48 +        error:                  null,
49 +        params:                 { bundle:"true" }
50 +    };
51 +
52 +    SimileAjax.Platform = new Object();
53 +        /*
54 +            HACK: We need these 2 things here because we cannot simply append
55 +            a <script> element containing code that accesses SimileAjax.Platform
56 +            to initialize it because IE executes that <script> code first
57 +            before it loads ajax.js and platform.js.
58 +        */
59 +
60 +    var getHead = function(doc) {
61 +        return doc.getElementsByTagName("head")[0];
62 +    };
63 +
64 +    SimileAjax.findScript = function(doc, substring) {
65 +        var heads = doc.documentElement.getElementsByTagName("head");
66 +        for (var h = 0; h < heads.length; h++) {
67 +            var node = heads[h].firstChild;
68 +            while (node != null) {
69 +                if (node.nodeType == 1 && node.tagName.toLowerCase() == "script") {
70 +                    var url = node.src;
71 +                    var i = url.indexOf(substring);
72 +                    if (i >= 0) {
73 +                        return url;
74 +                    }
75 +                }
76 +                node = node.nextSibling;
77 +            }
78 +        }
79 +        return null;
80 +    };
81 +    SimileAjax.includeJavascriptFile = function(doc, url, onerror, charset) {
82 +        onerror = onerror || "";
83 +        if (doc.body == null) {
84 +            try {
85 +                var q = "'" + onerror.replace( /'/g, '&apos' ) + "'"; // "
86 +                doc.write("<script src='" + url + "' onerror="+ q +
87 +                          (charset ? " charset='"+ charset +"'" : "") +
88 +                          " type='text/javascript'>"+ onerror + "</script>");
89 +                return;
90 +            } catch (e) {
91 +                // fall through
92 +            }
93 +        }
94 +
95 +        var script = doc.createElement("script");
96 +        if (onerror) {
97 +            try { script.innerHTML = onerror; } catch(e) {}
98 +            script.setAttribute("onerror", onerror);
99 +        }
100 +        if (charset) {
101 +            script.setAttribute("charset", charset);
102 +        }
103 +        script.type = "text/javascript";
104 +        script.language = "JavaScript";
105 +        script.src = url;
106 +        return getHead(doc).appendChild(script);
107 +    };
108 +    SimileAjax.includeJavascriptFiles = function(doc, urlPrefix, filenames) {
109 +        for (var i = 0; i < filenames.length; i++) {
110 +            SimileAjax.includeJavascriptFile(doc, urlPrefix + filenames[i]);
111 +        }
112 +        SimileAjax.loadingScriptsCount += filenames.length;
113 +        // XXX adim SimileAjax.includeJavascriptFile(doc, SimileAjax.urlPrefix + "scripts/signal.js?" + filenames.length);
114 +    };
115 +    SimileAjax.includeCssFile = function(doc, url) {
116 +        if (doc.body == null) {
117 +            try {
118 +                doc.write("<link rel='stylesheet' href='" + url + "' type='text/css'/>");
119 +                return;
120 +            } catch (e) {
121 +                // fall through
122 +            }
123 +        }
124 +
125 +        var link = doc.createElement("link");
126 +        link.setAttribute("rel", "stylesheet");
127 +        link.setAttribute("type", "text/css");
128 +        link.setAttribute("href", url);
129 +        getHead(doc).appendChild(link);
130 +    };
131 +    SimileAjax.includeCssFiles = function(doc, urlPrefix, filenames) {
132 +        for (var i = 0; i < filenames.length; i++) {
133 +            SimileAjax.includeCssFile(doc, urlPrefix + filenames[i]);
134 +        }
135 +    };
136 +
137 +    /**
138 +     * Append into urls each string in suffixes after prefixing it with urlPrefix.
139 +     * @param {Array} urls
140 +     * @param {String} urlPrefix
141 +     * @param {Array} suffixes
142 +     */
143 +    SimileAjax.prefixURLs = function(urls, urlPrefix, suffixes) {
144 +        for (var i = 0; i < suffixes.length; i++) {
145 +            urls.push(urlPrefix + suffixes[i]);
146 +        }
147 +    };
148 +
149 +    /**
150 +     * Parse out the query parameters from a URL
151 +     * @param {String} url    the url to parse, or location.href if undefined
152 +     * @param {Object} to     optional object to extend with the parameters
153 +     * @param {Object} types  optional object mapping keys to value types
154 +     *        (String, Number, Boolean or Array, String by default)
155 +     * @return a key/value Object whose keys are the query parameter names
156 +     * @type Object
157 +     */
158 +    SimileAjax.parseURLParameters = function(url, to, types) {
159 +        to = to || {};
160 +        types = types || {};
161 +
162 +        if (typeof url == "undefined") {
163 +            url = location.href;
164 +        }
165 +        var q = url.indexOf("?");
166 +        if (q < 0) {
167 +            return to;
168 +        }
169 +        url = (url+"#").slice(q+1, url.indexOf("#")); // toss the URL fragment
170 +
171 +        var params = url.split("&"), param, parsed = {};
172 +        var decode = window.decodeURIComponent || unescape;
173 +        for (var i = 0; param = params[i]; i++) {
174 +            var eq = param.indexOf("=");
175 +            var name = decode(param.slice(0,eq));
176 +            var old = parsed[name];
177 +            if (typeof old == "undefined") {
178 +                old = [];
179 +            } else if (!(old instanceof Array)) {
180 +                old = [old];
181 +            }
182 +            parsed[name] = old.concat(decode(param.slice(eq+1)));
183 +        }
184 +        for (var i in parsed) {
185 +            if (!parsed.hasOwnProperty(i)) continue;
186 +            var type = types[i] || String;
187 +            var data = parsed[i];
188 +            if (!(data instanceof Array)) {
189 +                data = [data];
190 +            }
191 +            if (type === Boolean && data[0] == "false") {
192 +                to[i] = false; // because Boolean("false") === true
193 +            } else {
194 +                to[i] = type.apply(this, data);
195 +            }
196 +        }
197 +        return to;
198 +    };
199 +
200 +    (function() {
201 +        var javascriptFiles = [
202 +            "jquery-1.2.6.js",
203 +            "platform.js",
204 +            "debug.js",
205 +            "xmlhttp.js",
206 +            "json.js",
207 +            "dom.js",
208 +            "graphics.js",
209 +            "date-time.js",
210 +            "string.js",
211 +            "html.js",
212 +            "data-structure.js",
213 +            "units.js",
214 +
215 +            "ajax.js",
216 +            "history.js",
217 +            "window-manager.js"
218 +        ];
219 +        var cssFiles = [
220 +            "graphics.css"
221 +        ];
222 +
223 +        if (typeof SimileAjax_urlPrefix == "string") {
224 +            SimileAjax.urlPrefix = SimileAjax_urlPrefix;
225 +        } else {
226 +            var url = SimileAjax.findScript(document, "simile-ajax-api.js");
227 +            if (url == null) {
228 +                SimileAjax.error = new Error("Failed to derive URL prefix for Simile Ajax API code files");
229 +                return;
230 +            }
231 +
232 +            SimileAjax.urlPrefix = url.substr(0, url.indexOf("simile-ajax-api.js"));
233 +        }
234 +
235 +        SimileAjax.parseURLParameters(url, SimileAjax.params, {bundle:Boolean});
236 +//         if (SimileAjax.params.bundle) {
237 +//             SimileAjax.includeJavascriptFiles(document, SimileAjax.urlPrefix, [ "simile-ajax-bundle.js" ]);
238 +//         } else {
239 +//             SimileAjax.includeJavascriptFiles(document, SimileAjax.urlPrefix + "scripts/", javascriptFiles);
240 +//         }
241 +//         SimileAjax.includeCssFiles(document, SimileAjax.urlPrefix + "styles/", cssFiles);
242 +
243 +        SimileAjax.loaded = true;
244 +    })();
245 +}
246 +/*
247 + *  Platform Utility Functions and Constants
248 + *
249 + */
250 +
251 +/*  This must be called after our jQuery has been loaded
252 +    but before control returns to user-code.
253 +*/
254 +SimileAjax.jQuery = jQuery;
255 +// SimileAjax.jQuery = jQuery.noConflict(true);
256 +if (typeof window["$"] == "undefined") {
257 +    window.$ = SimileAjax.jQuery;
258 +}
259 +
260 +SimileAjax.Platform.os = {
261 +    isMac:   false,
262 +    isWin:   false,
263 +    isWin32: false,
264 +    isUnix:  false
265 +};
266 +SimileAjax.Platform.browser = {
267 +    isIE:           false,
268 +    isNetscape:     false,
269 +    isMozilla:      false,
270 +    isFirefox:      false,
271 +    isOpera:        false,
272 +    isSafari:       false,
273 +
274 +    majorVersion:   0,
275 +    minorVersion:   0
276 +};
277 +
278 +(function() {
279 +    var an = navigator.appName.toLowerCase();
280 +	var ua = navigator.userAgent.toLowerCase();
281 +
282 +    /*
283 +     *  Operating system
284 +     */
285 +	SimileAjax.Platform.os.isMac = (ua.indexOf('mac') != -1);
286 +	SimileAjax.Platform.os.isWin = (ua.indexOf('win') != -1);
287 +	SimileAjax.Platform.os.isWin32 = SimileAjax.Platform.isWin && (
288 +        ua.indexOf('95') != -1 ||
289 +        ua.indexOf('98') != -1 ||
290 +        ua.indexOf('nt') != -1 ||
291 +        ua.indexOf('win32') != -1 ||
292 +        ua.indexOf('32bit') != -1
293 +    );
294 +	SimileAjax.Platform.os.isUnix = (ua.indexOf('x11') != -1);
295 +
296 +    /*
297 +     *  Browser
298 +     */
299 +    SimileAjax.Platform.browser.isIE = (an.indexOf("microsoft") != -1);
300 +    SimileAjax.Platform.browser.isNetscape = (an.indexOf("netscape") != -1);
301 +    SimileAjax.Platform.browser.isMozilla = (ua.indexOf("mozilla") != -1);
302 +    SimileAjax.Platform.browser.isFirefox = (ua.indexOf("firefox") != -1);
303 +    SimileAjax.Platform.browser.isOpera = (an.indexOf("opera") != -1);
304 +    SimileAjax.Platform.browser.isSafari = (an.indexOf("safari") != -1);
305 +
306 +    var parseVersionString = function(s) {
307 +        var a = s.split(".");
308 +        SimileAjax.Platform.browser.majorVersion = parseInt(a[0]);
309 +        SimileAjax.Platform.browser.minorVersion = parseInt(a[1]);
310 +    };
311 +    var indexOf = function(s, sub, start) {
312 +        var i = s.indexOf(sub, start);
313 +        return i >= 0 ? i : s.length;
314 +    };
315 +
316 +    if (SimileAjax.Platform.browser.isMozilla) {
317 +        var offset = ua.indexOf("mozilla/");
318 +        if (offset >= 0) {
319 +            parseVersionString(ua.substring(offset + 8, indexOf(ua, " ", offset)));
320 +        }
321 +    }
322 +    if (SimileAjax.Platform.browser.isIE) {
323 +        var offset = ua.indexOf("msie ");
324 +        if (offset >= 0) {
325 +            parseVersionString(ua.substring(offset + 5, indexOf(ua, ";", offset)));
326 +        }
327 +    }
328 +    if (SimileAjax.Platform.browser.isNetscape) {
329 +        var offset = ua.indexOf("rv:");
330 +        if (offset >= 0) {
331 +            parseVersionString(ua.substring(offset + 3, indexOf(ua, ")", offset)));
332 +        }
333 +    }
334 +    if (SimileAjax.Platform.browser.isFirefox) {
335 +        var offset = ua.indexOf("firefox/");
336 +        if (offset >= 0) {
337 +            parseVersionString(ua.substring(offset + 8, indexOf(ua, " ", offset)));
338 +        }
339 +    }
340 +
341 +    if (!("localeCompare" in String.prototype)) {
342 +        String.prototype.localeCompare = function (s) {
343 +            if (this < s) return -1;
344 +            else if (this > s) return 1;
345 +            else return 0;
346 +        };
347 +    }
348 +})();
349 +
350 +SimileAjax.Platform.getDefaultLocale = function() {
351 +    return SimileAjax.Platform.clientLocale;
352 +};
353 +/*
354 + *  Debug Utility Functions
355 + *
356 + */
357 +
358 +SimileAjax.Debug = {
359 +    silent: false
360 +};
361 +
362 +SimileAjax.Debug.log = function(msg) {
363 +    var f;
364 +    if ("console" in window && "log" in window.console) { // FireBug installed
365 +        f = function(msg2) {
366 +            console.log(msg2);
367 +        }
368 +    } else {
369 +        f = function(msg2) {
370 +            if (!SimileAjax.Debug.silent) {
371 +                alert(msg2);
372 +            }
373 +        }
374 +    }
375 +    SimileAjax.Debug.log = f;
376 +    f(msg);
377 +};
378 +
379 +SimileAjax.Debug.warn = function(msg) {
380 +    var f;
381 +    if ("console" in window && "warn" in window.console) { // FireBug installed
382 +        f = function(msg2) {
383 +            console.warn(msg2);
384 +        }
385 +    } else {
386 +        f = function(msg2) {
387 +            if (!SimileAjax.Debug.silent) {
388 +                alert(msg2);
389 +            }
390 +        }
391 +    }
392 +    SimileAjax.Debug.warn = f;
393 +    f(msg);
394 +};
395 +
396 +SimileAjax.Debug.exception = function(e, msg) {
397 +    var f, params = SimileAjax.parseURLParameters();
398 +    if (params.errors == "throw" || SimileAjax.params.errors == "throw") {
399 +        f = function(e2, msg2) {
400 +            throw(e2); // do not hide from browser's native debugging features
401 +        };
402 +    } else if ("console" in window && "error" in window.console) { // FireBug installed
403 +        f = function(e2, msg2) {
404 +            if (msg2 != null) {
405 +                console.error(msg2 + " %o", e2);
406 +            } else {
407 +                console.error(e2);
408 +            }
409 +            throw(e2); // do not hide from browser's native debugging features
410 +        };
411 +    } else {
412 +        f = function(e2, msg2) {
413 +            if (!SimileAjax.Debug.silent) {
414 +                alert("Caught exception: " + msg2 + "\n\nDetails: " + ("description" in e2 ? e2.description : e2));
415 +            }
416 +            throw(e2); // do not hide from browser's native debugging features
417 +        };
418 +    }
419 +    SimileAjax.Debug.exception = f;
420 +    f(e, msg);
421 +};
422 +
423 +SimileAjax.Debug.objectToString = function(o) {
424 +    return SimileAjax.Debug._objectToString(o, "");
425 +};
426 +
427 +SimileAjax.Debug._objectToString = function(o, indent) {
428 +    var indent2 = indent + " ";
429 +    if (typeof o == "object") {
430 +        var s = "{";
431 +        for (n in o) {
432 +            s += indent2 + n + ": " + SimileAjax.Debug._objectToString(o[n], indent2) + "\n";
433 +        }
434 +        s += indent + "}";
435 +        return s;
436 +    } else if (typeof o == "array") {
437 +        var s = "[";
438 +        for (var n = 0; n < o.length; n++) {
439 +            s += SimileAjax.Debug._objectToString(o[n], indent2) + "\n";
440 +        }
441 +        s += indent + "]";
442 +        return s;
443 +    } else {
444 +        return o;
445 +    }
446 +};
447 +/**
448 + * @fileOverview XmlHttp utility functions
449 + * @name SimileAjax.XmlHttp
450 + */
451 +
452 +SimileAjax.XmlHttp = new Object();
453 +
454 +/**
455 + *  Callback for XMLHttp onRequestStateChange.
456 + */
457 +SimileAjax.XmlHttp._onReadyStateChange = function(xmlhttp, fError, fDone) {
458 +    switch (xmlhttp.readyState) {
459 +    // 1: Request not yet made
460 +    // 2: Contact established with server but nothing downloaded yet
461 +    // 3: Called multiple while downloading in progress
462 +
463 +    // Download complete
464 +    case 4:
465 +        try {
466 +            if (xmlhttp.status == 0     // file:// urls, works on Firefox
467 +             || xmlhttp.status == 200   // http:// urls
468 +            ) {
469 +                if (fDone) {
470 +                    fDone(xmlhttp);
471 +                }
472 +            } else {
473 +                if (fError) {
474 +                    fError(
475 +                        xmlhttp.statusText,
476 +                        xmlhttp.status,
477 +                        xmlhttp
478 +                    );
479 +                }
480 +            }
481 +        } catch (e) {
482 +            SimileAjax.Debug.exception("XmlHttp: Error handling onReadyStateChange", e);
483 +        }
484 +        break;
485 +    }
486 +};
487 +
488 +/**
489 + *  Creates an XMLHttpRequest object. On the first run, this
490 + *  function creates a platform-specific function for
491 + *  instantiating an XMLHttpRequest object and then replaces
492 + *  itself with that function.
493 + */
494 +SimileAjax.XmlHttp._createRequest = function() {
495 +    if (SimileAjax.Platform.browser.isIE) {
496 +        var programIDs = [
497 +        "Msxml2.XMLHTTP",
498 +        "Microsoft.XMLHTTP",
499 +        "Msxml2.XMLHTTP.4.0"
500 +        ];
501 +        for (var i = 0; i < programIDs.length; i++) {
502 +            try {
503 +                var programID = programIDs[i];
504 +                var f = function() {
505 +                    return new ActiveXObject(programID);
506 +                };
507 +                var o = f();
508 +
509 +                // We are replacing the SimileAjax._createXmlHttpRequest
510 +                // function with this inner function as we've
511 +                // found out that it works. This is so that we
512 +                // don't have to do all the testing over again
513 +                // on subsequent calls.
514 +                SimileAjax.XmlHttp._createRequest = f;
515 +
516 +                return o;
517 +            } catch (e) {
518 +                // silent
519 +            }
520 +        }
521 +        // fall through to try new XMLHttpRequest();
522 +    }
523 +
524 +    try {
525 +        var f = function() {
526 +            return new XMLHttpRequest();
527 +        };
528 +        var o = f();
529 +
530 +        // We are replacing the SimileAjax._createXmlHttpRequest
531 +        // function with this inner function as we've
532 +        // found out that it works. This is so that we
533 +        // don't have to do all the testing over again
534 +        // on subsequent calls.
535 +        SimileAjax.XmlHttp._createRequest = f;
536 +
537 +        return o;
538 +    } catch (e) {
539 +        throw new Error("Failed to create an XMLHttpRequest object");
540 +    }
541 +};
542 +
543 +/**
544 + * Performs an asynchronous HTTP GET.
545 + *
546 + * @param {Function} fError a function of the form
547 +     function(statusText, statusCode, xmlhttp)
548 + * @param {Function} fDone a function of the form function(xmlhttp)
549 + */
550 +SimileAjax.XmlHttp.get = function(url, fError, fDone) {
551 +    var xmlhttp = SimileAjax.XmlHttp._createRequest();
552 +
553 +    xmlhttp.open("GET", url, true);
554 +    xmlhttp.onreadystatechange = function() {
555 +        SimileAjax.XmlHttp._onReadyStateChange(xmlhttp, fError, fDone);
556 +    };
557 +    xmlhttp.send(null);
558 +};
559 +
560 +/**
561 + * Performs an asynchronous HTTP POST.
562 + *
563 + * @param {Function} fError a function of the form
564 +     function(statusText, statusCode, xmlhttp)
565 + * @param {Function} fDone a function of the form function(xmlhttp)
566 + */
567 +SimileAjax.XmlHttp.post = function(url, body, fError, fDone) {
568 +    var xmlhttp = SimileAjax.XmlHttp._createRequest();
569 +
570 +    xmlhttp.open("POST", url, true);
571 +    xmlhttp.onreadystatechange = function() {
572 +        SimileAjax.XmlHttp._onReadyStateChange(xmlhttp, fError, fDone);
573 +    };
574 +    xmlhttp.send(body);
575 +};
576 +
577 +SimileAjax.XmlHttp._forceXML = function(xmlhttp) {
578 +    try {
579 +        xmlhttp.overrideMimeType("text/xml");
580 +    } catch (e) {
581 +        xmlhttp.setrequestheader("Content-Type", "text/xml");
582 +    }
583 +};/*
584 + *  Copied directly from http://www.json.org/json.js.
585 + */
586 +
587 +/*
588 +    json.js
589 +    2006-04-28
590 +
591 +    This file adds these methods to JavaScript:
592 +
593 +        object.toJSONString()
594 +
595 +            This method produces a JSON text from an object. The
596 +            object must not contain any cyclical references.
597 +
598 +        array.toJSONString()
599 +
600 +            This method produces a JSON text from an array. The
601 +            array must not contain any cyclical references.
602 +
603 +        string.parseJSON()
604 +
605 +            This method parses a JSON text to produce an object or
606 +            array. It will return false if there is an error.
607 +*/
608 +
609 +SimileAjax.JSON = new Object();
610 +
611 +(function () {
612 +    var m = {
613 +        '\b': '\\b',
614 +        '\t': '\\t',
615 +        '\n': '\\n',
616 +        '\f': '\\f',
617 +        '\r': '\\r',
618 +        '"' : '\\"',
619 +        '\\': '\\\\'
620 +    };
621 +    var s = {
622 +        array: function (x) {
623 +            var a = ['['], b, f, i, l = x.length, v;
624 +            for (i = 0; i < l; i += 1) {
625 +                v = x[i];
626 +                f = s[typeof v];
627 +                if (f) {
628 +                    v = f(v);
629 +                    if (typeof v == 'string') {
630 +                        if (b) {
631 +                            a[a.length] = ',';
632 +                        }
633 +                        a[a.length] = v;
634 +                        b = true;
635 +                    }
636 +                }
637 +            }
638 +            a[a.length] = ']';
639 +            return a.join('');
640 +        },
641 +        'boolean': function (x) {
642 +            return String(x);
643 +        },
644 +        'null': function (x) {
645 +            return "null";
646 +        },
647 +        number: function (x) {
648 +            return isFinite(x) ? String(x) : 'null';
649 +        },
650 +        object: function (x) {
651 +            if (x) {
652 +                if (x instanceof Array) {
653 +                    return s.array(x);
654 +                }
655 +                var a = ['{'], b, f, i, v;
656 +                for (i in x) {
657 +                    v = x[i];
658 +                    f = s[typeof v];
659 +                    if (f) {
660 +                        v = f(v);
661 +                        if (typeof v == 'string') {
662 +                            if (b) {
663 +                                a[a.length] = ',';
664 +                            }
665 +                            a.push(s.string(i), ':', v);
666 +                            b = true;
667 +                        }
668 +                    }
669 +                }
670 +                a[a.length] = '}';
671 +                return a.join('');
672 +            }
673 +            return 'null';
674 +        },
675 +        string: function (x) {
676 +            if (/["\\\x00-\x1f]/.test(x)) {
677 +                x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) {
678 +                    var c = m[b];
679 +                    if (c) {
680 +                        return c;
681 +                    }
682 +                    c = b.charCodeAt();
683 +                    return '\\u00' +
684 +                        Math.floor(c / 16).toString(16) +
685 +                        (c % 16).toString(16);
686 +                });
687 +            }
688 +            return '"' + x + '"';
689 +        }
690 +    };
691 +
692 +    SimileAjax.JSON.toJSONString = function(o) {
693 +        if (o instanceof Object) {
694 +            return s.object(o);
695 +        } else if (o instanceof Array) {
696 +            return s.array(o);
697 +        } else {
698 +            return o.toString();
699 +        }
700 +    };
701 +
702 +    SimileAjax.JSON.parseJSON = function () {
703 +        try {
704 +            return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
705 +                    this.replace(/"(\\.|[^"\\])*"/g, ''))) &&
706 +                eval('(' + this + ')');
707 +        } catch (e) {
708 +            return false;
709 +        }
710 +    };
711 +})();
712 +/*
713 + *  DOM Utility Functions
714 + *
715 + */
716 +
717 +SimileAjax.DOM = new Object();
718 +
719 +SimileAjax.DOM.registerEventWithObject = function(elmt, eventName, obj, handlerName) {
720 +    SimileAjax.DOM.registerEvent(elmt, eventName, function(elmt2, evt, target) {
721 +        return obj[handlerName].call(obj, elmt2, evt, target);
722 +    });
723 +};
724 +
725 +SimileAjax.DOM.registerEvent = function(elmt, eventName, handler) {
726 +    var handler2 = function(evt) {
727 +        evt = (evt) ? evt : ((event) ? event : null);
728 +        if (evt) {
729 +            var target = (evt.target) ?
730 +                evt.target : ((evt.srcElement) ? evt.srcElement : null);
731 +            if (target) {
732 +                target = (target.nodeType == 1 || target.nodeType == 9) ?
733 +                    target : target.parentNode;
734 +            }
735 +
736 +            return handler(elmt, evt, target);
737 +        }
738 +        return true;
739 +    }
740 +
741 +    if (SimileAjax.Platform.browser.isIE) {
742 +        elmt.attachEvent("on" + eventName, handler2);
743 +    } else {
744 +        elmt.addEventListener(eventName, handler2, false);
745 +    }
746 +};
747 +
748 +SimileAjax.DOM.getPageCoordinates = function(elmt) {
749 +    var left = 0;
750 +    var top = 0;
751 +
752 +    if (elmt.nodeType != 1) {
753 +        elmt = elmt.parentNode;
754 +    }
755 +
756 +    var elmt2 = elmt;
757 +    while (elmt2 != null) {
758 +        left += elmt2.offsetLeft;
759 +        top += elmt2.offsetTop;
760 +        elmt2 = elmt2.offsetParent;
761 +    }
762 +
763 +    var body = document.body;
764 +    while (elmt != null && elmt != body) {
765 +        if ("scrollLeft" in elmt) {
766 +            left -= elmt.scrollLeft;
767 +            top -= elmt.scrollTop;
768 +        }
769 +        elmt = elmt.parentNode;
770 +    }
771 +
772 +    return { left: left, top: top };
773 +};
774 +
775 +SimileAjax.DOM.getSize = function(elmt) {
776 +	var w = this.getStyle(elmt,"width");
777 +	var h = this.getStyle(elmt,"height");
778 +	if (w.indexOf("px") > -1) w = w.replace("px","");
779 +	if (h.indexOf("px") > -1) h = h.replace("px","");
780 +	return {
781 +		w: w,
782 +		h: h
783 +	}
784 +}
785 +
786 +SimileAjax.DOM.getStyle = function(elmt, styleProp) {
787 +    if (elmt.currentStyle) { // IE
788 +        var style = elmt.currentStyle[styleProp];
789 +    } else if (window.getComputedStyle) { // standard DOM
790 +        var style = document.defaultView.getComputedStyle(elmt, null).getPropertyValue(styleProp);
791 +    } else {
792 +    	var style = "";
793 +    }
794 +    return style;
795 +}
796 +
797 +SimileAjax.DOM.getEventRelativeCoordinates = function(evt, elmt) {
798 +    if (SimileAjax.Platform.browser.isIE) {
799 +      if (evt.type == "mousewheel") {
800 +        var coords = SimileAjax.DOM.getPageCoordinates(elmt);
801 +        return {
802 +          x: evt.clientX - coords.left,
803 +          y: evt.clientY - coords.top
804 +        };
805 +      } else {
806 +        return {
807 +          x: evt.offsetX,
808 +          y: evt.offsetY
809 +        };
810 +      }
811 +    } else {
812 +        var coords = SimileAjax.DOM.getPageCoordinates(elmt);
813 +
814 +        if ((evt.type == "DOMMouseScroll") &&
815 +          SimileAjax.Platform.browser.isFirefox &&
816 +          (SimileAjax.Platform.browser.majorVersion == 2)) {
817 +          // Due to: https://bugzilla.mozilla.org/show_bug.cgi?id=352179
818 +
819 +          return {
820 +            x: evt.screenX - coords.left,
821 +            y: evt.screenY - coords.top
822 +          };
823 +        } else {
824 +          return {
825 +              x: evt.pageX - coords.left,
826 +              y: evt.pageY - coords.top
827 +          };
828 +        }
829 +    }
830 +};
831 +
832 +SimileAjax.DOM.getEventPageCoordinates = function(evt) {
833 +    if (SimileAjax.Platform.browser.isIE) {
834 +        return {
835 +            x: evt.clientX + document.body.scrollLeft,
836 +            y: evt.clientY + document.body.scrollTop
837 +        };
838 +    } else {
839 +        return {
840 +            x: evt.pageX,
841 +            y: evt.pageY
842 +        };
843 +    }
844 +};
845 +
846 +SimileAjax.DOM.hittest = function(x, y, except) {
847 +    return SimileAjax.DOM._hittest(document.body, x, y, except);
848 +};
849 +
850 +SimileAjax.DOM._hittest = function(elmt, x, y, except) {
851 +    var childNodes = elmt.childNodes;
852 +    outer: for (var i = 0; i < childNodes.length; i++) {
853 +        var childNode = childNodes[i];
854 +        for (var j = 0; j < except.length; j++) {
855 +            if (childNode == except[j]) {
856 +                continue outer;
857 +            }
858 +        }
859 +
860 +        if (childNode.offsetWidth == 0 && childNode.offsetHeight == 0) {
861 +            /*
862 +             *  Sometimes SPAN elements have zero width and height but
863 +             *  they have children like DIVs that cover non-zero areas.
864 +             */
865 +            var hitNode = SimileAjax.DOM._hittest(childNode, x, y, except);
866 +            if (hitNode != childNode) {
867 +                return hitNode;
868 +            }
869 +        } else {
870 +            var top = 0;
871 +            var left = 0;
872 +
873 +            var node = childNode;
874 +            while (node) {
875 +                top += node.offsetTop;
876 +                left += node.offsetLeft;
877 +                node = node.offsetParent;
878 +            }
879 +
880 +            if (left <= x && top <= y && (x - left) < childNode.offsetWidth && (y - top) < childNode.offsetHeight) {
881 +                return SimileAjax.DOM._hittest(childNode, x, y, except);
882 +            } else if (childNode.nodeType == 1 && childNode.tagName == "TR") {
883 +                /*
884 +                 *  Table row might have cells that span several rows.
885 +                 */
886 +                var childNode2 = SimileAjax.DOM._hittest(childNode, x, y, except);
887 +                if (childNode2 != childNode) {
888 +                    return childNode2;
889 +                }
890 +            }
891 +        }
892 +    }
893 +    return elmt;
894 +};
895 +
896 +SimileAjax.DOM.cancelEvent = function(evt) {
897 +    evt.returnValue = false;
898 +    evt.cancelBubble = true;
899 +    if ("preventDefault" in evt) {
900 +        evt.preventDefault();
901 +    }
902 +};
903 +
904 +SimileAjax.DOM.appendClassName = function(elmt, className) {
905 +    var classes = elmt.className.split(" ");
906 +    for (var i = 0; i < classes.length; i++) {
907 +        if (classes[i] == className) {
908 +            return;
909 +        }
910 +    }
911 +    classes.push(className);
912 +    elmt.className = classes.join(" ");
913 +};
914 +
915 +SimileAjax.DOM.createInputElement = function(type) {
916 +    var div = document.createElement("div");
917 +    div.innerHTML = "<input type='" + type + "' />";
918 +
919 +    return div.firstChild;
920 +};
921 +
922 +SimileAjax.DOM.createDOMFromTemplate = function(template) {
923 +    var result = {};
924 +    result.elmt = SimileAjax.DOM._createDOMFromTemplate(template, result, null);
925 +
926 +    return result;
927 +};
928 +
929 +SimileAjax.DOM._createDOMFromTemplate = function(templateNode, result, parentElmt) {
930 +    if (templateNode == null) {
931 +        /*
932 +        var node = doc.createTextNode("--null--");
933 +        if (parentElmt != null) {
934 +            parentElmt.appendChild(node);
935 +        }
936 +        return node;
937 +        */
938 +        return null;
939 +    } else if (typeof templateNode != "object") {
940 +        var node = document.createTextNode(templateNode);
941 +        if (parentElmt != null) {
942 +            parentElmt.appendChild(node);
943 +        }
944 +        return node;
945 +    } else {
946 +        var elmt = null;
947 +        if ("tag" in templateNode) {
948 +            var tag = templateNode.tag;
949 +            if (parentElmt != null) {
950 +                if (tag == "tr") {
951 +                    elmt = parentElmt.insertRow(parentElmt.rows.length);
952 +                } else if (tag == "td") {
953 +                    elmt = parentElmt.insertCell(parentElmt.cells.length);
954 +                }
955 +            }
956 +            if (elmt == null) {
957 +                elmt = tag == "input" ?
958 +                    SimileAjax.DOM.createInputElement(templateNode.type) :
959 +                    document.createElement(tag);
960 +
961 +                if (parentElmt != null) {
962 +                    parentElmt.appendChild(elmt);
963 +                }
964 +            }
965 +        } else {
966 +            elmt = templateNode.elmt;
967 +            if (parentElmt != null) {
968 +                parentElmt.appendChild(elmt);
969 +            }
970 +        }
971 +
972 +        for (var attribute in templateNode) {
973 +            var value = templateNode[attribute];
974 +
975 +            if (attribute == "field") {
976 +                result[value] = elmt;
977 +
978 +            } else if (attribute == "className") {
979 +                elmt.className = value;
980 +            } else if (attribute == "id") {
981 +                elmt.id = value;
982 +            } else if (attribute == "title") {
983 +                elmt.title = value;
984 +            } else if (attribute == "type" && elmt.tagName == "input") {
985 +                // do nothing
986 +            } else if (attribute == "style") {
987 +                for (n in value) {
988 +                    var v = value[n];
989 +                    if (n == "float") {
990 +                        n = SimileAjax.Platform.browser.isIE ? "styleFloat" : "cssFloat";
991 +                    }
992 +                    elmt.style[n] = v;
993 +                }
994 +            } else if (attribute == "children") {
995 +                for (var i = 0; i < value.length; i++) {
996 +                    SimileAjax.DOM._createDOMFromTemplate(value[i], result, elmt);
997 +                }
998 +            } else if (attribute != "tag" && attribute != "elmt") {
999 +                elmt.setAttribute(attribute, value);
1000 +            }
1001 +        }
1002 +        return elmt;
1003 +    }
1004 +}
1005 +
1006 +SimileAjax.DOM._cachedParent = null;
1007 +SimileAjax.DOM.createElementFromString = function(s) {
1008 +    if (SimileAjax.DOM._cachedParent == null) {
1009 +        SimileAjax.DOM._cachedParent = document.createElement("div");
1010 +    }
1011 +    SimileAjax.DOM._cachedParent.innerHTML = s;
1012 +    return SimileAjax.DOM._cachedParent.firstChild;
1013 +};
1014 +
1015 +SimileAjax.DOM.createDOMFromString = function(root, s, fieldElmts) {
1016 +    var elmt = typeof root == "string" ? document.createElement(root) : root;
1017 +    elmt.innerHTML = s;
1018 +
1019 +    var dom = { elmt: elmt };
1020 +    SimileAjax.DOM._processDOMChildrenConstructedFromString(dom, elmt, fieldElmts != null ? fieldElmts : {} );
1021 +
1022 +    return dom;
1023 +};
1024 +
1025 +SimileAjax.DOM._processDOMConstructedFromString = function(dom, elmt, fieldElmts) {
1026 +    var id = elmt.id;
1027 +    if (id != null && id.length > 0) {
1028 +        elmt.removeAttribute("id");
1029 +        if (id in fieldElmts) {
1030 +            var parentElmt = elmt.parentNode;
1031 +            parentElmt.insertBefore(fieldElmts[id], elmt);
1032 +            parentElmt.removeChild(elmt);
1033 +
1034 +            dom[id] = fieldElmts[id];
1035 +            return;
1036 +        } else {
1037 +            dom[id] = elmt;
1038 +        }
1039 +    }
1040 +
1041 +    if (elmt.hasChildNodes()) {
1042 +        SimileAjax.DOM._processDOMChildrenConstructedFromString(dom, elmt, fieldElmts);
1043 +    }
1044 +};
1045 +
1046 +SimileAjax.DOM._processDOMChildrenConstructedFromString = function(dom, elmt, fieldElmts) {
1047 +    var node = elmt.firstChild;
1048 +    while (node != null) {
1049 +        var node2 = node.nextSibling;
1050 +        if (node.nodeType == 1) {
1051 +            SimileAjax.DOM._processDOMConstructedFromString(dom, node, fieldElmts);
1052 +        }
1053 +        node = node2;
1054 +    }
1055 +};
1056 +/**
1057 + * @fileOverview Graphics utility functions and constants
1058 + * @name SimileAjax.Graphics
1059 + */
1060 +
1061 +SimileAjax.Graphics = new Object();
1062 +
1063 +/**
1064 + * A boolean value indicating whether PNG translucency is supported on the
1065 + * user's browser or not.
1066 + *
1067 + * @type Boolean
1068 + */
1069 +SimileAjax.Graphics.pngIsTranslucent = (!SimileAjax.Platform.browser.isIE) || (SimileAjax.Platform.browser.majorVersion > 6);
1070 +if (!SimileAjax.Graphics.pngIsTranslucent) {
1071 +    SimileAjax.includeCssFile(document, SimileAjax.urlPrefix + "styles/graphics-ie6.css");
1072 +}
1073 +
1074 +/*
1075 + *  Opacity, translucency
1076 + *
1077 + */
1078 +SimileAjax.Graphics._createTranslucentImage1 = function(url, verticalAlign) {
1079 +    var elmt = document.createElement("img");
1080 +    elmt.setAttribute("src", url);
1081 +    if (verticalAlign != null) {
1082 +        elmt.style.verticalAlign = verticalAlign;
1083 +    }
1084 +    return elmt;
1085 +};
1086 +SimileAjax.Graphics._createTranslucentImage2 = function(url, verticalAlign) {
1087 +    var elmt = document.createElement("img");
1088 +    elmt.style.width = "1px";  // just so that IE will calculate the size property
1089 +    elmt.style.height = "1px";
1090 +    elmt.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + url +"', sizingMethod='image')";
1091 +    elmt.style.verticalAlign = (verticalAlign != null) ? verticalAlign : "middle";
1092 +    return elmt;
1093 +};
1094 +
1095 +/**
1096 + * Creates a DOM element for an <code>img</code> tag using the URL given. This
1097 + * is a convenience method that automatically includes the necessary CSS to
1098 + * allow for translucency, even on IE.
1099 + *
1100 + * @function
1101 + * @param {String} url the URL to the image
1102 + * @param {String} verticalAlign the CSS value for the image's vertical-align
1103 + * @return {Element} a DOM element containing the <code>img</code> tag
1104 + */
1105 +SimileAjax.Graphics.createTranslucentImage = SimileAjax.Graphics.pngIsTranslucent ?
1106 +    SimileAjax.Graphics._createTranslucentImage1 :
1107 +    SimileAjax.Graphics._createTranslucentImage2;
1108 +
1109 +SimileAjax.Graphics._createTranslucentImageHTML1 = function(url, verticalAlign) {
1110 +    return "<img src=\"" + url + "\"" +
1111 +        (verticalAlign != null ? " style=\"vertical-align: " + verticalAlign + ";\"" : "") +
1112 +        " />";
1113 +};
1114 +SimileAjax.Graphics._createTranslucentImageHTML2 = function(url, verticalAlign) {
1115 +    var style =
1116 +        "width: 1px; height: 1px; " +
1117 +        "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + url +"', sizingMethod='image');" +
1118 +        (verticalAlign != null ? " vertical-align: " + verticalAlign + ";" : "");
1119 +
1120 +    return "<img src='" + url + "' style=\"" + style + "\" />";
1121 +};
1122 +
1123 +/**
1124 + * Creates an HTML string for an <code>img</code> tag using the URL given.
1125 + * This is a convenience method that automatically includes the necessary CSS
1126 + * to allow for translucency, even on IE.
1127 + *
1128 + * @function
1129 + * @param {String} url the URL to the image
1130 + * @param {String} verticalAlign the CSS value for the image's vertical-align
1131 + * @return {String} a string containing the <code>img</code> tag
1132 + */
1133 +SimileAjax.Graphics.createTranslucentImageHTML = SimileAjax.Graphics.pngIsTranslucent ?
1134 +    SimileAjax.Graphics._createTranslucentImageHTML1 :
1135 +    SimileAjax.Graphics._createTranslucentImageHTML2;
1136 +
1137 +/**
1138 + * Sets the opacity on the given DOM element.
1139 + *
1140 + * @param {Element} elmt the DOM element to set the opacity on
1141 + * @param {Number} opacity an integer from 0 to 100 specifying the opacity
1142 + */
1143 +SimileAjax.Graphics.setOpacity = function(elmt, opacity) {
1144 +    if (SimileAjax.Platform.browser.isIE) {
1145 +        elmt.style.filter = "progid:DXImageTransform.Microsoft.Alpha(Style=0,Opacity=" + opacity + ")";
1146 +    } else {
1147 +        var o = (opacity / 100).toString();
1148 +        elmt.style.opacity = o;
1149 +        elmt.style.MozOpacity = o;
1150 +    }
1151 +};
1152 +
1153 +/*
1154 + *  Bubble
1155 + *
1156 + */
1157 +
1158 +SimileAjax.Graphics.bubbleConfig = {
1159 +    containerCSSClass:              "simileAjax-bubble-container",
1160 +    innerContainerCSSClass:         "simileAjax-bubble-innerContainer",
1161 +    contentContainerCSSClass:       "simileAjax-bubble-contentContainer",
1162 +
1163 +    borderGraphicSize:              50,
1164 +    borderGraphicCSSClassPrefix:    "simileAjax-bubble-border-",
1165 +
1166 +    arrowGraphicTargetOffset:       33,  // from tip of arrow to the side of the graphic that touches the content of the bubble
1167 +    arrowGraphicLength:             100, // dimension of arrow graphic along the direction that the arrow points
1168 +    arrowGraphicWidth:              49,  // dimension of arrow graphic perpendicular to the direction that the arrow points
1169 +    arrowGraphicCSSClassPrefix:     "simileAjax-bubble-arrow-",
1170 +
1171 +    closeGraphicCSSClass:           "simileAjax-bubble-close",
1172 +
1173 +    extraPadding:                   20
1174 +};
1175 +
1176 +/**
1177 + * Creates a nice, rounded bubble popup with the given content in a div,
1178 + * page coordinates and a suggested width. The bubble will point to the
1179 + * location on the page as described by pageX and pageY.  All measurements
1180 + * should be given in pixels.
1181 + *
1182 + * @param {Element} the content div
1183 + * @param {Number} pageX the x coordinate of the point to point to
1184 + * @param {Number} pageY the y coordinate of the point to point to
1185 + * @param {Number} contentWidth a suggested width of the content
1186 + * @param {String} orientation a string ("top", "bottom", "left", or "right")
1187 + *   that describes the orientation of the arrow on the bubble
1188 + * @param {Number} maxHeight. Add a scrollbar div if bubble would be too tall.
1189 + *   Default of 0 or null means no maximum
1190 + */
1191 +SimileAjax.Graphics.createBubbleForContentAndPoint = function(
1192 +       div, pageX, pageY, contentWidth, orientation, maxHeight) {
1193 +    if (typeof contentWidth != "number") {
1194 +        contentWidth = 300;
1195 +    }
1196 +    if (typeof maxHeight != "number") {
1197 +        maxHeight = 0;
1198 +    }
1199 +
1200 +    div.style.position = "absolute";
1201 +    div.style.left = "-5000px";
1202 +    div.style.top = "0px";
1203 +    div.style.width = contentWidth + "px";
1204 +    document.body.appendChild(div);
1205 +
1206 +    window.setTimeout(function() {
1207 +        var width = div.scrollWidth + 10;
1208 +        var height = div.scrollHeight + 10;
1209 +        var scrollDivW = 0; // width of the possible inner container when we want vertical scrolling
1210 +        if (maxHeight > 0 && height > maxHeight) {
1211 +          height = maxHeight;
1212 +          scrollDivW = width - 25;
1213 +        }
1214 +
1215 +        var bubble = SimileAjax.Graphics.createBubbleForPoint(pageX, pageY, width, height, orientation);
1216 +
1217 +        document.body.removeChild(div);
1218 +        div.style.position = "static";
1219 +        div.style.left = "";
1220 +        div.style.top = "";
1221 +
1222 +        // create a scroll div if needed
1223 +        if (scrollDivW > 0) {
1224 +          var scrollDiv = document.createElement("div");
1225 +          div.style.width = "";
1226 +          scrollDiv.style.width = scrollDivW + "px";
1227 +          scrollDiv.appendChild(div);
1228 +          bubble.content.appendChild(scrollDiv);
1229 +        } else {
1230 +          div.style.width = width + "px";
1231 +          bubble.content.appendChild(div);
1232 +        }
1233 +    }, 200);
1234 +};
1235 +
1236 +/**
1237 + * Creates a nice, rounded bubble popup with the given page coordinates and
1238 + * content dimensions.  The bubble will point to the location on the page
1239 + * as described by pageX and pageY.  All measurements should be given in
1240 + * pixels.
1241 + *
1242 + * @param {Number} pageX the x coordinate of the point to point to
1243 + * @param {Number} pageY the y coordinate of the point to point to
1244 + * @param {Number} contentWidth the width of the content box in the bubble
1245 + * @param {Number} contentHeight the height of the content box in the bubble
1246 + * @param {String} orientation a string ("top", "bottom", "left", or "right")
1247 + *   that describes the orientation of the arrow on the bubble
1248 + * @return {Element} a DOM element for the newly created bubble
1249 + */
1250 +SimileAjax.Graphics.createBubbleForPoint = function(pageX, pageY, contentWidth, contentHeight, orientation) {
1251 +    contentWidth = parseInt(contentWidth, 10); // harden against bad input bugs
1252 +    contentHeight = parseInt(contentHeight, 10); // getting numbers-as-strings
1253 +
1254 +    var bubbleConfig = SimileAjax.Graphics.bubbleConfig;
1255 +    var pngTransparencyClassSuffix =
1256 +        SimileAjax.Graphics.pngIsTranslucent ? "pngTranslucent" : "pngNotTranslucent";
1257 +
1258 +    var bubbleWidth = contentWidth + 2 * bubbleConfig.borderGraphicSize;
1259 +    var bubbleHeight = contentHeight + 2 * bubbleConfig.borderGraphicSize;
1260 +
1261 +    var generatePngSensitiveClass = function(className) {
1262 +        return className + " " + className + "-" + pngTransparencyClassSuffix;
1263 +    };
1264 +
1265 +    /*
1266 +     *  Render container divs
1267 +     */
1268 +    var div = document.createElement("div");
1269 +    div.className = generatePngSensitiveClass(bubbleConfig.containerCSSClass);
1270 +    div.style.width = contentWidth + "px";
1271 +    div.style.height = contentHeight + "px";
1272 +
1273 +    var divInnerContainer = document.createElement("div");
1274 +    divInnerContainer.className = generatePngSensitiveClass(bubbleConfig.innerContainerCSSClass);
1275 +    div.appendChild(divInnerContainer);
1276 +
1277 +    /*
1278 +     *  Create layer for bubble
1279 +     */
1280 +    var close = function() {
1281 +        if (!bubble._closed) {
1282 +            document.body.removeChild(bubble._div);
1283 +            bubble._doc = null;
1284 +            bubble._div = null;
1285 +            bubble._content = null;
1286 +            bubble._closed = true;
1287 +        }
1288 +    }
1289 +    var bubble = { _closed: false };
1290 +    var layer = SimileAjax.WindowManager.pushLayer(close, true, div);
1291 +    bubble._div = div;
1292 +    bubble.close = function() { SimileAjax.WindowManager.popLayer(layer); }
1293 +
1294 +    /*
1295 +     *  Render border graphics
1296 +     */
1297 +    var createBorder = function(classNameSuffix) {
1298 +        var divBorderGraphic = document.createElement("div");
1299 +        divBorderGraphic.className = generatePngSensitiveClass(bubbleConfig.borderGraphicCSSClassPrefix + classNameSuffix);
1300 +        divInnerContainer.appendChild(divBorderGraphic);
1301 +    };
1302 +    createBorder("top-left");
1303 +    createBorder("top-right");
1304 +    createBorder("bottom-left");
1305 +    createBorder("bottom-right");
1306 +    createBorder("left");
1307 +    createBorder("right");
1308 +    createBorder("top");
1309 +    createBorder("bottom");
1310 +
1311 +    /*
1312 +     *  Render content
1313 +     */
1314 +    var divContentContainer = document.createElement("div");
1315 +    divContentContainer.className = generatePngSensitiveClass(bubbleConfig.contentContainerCSSClass);
1316 +    divInnerContainer.appendChild(divContentContainer);
1317 +    bubble.content = divContentContainer;
1318 +
1319 +    /*
1320 +     *  Render close button
1321 +     */
1322 +    var divClose = document.createElement("div");
1323 +    divClose.className = generatePngSensitiveClass(bubbleConfig.closeGraphicCSSClass);
1324 +    divInnerContainer.appendChild(divClose);
1325 +    SimileAjax.WindowManager.registerEventWithObject(divClose, "click", bubble, "close");
1326 +
1327 +    (function() {
1328 +        var dims = SimileAjax.Graphics.getWindowDimensions();
1329 +        var docWidth = dims.w;
1330 +        var docHeight = dims.h;
1331 +
1332 +        var halfArrowGraphicWidth = Math.ceil(bubbleConfig.arrowGraphicWidth / 2);
1333 +
1334 +        var createArrow = function(classNameSuffix) {
1335 +            var divArrowGraphic = document.createElement("div");
1336 +            divArrowGraphic.className = generatePngSensitiveClass(bubbleConfig.arrowGraphicCSSClassPrefix + "point-" + classNameSuffix);
1337 +            divInnerContainer.appendChild(divArrowGraphic);
1338 +            return divArrowGraphic;
1339 +        };
1340 +
1341 +        if (pageX - halfArrowGraphicWidth - bubbleConfig.borderGraphicSize - bubbleConfig.extraPadding > 0 &&
1342 +            pageX + halfArrowGraphicWidth + bubbleConfig.borderGraphicSize + bubbleConfig.extraPadding < docWidth) {
1343 +
1344 +            /*
1345 +             *  Bubble can be positioned above or below the target point.
1346 +             */
1347 +
1348 +            var left = pageX - Math.round(contentWidth / 2);
1349 +            left = pageX < (docWidth / 2) ?
1350 +                Math.max(left, bubbleConfig.extraPadding + bubbleConfig.borderGraphicSize) :
1351 +                Math.min(left, docWidth - bubbleConfig.extraPadding - bubbleConfig.borderGraphicSize - contentWidth);
1352 +
1353 +            if ((orientation && orientation == "top") ||
1354 +                (!orientation &&
1355 +                    (pageY
1356 +                        - bubbleConfig.arrowGraphicTargetOffset
1357 +                        - contentHeight
1358 +                        - bubbleConfig.borderGraphicSize
1359 +                        - bubbleConfig.extraPadding > 0))) {
1360 +
1361 +                /*
1362 +                 *  Position bubble above the target point.
1363 +                 */
1364 +
1365 +                var divArrow = createArrow("down");
1366 +                divArrow.style.left = (pageX - halfArrowGraphicWidth - left) + "px";
1367 +
1368 +                div.style.left = left + "px";
1369 +                div.style.top = (pageY - bubbleConfig.arrowGraphicTargetOffset - contentHeight) + "px";
1370 +
1371 +                return;
1372 +            } else if ((orientation && orientation == "bottom") ||
1373 +                (!orientation &&
1374 +                    (pageY
1375 +                        + bubbleConfig.arrowGraphicTargetOffset
1376 +                        + contentHeight
1377 +                        + bubbleConfig.borderGraphicSize
1378 +                        + bubbleConfig.extraPadding < docHeight))) {
1379 +
1380 +                /*
1381 +                 *  Position bubble below the target point.
1382 +                 */
1383 +
1384 +                var divArrow = createArrow("up");
1385 +                divArrow.style.left = (pageX - halfArrowGraphicWidth - left) + "px";
1386 +
1387 +                div.style.left = left + "px";
1388 +                div.style.top = (pageY + bubbleConfig.arrowGraphicTargetOffset) + "px";
1389 +
1390 +                return;
1391 +            }
1392 +        }
1393 +
1394 +        var top = pageY - Math.round(contentHeight / 2);
1395 +        top = pageY < (docHeight / 2) ?
1396 +            Math.max(top, bubbleConfig.extraPadding + bubbleConfig.borderGraphicSize) :
1397 +            Math.min(top, docHeight - bubbleConfig.extraPadding - bubbleConfig.borderGraphicSize - contentHeight);
1398 +
1399 +        if ((orientation && orientation == "left") ||
1400 +            (!orientation &&
1401 +                (pageX
1402 +                    - bubbleConfig.arrowGraphicTargetOffset
1403 +                    - contentWidth
1404 +                    - bubbleConfig.borderGraphicSize
1405 +                    - bubbleConfig.extraPadding > 0))) {
1406 +
1407 +            /*
1408 +             *  Position bubble left of the target point.
1409 +             */
1410 +
1411 +            var divArrow = createArrow("right");
1412 +            divArrow.style.top = (pageY - halfArrowGraphicWidth - top) + "px";
1413 +
1414 +            div.style.top = top + "px";
1415 +            div.style.left = (pageX - bubbleConfig.arrowGraphicTargetOffset - contentWidth) + "px";
1416 +        } else {
1417 +
1418 +            /*
1419 +             *  Position bubble right of the target point, as the last resort.
1420 +             */
1421 +
1422 +            var divArrow = createArrow("left");
1423 +            divArrow.style.top = (pageY - halfArrowGraphicWidth - top) + "px";
1424 +
1425 +            div.style.top = top + "px";
1426 +            div.style.left = (pageX + bubbleConfig.arrowGraphicTargetOffset) + "px";
1427 +        }
1428 +    })();
1429 +
1430 +    document.body.appendChild(div);
1431 +
1432 +    return bubble;
1433 +};
1434 +
1435 +SimileAjax.Graphics.getWindowDimensions = function() {
1436 +    if (typeof window.innerHeight == 'number') {
1437 +        return { w:window.innerWidth, h:window.innerHeight }; // Non-IE
1438 +    } else if (document.documentElement && document.documentElement.clientHeight) {
1439 +        return { // IE6+, in "standards compliant mode"
1440 +            w:document.documentElement.clientWidth,
1441 +            h:document.documentElement.clientHeight
1442 +        };
1443 +    } else if (document.body && document.body.clientHeight) {
1444 +        return { // IE 4 compatible
1445 +            w:document.body.clientWidth,
1446 +            h:document.body.clientHeight
1447 +        };
1448 +    }
1449 +};
1450 +
1451 +
1452 +/**
1453 + * Creates a floating, rounded message bubble in the center of the window for
1454 + * displaying modal information, e.g. "Loading..."
1455 + *
1456 + * @param {Document} doc the root document for the page to render on
1457 + * @param {Object} an object with two properties, contentDiv and containerDiv,
1458 + *   consisting of the newly created DOM elements
1459 + */
1460 +SimileAjax.Graphics.createMessageBubble = function(doc) {
1461 +    var containerDiv = doc.createElement("div");
1462 +    if (SimileAjax.Graphics.pngIsTranslucent) {
1463 +        var topDiv = doc.createElement("div");
1464 +        topDiv.style.height = "33px";
1465 +        topDiv.style.background = "url(" + SimileAjax.urlPrefix + "images/message-top-left.png) top left no-repeat";
1466 +        topDiv.style.paddingLeft = "44px";
1467 +        containerDiv.appendChild(topDiv);
1468 +
1469 +        var topRightDiv = doc.createElement("div");
1470 +        topRightDiv.style.height = "33px";
1471 +        topRightDiv.style.background = "url(" + SimileAjax.urlPrefix + "images/message-top-right.png) top right no-repeat";
1472 +        topDiv.appendChild(topRightDiv);
1473 +
1474 +        var middleDiv = doc.createElement("div");
1475 +        middleDiv.style.background = "url(" + SimileAjax.urlPrefix + "images/message-left.png) top left repeat-y";
1476 +        middleDiv.style.paddingLeft = "44px";
1477 +        containerDiv.appendChild(middleDiv);
1478 +
1479 +        var middleRightDiv = doc.createElement("div");
1480 +        middleRightDiv.style.background = "url(" + SimileAjax.urlPrefix + "images/message-right.png) top right repeat-y";
1481 +        middleRightDiv.style.paddingRight = "44px";
1482 +        middleDiv.appendChild(middleRightDiv);
1483 +
1484 +        var contentDiv = doc.createElement("div");
1485 +        middleRightDiv.appendChild(contentDiv);
1486 +
1487 +        var bottomDiv = doc.createElement("div");
1488 +        bottomDiv.style.height = "55px";
1489 +        bottomDiv.style.background = "url(" + SimileAjax.urlPrefix + "images/message-bottom-left.png) bottom left no-repeat";
1490 +        bottomDiv.style.paddingLeft = "44px";
1491 +        containerDiv.appendChild(bottomDiv);
1492 +
1493 +        var bottomRightDiv = doc.createElement("div");
1494 +        bottomRightDiv.style.height = "55px";
1495 +        bottomRightDiv.style.background = "url(" + SimileAjax.urlPrefix + "images/message-bottom-right.png) bottom right no-repeat";
1496 +        bottomDiv.appendChild(bottomRightDiv);
1497 +    } else {
1498 +        containerDiv.style.border = "2px solid #7777AA";
1499 +        containerDiv.style.padding = "20px";
1500 +        containerDiv.style.background = "white";
1501 +        SimileAjax.Graphics.setOpacity(containerDiv, 90);
1502 +
1503 +        var contentDiv = doc.createElement("div");
1504 +        containerDiv.appendChild(contentDiv);
1505 +    }
1506 +
1507 +    return {
1508 +        containerDiv:   containerDiv,
1509 +        contentDiv:     contentDiv
1510 +    };
1511 +};
1512 +
1513 +/*
1514 + *  Animation
1515 + *
1516 + */
1517 +
1518 +/**
1519 + * Creates an animation for a function, and an interval of values.  The word
1520 + * "animation" here is used in the sense of repeatedly calling a function with
1521 + * a current value from within an interval, and a delta value.
1522 + *
1523 + * @param {Function} f a function to be called every 50 milliseconds throughout
1524 + *   the animation duration, of the form f(current, delta), where current is
1525 + *   the current value within the range and delta is the current change.
1526 + * @param {Number} from a starting value
1527 + * @param {Number} to an ending value
1528 + * @param {Number} duration the duration of the animation in milliseconds
1529 + * @param {Function} [cont] an optional function that is called at the end of
1530 + *   the animation, i.e. a continuation.
1531 + * @return {SimileAjax.Graphics._Animation} a new animation object
1532 + */
1533 +SimileAjax.Graphics.createAnimation = function(f, from, to, duration, cont) {
1534 +    return new SimileAjax.Graphics._Animation(f, from, to, duration, cont);
1535 +};
1536 +
1537 +SimileAjax.Graphics._Animation = function(f, from, to, duration, cont) {
1538 +    this.f = f;
1539 +    this.cont = (typeof cont == "function") ? cont : function() {};
1540 +
1541 +    this.from = from;
1542 +    this.to = to;
1543 +    this.current = from;
1544 +
1545 +    this.duration = duration;
1546 +    this.start = new Date().getTime();
1547 +    this.timePassed = 0;
1548 +};
1549 +
1550 +/**
1551 + * Runs this animation.
1552 + */
1553 +SimileAjax.Graphics._Animation.prototype.run = function() {
1554 +    var a = this;
1555 +    window.setTimeout(function() { a.step(); }, 50);
1556 +};
1557 +
1558 +/**
1559 + * Increments this animation by one step, and then continues the animation with
1560 + * <code>run()</code>.
1561 + */
1562 +SimileAjax.Graphics._Animation.prototype.step = function() {
1563 +    this.timePassed += 50;
1564 +
1565 +    var timePassedFraction = this.timePassed / this.duration;
1566 +    var parameterFraction = -Math.cos(timePassedFraction * Math.PI) / 2 + 0.5;
1567 +    var current = parameterFraction * (this.to - this.from) + this.from;
1568 +
1569 +    try {
1570 +        this.f(current, current - this.current);
1571 +    } catch (e) {
1572 +    }
1573 +    this.current = current;
1574 +
1575 +    if (this.timePassed < this.duration) {
1576 +        this.run();
1577 +    } else {
1578 +        this.f(this.to, 0);
1579 +        this["cont"]();
1580 +    }
1581 +};
1582 +
1583 +/*
1584 + *  CopyPasteButton
1585 + *
1586 + *  Adapted from http://spaces.live.com/editorial/rayozzie/demo/liveclip/liveclipsample/techPreview.html.
1587 + *
1588 + */
1589 +
1590 +/**
1591 + * Creates a button and textarea for displaying structured data and copying it
1592 + * to the clipboard.  The data is dynamically generated by the given
1593 + * createDataFunction parameter.
1594 + *
1595 + * @param {String} image an image URL to use as the background for the
1596 + *   generated box
1597 + * @param {Number} width the width in pixels of the generated box
1598 + * @param {Number} height the height in pixels of the generated box
1599 + * @param {Function} createDataFunction a function that is called with no
1600 + *   arguments to generate the structured data
1601 + * @return a new DOM element
1602 + */
1603 +SimileAjax.Graphics.createStructuredDataCopyButton = function(image, width, height, createDataFunction) {
1604 +    var div = document.createElement("div");
1605 +    div.style.position = "relative";
1606 +    div.style.display = "inline";
1607 +    div.style.width = width + "px";
1608 +    div.style.height = height + "px";
1609 +    div.style.overflow = "hidden";
1610 +    div.style.margin = "2px";
1611 +
1612 +    if (SimileAjax.Graphics.pngIsTranslucent) {
1613 +        div.style.background = "url(" + image + ") no-repeat";
1614 +    } else {
1615 +        div.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + image +"', sizingMethod='image')";
1616 +    }
1617 +
1618 +    var style;
1619 +    if (SimileAjax.Platform.browser.isIE) {
1620 +        style = "filter:alpha(opacity=0)";
1621 +    } else {
1622 +        style = "opacity: 0";
1623 +    }
1624 +    div.innerHTML = "<textarea rows='1' autocomplete='off' value='none' style='" + style + "' />";
1625 +
1626 +    var textarea = div.firstChild;
1627 +    textarea.style.width = width + "px";
1628 +    textarea.style.height = height + "px";
1629 +    textarea.onmousedown = function(evt) {
1630 +        evt = (evt) ? evt : ((event) ? event : null);
1631 +        if (evt.button == 2) {
1632 +            textarea.value = createDataFunction();
1633 +            textarea.select();
1634 +        }
1635 +    };
1636 +
1637 +    return div;
1638 +};
1639 +
1640 +/*
1641 + *  getWidthHeight
1642 + *
1643 + */
1644 +SimileAjax.Graphics.getWidthHeight = function(el) {
1645 +    // RETURNS hash {width:  w, height: h} in pixels
1646 +
1647 +    var w, h;
1648 +    // offsetWidth rounds on FF, so doesn't work for us.
1649 +    // See https://bugzilla.mozilla.org/show_bug.cgi?id=458617
1650 +    if (el.getBoundingClientRect == null) {
1651 +    	// use offsetWidth
1652 +      w = el.offsetWidth;
1653 +      h = el.offsetHeight;
1654 +    } else {
1655 +    	// use getBoundingClientRect
1656 +      var rect = el.getBoundingClientRect();

1657 +      w = Math.ceil(rect.right - rect.left);

1658 +    	h = Math.ceil(rect.bottom - rect.top);
1659 +    }
1660 +    return {
1661 +        width:  w,
1662 +        height: h
1663 +    };
1664 +};
1665 +
1666 +
1667 +/*
1668 + *  FontRenderingContext
1669 + *
1670 + */
1671 +SimileAjax.Graphics.getFontRenderingContext = function(elmt, width) {
1672 +    return new SimileAjax.Graphics._FontRenderingContext(elmt, width);
1673 +};
1674 +
1675 +SimileAjax.Graphics._FontRenderingContext = function(elmt, width) {
1676 +    this._elmt = elmt;
1677 +    this._elmt.style.visibility = "hidden";
1678 +    if (typeof width == "string") {
1679 +        this._elmt.style.width = width;
1680 +    } else if (typeof width == "number") {
1681 +        this._elmt.style.width = width + "px";
1682 +    }
1683 +};
1684 +
1685 +SimileAjax.Graphics._FontRenderingContext.prototype.dispose = function() {
1686 +    this._elmt = null;
1687 +};
1688 +
1689 +SimileAjax.Graphics._FontRenderingContext.prototype.update = function() {
1690 +    this._elmt.innerHTML = "A";
1691 +    this._lineHeight = this._elmt.offsetHeight;
1692 +};
1693 +
1694 +SimileAjax.Graphics._FontRenderingContext.prototype.computeSize = function(text, className) {
1695 +    // className arg is optional
1696 +    var el = this._elmt;
1697 +    el.innerHTML = text;
1698 +    el.className = className === undefined ? '' : className;
1699 +    var wh = SimileAjax.Graphics.getWidthHeight(el);

1700 +    el.className = ''; // reset for the next guy
1701 +
1702 +    return wh;
1703 +};
1704 +
1705 +SimileAjax.Graphics._FontRenderingContext.prototype.getLineHeight = function() {
1706 +    return this._lineHeight;
1707 +};
1708 +
1709 +/**
1710 + * @fileOverview A collection of date/time utility functions
1711 + * @name SimileAjax.DateTime
1712 + */
1713 +
1714 +SimileAjax.DateTime = new Object();
1715 +
1716 +SimileAjax.DateTime.MILLISECOND    = 0;
1717 +SimileAjax.DateTime.SECOND         = 1;
1718 +SimileAjax.DateTime.MINUTE         = 2;
1719 +SimileAjax.DateTime.HOUR           = 3;
1720 +SimileAjax.DateTime.DAY            = 4;
1721 +SimileAjax.DateTime.WEEK           = 5;
1722 +SimileAjax.DateTime.MONTH          = 6;
1723 +SimileAjax.DateTime.YEAR           = 7;
1724 +SimileAjax.DateTime.DECADE         = 8;
1725 +SimileAjax.DateTime.CENTURY        = 9;
1726 +SimileAjax.DateTime.MILLENNIUM     = 10;
1727 +
1728 +SimileAjax.DateTime.EPOCH          = -1;
1729 +SimileAjax.DateTime.ERA            = -2;
1730 +
1731 +/**
1732 + * An array of unit lengths, expressed in milliseconds, of various lengths of
1733 + * time.  The array indices are predefined and stored as properties of the
1734 + * SimileAjax.DateTime object, e.g. SimileAjax.DateTime.YEAR.
1735 + * @type Array
1736 + */
1737 +SimileAjax.DateTime.gregorianUnitLengths = [];
1738 +    (function() {
1739 +        var d = SimileAjax.DateTime;
1740 +        var a = d.gregorianUnitLengths;
1741 +
1742 +        a[d.MILLISECOND] = 1;
1743 +        a[d.SECOND]      = 1000;
1744 +        a[d.MINUTE]      = a[d.SECOND] * 60;
1745 +        a[d.HOUR]        = a[d.MINUTE] * 60;
1746 +        a[d.DAY]         = a[d.HOUR] * 24;
1747 +        a[d.WEEK]        = a[d.DAY] * 7;
1748 +        a[d.MONTH]       = a[d.DAY] * 31;
1749 +        a[d.YEAR]        = a[d.DAY] * 365;
1750 +        a[d.DECADE]      = a[d.YEAR] * 10;
1751 +        a[d.CENTURY]     = a[d.YEAR] * 100;
1752 +        a[d.MILLENNIUM]  = a[d.YEAR] * 1000;
1753 +    })();
1754 +
1755 +SimileAjax.DateTime._dateRegexp = new RegExp(
1756 +    "^(-?)([0-9]{4})(" + [
1757 +        "(-?([0-9]{2})(-?([0-9]{2}))?)", // -month-dayOfMonth
1758 +        "(-?([0-9]{3}))",                // -dayOfYear
1759 +        "(-?W([0-9]{2})(-?([1-7]))?)"    // -Wweek-dayOfWeek
1760 +    ].join("|") + ")?$"
1761 +);
1762 +SimileAjax.DateTime._timezoneRegexp = new RegExp(
1763 +    "Z|(([-+])([0-9]{2})(:?([0-9]{2}))?)$"
1764 +);
1765 +SimileAjax.DateTime._timeRegexp = new RegExp(
1766 +    "^([0-9]{2})(:?([0-9]{2})(:?([0-9]{2})(\.([0-9]+))?)?)?$"
1767 +);
1768 +
1769 +/**
1770 + * Takes a date object and a string containing an ISO 8601 date and sets the
1771 + * the date using information parsed from the string.  Note that this method
1772 + * does not parse any time information.
1773 + *
1774 + * @param {Date} dateObject the date object to modify
1775 + * @param {String} string an ISO 8601 string to parse
1776 + * @return {Date} the modified date object
1777 + */
1778 +SimileAjax.DateTime.setIso8601Date = function(dateObject, string) {
1779 +    /*
1780 +     *  This function has been adapted from dojo.date, v.0.3.0
1781 +     *  http://dojotoolkit.org/.
1782 +     */
1783 +
1784 +    var d = string.match(SimileAjax.DateTime._dateRegexp);
1785 +    if(!d) {
1786 +        throw new Error("Invalid date string: " + string);
1787 +    }
1788 +
1789 +    var sign = (d[1] == "-") ? -1 : 1; // BC or AD
1790 +    var year = sign * d[2];
1791 +    var month = d[5];
1792 +    var date = d[7];
1793 +    var dayofyear = d[9];
1794 +    var week = d[11];
1795 +    var dayofweek = (d[13]) ? d[13] : 1;
1796 +
1797 +    dateObject.setUTCFullYear(year);
1798 +    if (dayofyear) {
1799 +        dateObject.setUTCMonth(0);
1800 +        dateObject.setUTCDate(Number(dayofyear));
1801 +    } else if (week) {
1802 +        dateObject.setUTCMonth(0);
1803 +        dateObject.setUTCDate(1);
1804 +        var gd = dateObject.getUTCDay();
1805 +        var day =  (gd) ? gd : 7;
1806 +        var offset = Number(dayofweek) + (7 * Number(week));
1807 +
1808 +        if (day <= 4) {
1809 +            dateObject.setUTCDate(offset + 1 - day);
1810 +        } else {
1811 +            dateObject.setUTCDate(offset + 8 - day);
1812 +        }
1813 +    } else {
1814 +        if (month) {
1815 +            dateObject.setUTCDate(1);
1816 +            dateObject.setUTCMonth(month - 1);
1817 +        }
1818 +        if (date) {
1819 +            dateObject.setUTCDate(date);
1820 +        }
1821 +    }
1822 +
1823 +    return dateObject;
1824 +};
1825 +
1826 +/**
1827 + * Takes a date object and a string containing an ISO 8601 time and sets the
1828 + * the time using information parsed from the string.  Note that this method
1829 + * does not parse any date information.
1830 + *
1831 + * @param {Date} dateObject the date object to modify
1832 + * @param {String} string an ISO 8601 string to parse
1833 + * @return {Date} the modified date object
1834 + */
1835 +SimileAjax.DateTime.setIso8601Time = function (dateObject, string) {
1836 +    /*
1837 +     *  This function has been adapted from dojo.date, v.0.3.0
1838 +     *  http://dojotoolkit.org/.
1839 +     */
1840 +
1841 +    var d = string.match(SimileAjax.DateTime._timeRegexp);
1842 +    if(!d) {
1843 +        SimileAjax.Debug.warn("Invalid time string: " + string);
1844 +        return false;
1845 +    }
1846 +    var hours = d[1];
1847 +    var mins = Number((d[3]) ? d[3] : 0);
1848 +    var secs = (d[5]) ? d[5] : 0;
1849 +    var ms = d[7] ? (Number("0." + d[7]) * 1000) : 0;
1850 +
1851 +    dateObject.setUTCHours(hours);
1852 +    dateObject.setUTCMinutes(mins);
1853 +    dateObject.setUTCSeconds(secs);
1854 +    dateObject.setUTCMilliseconds(ms);
1855 +
1856 +    return dateObject;
1857 +};
1858 +
1859 +/**
1860 + * The timezone offset in minutes in the user's browser.
1861 + * @type Number
1862 + */
1863 +SimileAjax.DateTime.timezoneOffset = new Date().getTimezoneOffset();
1864 +
1865 +/**
1866 + * Takes a date object and a string containing an ISO 8601 date and time and
1867 + * sets the date object using information parsed from the string.
1868 + *
1869 + * @param {Date} dateObject the date object to modify
1870 + * @param {String} string an ISO 8601 string to parse
1871 + * @return {Date} the modified date object
1872 + */
1873 +SimileAjax.DateTime.setIso8601 = function (dateObject, string){
1874 +    /*
1875 +     *  This function has been adapted from dojo.date, v.0.3.0
1876 +     *  http://dojotoolkit.org/.
1877 +     */
1878 +
1879 +    var offset = null;
1880 +    var comps = (string.indexOf("T") == -1) ? string.split(" ") : string.split("T");
1881 +
1882 +    SimileAjax.DateTime.setIso8601Date(dateObject, comps[0]);
1883 +    if (comps.length == 2) {
1884 +        // first strip timezone info from the end
1885 +        var d = comps[1].match(SimileAjax.DateTime._timezoneRegexp);
1886 +        if (d) {
1887 +            if (d[0] == 'Z') {
1888 +                offset = 0;
1889 +            } else {
1890 +                offset = (Number(d[3]) * 60) + Number(d[5]);
1891 +                offset *= ((d[2] == '-') ? 1 : -1);
1892 +            }
1893 +            comps[1] = comps[1].substr(0, comps[1].length - d[0].length);
1894 +        }
1895 +
1896 +        SimileAjax.DateTime.setIso8601Time(dateObject, comps[1]);
1897 +    }
1898 +    if (offset == null) {
1899 +        offset = dateObject.getTimezoneOffset(); // local time zone if no tz info
1900 +    }
1901 +    dateObject.setTime(dateObject.getTime() + offset * 60000);
1902 +
1903 +    return dateObject;
1904 +};
1905 +
1906 +/**
1907 + * Takes a string containing an ISO 8601 date and returns a newly instantiated
1908 + * date object with the parsed date and time information from the string.
1909 + *
1910 + * @param {String} string an ISO 8601 string to parse
1911 + * @return {Date} a new date object created from the string
1912 + */
1913 +SimileAjax.DateTime.parseIso8601DateTime = function (string) {
1914 +    try {
1915 +        return SimileAjax.DateTime.setIso8601(new Date(0), string);
1916 +    } catch (e) {
1917 +        return null;
1918 +    }
1919 +};
1920 +
1921 +/**
1922 + * Takes a string containing a Gregorian date and time and returns a newly
1923 + * instantiated date object with the parsed date and time information from the
1924 + * string.  If the param is actually an instance of Date instead of a string,
1925 + * simply returns the given date instead.
1926 + *
1927 + * @param {Object} o an object, to either return or parse as a string
1928 + * @return {Date} the date object
1929 + */
1930 +SimileAjax.DateTime.parseGregorianDateTime = function(o) {
1931 +    if (o == null) {
1932 +        return null;
1933 +    } else if (o instanceof Date) {
1934 +        return o;
1935 +    }
1936 +
1937 +    var s = o.toString();
1938 +    if (s.length > 0 && s.length < 8) {
1939 +        var space = s.indexOf(" ");
1940 +        if (space > 0) {
1941 +            var year = parseInt(s.substr(0, space));
1942 +            var suffix = s.substr(space + 1);
1943 +            if (suffix.toLowerCase() == "bc") {
1944 +                year = 1 - year;
1945 +            }
1946 +        } else {
1947 +            var year = parseInt(s);
1948 +        }
1949 +
1950 +        var d = new Date(0);
1951 +        d.setUTCFullYear(year);
1952 +
1953 +        return d;
1954 +    }
1955 +
1956 +    try {
1957 +        return new Date(Date.parse(s));
1958 +    } catch (e) {
1959 +        return null;
1960 +    }
1961 +};
1962 +
1963 +/**
1964 + * Rounds date objects down to the nearest interval or multiple of an interval.
1965 + * This method modifies the given date object, converting it to the given
1966 + * timezone if specified.
1967 + *
1968 + * @param {Date} date the date object to round
1969 + * @param {Number} intervalUnit a constant, integer index specifying an
1970 + *   interval, e.g. SimileAjax.DateTime.HOUR
1971 + * @param {Number} timeZone a timezone shift, given in hours
1972 + * @param {Number} multiple a multiple of the interval to round by
1973 + * @param {Number} firstDayOfWeek an integer specifying the first day of the
1974 + *   week, 0 corresponds to Sunday, 1 to Monday, etc.
1975 + */
1976 +SimileAjax.DateTime.roundDownToInterval = function(date, intervalUnit, timeZone, multiple, firstDayOfWeek) {
1977 +    var timeShift = timeZone *
1978 +        SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.HOUR];
1979 +
1980 +    var date2 = new Date(date.getTime() + timeShift);
1981 +    var clearInDay = function(d) {
1982 +        d.setUTCMilliseconds(0);
1983 +        d.setUTCSeconds(0);
1984 +        d.setUTCMinutes(0);
1985 +        d.setUTCHours(0);
1986 +    };
1987 +    var clearInYear = function(d) {
1988 +        clearInDay(d);
1989 +        d.setUTCDate(1);
1990 +        d.setUTCMonth(0);
1991 +    };
1992 +
1993 +    switch(intervalUnit) {
1994 +    case SimileAjax.DateTime.MILLISECOND:
1995 +        var x = date2.getUTCMilliseconds();
1996 +        date2.setUTCMilliseconds(x - (x % multiple));
1997 +        break;
1998 +    case SimileAjax.DateTime.SECOND:
1999 +        date2.setUTCMilliseconds(0);
2000 +
2001 +        var x = date2.getUTCSeconds();
2002 +        date2.setUTCSeconds(x - (x % multiple));
2003 +        break;
2004 +    case SimileAjax.DateTime.MINUTE:
2005 +        date2.setUTCMilliseconds(0);
2006 +        date2.setUTCSeconds(0);
2007 +
2008 +        var x = date2.getUTCMinutes();
2009 +        date2.setTime(date2.getTime() -
2010 +            (x % multiple) * SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.MINUTE]);
2011 +        break;
2012 +    case SimileAjax.DateTime.HOUR:
2013 +        date2.setUTCMilliseconds(0);
2014 +        date2.setUTCSeconds(0);
2015 +        date2.setUTCMinutes(0);
2016 +
2017 +        var x = date2.getUTCHours();
2018 +        date2.setUTCHours(x - (x % multiple));
2019 +        break;
2020 +    case SimileAjax.DateTime.DAY:
2021 +        clearInDay(date2);
2022 +        break;
2023 +    case SimileAjax.DateTime.WEEK:
2024 +        clearInDay(date2);
2025 +        var d = (date2.getUTCDay() + 7 - firstDayOfWeek) % 7;
2026 +        date2.setTime(date2.getTime() -
2027 +            d * SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.DAY]);
2028 +        break;
2029 +    case SimileAjax.DateTime.MONTH:
2030 +        clearInDay(date2);
2031 +        date2.setUTCDate(1);
2032 +
2033 +        var x = date2.getUTCMonth();
2034 +        date2.setUTCMonth(x - (x % multiple));
2035 +        break;
2036 +    case SimileAjax.DateTime.YEAR:
2037 +        clearInYear(date2);
2038 +
2039 +        var x = date2.getUTCFullYear();
2040 +        date2.setUTCFullYear(x - (x % multiple));
2041 +        break;
2042 +    case SimileAjax.DateTime.DECADE:
2043 +        clearInYear(date2);
2044 +        date2.setUTCFullYear(Math.floor(date2.getUTCFullYear() / 10) * 10);
2045 +        break;
2046 +    case SimileAjax.DateTime.CENTURY:
2047 +        clearInYear(date2);
2048 +        date2.setUTCFullYear(Math.floor(date2.getUTCFullYear() / 100) * 100);
2049 +        break;
2050 +    case SimileAjax.DateTime.MILLENNIUM:
2051 +        clearInYear(date2);
2052 +        date2.setUTCFullYear(Math.floor(date2.getUTCFullYear() / 1000) * 1000);
2053 +        break;
2054 +    }
2055 +
2056 +    date.setTime(date2.getTime() - timeShift);
2057 +};
2058 +
2059 +/**
2060 + * Rounds date objects up to the nearest interval or multiple of an interval.
2061 + * This method modifies the given date object, converting it to the given
2062 + * timezone if specified.
2063 + *
2064 + * @param {Date} date the date object to round
2065 + * @param {Number} intervalUnit a constant, integer index specifying an
2066 + *   interval, e.g. SimileAjax.DateTime.HOUR
2067 + * @param {Number} timeZone a timezone shift, given in hours
2068 + * @param {Number} multiple a multiple of the interval to round by
2069 + * @param {Number} firstDayOfWeek an integer specifying the first day of the
2070 + *   week, 0 corresponds to Sunday, 1 to Monday, etc.
2071 + * @see SimileAjax.DateTime.roundDownToInterval
2072 + */
2073 +SimileAjax.DateTime.roundUpToInterval = function(date, intervalUnit, timeZone, multiple, firstDayOfWeek) {
2074 +    var originalTime = date.getTime();
2075 +    SimileAjax.DateTime.roundDownToInterval(date, intervalUnit, timeZone, multiple, firstDayOfWeek);
2076 +    if (date.getTime() < originalTime) {
2077 +        date.setTime(date.getTime() +
2078 +            SimileAjax.DateTime.gregorianUnitLengths[intervalUnit] * multiple);
2079 +    }
2080 +};
2081 +
2082 +/**
2083 + * Increments a date object by a specified interval, taking into
2084 + * consideration the timezone.
2085 + *
2086 + * @param {Date} date the date object to increment
2087 + * @param {Number} intervalUnit a constant, integer index specifying an
2088 + *   interval, e.g. SimileAjax.DateTime.HOUR
2089 + * @param {Number} timeZone the timezone offset in hours
2090 + */
2091 +SimileAjax.DateTime.incrementByInterval = function(date, intervalUnit, timeZone) {
2092 +    timeZone = (typeof timeZone == 'undefined') ? 0 : timeZone;
2093 +
2094 +    var timeShift = timeZone *
2095 +        SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.HOUR];
2096 +
2097 +    var date2 = new Date(date.getTime() + timeShift);
2098 +
2099 +    switch(intervalUnit) {
2100 +    case SimileAjax.DateTime.MILLISECOND:
2101 +        date2.setTime(date2.getTime() + 1)
2102 +        break;
2103 +    case SimileAjax.DateTime.SECOND:
2104 +        date2.setTime(date2.getTime() + 1000);
2105 +        break;
2106 +    case SimileAjax.DateTime.MINUTE:
2107 +        date2.setTime(date2.getTime() +
2108 +            SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.MINUTE]);
2109 +        break;
2110 +    case SimileAjax.DateTime.HOUR:
2111 +        date2.setTime(date2.getTime() +
2112 +            SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.HOUR]);
2113 +        break;
2114 +    case SimileAjax.DateTime.DAY:
2115 +        date2.setUTCDate(date2.getUTCDate() + 1);
2116 +        break;
2117 +    case SimileAjax.DateTime.WEEK:
2118 +        date2.setUTCDate(date2.getUTCDate() + 7);
2119 +        break;
2120 +    case SimileAjax.DateTime.MONTH:
2121 +        date2.setUTCMonth(date2.getUTCMonth() + 1);
2122 +        break;
2123 +    case SimileAjax.DateTime.YEAR:
2124 +        date2.setUTCFullYear(date2.getUTCFullYear() + 1);
2125 +        break;
2126 +    case SimileAjax.DateTime.DECADE:
2127 +        date2.setUTCFullYear(date2.getUTCFullYear() + 10);
2128 +        break;
2129 +    case SimileAjax.DateTime.CENTURY:
2130 +        date2.setUTCFullYear(date2.getUTCFullYear() + 100);
2131 +        break;
2132 +    case SimileAjax.DateTime.MILLENNIUM:
2133 +        date2.setUTCFullYear(date2.getUTCFullYear() + 1000);
2134 +        break;
2135 +    }
2136 +
2137 +    date.setTime(date2.getTime() - timeShift);
2138 +};
2139 +
2140 +/**
2141 + * Returns a new date object with the given time offset removed.
2142 + *
2143 + * @param {Date} date the starting date
2144 + * @param {Number} timeZone a timezone specified in an hour offset to remove
2145 + * @return {Date} a new date object with the offset removed
2146 + */
2147 +SimileAjax.DateTime.removeTimeZoneOffset = function(date, timeZone) {
2148 +    return new Date(date.getTime() +
2149 +        timeZone * SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.HOUR]);
2150 +};
2151 +
2152 +/**
2153 + * Returns the timezone of the user's browser.
2154 + *
2155 + * @return {Number} the timezone in the user's locale in hours
2156 + */
2157 +SimileAjax.DateTime.getTimezone = function() {
2158 +    var d = new Date().getTimezoneOffset();
2159 +    return d / -60;
2160 +};
2161 +/*
2162 + *  String Utility Functions and Constants
2163 + *
2164 + */
2165 +
2166 +String.prototype.trim = function() {
2167 +    return this.replace(/^\s+|\s+$/g, '');
2168 +};
2169 +
2170 +String.prototype.startsWith = function(prefix) {
2171 +    return this.length >= prefix.length && this.substr(0, prefix.length) == prefix;
2172 +};
2173 +
2174 +String.prototype.endsWith = function(suffix) {
2175 +    return this.length >= suffix.length && this.substr(this.length - suffix.length) == suffix;
2176 +};
2177 +
2178 +String.substitute = function(s, objects) {
2179 +    var result = "";
2180 +    var start = 0;
2181 +    while (start < s.length - 1) {
2182 +        var percent = s.indexOf("%", start);
2183 +        if (percent < 0 || percent == s.length - 1) {
2184 +            break;
2185 +        } else if (percent > start && s.charAt(percent - 1) == "\\") {
2186 +            result += s.substring(start, percent - 1) + "%";
2187 +            start = percent + 1;
2188 +        } else {
2189 +            var n = parseInt(s.charAt(percent + 1));
2190 +            if (isNaN(n) || n >= objects.length) {
2191 +                result += s.substring(start, percent + 2);
2192 +            } else {
2193 +                result += s.substring(start, percent) + objects[n].toString();
2194 +            }
2195 +            start = percent + 2;
2196 +        }
2197 +    }
2198 +
2199 +    if (start < s.length) {
2200 +        result += s.substring(start);
2201 +    }
2202 +    return result;
2203 +};
2204 +/*
2205 + *  HTML Utility Functions
2206 + *
2207 + */
2208 +
2209 +SimileAjax.HTML = new Object();
2210 +
2211 +SimileAjax.HTML._e2uHash = {};
2212 +(function() {
2213 +    var e2uHash = SimileAjax.HTML._e2uHash;
2214 +    e2uHash['nbsp']= '\u00A0[space]';
2215 +    e2uHash['iexcl']= '\u00A1';
2216 +    e2uHash['cent']= '\u00A2';
2217 +    e2uHash['pound']= '\u00A3';
2218 +    e2uHash['curren']= '\u00A4';
2219 +    e2uHash['yen']= '\u00A5';
2220 +    e2uHash['brvbar']= '\u00A6';
2221 +    e2uHash['sect']= '\u00A7';
2222 +    e2uHash['uml']= '\u00A8';
2223 +    e2uHash['copy']= '\u00A9';
2224 +    e2uHash['ordf']= '\u00AA';
2225 +    e2uHash['laquo']= '\u00AB';
2226 +    e2uHash['not']= '\u00AC';
2227 +    e2uHash['shy']= '\u00AD';
2228 +    e2uHash['reg']= '\u00AE';
2229 +    e2uHash['macr']= '\u00AF';
2230 +    e2uHash['deg']= '\u00B0';
2231 +    e2uHash['plusmn']= '\u00B1';
2232 +    e2uHash['sup2']= '\u00B2';
2233 +    e2uHash['sup3']= '\u00B3';
2234 +    e2uHash['acute']= '\u00B4';
2235 +    e2uHash['micro']= '\u00B5';
2236 +    e2uHash['para']= '\u00B6';
2237 +    e2uHash['middot']= '\u00B7';
2238 +    e2uHash['cedil']= '\u00B8';
2239 +    e2uHash['sup1']= '\u00B9';
2240 +    e2uHash['ordm']= '\u00BA';
2241 +    e2uHash['raquo']= '\u00BB';
2242 +    e2uHash['frac14']= '\u00BC';
2243 +    e2uHash['frac12']= '\u00BD';
2244 +    e2uHash['frac34']= '\u00BE';
2245 +    e2uHash['iquest']= '\u00BF';
2246 +    e2uHash['Agrave']= '\u00C0';
2247 +    e2uHash['Aacute']= '\u00C1';
2248 +    e2uHash['Acirc']= '\u00C2';
2249 +    e2uHash['Atilde']= '\u00C3';
2250 +    e2uHash['Auml']= '\u00C4';
2251 +    e2uHash['Aring']= '\u00C5';
2252 +    e2uHash['AElig']= '\u00C6';
2253 +    e2uHash['Ccedil']= '\u00C7';
2254 +    e2uHash['Egrave']= '\u00C8';
2255 +    e2uHash['Eacute']= '\u00C9';
2256 +    e2uHash['Ecirc']= '\u00CA';
2257 +    e2uHash['Euml']= '\u00CB';
2258 +    e2uHash['Igrave']= '\u00CC';
2259 +    e2uHash['Iacute']= '\u00CD';
2260 +    e2uHash['Icirc']= '\u00CE';
2261 +    e2uHash['Iuml']= '\u00CF';
2262 +    e2uHash['ETH']= '\u00D0';
2263 +    e2uHash['Ntilde']= '\u00D1';
2264 +    e2uHash['Ograve']= '\u00D2';
2265 +    e2uHash['Oacute']= '\u00D3';
2266 +    e2uHash['Ocirc']= '\u00D4';
2267 +    e2uHash['Otilde']= '\u00D5';
2268 +    e2uHash['Ouml']= '\u00D6';
2269 +    e2uHash['times']= '\u00D7';
2270 +    e2uHash['Oslash']= '\u00D8';
2271 +    e2uHash['Ugrave']= '\u00D9';
2272 +    e2uHash['Uacute']= '\u00DA';
2273 +    e2uHash['Ucirc']= '\u00DB';
2274 +    e2uHash['Uuml']= '\u00DC';
2275 +    e2uHash['Yacute']= '\u00DD';
2276 +    e2uHash['THORN']= '\u00DE';
2277 +    e2uHash['szlig']= '\u00DF';
2278 +    e2uHash['agrave']= '\u00E0';
2279 +    e2uHash['aacute']= '\u00E1';
2280 +    e2uHash['acirc']= '\u00E2';
2281 +    e2uHash['atilde']= '\u00E3';
2282 +    e2uHash['auml']= '\u00E4';
2283 +    e2uHash['aring']= '\u00E5';
2284 +    e2uHash['aelig']= '\u00E6';
2285 +    e2uHash['ccedil']= '\u00E7';
2286 +    e2uHash['egrave']= '\u00E8';
2287 +    e2uHash['eacute']= '\u00E9';
2288 +    e2uHash['ecirc']= '\u00EA';
2289 +    e2uHash['euml']= '\u00EB';
2290 +    e2uHash['igrave']= '\u00EC';
2291 +    e2uHash['iacute']= '\u00ED';
2292 +    e2uHash['icirc']= '\u00EE';
2293 +    e2uHash['iuml']= '\u00EF';
2294 +    e2uHash['eth']= '\u00F0';
2295 +    e2uHash['ntilde']= '\u00F1';
2296 +    e2uHash['ograve']= '\u00F2';
2297 +    e2uHash['oacute']= '\u00F3';
2298 +    e2uHash['ocirc']= '\u00F4';
2299 +    e2uHash['otilde']= '\u00F5';
2300 +    e2uHash['ouml']= '\u00F6';
2301 +    e2uHash['divide']= '\u00F7';
2302 +    e2uHash['oslash']= '\u00F8';
2303 +    e2uHash['ugrave']= '\u00F9';
2304 +    e2uHash['uacute']= '\u00FA';
2305 +    e2uHash['ucirc']= '\u00FB';
2306 +    e2uHash['uuml']= '\u00FC';
2307 +    e2uHash['yacute']= '\u00FD';
2308 +    e2uHash['thorn']= '\u00FE';
2309 +    e2uHash['yuml']= '\u00FF';
2310 +    e2uHash['quot']= '\u0022';
2311 +    e2uHash['amp']= '\u0026';
2312 +    e2uHash['lt']= '\u003C';
2313 +    e2uHash['gt']= '\u003E';
2314 +    e2uHash['OElig']= '';
2315 +    e2uHash['oelig']= '\u0153';
2316 +    e2uHash['Scaron']= '\u0160';
2317 +    e2uHash['scaron']= '\u0161';
2318 +    e2uHash['Yuml']= '\u0178';
2319 +    e2uHash['circ']= '\u02C6';
2320 +    e2uHash['tilde']= '\u02DC';
2321 +    e2uHash['ensp']= '\u2002';
2322 +    e2uHash['emsp']= '\u2003';
2323 +    e2uHash['thinsp']= '\u2010';
2324 +    e2uHash['zwnj']= '\u200C';
2325 +    e2uHash['zwj']= '\u200D';
2326 +    e2uHash['lrm']= '\u200E';
2327 +    e2uHash['rlm']= '\u200F';
2328 +    e2uHash['ndash']= '\u2013';
2329 +    e2uHash['mdash']= '\u2014';
2330 +    e2uHash['lsquo']= '\u2018';
2331 +    e2uHash['rsquo']= '\u2019';
2332 +    e2uHash['sbquo']= '\u201A';
2333 +    e2uHash['ldquo']= '\u201C';
2334 +    e2uHash['rdquo']= '\u201D';
2335 +    e2uHash['bdquo']= '\u201E';
2336 +    e2uHash['dagger']= '\u2020';
2337 +    e2uHash['Dagger']= '\u2021';
2338 +    e2uHash['permil']= '\u2030';
2339 +    e2uHash['lsaquo']= '\u2039';
2340 +    e2uHash['rsaquo']= '\u203A';
2341 +    e2uHash['euro']= '\u20AC';
2342 +    e2uHash['fnof']= '\u0192';
2343 +    e2uHash['Alpha']= '\u0391';
2344 +    e2uHash['Beta']= '\u0392';
2345 +    e2uHash['Gamma']= '\u0393';
2346 +    e2uHash['Delta']= '\u0394';
2347 +    e2uHash['Epsilon']= '\u0395';
2348 +    e2uHash['Zeta']= '\u0396';
2349 +    e2uHash['Eta']= '\u0397';
2350 +    e2uHash['Theta']= '\u0398';
2351 +    e2uHash['Iota']= '\u0399';
2352 +    e2uHash['Kappa']= '\u039A';
2353 +    e2uHash['Lambda']= '\u039B';
2354 +    e2uHash['Mu']= '\u039C';
2355 +    e2uHash['Nu']= '\u039D';
2356 +    e2uHash['Xi']= '\u039E';
2357 +    e2uHash['Omicron']= '\u039F';
2358 +    e2uHash['Pi']= '\u03A0';
2359 +    e2uHash['Rho']= '\u03A1';
2360 +    e2uHash['Sigma']= '\u03A3';
2361 +    e2uHash['Tau']= '\u03A4';
2362 +    e2uHash['Upsilon']= '\u03A5';
2363 +    e2uHash['Phi']= '\u03A6';
2364 +    e2uHash['Chi']= '\u03A7';
2365 +    e2uHash['Psi']= '\u03A8';
2366 +    e2uHash['Omega']= '\u03A9';
2367 +    e2uHash['alpha']= '\u03B1';
2368 +    e2uHash['beta']= '\u03B2';
2369 +    e2uHash['gamma']= '\u03B3';
2370 +    e2uHash['delta']= '\u03B4';
2371 +    e2uHash['epsilon']= '\u03B5';
2372 +    e2uHash['zeta']= '\u03B6';
2373 +    e2uHash['eta']= '\u03B7';
2374 +    e2uHash['theta']= '\u03B8';
2375 +    e2uHash['iota']= '\u03B9';
2376 +    e2uHash['kappa']= '\u03BA';
2377 +    e2uHash['lambda']= '\u03BB';
2378 +    e2uHash['mu']= '\u03BC';
2379 +    e2uHash['nu']= '\u03BD';
2380 +    e2uHash['xi']= '\u03BE';
2381 +    e2uHash['omicron']= '\u03BF';
2382 +    e2uHash['pi']= '\u03C0';
2383 +    e2uHash['rho']= '\u03C1';
2384 +    e2uHash['sigmaf']= '\u03C2';
2385 +    e2uHash['sigma']= '\u03C3';
2386 +    e2uHash['tau']= '\u03C4';
2387 +    e2uHash['upsilon']= '\u03C5';
2388 +    e2uHash['phi']= '\u03C6';
2389 +    e2uHash['chi']= '\u03C7';
2390 +    e2uHash['psi']= '\u03C8';
2391 +    e2uHash['omega']= '\u03C9';
2392 +    e2uHash['thetasym']= '\u03D1';
2393 +    e2uHash['upsih']= '\u03D2';
2394 +    e2uHash['piv']= '\u03D6';
2395 +    e2uHash['bull']= '\u2022';
2396 +    e2uHash['hellip']= '\u2026';
2397 +    e2uHash['prime']= '\u2032';
2398 +    e2uHash['Prime']= '\u2033';
2399 +    e2uHash['oline']= '\u203E';
2400 +    e2uHash['frasl']= '\u2044';
2401 +    e2uHash['weierp']= '\u2118';
2402 +    e2uHash['image']= '\u2111';
2403 +    e2uHash['real']= '\u211C';
2404 +    e2uHash['trade']= '\u2122';
2405 +    e2uHash['alefsym']= '\u2135';
2406 +    e2uHash['larr']= '\u2190';
2407 +    e2uHash['uarr']= '\u2191';
2408 +    e2uHash['rarr']= '\u2192';
2409 +    e2uHash['darr']= '\u2193';
2410 +    e2uHash['harr']= '\u2194';
2411 +    e2uHash['crarr']= '\u21B5';
2412 +    e2uHash['lArr']= '\u21D0';
2413 +    e2uHash['uArr']= '\u21D1';
2414 +    e2uHash['rArr']= '\u21D2';
2415 +    e2uHash['dArr']= '\u21D3';
2416 +    e2uHash['hArr']= '\u21D4';
2417 +    e2uHash['forall']= '\u2200';
2418 +    e2uHash['part']= '\u2202';
2419 +    e2uHash['exist']= '\u2203';
2420 +    e2uHash['empty']= '\u2205';
2421 +    e2uHash['nabla']= '\u2207';
2422 +    e2uHash['isin']= '\u2208';
2423 +    e2uHash['notin']= '\u2209';
2424 +    e2uHash['ni']= '\u220B';
2425 +    e2uHash['prod']= '\u220F';
2426 +    e2uHash['sum']= '\u2211';
2427 +    e2uHash['minus']= '\u2212';
2428 +    e2uHash['lowast']= '\u2217';
2429 +    e2uHash['radic']= '\u221A';
2430 +    e2uHash['prop']= '\u221D';
2431 +    e2uHash['infin']= '\u221E';
2432 +    e2uHash['ang']= '\u2220';
2433 +    e2uHash['and']= '\u2227';
2434 +    e2uHash['or']= '\u2228';
2435 +    e2uHash['cap']= '\u2229';
2436 +    e2uHash['cup']= '\u222A';
2437 +    e2uHash['int']= '\u222B';
2438 +    e2uHash['there4']= '\u2234';
2439 +    e2uHash['sim']= '\u223C';
2440 +    e2uHash['cong']= '\u2245';
2441 +    e2uHash['asymp']= '\u2248';
2442 +    e2uHash['ne']= '\u2260';
2443 +    e2uHash['equiv']= '\u2261';
2444 +    e2uHash['le']= '\u2264';
2445 +    e2uHash['ge']= '\u2265';
2446 +    e2uHash['sub']= '\u2282';
2447 +    e2uHash['sup']= '\u2283';
2448 +    e2uHash['nsub']= '\u2284';
2449 +    e2uHash['sube']= '\u2286';
2450 +    e2uHash['supe']= '\u2287';
2451 +    e2uHash['oplus']= '\u2295';
2452 +    e2uHash['otimes']= '\u2297';
2453 +    e2uHash['perp']= '\u22A5';
2454 +    e2uHash['sdot']= '\u22C5';
2455 +    e2uHash['lceil']= '\u2308';
2456 +    e2uHash['rceil']= '\u2309';
2457 +    e2uHash['lfloor']= '\u230A';
2458 +    e2uHash['rfloor']= '\u230B';
2459 +    e2uHash['lang']= '\u2329';
2460 +    e2uHash['rang']= '\u232A';
2461 +    e2uHash['loz']= '\u25CA';
2462 +    e2uHash['spades']= '\u2660';
2463 +    e2uHash['clubs']= '\u2663';
2464 +    e2uHash['hearts']= '\u2665';
2465 +    e2uHash['diams']= '\u2666';
2466 +})();
2467 +
2468 +SimileAjax.HTML.deEntify = function(s) {
2469 +    var e2uHash = SimileAjax.HTML._e2uHash;
2470 +
2471 +    var re = /&(\w+?);/;
2472 +    while (re.test(s)) {
2473 +        var m = s.match(re);
2474 +        s = s.replace(re, e2uHash[m[1]]);
2475 +    }
2476 +    return s;
2477 +};/**
2478 + * A basic set (in the mathematical sense) data structure
2479 + *
2480 + * @constructor
2481 + * @param {Array or SimileAjax.Set} [a] an initial collection
2482 + */
2483 +SimileAjax.Set = function(a) {
2484 +    this._hash = {};
2485 +    this._count = 0;
2486 +
2487 +    if (a instanceof Array) {
2488 +        for (var i = 0; i < a.length; i++) {
2489 +            this.add(a[i]);
2490 +        }
2491 +    } else if (a instanceof SimileAjax.Set) {
2492 +        this.addSet(a);
2493 +    }
2494 +}
2495 +
2496 +/**
2497 + * Adds the given object to this set, assuming there it does not already exist
2498 + *
2499 + * @param {Object} o the object to add
2500 + * @return {Boolean} true if the object was added, false if not
2501 + */
2502 +SimileAjax.Set.prototype.add = function(o) {
2503 +    if (!(o in this._hash)) {
2504 +        this._hash[o] = true;
2505 +        this._count++;
2506 +        return true;
2507 +    }
2508 +    return false;
2509 +}
2510 +
2511 +/**
2512 + * Adds each element in the given set to this set
2513 + *
2514 + * @param {SimileAjax.Set} set the set of elements to add
2515 + */
2516 +SimileAjax.Set.prototype.addSet = function(set) {
2517 +    for (var o in set._hash) {
2518 +        this.add(o);
2519 +    }
2520 +}
2521 +
2522 +/**
2523 + * Removes the given element from this set
2524 + *
2525 + * @param {Object} o the object to remove
2526 + * @return {Boolean} true if the object was successfully removed,
2527 + *   false otherwise
2528 + */
2529 +SimileAjax.Set.prototype.remove = function(o) {
2530 +    if (o in this._hash) {
2531 +        delete this._hash[o];
2532 +        this._count--;
2533 +        return true;
2534 +    }
2535 +    return false;
2536 +}
2537 +
2538 +/**
2539 + * Removes the elements in this set that correspond to the elements in the
2540 + * given set
2541 + *
2542 + * @param {SimileAjax.Set} set the set of elements to remove
2543 + */
2544 +SimileAjax.Set.prototype.removeSet = function(set) {
2545 +    for (var o in set._hash) {
2546 +        this.remove(o);
2547 +    }
2548 +}
2549 +
2550 +/**
2551 + * Removes all elements in this set that are not present in the given set, i.e.
2552 + * modifies this set to the intersection of the two sets
2553 + *
2554 + * @param {SimileAjax.Set} set the set to intersect
2555 + */
2556 +SimileAjax.Set.prototype.retainSet = function(set) {
2557 +    for (var o in this._hash) {
2558 +        if (!set.contains(o)) {
2559 +            delete this._hash[o];
2560 +            this._count--;
2561 +        }
2562 +    }
2563 +}
2564 +
2565 +/**
2566 + * Returns whether or not the given element exists in this set
2567 + *
2568 + * @param {SimileAjax.Set} o the object to test for
2569 + * @return {Boolean} true if the object is present, false otherwise
2570 + */
2571 +SimileAjax.Set.prototype.contains = function(o) {
2572 +    return (o in this._hash);
2573 +}
2574 +
2575 +/**
2576 + * Returns the number of elements in this set
2577 + *
2578 + * @return {Number} the number of elements in this set
2579 + */
2580 +SimileAjax.Set.prototype.size = function() {
2581 +    return this._count;
2582 +}
2583 +
2584 +/**
2585 + * Returns the elements of this set as an array
2586 + *
2587 + * @return {Array} a new array containing the elements of this set
2588 + */
2589 +SimileAjax.Set.prototype.toArray = function() {
2590 +    var a = [];
2591 +    for (var o in this._hash) {
2592 +        a.push(o);
2593 +    }
2594 +    return a;
2595 +}
2596 +
2597 +/**
2598 + * Iterates through the elements of this set, order unspecified, executing the
2599 + * given function on each element until the function returns true
2600 + *
2601 + * @param {Function} f a function of form f(element)
2602 + */
2603 +SimileAjax.Set.prototype.visit = function(f) {
2604 +    for (var o in this._hash) {
2605 +        if (f(o) == true) {
2606 +            break;
2607 +        }
2608 +    }
2609 +}
2610 +
2611 +/**
2612 + * A sorted array data structure
2613 + *
2614 + * @constructor
2615 + */
2616 +SimileAjax.SortedArray = function(compare, initialArray) {
2617 +    this._a = (initialArray instanceof Array) ? initialArray : [];
2618 +    this._compare = compare;
2619 +};
2620 +
2621 +SimileAjax.SortedArray.prototype.add = function(elmt) {
2622 +    var sa = this;
2623 +    var index = this.find(function(elmt2) {
2624 +        return sa._compare(elmt2, elmt);
2625 +    });
2626 +
2627 +    if (index < this._a.length) {
2628 +        this._a.splice(index, 0, elmt);
2629 +    } else {
2630 +        this._a.push(elmt);
2631 +    }
2632 +};
2633 +
2634 +SimileAjax.SortedArray.prototype.remove = function(elmt) {
2635 +    var sa = this;
2636 +    var index = this.find(function(elmt2) {
2637 +        return sa._compare(elmt2, elmt);
2638 +    });
2639 +
2640 +    while (index < this._a.length && this._compare(this._a[index], elmt) == 0) {
2641 +        if (this._a[index] == elmt) {
2642 +            this._a.splice(index, 1);
2643 +            return true;
2644 +        } else {
2645 +            index++;
2646 +        }
2647 +    }
2648 +    return false;
2649 +};
2650 +
2651 +SimileAjax.SortedArray.prototype.removeAll = function() {
2652 +    this._a = [];
2653 +};
2654 +
2655 +SimileAjax.SortedArray.prototype.elementAt = function(index) {
2656 +    return this._a[index];
2657 +};
2658 +
2659 +SimileAjax.SortedArray.prototype.length = function() {
2660 +    return this._a.length;
2661 +};
2662 +
2663 +SimileAjax.SortedArray.prototype.find = function(compare) {
2664 +    var a = 0;
2665 +    var b = this._a.length;
2666 +
2667 +    while (a < b) {
2668 +        var mid = Math.floor((a + b) / 2);
2669 +        var c = compare(this._a[mid]);
2670 +        if (mid == a) {
2671 +            return c < 0 ? a+1 : a;
2672 +        } else if (c < 0) {
2673 +            a = mid;
2674 +        } else {
2675 +            b = mid;
2676 +        }
2677 +    }
2678 +    return a;
2679 +};
2680 +
2681 +SimileAjax.SortedArray.prototype.getFirst = function() {
2682 +    return (this._a.length > 0) ? this._a[0] : null;
2683 +};
2684 +
2685 +SimileAjax.SortedArray.prototype.getLast = function() {
2686 +    return (this._a.length > 0) ? this._a[this._a.length - 1] : null;
2687 +};
2688 +
2689 +/*
2690 + *  Event Index
2691 + *
2692 + */
2693 +
2694 +SimileAjax.EventIndex = function(unit) {
2695 +    var eventIndex = this;
2696 +
2697 +    this._unit = (unit != null) ? unit : SimileAjax.NativeDateUnit;
2698 +    this._events = new SimileAjax.SortedArray(
2699 +        function(event1, event2) {
2700 +            return eventIndex._unit.compare(event1.getStart(), event2.getStart());
2701 +        }
2702 +    );
2703 +    this._idToEvent = {};
2704 +    this._indexed = true;
2705 +};
2706 +
2707 +SimileAjax.EventIndex.prototype.getUnit = function() {
2708 +    return this._unit;
2709 +};
2710 +
2711 +SimileAjax.EventIndex.prototype.getEvent = function(id) {
2712 +    return this._idToEvent[id];
2713 +};
2714 +
2715 +SimileAjax.EventIndex.prototype.add = function(evt) {
2716 +    this._events.add(evt);
2717 +    this._idToEvent[evt.getID()] = evt;
2718 +    this._indexed = false;
2719 +};
2720 +
2721 +SimileAjax.EventIndex.prototype.removeAll = function() {
2722 +    this._events.removeAll();
2723 +    this._idToEvent = {};
2724 +    this._indexed = false;
2725 +};
2726 +
2727 +SimileAjax.EventIndex.prototype.getCount = function() {
2728 +    return this._events.length();
2729 +};
2730 +
2731 +SimileAjax.EventIndex.prototype.getIterator = function(startDate, endDate) {
2732 +    if (!this._indexed) {
2733 +        this._index();
2734 +    }
2735 +    return new SimileAjax.EventIndex._Iterator(this._events, startDate, endDate, this._unit);
2736 +};
2737 +
2738 +SimileAjax.EventIndex.prototype.getReverseIterator = function(startDate, endDate) {
2739 +    if (!this._indexed) {
2740 +        this._index();
2741 +    }
2742 +    return new SimileAjax.EventIndex._ReverseIterator(this._events, startDate, endDate, this._unit);
2743 +};
2744 +
2745 +SimileAjax.EventIndex.prototype.getAllIterator = function() {
2746 +    return new SimileAjax.EventIndex._AllIterator(this._events);
2747 +};
2748 +
2749 +SimileAjax.EventIndex.prototype.getEarliestDate = function() {
2750 +    var evt = this._events.getFirst();
2751 +    return (evt == null) ? null : evt.getStart();
2752 +};
2753 +
2754 +SimileAjax.EventIndex.prototype.getLatestDate = function() {
2755 +    var evt = this._events.getLast();
2756 +    if (evt == null) {
2757 +        return null;
2758 +    }
2759 +
2760 +    if (!this._indexed) {
2761 +        this._index();
2762 +    }
2763 +
2764 +    var index = evt._earliestOverlapIndex;
2765 +    var date = this._events.elementAt(index).getEnd();
2766 +    for (var i = index + 1; i < this._events.length(); i++) {
2767 +        date = this._unit.later(date, this._events.elementAt(i).getEnd());
2768 +    }
2769 +
2770 +    return date;
2771 +};
2772 +
2773 +SimileAjax.EventIndex.prototype._index = function() {
2774 +    /*
2775 +     *  For each event, we want to find the earliest preceding
2776 +     *  event that overlaps with it, if any.
2777 +     */
2778 +
2779 +    var l = this._events.length();
2780 +    for (var i = 0; i < l; i++) {
2781 +        var evt = this._events.elementAt(i);
2782 +        evt._earliestOverlapIndex = i;
2783 +    }
2784 +
2785 +    var toIndex = 1;
2786 +    for (var i = 0; i < l; i++) {
2787 +        var evt = this._events.elementAt(i);
2788 +        var end = evt.getEnd();
2789 +
2790 +        toIndex = Math.max(toIndex, i + 1);
2791 +        while (toIndex < l) {
2792 +            var evt2 = this._events.elementAt(toIndex);
2793 +            var start2 = evt2.getStart();
2794 +
2795 +            if (this._unit.compare(start2, end) < 0) {
2796 +                evt2._earliestOverlapIndex = i;
2797 +                toIndex++;
2798 +            } else {
2799 +                break;
2800 +            }
2801 +        }
2802 +    }
2803 +    this._indexed = true;
2804 +};
2805 +
2806 +SimileAjax.EventIndex._Iterator = function(events, startDate, endDate, unit) {
2807 +    this._events = events;
2808 +    this._startDate = startDate;
2809 +    this._endDate = endDate;
2810 +    this._unit = unit;
2811 +
2812 +    this._currentIndex = events.find(function(evt) {
2813 +        return unit.compare(evt.getStart(), startDate);
2814 +    });
2815 +    if (this._currentIndex - 1 >= 0) {
2816 +        this._currentIndex = this._events.elementAt(this._currentIndex - 1)._earliestOverlapIndex;
2817 +    }
2818 +    this._currentIndex--;
2819 +
2820 +    this._maxIndex = events.find(function(evt) {
2821 +        return unit.compare(evt.getStart(), endDate);
2822 +    });
2823 +
2824 +    this._hasNext = false;
2825 +    this._next = null;
2826 +    this._findNext();
2827 +};
2828 +
2829 +SimileAjax.EventIndex._Iterator.prototype = {
2830 +    hasNext: function() { return this._hasNext; },
2831 +    next: function() {
2832 +        if (this._hasNext) {
2833 +            var next = this._next;
2834 +            this._findNext();
2835 +
2836 +            return next;
2837 +        } else {
2838 +            return null;
2839 +        }
2840 +    },
2841 +    _findNext: function() {
2842 +        var unit = this._unit;
2843 +        while ((++this._currentIndex) < this._maxIndex) {
2844 +            var evt = this._events.elementAt(this._currentIndex);
2845 +            if (unit.compare(evt.getStart(), this._endDate) < 0 &&
2846 +                unit.compare(evt.getEnd(), this._startDate) > 0) {
2847 +
2848 +                this._next = evt;
2849 +                this._hasNext = true;
2850 +                return;
2851 +            }
2852 +        }
2853 +        this._next = null;
2854 +        this._hasNext = false;
2855 +    }
2856 +};
2857 +
2858 +SimileAjax.EventIndex._ReverseIterator = function(events, startDate, endDate, unit) {
2859 +    this._events = events;
2860 +    this._startDate = startDate;
2861 +    this._endDate = endDate;
2862 +    this._unit = unit;
2863 +
2864 +    this._minIndex = events.find(function(evt) {
2865 +        return unit.compare(evt.getStart(), startDate);
2866 +    });
2867 +    if (this._minIndex - 1 >= 0) {
2868 +        this._minIndex = this._events.elementAt(this._minIndex - 1)._earliestOverlapIndex;
2869 +    }
2870 +
2871 +    this._maxIndex = events.find(function(evt) {
2872 +        return unit.compare(evt.getStart(), endDate);
2873 +    });
2874 +
2875 +    this._currentIndex = this._maxIndex;
2876 +    this._hasNext = false;
2877 +    this._next = null;
2878 +    this._findNext();
2879 +};
2880 +
2881 +SimileAjax.EventIndex._ReverseIterator.prototype = {
2882 +    hasNext: function() { return this._hasNext; },
2883 +    next: function() {
2884 +        if (this._hasNext) {
2885 +            var next = this._next;
2886 +            this._findNext();
2887 +
2888 +            return next;
2889 +        } else {
2890 +            return null;
2891 +        }
2892 +    },
2893 +    _findNext: function() {
2894 +        var unit = this._unit;
2895 +        while ((--this._currentIndex) >= this._minIndex) {
2896 +            var evt = this._events.elementAt(this._currentIndex);
2897 +            if (unit.compare(evt.getStart(), this._endDate) < 0 &&
2898 +                unit.compare(evt.getEnd(), this._startDate) > 0) {
2899 +
2900 +                this._next = evt;
2901 +                this._hasNext = true;
2902 +                return;
2903 +            }
2904 +        }
2905 +        this._next = null;
2906 +        this._hasNext = false;
2907 +    }
2908 +};
2909 +
2910 +SimileAjax.EventIndex._AllIterator = function(events) {
2911 +    this._events = events;
2912 +    this._index = 0;
2913 +};
2914 +
2915 +SimileAjax.EventIndex._AllIterator.prototype = {
2916 +    hasNext: function() {
2917 +        return this._index < this._events.length();
2918 +    },
2919 +    next: function() {
2920 +        return this._index < this._events.length() ?
2921 +            this._events.elementAt(this._index++) : null;
2922 +    }
2923 +};/*
2924 + *  Default Unit

2925 + *
2926 + */

2927 +

2928 +SimileAjax.NativeDateUnit = new Object();

2929 +

2930 +SimileAjax.NativeDateUnit.makeDefaultValue = function() {

2931 +    return new Date();

2932 +};

2933 +

2934 +SimileAjax.NativeDateUnit.cloneValue = function(v) {

2935 +    return new Date(v.getTime());

2936 +};

2937 +

2938 +SimileAjax.NativeDateUnit.getParser = function(format) {

2939 +    if (typeof format == "string") {

2940 +        format = format.toLowerCase();

2941 +    }

2942 +    return (format == "iso8601" || format == "iso 8601") ?

2943 +        SimileAjax.DateTime.parseIso8601DateTime : 

2944 +        SimileAjax.DateTime.parseGregorianDateTime;

2945 +};

2946 +

2947 +SimileAjax.NativeDateUnit.parseFromObject = function(o) {

2948 +    return SimileAjax.DateTime.parseGregorianDateTime(o);

2949 +};

2950 +

2951 +SimileAjax.NativeDateUnit.toNumber = function(v) {

2952 +    return v.getTime();

2953 +};

2954 +

2955 +SimileAjax.NativeDateUnit.fromNumber = function(n) {

2956 +    return new Date(n);

2957 +};

2958 +

2959 +SimileAjax.NativeDateUnit.compare = function(v1, v2) {

2960 +    var n1, n2;

2961 +    if (typeof v1 == "object") {

2962 +        n1 = v1.getTime();

2963 +    } else {

2964 +        n1 = Number(v1);

2965 +    }

2966 +    if (typeof v2 == "object") {

2967 +        n2 = v2.getTime();

2968 +    } else {

2969 +        n2 = Number(v2);

2970 +    }

2971 +    

2972 +    return n1 - n2;

2973 +};

2974 +

2975 +SimileAjax.NativeDateUnit.earlier = function(v1, v2) {

2976 +    return SimileAjax.NativeDateUnit.compare(v1, v2) < 0 ? v1 : v2;

2977 +};

2978 +

2979 +SimileAjax.NativeDateUnit.later = function(v1, v2) {

2980 +    return SimileAjax.NativeDateUnit.compare(v1, v2) > 0 ? v1 : v2;

2981 +};

2982 +

2983 +SimileAjax.NativeDateUnit.change = function(v, n) {

2984 +    return new Date(v.getTime() + n);

2985 +};

2986 +

2987 +/*
2988 + *  General, miscellaneous SimileAjax stuff
2989 + *
2990 + */
2991 +
2992 +SimileAjax.ListenerQueue = function(wildcardHandlerName) {
2993 +    this._listeners = [];
2994 +    this._wildcardHandlerName = wildcardHandlerName;
2995 +};
2996 +
2997 +SimileAjax.ListenerQueue.prototype.add = function(listener) {
2998 +    this._listeners.push(listener);
2999 +};
3000 +
3001 +SimileAjax.ListenerQueue.prototype.remove = function(listener) {
3002 +    var listeners = this._listeners;
3003 +    for (var i = 0; i < listeners.length; i++) {
3004 +        if (listeners[i] == listener) {
3005 +            listeners.splice(i, 1);
3006 +            break;
3007 +        }
3008 +    }
3009 +};
3010 +
3011 +SimileAjax.ListenerQueue.prototype.fire = function(handlerName, args) {
3012 +    var listeners = [].concat(this._listeners);
3013 +    for (var i = 0; i < listeners.length; i++) {
3014 +        var listener = listeners[i];
3015 +        if (handlerName in listener) {
3016 +            try {
3017 +                listener[handlerName].apply(listener, args);
3018 +            } catch (e) {
3019 +                SimileAjax.Debug.exception("Error firing event of name " + handlerName, e);
3020 +            }
3021 +        } else if (this._wildcardHandlerName != null &&
3022 +            this._wildcardHandlerName in listener) {
3023 +            try {
3024 +                listener[this._wildcardHandlerName].apply(listener, [ handlerName ]);
3025 +            } catch (e) {
3026 +                SimileAjax.Debug.exception("Error firing event of name " + handlerName + " to wildcard handler", e);
3027 +            }
3028 +        }
3029 +    }
3030 +};
3031 +
3032 +/*
3033 + *  History
3034 + *
3035 + *  This is a singleton that keeps track of undoable user actions and
3036 + *  performs undos and redos in response to the browser's Back and
3037 + *  Forward buttons.
3038 + *
3039 + *  Call addAction(action) to register an undoable user action. action
3040 + *  must have 4 fields:
3041 + *
3042 + *      perform: an argument-less function that carries out the action
3043 + *      undo:    an argument-less function that undos the action
3044 + *      label:   a short, user-friendly string describing the action
3045 + *      uiLayer: the UI layer on which the action takes place
3046 + *
3047 + *  By default, the history keeps track of upto 10 actions. You can
3048 + *  configure this behavior by setting
3049 + *      SimileAjax.History.maxHistoryLength
3050 + *  to a different number.
3051 + *
3052 + *  An iframe is inserted into the document's body element to track
3053 + *  onload events.
3054 + *
3055 + */
3056 +
3057 +SimileAjax.History = {
3058 +    maxHistoryLength:       10,
3059 +    historyFile:            "__history__.html",
3060 +    enabled:               true,
3061 +
3062 +    _initialized:           false,
3063 +    _listeners:             new SimileAjax.ListenerQueue(),
3064 +
3065 +    _actions:               [],
3066 +    _baseIndex:             0,
3067 +    _currentIndex:          0,
3068 +
3069 +    _plainDocumentTitle:    document.title
3070 +};
3071 +
3072 +SimileAjax.History.formatHistoryEntryTitle = function(actionLabel) {
3073 +    return SimileAjax.History._plainDocumentTitle + " {" + actionLabel + "}";
3074 +};
3075 +
3076 +SimileAjax.History.initialize = function() {
3077 +    if (SimileAjax.History._initialized) {
3078 +        return;
3079 +    }
3080 +
3081 +    if (SimileAjax.History.enabled) {
3082 +        var iframe = document.createElement("iframe");
3083 +        iframe.id = "simile-ajax-history";
3084 +        iframe.style.position = "absolute";
3085 +        iframe.style.width = "10px";
3086 +        iframe.style.height = "10px";
3087 +        iframe.style.top = "0px";
3088 +        iframe.style.left = "0px";
3089 +        iframe.style.visibility = "hidden";
3090 +        iframe.src = SimileAjax.History.historyFile + "?0";
3091 +
3092 +        document.body.appendChild(iframe);
3093 +        SimileAjax.DOM.registerEvent(iframe, "load", SimileAjax.History._handleIFrameOnLoad);
3094 +
3095 +        SimileAjax.History._iframe = iframe;
3096 +    }
3097 +    SimileAjax.History._initialized = true;
3098 +};
3099 +
3100 +SimileAjax.History.addListener = function(listener) {
3101 +    SimileAjax.History.initialize();
3102 +
3103 +    SimileAjax.History._listeners.add(listener);
3104 +};
3105 +
3106 +SimileAjax.History.removeListener = function(listener) {
3107 +    SimileAjax.History.initialize();
3108 +
3109 +    SimileAjax.History._listeners.remove(listener);
3110 +};
3111 +
3112 +SimileAjax.History.addAction = function(action) {
3113 +    SimileAjax.History.initialize();
3114 +
3115 +    SimileAjax.History._listeners.fire("onBeforePerform", [ action ]);
3116 +    window.setTimeout(function() {
3117 +        try {
3118 +            action.perform();
3119 +            SimileAjax.History._listeners.fire("onAfterPerform", [ action ]);
3120 +
3121 +            if (SimileAjax.History.enabled) {
3122 +                SimileAjax.History._actions = SimileAjax.History._actions.slice(
3123 +                    0, SimileAjax.History._currentIndex - SimileAjax.History._baseIndex);
3124 +
3125 +                SimileAjax.History._actions.push(action);
3126 +                SimileAjax.History._currentIndex++;
3127 +
3128 +                var diff = SimileAjax.History._actions.length - SimileAjax.History.maxHistoryLength;
3129 +                if (diff > 0) {
3130 +                    SimileAjax.History._actions = SimileAjax.History._actions.slice(diff);
3131 +                    SimileAjax.History._baseIndex += diff;
3132 +                }
3133 +
3134 +                try {
3135 +                    SimileAjax.History._iframe.contentWindow.location.search =
3136 +                        "?" + SimileAjax.History._currentIndex;
3137 +                } catch (e) {
3138 +                    /*
3139 +                     *  We can't modify location.search most probably because it's a file:// url.
3140 +                     *  We'll just going to modify the document's title.
3141 +                     */
3142 +                    var title = SimileAjax.History.formatHistoryEntryTitle(action.label);
3143 +                    document.title = title;
3144 +                }
3145 +            }
3146 +        } catch (e) {
3147 +            SimileAjax.Debug.exception(e, "Error adding action {" + action.label + "} to history");
3148 +        }
3149 +    }, 0);
3150 +};
3151 +
3152 +SimileAjax.History.addLengthyAction = function(perform, undo, label) {
3153 +    SimileAjax.History.addAction({
3154 +        perform:    perform,
3155 +        undo:       undo,
3156 +        label:      label,
3157 +        uiLayer:    SimileAjax.WindowManager.getBaseLayer(),
3158 +        lengthy:    true
3159 +    });
3160 +};
3161 +
3162 +SimileAjax.History._handleIFrameOnLoad = function() {
3163 +    /*
3164 +     *  This function is invoked when the user herself
3165 +     *  navigates backward or forward. We need to adjust
3166 +     *  the application's state accordingly.
3167 +     */
3168 +
3169 +    try {
3170 +        var q = SimileAjax.History._iframe.contentWindow.location.search;
3171 +        var c = (q.length == 0) ? 0 : Math.max(0, parseInt(q.substr(1)));
3172 +
3173 +        var finishUp = function() {
3174 +            var diff = c - SimileAjax.History._currentIndex;
3175 +            SimileAjax.History._currentIndex += diff;
3176 +            SimileAjax.History._baseIndex += diff;
3177 +
3178 +            SimileAjax.History._iframe.contentWindow.location.search = "?" + c;
3179 +        };
3180 +
3181 +        if (c < SimileAjax.History._currentIndex) { // need to undo
3182 +            SimileAjax.History._listeners.fire("onBeforeUndoSeveral", []);
3183 +            window.setTimeout(function() {
3184 +                while (SimileAjax.History._currentIndex > c &&
3185 +                       SimileAjax.History._currentIndex > SimileAjax.History._baseIndex) {
3186 +
3187 +                    SimileAjax.History._currentIndex--;
3188 +
3189 +                    var action = SimileAjax.History._actions[SimileAjax.History._currentIndex - SimileAjax.History._baseIndex];
3190 +
3191 +                    try {
3192 +                        action.undo();
3193 +                    } catch (e) {
3194 +                        SimileAjax.Debug.exception(e, "History: Failed to undo action {" + action.label + "}");
3195 +                    }
3196 +                }
3197 +
3198 +                SimileAjax.History._listeners.fire("onAfterUndoSeveral", []);
3199 +                finishUp();
3200 +            }, 0);
3201 +        } else if (c > SimileAjax.History._currentIndex) { // need to redo
3202 +            SimileAjax.History._listeners.fire("onBeforeRedoSeveral", []);
3203 +            window.setTimeout(function() {
3204 +                while (SimileAjax.History._currentIndex < c &&
3205 +                       SimileAjax.History._currentIndex - SimileAjax.History._baseIndex < SimileAjax.History._actions.length) {
3206 +
3207 +                    var action = SimileAjax.History._actions[SimileAjax.History._currentIndex - SimileAjax.History._baseIndex];
3208 +
3209 +                    try {
3210 +                        action.perform();
3211 +                    } catch (e) {
3212 +                        SimileAjax.Debug.exception(e, "History: Failed to redo action {" + action.label + "}");
3213 +                    }
3214 +
3215 +                    SimileAjax.History._currentIndex++;
3216 +                }
3217 +
3218 +                SimileAjax.History._listeners.fire("onAfterRedoSeveral", []);
3219 +                finishUp();
3220 +            }, 0);
3221 +        } else {
3222 +            var index = SimileAjax.History._currentIndex - SimileAjax.History._baseIndex - 1;
3223 +            var title = (index >= 0 && index < SimileAjax.History._actions.length) ?
3224 +                SimileAjax.History.formatHistoryEntryTitle(SimileAjax.History._actions[index].label) :
3225 +                SimileAjax.History._plainDocumentTitle;
3226 +
3227 +            SimileAjax.History._iframe.contentWindow.document.title = title;
3228 +            document.title = title;
3229 +        }
3230 +    } catch (e) {
3231 +        // silent
3232 +    }
3233 +};
3234 +
3235 +SimileAjax.History.getNextUndoAction = function() {
3236 +    try {
3237 +        var index = SimileAjax.History._currentIndex - SimileAjax.History._baseIndex - 1;
3238 +        return SimileAjax.History._actions[index];
3239 +    } catch (e) {
3240 +        return null;
3241 +    }
3242 +};
3243 +
3244 +SimileAjax.History.getNextRedoAction = function() {
3245 +    try {
3246 +        var index = SimileAjax.History._currentIndex - SimileAjax.History._baseIndex;
3247 +        return SimileAjax.History._actions[index];
3248 +    } catch (e) {
3249 +        return null;
3250 +    }
3251 +};
3252 +/**
3253 + * @fileOverview UI layers and window-wide dragging
3254 + * @name SimileAjax.WindowManager
3255 + */
3256 +
3257 +/**
3258 + *  This is a singleton that keeps track of UI layers (modal and
3259 + *  modeless) and enables/disables UI elements based on which layers
3260 + *  they belong to. It also provides window-wide dragging
3261 + *  implementation.
3262 + */
3263 +SimileAjax.WindowManager = {
3264 +    _initialized:       false,
3265 +    _listeners:         [],
3266 +
3267 +    _draggedElement:                null,
3268 +    _draggedElementCallback:        null,
3269 +    _dropTargetHighlightElement:    null,
3270 +    _lastCoords:                    null,
3271 +    _ghostCoords:                   null,
3272 +    _draggingMode:                  "",
3273 +    _dragging:                      false,
3274 +
3275 +    _layers:            []
3276 +};
3277 +
3278 +SimileAjax.WindowManager.initialize = function() {
3279 +    if (SimileAjax.WindowManager._initialized) {
3280 +        return;
3281 +    }
3282 +
3283 +    SimileAjax.DOM.registerEvent(document.body, "mousedown", SimileAjax.WindowManager._onBodyMouseDown);
3284 +    SimileAjax.DOM.registerEvent(document.body, "mousemove", SimileAjax.WindowManager._onBodyMouseMove);
3285 +    SimileAjax.DOM.registerEvent(document.body, "mouseup",   SimileAjax.WindowManager._onBodyMouseUp);
3286 +    SimileAjax.DOM.registerEvent(document, "keydown",       SimileAjax.WindowManager._onBodyKeyDown);
3287 +    SimileAjax.DOM.registerEvent(document, "keyup",         SimileAjax.WindowManager._onBodyKeyUp);
3288 +
3289 +    SimileAjax.WindowManager._layers.push({index: 0});
3290 +
3291 +    SimileAjax.WindowManager._historyListener = {
3292 +        onBeforeUndoSeveral:    function() {},
3293 +        onAfterUndoSeveral:     function() {},
3294 +        onBeforeUndo:           function() {},
3295 +        onAfterUndo:            function() {},
3296 +
3297 +        onBeforeRedoSeveral:    function() {},
3298 +        onAfterRedoSeveral:     function() {},
3299 +        onBeforeRedo:           function() {},
3300 +        onAfterRedo:            function() {}
3301 +    };
3302 +    SimileAjax.History.addListener(SimileAjax.WindowManager._historyListener);
3303 +
3304 +    SimileAjax.WindowManager._initialized = true;
3305 +};
3306 +
3307 +SimileAjax.WindowManager.getBaseLayer = function() {
3308 +    SimileAjax.WindowManager.initialize();
3309 +    return SimileAjax.WindowManager._layers[0];
3310 +};
3311 +
3312 +SimileAjax.WindowManager.getHighestLayer = function() {
3313 +    SimileAjax.WindowManager.initialize();
3314 +    return SimileAjax.WindowManager._layers[SimileAjax.WindowManager._layers.length - 1];
3315 +};
3316 +
3317 +SimileAjax.WindowManager.registerEventWithObject = function(elmt, eventName, obj, handlerName, layer) {
3318 +    SimileAjax.WindowManager.registerEvent(
3319 +        elmt,
3320 +        eventName,
3321 +        function(elmt2, evt, target) {
3322 +            return obj[handlerName].call(obj, elmt2, evt, target);
3323 +        },
3324 +        layer
3325 +    );
3326 +};
3327 +
3328 +SimileAjax.WindowManager.registerEvent = function(elmt, eventName, handler, layer) {
3329 +    if (layer == null) {
3330 +        layer = SimileAjax.WindowManager.getHighestLayer();
3331 +    }
3332 +
3333 +    var handler2 = function(elmt, evt, target) {
3334 +        if (SimileAjax.WindowManager._canProcessEventAtLayer(layer)) {
3335 +            SimileAjax.WindowManager._popToLayer(layer.index);
3336 +            try {
3337 +                handler(elmt, evt, target);
3338 +            } catch (e) {
3339 +                SimileAjax.Debug.exception(e);
3340 +            }
3341 +        }
3342 +        SimileAjax.DOM.cancelEvent(evt);
3343 +        return false;
3344 +    }
3345 +
3346 +    SimileAjax.DOM.registerEvent(elmt, eventName, handler2);
3347 +};
3348 +
3349 +SimileAjax.WindowManager.pushLayer = function(f, ephemeral, elmt) {
3350 +    var layer = { onPop: f, index: SimileAjax.WindowManager._layers.length, ephemeral: (ephemeral), elmt: elmt };
3351 +    SimileAjax.WindowManager._layers.push(layer);
3352 +
3353 +    return layer;
3354 +};
3355 +
3356 +SimileAjax.WindowManager.popLayer = function(layer) {
3357 +    for (var i = 1; i < SimileAjax.WindowManager._layers.length; i++) {
3358 +        if (SimileAjax.WindowManager._layers[i] == layer) {
3359 +            SimileAjax.WindowManager._popToLayer(i - 1);
3360 +            break;
3361 +        }
3362 +    }
3363 +};
3364 +
3365 +SimileAjax.WindowManager.popAllLayers = function() {
3366 +    SimileAjax.WindowManager._popToLayer(0);
3367 +};
3368 +
3369 +SimileAjax.WindowManager.registerForDragging = function(elmt, callback, layer) {
3370 +    SimileAjax.WindowManager.registerEvent(
3371 +        elmt,
3372 +        "mousedown",
3373 +        function(elmt, evt, target) {
3374 +            SimileAjax.WindowManager._handleMouseDown(elmt, evt, callback);
3375 +        },
3376 +        layer
3377 +    );
3378 +};
3379 +
3380 +SimileAjax.WindowManager._popToLayer = function(level) {
3381 +    while (level+1 < SimileAjax.WindowManager._layers.length) {
3382 +        try {
3383 +            var layer = SimileAjax.WindowManager._layers.pop();
3384 +            if (layer.onPop != null) {
3385 +                layer.onPop();
3386 +            }
3387 +        } catch (e) {
3388 +        }
3389 +    }
3390 +};
3391 +
3392 +SimileAjax.WindowManager._canProcessEventAtLayer = function(layer) {
3393 +    if (layer.index == (SimileAjax.WindowManager._layers.length - 1)) {
3394 +        return true;
3395 +    }
3396 +    for (var i = layer.index + 1; i < SimileAjax.WindowManager._layers.length; i++) {
3397 +        if (!SimileAjax.WindowManager._layers[i].ephemeral) {
3398 +            return false;
3399 +        }
3400 +    }
3401 +    return true;
3402 +};
3403 +
3404 +SimileAjax.WindowManager.cancelPopups = function(evt) {
3405 +    var evtCoords = (evt) ? SimileAjax.DOM.getEventPageCoordinates(evt) : { x: -1, y: -1 };
3406 +
3407 +    var i = SimileAjax.WindowManager._layers.length - 1;
3408 +    while (i > 0 && SimileAjax.WindowManager._layers[i].ephemeral) {
3409 +        var layer = SimileAjax.WindowManager._layers[i];
3410 +        if (layer.elmt != null) { // if event falls within main element of layer then don't cancel
3411 +            var elmt = layer.elmt;
3412 +            var elmtCoords = SimileAjax.DOM.getPageCoordinates(elmt);
3413 +            if (evtCoords.x >= elmtCoords.left && evtCoords.x < (elmtCoords.left + elmt.offsetWidth) &&
3414 +                evtCoords.y >= elmtCoords.top && evtCoords.y < (elmtCoords.top + elmt.offsetHeight)) {
3415 +                break;
3416 +            }
3417 +        }
3418 +        i--;
3419 +    }
3420 +    SimileAjax.WindowManager._popToLayer(i);
3421 +};
3422 +
3423 +SimileAjax.WindowManager._onBodyMouseDown = function(elmt, evt, target) {
3424 +    if (!("eventPhase" in evt) || evt.eventPhase == evt.BUBBLING_PHASE) {
3425 +        SimileAjax.WindowManager.cancelPopups(evt);
3426 +    }
3427 +};
3428 +
3429 +SimileAjax.WindowManager._handleMouseDown = function(elmt, evt, callback) {
3430 +    SimileAjax.WindowManager._draggedElement = elmt;
3431 +    SimileAjax.WindowManager._draggedElementCallback = callback;
3432 +    SimileAjax.WindowManager._lastCoords = { x: evt.clientX, y: evt.clientY };
3433 +
3434 +    SimileAjax.DOM.cancelEvent(evt);
3435 +    return false;
3436 +};
3437 +
3438 +SimileAjax.WindowManager._onBodyKeyDown = function(elmt, evt, target) {
3439 +    if (SimileAjax.WindowManager._dragging) {
3440 +        if (evt.keyCode == 27) { // esc
3441 +            SimileAjax.WindowManager._cancelDragging();
3442 +        } else if ((evt.keyCode == 17 || evt.keyCode == 16) && SimileAjax.WindowManager._draggingMode != "copy") {
3443 +            SimileAjax.WindowManager._draggingMode = "copy";
3444 +
3445 +            var img = SimileAjax.Graphics.createTranslucentImage(SimileAjax.urlPrefix + "images/copy.png");
3446 +            img.style.position = "absolute";
3447 +            img.style.left = (SimileAjax.WindowManager._ghostCoords.left - 16) + "px";
3448 +            img.style.top = (SimileAjax.WindowManager._ghostCoords.top) + "px";
3449 +            document.body.appendChild(img);
3450 +
3451 +            SimileAjax.WindowManager._draggingModeIndicatorElmt = img;
3452 +        }
3453 +    }
3454 +};
3455 +
3456 +SimileAjax.WindowManager._onBodyKeyUp = function(elmt, evt, target) {
3457 +    if (SimileAjax.WindowManager._dragging) {
3458 +        if (evt.keyCode == 17 || evt.keyCode == 16) {
3459 +            SimileAjax.WindowManager._draggingMode = "";
3460 +            if (SimileAjax.WindowManager._draggingModeIndicatorElmt != null) {
3461 +                document.body.removeChild(SimileAjax.WindowManager._draggingModeIndicatorElmt);
3462 +                SimileAjax.WindowManager._draggingModeIndicatorElmt = null;
3463 +            }
3464 +        }
3465 +    }
3466 +};
3467 +
3468 +SimileAjax.WindowManager._onBodyMouseMove = function(elmt, evt, target) {
3469 +    if (SimileAjax.WindowManager._draggedElement != null) {
3470 +        var callback = SimileAjax.WindowManager._draggedElementCallback;
3471 +
3472 +        var lastCoords = SimileAjax.WindowManager._lastCoords;
3473 +        var diffX = evt.clientX - lastCoords.x;
3474 +        var diffY = evt.clientY - lastCoords.y;
3475 +
3476 +        if (!SimileAjax.WindowManager._dragging) {
3477 +            if (Math.abs(diffX) > 5 || Math.abs(diffY) > 5) {
3478 +                try {
3479 +                    if ("onDragStart" in callback) {
3480 +                        callback.onDragStart();
3481 +                    }
3482 +
3483 +                    if ("ghost" in callback && callback.ghost) {
3484 +                        var draggedElmt = SimileAjax.WindowManager._draggedElement;
3485 +
3486 +                        SimileAjax.WindowManager._ghostCoords = SimileAjax.DOM.getPageCoordinates(draggedElmt);
3487 +                        SimileAjax.WindowManager._ghostCoords.left += diffX;
3488 +                        SimileAjax.WindowManager._ghostCoords.top += diffY;
3489 +
3490 +                        var ghostElmt = draggedElmt.cloneNode(true);
3491 +                        ghostElmt.style.position = "absolute";
3492 +                        ghostElmt.style.left = SimileAjax.WindowManager._ghostCoords.left + "px";
3493 +                        ghostElmt.style.top = SimileAjax.WindowManager._ghostCoords.top + "px";
3494 +                        ghostElmt.style.zIndex = 1000;
3495 +                        SimileAjax.Graphics.setOpacity(ghostElmt, 50);
3496 +
3497 +                        document.body.appendChild(ghostElmt);
3498 +                        callback._ghostElmt = ghostElmt;
3499 +                    }
3500 +
3501 +                    SimileAjax.WindowManager._dragging = true;
3502 +                    SimileAjax.WindowManager._lastCoords = { x: evt.clientX, y: evt.clientY };
3503 +
3504 +                    document.body.focus();
3505 +                } catch (e) {
3506 +                    SimileAjax.Debug.exception("WindowManager: Error handling mouse down", e);
3507 +                    SimileAjax.WindowManager._cancelDragging();
3508 +                }
3509 +            }
3510 +        } else {
3511 +            try {
3512 +                SimileAjax.WindowManager._lastCoords = { x: evt.clientX, y: evt.clientY };
3513 +
3514 +                if ("onDragBy" in callback) {
3515 +                    callback.onDragBy(diffX, diffY);
3516 +                }
3517 +
3518 +                if ("_ghostElmt" in callback) {
3519 +                    var ghostElmt = callback._ghostElmt;
3520 +
3521 +                    SimileAjax.WindowManager._ghostCoords.left += diffX;
3522 +                    SimileAjax.WindowManager._ghostCoords.top += diffY;
3523 +
3524 +                    ghostElmt.style.left = SimileAjax.WindowManager._ghostCoords.left + "px";
3525 +                    ghostElmt.style.top = SimileAjax.WindowManager._ghostCoords.top + "px";
3526 +                    if (SimileAjax.WindowManager._draggingModeIndicatorElmt != null) {
3527 +                        var indicatorElmt = SimileAjax.WindowManager._draggingModeIndicatorElmt;
3528 +
3529 +                        indicatorElmt.style.left = (SimileAjax.WindowManager._ghostCoords.left - 16) + "px";
3530 +                        indicatorElmt.style.top = SimileAjax.WindowManager._ghostCoords.top + "px";
3531 +                    }
3532 +
3533 +                    if ("droppable" in callback && callback.droppable) {
3534 +                        var coords = SimileAjax.DOM.getEventPageCoordinates(evt);
3535 +                        var target = SimileAjax.DOM.hittest(
3536 +                            coords.x, coords.y,
3537 +                            [   SimileAjax.WindowManager._ghostElmt,
3538 +                                SimileAjax.WindowManager._dropTargetHighlightElement
3539 +                            ]
3540 +                        );
3541 +                        target = SimileAjax.WindowManager._findDropTarget(target);
3542 +
3543 +                        if (target != SimileAjax.WindowManager._potentialDropTarget) {
3544 +                            if (SimileAjax.WindowManager._dropTargetHighlightElement != null) {
3545 +                                document.body.removeChild(SimileAjax.WindowManager._dropTargetHighlightElement);
3546 +
3547 +                                SimileAjax.WindowManager._dropTargetHighlightElement = null;
3548 +                                SimileAjax.WindowManager._potentialDropTarget = null;
3549 +                            }
3550 +
3551 +                            var droppable = false;
3552 +                            if (target != null) {
3553 +                                if ((!("canDropOn" in callback) || callback.canDropOn(target)) &&
3554 +                                    (!("canDrop" in target) || target.canDrop(SimileAjax.WindowManager._draggedElement))) {
3555 +
3556 +                                    droppable = true;
3557 +                                }
3558 +                            }
3559 +
3560 +                            if (droppable) {
3561 +                                var border = 4;
3562 +                                var targetCoords = SimileAjax.DOM.getPageCoordinates(target);
3563 +                                var highlight = document.createElement("div");
3564 +                                highlight.style.border = border + "px solid yellow";
3565 +                                highlight.style.backgroundColor = "yellow";
3566 +                                highlight.style.position = "absolute";
3567 +                                highlight.style.left = targetCoords.left + "px";
3568 +                                highlight.style.top = targetCoords.top + "px";
3569 +                                highlight.style.width = (target.offsetWidth - border * 2) + "px";
3570 +                                highlight.style.height = (target.offsetHeight - border * 2) + "px";
3571 +                                SimileAjax.Graphics.setOpacity(highlight, 30);
3572 +                                document.body.appendChild(highlight);
3573 +
3574 +                                SimileAjax.WindowManager._potentialDropTarget = target;
3575 +                                SimileAjax.WindowManager._dropTargetHighlightElement = highlight;
3576 +                            }
3577 +                        }
3578 +                    }
3579 +                }
3580 +            } catch (e) {
3581 +                SimileAjax.Debug.exception("WindowManager: Error handling mouse move", e);
3582 +                SimileAjax.WindowManager._cancelDragging();
3583 +            }
3584 +        }
3585 +
3586 +        SimileAjax.DOM.cancelEvent(evt);
3587 +        return false;
3588 +    }
3589 +};
3590 +
3591 +SimileAjax.WindowManager._onBodyMouseUp = function(elmt, evt, target) {
3592 +    if (SimileAjax.WindowManager._draggedElement != null) {
3593 +        try {
3594 +            if (SimileAjax.WindowManager._dragging) {
3595 +                var callback = SimileAjax.WindowManager._draggedElementCallback;
3596 +                if ("onDragEnd" in callback) {
3597 +                    callback.onDragEnd();
3598 +                }
3599 +                if ("droppable" in callback && callback.droppable) {
3600 +                    var dropped = false;
3601 +
3602 +                    var target = SimileAjax.WindowManager._potentialDropTarget;
3603 +                    if (target != null) {
3604 +                        if ((!("canDropOn" in callback) || callback.canDropOn(target)) &&
3605 +                            (!("canDrop" in target) || target.canDrop(SimileAjax.WindowManager._draggedElement))) {
3606 +
3607 +                            if ("onDropOn" in callback) {
3608 +                                callback.onDropOn(target);
3609 +                            }
3610 +                            target.ondrop(SimileAjax.WindowManager._draggedElement, SimileAjax.WindowManager._draggingMode);
3611 +
3612 +                            dropped = true;
3613 +                        }
3614 +                    }
3615 +
3616 +                    if (!dropped) {
3617 +                        // TODO: do holywood explosion here
3618 +                    }
3619 +                }
3620 +            }
3621 +        } finally {
3622 +            SimileAjax.WindowManager._cancelDragging();
3623 +        }
3624 +
3625 +        SimileAjax.DOM.cancelEvent(evt);
3626 +        return false;
3627 +    }
3628 +};
3629 +
3630 +SimileAjax.WindowManager._cancelDragging = function() {
3631 +    var callback = SimileAjax.WindowManager._draggedElementCallback;
3632 +    if ("_ghostElmt" in callback) {
3633 +        var ghostElmt = callback._ghostElmt;
3634 +        document.body.removeChild(ghostElmt);
3635 +
3636 +        delete callback._ghostElmt;
3637 +    }
3638 +    if (SimileAjax.WindowManager._dropTargetHighlightElement != null) {
3639 +        document.body.removeChild(SimileAjax.WindowManager._dropTargetHighlightElement);
3640 +        SimileAjax.WindowManager._dropTargetHighlightElement = null;
3641 +    }
3642 +    if (SimileAjax.WindowManager._draggingModeIndicatorElmt != null) {
3643 +        document.body.removeChild(SimileAjax.WindowManager._draggingModeIndicatorElmt);
3644 +        SimileAjax.WindowManager._draggingModeIndicatorElmt = null;
3645 +    }
3646 +
3647 +    SimileAjax.WindowManager._draggedElement = null;
3648 +    SimileAjax.WindowManager._draggedElementCallback = null;
3649 +    SimileAjax.WindowManager._potentialDropTarget = null;
3650 +    SimileAjax.WindowManager._dropTargetHighlightElement = null;
3651 +    SimileAjax.WindowManager._lastCoords = null;
3652 +    SimileAjax.WindowManager._ghostCoords = null;
3653 +    SimileAjax.WindowManager._draggingMode = "";
3654 +    SimileAjax.WindowManager._dragging = false;
3655 +};
3656 +
3657 +SimileAjax.WindowManager._findDropTarget = function(elmt) {
3658 +    while (elmt != null) {
3659 +        if ("ondrop" in elmt && (typeof elmt.ondrop) == "function") {
3660 +            break;
3661 +        }
3662 +        elmt = elmt.parentNode;
3663 +    }
3664 +    return elmt;
3665 +};
3666 +/*
3667 + *  Timeline API
3668 + *
3669 + *  This file will load all the Javascript files
3670 + *  necessary to make the standard timeline work.
3671 + *  It also detects the default locale.
3672 + *
3673 + *  To run from the MIT copy of Timeline:
3674 + *  Include this file in your HTML file as follows:
3675 + *
3676 + *    <script src="http://api.simile-widgets.org/timeline/2.3.1/timeline-api.js"
3677 + *     type="text/javascript"></script>
3678 + *
3679 + *
3680 + * To host the Timeline files on your own server:
3681 + *   1) Install the Timeline and Simile-Ajax files onto your webserver using
3682 + *      timeline_libraries.zip or timeline_source.zip
3683 + *
3684 + *   2) Set global js variables used to send parameters to this script:
3685 + *        var Timeline_ajax_url -- url for simile-ajax-api.js
3686 + *        var Timeline_urlPrefix -- url for the *directory* that contains timeline-api.js
3687 + *            Include trailing slash
3688 + *        var Timeline_parameters='bundle=true'; // you must set bundle to true if you are using
3689 + *                                               // timeline_libraries.zip since only the
3690 + *                                               // bundled libraries are included
3691 + *
3692 + * eg your html page would include
3693 + *
3694 + *   <script>
3695 + *     var Timeline_ajax_url="http://YOUR_SERVER/javascripts/timeline/timeline_ajax/simile-ajax-api.js";
3696 + *     var Timeline_urlPrefix='http://YOUR_SERVER/javascripts/timeline/timeline_js/';
3697 + *     var Timeline_parameters='bundle=true';
3698 + *   </script>
3699 + *   <script src="http://YOUR_SERVER/javascripts/timeline/timeline_js/timeline-api.js"
3700 + *     type="text/javascript">
3701 + *   </script>
3702 + *
3703 + * SCRIPT PARAMETERS
3704 + * This script auto-magically figures out locale and has defaults for other parameters
3705 + * To set parameters explicity, set js global variable Timeline_parameters or include as
3706 + * parameters on the url using GET style. Eg the two next lines pass the same parameters:
3707 + *     Timeline_parameters='bundle=true';                    // pass parameter via js variable
3708 + *     <script src="http://....timeline-api.js?bundle=true"  // pass parameter via url
3709 + *
3710 + * Parameters
3711 + *   timeline-use-local-resources --
3712 + *   bundle -- true: use the single js bundle file; false: load individual files (for debugging)
3713 + *   locales --
3714 + *   defaultLocale --
3715 + *   forceLocale -- force locale to be a particular value--used for debugging. Normally locale is determined
3716 + *                  by browser's and server's locale settings.
3717 + *
3718 + * DEBUGGING
3719 + * If you have a problem with Timeline, the first step is to use the unbundled Javascript files. To do so:
3720 + * To use the unbundled Timeline and Ajax libraries

3721 + * Change

3722 + *   <script src="http://api.simile-widgets.org/timeline/2.3.1/api/timeline-api.js?bundle=true" type="text/javascript"></script>

3723 + * To

3724 + *   <script>var Timeline_ajax_url = "http://api.simile-widgets.org/ajax/2.2.1/simile-ajax-api.js?bundle=false"</script>

3725 + *   <script src="http://api.simile-widgets.org/timeline/2.3.1/api/timeline-api.js?bundle=false" type="text/javascript"></script>

3726 + *
3727 + * Note that the Ajax version is usually NOT the same as the Timeline version.
3728 + * See variable simile_ajax_ver below for the current version
3729 + *
3730 + *
3731 + */
3732 +
3733 +(function() {
3734 +
3735 +    var simile_ajax_ver = "2.2.1"; // ===========>>>  current Simile-Ajax version
3736 +
3737 +    var useLocalResources = false;
3738 +    if (document.location.search.length > 0) {
3739 +        var params = document.location.search.substr(1).split("&");
3740 +        for (var i = 0; i < params.length; i++) {
3741 +            if (params[i] == "timeline-use-local-resources") {
3742 +                useLocalResources = true;
3743 +            }
3744 +        }
3745 +    };
3746 +
3747 +    var loadMe = function() {
3748 +        if ("Timeline" in window) {
3749 +            return;
3750 +        }
3751 +
3752 +        window.Timeline = new Object();
3753 +        window.Timeline.DateTime = window.SimileAjax.DateTime; // for backward compatibility
3754 +
3755 +        var bundle = false;
3756 +        var javascriptFiles = [
3757 +            "timeline.js",
3758 +            "band.js",
3759 +            "themes.js",
3760 +            "ethers.js",
3761 +            "ether-painters.js",
3762 +            "event-utils.js",
3763 +            "labellers.js",
3764 +            "sources.js",
3765 +            "original-painter.js",
3766 +            "detailed-painter.js",
3767 +            "overview-painter.js",
3768 +            "compact-painter.js",
3769 +            "decorators.js",
3770 +            "units.js"
3771 +        ];
3772 +        var cssFiles = [
3773 +            "timeline.css",
3774 +            "ethers.css",
3775 +            "events.css"
3776 +        ];
3777 +
3778 +        var localizedJavascriptFiles = [
3779 +            "timeline.js",
3780 +            "labellers.js"
3781 +        ];
3782 +        var localizedCssFiles = [
3783 +        ];
3784 +
3785 +        // ISO-639 language codes, ISO-3166 country codes (2 characters)
3786 +        var supportedLocales = [
3787 +            "cs",       // Czech
3788 +            "de",       // German
3789 +            "en",       // English
3790 +            "es",       // Spanish
3791 +            "fr",       // French
3792 +            "it",       // Italian
3793 +            "nl",       // Dutch (The Netherlands)
3794 +            "ru",       // Russian
3795 +            "se",       // Swedish
3796 +            "tr",       // Turkish
3797 +            "vi",       // Vietnamese
3798 +            "zh"        // Chinese
3799 +        ];
3800 +
3801 +        try {
3802 +            var desiredLocales = [ "en" ],
3803 +                defaultServerLocale = "en",
3804 +                forceLocale = null;
3805 +
3806 +            var parseURLParameters = function(parameters) {
3807 +                var params = parameters.split("&");
3808 +                for (var p = 0; p < params.length; p++) {
3809 +                    var pair = params[p].split("=");
3810 +                    if (pair[0] == "locales") {
3811 +                        desiredLocales = desiredLocales.concat(pair[1].split(","));
3812 +                    } else if (pair[0] == "defaultLocale") {
3813 +                        defaultServerLocale = pair[1];
3814 +                    } else if (pair[0] == "forceLocale") {
3815 +                        forceLocale = pair[1];
3816 +                        desiredLocales = desiredLocales.concat(pair[1].split(","));
3817 +                    } else if (pair[0] == "bundle") {
3818 +                        bundle = pair[1] != "false";
3819 +                    }
3820 +                }
3821 +            };
3822 +
3823 +            (function() {
3824 +                if (typeof Timeline_urlPrefix == "string") {
3825 +                    Timeline.urlPrefix = Timeline_urlPrefix;
3826 +                    if (typeof Timeline_parameters == "string") {
3827 +                        parseURLParameters(Timeline_parameters);
3828 +                    }
3829 +                } else {
3830 +                    var heads = document.documentElement.getElementsByTagName("head");
3831 +                    for (var h = 0; h < heads.length; h++) {
3832 +                        var scripts = heads[h].getElementsByTagName("script");
3833 +                        for (var s = 0; s < scripts.length; s++) {
3834 +                            var url = scripts[s].src;
3835 +                            var i = url.indexOf("timeline-api.js");
3836 +                            if (i >= 0) {
3837 +                                Timeline.urlPrefix = url.substr(0, i);
3838 +                                var q = url.indexOf("?");
3839 +                                if (q > 0) {
3840 +                                    parseURLParameters(url.substr(q + 1));
3841 +                                }
3842 +                                return;
3843 +                            }
3844 +                        }
3845 +                    }
3846 +                    throw new Error("Failed to derive URL prefix for Timeline API code files");
3847 +                }
3848 +            })();
3849 +
3850 +            var includeJavascriptFiles = function(urlPrefix, filenames) {
3851 +                SimileAjax.includeJavascriptFiles(document, urlPrefix, filenames);
3852 +            }
3853 +            var includeCssFiles = function(urlPrefix, filenames) {
3854 +                SimileAjax.includeCssFiles(document, urlPrefix, filenames);
3855 +            }
3856 +
3857 +            /*
3858 +             *  Include non-localized files
3859 +             */
3860 +            if (bundle) {
3861 +                includeJavascriptFiles(Timeline.urlPrefix, [ "timeline-bundle.js" ]);
3862 +                includeCssFiles(Timeline.urlPrefix, [ "timeline-bundle.css" ]);
3863 +            } else {
3864 +                // XXX adim includeJavascriptFiles(Timeline.urlPrefix + "scripts/", javascriptFiles);
3865 +                // XXX adim includeCssFiles(Timeline.urlPrefix + "styles/", cssFiles);
3866 +            }
3867 +
3868 +            /*
3869 +             *  Include localized files
3870 +             */
3871 +            var loadLocale = [];
3872 +            loadLocale[defaultServerLocale] = true;
3873 +
3874 +            var tryExactLocale = function(locale) {
3875 +                for (var l = 0; l < supportedLocales.length; l++) {
3876 +                    if (locale == supportedLocales[l]) {
3877 +                        loadLocale[locale] = true;
3878 +                        return true;
3879 +                    }
3880 +                }
3881 +                return false;
3882 +            }
3883 +            var tryLocale = function(locale) {
3884 +                if (tryExactLocale(locale)) {
3885 +                    return locale;
3886 +                }
3887 +
3888 +                var dash = locale.indexOf("-");
3889 +                if (dash > 0 && tryExactLocale(locale.substr(0, dash))) {
3890 +                    return locale.substr(0, dash);
3891 +                }
3892 +
3893 +                return null;
3894 +            }
3895 +
3896 +            for (var l = 0; l < desiredLocales.length; l++) {
3897 +                tryLocale(desiredLocales[l]);
3898 +            }
3899 +
3900 +            var defaultClientLocale = defaultServerLocale;
3901 +            var defaultClientLocales = ("language" in navigator ? navigator.language : navigator.browserLanguage).split(";");
3902 +            for (var l = 0; l < defaultClientLocales.length; l++) {
3903 +                var locale = tryLocale(defaultClientLocales[l]);
3904 +                if (locale != null) {
3905 +                    defaultClientLocale = locale;
3906 +                    break;
3907 +                }
3908 +            }
3909 +
3910 +            for (var l = 0; l < supportedLocales.length; l++) {
3911 +                var locale = supportedLocales[l];
3912 +                if (loadLocale[locale]) {
3913 +                    // XXX adim includeJavascriptFiles(Timeline.urlPrefix + "scripts/l10n/" + locale + "/", localizedJavascriptFiles);
3914 +                    // XXX adim includeCssFiles(Timeline.urlPrefix + "styles/l10n/" + locale + "/", localizedCssFiles);
3915 +                }
3916 +            }
3917 +
3918 +            if (forceLocale == null) {
3919 +              Timeline.serverLocale = defaultServerLocale;
3920 +              Timeline.clientLocale = defaultClientLocale;
3921 +            } else {
3922 +              Timeline.serverLocale = forceLocale;
3923 +              Timeline.clientLocale = forceLocale;
3924 +            }
3925 +        } catch (e) {
3926 +            alert(e);
3927 +        }
3928 +    };
3929 +
3930 +    /*
3931 +     *  Load SimileAjax if it's not already loaded
3932 +     */
3933 +    if (typeof SimileAjax == "undefined") {
3934 +        window.SimileAjax_onLoad = loadMe;
3935 +
3936 +        var url = useLocalResources ?
3937 +            "http://127.0.0.1:9999/ajax/api/simile-ajax-api.js?bundle=false" :
3938 +            "http://api.simile-widgets.org/ajax/" + simile_ajax_ver + "/simile-ajax-api.js";
3939 +        if (typeof Timeline_ajax_url == "string") {
3940 +           url = Timeline_ajax_url;
3941 +        }
3942 +        var createScriptElement = function() {
3943 +            var script = document.createElement("script");
3944 +            script.type = "text/javascript";
3945 +            script.language = "JavaScript";
3946 +            script.src = url;
3947 +            document.getElementsByTagName("head")[0].appendChild(script);
3948 +        }
3949 +        if (document.body == null) {
3950 +            try {
3951 +                document.write("<script src='" + url + "' type='text/javascript'></script>");
3952 +            } catch (e) {
3953 +                createScriptElement();
3954 +            }
3955 +        } else {
3956 +            createScriptElement();
3957 +        }
3958 +    } else {
3959 +        loadMe();
3960 +    }
3961 +})();
3962 +/*
3963 + *
3964 + * Coding standards:
3965 + *
3966 + * We aim towards Douglas Crockford's Javascript conventions.
3967 + * See:  http://javascript.crockford.com/code.html
3968 + * See also: http://www.crockford.com/javascript/javascript.html
3969 + *
3970 + * That said, this JS code was written before some recent JS
3971 + * support libraries became widely used or available.
3972 + * In particular, the _ character is used to indicate a class function or
3973 + * variable that should be considered private to the class.
3974 + *
3975 + * The code mostly uses accessor methods for getting/setting the private
3976 + * class variables.
3977 + *
3978 + * Over time, we'd like to formalize the convention by using support libraries
3979 + * which enforce privacy in objects.
3980 + *
3981 + * We also want to use jslint:  http://www.jslint.com/
3982 + *
3983 + *
3984 + *
3985 + */
3986 +
3987 +
3988 +
3989 +/*
3990 + *  Timeline VERSION
3991 + *
3992 + */
3993 +// Note: version is also stored in the build.xml file
3994 +Timeline.version = 'pre 2.4.0';  // use format 'pre 1.2.3' for trunk versions
3995 +Timeline.ajax_lib_version = SimileAjax.version;
3996 +Timeline.display_version = Timeline.version + ' (with Ajax lib ' + Timeline.ajax_lib_version + ')';
3997 + // cf method Timeline.writeVersion
3998 +
3999 +/*
4000 + *  Timeline
4001 + *
4002 + */
4003 +Timeline.strings = {}; // localization string tables
4004 +Timeline.HORIZONTAL = 0;
4005 +Timeline.VERTICAL = 1;
4006 +Timeline._defaultTheme = null;
4007 +
4008 +Timeline.getDefaultLocale = function() {
4009 +    return Timeline.clientLocale;
4010 +};
4011 +
4012 +Timeline.create = function(elmt, bandInfos, orientation, unit) {
4013 +    if (Timeline.timelines == null) {
4014 +        Timeline.timelines = [];
4015 +        // Timeline.timelines array can have null members--Timelines that
4016 +        // once existed on the page, but were later disposed of.
4017 +    }
4018 +
4019 +    var timelineID = Timeline.timelines.length;
4020 +    Timeline.timelines[timelineID] = null; // placeholder until we have the object
4021 +    var new_tl = new Timeline._Impl(elmt, bandInfos, orientation, unit,
4022 +      timelineID);
4023 +    Timeline.timelines[timelineID] = new_tl;
4024 +    return new_tl;
4025 +};
4026 +
4027 +Timeline.createBandInfo = function(params) {
4028 +    var theme = ("theme" in params) ? params.theme : Timeline.getDefaultTheme();
4029 +
4030 +    var eventSource = ("eventSource" in params) ? params.eventSource : null;
4031 +
4032 +    var etherParams = {
4033 +        interval:           SimileAjax.DateTime.gregorianUnitLengths[params.intervalUnit],
4034 +        pixelsPerInterval: params.intervalPixels,
4035 +	theme: theme
4036 +    };
4037 +    if ('startsOn' in params || 'endsOn' in params) {
4038 +	if ('startsOn' in params) {
4039 +	    etherParams.startsOn = params.startsOn;
4040 +	}
4041 +	if ('endsOn' in params) {
4042 +	    etherParams.endsOn = params.endsOn;
4043 +	}
4044 +    } else {
4045 +	etherParams.centersOn = ("date" in params) ? params.date : new Date();
4046 +    }
4047 +    var ether = new Timeline.LinearEther(etherParams);
4048 +
4049 +    var etherPainter = new Timeline.GregorianEtherPainter({
4050 +        unit:       params.intervalUnit,
4051 +        multiple:   ("multiple" in params) ? params.multiple : 1,
4052 +        theme:      theme,
4053 +        align:      ("align" in params) ? params.align : undefined
4054 +    });
4055 +
4056 +    var eventPainterParams = {
4057 +        showText:   ("showEventText" in params) ? params.showEventText : true,
4058 +        theme:      theme
4059 +    };
4060 +    // pass in custom parameters for the event painter
4061 +    if ("eventPainterParams" in params) {
4062 +        for (var prop in params.eventPainterParams) {
4063 +            eventPainterParams[prop] = params.eventPainterParams[prop];
4064 +        }
4065 +    }
4066 +
4067 +    if ("trackHeight" in params) {
4068 +        eventPainterParams.trackHeight = params.trackHeight;
4069 +    }
4070 +    if ("trackGap" in params) {
4071 +        eventPainterParams.trackGap = params.trackGap;
4072 +    }
4073 +
4074 +    var layout = ("overview" in params && params.overview) ? "overview" : ("layout" in params ? params.layout : "original");
4075 +    var eventPainter;
4076 +    if ("eventPainter" in params) {
4077 +        eventPainter = new params.eventPainter(eventPainterParams);
4078 +    } else {
4079 +        switch (layout) {
4080 +            case "overview" :
4081 +                eventPainter = new Timeline.OverviewEventPainter(eventPainterParams);
4082 +                break;
4083 +            case "detailed" :
4084 +                eventPainter = new Timeline.DetailedEventPainter(eventPainterParams);
4085 +                break;
4086 +            default:
4087 +                eventPainter = new Timeline.OriginalEventPainter(eventPainterParams);
4088 +        }
4089 +    }
4090 +
4091 +    return {
4092 +        width:          params.width,
4093 +        eventSource:    eventSource,
4094 +        timeZone:       ("timeZone" in params) ? params.timeZone : 0,
4095 +        ether:          ether,
4096 +        etherPainter:   etherPainter,
4097 +        eventPainter:   eventPainter,
4098 +        theme:          theme,
4099 +        zoomIndex:      ("zoomIndex" in params) ? params.zoomIndex : 0,
4100 +        zoomSteps:      ("zoomSteps" in params) ? params.zoomSteps : null
4101 +    };
4102 +};
4103 +
4104 +Timeline.createHotZoneBandInfo = function(params) {
4105 +    var theme = ("theme" in params) ? params.theme : Timeline.getDefaultTheme();
4106 +
4107 +    var eventSource = ("eventSource" in params) ? params.eventSource : null;
4108 +
4109 +    var ether = new Timeline.HotZoneEther({
4110 +        centersOn:          ("date" in params) ? params.date : new Date(),
4111 +        interval:           SimileAjax.DateTime.gregorianUnitLengths[params.intervalUnit],
4112 +        pixelsPerInterval:  params.intervalPixels,
4113 +        zones:              params.zones,
4114 +        theme:              theme
4115 +    });
4116 +
4117 +    var etherPainter = new Timeline.HotZoneGregorianEtherPainter({
4118 +        unit:       params.intervalUnit,
4119 +        zones:      params.zones,
4120 +        theme:      theme,
4121 +        align:      ("align" in params) ? params.align : undefined
4122 +    });
4123 +
4124 +    var eventPainterParams = {
4125 +        showText:   ("showEventText" in params) ? params.showEventText : true,
4126 +        theme:      theme
4127 +    };
4128 +    // pass in custom parameters for the event painter
4129 +    if ("eventPainterParams" in params) {
4130 +        for (var prop in params.eventPainterParams) {
4131 +            eventPainterParams[prop] = params.eventPainterParams[prop];
4132 +        }
4133 +    }
4134 +    if ("trackHeight" in params) {
4135 +        eventPainterParams.trackHeight = params.trackHeight;
4136 +    }
4137 +    if ("trackGap" in params) {
4138 +        eventPainterParams.trackGap = params.trackGap;
4139 +    }
4140 +
4141 +    var layout = ("overview" in params && params.overview) ? "overview" : ("layout" in params ? params.layout : "original");
4142 +    var eventPainter;
4143 +    if ("eventPainter" in params) {
4144 +        eventPainter = new params.eventPainter(eventPainterParams);
4145 +    } else {
4146 +        switch (layout) {
4147 +            case "overview" :
4148 +                eventPainter = new Timeline.OverviewEventPainter(eventPainterParams);
4149 +                break;
4150 +            case "detailed" :
4151 +                eventPainter = new Timeline.DetailedEventPainter(eventPainterParams);
4152 +                break;
4153 +            default:
4154 +                eventPainter = new Timeline.OriginalEventPainter(eventPainterParams);
4155 +        }
4156 +    }
4157 +    return {
4158 +        width:          params.width,
4159 +        eventSource:    eventSource,
4160 +        timeZone:       ("timeZone" in params) ? params.timeZone : 0,
4161 +        ether:          ether,
4162 +        etherPainter:   etherPainter,
4163 +        eventPainter:   eventPainter,
4164 +        theme:          theme,
4165 +        zoomIndex:      ("zoomIndex" in params) ? params.zoomIndex : 0,
4166 +        zoomSteps:      ("zoomSteps" in params) ? params.zoomSteps : null
4167 +    };
4168 +};
4169 +
4170 +Timeline.getDefaultTheme = function() {
4171 +    if (Timeline._defaultTheme == null) {
4172 +        Timeline._defaultTheme = Timeline.ClassicTheme.create(Timeline.getDefaultLocale());
4173 +    }
4174 +    return Timeline._defaultTheme;
4175 +};
4176 +
4177 +Timeline.setDefaultTheme = function(theme) {
4178 +    Timeline._defaultTheme = theme;
4179 +};
4180 +
4181 +Timeline.loadXML = function(url, f) {
4182 +    var fError = function(statusText, status, xmlhttp) {
4183 +        alert("Failed to load data xml from " + url + "\n" + statusText);
4184 +    };
4185 +    var fDone = function(xmlhttp) {
4186 +        var xml = xmlhttp.responseXML;
4187 +        if (!xml.documentElement && xmlhttp.responseStream) {
4188 +            xml.load(xmlhttp.responseStream);
4189 +        }
4190 +        f(xml, url);
4191 +    };
4192 +    SimileAjax.XmlHttp.get(url, fError, fDone);
4193 +};
4194 +
4195 +
4196 +Timeline.loadJSON = function(url, f) {
4197 +    var fError = function(statusText, status, xmlhttp) {
4198 +        alert("Failed to load json data from " + url + "\n" + statusText);
4199 +    };
4200 +    var fDone = function(xmlhttp) {
4201 +        f(eval('(' + xmlhttp.responseText + ')'), url);
4202 +    };
4203 +    SimileAjax.XmlHttp.get(url, fError, fDone);
4204 +};
4205 +
4206 +Timeline.getTimelineFromID = function(timelineID) {
4207 +    return Timeline.timelines[timelineID];
4208 +};
4209 +
4210 +// Write the current Timeline version as the contents of element with id el_id
4211 +Timeline.writeVersion = function(el_id) {
4212 +  document.getElementById(el_id).innerHTML = this.display_version;
4213 +};
4214 +
4215 +
4216 +
4217 +/*
4218 + *  Timeline Implementation object
4219 + *
4220 + */
4221 +Timeline._Impl = function(elmt, bandInfos, orientation, unit, timelineID) {
4222 +    SimileAjax.WindowManager.initialize();
4223 +
4224 +    this._containerDiv = elmt;
4225 +
4226 +    this._bandInfos = bandInfos;
4227 +    this._orientation = orientation == null ? Timeline.HORIZONTAL : orientation;
4228 +    this._unit = (unit != null) ? unit : SimileAjax.NativeDateUnit;
4229 +    this._starting = true; // is the Timeline being created? Used by autoWidth
4230 +                           // functions
4231 +    this._autoResizing = false;
4232 +
4233 +    // autoWidth is a "public" property of the Timeline object
4234 +    this.autoWidth = bandInfos && bandInfos[0] && bandInfos[0].theme &&
4235 +                     bandInfos[0].theme.autoWidth;
4236 +    this.autoWidthAnimationTime = bandInfos && bandInfos[0] && bandInfos[0].theme &&
4237 +                     bandInfos[0].theme.autoWidthAnimationTime;
4238 +    this.timelineID = timelineID; // also public attribute
4239 +    this.timeline_start = bandInfos && bandInfos[0] && bandInfos[0].theme &&
4240 +                     bandInfos[0].theme.timeline_start;
4241 +    this.timeline_stop  = bandInfos && bandInfos[0] && bandInfos[0].theme &&
4242 +                     bandInfos[0].theme.timeline_stop;
4243 +    this.timeline_at_start = false; // already at start or stop? Then won't
4244 +    this.timeline_at_stop = false;  // try to move further in the wrong direction
4245 +
4246 +    this._initialize();
4247 +};
4248 +
4249 +//
4250 +// Public functions used by client sw
4251 +//
4252 +Timeline._Impl.prototype.dispose = function() {
4253 +    for (var i = 0; i < this._bands.length; i++) {
4254 +        this._bands[i].dispose();
4255 +    }
4256 +    this._bands = null;
4257 +    this._bandInfos = null;
4258 +    this._containerDiv.innerHTML = "";
4259 +    // remove from array of Timelines
4260 +    Timeline.timelines[this.timelineID] = null;
4261 +};
4262 +
4263 +Timeline._Impl.prototype.getBandCount = function() {
4264 +    return this._bands.length;
4265 +};
4266 +
4267 +Timeline._Impl.prototype.getBand = function(index) {
4268 +    return this._bands[index];
4269 +};
4270 +
4271 +Timeline._Impl.prototype.finishedEventLoading = function() {
4272 +    // Called by client after events have been loaded into Timeline
4273 +    // Only used if the client has set autoWidth
4274 +    // Sets width to Timeline's requested amount and will shrink down the div if
4275 +    // need be.
4276 +    this._autoWidthCheck(true);
4277 +    this._starting = false;
4278 +};
4279 +
4280 +Timeline._Impl.prototype.layout = function() {
4281 +    // called by client when browser is resized
4282 +    this._autoWidthCheck(true);
4283 +    this._distributeWidths();
4284 +};
4285 +
4286 +Timeline._Impl.prototype.paint = function() {
4287 +    for (var i = 0; i < this._bands.length; i++) {
4288 +        this._bands[i].paint();
4289 +    }
4290 +};
4291 +
4292 +Timeline._Impl.prototype.getDocument = function() {
4293 +    return this._containerDiv.ownerDocument;
4294 +};
4295 +
4296 +Timeline._Impl.prototype.addDiv = function(div) {
4297 +    this._containerDiv.appendChild(div);
4298 +};
4299 +
4300 +Timeline._Impl.prototype.removeDiv = function(div) {
4301 +    this._containerDiv.removeChild(div);
4302 +};
4303 +
4304 +Timeline._Impl.prototype.isHorizontal = function() {
4305 +    return this._orientation == Timeline.HORIZONTAL;
4306 +};
4307 +
4308 +Timeline._Impl.prototype.isVertical = function() {
4309 +    return this._orientation == Timeline.VERTICAL;
4310 +};
4311 +
4312 +Timeline._Impl.prototype.getPixelLength = function() {
4313 +    return this._orientation == Timeline.HORIZONTAL ?
4314 +        this._containerDiv.offsetWidth : this._containerDiv.offsetHeight;
4315 +};
4316 +
4317 +Timeline._Impl.prototype.getPixelWidth = function() {
4318 +    return this._orientation == Timeline.VERTICAL ?
4319 +        this._containerDiv.offsetWidth : this._containerDiv.offsetHeight;
4320 +};
4321 +
4322 +Timeline._Impl.prototype.getUnit = function() {
4323 +    return this._unit;
4324 +};
4325 +
4326 +Timeline._Impl.prototype.getWidthStyle = function() {
4327 +    // which element.style attribute should be changed to affect Timeline's "width"
4328 +    return this._orientation == Timeline.HORIZONTAL ? 'height' : 'width';
4329 +};
4330 +
4331 +Timeline._Impl.prototype.loadXML = function(url, f) {
4332 +    var tl = this;
4333 +
4334 +
4335 +    var fError = function(statusText, status, xmlhttp) {
4336 +        alert("Failed to load data xml from " + url + "\n" + statusText);
4337 +        tl.hideLoadingMessage();
4338 +    };
4339 +    var fDone = function(xmlhttp) {
4340 +        try {
4341 +            var xml = xmlhttp.responseXML;
4342 +            if (!xml.documentElement && xmlhttp.responseStream) {
4343 +                xml.load(xmlhttp.responseStream);
4344 +            }
4345 +            f(xml, url);
4346 +        } finally {
4347 +            tl.hideLoadingMessage();
4348 +        }
4349 +    };
4350 +
4351 +    this.showLoadingMessage();
4352 +    window.setTimeout(function() { SimileAjax.XmlHttp.get(url, fError, fDone); }, 0);
4353 +};
4354 +
4355 +Timeline._Impl.prototype.loadJSON = function(url, f) {
4356 +    var tl = this;
4357 +
4358 +    var fError = function(statusText, status, xmlhttp) {
4359 +        alert("Failed to load json data from " + url + "\n" + statusText);
4360 +        tl.hideLoadingMessage();
4361 +    };
4362 +    var fDone = function(xmlhttp) {
4363 +        try {
4364 +            f(eval('(' + xmlhttp.responseText + ')'), url);
4365 +        } finally {
4366 +            tl.hideLoadingMessage();
4367 +        }
4368 +    };
4369 +
4370 +    this.showLoadingMessage();
4371 +    window.setTimeout(function() { SimileAjax.XmlHttp.get(url, fError, fDone); }, 0);
4372 +};
4373 +
4374 +
4375 +//
4376 +// Private functions used by Timeline object functions
4377 +//
4378 +
4379 +Timeline._Impl.prototype._autoWidthScrollListener = function(band) {
4380 +    band.getTimeline()._autoWidthCheck(false);
4381 +};
4382 +
4383 +// called to re-calculate auto width and adjust the overall Timeline div if needed
4384 +Timeline._Impl.prototype._autoWidthCheck = function(okToShrink) {
4385 +    var timeline = this; // this Timeline
4386 +    var immediateChange = timeline._starting;
4387 +    var newWidth = 0;
4388 +
4389 +    function changeTimelineWidth() {
4390 +        var widthStyle = timeline.getWidthStyle();
4391 +        if (immediateChange) {
4392 +            timeline._containerDiv.style[widthStyle] = newWidth + 'px';
4393 +        } else {
4394 +        	  // animate change
4395 +        	  timeline._autoResizing = true;
4396 +        	  var animateParam ={};
4397 +        	  animateParam[widthStyle] = newWidth + 'px';
4398 +
4399 +        	  SimileAjax.jQuery(timeline._containerDiv).animate(
4400 +        	      animateParam, timeline.autoWidthAnimationTime,
4401 +        	      'linear', function(){timeline._autoResizing = false;});
4402 +        }
4403 +    }
4404 +
4405 +    function checkTimelineWidth() {
4406 +        var targetWidth = 0; // the new desired width
4407 +        var currentWidth = timeline.getPixelWidth();
4408 +
4409 +        if (timeline._autoResizing) {
4410 +        	return; // early return
4411 +        }
4412 +
4413 +        // compute targetWidth
4414 +        for (var i = 0; i < timeline._bands.length; i++) {
4415 +            timeline._bands[i].checkAutoWidth();
4416 +            targetWidth += timeline._bandInfos[i].width;
4417 +        }
4418 +
4419 +        if (targetWidth > currentWidth || okToShrink) {
4420 +            // yes, let's change the size
4421 +            newWidth = targetWidth;
4422 +            changeTimelineWidth();
4423 +            timeline._distributeWidths();
4424 +        }
4425 +    }
4426 +
4427 +    // function's mainline
4428 +    if (!timeline.autoWidth) {
4429 +        return; // early return
4430 +    }
4431 +
4432 +    checkTimelineWidth();
4433 +};
4434 +
4435 +Timeline._Impl.prototype._initialize = function() {
4436 +    var containerDiv = this._containerDiv;
4437 +    var doc = containerDiv.ownerDocument;
4438 +
4439 +    containerDiv.className =
4440 +        containerDiv.className.split(" ").concat("timeline-container").join(" ");
4441 +
4442 +	/*
4443 +	 * Set css-class on container div that will define orientation
4444 +	 */
4445 +	var orientation = (this.isHorizontal()) ? 'horizontal' : 'vertical'
4446 +	containerDiv.className +=' timeline-'+orientation;
4447 +
4448 +
4449 +    while (containerDiv.firstChild) {
4450 +        containerDiv.removeChild(containerDiv.firstChild);
4451 +    }
4452 +
4453 +    /*
4454 +     *  inserting copyright and link to simile
4455 +     */
4456 +    var elmtCopyright = SimileAjax.Graphics.createTranslucentImage(Timeline.urlPrefix + (this.isHorizontal() ? "images/copyright-vertical.png" : "images/copyright.png"));
4457 +    elmtCopyright.className = "timeline-copyright";
4458 +    elmtCopyright.title = "Timeline copyright SIMILE - www.code.google.com/p/simile-widgets/";
4459 +    SimileAjax.DOM.registerEvent(elmtCopyright, "click", function() { window.location = "http://www.simile-widgets.org/"; });
4460 +    containerDiv.appendChild(elmtCopyright);
4461 +
4462 +    /*
4463 +     *  creating bands
4464 +     */
4465 +    this._bands = [];
4466 +    for (var i = 0; i < this._bandInfos.length; i++) {
4467 +        var bandInfo = this._bandInfos[i];
4468 +        var bandClass = bandInfo.bandClass || Timeline._Band;
4469 +        var band = new bandClass(this, this._bandInfos[i], i);
4470 +        this._bands.push(band);
4471 +    }
4472 +    this._distributeWidths();
4473 +
4474 +    /*
4475 +     *  sync'ing bands
4476 +     */
4477 +    for (var i = 0; i < this._bandInfos.length; i++) {
4478 +        var bandInfo = this._bandInfos[i];
4479 +        if ("syncWith" in bandInfo) {
4480 +            this._bands[i].setSyncWithBand(
4481 +                this._bands[bandInfo.syncWith],
4482 +                ("highlight" in bandInfo) ? bandInfo.highlight : false
4483 +            );
4484 +        }
4485 +    }
4486 +
4487 +
4488 +    if (this.autoWidth) {
4489 +        for (var i = 0; i < this._bands.length; i++) {
4490 +            this._bands[i].addOnScrollListener(this._autoWidthScrollListener);
4491 +        }
4492 +    }
4493 +
4494 +
4495 +    /*
4496 +     *  creating loading UI
4497 +     */
4498 +    var message = SimileAjax.Graphics.createMessageBubble(doc);
4499 +    message.containerDiv.className = "timeline-message-container";
4500 +    containerDiv.appendChild(message.containerDiv);
4501 +
4502 +    message.contentDiv.className = "timeline-message";
4503 +    message.contentDiv.innerHTML = "<img src='" + Timeline.urlPrefix + "images/progress-running.gif' /> Loading...";
4504 +
4505 +    this.showLoadingMessage = function() { message.containerDiv.style.display = "block"; };
4506 +    this.hideLoadingMessage = function() { message.containerDiv.style.display = "none"; };
4507 +};
4508 +
4509 +Timeline._Impl.prototype._distributeWidths = function() {
4510 +    var length = this.getPixelLength();
4511 +    var width = this.getPixelWidth();
4512 +    var cumulativeWidth = 0;
4513 +
4514 +    for (var i = 0; i < this._bands.length; i++) {
4515 +        var band = this._bands[i];
4516 +        var bandInfos = this._bandInfos[i];
4517 +        var widthString = bandInfos.width;
4518 +        var bandWidth;
4519 +
4520 +        if (typeof widthString == 'string') {
4521 +          var x =  widthString.indexOf("%");
4522 +          if (x > 0) {
4523 +              var percent = parseInt(widthString.substr(0, x));
4524 +              bandWidth = Math.round(percent * width / 100);
4525 +          } else {
4526 +              bandWidth = parseInt(widthString);
4527 +          }
4528 +        } else {
4529 +        	// was given an integer
4530 +        	bandWidth = widthString;
4531 +        }
4532 +
4533 +        band.setBandShiftAndWidth(cumulativeWidth, bandWidth);
4534 +        band.setViewLength(length);
4535 +
4536 +        cumulativeWidth += bandWidth;
4537 +    }
4538 +};
4539 +
4540 +Timeline._Impl.prototype.shiftOK = function(index, shift) {
4541 +    // Returns true if the proposed shift is ok
4542 +    //
4543 +    // Positive shift means going back in time
4544 +    var going_back = shift > 0,
4545 +        going_forward = shift < 0;
4546 +
4547 +    // Is there an edge?
4548 +    if ((going_back    && this.timeline_start == null) ||
4549 +        (going_forward && this.timeline_stop  == null) ||
4550 +        (shift == 0)) {
4551 +        return (true);  // early return
4552 +    }
4553 +
4554 +    // If any of the bands has noted that it is changing the others,
4555 +    // then this shift is a secondary shift in reaction to the real shift,
4556 +    // which already happened. In such cases, ignore it. (The issue is
4557 +    // that a positive original shift can cause a negative secondary shift,
4558 +    // as the bands adjust.)
4559 +    var secondary_shift = false;
4560 +    for (var i = 0; i < this._bands.length && !secondary_shift; i++) {
4561 +       secondary_shift = this._bands[i].busy();
4562 +    }
4563 +    if (secondary_shift) {
4564 +        return(true); // early return
4565 +    }
4566 +
4567 +    // If we are already at an edge, then don't even think about going any further
4568 +    if ((going_back    && this.timeline_at_start) ||
4569 +        (going_forward && this.timeline_at_stop)) {
4570 +        return (false);  // early return
4571 +    }
4572 +
4573 +    // Need to check all the bands
4574 +    var ok = false; // return value
4575 +    // If any of the bands will be or are showing an ok date, then let the shift proceed.
4576 +    for (var i = 0; i < this._bands.length && !ok; i++) {
4577 +       var band = this._bands[i];
4578 +       if (going_back) {
4579 +           ok = (i == index ? band.getMinVisibleDateAfterDelta(shift) : band.getMinVisibleDate())
4580 +                >= this.timeline_start;
4581 +       } else {
4582 +           ok = (i == index ? band.getMaxVisibleDateAfterDelta(shift) : band.getMaxVisibleDate())
4583 +                <= this.timeline_stop;
4584 +       }
4585 +    }
4586 +
4587 +    // process results
4588 +    if (going_back) {
4589 +       this.timeline_at_start = !ok;
4590 +       this.timeline_at_stop = false;
4591 +    } else {
4592 +       this.timeline_at_stop = !ok;
4593 +       this.timeline_at_start = false;
4594 +    }
4595 +    // This is where you could have an effect once per hitting an
4596 +    // edge of the Timeline. Eg jitter the Timeline
4597 +    //if (!ok) {
4598 +        //alert(going_back ? "At beginning" : "At end");
4599 +    //}
4600 +    return (ok);
4601 +};
4602 +
4603 +Timeline._Impl.prototype.zoom = function (zoomIn, x, y, target) {
4604 +  var matcher = new RegExp("^timeline-band-([0-9]+)$");
4605 +  var bandIndex = null;
4606 +
4607 +  var result = matcher.exec(target.id);
4608 +  if (result) {
4609 +    bandIndex = parseInt(result[1]);
4610 +  }
4611 +
4612 +  if (bandIndex != null) {
4613 +    this._bands[bandIndex].zoom(zoomIn, x, y, target);
4614 +  }
4615 +
4616 +  this.paint();
4617 +};
4618 +
4619 +/*
4620 + *
4621 + * Coding standards:
4622 + *
4623 + * We aim towards Douglas Crockford's Javascript conventions.
4624 + * See:  http://javascript.crockford.com/code.html
4625 + * See also: http://www.crockford.com/javascript/javascript.html
4626 + *
4627 + * That said, this JS code was written before some recent JS
4628 + * support libraries became widely used or available.
4629 + * In particular, the _ character is used to indicate a class function or
4630 + * variable that should be considered private to the class.
4631 + *
4632 + * The code mostly uses accessor methods for getting/setting the private
4633 + * class variables.
4634 + *
4635 + * Over time, we'd like to formalize the convention by using support libraries
4636 + * which enforce privacy in objects.
4637 + *
4638 + * We also want to use jslint:  http://www.jslint.com/
4639 + *
4640 + *
4641 + *
4642 + */
4643 +
4644 +
4645 +
4646 +/*
4647 + *  Band
4648 + *
4649 + */
4650 +Timeline._Band = function(timeline, bandInfo, index) {
4651 +    // hack for easier subclassing
4652 +    if (timeline !== undefined) {
4653 +        this.initialize(timeline, bandInfo, index);
4654 +    }
4655 +};
4656 +
4657 +Timeline._Band.prototype.initialize = function(timeline, bandInfo, index) {
4658 +    // Set up the band's object
4659 +
4660 +    // Munge params: If autoWidth is on for the Timeline, then ensure that
4661 +    // bandInfo.width is an integer
4662 +    if (timeline.autoWidth && typeof bandInfo.width == 'string') {
4663 +        bandInfo.width = bandInfo.width.indexOf("%") > -1 ? 0 : parseInt(bandInfo.width);
4664 +    }
4665 +
4666 +    this._timeline = timeline;
4667 +    this._bandInfo = bandInfo;
4668 +
4669 +    this._index = index;
4670 +
4671 +    this._locale = ("locale" in bandInfo) ? bandInfo.locale : Timeline.getDefaultLocale();
4672 +    this._timeZone = ("timeZone" in bandInfo) ? bandInfo.timeZone : 0;
4673 +    this._labeller = ("labeller" in bandInfo) ? bandInfo.labeller :
4674 +        (("createLabeller" in timeline.getUnit()) ?
4675 +            timeline.getUnit().createLabeller(this._locale, this._timeZone) :
4676 +            new Timeline.GregorianDateLabeller(this._locale, this._timeZone));
4677 +    this._theme = bandInfo.theme;
4678 +    this._zoomIndex = ("zoomIndex" in bandInfo) ? bandInfo.zoomIndex : 0;
4679 +    this._zoomSteps = ("zoomSteps" in bandInfo) ? bandInfo.zoomSteps : null;
4680 +
4681 +    this._dragging = false;
4682 +    this._changing = false;
4683 +    this._originalScrollSpeed = 5; // pixels
4684 +    this._scrollSpeed = this._originalScrollSpeed;
4685 +    this._viewOrthogonalOffset= 0; // vertical offset if the timeline is horizontal, and vice versa
4686 +    this._onScrollListeners = [];
4687 +
4688 +    var b = this;
4689 +    this._syncWithBand = null;
4690 +    this._syncWithBandHandler = function(band) {
4691 +        b._onHighlightBandScroll();
4692 +    };
4693 +    this._selectorListener = function(band) {
4694 +        b._onHighlightBandScroll();
4695 +    };
4696 +
4697 +    /*
4698 +     *  Install a textbox to capture keyboard events
4699 +     */
4700 +    var inputDiv = this._timeline.getDocument().createElement("div");
4701 +    inputDiv.className = "timeline-band-input";
4702 +    this._timeline.addDiv(inputDiv);
4703 +
4704 +    this._keyboardInput = document.createElement("input");
4705 +    this._keyboardInput.type = "text";
4706 +    inputDiv.appendChild(this._keyboardInput);
4707 +    SimileAjax.DOM.registerEventWithObject(this._keyboardInput, "keydown", this, "_onKeyDown");
4708 +    SimileAjax.DOM.registerEventWithObject(this._keyboardInput, "keyup", this, "_onKeyUp");
4709 +
4710 +    /*
4711 +     *  The band's outer most div that slides with respect to the timeline's div
4712 +     */
4713 +    this._div = this._timeline.getDocument().createElement("div");
4714 +    this._div.id = "timeline-band-" + index;
4715 +    this._div.className = "timeline-band timeline-band-" + index;
4716 +    this._timeline.addDiv(this._div);
4717 +
4718 +    SimileAjax.DOM.registerEventWithObject(this._div, "mousedown", this, "_onMouseDown");
4719 +    SimileAjax.DOM.registerEventWithObject(this._div, "mousemove", this, "_onMouseMove");
4720 +    SimileAjax.DOM.registerEventWithObject(this._div, "mouseup", this, "_onMouseUp");
4721 +    SimileAjax.DOM.registerEventWithObject(this._div, "mouseout", this, "_onMouseOut");
4722 +    SimileAjax.DOM.registerEventWithObject(this._div, "dblclick", this, "_onDblClick");
4723 +
4724 +    var mouseWheel = this._theme!= null ? this._theme.mouseWheel : 'scroll'; // theme is not always defined
4725 +    if (mouseWheel === 'zoom' || mouseWheel === 'scroll' || this._zoomSteps) {
4726 +        // capture mouse scroll
4727 +        if (SimileAjax.Platform.browser.isFirefox) {
4728 +            SimileAjax.DOM.registerEventWithObject(this._div, "DOMMouseScroll", this, "_onMouseScroll");
4729 +        } else {
4730 +            SimileAjax.DOM.registerEventWithObject(this._div, "mousewheel", this, "_onMouseScroll");
4731 +        }
4732 +    }
4733 +
4734 +    /*
4735 +     *  The inner div that contains layers
4736 +     */
4737 +    this._innerDiv = this._timeline.getDocument().createElement("div");
4738 +    this._innerDiv.className = "timeline-band-inner";
4739 +    this._div.appendChild(this._innerDiv);
4740 +
4741 +    /*
4742 +     *  Initialize parts of the band
4743 +     */
4744 +    this._ether = bandInfo.ether;
4745 +    bandInfo.ether.initialize(this, timeline);
4746 +
4747 +    this._etherPainter = bandInfo.etherPainter;
4748 +    bandInfo.etherPainter.initialize(this, timeline);
4749 +
4750 +    this._eventSource = bandInfo.eventSource;
4751 +    if (this._eventSource) {
4752 +        this._eventListener = {
4753 +            onAddMany: function() { b._onAddMany(); },
4754 +            onClear:   function() { b._onClear(); }
4755 +        }
4756 +        this._eventSource.addListener(this._eventListener);
4757 +    }
4758 +
4759 +    this._eventPainter = bandInfo.eventPainter;
4760 +    this._eventTracksNeeded = 0;   // set by painter via updateEventTrackInfo
4761 +    this._eventTrackIncrement = 0;
4762 +    bandInfo.eventPainter.initialize(this, timeline);
4763 +
4764 +    this._decorators = ("decorators" in bandInfo) ? bandInfo.decorators : [];
4765 +    for (var i = 0; i < this._decorators.length; i++) {
4766 +        this._decorators[i].initialize(this, timeline);
4767 +    }
4768 +};
4769 +
4770 +Timeline._Band.SCROLL_MULTIPLES = 5;
4771 +
4772 +Timeline._Band.prototype.dispose = function() {
4773 +    this.closeBubble();
4774 +
4775 +    if (this._eventSource) {
4776 +        this._eventSource.removeListener(this._eventListener);
4777 +        this._eventListener = null;
4778 +        this._eventSource = null;
4779 +    }
4780 +
4781 +    this._timeline = null;
4782 +    this._bandInfo = null;
4783 +
4784 +    this._labeller = null;
4785 +    this._ether = null;
4786 +    this._etherPainter = null;
4787 +    this._eventPainter = null;
4788 +    this._decorators = null;
4789 +
4790 +    this._onScrollListeners = null;
4791 +    this._syncWithBandHandler = null;
4792 +    this._selectorListener = null;
4793 +
4794 +    this._div = null;
4795 +    this._innerDiv = null;
4796 +    this._keyboardInput = null;
4797 +};
4798 +
4799 +Timeline._Band.prototype.addOnScrollListener = function(listener) {
4800 +    this._onScrollListeners.push(listener);
4801 +};
4802 +
4803 +Timeline._Band.prototype.removeOnScrollListener = function(listener) {
4804 +    for (var i = 0; i < this._onScrollListeners.length; i++) {
4805 +        if (this._onScrollListeners[i] == listener) {
4806 +            this._onScrollListeners.splice(i, 1);
4807 +            break;
4808 +        }
4809 +    }
4810 +};
4811 +
4812 +Timeline._Band.prototype.setSyncWithBand = function(band, highlight) {
4813 +    if (this._syncWithBand) {
4814 +        this._syncWithBand.removeOnScrollListener(this._syncWithBandHandler);
4815 +    }
4816 +
4817 +    this._syncWithBand = band;
4818 +    this._syncWithBand.addOnScrollListener(this._syncWithBandHandler);
4819 +    this._highlight = highlight;
4820 +    this._positionHighlight();
4821 +};
4822 +
4823 +Timeline._Band.prototype.getLocale = function() {
4824 +    return this._locale;
4825 +};
4826 +
4827 +Timeline._Band.prototype.getTimeZone = function() {
4828 +    return this._timeZone;
4829 +};
4830 +
4831 +Timeline._Band.prototype.getLabeller = function() {
4832 +    return this._labeller;
4833 +};
4834 +
4835 +Timeline._Band.prototype.getIndex = function() {
4836 +    return this._index;
4837 +};
4838 +
4839 +Timeline._Band.prototype.getEther = function() {
4840 +    return this._ether;
4841 +};
4842 +
4843 +Timeline._Band.prototype.getEtherPainter = function() {
4844 +    return this._etherPainter;
4845 +};
4846 +
4847 +Timeline._Band.prototype.getEventSource = function() {
4848 +    return this._eventSource;
4849 +};
4850 +
4851 +Timeline._Band.prototype.getEventPainter = function() {
4852 +    return this._eventPainter;
4853 +};
4854 +
4855 +Timeline._Band.prototype.getTimeline = function() {
4856 +    return this._timeline;
4857 +};
4858 +
4859 +// Autowidth support
4860 +Timeline._Band.prototype.updateEventTrackInfo = function(tracks, increment) {
4861 +    this._eventTrackIncrement = increment; // doesn't vary for a specific band
4862 +
4863 +    if (tracks > this._eventTracksNeeded) {
4864 +        this._eventTracksNeeded = tracks;
4865 +    }
4866 +};
4867 +
4868 +// Autowidth support
4869 +Timeline._Band.prototype.checkAutoWidth = function() {
4870 +    // if a new (larger) width is needed by the band
4871 +    // then: a) updates the band's bandInfo.width
4872 +    //
4873 +    // desiredWidth for the band is
4874 +    //   (number of tracks + margin) * track increment
4875 +    if (! this._timeline.autoWidth) {
4876 +      return; // early return
4877 +    }
4878 +
4879 +    var overviewBand = this._eventPainter.getType() == 'overview';
4880 +    var margin = overviewBand ?
4881 +       this._theme.event.overviewTrack.autoWidthMargin :
4882 +       this._theme.event.track.autoWidthMargin;
4883 +    var desiredWidth = Math.ceil((this._eventTracksNeeded + margin) *
4884 +                       this._eventTrackIncrement);
4885 +    // add offset amount (additional margin)
4886 +    desiredWidth += overviewBand ? this._theme.event.overviewTrack.offset :
4887 +                                   this._theme.event.track.offset;
4888 +    var bandInfo = this._bandInfo;
4889 +
4890 +    if (desiredWidth != bandInfo.width) {
4891 +        bandInfo.width = desiredWidth;
4892 +    }
4893 +};
4894 +
4895 +Timeline._Band.prototype.layout = function() {
4896 +    this.paint();
4897 +};
4898 +
4899 +Timeline._Band.prototype.paint = function() {
4900 +    this._etherPainter.paint();
4901 +    this._paintDecorators();
4902 +    this._paintEvents();
4903 +};
4904 +
4905 +Timeline._Band.prototype.softLayout = function() {
4906 +    this.softPaint();
4907 +};
4908 +
4909 +Timeline._Band.prototype.softPaint = function() {
4910 +    this._etherPainter.softPaint();
4911 +    this._softPaintDecorators();
4912 +    this._softPaintEvents();
4913 +};
4914 +
4915 +Timeline._Band.prototype.setBandShiftAndWidth = function(shift, width) {
4916 +    var inputDiv = this._keyboardInput.parentNode;
4917 +    var middle = shift + Math.floor(width / 2);
4918 +    if (this._timeline.isHorizontal()) {
4919 +        this._div.style.top = shift + "px";
4920 +        this._div.style.height = width + "px";
4921 +
4922 +        inputDiv.style.top = middle + "px";
4923 +        inputDiv.style.left = "-1em";
4924 +    } else {
4925 +        this._div.style.left = shift + "px";
4926 +        this._div.style.width = width + "px";
4927 +
4928 +        inputDiv.style.left = middle + "px";
4929 +        inputDiv.style.top = "-1em";
4930 +    }
4931 +};
4932 +
4933 +Timeline._Band.prototype.getViewWidth = function() {
4934 +    if (this._timeline.isHorizontal()) {
4935 +        return this._div.offsetHeight;
4936 +    } else {
4937 +        return this._div.offsetWidth;
4938 +    }
4939 +};
4940 +
4941 +Timeline._Band.prototype.setViewLength = function(length) {
4942 +    this._viewLength = length;
4943 +    this._recenterDiv();
4944 +    this._onChanging();
4945 +};
4946 +
4947 +Timeline._Band.prototype.getViewLength = function() {
4948 +    return this._viewLength;
4949 +};
4950 +
4951 +Timeline._Band.prototype.getTotalViewLength = function() {
4952 +    return Timeline._Band.SCROLL_MULTIPLES * this._viewLength;
4953 +};
4954 +
4955 +Timeline._Band.prototype.getViewOffset = function() {
4956 +    return this._viewOffset;
4957 +};
4958 +
4959 +Timeline._Band.prototype.getMinDate = function() {
4960 +    return this._ether.pixelOffsetToDate(this._viewOffset);
4961 +};
4962 +
4963 +Timeline._Band.prototype.getMaxDate = function() {
4964 +    return this._ether.pixelOffsetToDate(this._viewOffset + Timeline._Band.SCROLL_MULTIPLES * this._viewLength);
4965 +};
4966 +
4967 +Timeline._Band.prototype.getMinVisibleDate = function() {
4968 +    return this._ether.pixelOffsetToDate(0);
4969 +};
4970 +
4971 +Timeline._Band.prototype.getMinVisibleDateAfterDelta = function(delta) {
4972 +    return this._ether.pixelOffsetToDate(delta);
4973 +};
4974 +
4975 +Timeline._Band.prototype.getMaxVisibleDate = function() {
4976 +    // Max date currently visible on band
4977 +    return this._ether.pixelOffsetToDate(this._viewLength);
4978 +};
4979 +
4980 +Timeline._Band.prototype.getMaxVisibleDateAfterDelta = function(delta) {
4981 +    // Max date visible on band after delta px view change is applied
4982 +    return this._ether.pixelOffsetToDate(this._viewLength + delta);
4983 +};
4984 +
4985 +Timeline._Band.prototype.getCenterVisibleDate = function() {
4986 +    return this._ether.pixelOffsetToDate(this._viewLength / 2);
4987 +};
4988 +
4989 +Timeline._Band.prototype.setMinVisibleDate = function(date) {
4990 +    if (!this._changing) {
4991 +        this._moveEther(Math.round(-this._ether.dateToPixelOffset(date)));
4992 +    }
4993 +};
4994 +
4995 +Timeline._Band.prototype.setMaxVisibleDate = function(date) {
4996 +    if (!this._changing) {
4997 +        this._moveEther(Math.round(this._viewLength - this._ether.dateToPixelOffset(date)));
4998 +    }
4999 +};
5000 +
5001 +Timeline._Band.prototype.setCenterVisibleDate = function(date) {
5002 +    if (!this._changing) {
5003 +        this._moveEther(Math.round(this._viewLength / 2 - this._ether.dateToPixelOffset(date)));
5004 +    }
5005 +};
5006 +
5007 +Timeline._Band.prototype.dateToPixelOffset = function(date) {
5008 +    return this._ether.dateToPixelOffset(date) - this._viewOffset;
5009 +};
5010 +
5011 +Timeline._Band.prototype.pixelOffsetToDate = function(pixels) {
5012 +    return this._ether.pixelOffsetToDate(pixels + this._viewOffset);
5013 +};
5014 +
5015 +Timeline._Band.prototype.getViewOrthogonalOffset = function() {
5016 +    return this._viewOrthogonalOffset;
5017 +};
5018 +
5019 +Timeline._Band.prototype.setViewOrthogonalOffset = function(offset) {
5020 +    this._viewOrthogonalOffset = Math.max(0, offset);
5021 +};
5022 +
5023 +Timeline._Band.prototype.createLayerDiv = function(zIndex, className) {
5024 +    var div = this._timeline.getDocument().createElement("div");
5025 +    div.className = "timeline-band-layer" + (typeof className == "string" ? (" " + className) : "");
5026 +    div.style.zIndex = zIndex;
5027 +    this._innerDiv.appendChild(div);
5028 +
5029 +    var innerDiv = this._timeline.getDocument().createElement("div");
5030 +    innerDiv.className = "timeline-band-layer-inner";
5031 +    if (SimileAjax.Platform.browser.isIE) {
5032 +        innerDiv.style.cursor = "move";
5033 +    } else {
5034 +        innerDiv.style.cursor = "-moz-grab";
5035 +    }
5036 +    div.appendChild(innerDiv);
5037 +
5038 +    return innerDiv;
5039 +};
5040 +
5041 +Timeline._Band.prototype.removeLayerDiv = function(div) {
5042 +    this._innerDiv.removeChild(div.parentNode);
5043 +};
5044 +
5045 +Timeline._Band.prototype.scrollToCenter = function(date, f) {
5046 +    var pixelOffset = this._ether.dateToPixelOffset(date);
5047 +    if (pixelOffset < -this._viewLength / 2) {
5048 +        this.setCenterVisibleDate(this.pixelOffsetToDate(pixelOffset + this._viewLength));
5049 +    } else if (pixelOffset > 3 * this._viewLength / 2) {
5050 +        this.setCenterVisibleDate(this.pixelOffsetToDate(pixelOffset - this._viewLength));
5051 +    }
5052 +    this._autoScroll(Math.round(this._viewLength / 2 - this._ether.dateToPixelOffset(date)), f);
5053 +};
5054 +
5055 +Timeline._Band.prototype.showBubbleForEvent = function(eventID) {
5056 +    var evt = this.getEventSource().getEvent(eventID);
5057 +    if (evt) {
5058 +        var self = this;
5059 +        this.scrollToCenter(evt.getStart(), function() {
5060 +            self._eventPainter.showBubble(evt);
5061 +        });
5062 +    }
5063 +};
5064 +
5065 +Timeline._Band.prototype.zoom = function(zoomIn, x, y, target) {
5066 +  if (!this._zoomSteps) {
5067 +    // zoom disabled
5068 +    return;
5069 +  }
5070 +
5071 +  // shift the x value by our offset
5072 +  x += this._viewOffset;
5073 +
5074 +  var zoomDate = this._ether.pixelOffsetToDate(x);
5075 +  var netIntervalChange = this._ether.zoom(zoomIn);
5076 +  this._etherPainter.zoom(netIntervalChange);
5077 +
5078 +  // shift our zoom date to the far left
5079 +  this._moveEther(Math.round(-this._ether.dateToPixelOffset(zoomDate)));
5080 +  // then shift it back to where the mouse was
5081 +  this._moveEther(x);
5082 +};
5083 +
5084 +Timeline._Band.prototype._onMouseDown = function(innerFrame, evt, target) {
5085 +    this.closeBubble();
5086 +
5087 +    this._dragging = true;
5088 +    this._dragX = evt.clientX;
5089 +    this._dragY = evt.clientY;
5090 +};
5091 +
5092 +Timeline._Band.prototype._onMouseMove = function(innerFrame, evt, target) {
5093 +    if (this._dragging) {
5094 +        var diffX = evt.clientX - this._dragX;
5095 +        var diffY = evt.clientY - this._dragY;
5096 +
5097 +        this._dragX = evt.clientX;
5098 +        this._dragY = evt.clientY;
5099 +
5100 +        if (this._timeline.isHorizontal()) {
5101 +            this._moveEther(diffX, diffY);
5102 +        } else {
5103 +            this._moveEther(diffY, diffX);
5104 +        }
5105 +        this._positionHighlight();
5106 +    }
5107 +};
5108 +
5109 +Timeline._Band.prototype._onMouseUp = function(innerFrame, evt, target) {
5110 +    this._dragging = false;
5111 +    this._keyboardInput.focus();
5112 +};
5113 +
5114 +Timeline._Band.prototype._onMouseOut = function(innerFrame, evt, target) {
5115 +    var coords = SimileAjax.DOM.getEventRelativeCoordinates(evt, innerFrame);
5116 +    coords.x += this._viewOffset;
5117 +    if (coords.x < 0 || coords.x > innerFrame.offsetWidth ||
5118 +        coords.y < 0 || coords.y > innerFrame.offsetHeight) {
5119 +        this._dragging = false;
5120 +    }
5121 +};
5122 +
5123 +Timeline._Band.prototype._onMouseScroll = function(innerFrame, evt, target) {
5124 +  var now = new Date();
5125 +  now = now.getTime();
5126 +
5127 +  if (!this._lastScrollTime || ((now - this._lastScrollTime) > 50)) {
5128 +    // limit 1 scroll per 200ms due to FF3 sending multiple events back to back
5129 +    this._lastScrollTime = now;
5130 +
5131 +    var delta = 0;
5132 +    if (evt.wheelDelta) {
5133 +      delta = evt.wheelDelta/120;
5134 +    } else if (evt.detail) {
5135 +      delta = -evt.detail/3;
5136 +    }
5137 +
5138 +    // either scroll or zoom
5139 +    var mouseWheel = this._theme.mouseWheel;
5140 +
5141 +    if (this._zoomSteps || mouseWheel === 'zoom') {
5142 +      var loc = SimileAjax.DOM.getEventRelativeCoordinates(evt, innerFrame);
5143 +      if (delta != 0) {
5144 +        var zoomIn;
5145 +        if (delta > 0)
5146 +          zoomIn = true;
5147 +        if (delta < 0)
5148 +          zoomIn = false;
5149 +        // call zoom on the timeline so we could zoom multiple bands if desired
5150 +        this._timeline.zoom(zoomIn, loc.x, loc.y, innerFrame);
5151 +      }
5152 +    }
5153 +    else if (mouseWheel === 'scroll') {
5154 +    	var move_amt = 50 * (delta < 0 ? -1 : 1);
5155 +      this._moveEther(move_amt);
5156 +    }
5157 +  }
5158 +
5159 +  // prevent bubble
5160 +  if (evt.stopPropagation) {
5161 +    evt.stopPropagation();
5162 +  }
5163 +  evt.cancelBubble = true;
5164 +
5165 +  // prevent the default action
5166 +  if (evt.preventDefault) {
5167 +    evt.preventDefault();
5168 +  }
5169 +  evt.returnValue = false;
5170 +};
5171 +
5172 +Timeline._Band.prototype._onDblClick = function(innerFrame, evt, target) {
5173 +    var coords = SimileAjax.DOM.getEventRelativeCoordinates(evt, innerFrame);
5174 +    var distance = coords.x - (this._viewLength / 2 - this._viewOffset);
5175 +
5176 +    this._autoScroll(-distance);
5177 +};
5178 +
5179 +Timeline._Band.prototype._onKeyDown = function(keyboardInput, evt, target) {
5180 +    if (!this._dragging) {
5181 +        switch (evt.keyCode) {
5182 +        case 27: // ESC
5183 +            break;
5184 +        case 37: // left arrow
5185 +        case 38: // up arrow
5186 +            this._scrollSpeed = Math.min(50, Math.abs(this._scrollSpeed * 1.05));
5187 +            this._moveEther(this._scrollSpeed);
5188 +            break;
5189 +        case 39: // right arrow
5190 +        case 40: // down arrow
5191 +            this._scrollSpeed = -Math.min(50, Math.abs(this._scrollSpeed * 1.05));
5192 +            this._moveEther(this._scrollSpeed);
5193 +            break;
5194 +        default:
5195 +            return true;
5196 +        }
5197 +        this.closeBubble();
5198 +
5199 +        SimileAjax.DOM.cancelEvent(evt);
5200 +        return false;
5201 +    }
5202 +    return true;
5203 +};
5204 +
5205 +Timeline._Band.prototype._onKeyUp = function(keyboardInput, evt, target) {
5206 +    if (!this._dragging) {
5207 +        this._scrollSpeed = this._originalScrollSpeed;
5208 +
5209 +        switch (evt.keyCode) {
5210 +        case 35: // end
5211 +            this.setCenterVisibleDate(this._eventSource.getLatestDate());
5212 +            break;
5213 +        case 36: // home
5214 +            this.setCenterVisibleDate(this._eventSource.getEarliestDate());
5215 +            break;
5216 +        case 33: // page up
5217 +            this._autoScroll(this._timeline.getPixelLength());
5218 +            break;
5219 +        case 34: // page down
5220 +            this._autoScroll(-this._timeline.getPixelLength());
5221 +            break;
5222 +        default:
5223 +            return true;
5224 +        }
5225 +
5226 +        this.closeBubble();
5227 +
5228 +        SimileAjax.DOM.cancelEvent(evt);
5229 +        return false;
5230 +    }
5231 +    return true;
5232 +};
5233 +
5234 +Timeline._Band.prototype._autoScroll = function(distance, f) {
5235 +    var b = this;
5236 +    var a = SimileAjax.Graphics.createAnimation(
5237 +        function(abs, diff) {
5238 +            b._moveEther(diff);
5239 +        },
5240 +        0,
5241 +        distance,
5242 +        1000,
5243 +        f
5244 +    );
5245 +    a.run();
5246 +};
5247 +
5248 +Timeline._Band.prototype._moveEther = function(shift, orthogonalShift) {
5249 +    if (orthogonalShift === undefined) {
5250 +        orthogonalShift = 0;
5251 +    }
5252 +
5253 +    this.closeBubble();
5254 +
5255 +    // A positive shift means back in time
5256 +    // Check that we're not moving beyond Timeline's limits
5257 +    if (!this._timeline.shiftOK(this._index, shift)) {
5258 +        return; // early return
5259 +    }
5260 +
5261 +    this._viewOffset += shift;
5262 +    this._viewOrthogonalOffset = Math.min(0, this._viewOrthogonalOffset + orthogonalShift);
5263 +
5264 +    this._ether.shiftPixels(-shift);
5265 +    if (this._timeline.isHorizontal()) {
5266 +        this._div.style.left = this._viewOffset + "px";
5267 +    } else {
5268 +        this._div.style.top = this._viewOffset + "px";
5269 +    }
5270 +
5271 +    if (this._viewOffset > -this._viewLength * 0.5 ||
5272 +        this._viewOffset < -this._viewLength * (Timeline._Band.SCROLL_MULTIPLES - 1.5)) {
5273 +
5274 +        this._recenterDiv();
5275 +    } else {
5276 +        this.softLayout();
5277 +    }
5278 +
5279 +    this._onChanging();
5280 +}
5281 +
5282 +Timeline._Band.prototype._onChanging = function() {
5283 +    this._changing = true;
5284 +
5285 +    this._fireOnScroll();
5286 +    this._setSyncWithBandDate();
5287 +
5288 +    this._changing = false;
5289 +};
5290 +
5291 +Timeline._Band.prototype.busy = function() {
5292 +    // Is this band busy changing other bands?
5293 +    return(this._changing);
5294 +};
5295 +
5296 +Timeline._Band.prototype._fireOnScroll = function() {
5297 +    for (var i = 0; i < this._onScrollListeners.length; i++) {
5298 +        this._onScrollListeners[i](this);
5299 +    }
5300 +};
5301 +
5302 +Timeline._Band.prototype._setSyncWithBandDate = function() {
5303 +    if (this._syncWithBand) {
5304 +        var centerDate = this._ether.pixelOffsetToDate(this.getViewLength() / 2);
5305 +        this._syncWithBand.setCenterVisibleDate(centerDate);
5306 +    }
5307 +};
5308 +
5309 +Timeline._Band.prototype._onHighlightBandScroll = function() {
5310 +    if (this._syncWithBand) {
5311 +        var centerDate = this._syncWithBand.getCenterVisibleDate();
5312 +        var centerPixelOffset = this._ether.dateToPixelOffset(centerDate);
5313 +
5314 +        this._moveEther(Math.round(this._viewLength / 2 - centerPixelOffset));
5315 +
5316 +        if (this._highlight) {
5317 +            this._etherPainter.setHighlight(
5318 +                this._syncWithBand.getMinVisibleDate(),
5319 +                this._syncWithBand.getMaxVisibleDate());
5320 +        }
5321 +    }
5322 +};
5323 +
5324 +Timeline._Band.prototype._onAddMany = function() {
5325 +    this._paintEvents();
5326 +};
5327 +
5328 +Timeline._Band.prototype._onClear = function() {
5329 +    this._paintEvents();
5330 +};
5331 +
5332 +Timeline._Band.prototype._positionHighlight = function() {
5333 +    if (this._syncWithBand) {
5334 +        var startDate = this._syncWithBand.getMinVisibleDate();
5335 +        var endDate = this._syncWithBand.getMaxVisibleDate();
5336 +
5337 +        if (this._highlight) {
5338 +            this._etherPainter.setHighlight(startDate, endDate);
5339 +        }
5340 +    }
5341 +};
5342 +
5343 +Timeline._Band.prototype._recenterDiv = function() {
5344 +    this._viewOffset = -this._viewLength * (Timeline._Band.SCROLL_MULTIPLES - 1) / 2;
5345 +    if (this._timeline.isHorizontal()) {
5346 +        this._div.style.left = this._viewOffset + "px";
5347 +        this._div.style.width = (Timeline._Band.SCROLL_MULTIPLES * this._viewLength) + "px";
5348 +    } else {
5349 +        this._div.style.top = this._viewOffset + "px";
5350 +        this._div.style.height = (Timeline._Band.SCROLL_MULTIPLES * this._viewLength) + "px";
5351 +    }
5352 +    this.layout();
5353 +};
5354 +
5355 +Timeline._Band.prototype._paintEvents = function() {
5356 +    this._eventPainter.paint();
5357 +};
5358 +
5359 +Timeline._Band.prototype._softPaintEvents = function() {
5360 +    this._eventPainter.softPaint();
5361 +};
5362 +
5363 +Timeline._Band.prototype._paintDecorators = function() {
5364 +    for (var i = 0; i < this._decorators.length; i++) {
5365 +        this._decorators[i].paint();
5366 +    }
5367 +};
5368 +
5369 +Timeline._Band.prototype._softPaintDecorators = function() {
5370 +    for (var i = 0; i < this._decorators.length; i++) {
5371 +        this._decorators[i].softPaint();
5372 +    }
5373 +};
5374 +
5375 +Timeline._Band.prototype.closeBubble = function() {
5376 +    SimileAjax.WindowManager.cancelPopups();
5377 +};
5378 +/*
5379 + *  Classic Theme
5380 + *
5381 + */
5382 +
5383 +
5384 +
5385 +Timeline.ClassicTheme = new Object();
5386 +
5387 +Timeline.ClassicTheme.implementations = [];
5388 +
5389 +Timeline.ClassicTheme.create = function(locale) {
5390 +    if (locale == null) {
5391 +        locale = Timeline.getDefaultLocale();
5392 +    }
5393 +
5394 +    var f = Timeline.ClassicTheme.implementations[locale];
5395 +    if (f == null) {
5396 +        f = Timeline.ClassicTheme._Impl;
5397 +    }
5398 +    return new f();
5399 +};
5400 +
5401 +Timeline.ClassicTheme._Impl = function() {
5402 +    this.firstDayOfWeek = 0; // Sunday
5403 +
5404 +    // Note: Many styles previously set here are now set using CSS
5405 +    //       The comments indicate settings controlled by CSS, not
5406 +    //       lines to be un-commented.
5407 +    //
5408 +    //
5409 +    // Attributes autoWidth, autoWidthAnimationTime, timeline_start
5410 +    // and timeline_stop must be set on the first band's theme.
5411 +    // The other attributes can be set differently for each
5412 +    // band by using different themes for the bands.
5413 +    this.autoWidth = false; // Should the Timeline automatically grow itself, as
5414 +                            // needed when too many events for the available width
5415 +                            // are painted on the visible part of the Timeline?
5416 +    this.autoWidthAnimationTime = 500; // mSec
5417 +    this.timeline_start = null; // Setting a date, eg new Date(Date.UTC(2010,0,17,20,00,00,0)) will prevent the
5418 +                                // Timeline from being moved to anytime before the date.
5419 +    this.timeline_stop = null;  // Use for setting a maximum date. The Timeline will not be able
5420 +                                // to be moved to anytime after this date.
5421 +    this.ether = {
5422 +        backgroundColors: [
5423 +        //    "#EEE",
5424 +        //    "#DDD",
5425 +        //    "#CCC",
5426 +        //    "#AAA"
5427 +        ],
5428 +     //   highlightColor:     "white",
5429 +        highlightOpacity:   50,
5430 +        interval: {
5431 +            line: {
5432 +                show:       true,
5433 +                opacity:    25
5434 +               // color:      "#aaa",
5435 +            },
5436 +            weekend: {
5437 +                opacity:    30
5438 +              //  color:      "#FFFFE0",
5439 +            },
5440 +            marker: {
5441 +                hAlign:     "Bottom",
5442 +                vAlign:     "Right"
5443 +                                        /*
5444 +                hBottomStyler: function(elmt) {
5445 +                    elmt.className = "timeline-ether-marker-bottom";
5446 +                },
5447 +                hBottomEmphasizedStyler: function(elmt) {
5448 +                    elmt.className = "timeline-ether-marker-bottom-emphasized";
5449 +                },
5450 +                hTopStyler: function(elmt) {
5451 +                    elmt.className = "timeline-ether-marker-top";
5452 +                },
5453 +                hTopEmphasizedStyler: function(elmt) {
5454 +                    elmt.className = "timeline-ether-marker-top-emphasized";
5455 +                },
5456 +                */
5457 +
5458 +
5459 +               /*
5460 +                                  vRightStyler: function(elmt) {
5461 +                    elmt.className = "timeline-ether-marker-right";
5462 +                },
5463 +                vRightEmphasizedStyler: function(elmt) {
5464 +                    elmt.className = "timeline-ether-marker-right-emphasized";
5465 +                },
5466 +                vLeftStyler: function(elmt) {
5467 +                    elmt.className = "timeline-ether-marker-left";
5468 +                },
5469 +                vLeftEmphasizedStyler:function(elmt) {
5470 +                    elmt.className = "timeline-ether-marker-left-emphasized";
5471 +                }
5472 +                */
5473 +            }
5474 +        }
5475 +    };
5476 +
5477 +    this.event = {
5478 +        track: {
5479 +                   height: 10, // px. You will need to change the track
5480 +                               //     height if you change the tape height.
5481 +                      gap:  2, // px. Gap between tracks
5482 +                   offset:  2, // px. top margin above tapes
5483 +          autoWidthMargin:  1.5
5484 +          /* autoWidthMargin is only used if autoWidth (see above) is true.
5485 +             The autoWidthMargin setting is used to set how close the bottom of the
5486 +             lowest track is to the edge of the band's div. The units are total track
5487 +             width (tape + label + gap). A min of 0.5 is suggested. Use this setting to
5488 +             move the bottom track's tapes above the axis markers, if needed for your
5489 +             Timeline.
5490 +          */
5491 +        },
5492 +        overviewTrack: {
5493 +                  offset: 20, // px -- top margin above tapes
5494 +              tickHeight:  6, // px
5495 +                  height:  2, // px
5496 +                     gap:  1, // px
5497 +         autoWidthMargin:  5 // This attribute is only used if autoWidth (see above) is true.
5498 +        },
5499 +        tape: {
5500 +            height:         4 // px. For thicker tapes, remember to change track height too.
5501 +        },
5502 +        instant: {
5503 +                           icon: Timeline.urlPrefix + "images/dull-blue-circle.png",
5504 +                                 // default icon. Icon can also be specified per event
5505 +                      iconWidth: 10,
5506 +                     iconHeight: 10,
5507 +               impreciseOpacity: 20, // opacity of the tape when durationEvent is false
5508 +            impreciseIconMargin: 3   // A tape and an icon are painted for imprecise instant
5509 +                                     // events. This attribute is the margin between the
5510 +                                     // bottom of the tape and the top of the icon in that
5511 +                                     // case.
5512 +    //        color:             "#58A0DC",
5513 +    //        impreciseColor:    "#58A0DC",
5514 +        },
5515 +        duration: {
5516 +            impreciseOpacity: 20 // tape opacity for imprecise part of duration events
5517 +      //      color:            "#58A0DC",
5518 +      //      impreciseColor:   "#58A0DC",
5519 +        },
5520 +        label: {
5521 +            backgroundOpacity: 50,// only used in detailed painter
5522 +               offsetFromLine:  3 // px left margin amount from icon's right edge
5523 +      //      backgroundColor:   "white",
5524 +      //      lineColor:         "#58A0DC",
5525 +        },
5526 +        highlightColors: [  // Use with getEventPainter().setHighlightMatcher
5527 +                            // See webapp/examples/examples.js
5528 +            "#FFFF00",
5529 +            "#FFC000",
5530 +            "#FF0000",
5531 +            "#0000FF"
5532 +        ],
5533 +        highlightLabelBackground: false, // When highlighting an event, also change the event's label background?
5534 +        bubble: {
5535 +            width:          250, // px
5536 +            maxHeight:        0, // px Maximum height of bubbles. 0 means no max height.
5537 +                                 // scrollbar will be added for taller bubbles
5538 +            titleStyler: function(elmt) {
5539 +                elmt.className = "timeline-event-bubble-title";
5540 +            },
5541 +            bodyStyler: function(elmt) {
5542 +                elmt.className = "timeline-event-bubble-body";
5543 +            },
5544 +            imageStyler: function(elmt) {
5545 +                elmt.className = "timeline-event-bubble-image";
5546 +            },
5547 +            wikiStyler: function(elmt) {
5548 +                elmt.className = "timeline-event-bubble-wiki";
5549 +            },
5550 +            timeStyler: function(elmt) {
5551 +                elmt.className = "timeline-event-bubble-time";
5552 +            }
5553 +        }
5554 +    };
5555 +
5556 +    this.mouseWheel = 'scroll'; // 'default', 'zoom', 'scroll'
5557 +};/*
5558 + *  An "ether" is a object that maps date/time to pixel coordinates.
5559 + *
5560 + */
5561 +
5562 +/*
5563 + *  Linear Ether
5564 + *
5565 + */
5566 +
5567 +Timeline.LinearEther = function(params) {
5568 +    this._params = params;
5569 +    this._interval = params.interval;
5570 +    this._pixelsPerInterval = params.pixelsPerInterval;
5571 +};
5572 +
5573 +Timeline.LinearEther.prototype.initialize = function(band, timeline) {
5574 +    this._band = band;
5575 +    this._timeline = timeline;
5576 +    this._unit = timeline.getUnit();
5577 +
5578 +    if ("startsOn" in this._params) {
5579 +        this._start = this._unit.parseFromObject(this._params.startsOn);
5580 +    } else if ("endsOn" in this._params) {
5581 +        this._start = this._unit.parseFromObject(this._params.endsOn);
5582 +        this.shiftPixels(-this._timeline.getPixelLength());
5583 +    } else if ("centersOn" in this._params) {
5584 +        this._start = this._unit.parseFromObject(this._params.centersOn);
5585 +        this.shiftPixels(-this._timeline.getPixelLength() / 2);
5586 +    } else {
5587 +        this._start = this._unit.makeDefaultValue();
5588 +        this.shiftPixels(-this._timeline.getPixelLength() / 2);
5589 +    }
5590 +};
5591 +
5592 +Timeline.LinearEther.prototype.setDate = function(date) {
5593 +    this._start = this._unit.cloneValue(date);
5594 +};
5595 +
5596 +Timeline.LinearEther.prototype.shiftPixels = function(pixels) {
5597 +    var numeric = this._interval * pixels / this._pixelsPerInterval;
5598 +    this._start = this._unit.change(this._start, numeric);
5599 +};
5600 +
5601 +Timeline.LinearEther.prototype.dateToPixelOffset = function(date) {
5602 +    var numeric = this._unit.compare(date, this._start);
5603 +    return this._pixelsPerInterval * numeric / this._interval;
5604 +};
5605 +
5606 +Timeline.LinearEther.prototype.pixelOffsetToDate = function(pixels) {
5607 +    var numeric = pixels * this._interval / this._pixelsPerInterval;
5608 +    return this._unit.change(this._start, numeric);
5609 +};
5610 +
5611 +Timeline.LinearEther.prototype.zoom = function(zoomIn) {
5612 +  var netIntervalChange = 0;
5613 +  var currentZoomIndex = this._band._zoomIndex;
5614 +  var newZoomIndex = currentZoomIndex;
5615 +
5616 +  if (zoomIn && (currentZoomIndex > 0)) {
5617 +    newZoomIndex = currentZoomIndex - 1;
5618 +  }
5619 +
5620 +  if (!zoomIn && (currentZoomIndex < (this._band._zoomSteps.length - 1))) {
5621 +    newZoomIndex = currentZoomIndex + 1;
5622 +  }
5623 +
5624 +  this._band._zoomIndex = newZoomIndex;
5625 +  this._interval =
5626 +    SimileAjax.DateTime.gregorianUnitLengths[this._band._zoomSteps[newZoomIndex].unit];
5627 +  this._pixelsPerInterval = this._band._zoomSteps[newZoomIndex].pixelsPerInterval;
5628 +  netIntervalChange = this._band._zoomSteps[newZoomIndex].unit -
5629 +    this._band._zoomSteps[currentZoomIndex].unit;
5630 +
5631 +  return netIntervalChange;
5632 +};
5633 +
5634 +
5635 +/*
5636 + *  Hot Zone Ether
5637 + *
5638 + */
5639 +
5640 +Timeline.HotZoneEther = function(params) {
5641 +    this._params = params;
5642 +    this._interval = params.interval;
5643 +    this._pixelsPerInterval = params.pixelsPerInterval;
5644 +    this._theme = params.theme;
5645 +};
5646 +
5647 +Timeline.HotZoneEther.prototype.initialize = function(band, timeline) {
5648 +    this._band = band;
5649 +    this._timeline = timeline;
5650 +    this._unit = timeline.getUnit();
5651 +
5652 +    this._zones = [{
5653 +        startTime:  Number.NEGATIVE_INFINITY,
5654 +        endTime:    Number.POSITIVE_INFINITY,
5655 +        magnify:    1
5656 +    }];
5657 +    var params = this._params;
5658 +    for (var i = 0; i < params.zones.length; i++) {
5659 +        var zone = params.zones[i];
5660 +        var zoneStart = this._unit.parseFromObject(zone.start);
5661 +        var zoneEnd =   this._unit.parseFromObject(zone.end);
5662 +
5663 +        for (var j = 0; j < this._zones.length && this._unit.compare(zoneEnd, zoneStart) > 0; j++) {
5664 +            var zone2 = this._zones[j];
5665 +
5666 +            if (this._unit.compare(zoneStart, zone2.endTime) < 0) {
5667 +                if (this._unit.compare(zoneStart, zone2.startTime) > 0) {
5668 +                    this._zones.splice(j, 0, {
5669 +                        startTime:   zone2.startTime,
5670 +                        endTime:     zoneStart,
5671 +                        magnify:     zone2.magnify
5672 +                    });
5673 +                    j++;
5674 +
5675 +                    zone2.startTime = zoneStart;
5676 +                }
5677 +
5678 +                if (this._unit.compare(zoneEnd, zone2.endTime) < 0) {
5679 +                    this._zones.splice(j, 0, {
5680 +                        startTime:  zoneStart,
5681 +                        endTime:    zoneEnd,
5682 +                        magnify:    zone.magnify * zone2.magnify
5683 +                    });
5684 +                    j++;
5685 +
5686 +                    zone2.startTime = zoneEnd;
5687 +                    zoneStart = zoneEnd;
5688 +                } else {
5689 +                    zone2.magnify *= zone.magnify;
5690 +                    zoneStart = zone2.endTime;
5691 +                }
5692 +            } // else, try the next existing zone
5693 +        }
5694 +    }
5695 +
5696 +    if ("startsOn" in this._params) {
5697 +        this._start = this._unit.parseFromObject(this._params.startsOn);
5698 +    } else if ("endsOn" in this._params) {
5699 +        this._start = this._unit.parseFromObject(this._params.endsOn);
5700 +        this.shiftPixels(-this._timeline.getPixelLength());
5701 +    } else if ("centersOn" in this._params) {
5702 +        this._start = this._unit.parseFromObject(this._params.centersOn);
5703 +        this.shiftPixels(-this._timeline.getPixelLength() / 2);
5704 +    } else {
5705 +        this._start = this._unit.makeDefaultValue();
5706 +        this.shiftPixels(-this._timeline.getPixelLength() / 2);
5707 +    }
5708 +};
5709 +
5710 +Timeline.HotZoneEther.prototype.setDate = function(date) {
5711 +    this._start = this._unit.cloneValue(date);
5712 +};
5713 +
5714 +Timeline.HotZoneEther.prototype.shiftPixels = function(pixels) {
5715 +    this._start = this.pixelOffsetToDate(pixels);
5716 +};
5717 +
5718 +Timeline.HotZoneEther.prototype.dateToPixelOffset = function(date) {
5719 +    return this._dateDiffToPixelOffset(this._start, date);
5720 +};
5721 +
5722 +Timeline.HotZoneEther.prototype.pixelOffsetToDate = function(pixels) {
5723 +    return this._pixelOffsetToDate(pixels, this._start);
5724 +};
5725 +
5726 +Timeline.HotZoneEther.prototype.zoom = function(zoomIn) {
5727 +  var netIntervalChange = 0;
5728 +  var currentZoomIndex = this._band._zoomIndex;
5729 +  var newZoomIndex = currentZoomIndex;
5730 +
5731 +  if (zoomIn && (currentZoomIndex > 0)) {
5732 +    newZoomIndex = currentZoomIndex - 1;
5733 +  }
5734 +
5735 +  if (!zoomIn && (currentZoomIndex < (this._band._zoomSteps.length - 1))) {
5736 +    newZoomIndex = currentZoomIndex + 1;
5737 +  }
5738 +
5739 +  this._band._zoomIndex = newZoomIndex;
5740 +  this._interval =
5741 +    SimileAjax.DateTime.gregorianUnitLengths[this._band._zoomSteps[newZoomIndex].unit];
5742 +  this._pixelsPerInterval = this._band._zoomSteps[newZoomIndex].pixelsPerInterval;
5743 +  netIntervalChange = this._band._zoomSteps[newZoomIndex].unit -
5744 +    this._band._zoomSteps[currentZoomIndex].unit;
5745 +
5746 +  return netIntervalChange;
5747 +};
5748 +
5749 +Timeline.HotZoneEther.prototype._dateDiffToPixelOffset = function(fromDate, toDate) {
5750 +    var scale = this._getScale();
5751 +    var fromTime = fromDate;
5752 +    var toTime = toDate;
5753 +
5754 +    var pixels = 0;
5755 +    if (this._unit.compare(fromTime, toTime) < 0) {
5756 +        var z = 0;
5757 +        while (z < this._zones.length) {
5758 +            if (this._unit.compare(fromTime, this._zones[z].endTime) < 0) {
5759 +                break;
5760 +            }
5761 +            z++;
5762 +        }
5763 +
5764 +        while (this._unit.compare(fromTime, toTime) < 0) {
5765 +            var zone = this._zones[z];
5766 +            var toTime2 = this._unit.earlier(toTime, zone.endTime);
5767 +
5768 +            pixels += (this._unit.compare(toTime2, fromTime) / (scale / zone.magnify));
5769 +
5770 +            fromTime = toTime2;
5771 +            z++;
5772 +        }
5773 +    } else {
5774 +        var z = this._zones.length - 1;
5775 +        while (z >= 0) {
5776 +            if (this._unit.compare(fromTime, this._zones[z].startTime) > 0) {
5777 +                break;
5778 +            }
5779 +            z--;
5780 +        }
5781 +
5782 +        while (this._unit.compare(fromTime, toTime) > 0) {
5783 +            var zone = this._zones[z];
5784 +            var toTime2 = this._unit.later(toTime, zone.startTime);
5785 +
5786 +            pixels += (this._unit.compare(toTime2, fromTime) / (scale / zone.magnify));
5787 +
5788 +            fromTime = toTime2;
5789 +            z--;
5790 +        }
5791 +    }
5792 +    return pixels;
5793 +};
5794 +
5795 +Timeline.HotZoneEther.prototype._pixelOffsetToDate = function(pixels, fromDate) {
5796 +    var scale = this._getScale();
5797 +    var time = fromDate;
5798 +    if (pixels > 0) {
5799 +        var z = 0;
5800 +        while (z < this._zones.length) {
5801 +            if (this._unit.compare(time, this._zones[z].endTime) < 0) {
5802 +                break;
5803 +            }
5804 +            z++;
5805 +        }
5806 +
5807 +        while (pixels > 0) {
5808 +            var zone = this._zones[z];
5809 +            var scale2 = scale / zone.magnify;
5810 +
5811 +            if (zone.endTime == Number.POSITIVE_INFINITY) {
5812 +                time = this._unit.change(time, pixels * scale2);
5813 +                pixels = 0;
5814 +            } else {
5815 +                var pixels2 = this._unit.compare(zone.endTime, time) / scale2;
5816 +                if (pixels2 > pixels) {
5817 +                    time = this._unit.change(time, pixels * scale2);
5818 +                    pixels = 0;
5819 +                } else {
5820 +                    time = zone.endTime;
5821 +                    pixels -= pixels2;
5822 +                }
5823 +            }
5824 +            z++;
5825 +        }
5826 +    } else {
5827 +        var z = this._zones.length - 1;
5828 +        while (z >= 0) {
5829 +            if (this._unit.compare(time, this._zones[z].startTime) > 0) {
5830 +                break;
5831 +            }
5832 +            z--;
5833 +        }
5834 +
5835 +        pixels = -pixels;
5836 +        while (pixels > 0) {
5837 +            var zone = this._zones[z];
5838 +            var scale2 = scale / zone.magnify;
5839 +
5840 +            if (zone.startTime == Number.NEGATIVE_INFINITY) {
5841 +                time = this._unit.change(time, -pixels * scale2);
5842 +                pixels = 0;
5843 +            } else {
5844 +                var pixels2 = this._unit.compare(time, zone.startTime) / scale2;
5845 +                if (pixels2 > pixels) {
5846 +                    time = this._unit.change(time, -pixels * scale2);
5847 +                    pixels = 0;
5848 +                } else {
5849 +                    time = zone.startTime;
5850 +                    pixels -= pixels2;
5851 +                }
5852 +            }
5853 +            z--;
5854 +        }
5855 +    }
5856 +    return time;
5857 +};
5858 +
5859 +Timeline.HotZoneEther.prototype._getScale = function() {
5860 +    return this._interval / this._pixelsPerInterval;
5861 +};
5862 +/*
5863 + *  Gregorian Ether Painter
5864 + *
5865 + */
5866 +
5867 +Timeline.GregorianEtherPainter = function(params) {
5868 +    this._params = params;
5869 +    this._theme = params.theme;
5870 +    this._unit = params.unit;
5871 +    this._multiple = ("multiple" in params) ? params.multiple : 1;
5872 +};
5873 +
5874 +Timeline.GregorianEtherPainter.prototype.initialize = function(band, timeline) {
5875 +    this._band = band;
5876 +    this._timeline = timeline;
5877 +
5878 +    this._backgroundLayer = band.createLayerDiv(0);
5879 +    this._backgroundLayer.setAttribute("name", "ether-background"); // for debugging
5880 +    this._backgroundLayer.className = 'timeline-ether-bg';
5881 +  //  this._backgroundLayer.style.background = this._theme.ether.backgroundColors[band.getIndex()];
5882 +
5883 +
5884 +    this._markerLayer = null;
5885 +    this._lineLayer = null;
5886 +
5887 +    var align = ("align" in this._params && this._params.align != undefined) ? this._params.align :
5888 +        this._theme.ether.interval.marker[timeline.isHorizontal() ? "hAlign" : "vAlign"];
5889 +    var showLine = ("showLine" in this._params) ? this._params.showLine :
5890 +        this._theme.ether.interval.line.show;
5891 +
5892 +    this._intervalMarkerLayout = new Timeline.EtherIntervalMarkerLayout(
5893 +        this._timeline, this._band, this._theme, align, showLine);
5894 +
5895 +    this._highlight = new Timeline.EtherHighlight(
5896 +        this._timeline, this._band, this._theme, this._backgroundLayer);
5897 +}
5898 +
5899 +Timeline.GregorianEtherPainter.prototype.setHighlight = function(startDate, endDate) {
5900 +    this._highlight.position(startDate, endDate);
5901 +}
5902 +
5903 +Timeline.GregorianEtherPainter.prototype.paint = function() {
5904 +    if (this._markerLayer) {
5905 +        this._band.removeLayerDiv(this._markerLayer);
5906 +    }
5907 +    this._markerLayer = this._band.createLayerDiv(100);
5908 +    this._markerLayer.setAttribute("name", "ether-markers"); // for debugging
5909 +    this._markerLayer.style.display = "none";
5910 +
5911 +    if (this._lineLayer) {
5912 +        this._band.removeLayerDiv(this._lineLayer);
5913 +    }
5914 +    this._lineLayer = this._band.createLayerDiv(1);
5915 +    this._lineLayer.setAttribute("name", "ether-lines"); // for debugging
5916 +    this._lineLayer.style.display = "none";
5917 +
5918 +    var minDate = this._band.getMinDate();
5919 +    var maxDate = this._band.getMaxDate();
5920 +
5921 +    var timeZone = this._band.getTimeZone();
5922 +    var labeller = this._band.getLabeller();
5923 +
5924 +    SimileAjax.DateTime.roundDownToInterval(minDate, this._unit, timeZone, this._multiple, this._theme.firstDayOfWeek);
5925 +
5926 +    var p = this;
5927 +    var incrementDate = function(date) {
5928 +        for (var i = 0; i < p._multiple; i++) {
5929 +            SimileAjax.DateTime.incrementByInterval(date, p._unit);
5930 +        }
5931 +    };
5932 +
5933 +    while (minDate.getTime() < maxDate.getTime()) {
5934 +        this._intervalMarkerLayout.createIntervalMarker(
5935 +            minDate, labeller, this._unit, this._markerLayer, this._lineLayer);
5936 +
5937 +        incrementDate(minDate);
5938 +    }
5939 +    this._markerLayer.style.display = "block";
5940 +    this._lineLayer.style.display = "block";
5941 +};
5942 +
5943 +Timeline.GregorianEtherPainter.prototype.softPaint = function() {
5944 +};
5945 +
5946 +Timeline.GregorianEtherPainter.prototype.zoom = function(netIntervalChange) {
5947 +  if (netIntervalChange != 0) {
5948 +    this._unit += netIntervalChange;
5949 +  }
5950 +};
5951 +
5952 +
5953 +/*
5954 + *  Hot Zone Gregorian Ether Painter
5955 + *
5956 + */
5957 +
5958 +Timeline.HotZoneGregorianEtherPainter = function(params) {
5959 +    this._params = params;
5960 +    this._theme = params.theme;
5961 +
5962 +    this._zones = [{
5963 +        startTime:  Number.NEGATIVE_INFINITY,
5964 +        endTime:    Number.POSITIVE_INFINITY,
5965 +        unit:       params.unit,
5966 +        multiple:   1
5967 +    }];
5968 +    for (var i = 0; i < params.zones.length; i++) {
5969 +        var zone = params.zones[i];
5970 +        var zoneStart = SimileAjax.DateTime.parseGregorianDateTime(zone.start).getTime();
5971 +        var zoneEnd = SimileAjax.DateTime.parseGregorianDateTime(zone.end).getTime();
5972 +
5973 +        for (var j = 0; j < this._zones.length && zoneEnd > zoneStart; j++) {
5974 +            var zone2 = this._zones[j];
5975 +
5976 +            if (zoneStart < zone2.endTime) {
5977 +                if (zoneStart > zone2.startTime) {
5978 +                    this._zones.splice(j, 0, {
5979 +                        startTime:   zone2.startTime,
5980 +                        endTime:     zoneStart,
5981 +                        unit:        zone2.unit,
5982 +                        multiple:    zone2.multiple
5983 +                    });
5984 +                    j++;
5985 +
5986 +                    zone2.startTime = zoneStart;
5987 +                }
5988 +
5989 +                if (zoneEnd < zone2.endTime) {
5990 +                    this._zones.splice(j, 0, {
5991 +                        startTime:  zoneStart,
5992 +                        endTime:    zoneEnd,
5993 +                        unit:       zone.unit,
5994 +                        multiple:   (zone.multiple) ? zone.multiple : 1
5995 +                    });
5996 +                    j++;
5997 +
5998 +                    zone2.startTime = zoneEnd;
5999 +                    zoneStart = zoneEnd;
6000 +                } else {
6001 +                    zone2.multiple = zone.multiple;
6002 +                    zone2.unit = zone.unit;
6003 +                    zoneStart = zone2.endTime;
6004 +                }
6005 +            } // else, try the next existing zone
6006 +        }
6007 +    }
6008 +};
6009 +
6010 +Timeline.HotZoneGregorianEtherPainter.prototype.initialize = function(band, timeline) {
6011 +    this._band = band;
6012 +    this._timeline = timeline;
6013 +
6014 +    this._backgroundLayer = band.createLayerDiv(0);
6015 +    this._backgroundLayer.setAttribute("name", "ether-background"); // for debugging
6016 +    this._backgroundLayer.className ='timeline-ether-bg';
6017 +    //this._backgroundLayer.style.background = this._theme.ether.backgroundColors[band.getIndex()];
6018 +
6019 +    this._markerLayer = null;
6020 +    this._lineLayer = null;
6021 +
6022 +    var align = ("align" in this._params && this._params.align != undefined) ? this._params.align :
6023 +        this._theme.ether.interval.marker[timeline.isHorizontal() ? "hAlign" : "vAlign"];
6024 +    var showLine = ("showLine" in this._params) ? this._params.showLine :
6025 +        this._theme.ether.interval.line.show;
6026 +
6027 +    this._intervalMarkerLayout = new Timeline.EtherIntervalMarkerLayout(
6028 +        this._timeline, this._band, this._theme, align, showLine);
6029 +
6030 +    this._highlight = new Timeline.EtherHighlight(
6031 +        this._timeline, this._band, this._theme, this._backgroundLayer);
6032 +}
6033 +
6034 +Timeline.HotZoneGregorianEtherPainter.prototype.setHighlight = function(startDate, endDate) {
6035 +    this._highlight.position(startDate, endDate);
6036 +}
6037 +
6038 +Timeline.HotZoneGregorianEtherPainter.prototype.paint = function() {
6039 +    if (this._markerLayer) {
6040 +        this._band.removeLayerDiv(this._markerLayer);
6041 +    }
6042 +    this._markerLayer = this._band.createLayerDiv(100);
6043 +    this._markerLayer.setAttribute("name", "ether-markers"); // for debugging
6044 +    this._markerLayer.style.display = "none";
6045 +
6046 +    if (this._lineLayer) {
6047 +        this._band.removeLayerDiv(this._lineLayer);
6048 +    }
6049 +    this._lineLayer = this._band.createLayerDiv(1);
6050 +    this._lineLayer.setAttribute("name", "ether-lines"); // for debugging
6051 +    this._lineLayer.style.display = "none";
6052 +
6053 +    var minDate = this._band.getMinDate();
6054 +    var maxDate = this._band.getMaxDate();
6055 +
6056 +    var timeZone = this._band.getTimeZone();
6057 +    var labeller = this._band.getLabeller();
6058 +
6059 +    var p = this;
6060 +    var incrementDate = function(date, zone) {
6061 +        for (var i = 0; i < zone.multiple; i++) {
6062 +            SimileAjax.DateTime.incrementByInterval(date, zone.unit);
6063 +        }
6064 +    };
6065 +
6066 +    var zStart = 0;
6067 +    while (zStart < this._zones.length) {
6068 +        if (minDate.getTime() < this._zones[zStart].endTime) {
6069 +            break;
6070 +        }
6071 +        zStart++;
6072 +    }
6073 +    var zEnd = this._zones.length - 1;
6074 +    while (zEnd >= 0) {
6075 +        if (maxDate.getTime() > this._zones[zEnd].startTime) {
6076 +            break;
6077 +        }
6078 +        zEnd--;
6079 +    }
6080 +
6081 +    for (var z = zStart; z <= zEnd; z++) {
6082 +        var zone = this._zones[z];
6083 +
6084 +        var minDate2 = new Date(Math.max(minDate.getTime(), zone.startTime));
6085 +        var maxDate2 = new Date(Math.min(maxDate.getTime(), zone.endTime));
6086 +
6087 +        SimileAjax.DateTime.roundDownToInterval(minDate2, zone.unit, timeZone, zone.multiple, this._theme.firstDayOfWeek);
6088 +        SimileAjax.DateTime.roundUpToInterval(maxDate2, zone.unit, timeZone, zone.multiple, this._theme.firstDayOfWeek);
6089 +
6090 +        while (minDate2.getTime() < maxDate2.getTime()) {
6091 +            this._intervalMarkerLayout.createIntervalMarker(
6092 +                minDate2, labeller, zone.unit, this._markerLayer, this._lineLayer);
6093 +
6094 +            incrementDate(minDate2, zone);
6095 +        }
6096 +    }
6097 +    this._markerLayer.style.display = "block";
6098 +    this._lineLayer.style.display = "block";
6099 +};
6100 +
6101 +Timeline.HotZoneGregorianEtherPainter.prototype.softPaint = function() {
6102 +};
6103 +
6104 +Timeline.HotZoneGregorianEtherPainter.prototype.zoom = function(netIntervalChange) {
6105 +  if (netIntervalChange != 0) {
6106 +    for (var i = 0; i < this._zones.length; ++i) {
6107 +      if (this._zones[i]) {
6108 +        this._zones[i].unit += netIntervalChange;
6109 +      }
6110 +    }
6111 +  }
6112 +};
6113 +
6114 +/*
6115 + *  Year Count Ether Painter
6116 + *
6117 + */
6118 +
6119 +Timeline.YearCountEtherPainter = function(params) {
6120 +    this._params = params;
6121 +    this._theme = params.theme;
6122 +    this._startDate = SimileAjax.DateTime.parseGregorianDateTime(params.startDate);
6123 +    this._multiple = ("multiple" in params) ? params.multiple : 1;
6124 +};
6125 +
6126 +Timeline.YearCountEtherPainter.prototype.initialize = function(band, timeline) {
6127 +    this._band = band;
6128 +    this._timeline = timeline;
6129 +
6130 +    this._backgroundLayer = band.createLayerDiv(0);
6131 +    this._backgroundLayer.setAttribute("name", "ether-background"); // for debugging
6132 +    this._backgroundLayer.className = 'timeline-ether-bg';
6133 +   // this._backgroundLayer.style.background = this._theme.ether.backgroundColors[band.getIndex()];
6134 +
6135 +    this._markerLayer = null;
6136 +    this._lineLayer = null;
6137 +
6138 +    var align = ("align" in this._params) ? this._params.align :
6139 +        this._theme.ether.interval.marker[timeline.isHorizontal() ? "hAlign" : "vAlign"];
6140 +    var showLine = ("showLine" in this._params) ? this._params.showLine :
6141 +        this._theme.ether.interval.line.show;
6142 +
6143 +    this._intervalMarkerLayout = new Timeline.EtherIntervalMarkerLayout(
6144 +        this._timeline, this._band, this._theme, align, showLine);
6145 +
6146 +    this._highlight = new Timeline.EtherHighlight(
6147 +        this._timeline, this._band, this._theme, this._backgroundLayer);
6148 +};
6149 +
6150 +Timeline.YearCountEtherPainter.prototype.setHighlight = function(startDate, endDate) {
6151 +    this._highlight.position(startDate, endDate);
6152 +};
6153 +
6154 +Timeline.YearCountEtherPainter.prototype.paint = function() {
6155 +    if (this._markerLayer) {
6156 +        this._band.removeLayerDiv(this._markerLayer);
6157 +    }
6158 +    this._markerLayer = this._band.createLayerDiv(100);
6159 +    this._markerLayer.setAttribute("name", "ether-markers"); // for debugging
6160 +    this._markerLayer.style.display = "none";
6161 +
6162 +    if (this._lineLayer) {
6163 +        this._band.removeLayerDiv(this._lineLayer);
6164 +    }
6165 +    this._lineLayer = this._band.createLayerDiv(1);
6166 +    this._lineLayer.setAttribute("name", "ether-lines"); // for debugging
6167 +    this._lineLayer.style.display = "none";
6168 +
6169 +    var minDate = new Date(this._startDate.getTime());
6170 +    var maxDate = this._band.getMaxDate();
6171 +    var yearDiff = this._band.getMinDate().getUTCFullYear() - this._startDate.getUTCFullYear();
6172 +    minDate.setUTCFullYear(this._band.getMinDate().getUTCFullYear() - yearDiff % this._multiple);
6173 +
6174 +    var p = this;
6175 +    var incrementDate = function(date) {
6176 +        for (var i = 0; i < p._multiple; i++) {
6177 +            SimileAjax.DateTime.incrementByInterval(date, SimileAjax.DateTime.YEAR);
6178 +        }
6179 +    };
6180 +    var labeller = {
6181 +        labelInterval: function(date, intervalUnit) {
6182 +            var diff = date.getUTCFullYear() - p._startDate.getUTCFullYear();
6183 +            return {
6184 +                text: diff,
6185 +                emphasized: diff == 0
6186 +            };
6187 +        }
6188 +    };
6189 +
6190 +    while (minDate.getTime() < maxDate.getTime()) {
6191 +        this._intervalMarkerLayout.createIntervalMarker(
6192 +            minDate, labeller, SimileAjax.DateTime.YEAR, this._markerLayer, this._lineLayer);
6193 +
6194 +        incrementDate(minDate);
6195 +    }
6196 +    this._markerLayer.style.display = "block";
6197 +    this._lineLayer.style.display = "block";
6198 +};
6199 +
6200 +Timeline.YearCountEtherPainter.prototype.softPaint = function() {
6201 +};
6202 +
6203 +/*
6204 + *  Quarterly Ether Painter
6205 + *
6206 + */
6207 +
6208 +Timeline.QuarterlyEtherPainter = function(params) {
6209 +    this._params = params;
6210 +    this._theme = params.theme;
6211 +    this._startDate = SimileAjax.DateTime.parseGregorianDateTime(params.startDate);
6212 +};
6213 +
6214 +Timeline.QuarterlyEtherPainter.prototype.initialize = function(band, timeline) {
6215 +    this._band = band;
6216 +    this._timeline = timeline;
6217 +
6218 +    this._backgroundLayer = band.createLayerDiv(0);
6219 +    this._backgroundLayer.setAttribute("name", "ether-background"); // for debugging
6220 +    this._backgroundLayer.className = 'timeline-ether-bg';
6221 + //   this._backgroundLayer.style.background = this._theme.ether.backgroundColors[band.getIndex()];
6222 +
6223 +    this._markerLayer = null;
6224 +    this._lineLayer = null;
6225 +
6226 +    var align = ("align" in this._params) ? this._params.align :
6227 +        this._theme.ether.interval.marker[timeline.isHorizontal() ? "hAlign" : "vAlign"];
6228 +    var showLine = ("showLine" in this._params) ? this._params.showLine :
6229 +        this._theme.ether.interval.line.show;
6230 +
6231 +    this._intervalMarkerLayout = new Timeline.EtherIntervalMarkerLayout(
6232 +        this._timeline, this._band, this._theme, align, showLine);
6233 +
6234 +    this._highlight = new Timeline.EtherHighlight(
6235 +        this._timeline, this._band, this._theme, this._backgroundLayer);
6236 +};
6237 +
6238 +Timeline.QuarterlyEtherPainter.prototype.setHighlight = function(startDate, endDate) {
6239 +    this._highlight.position(startDate, endDate);
6240 +};
6241 +
6242 +Timeline.QuarterlyEtherPainter.prototype.paint = function() {
6243 +    if (this._markerLayer) {
6244 +        this._band.removeLayerDiv(this._markerLayer);
6245 +    }
6246 +    this._markerLayer = this._band.createLayerDiv(100);
6247 +    this._markerLayer.setAttribute("name", "ether-markers"); // for debugging
6248 +    this._markerLayer.style.display = "none";
6249 +
6250 +    if (this._lineLayer) {
6251 +        this._band.removeLayerDiv(this._lineLayer);
6252 +    }
6253 +    this._lineLayer = this._band.createLayerDiv(1);
6254 +    this._lineLayer.setAttribute("name", "ether-lines"); // for debugging
6255 +    this._lineLayer.style.display = "none";
6256 +
6257 +    var minDate = new Date(0);
6258 +    var maxDate = this._band.getMaxDate();
6259 +
6260 +    minDate.setUTCFullYear(Math.max(this._startDate.getUTCFullYear(), this._band.getMinDate().getUTCFullYear()));
6261 +    minDate.setUTCMonth(this._startDate.getUTCMonth());
6262 +
6263 +    var p = this;
6264 +    var incrementDate = function(date) {
6265 +        date.setUTCMonth(date.getUTCMonth() + 3);
6266 +    };
6267 +    var labeller = {
6268 +        labelInterval: function(date, intervalUnit) {
6269 +            var quarters = (4 + (date.getUTCMonth() - p._startDate.getUTCMonth()) / 3) % 4;
6270 +            if (quarters != 0) {
6271 +                return { text: "Q" + (quarters + 1), emphasized: false };
6272 +            } else {
6273 +                return { text: "Y" + (date.getUTCFullYear() - p._startDate.getUTCFullYear() + 1), emphasized: true };
6274 +            }
6275 +        }
6276 +    };
6277 +
6278 +    while (minDate.getTime() < maxDate.getTime()) {
6279 +        this._intervalMarkerLayout.createIntervalMarker(
6280 +            minDate, labeller, SimileAjax.DateTime.YEAR, this._markerLayer, this._lineLayer);
6281 +
6282 +        incrementDate(minDate);
6283 +    }
6284 +    this._markerLayer.style.display = "block";
6285 +    this._lineLayer.style.display = "block";
6286 +};
6287 +
6288 +Timeline.QuarterlyEtherPainter.prototype.softPaint = function() {
6289 +};
6290 +
6291 +/*
6292 + *  Ether Interval Marker Layout
6293 + *
6294 + */
6295 +
6296 +Timeline.EtherIntervalMarkerLayout = function(timeline, band, theme, align, showLine) {
6297 +    var horizontal = timeline.isHorizontal();
6298 +    if (horizontal) {
6299 +        if (align == "Top") {
6300 +            this.positionDiv = function(div, offset) {
6301 +                div.style.left = offset + "px";
6302 +                div.style.top = "0px";
6303 +            };
6304 +        } else {
6305 +            this.positionDiv = function(div, offset) {
6306 +                div.style.left = offset + "px";
6307 +                div.style.bottom = "0px";
6308 +            };
6309 +        }
6310 +    } else {
6311 +        if (align == "Left") {
6312 +            this.positionDiv = function(div, offset) {
6313 +                div.style.top = offset + "px";
6314 +                div.style.left = "0px";
6315 +            };
6316 +        } else {
6317 +            this.positionDiv = function(div, offset) {
6318 +                div.style.top = offset + "px";
6319 +                div.style.right = "0px";
6320 +            };
6321 +        }
6322 +    }
6323 +
6324 +    var markerTheme = theme.ether.interval.marker;
6325 +    var lineTheme = theme.ether.interval.line;
6326 +    var weekendTheme = theme.ether.interval.weekend;
6327 +
6328 +    var stylePrefix = (horizontal ? "h" : "v") + align;
6329 +    var labelStyler = markerTheme[stylePrefix + "Styler"];
6330 +    var emphasizedLabelStyler = markerTheme[stylePrefix + "EmphasizedStyler"];
6331 +    var day = SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.DAY];
6332 +
6333 +    this.createIntervalMarker = function(date, labeller, unit, markerDiv, lineDiv) {
6334 +        var offset = Math.round(band.dateToPixelOffset(date));
6335 +
6336 +        if (showLine && unit != SimileAjax.DateTime.WEEK) {
6337 +            var divLine = timeline.getDocument().createElement("div");
6338 +            divLine.className = "timeline-ether-lines";
6339 +
6340 +            if (lineTheme.opacity < 100) {
6341 +                SimileAjax.Graphics.setOpacity(divLine, lineTheme.opacity);
6342 +            }
6343 +
6344 +            if (horizontal) {
6345 +				//divLine.className += " timeline-ether-lines-vertical";
6346 +				divLine.style.left = offset + "px";
6347 +            } else {
6348 +				//divLine.className += " timeline-ether-lines-horizontal";
6349 +                divLine.style.top = offset + "px";
6350 +            }
6351 +            lineDiv.appendChild(divLine);
6352 +        }
6353 +        if (unit == SimileAjax.DateTime.WEEK) {
6354 +            var firstDayOfWeek = theme.firstDayOfWeek;
6355 +
6356 +            var saturday = new Date(date.getTime() + (6 - firstDayOfWeek - 7) * day);
6357 +            var monday = new Date(saturday.getTime() + 2 * day);
6358 +
6359 +            var saturdayPixel = Math.round(band.dateToPixelOffset(saturday));
6360 +            var mondayPixel = Math.round(band.dateToPixelOffset(monday));
6361 +            var length = Math.max(1, mondayPixel - saturdayPixel);
6362 +
6363 +            var divWeekend = timeline.getDocument().createElement("div");
6364 +			divWeekend.className = 'timeline-ether-weekends'
6365 +
6366 +            if (weekendTheme.opacity < 100) {
6367 +                SimileAjax.Graphics.setOpacity(divWeekend, weekendTheme.opacity);
6368 +            }
6369 +
6370 +            if (horizontal) {
6371 +                divWeekend.style.left = saturdayPixel + "px";
6372 +                divWeekend.style.width = length + "px";
6373 +            } else {
6374 +                divWeekend.style.top = saturdayPixel + "px";
6375 +                divWeekend.style.height = length + "px";
6376 +            }
6377 +            lineDiv.appendChild(divWeekend);
6378 +        }
6379 +
6380 +        var label = labeller.labelInterval(date, unit);
6381 +
6382 +        var div = timeline.getDocument().createElement("div");
6383 +        div.innerHTML = label.text;
6384 +
6385 +
6386 +
6387 +		div.className = 'timeline-date-label'
6388 +		if(label.emphasized) div.className += ' timeline-date-label-em'
6389 +
6390 +        this.positionDiv(div, offset);
6391 +        markerDiv.appendChild(div);
6392 +
6393 +        return div;
6394 +    };
6395 +};
6396 +
6397 +/*
6398 + *  Ether Highlight Layout
6399 + *
6400 + */
6401 +
6402 +Timeline.EtherHighlight = function(timeline, band, theme, backgroundLayer) {
6403 +    var horizontal = timeline.isHorizontal();
6404 +
6405 +    this._highlightDiv = null;
6406 +    this._createHighlightDiv = function() {
6407 +        if (this._highlightDiv == null) {
6408 +            this._highlightDiv = timeline.getDocument().createElement("div");
6409 +            this._highlightDiv.setAttribute("name", "ether-highlight"); // for debugging
6410 +            this._highlightDiv.className = 'timeline-ether-highlight'
6411 +
6412 +            var opacity = theme.ether.highlightOpacity;
6413 +            if (opacity < 100) {
6414 +                SimileAjax.Graphics.setOpacity(this._highlightDiv, opacity);
6415 +            }
6416 +
6417 +            backgroundLayer.appendChild(this._highlightDiv);
6418 +        }
6419 +    }
6420 +
6421 +    this.position = function(startDate, endDate) {
6422 +        this._createHighlightDiv();
6423 +
6424 +        var startPixel = Math.round(band.dateToPixelOffset(startDate));
6425 +        var endPixel = Math.round(band.dateToPixelOffset(endDate));
6426 +        var length = Math.max(endPixel - startPixel, 3);
6427 +        if (horizontal) {
6428 +            this._highlightDiv.style.left = startPixel + "px";
6429 +            this._highlightDiv.style.width = length + "px";
6430 +            this._highlightDiv.style.height = (band.getViewWidth() - 4) + "px";
6431 +        } else {
6432 +            this._highlightDiv.style.top = startPixel + "px";
6433 +            this._highlightDiv.style.height = length + "px";
6434 +            this._highlightDiv.style.width = (band.getViewWidth() - 4) + "px";
6435 +        }
6436 +    }
6437 +};
6438 +/*
6439 + *  Event Utils
6440 + *
6441 + */
6442 +Timeline.EventUtils = {};
6443 +
6444 +Timeline.EventUtils.getNewEventID = function() {
6445 +    // global across page
6446 +    if (this._lastEventID == null) {
6447 +        this._lastEventID = 0;
6448 +    }
6449 +
6450 +    this._lastEventID += 1;
6451 +    return "e" + this._lastEventID;
6452 +};
6453 +
6454 +Timeline.EventUtils.decodeEventElID = function(elementID) {
6455 +    /*
6456 +     *
6457 +     * Use this function to decode an event element's id on a band (label div,
6458 +     * tape div or icon img).
6459 +     *
6460 +     * Returns {band: <bandObj>, evt: <eventObj>}
6461 +     *
6462 +     * To enable a single event listener to monitor everything
6463 +     * on a Timeline, a set format is used for the id's of the
6464 +     * elements on the Timeline--
6465 +     *
6466 +     * element id format for labels, icons, tapes:
6467 +     *   labels: label-tl-<timelineID>-<band_index>-<evt.id>
6468 +     *    icons: icon-tl-<timelineID>-<band_index>-<evt.id>
6469 +     *    tapes: tape1-tl-<timelineID>-<band_index>-<evt.id>
6470 +     *           tape2-tl-<timelineID>-<band_index>-<evt.id>
6471 +     *           // some events have more than one tape
6472 +     *    highlight: highlight1-tl-<timelineID>-<band_index>-<evt.id>
6473 +     *               highlight2-tl-<timelineID>-<band_index>-<evt.id>
6474 +     *           // some events have more than one highlight div (future)
6475 +     * Note: use split('-') to get array of the format's parts
6476 +     *
6477 +     * You can then retrieve the timeline object and event object
6478 +     * by using Timeline.getTimeline, Timeline.getBand, or
6479 +     * Timeline.getEvent and passing in the element's id
6480 +     *
6481 +     *
6482 +     */
6483 +
6484 +    var parts = elementID.split('-');
6485 +    if (parts[1] != 'tl') {
6486 +        alert("Internal Timeline problem 101, please consult support");
6487 +        return {band: null, evt: null}; // early return
6488 +    }
6489 +
6490 +    var timeline = Timeline.getTimelineFromID(parts[2]);
6491 +    var band = timeline.getBand(parts[3]);
6492 +    var evt = band.getEventSource.getEvent(parts[4]);
6493 +
6494 +    return {band: band, evt: evt};
6495 +};
6496 +
6497 +Timeline.EventUtils.encodeEventElID = function(timeline, band, elType, evt) {
6498 +    // elType should be one of {label | icon | tapeN | highlightN}
6499 +    return elType + "-tl-" + timeline.timelineID +
6500 +       "-" + band.getIndex() + "-" + evt.getID();
6501 +};/*
6502 + *  Gregorian Date Labeller
6503 + *
6504 + */
6505 +
6506 +Timeline.GregorianDateLabeller = function(locale, timeZone) {
6507 +    this._locale = locale;
6508 +    this._timeZone = timeZone;
6509 +};
6510 +
6511 +Timeline.GregorianDateLabeller.monthNames = [];
6512 +Timeline.GregorianDateLabeller.dayNames = [];
6513 +Timeline.GregorianDateLabeller.labelIntervalFunctions = [];
6514 +
6515 +Timeline.GregorianDateLabeller.getMonthName = function(month, locale) {
6516 +    return Timeline.GregorianDateLabeller.monthNames[locale][month];
6517 +};
6518 +
6519 +Timeline.GregorianDateLabeller.prototype.labelInterval = function(date, intervalUnit) {
6520 +    var f = Timeline.GregorianDateLabeller.labelIntervalFunctions[this._locale];
6521 +    if (f == null) {
6522 +        f = Timeline.GregorianDateLabeller.prototype.defaultLabelInterval;
6523 +    }
6524 +    return f.call(this, date, intervalUnit);
6525 +};
6526 +
6527 +Timeline.GregorianDateLabeller.prototype.labelPrecise = function(date) {
6528 +    return SimileAjax.DateTime.removeTimeZoneOffset(
6529 +        date,
6530 +        this._timeZone //+ (new Date().getTimezoneOffset() / 60)
6531 +    ).toUTCString();
6532 +};
6533 +
6534 +Timeline.GregorianDateLabeller.prototype.defaultLabelInterval = function(date, intervalUnit) {
6535 +    var text;
6536 +    var emphasized = false;
6537 +
6538 +    date = SimileAjax.DateTime.removeTimeZoneOffset(date, this._timeZone);
6539 +
6540 +    switch(intervalUnit) {
6541 +    case SimileAjax.DateTime.MILLISECOND:
6542 +        text = date.getUTCMilliseconds();
6543 +        break;
6544 +    case SimileAjax.DateTime.SECOND:
6545 +        text = date.getUTCSeconds();
6546 +        break;
6547 +    case SimileAjax.DateTime.MINUTE:
6548 +        var m = date.getUTCMinutes();
6549 +        if (m == 0) {
6550 +            text = date.getUTCHours() + ":00";
6551 +            emphasized = true;
6552 +        } else {
6553 +            text = m;
6554 +        }
6555 +        break;
6556 +    case SimileAjax.DateTime.HOUR:
6557 +        text = date.getUTCHours() + "hr";
6558 +        break;
6559 +    case SimileAjax.DateTime.DAY:
6560 +        text = Timeline.GregorianDateLabeller.getMonthName(date.getUTCMonth(), this._locale) + " " + date.getUTCDate();
6561 +        break;
6562 +    case SimileAjax.DateTime.WEEK:
6563 +        text = Timeline.GregorianDateLabeller.getMonthName(date.getUTCMonth(), this._locale) + " " + date.getUTCDate();
6564 +        break;
6565 +    case SimileAjax.DateTime.MONTH:
6566 +        var m = date.getUTCMonth();
6567 +        if (m != 0) {
6568 +            text = Timeline.GregorianDateLabeller.getMonthName(m, this._locale);
6569 +            break;
6570 +        } // else, fall through
6571 +    case SimileAjax.DateTime.YEAR:
6572 +    case SimileAjax.DateTime.DECADE:
6573 +    case SimileAjax.DateTime.CENTURY:
6574 +    case SimileAjax.DateTime.MILLENNIUM:
6575 +        var y = date.getUTCFullYear();
6576 +        if (y > 0) {
6577 +            text = date.getUTCFullYear();
6578 +        } else {
6579 +            text = (1 - y) + "BC";
6580 +        }
6581 +        emphasized =
6582 +            (intervalUnit == SimileAjax.DateTime.MONTH) ||
6583 +            (intervalUnit == SimileAjax.DateTime.DECADE && y % 100 == 0) ||
6584 +            (intervalUnit == SimileAjax.DateTime.CENTURY && y % 1000 == 0);
6585 +        break;
6586 +    default:
6587 +        text = date.toUTCString();
6588 +    }
6589 +    return { text: text, emphasized: emphasized };
6590 +}
6591 +
6592 +/*
6593 + *  Default Event Source
6594 + *
6595 + */
6596 +
6597 +
6598 +Timeline.DefaultEventSource = function(eventIndex) {
6599 +    this._events = (eventIndex instanceof Object) ? eventIndex : new SimileAjax.EventIndex();
6600 +    this._listeners = [];
6601 +};
6602 +
6603 +Timeline.DefaultEventSource.prototype.addListener = function(listener) {
6604 +    this._listeners.push(listener);
6605 +};
6606 +
6607 +Timeline.DefaultEventSource.prototype.removeListener = function(listener) {
6608 +    for (var i = 0; i < this._listeners.length; i++) {
6609 +        if (this._listeners[i] == listener) {
6610 +            this._listeners.splice(i, 1);
6611 +            break;
6612 +        }
6613 +    }
6614 +};
6615 +
6616 +Timeline.DefaultEventSource.prototype.loadXML = function(xml, url) {
6617 +    var base = this._getBaseURL(url);
6618 +
6619 +    var wikiURL = xml.documentElement.getAttribute("wiki-url");
6620 +    var wikiSection = xml.documentElement.getAttribute("wiki-section");
6621 +
6622 +    var dateTimeFormat = xml.documentElement.getAttribute("date-time-format");
6623 +    var parseDateTimeFunction = this._events.getUnit().getParser(dateTimeFormat);
6624 +
6625 +    var node = xml.documentElement.firstChild;
6626 +    var added = false;
6627 +    while (node != null) {
6628 +        if (node.nodeType == 1) {
6629 +            var description = "";
6630 +            if (node.firstChild != null && node.firstChild.nodeType == 3) {
6631 +                description = node.firstChild.nodeValue;
6632 +            }
6633 +            // instant event: default is true. Or use values from isDuration or durationEvent
6634 +            var instant = (node.getAttribute("isDuration")    === null &&
6635 +                           node.getAttribute("durationEvent") === null) ||
6636 +                          node.getAttribute("isDuration") == "false" ||
6637 +                          node.getAttribute("durationEvent") == "false";
6638 +
6639 +            var evt = new Timeline.DefaultEventSource.Event( {
6640 +                          id: node.getAttribute("id"),
6641 +                       start: parseDateTimeFunction(node.getAttribute("start")),
6642 +                         end: parseDateTimeFunction(node.getAttribute("end")),
6643 +                 latestStart: parseDateTimeFunction(node.getAttribute("latestStart")),
6644 +                 earliestEnd: parseDateTimeFunction(node.getAttribute("earliestEnd")),
6645 +                     instant: instant,
6646 +                        text: node.getAttribute("title"),
6647 +                 description: description,
6648 +                       image: this._resolveRelativeURL(node.getAttribute("image"), base),
6649 +                        link: this._resolveRelativeURL(node.getAttribute("link") , base),
6650 +                        icon: this._resolveRelativeURL(node.getAttribute("icon") , base),
6651 +                       color: node.getAttribute("color"),
6652 +                   textColor: node.getAttribute("textColor"),
6653 +                   hoverText: node.getAttribute("hoverText"),
6654 +                   classname: node.getAttribute("classname"),
6655 +                   tapeImage: node.getAttribute("tapeImage"),
6656 +                  tapeRepeat: node.getAttribute("tapeRepeat"),
6657 +                     caption: node.getAttribute("caption"),
6658 +                     eventID: node.getAttribute("eventID"),
6659 +                    trackNum: node.getAttribute("trackNum")
6660 +            });
6661 +
6662 +            evt._node = node;
6663 +            evt.getProperty = function(name) {
6664 +                return this._node.getAttribute(name);
6665 +            };
6666 +            evt.setWikiInfo(wikiURL, wikiSection);
6667 +
6668 +            this._events.add(evt);
6669 +
6670 +            added = true;
6671 +        }
6672 +        node = node.nextSibling;
6673 +    }
6674 +
6675 +    if (added) {
6676 +        this._fire("onAddMany", []);
6677 +    }
6678 +};
6679 +
6680 +
6681 +Timeline.DefaultEventSource.prototype.loadJSON = function(data, url) {
6682 +    var base = this._getBaseURL(url);
6683 +    var added = false;
6684 +    if (data && data.events){
6685 +        var wikiURL = ("wikiURL" in data) ? data.wikiURL : null;
6686 +        var wikiSection = ("wikiSection" in data) ? data.wikiSection : null;
6687 +
6688 +        var dateTimeFormat = ("dateTimeFormat" in data) ? data.dateTimeFormat : null;
6689 +        var parseDateTimeFunction = this._events.getUnit().getParser(dateTimeFormat);
6690 +
6691 +        for (var i=0; i < data.events.length; i++){
6692 +            var event = data.events[i];
6693 +            // Fixing issue 33:
6694 +            // instant event: default (for JSON only) is false. Or use values from isDuration or durationEvent
6695 +            // isDuration was negated (see issue 33, so keep that interpretation
6696 +            var instant = event.isDuration || (event.durationEvent != null && !event.durationEvent);
6697 +
6698 +            var evt = new Timeline.DefaultEventSource.Event({
6699 +                          id: ("id" in event) ? event.id : undefined,
6700 +                       start: parseDateTimeFunction(event.start),
6701 +                         end: parseDateTimeFunction(event.end),
6702 +                 latestStart: parseDateTimeFunction(event.latestStart),
6703 +                 earliestEnd: parseDateTimeFunction(event.earliestEnd),
6704 +                     instant: instant,
6705 +                        text: event.title,
6706 +                 description: event.description,
6707 +                       image: this._resolveRelativeURL(event.image, base),
6708 +                        link: this._resolveRelativeURL(event.link , base),
6709 +                        icon: this._resolveRelativeURL(event.icon , base),
6710 +                       color: event.color,
6711 +                   textColor: event.textColor,
6712 +                   hoverText: event.hoverText,
6713 +                   classname: event.classname,
6714 +                   tapeImage: event.tapeImage,
6715 +                  tapeRepeat: event.tapeRepeat,
6716 +                     caption: event.caption,
6717 +                     eventID: event.eventID,
6718 +                    trackNum: event.trackNum
6719 +            });
6720 +            evt._obj = event;
6721 +            evt.getProperty = function(name) {
6722 +                return this._obj[name];
6723 +            };
6724 +            evt.setWikiInfo(wikiURL, wikiSection);
6725 +
6726 +            this._events.add(evt);
6727 +            added = true;
6728 +        }
6729 +    }
6730 +
6731 +    if (added) {
6732 +        this._fire("onAddMany", []);
6733 +    }
6734 +};
6735 +
6736 +/*
6737 + *  Contributed by Morten Frederiksen, http://www.wasab.dk/morten/
6738 + */
6739 +Timeline.DefaultEventSource.prototype.loadSPARQL = function(xml, url) {
6740 +    var base = this._getBaseURL(url);
6741 +
6742 +    var dateTimeFormat = 'iso8601';
6743 +    var parseDateTimeFunction = this._events.getUnit().getParser(dateTimeFormat);
6744 +
6745 +    if (xml == null) {
6746 +        return;
6747 +    }
6748 +
6749 +    /*
6750 +     *  Find <results> tag
6751 +     */
6752 +    var node = xml.documentElement.firstChild;
6753 +    while (node != null && (node.nodeType != 1 || node.nodeName != 'results')) {
6754 +        node = node.nextSibling;
6755 +    }
6756 +
6757 +    var wikiURL = null;
6758 +    var wikiSection = null;
6759 +    if (node != null) {
6760 +        wikiURL = node.getAttribute("wiki-url");
6761 +        wikiSection = node.getAttribute("wiki-section");
6762 +
6763 +        node = node.firstChild;
6764 +    }
6765 +
6766 +    var added = false;
6767 +    while (node != null) {
6768 +        if (node.nodeType == 1) {
6769 +            var bindings = { };
6770 +            var binding = node.firstChild;
6771 +            while (binding != null) {
6772 +                if (binding.nodeType == 1 &&
6773 +                    binding.firstChild != null &&
6774 +                    binding.firstChild.nodeType == 1 &&
6775 +                    binding.firstChild.firstChild != null &&
6776 +                    binding.firstChild.firstChild.nodeType == 3) {
6777 +                    bindings[binding.getAttribute('name')] = binding.firstChild.firstChild.nodeValue;
6778 +                }
6779 +                binding = binding.nextSibling;
6780 +            }
6781 +
6782 +            if (bindings["start"] == null && bindings["date"] != null) {
6783 +                bindings["start"] = bindings["date"];
6784 +            }
6785 +
6786 +            // instant event: default is true. Or use values from isDuration or durationEvent
6787 +            var instant = (bindings["isDuration"]    === null &&
6788 +                           bindings["durationEvent"] === null) ||
6789 +                          bindings["isDuration"] == "false" ||
6790 +                          bindings["durationEvent"] == "false";
6791 +
6792 +            var evt = new Timeline.DefaultEventSource.Event({
6793 +                          id: bindings["id"],
6794 +                       start: parseDateTimeFunction(bindings["start"]),
6795 +                         end: parseDateTimeFunction(bindings["end"]),
6796 +                 latestStart: parseDateTimeFunction(bindings["latestStart"]),
6797 +                 earliestEnd: parseDateTimeFunction(bindings["earliestEnd"]),
6798 +                     instant: instant, // instant
6799 +                        text: bindings["title"], // text
6800 +                 description: bindings["description"],
6801 +                       image: this._resolveRelativeURL(bindings["image"], base),
6802 +                        link: this._resolveRelativeURL(bindings["link"] , base),
6803 +                        icon: this._resolveRelativeURL(bindings["icon"] , base),
6804 +                       color: bindings["color"],
6805 +                   textColor: bindings["textColor"],
6806 +                   hoverText: bindings["hoverText"],
6807 +                     caption: bindings["caption"],
6808 +                   classname: bindings["classname"],
6809 +                   tapeImage: bindings["tapeImage"],
6810 +                  tapeRepeat: bindings["tapeRepeat"],
6811 +                     eventID: bindings["eventID"],
6812 +                    trackNum: bindings["trackNum"]
6813 +            });
6814 +            evt._bindings = bindings;
6815 +            evt.getProperty = function(name) {
6816 +                return this._bindings[name];
6817 +            };
6818 +            evt.setWikiInfo(wikiURL, wikiSection);
6819 +
6820 +            this._events.add(evt);
6821 +            added = true;
6822 +        }
6823 +        node = node.nextSibling;
6824 +    }
6825 +
6826 +    if (added) {
6827 +        this._fire("onAddMany", []);
6828 +    }
6829 +};
6830 +
6831 +Timeline.DefaultEventSource.prototype.add = function(evt) {
6832 +    this._events.add(evt);
6833 +    this._fire("onAddOne", [evt]);
6834 +};
6835 +
6836 +Timeline.DefaultEventSource.prototype.addMany = function(events) {
6837 +    for (var i = 0; i < events.length; i++) {
6838 +        this._events.add(events[i]);
6839 +    }
6840 +    this._fire("onAddMany", []);
6841 +};
6842 +
6843 +Timeline.DefaultEventSource.prototype.clear = function() {
6844 +    this._events.removeAll();
6845 +    this._fire("onClear", []);
6846 +};
6847 +
6848 +Timeline.DefaultEventSource.prototype.getEvent = function(id) {
6849 +    return this._events.getEvent(id);
6850 +};
6851 +
6852 +Timeline.DefaultEventSource.prototype.getEventIterator = function(startDate, endDate) {
6853 +    return this._events.getIterator(startDate, endDate);
6854 +};
6855 +
6856 +Timeline.DefaultEventSource.prototype.getEventReverseIterator = function(startDate, endDate) {
6857 +    return this._events.getReverseIterator(startDate, endDate);
6858 +};
6859 +
6860 +Timeline.DefaultEventSource.prototype.getAllEventIterator = function() {
6861 +    return this._events.getAllIterator();
6862 +};
6863 +
6864 +Timeline.DefaultEventSource.prototype.getCount = function() {
6865 +    return this._events.getCount();
6866 +};
6867 +
6868 +Timeline.DefaultEventSource.prototype.getEarliestDate = function() {
6869 +    return this._events.getEarliestDate();
6870 +};
6871 +
6872 +Timeline.DefaultEventSource.prototype.getLatestDate = function() {
6873 +    return this._events.getLatestDate();
6874 +};
6875 +
6876 +Timeline.DefaultEventSource.prototype._fire = function(handlerName, args) {
6877 +    for (var i = 0; i < this._listeners.length; i++) {
6878 +        var listener = this._listeners[i];
6879 +        if (handlerName in listener) {
6880 +            try {
6881 +                listener[handlerName].apply(listener, args);
6882 +            } catch (e) {
6883 +                SimileAjax.Debug.exception(e);
6884 +            }
6885 +        }
6886 +    }
6887 +};
6888 +
6889 +Timeline.DefaultEventSource.prototype._getBaseURL = function(url) {
6890 +    if (url.indexOf("://") < 0) {
6891 +        var url2 = this._getBaseURL(document.location.href);
6892 +        if (url.substr(0,1) == "/") {
6893 +            url = url2.substr(0, url2.indexOf("/", url2.indexOf("://") + 3)) + url;
6894 +        } else {
6895 +            url = url2 + url;
6896 +        }
6897 +    }
6898 +
6899 +    var i = url.lastIndexOf("/");
6900 +    if (i < 0) {
6901 +        return "";
6902 +    } else {
6903 +        return url.substr(0, i+1);
6904 +    }
6905 +};
6906 +
6907 +Timeline.DefaultEventSource.prototype._resolveRelativeURL = function(url, base) {
6908 +    if (url == null || url == "") {
6909 +        return url;
6910 +    } else if (url.indexOf("://") > 0) {
6911 +        return url;
6912 +    } else if (url.substr(0,1) == "/") {
6913 +        return base.substr(0, base.indexOf("/", base.indexOf("://") + 3)) + url;
6914 +    } else {
6915 +        return base + url;
6916 +    }
6917 +};
6918 +
6919 +
6920 +Timeline.DefaultEventSource.Event = function(args) {
6921 +  //
6922 +  // Attention developers!
6923 +  // If you add a new event attribute, please be sure to add it to
6924 +  // all three load functions: loadXML, loadSPARCL, loadJSON.
6925 +  // Thanks!
6926 +  //
6927 +  // args is a hash/object. It supports the following keys. Most are optional
6928 +  //   id            -- an internal id. Really shouldn't be used by events.
6929 +  //                    Timeline library clients should use eventID
6930 +  //   eventID       -- For use by library client when writing custom painters or
6931 +  //                    custom fillInfoBubble
6932 +  //   start
6933 +  //   end
6934 +  //   latestStart
6935 +  //   earliestEnd
6936 +  //   instant      -- boolean. Controls precise/non-precise logic & duration/instant issues
6937 +  //   text         -- event source attribute 'title' -- used as the label on Timelines and in bubbles.
6938 +  //   description  -- used in bubbles
6939 +  //   image        -- used in bubbles
6940 +  //   link         -- used in bubbles
6941 +  //   icon         -- on the Timeline
6942 +  //   color        -- Timeline label and tape color
6943 +  //   textColor    -- Timeline label color, overrides color attribute
6944 +  //   hoverText    -- deprecated, here for backwards compatibility.
6945 +  //                   Superceeded by caption
6946 +  //   caption      -- tooltip-like caption on the Timeline. Uses HTML title attribute
6947 +  //   classname    -- used to set classname in Timeline. Enables better CSS selector rules
6948 +  //   tapeImage    -- background image of the duration event's tape div on the Timeline
6949 +  //   tapeRepeat   -- repeat attribute for tapeImage. {repeat | repeat-x | repeat-y }
6950 +
6951 +  function cleanArg(arg) {
6952 +      // clean up an arg
6953 +      return (args[arg] != null && args[arg] != "") ? args[arg] : null;
6954 +  }
6955 +
6956 +  var id = args.id ? args.id.trim() : "";
6957 +  this._id = id.length > 0 ? id : Timeline.EventUtils.getNewEventID();
6958 +
6959 +  this._instant = args.instant || (args.end == null);
6960 +
6961 +  this._start = args.start;
6962 +  this._end = (args.end != null) ? args.end : args.start;
6963 +
6964 +  this._latestStart = (args.latestStart != null) ?
6965 +                       args.latestStart : (args.instant ? this._end : this._start);
6966 +  this._earliestEnd = (args.earliestEnd != null) ? args.earliestEnd : this._end;
6967 +
6968 +  // check sanity of dates since incorrect dates will later cause calculation errors
6969 +  // when painting
6970 +  var err=[];
6971 +  if (this._start > this._latestStart) {
6972 +          this._latestStart = this._start;
6973 +          err.push("start is > latestStart");}
6974 +  if (this._start > this._earliestEnd) {
6975 +          this._earliestEnd = this._latestStart;
6976 +          err.push("start is > earliestEnd");}
6977 +  if (this._start > this._end) {
6978 +          this._end = this._earliestEnd;
6979 +          err.push("start is > end");}
6980 +  if (this._latestStart > this._earliestEnd) {
6981 +          this._earliestEnd = this._latestStart;
6982 +          err.push("latestStart is > earliestEnd");}
6983 +  if (this._latestStart > this._end) {
6984 +          this._end = this._earliestEnd;
6985 +          err.push("latestStart is > end");}
6986 +  if (this._earliestEnd > this._end) {
6987 +          this._end = this._earliestEnd;
6988 +          err.push("earliestEnd is > end");}
6989 +
6990 +  this._eventID = cleanArg('eventID');
6991 +  this._text = (args.text != null) ? SimileAjax.HTML.deEntify(args.text) : ""; // Change blank titles to ""
6992 +  if (err.length > 0) {
6993 +          this._text += " PROBLEM: " + err.join(", ");
6994 +  }
6995 +
6996 +  this._description = SimileAjax.HTML.deEntify(args.description);
6997 +  this._image = cleanArg('image');
6998 +  this._link =  cleanArg('link');
6999 +  this._title = cleanArg('hoverText');
7000 +  this._title = cleanArg('caption');
7001 +
7002 +  this._icon = cleanArg('icon');
7003 +  this._color = cleanArg('color');
7004 +  this._textColor = cleanArg('textColor');
7005 +  this._classname = cleanArg('classname');
7006 +  this._tapeImage = cleanArg('tapeImage');
7007 +  this._tapeRepeat = cleanArg('tapeRepeat');
7008 +  this._trackNum = cleanArg('trackNum');
7009 +  if (this._trackNum != null) {
7010 +      this._trackNum = parseInt(this._trackNum);
7011 +  }
7012 +
7013 +  this._wikiURL = null;
7014 +  this._wikiSection = null;
7015 +};
7016 +
7017 +Timeline.DefaultEventSource.Event.prototype = {
7018 +    getID:          function() { return this._id; },
7019 +
7020 +    isInstant:      function() { return this._instant; },
7021 +    isImprecise:    function() { return this._start != this._latestStart || this._end != this._earliestEnd; },
7022 +
7023 +    getStart:       function() { return this._start; },
7024 +    getEnd:         function() { return this._end; },
7025 +    getLatestStart: function() { return this._latestStart; },
7026 +    getEarliestEnd: function() { return this._earliestEnd; },
7027 +
7028 +    getEventID:     function() { return this._eventID; },
7029 +    getText:        function() { return this._text; }, // title
7030 +    getDescription: function() { return this._description; },
7031 +    getImage:       function() { return this._image; },
7032 +    getLink:        function() { return this._link; },
7033 +
7034 +    getIcon:        function() { return this._icon; },
7035 +    getColor:       function() { return this._color; },
7036 +    getTextColor:   function() { return this._textColor; },
7037 +    getClassName:   function() { return this._classname; },
7038 +    getTapeImage:   function() { return this._tapeImage; },
7039 +    getTapeRepeat:  function() { return this._tapeRepeat; },
7040 +    getTrackNum:    function() { return this._trackNum; },
7041 +
7042 +    getProperty:    function(name) { return null; },
7043 +
7044 +    getWikiURL:     function() { return this._wikiURL; },
7045 +    getWikiSection: function() { return this._wikiSection; },
7046 +    setWikiInfo: function(wikiURL, wikiSection) {
7047 +        this._wikiURL = wikiURL;
7048 +        this._wikiSection = wikiSection;
7049 +    },
7050 +
7051 +    fillDescription: function(elmt) {
7052 +        elmt.innerHTML = this._description;
7053 +    },
7054 +    fillWikiInfo: function(elmt) {
7055 +        // Many bubbles will not support a wiki link.
7056 +        //
7057 +        // Strategy: assume no wiki link. If we do have
7058 +        // enough parameters for one, then create it.
7059 +        elmt.style.display = "none"; // default
7060 +
7061 +        if (this._wikiURL == null || this._wikiSection == null) {
7062 +          return; // EARLY RETURN
7063 +        }
7064 +
7065 +        // create the wikiID from the property or from the event text (the title)
7066 +        var wikiID = this.getProperty("wikiID");
7067 +        if (wikiID == null || wikiID.length == 0) {
7068 +            wikiID = this.getText(); // use the title as the backup wiki id
7069 +        }
7070 +
7071 +        if (wikiID == null || wikiID.length == 0) {
7072 +          return; // No wikiID. Thus EARLY RETURN
7073 +        }
7074 +
7075 +        // ready to go...
7076 +        elmt.style.display = "inline";
7077 +        wikiID = wikiID.replace(/\s/g, "_");
7078 +        var url = this._wikiURL + this._wikiSection.replace(/\s/g, "_") + "/" + wikiID;
7079 +        var a = document.createElement("a");
7080 +        a.href = url;
7081 +        a.target = "new";
7082 +        a.innerHTML = Timeline.strings[Timeline.clientLocale].wikiLinkLabel;
7083 +
7084 +        elmt.appendChild(document.createTextNode("["));
7085 +        elmt.appendChild(a);
7086 +        elmt.appendChild(document.createTextNode("]"));
7087 +    },
7088 +
7089 +    fillTime: function(elmt, labeller) {
7090 +        if (this._instant) {
7091 +            if (this.isImprecise()) {
7092 +                elmt.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this._start)));
7093 +                elmt.appendChild(elmt.ownerDocument.createElement("br"));
7094 +                elmt.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this._end)));
7095 +            } else {
7096 +                elmt.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this._start)));
7097 +            }
7098 +        } else {
7099 +            if (this.isImprecise()) {
7100 +                elmt.appendChild(elmt.ownerDocument.createTextNode(
7101 +                    labeller.labelPrecise(this._start) + " ~ " + labeller.labelPrecise(this._latestStart)));
7102 +                elmt.appendChild(elmt.ownerDocument.createElement("br"));
7103 +                elmt.appendChild(elmt.ownerDocument.createTextNode(
7104 +                    labeller.labelPrecise(this._earliestEnd) + " ~ " + labeller.labelPrecise(this._end)));
7105 +            } else {
7106 +                elmt.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this._start)));
7107 +                elmt.appendChild(elmt.ownerDocument.createElement("br"));
7108 +                elmt.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this._end)));
7109 +            }
7110 +        }
7111 +    },
7112 +
7113 +    fillInfoBubble: function(elmt, theme, labeller) {
7114 +        var doc = elmt.ownerDocument;
7115 +
7116 +        var title = this.getText();
7117 +        var link = this.getLink();
7118 +        var image = this.getImage();
7119 +
7120 +        if (image != null) {
7121 +            var img = doc.createElement("img");
7122 +            img.src = image;
7123 +
7124 +            theme.event.bubble.imageStyler(img);
7125 +            elmt.appendChild(img);
7126 +        }
7127 +
7128 +        var divTitle = doc.createElement("div");
7129 +        var textTitle = doc.createTextNode(title);
7130 +        if (link != null) {
7131 +            var a = doc.createElement("a");
7132 +            a.href = link;
7133 +            a.appendChild(textTitle);
7134 +            divTitle.appendChild(a);
7135 +        } else {
7136 +            divTitle.appendChild(textTitle);
7137 +        }
7138 +        theme.event.bubble.titleStyler(divTitle);
7139 +        elmt.appendChild(divTitle);
7140 +
7141 +        var divBody = doc.createElement("div");
7142 +        this.fillDescription(divBody);
7143 +        theme.event.bubble.bodyStyler(divBody);
7144 +        elmt.appendChild(divBody);
7145 +
7146 +        var divTime = doc.createElement("div");
7147 +        this.fillTime(divTime, labeller);
7148 +        theme.event.bubble.timeStyler(divTime);
7149 +        elmt.appendChild(divTime);
7150 +
7151 +        var divWiki = doc.createElement("div");
7152 +        this.fillWikiInfo(divWiki);
7153 +        theme.event.bubble.wikiStyler(divWiki);
7154 +        elmt.appendChild(divWiki);
7155 +    }
7156 +};
7157 +
7158 +
7159 +/*
7160 + *  Original Event Painter
7161 + *
7162 + */
7163 +
7164 +/*
7165 + *
7166 + * To enable a single event listener to monitor everything
7167 + * on a Timeline, we need a way to map from an event's icon,
7168 + * label or tape element to the associated timeline, band and
7169 + * specific event.
7170 + *
7171 + * Thus a set format is used for the id's of the
7172 + * events' elements on the Timeline--
7173 + *
7174 + * element id format for labels, icons, tapes:
7175 + *   labels: label-tl-<timelineID>-<band_index>-<evt.id>
7176 + *    icons: icon-tl-<timelineID>-<band_index>-<evt.id>
7177 + *    tapes: tape1-tl-<timelineID>-<band_index>-<evt.id>
7178 + *           tape2-tl-<timelineID>-<band_index>-<evt.id>
7179 + *           // some events have more than one tape
7180 + *    highlight: highlight1-tl-<timelineID>-<band_index>-<evt.id>
7181 + *               highlight2-tl-<timelineID>-<band_index>-<evt.id>
7182 + *           // some events have more than one highlight div (future)
7183 + * You can then retrieve the band/timeline objects and event object
7184 + * by using Timeline.EventUtils.decodeEventElID
7185 + *
7186 + *
7187 + */
7188 +
7189 +/*
7190 + *    eventPaintListener functions receive calls about painting.
7191 + *    function(band, op, evt, els)
7192 + *       context: 'this' will be an OriginalEventPainter object.
7193 + *                It has properties and methods for obtaining
7194 + *                the relevant band, timeline, etc
7195 + *       band = the band being painted
7196 + *       op = 'paintStarting' // the painter is about to remove
7197 + *            all previously painted events, if any. It will
7198 + *            then start painting all of the visible events that
7199 + *            pass the filter.
7200 + *            evt = null, els = null
7201 + *       op = 'paintEnded' // the painter has finished painting
7202 + *            all of the visible events that passed the filter
7203 + *            evt = null, els = null
7204 + *       op = 'paintedEvent' // the painter just finished painting an event
7205 + *            evt = event just painted
7206 + *            els = array of painted elements' divs. Depending on the event,
7207 + *                  the array could be just a tape or icon (if no label).
7208 + *                  Or could include label, multiple tape divs (imprecise event),
7209 + *                  highlight divs. The array is not ordered. The meaning of
7210 + *                  each el is available by decoding the el's id
7211 + *      Note that there may be no paintedEvent calls if no events were visible
7212 + *      or passed the filter.
7213 + */
7214 +
7215 +Timeline.OriginalEventPainter = function(params) {
7216 +    this._params = params;
7217 +    this._onSelectListeners = [];
7218 +    this._eventPaintListeners = [];
7219 +
7220 +    this._filterMatcher = null;
7221 +    this._highlightMatcher = null;
7222 +    this._frc = null;
7223 +
7224 +    this._eventIdToElmt = {};
7225 +};
7226 +
7227 +Timeline.OriginalEventPainter.prototype.initialize = function(band, timeline) {
7228 +    this._band = band;
7229 +    this._timeline = timeline;
7230 +
7231 +    this._backLayer = null;
7232 +    this._eventLayer = null;
7233 +    this._lineLayer = null;
7234 +    this._highlightLayer = null;
7235 +
7236 +    this._eventIdToElmt = null;
7237 +};
7238 +
7239 +Timeline.OriginalEventPainter.prototype.getType = function() {
7240 +    return 'original';
7241 +};
7242 +
7243 +Timeline.OriginalEventPainter.prototype.addOnSelectListener = function(listener) {
7244 +    this._onSelectListeners.push(listener);
7245 +};
7246 +
7247 +Timeline.OriginalEventPainter.prototype.removeOnSelectListener = function(listener) {
7248 +    for (var i = 0; i < this._onSelectListeners.length; i++) {
7249 +        if (this._onSelectListeners[i] == listener) {
7250 +            this._onSelectListeners.splice(i, 1);
7251 +            break;
7252 +        }
7253 +    }
7254 +};
7255 +
7256 +Timeline.OriginalEventPainter.prototype.addEventPaintListener = function(listener) {
7257 +    this._eventPaintListeners.push(listener);
7258 +};
7259 +
7260 +Timeline.OriginalEventPainter.prototype.removeEventPaintListener = function(listener) {
7261 +    for (var i = 0; i < this._eventPaintListeners.length; i++) {
7262 +        if (this._eventPaintListeners[i] == listener) {
7263 +            this._eventPaintListeners.splice(i, 1);
7264 +            break;
7265 +        }
7266 +    }
7267 +};
7268 +
7269 +Timeline.OriginalEventPainter.prototype.getFilterMatcher = function() {
7270 +    return this._filterMatcher;
7271 +};
7272 +
7273 +Timeline.OriginalEventPainter.prototype.setFilterMatcher = function(filterMatcher) {
7274 +    this._filterMatcher = filterMatcher;
7275 +};
7276 +
7277 +Timeline.OriginalEventPainter.prototype.getHighlightMatcher = function() {
7278 +    return this._highlightMatcher;
7279 +};
7280 +
7281 +Timeline.OriginalEventPainter.prototype.setHighlightMatcher = function(highlightMatcher) {
7282 +    this._highlightMatcher = highlightMatcher;
7283 +};
7284 +
7285 +Timeline.OriginalEventPainter.prototype.paint = function() {
7286 +    // Paints the events for a given section of the band--what is
7287 +    // visible on screen and some extra.
7288 +    var eventSource = this._band.getEventSource();
7289 +    if (eventSource == null) {
7290 +        return;
7291 +    }
7292 +
7293 +    this._eventIdToElmt = {};
7294 +    this._fireEventPaintListeners('paintStarting', null, null);
7295 +    this._prepareForPainting();
7296 +
7297 +    var metrics = this._computeMetrics();
7298 +    var minDate = this._band.getMinDate();
7299 +    var maxDate = this._band.getMaxDate();
7300 +
7301 +    var filterMatcher = (this._filterMatcher != null) ?
7302 +        this._filterMatcher :
7303 +        function(evt) { return true; };
7304 +    var highlightMatcher = (this._highlightMatcher != null) ?
7305 +        this._highlightMatcher :
7306 +        function(evt) { return -1; };
7307 +
7308 +    var iterator = eventSource.getEventReverseIterator(minDate, maxDate);
7309 +    while (iterator.hasNext()) {
7310 +        var evt = iterator.next();
7311 +        if (filterMatcher(evt)) {
7312 +            this.paintEvent(evt, metrics, this._params.theme, highlightMatcher(evt));
7313 +        }
7314 +    }
7315 +
7316 +    this._highlightLayer.style.display = "block";
7317 +    this._lineLayer.style.display = "block";
7318 +    this._eventLayer.style.display = "block";
7319 +    // update the band object for max number of tracks in this section of the ether
7320 +    this._band.updateEventTrackInfo(this._tracks.length, metrics.trackIncrement);
7321 +    this._fireEventPaintListeners('paintEnded', null, null);
7322 +
7323 +    this._setOrthogonalOffset(metrics);
7324 +};
7325 +
7326 +Timeline.OriginalEventPainter.prototype.softPaint = function() {
7327 +    this._setOrthogonalOffset(this._computeMetrics());
7328 +};
7329 +
7330 +Timeline.OriginalEventPainter.prototype._setOrthogonalOffset = function(metrics) {
7331 +    var actualViewWidth = 2 * metrics.trackOffset + this._tracks.length * metrics.trackIncrement;
7332 +    var minOrthogonalOffset = Math.min(0, this._band.getViewWidth() - actualViewWidth);
7333 +    var orthogonalOffset = Math.max(minOrthogonalOffset, this._band.getViewOrthogonalOffset());
7334 +
7335 +    this._highlightLayer.style.top =
7336 +        this._lineLayer.style.top =
7337 +            this._eventLayer.style.top =
7338 +                orthogonalOffset + "px";
7339 +};
7340 +
7341 +Timeline.OriginalEventPainter.prototype._computeMetrics = function() {
7342 +     var eventTheme = this._params.theme.event;
7343 +     var trackHeight = Math.max(eventTheme.track.height, eventTheme.tape.height +
7344 +                         this._frc.getLineHeight());
7345 +     var metrics = {
7346 +            trackOffset: eventTheme.track.offset,
7347 +            trackHeight: trackHeight,
7348 +               trackGap: eventTheme.track.gap,
7349 +         trackIncrement: trackHeight + eventTheme.track.gap,
7350 +                   icon: eventTheme.instant.icon,
7351 +              iconWidth: eventTheme.instant.iconWidth,
7352 +             iconHeight: eventTheme.instant.iconHeight,
7353 +             labelWidth: eventTheme.label.width,
7354 +           maxLabelChar: eventTheme.label.maxLabelChar,
7355 +    impreciseIconMargin: eventTheme.instant.impreciseIconMargin
7356 +     };
7357 +
7358 +     return metrics;
7359 +};
7360 +
7361 +Timeline.OriginalEventPainter.prototype._prepareForPainting = function() {
7362 +    // Remove everything previously painted: highlight, line and event layers.
7363 +    // Prepare blank layers for painting.
7364 +    var band = this._band;
7365 +
7366 +    if (this._backLayer == null) {
7367 +        this._backLayer = this._band.createLayerDiv(0, "timeline-band-events");
7368 +        this._backLayer.style.visibility = "hidden";
7369 +
7370 +        var eventLabelPrototype = document.createElement("span");
7371 +        eventLabelPrototype.className = "timeline-event-label";
7372 +        this._backLayer.appendChild(eventLabelPrototype);
7373 +        this._frc = SimileAjax.Graphics.getFontRenderingContext(eventLabelPrototype);
7374 +    }
7375 +    this._frc.update();
7376 +    this._tracks = [];
7377 +
7378 +    if (this._highlightLayer != null) {
7379 +        band.removeLayerDiv(this._highlightLayer);
7380 +    }
7381 +    this._highlightLayer = band.createLayerDiv(105, "timeline-band-highlights");
7382 +    this._highlightLayer.style.display = "none";
7383 +
7384 +    if (this._lineLayer != null) {
7385 +        band.removeLayerDiv(this._lineLayer);
7386 +    }
7387 +    this._lineLayer = band.createLayerDiv(110, "timeline-band-lines");
7388 +    this._lineLayer.style.display = "none";
7389 +
7390 +    if (this._eventLayer != null) {
7391 +        band.removeLayerDiv(this._eventLayer);
7392 +    }
7393 +    this._eventLayer = band.createLayerDiv(115, "timeline-band-events");
7394 +    this._eventLayer.style.display = "none";
7395 +};
7396 +
7397 +Timeline.OriginalEventPainter.prototype.paintEvent = function(evt, metrics, theme, highlightIndex) {
7398 +    if (evt.isInstant()) {
7399 +        this.paintInstantEvent(evt, metrics, theme, highlightIndex);
7400 +    } else {
7401 +        this.paintDurationEvent(evt, metrics, theme, highlightIndex);
7402 +    }
7403 +};
7404 +
7405 +Timeline.OriginalEventPainter.prototype.paintInstantEvent = function(evt, metrics, theme, highlightIndex) {
7406 +    if (evt.isImprecise()) {
7407 +        this.paintImpreciseInstantEvent(evt, metrics, theme, highlightIndex);
7408 +    } else {
7409 +        this.paintPreciseInstantEvent(evt, metrics, theme, highlightIndex);
7410 +    }
7411 +}
7412 +
7413 +Timeline.OriginalEventPainter.prototype.paintDurationEvent = function(evt, metrics, theme, highlightIndex) {
7414 +    if (evt.isImprecise()) {
7415 +        this.paintImpreciseDurationEvent(evt, metrics, theme, highlightIndex);
7416 +    } else {
7417 +        this.paintPreciseDurationEvent(evt, metrics, theme, highlightIndex);
7418 +    }
7419 +}
7420 +
7421 +Timeline.OriginalEventPainter.prototype.paintPreciseInstantEvent = function(evt, metrics, theme, highlightIndex) {
7422 +    var doc = this._timeline.getDocument();
7423 +    var text = evt.getText();
7424 +
7425 +    var startDate = evt.getStart();
7426 +    var startPixel = Math.round(this._band.dateToPixelOffset(startDate));
7427 +    var iconRightEdge = Math.round(startPixel + metrics.iconWidth / 2);
7428 +    var iconLeftEdge = Math.round(startPixel - metrics.iconWidth / 2);
7429 +
7430 +    var labelDivClassName = this._getLabelDivClassName(evt);
7431 +    var labelSize = this._frc.computeSize(text, labelDivClassName);
7432 +    var labelLeft = iconRightEdge + theme.event.label.offsetFromLine;
7433 +    var labelRight = labelLeft + labelSize.width;
7434 +
7435 +    var rightEdge = labelRight;
7436 +    var track = this._findFreeTrack(evt, rightEdge);
7437 +
7438 +    var labelTop = Math.round(
7439 +        metrics.trackOffset + track * metrics.trackIncrement +
7440 +        metrics.trackHeight / 2 - labelSize.height / 2);
7441 +
7442 +    var iconElmtData = this._paintEventIcon(evt, track, iconLeftEdge, metrics, theme, 0);
7443 +    var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width,
7444 +        labelSize.height, theme, labelDivClassName, highlightIndex);
7445 +    var els = [iconElmtData.elmt, labelElmtData.elmt];
7446 +
7447 +    var self = this;
7448 +    var clickHandler = function(elmt, domEvt, target) {
7449 +        return self._onClickInstantEvent(iconElmtData.elmt, domEvt, evt);
7450 +    };
7451 +    SimileAjax.DOM.registerEvent(iconElmtData.elmt, "mousedown", clickHandler);
7452 +    SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler);
7453 +
7454 +    var hDiv = this._createHighlightDiv(highlightIndex, iconElmtData, theme, evt);
7455 +    if (hDiv != null) {els.push(hDiv);}
7456 +    this._fireEventPaintListeners('paintedEvent', evt, els);
7457 +
7458 +
7459 +    this._eventIdToElmt[evt.getID()] = iconElmtData.elmt;
7460 +    this._tracks[track] = iconLeftEdge;
7461 +};
7462 +
7463 +Timeline.OriginalEventPainter.prototype.paintImpreciseInstantEvent = function(evt, metrics, theme, highlightIndex) {
7464 +    var doc = this._timeline.getDocument();
7465 +    var text = evt.getText();
7466 +
7467 +    var startDate = evt.getStart();
7468 +    var endDate = evt.getEnd();
7469 +    var startPixel = Math.round(this._band.dateToPixelOffset(startDate));
7470 +    var endPixel = Math.round(this._band.dateToPixelOffset(endDate));
7471 +
7472 +    var iconRightEdge = Math.round(startPixel + metrics.iconWidth / 2);
7473 +    var iconLeftEdge = Math.round(startPixel - metrics.iconWidth / 2);
7474 +
7475 +    var labelDivClassName = this._getLabelDivClassName(evt);
7476 +    var labelSize = this._frc.computeSize(text, labelDivClassName);
7477 +    var labelLeft = iconRightEdge + theme.event.label.offsetFromLine;
7478 +    var labelRight = labelLeft + labelSize.width;
7479 +
7480 +    var rightEdge = Math.max(labelRight, endPixel);
7481 +    var track = this._findFreeTrack(evt, rightEdge);
7482 +    var tapeHeight = theme.event.tape.height;
7483 +    var labelTop = Math.round(
7484 +        metrics.trackOffset + track * metrics.trackIncrement + tapeHeight);
7485 +
7486 +    var iconElmtData = this._paintEventIcon(evt, track, iconLeftEdge, metrics, theme, tapeHeight);
7487 +    var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width,
7488 +                        labelSize.height, theme, labelDivClassName, highlightIndex);
7489 +
7490 +    var color = evt.getColor();
7491 +    color = color != null ? color : theme.event.instant.impreciseColor;
7492 +
7493 +    var tapeElmtData = this._paintEventTape(evt, track, startPixel, endPixel,
7494 +        color, theme.event.instant.impreciseOpacity, metrics, theme, 0);
7495 +    var els = [iconElmtData.elmt, labelElmtData.elmt, tapeElmtData.elmt];
7496 +
7497 +    var self = this;
7498 +    var clickHandler = function(elmt, domEvt, target) {
7499 +        return self._onClickInstantEvent(iconElmtData.elmt, domEvt, evt);
7500 +    };
7501 +    SimileAjax.DOM.registerEvent(iconElmtData.elmt, "mousedown", clickHandler);
7502 +    SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler);
7503 +    SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler);
7504 +
7505 +    var hDiv = this._createHighlightDiv(highlightIndex, iconElmtData, theme, evt);
7506 +    if (hDiv != null) {els.push(hDiv);}
7507 +    this._fireEventPaintListeners('paintedEvent', evt, els);
7508 +
7509 +    this._eventIdToElmt[evt.getID()] = iconElmtData.elmt;
7510 +    this._tracks[track] = iconLeftEdge;
7511 +};
7512 +
7513 +Timeline.OriginalEventPainter.prototype.paintPreciseDurationEvent = function(evt, metrics, theme, highlightIndex) {
7514 +    var doc = this._timeline.getDocument();
7515 +    var text = evt.getText();
7516 +
7517 +    var startDate = evt.getStart();
7518 +    var endDate = evt.getEnd();
7519 +    var startPixel = Math.round(this._band.dateToPixelOffset(startDate));
7520 +    var endPixel = Math.round(this._band.dateToPixelOffset(endDate));
7521 +
7522 +    var labelDivClassName = this._getLabelDivClassName(evt);
7523 +    var labelSize = this._frc.computeSize(text, labelDivClassName);
7524 +    var labelLeft = startPixel;
7525 +    var labelRight = labelLeft + labelSize.width;
7526 +
7527 +    var rightEdge = Math.max(labelRight, endPixel);
7528 +    var track = this._findFreeTrack(evt, rightEdge);
7529 +    var labelTop = Math.round(
7530 +        metrics.trackOffset + track * metrics.trackIncrement + theme.event.tape.height);
7531 +
7532 +    var color = evt.getColor();
7533 +    color = color != null ? color : theme.event.duration.color;
7534 +
7535 +    var tapeElmtData = this._paintEventTape(evt, track, startPixel, endPixel, color, 100, metrics, theme, 0);
7536 +    var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width,
7537 +      labelSize.height, theme, labelDivClassName, highlightIndex);
7538 +    var els = [tapeElmtData.elmt, labelElmtData.elmt];
7539 +
7540 +    var self = this;
7541 +    var clickHandler = function(elmt, domEvt, target) {
7542 +        return self._onClickDurationEvent(tapeElmtData.elmt, domEvt, evt);
7543 +    };
7544 +    SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler);
7545 +    SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler);
7546 +
7547 +    var hDiv = this._createHighlightDiv(highlightIndex, tapeElmtData, theme, evt);
7548 +    if (hDiv != null) {els.push(hDiv);}
7549 +    this._fireEventPaintListeners('paintedEvent', evt, els);
7550 +
7551 +    this._eventIdToElmt[evt.getID()] = tapeElmtData.elmt;
7552 +    this._tracks[track] = startPixel;
7553 +};
7554 +
7555 +Timeline.OriginalEventPainter.prototype.paintImpreciseDurationEvent = function(evt, metrics, theme, highlightIndex) {
7556 +    var doc = this._timeline.getDocument();
7557 +    var text = evt.getText();
7558 +
7559 +    var startDate = evt.getStart();
7560 +    var latestStartDate = evt.getLatestStart();
7561 +    var endDate = evt.getEnd();
7562 +    var earliestEndDate = evt.getEarliestEnd();
7563 +
7564 +    var startPixel = Math.round(this._band.dateToPixelOffset(startDate));
7565 +    var latestStartPixel = Math.round(this._band.dateToPixelOffset(latestStartDate));
7566 +    var endPixel = Math.round(this._band.dateToPixelOffset(endDate));
7567 +    var earliestEndPixel = Math.round(this._band.dateToPixelOffset(earliestEndDate));
7568 +
7569 +    var labelDivClassName = this._getLabelDivClassName(evt);
7570 +    var labelSize = this._frc.computeSize(text, labelDivClassName);
7571 +    var labelLeft = latestStartPixel;
7572 +    var labelRight = labelLeft + labelSize.width;
7573 +
7574 +    var rightEdge = Math.max(labelRight, endPixel);
7575 +    var track = this._findFreeTrack(evt, rightEdge);
7576 +    var labelTop = Math.round(
7577 +        metrics.trackOffset + track * metrics.trackIncrement + theme.event.tape.height);
7578 +
7579 +    var color = evt.getColor();
7580 +    color = color != null ? color : theme.event.duration.color;
7581 +
7582 +    // Imprecise events can have two event tapes
7583 +    // The imprecise dates tape, uses opacity to be dimmer than precise dates
7584 +    var impreciseTapeElmtData = this._paintEventTape(evt, track, startPixel, endPixel,
7585 +        theme.event.duration.impreciseColor,
7586 +        theme.event.duration.impreciseOpacity, metrics, theme, 0);
7587 +    // The precise dates tape, regular (100%) opacity
7588 +    var tapeElmtData = this._paintEventTape(evt, track, latestStartPixel,
7589 +        earliestEndPixel, color, 100, metrics, theme, 1);
7590 +
7591 +    var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop,
7592 +        labelSize.width, labelSize.height, theme, labelDivClassName, highlightIndex);
7593 +    var els = [impreciseTapeElmtData.elmt, tapeElmtData.elmt, labelElmtData.elmt];
7594 +
7595 +    var self = this;
7596 +    var clickHandler = function(elmt, domEvt, target) {
7597 +        return self._onClickDurationEvent(tapeElmtData.elmt, domEvt, evt);
7598 +    };
7599 +    SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler);
7600 +    SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler);
7601 +
7602 +    var hDiv = this._createHighlightDiv(highlightIndex, tapeElmtData, theme, evt);
7603 +    if (hDiv != null) {els.push(hDiv);}
7604 +    this._fireEventPaintListeners('paintedEvent', evt, els);
7605 +
7606 +    this._eventIdToElmt[evt.getID()] = tapeElmtData.elmt;
7607 +    this._tracks[track] = startPixel;
7608 +};
7609 +
7610 +Timeline.OriginalEventPainter.prototype._encodeEventElID = function(elType, evt) {
7611 +    return Timeline.EventUtils.encodeEventElID(this._timeline, this._band, elType, evt);
7612 +};
7613 +
7614 +Timeline.OriginalEventPainter.prototype._findFreeTrack = function(event, rightEdge) {
7615 +    var trackAttribute = event.getTrackNum();
7616 +    if (trackAttribute != null) {
7617 +        return trackAttribute; // early return since event includes track number
7618 +    }
7619 +
7620 +    // normal case: find an open track
7621 +    for (var i = 0; i < this._tracks.length; i++) {
7622 +        var t = this._tracks[i];
7623 +        if (t > rightEdge) {
7624 +            break;
7625 +        }
7626 +    }
7627 +    return i;
7628 +};
7629 +
7630 +Timeline.OriginalEventPainter.prototype._paintEventIcon = function(evt, iconTrack, left, metrics, theme, tapeHeight) {
7631 +    // If no tape, then paint the icon in the middle of the track.
7632 +    // If there is a tape, paint the icon below the tape + impreciseIconMargin
7633 +    var icon = evt.getIcon();
7634 +    icon = icon != null ? icon : metrics.icon;
7635 +
7636 +    var top; // top of the icon
7637 +    if (tapeHeight > 0) {
7638 +        top = metrics.trackOffset + iconTrack * metrics.trackIncrement +
7639 +              tapeHeight + metrics.impreciseIconMargin;
7640 +    } else {
7641 +        var middle = metrics.trackOffset + iconTrack * metrics.trackIncrement +
7642 +                     metrics.trackHeight / 2;
7643 +        top = Math.round(middle - metrics.iconHeight / 2);
7644 +    }
7645 +    var img = SimileAjax.Graphics.createTranslucentImage(icon);
7646 +    var iconDiv = this._timeline.getDocument().createElement("div");
7647 +    iconDiv.className = this._getElClassName('timeline-event-icon', evt, 'icon');
7648 +    iconDiv.id = this._encodeEventElID('icon', evt);
7649 +    iconDiv.style.left = left + "px";
7650 +    iconDiv.style.top = top + "px";
7651 +    iconDiv.appendChild(img);
7652 +
7653 +    if(evt._title != null)
7654 +        iconDiv.title = evt._title;
7655 +
7656 +    this._eventLayer.appendChild(iconDiv);
7657 +
7658 +    return {
7659 +        left:   left,
7660 +        top:    top,
7661 +        width:  metrics.iconWidth,
7662 +        height: metrics.iconHeight,
7663 +        elmt:   iconDiv
7664 +    };
7665 +};
7666 +
7667 +Timeline.OriginalEventPainter.prototype._paintEventLabel = function(evt, text, left, top, width,
7668 +    height, theme, labelDivClassName, highlightIndex) {
7669 +    var doc = this._timeline.getDocument();
7670 +
7671 +    var labelDiv = doc.createElement("div");
7672 +    labelDiv.className = labelDivClassName;
7673 +    labelDiv.id = this._encodeEventElID('label', evt);
7674 +    labelDiv.style.left = left + "px";
7675 +    labelDiv.style.width = width + "px";
7676 +    labelDiv.style.top = top + "px";
7677 +    labelDiv.innerHTML = text;
7678 +
7679 +    if(evt._title != null)
7680 +        labelDiv.title = evt._title;
7681 +
7682 +    var color = evt.getTextColor();
7683 +    if (color == null) {
7684 +        color = evt.getColor();
7685 +    }
7686 +    if (color != null) {
7687 +        labelDiv.style.color = color;
7688 +    }
7689 +    if (theme.event.highlightLabelBackground && highlightIndex >= 0) {
7690 +        labelDiv.style.background = this._getHighlightColor(highlightIndex, theme);
7691 +    }
7692 +
7693 +    this._eventLayer.appendChild(labelDiv);
7694 +
7695 +    return {
7696 +        left:   left,
7697 +        top:    top,
7698 +        width:  width,
7699 +        height: height,
7700 +        elmt:   labelDiv
7701 +    };
7702 +};
7703 +
7704 +Timeline.OriginalEventPainter.prototype._paintEventTape = function(
7705 +    evt, iconTrack, startPixel, endPixel, color, opacity, metrics, theme, tape_index) {
7706 +
7707 +    var tapeWidth = endPixel - startPixel;
7708 +    var tapeHeight = theme.event.tape.height;
7709 +    var top = metrics.trackOffset + iconTrack * metrics.trackIncrement;
7710 +
7711 +    var tapeDiv = this._timeline.getDocument().createElement("div");
7712 +    tapeDiv.className = this._getElClassName('timeline-event-tape', evt, 'tape');
7713 +    tapeDiv.id = this._encodeEventElID('tape' + tape_index, evt);
7714 +    tapeDiv.style.left = startPixel + "px";
7715 +    tapeDiv.style.width = tapeWidth + "px";
7716 +    tapeDiv.style.height = tapeHeight + "px";
7717 +    tapeDiv.style.top = top + "px";
7718 +
7719 +    if(evt._title != null)
7720 +        tapeDiv.title = evt._title;
7721 +
7722 +    if(color != null) {
7723 +        tapeDiv.style.backgroundColor = color;
7724 +    }
7725 +
7726 +    var backgroundImage = evt.getTapeImage();
7727 +    var backgroundRepeat = evt.getTapeRepeat();
7728 +    backgroundRepeat = backgroundRepeat != null ? backgroundRepeat : 'repeat';
7729 +    if(backgroundImage != null) {
7730 +      tapeDiv.style.backgroundImage = "url(" + backgroundImage + ")";
7731 +      tapeDiv.style.backgroundRepeat = backgroundRepeat;
7732 +    }
7733 +
7734 +    SimileAjax.Graphics.setOpacity(tapeDiv, opacity);
7735 +
7736 +    this._eventLayer.appendChild(tapeDiv);
7737 +
7738 +    return {
7739 +        left:   startPixel,
7740 +        top:    top,
7741 +        width:  tapeWidth,
7742 +        height: tapeHeight,
7743 +        elmt:   tapeDiv
7744 +    };
7745 +}
7746 +
7747 +Timeline.OriginalEventPainter.prototype._getLabelDivClassName = function(evt) {
7748 +    return this._getElClassName('timeline-event-label', evt, 'label');
7749 +};
7750 +
7751 +Timeline.OriginalEventPainter.prototype._getElClassName = function(elClassName, evt, prefix) {
7752 +    // Prefix and '_' is added to the event's classname. Set to null for no prefix
7753 +    var evt_classname = evt.getClassName(),
7754 +        pieces = [];
7755 +
7756 +    if (evt_classname) {
7757 +      if (prefix) {pieces.push(prefix + '-' + evt_classname + ' ');}
7758 +      pieces.push(evt_classname + ' ');
7759 +    }
7760 +    pieces.push(elClassName);
7761 +    return(pieces.join(''));
7762 +};
7763 +
7764 +Timeline.OriginalEventPainter.prototype._getHighlightColor = function(highlightIndex, theme) {
7765 +    var highlightColors = theme.event.highlightColors;
7766 +    return highlightColors[Math.min(highlightIndex, highlightColors.length - 1)];
7767 +};
7768 +
7769 +Timeline.OriginalEventPainter.prototype._createHighlightDiv = function(highlightIndex, dimensions, theme, evt) {
7770 +    var div = null;
7771 +    if (highlightIndex >= 0) {
7772 +        var doc = this._timeline.getDocument();
7773 +        var color = this._getHighlightColor(highlightIndex, theme);
7774 +
7775 +        div = doc.createElement("div");
7776 +        div.className = this._getElClassName('timeline-event-highlight', evt, 'highlight');
7777 +        div.id = this._encodeEventElID('highlight0', evt); // in future will have other
7778 +                                                           // highlight divs for tapes + icons
7779 +        div.style.position = "absolute";
7780 +        div.style.overflow = "hidden";
7781 +        div.style.left =    (dimensions.left - 2) + "px";
7782 +        div.style.width =   (dimensions.width + 4) + "px";
7783 +        div.style.top =     (dimensions.top - 2) + "px";
7784 +        div.style.height =  (dimensions.height + 4) + "px";
7785 +        div.style.background = color;
7786 +
7787 +        this._highlightLayer.appendChild(div);
7788 +    }
7789 +    return div;
7790 +};
7791 +
7792 +Timeline.OriginalEventPainter.prototype._onClickInstantEvent = function(icon, domEvt, evt) {
7793 +    var c = SimileAjax.DOM.getPageCoordinates(icon);
7794 +    this._showBubble(
7795 +        c.left + Math.ceil(icon.offsetWidth / 2),
7796 +        c.top + Math.ceil(icon.offsetHeight / 2),
7797 +        evt
7798 +    );
7799 +    this._fireOnSelect(evt.getID());
7800 +
7801 +    domEvt.cancelBubble = true;
7802 +    SimileAjax.DOM.cancelEvent(domEvt);
7803 +    return false;
7804 +};
7805 +
7806 +Timeline.OriginalEventPainter.prototype._onClickDurationEvent = function(target, domEvt, evt) {
7807 +    if ("pageX" in domEvt) {
7808 +        var x = domEvt.pageX;
7809 +        var y = domEvt.pageY;
7810 +    } else {
7811 +        var c = SimileAjax.DOM.getPageCoordinates(target);
7812 +        var x = domEvt.offsetX + c.left;
7813 +        var y = domEvt.offsetY + c.top;
7814 +    }
7815 +    this._showBubble(x, y, evt);
7816 +    this._fireOnSelect(evt.getID());
7817 +
7818 +    domEvt.cancelBubble = true;
7819 +    SimileAjax.DOM.cancelEvent(domEvt);
7820 +    return false;
7821 +};
7822 +
7823 +Timeline.OriginalEventPainter.prototype.showBubble = function(evt) {
7824 +    var elmt = this._eventIdToElmt[evt.getID()];
7825 +    if (elmt) {
7826 +        var c = SimileAjax.DOM.getPageCoordinates(elmt);
7827 +        this._showBubble(c.left + elmt.offsetWidth / 2, c.top + elmt.offsetHeight / 2, evt);
7828 +    }
7829 +};
7830 +
7831 +Timeline.OriginalEventPainter.prototype._showBubble = function(x, y, evt) {
7832 +    var div = document.createElement("div");
7833 +    var themeBubble = this._params.theme.event.bubble;
7834 +    evt.fillInfoBubble(div, this._params.theme, this._band.getLabeller());
7835 +
7836 +    SimileAjax.WindowManager.cancelPopups();
7837 +    SimileAjax.Graphics.createBubbleForContentAndPoint(div, x, y,
7838 +        themeBubble.width, null, themeBubble.maxHeight);
7839 +};
7840 +
7841 +Timeline.OriginalEventPainter.prototype._fireOnSelect = function(eventID) {
7842 +    for (var i = 0; i < this._onSelectListeners.length; i++) {
7843 +        this._onSelectListeners[i](eventID);
7844 +    }
7845 +};
7846 +
7847 +Timeline.OriginalEventPainter.prototype._fireEventPaintListeners = function(op, evt, els) {
7848 +    for (var i = 0; i < this._eventPaintListeners.length; i++) {
7849 +        this._eventPaintListeners[i](this._band, op, evt, els);
7850 +    }
7851 +};
7852 +/*
7853 + *  Detailed Event Painter
7854 + *
7855 + */
7856 +
7857 +// Note: a number of features from original-painter
7858 +//       are not yet implemented in detailed painter.
7859 +//       Eg classname, id attributes for icons, labels, tapes
7860 +
7861 +Timeline.DetailedEventPainter = function(params) {
7862 +    this._params = params;
7863 +    this._onSelectListeners = [];
7864 +
7865 +    this._filterMatcher = null;
7866 +    this._highlightMatcher = null;
7867 +    this._frc = null;
7868 +
7869 +    this._eventIdToElmt = {};
7870 +};
7871 +
7872 +Timeline.DetailedEventPainter.prototype.initialize = function(band, timeline) {
7873 +    this._band = band;
7874 +    this._timeline = timeline;
7875 +
7876 +    this._backLayer = null;
7877 +    this._eventLayer = null;
7878 +    this._lineLayer = null;
7879 +    this._highlightLayer = null;
7880 +
7881 +    this._eventIdToElmt = null;
7882 +};
7883 +
7884 +Timeline.DetailedEventPainter.prototype.getType = function() {
7885 +    return 'detailed';
7886 +};
7887 +
7888 +Timeline.DetailedEventPainter.prototype.addOnSelectListener = function(listener) {
7889 +    this._onSelectListeners.push(listener);
7890 +};
7891 +
7892 +Timeline.DetailedEventPainter.prototype.removeOnSelectListener = function(listener) {
7893 +    for (var i = 0; i < this._onSelectListeners.length; i++) {
7894 +        if (this._onSelectListeners[i] == listener) {
7895 +            this._onSelectListeners.splice(i, 1);
7896 +            break;
7897 +        }
7898 +    }
7899 +};
7900 +
7901 +Timeline.DetailedEventPainter.prototype.getFilterMatcher = function() {
7902 +    return this._filterMatcher;
7903 +};
7904 +
7905 +Timeline.DetailedEventPainter.prototype.setFilterMatcher = function(filterMatcher) {
7906 +    this._filterMatcher = filterMatcher;
7907 +};
7908 +
7909 +Timeline.DetailedEventPainter.prototype.getHighlightMatcher = function() {
7910 +    return this._highlightMatcher;
7911 +};
7912 +
7913 +Timeline.DetailedEventPainter.prototype.setHighlightMatcher = function(highlightMatcher) {
7914 +    this._highlightMatcher = highlightMatcher;
7915 +};
7916 +
7917 +Timeline.DetailedEventPainter.prototype.paint = function() {
7918 +    var eventSource = this._band.getEventSource();
7919 +    if (eventSource == null) {
7920 +        return;
7921 +    }
7922 +
7923 +    this._eventIdToElmt = {};
7924 +    this._prepareForPainting();
7925 +
7926 +    var eventTheme = this._params.theme.event;
7927 +    var trackHeight = Math.max(eventTheme.track.height, this._frc.getLineHeight());
7928 +    var metrics = {
7929 +        trackOffset:    Math.round(this._band.getViewWidth() / 2 - trackHeight / 2),
7930 +        trackHeight:    trackHeight,
7931 +        trackGap:       eventTheme.track.gap,
7932 +        trackIncrement: trackHeight + eventTheme.track.gap,
7933 +        icon:           eventTheme.instant.icon,
7934 +        iconWidth:      eventTheme.instant.iconWidth,
7935 +        iconHeight:     eventTheme.instant.iconHeight,
7936 +        labelWidth:     eventTheme.label.width
7937 +    }
7938 +
7939 +    var minDate = this._band.getMinDate();
7940 +    var maxDate = this._band.getMaxDate();
7941 +
7942 +    var filterMatcher = (this._filterMatcher != null) ?
7943 +        this._filterMatcher :
7944 +        function(evt) { return true; };
7945 +    var highlightMatcher = (this._highlightMatcher != null) ?
7946 +        this._highlightMatcher :
7947 +        function(evt) { return -1; };
7948 +
7949 +    var iterator = eventSource.getEventReverseIterator(minDate, maxDate);
7950 +    while (iterator.hasNext()) {
7951 +        var evt = iterator.next();
7952 +        if (filterMatcher(evt)) {
7953 +            this.paintEvent(evt, metrics, this._params.theme, highlightMatcher(evt));
7954 +        }
7955 +    }
7956 +
7957 +    this._highlightLayer.style.display = "block";
7958 +    this._lineLayer.style.display = "block";
7959 +    this._eventLayer.style.display = "block";
7960 +    // update the band object for max number of tracks in this section of the ether
7961 +    this._band.updateEventTrackInfo(this._lowerTracks.length + this._upperTracks.length,
7962 +                                 metrics.trackIncrement);
7963 +};
7964 +
7965 +Timeline.DetailedEventPainter.prototype.softPaint = function() {
7966 +};
7967 +
7968 +Timeline.DetailedEventPainter.prototype._prepareForPainting = function() {
7969 +    var band = this._band;
7970 +
7971 +    if (this._backLayer == null) {
7972 +        this._backLayer = this._band.createLayerDiv(0, "timeline-band-events");
7973 +        this._backLayer.style.visibility = "hidden";
7974 +
7975 +        var eventLabelPrototype = document.createElement("span");
7976 +        eventLabelPrototype.className = "timeline-event-label";
7977 +        this._backLayer.appendChild(eventLabelPrototype);
7978 +        this._frc = SimileAjax.Graphics.getFontRenderingContext(eventLabelPrototype);
7979 +    }
7980 +    this._frc.update();
7981 +    this._lowerTracks = [];
7982 +    this._upperTracks = [];
7983 +
7984 +    if (this._highlightLayer != null) {
7985 +        band.removeLayerDiv(this._highlightLayer);
7986 +    }
7987 +    this._highlightLayer = band.createLayerDiv(105, "timeline-band-highlights");
7988 +    this._highlightLayer.style.display = "none";
7989 +
7990 +    if (this._lineLayer != null) {
7991 +        band.removeLayerDiv(this._lineLayer);
7992 +    }
7993 +    this._lineLayer = band.createLayerDiv(110, "timeline-band-lines");
7994 +    this._lineLayer.style.display = "none";
7995 +
7996 +    if (this._eventLayer != null) {
7997 +        band.removeLayerDiv(this._eventLayer);
7998 +    }
7999 +    this._eventLayer = band.createLayerDiv(110, "timeline-band-events");
8000 +    this._eventLayer.style.display = "none";
8001 +};
8002 +
8003 +Timeline.DetailedEventPainter.prototype.paintEvent = function(evt, metrics, theme, highlightIndex) {
8004 +    if (evt.isInstant()) {
8005 +        this.paintInstantEvent(evt, metrics, theme, highlightIndex);
8006 +    } else {
8007 +        this.paintDurationEvent(evt, metrics, theme, highlightIndex);
8008 +    }
8009 +};
8010 +
8011 +Timeline.DetailedEventPainter.prototype.paintInstantEvent = function(evt, metrics, theme, highlightIndex) {
8012 +    if (evt.isImprecise()) {
8013 +        this.paintImpreciseInstantEvent(evt, metrics, theme, highlightIndex);
8014 +    } else {
8015 +        this.paintPreciseInstantEvent(evt, metrics, theme, highlightIndex);
8016 +    }
8017 +}
8018 +
8019 +Timeline.DetailedEventPainter.prototype.paintDurationEvent = function(evt, metrics, theme, highlightIndex) {
8020 +    if (evt.isImprecise()) {
8021 +        this.paintImpreciseDurationEvent(evt, metrics, theme, highlightIndex);
8022 +    } else {
8023 +        this.paintPreciseDurationEvent(evt, metrics, theme, highlightIndex);
8024 +    }
8025 +}
8026 +
8027 +Timeline.DetailedEventPainter.prototype.paintPreciseInstantEvent = function(evt, metrics, theme, highlightIndex) {
8028 +    var doc = this._timeline.getDocument();
8029 +    var text = evt.getText();
8030 +
8031 +    var startDate = evt.getStart();
8032 +    var startPixel = Math.round(this._band.dateToPixelOffset(startDate));
8033 +    var iconRightEdge = Math.round(startPixel + metrics.iconWidth / 2);
8034 +    var iconLeftEdge = Math.round(startPixel - metrics.iconWidth / 2);
8035 +
8036 +    var labelSize = this._frc.computeSize(text);
8037 +    var iconTrack = this._findFreeTrackForSolid(iconRightEdge, startPixel);
8038 +    var iconElmtData = this._paintEventIcon(evt, iconTrack, iconLeftEdge, metrics, theme);
8039 +
8040 +    var labelLeft = iconRightEdge + theme.event.label.offsetFromLine;
8041 +    var labelTrack = iconTrack;
8042 +
8043 +    var iconTrackData = this._getTrackData(iconTrack);
8044 +    if (Math.min(iconTrackData.solid, iconTrackData.text) >= labelLeft + labelSize.width) { // label on the same track, to the right of icon
8045 +        iconTrackData.solid = iconLeftEdge;
8046 +        iconTrackData.text = labelLeft;
8047 +    } else { // label on a different track, below icon
8048 +        iconTrackData.solid = iconLeftEdge;
8049 +
8050 +        labelLeft = startPixel + theme.event.label.offsetFromLine;
8051 +        labelTrack = this._findFreeTrackForText(iconTrack, labelLeft + labelSize.width, function(t) { t.line = startPixel - 2; });
8052 +        this._getTrackData(labelTrack).text = iconLeftEdge;
8053 +
8054 +        this._paintEventLine(evt, startPixel, iconTrack, labelTrack, metrics, theme);
8055 +    }
8056 +
8057 +    var labelTop = Math.round(
8058 +        metrics.trackOffset + labelTrack * metrics.trackIncrement +
8059 +        metrics.trackHeight / 2 - labelSize.height / 2);
8060 +
8061 +    var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width, labelSize.height, theme);
8062 +
8063 +    var self = this;
8064 +    var clickHandler = function(elmt, domEvt, target) {
8065 +        return self._onClickInstantEvent(iconElmtData.elmt, domEvt, evt);
8066 +    };
8067 +    SimileAjax.DOM.registerEvent(iconElmtData.elmt, "mousedown", clickHandler);
8068 +    SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler);
8069 +
8070 +    this._createHighlightDiv(highlightIndex, iconElmtData, theme);
8071 +
8072 +    this._eventIdToElmt[evt.getID()] = iconElmtData.elmt;
8073 +};
8074 +
8075 +Timeline.DetailedEventPainter.prototype.paintImpreciseInstantEvent = function(evt, metrics, theme, highlightIndex) {
8076 +    var doc = this._timeline.getDocument();
8077 +    var text = evt.getText();
8078 +
8079 +    var startDate = evt.getStart();
8080 +    var endDate = evt.getEnd();
8081 +    var startPixel = Math.round(this._band.dateToPixelOffset(startDate));
8082 +    var endPixel = Math.round(this._band.dateToPixelOffset(endDate));
8083 +
8084 +    var iconRightEdge = Math.round(startPixel + metrics.iconWidth / 2);
8085 +    var iconLeftEdge = Math.round(startPixel - metrics.iconWidth / 2);
8086 +
8087 +    var labelSize = this._frc.computeSize(text);
8088 +    var iconTrack = this._findFreeTrackForSolid(endPixel, startPixel);
8089 +
8090 +    var tapeElmtData = this._paintEventTape(evt, iconTrack, startPixel, endPixel,
8091 +        theme.event.instant.impreciseColor, theme.event.instant.impreciseOpacity, metrics, theme);
8092 +    var iconElmtData = this._paintEventIcon(evt, iconTrack, iconLeftEdge, metrics, theme);
8093 +
8094 +    var iconTrackData = this._getTrackData(iconTrack);
8095 +    iconTrackData.solid = iconLeftEdge;
8096 +
8097 +    var labelLeft = iconRightEdge + theme.event.label.offsetFromLine;
8098 +    var labelRight = labelLeft + labelSize.width;
8099 +    var labelTrack;
8100 +    if (labelRight < endPixel) {
8101 +        labelTrack = iconTrack;
8102 +    } else {
8103 +        labelLeft = startPixel + theme.event.label.offsetFromLine;
8104 +        labelRight = labelLeft + labelSize.width;
8105 +
8106 +        labelTrack = this._findFreeTrackForText(iconTrack, labelRight, function(t) { t.line = startPixel - 2; });
8107 +        this._getTrackData(labelTrack).text = iconLeftEdge;
8108 +
8109 +        this._paintEventLine(evt, startPixel, iconTrack, labelTrack, metrics, theme);
8110 +    }
8111 +    var labelTop = Math.round(
8112 +        metrics.trackOffset + labelTrack * metrics.trackIncrement +
8113 +        metrics.trackHeight / 2 - labelSize.height / 2);
8114 +
8115 +    var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width, labelSize.height, theme);
8116 +
8117 +    var self = this;
8118 +    var clickHandler = function(elmt, domEvt, target) {
8119 +        return self._onClickInstantEvent(iconElmtData.elmt, domEvt, evt);
8120 +    };
8121 +    SimileAjax.DOM.registerEvent(iconElmtData.elmt, "mousedown", clickHandler);
8122 +    SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler);
8123 +    SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler);
8124 +
8125 +    this._createHighlightDiv(highlightIndex, iconElmtData, theme);
8126 +
8127 +    this._eventIdToElmt[evt.getID()] = iconElmtData.elmt;
8128 +};
8129 +
8130 +Timeline.DetailedEventPainter.prototype.paintPreciseDurationEvent = function(evt, metrics, theme, highlightIndex) {
8131 +    var doc = this._timeline.getDocument();
8132 +    var text = evt.getText();
8133 +
8134 +    var startDate = evt.getStart();
8135 +    var endDate = evt.getEnd();
8136 +    var startPixel = Math.round(this._band.dateToPixelOffset(startDate));
8137 +    var endPixel = Math.round(this._band.dateToPixelOffset(endDate));
8138 +
8139 +    var labelSize = this._frc.computeSize(text);
8140 +    var tapeTrack = this._findFreeTrackForSolid(endPixel);
8141 +    var color = evt.getColor();
8142 +    color = color != null ? color : theme.event.duration.color;
8143 +
8144 +    var tapeElmtData = this._paintEventTape(evt, tapeTrack, startPixel, endPixel, color, 100, metrics, theme);
8145 +
8146 +    var tapeTrackData = this._getTrackData(tapeTrack);
8147 +    tapeTrackData.solid = startPixel;
8148 +
8149 +    var labelLeft = startPixel + theme.event.label.offsetFromLine;
8150 +    var labelTrack = this._findFreeTrackForText(tapeTrack, labelLeft + labelSize.width, function(t) { t.line = startPixel - 2; });
8151 +    this._getTrackData(labelTrack).text = startPixel - 2;
8152 +
8153 +    this._paintEventLine(evt, startPixel, tapeTrack, labelTrack, metrics, theme);
8154 +
8155 +    var labelTop = Math.round(
8156 +        metrics.trackOffset + labelTrack * metrics.trackIncrement +
8157 +        metrics.trackHeight / 2 - labelSize.height / 2);
8158 +
8159 +    var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width, labelSize.height, theme);
8160 +
8161 +    var self = this;
8162 +    var clickHandler = function(elmt, domEvt, target) {
8163 +        return self._onClickDurationEvent(tapeElmtData.elmt, domEvt, evt);
8164 +    };
8165 +    SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler);
8166 +    SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler);
8167 +
8168 +    this._createHighlightDiv(highlightIndex, tapeElmtData, theme);
8169 +
8170 +    this._eventIdToElmt[evt.getID()] = tapeElmtData.elmt;
8171 +};
8172 +
8173 +Timeline.DetailedEventPainter.prototype.paintImpreciseDurationEvent = function(evt, metrics, theme, highlightIndex) {
8174 +    var doc = this._timeline.getDocument();
8175 +    var text = evt.getText();
8176 +
8177 +    var startDate = evt.getStart();
8178 +    var latestStartDate = evt.getLatestStart();
8179 +    var endDate = evt.getEnd();
8180 +    var earliestEndDate = evt.getEarliestEnd();
8181 +
8182 +    var startPixel = Math.round(this._band.dateToPixelOffset(startDate));
8183 +    var latestStartPixel = Math.round(this._band.dateToPixelOffset(latestStartDate));
8184 +    var endPixel = Math.round(this._band.dateToPixelOffset(endDate));
8185 +    var earliestEndPixel = Math.round(this._band.dateToPixelOffset(earliestEndDate));
8186 +
8187 +    var labelSize = this._frc.computeSize(text);
8188 +    var tapeTrack = this._findFreeTrackForSolid(endPixel);
8189 +    var color = evt.getColor();
8190 +    color = color != null ? color : theme.event.duration.color;
8191 +
8192 +    var impreciseTapeElmtData = this._paintEventTape(evt, tapeTrack, startPixel, endPixel,
8193 +        theme.event.duration.impreciseColor, theme.event.duration.impreciseOpacity, metrics, theme);
8194 +    var tapeElmtData = this._paintEventTape(evt, tapeTrack, latestStartPixel, earliestEndPixel, color, 100, metrics, theme);
8195 +
8196 +    var tapeTrackData = this._getTrackData(tapeTrack);
8197 +    tapeTrackData.solid = startPixel;
8198 +
8199 +    var labelLeft = latestStartPixel + theme.event.label.offsetFromLine;
8200 +    var labelTrack = this._findFreeTrackForText(tapeTrack, labelLeft + labelSize.width, function(t) { t.line = latestStartPixel - 2; });
8201 +    this._getTrackData(labelTrack).text = latestStartPixel - 2;
8202 +
8203 +    this._paintEventLine(evt, latestStartPixel, tapeTrack, labelTrack, metrics, theme);
8204 +
8205 +    var labelTop = Math.round(
8206 +        metrics.trackOffset + labelTrack * metrics.trackIncrement +
8207 +        metrics.trackHeight / 2 - labelSize.height / 2);
8208 +
8209 +    var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width, labelSize.height, theme);
8210 +
8211 +    var self = this;
8212 +    var clickHandler = function(elmt, domEvt, target) {
8213 +        return self._onClickDurationEvent(tapeElmtData.elmt, domEvt, evt);
8214 +    };
8215 +    SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler);
8216 +    SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler);
8217 +
8218 +    this._createHighlightDiv(highlightIndex, tapeElmtData, theme);
8219 +
8220 +    this._eventIdToElmt[evt.getID()] = tapeElmtData.elmt;
8221 +};
8222 +
8223 +Timeline.DetailedEventPainter.prototype._findFreeTrackForSolid = function(solidEdge, softEdge) {
8224 +    for (var i = 0; true; i++) {
8225 +        if (i < this._lowerTracks.length) {
8226 +            var t = this._lowerTracks[i];
8227 +            if (Math.min(t.solid, t.text) > solidEdge && (!(softEdge) || t.line > softEdge)) {
8228 +                return i;
8229 +            }
8230 +        } else {
8231 +            this._lowerTracks.push({
8232 +                solid:  Number.POSITIVE_INFINITY,
8233 +                text:   Number.POSITIVE_INFINITY,
8234 +                line:   Number.POSITIVE_INFINITY
8235 +            });
8236 +
8237 +            return i;
8238 +        }
8239 +
8240 +        if (i < this._upperTracks.length) {
8241 +            var t = this._upperTracks[i];
8242 +            if (Math.min(t.solid, t.text) > solidEdge && (!(softEdge) || t.line > softEdge)) {
8243 +                return -1 - i;
8244 +            }
8245 +        } else {
8246 +            this._upperTracks.push({
8247 +                solid:  Number.POSITIVE_INFINITY,
8248 +                text:   Number.POSITIVE_INFINITY,
8249 +                line:   Number.POSITIVE_INFINITY
8250 +            });
8251 +
8252 +            return -1 - i;
8253 +        }
8254 +    }
8255 +};
8256 +
8257 +Timeline.DetailedEventPainter.prototype._findFreeTrackForText = function(fromTrack, edge, occupiedTrackVisitor) {
8258 +    var extendUp;
8259 +    var index;
8260 +    var firstIndex;
8261 +    var result;
8262 +
8263 +    if (fromTrack < 0) {
8264 +        extendUp = true;
8265 +        firstIndex = -fromTrack;
8266 +
8267 +        index = this._findFreeUpperTrackForText(firstIndex, edge);
8268 +        result = -1 - index;
8269 +    } else if (fromTrack > 0) {
8270 +        extendUp = false;
8271 +        firstIndex = fromTrack + 1;
8272 +
8273 +        index = this._findFreeLowerTrackForText(firstIndex, edge);
8274 +        result = index;
8275 +    } else {
8276 +        var upIndex = this._findFreeUpperTrackForText(0, edge);
8277 +        var downIndex = this._findFreeLowerTrackForText(1, edge);
8278 +
8279 +        if (downIndex - 1 <= upIndex) {
8280 +            extendUp = false;
8281 +            firstIndex = 1;
8282 +            index = downIndex;
8283 +            result = index;
8284 +        } else {
8285 +            extendUp = true;
8286 +            firstIndex = 0;
8287 +            index = upIndex;
8288 +            result = -1 - index;
8289 +        }
8290 +    }
8291 +
8292 +    if (extendUp) {
8293 +        if (index == this._upperTracks.length) {
8294 +            this._upperTracks.push({
8295 +                solid:  Number.POSITIVE_INFINITY,
8296 +                text:   Number.POSITIVE_INFINITY,
8297 +                line:   Number.POSITIVE_INFINITY
8298 +            });
8299 +        }
8300 +        for (var i = firstIndex; i < index; i++) {
8301 +            occupiedTrackVisitor(this._upperTracks[i]);
8302 +        }
8303 +    } else {
8304 +        if (index == this._lowerTracks.length) {
8305 +            this._lowerTracks.push({
8306 +                solid:  Number.POSITIVE_INFINITY,
8307 +                text:   Number.POSITIVE_INFINITY,
8308 +                line:   Number.POSITIVE_INFINITY
8309 +            });
8310 +        }
8311 +        for (var i = firstIndex; i < index; i++) {
8312 +            occupiedTrackVisitor(this._lowerTracks[i]);
8313 +        }
8314 +    }
8315 +    return result;
8316 +};
8317 +
8318 +Timeline.DetailedEventPainter.prototype._findFreeLowerTrackForText = function(index, edge) {
8319 +    for (; index < this._lowerTracks.length; index++) {
8320 +        var t = this._lowerTracks[index];
8321 +        if (Math.min(t.solid, t.text) >= edge) {
8322 +            break;
8323 +        }
8324 +    }
8325 +    return index;
8326 +};
8327 +
8328 +Timeline.DetailedEventPainter.prototype._findFreeUpperTrackForText = function(index, edge) {
8329 +    for (; index < this._upperTracks.length; index++) {
8330 +        var t = this._upperTracks[index];
8331 +        if (Math.min(t.solid, t.text) >= edge) {
8332 +            break;
8333 +        }
8334 +    }
8335 +    return index;
8336 +};
8337 +
8338 +Timeline.DetailedEventPainter.prototype._getTrackData = function(index) {
8339 +    return (index < 0) ? this._upperTracks[-index - 1] : this._lowerTracks[index];
8340 +};
8341 +
8342 +Timeline.DetailedEventPainter.prototype._paintEventLine = function(evt, left, startTrack, endTrack, metrics, theme) {
8343 +    var top = Math.round(metrics.trackOffset + startTrack * metrics.trackIncrement + metrics.trackHeight / 2);
8344 +    var height = Math.round(Math.abs(endTrack - startTrack) * metrics.trackIncrement);
8345 +
8346 +    var lineStyle = "1px solid " + theme.event.label.lineColor;
8347 +    var lineDiv = this._timeline.getDocument().createElement("div");
8348 +	lineDiv.style.position = "absolute";
8349 +    lineDiv.style.left = left + "px";
8350 +    lineDiv.style.width = theme.event.label.offsetFromLine + "px";
8351 +    lineDiv.style.height = height + "px";
8352 +    if (startTrack > endTrack) {
8353 +        lineDiv.style.top = (top - height) + "px";
8354 +        lineDiv.style.borderTop = lineStyle;
8355 +    } else {
8356 +        lineDiv.style.top = top + "px";
8357 +        lineDiv.style.borderBottom = lineStyle;
8358 +    }
8359 +    lineDiv.style.borderLeft = lineStyle;
8360 +    this._lineLayer.appendChild(lineDiv);
8361 +};
8362 +
8363 +Timeline.DetailedEventPainter.prototype._paintEventIcon = function(evt, iconTrack, left, metrics, theme) {
8364 +    var icon = evt.getIcon();
8365 +    icon = icon != null ? icon : metrics.icon;
8366 +
8367 +    var middle = metrics.trackOffset + iconTrack * metrics.trackIncrement + metrics.trackHeight / 2;
8368 +    var top = Math.round(middle - metrics.iconHeight / 2);
8369 +
8370 +    var img = SimileAjax.Graphics.createTranslucentImage(icon);
8371 +    var iconDiv = this._timeline.getDocument().createElement("div");
8372 +    iconDiv.style.position = "absolute";
8373 +    iconDiv.style.left = left + "px";
8374 +    iconDiv.style.top = top + "px";
8375 +    iconDiv.appendChild(img);
8376 +    iconDiv.style.cursor = "pointer";
8377 +
8378 +    if(evt._title != null)
8379 +        iconDiv.title = evt._title
8380 +
8381 +    this._eventLayer.appendChild(iconDiv);
8382 +
8383 +    return {
8384 +        left:   left,
8385 +        top:    top,
8386 +        width:  metrics.iconWidth,
8387 +        height: metrics.iconHeight,
8388 +        elmt:   iconDiv
8389 +    };
8390 +};
8391 +
8392 +Timeline.DetailedEventPainter.prototype._paintEventLabel = function(evt, text, left, top, width, height, theme) {
8393 +    var doc = this._timeline.getDocument();
8394 +
8395 +    var labelBackgroundDiv = doc.createElement("div");
8396 +    labelBackgroundDiv.style.position = "absolute";
8397 +    labelBackgroundDiv.style.left = left + "px";
8398 +    labelBackgroundDiv.style.width = width + "px";
8399 +    labelBackgroundDiv.style.top = top + "px";
8400 +    labelBackgroundDiv.style.height = height + "px";
8401 +    labelBackgroundDiv.style.backgroundColor = theme.event.label.backgroundColor;
8402 +    SimileAjax.Graphics.setOpacity(labelBackgroundDiv, theme.event.label.backgroundOpacity);
8403 +    this._eventLayer.appendChild(labelBackgroundDiv);
8404 +
8405 +    var labelDiv = doc.createElement("div");
8406 +    labelDiv.style.position = "absolute";
8407 +    labelDiv.style.left = left + "px";
8408 +    labelDiv.style.width = width + "px";
8409 +    labelDiv.style.top = top + "px";
8410 +    labelDiv.innerHTML = text;
8411 +    labelDiv.style.cursor = "pointer";
8412 +
8413 +    if(evt._title != null)
8414 +        labelDiv.title = evt._title;
8415 +
8416 +    var color = evt.getTextColor();
8417 +    if (color == null) {
8418 +        color = evt.getColor();
8419 +    }
8420 +    if (color != null) {
8421 +        labelDiv.style.color = color;
8422 +    }
8423 +
8424 +    this._eventLayer.appendChild(labelDiv);
8425 +
8426 +    return {
8427 +        left:   left,
8428 +        top:    top,
8429 +        width:  width,
8430 +        height: height,
8431 +        elmt:   labelDiv
8432 +    };
8433 +};
8434 +
8435 +Timeline.DetailedEventPainter.prototype._paintEventTape = function(
8436 +    evt, iconTrack, startPixel, endPixel, color, opacity, metrics, theme) {
8437 +
8438 +    var tapeWidth = endPixel - startPixel;
8439 +    var tapeHeight = theme.event.tape.height;
8440 +    var middle = metrics.trackOffset + iconTrack * metrics.trackIncrement + metrics.trackHeight / 2;
8441 +    var top = Math.round(middle - tapeHeight / 2);
8442 +
8443 +    var tapeDiv = this._timeline.getDocument().createElement("div");
8444 +    tapeDiv.style.position = "absolute";
8445 +    tapeDiv.style.left = startPixel + "px";
8446 +    tapeDiv.style.width = tapeWidth + "px";
8447 +    tapeDiv.style.top = top + "px";
8448 +    tapeDiv.style.height = tapeHeight + "px";
8449 +    tapeDiv.style.backgroundColor = color;
8450 +    tapeDiv.style.overflow = "hidden";
8451 +    tapeDiv.style.cursor = "pointer";
8452 +
8453 +    if(evt._title != null)
8454 +        tapeDiv.title = evt._title;
8455 +
8456 +    SimileAjax.Graphics.setOpacity(tapeDiv, opacity);
8457 +
8458 +    this._eventLayer.appendChild(tapeDiv);
8459 +
8460 +    return {
8461 +        left:   startPixel,
8462 +        top:    top,
8463 +        width:  tapeWidth,
8464 +        height: tapeHeight,
8465 +        elmt:   tapeDiv
8466 +    };
8467 +}
8468 +
8469 +Timeline.DetailedEventPainter.prototype._createHighlightDiv = function(highlightIndex, dimensions, theme) {
8470 +    if (highlightIndex >= 0) {
8471 +        var doc = this._timeline.getDocument();
8472 +        var eventTheme = theme.event;
8473 +
8474 +        var color = eventTheme.highlightColors[Math.min(highlightIndex, eventTheme.highlightColors.length - 1)];
8475 +
8476 +        var div = doc.createElement("div");
8477 +        div.style.position = "absolute";
8478 +        div.style.overflow = "hidden";
8479 +        div.style.left =    (dimensions.left - 2) + "px";
8480 +        div.style.width =   (dimensions.width + 4) + "px";
8481 +        div.style.top =     (dimensions.top - 2) + "px";
8482 +        div.style.height =  (dimensions.height + 4) + "px";
8483 +        div.style.background = color;
8484 +
8485 +        this._highlightLayer.appendChild(div);
8486 +    }
8487 +};
8488 +
8489 +Timeline.DetailedEventPainter.prototype._onClickInstantEvent = function(icon, domEvt, evt) {
8490 +    var c = SimileAjax.DOM.getPageCoordinates(icon);
8491 +    this._showBubble(
8492 +        c.left + Math.ceil(icon.offsetWidth / 2),
8493 +        c.top + Math.ceil(icon.offsetHeight / 2),
8494 +        evt
8495 +    );
8496 +    this._fireOnSelect(evt.getID());
8497 +
8498 +    domEvt.cancelBubble = true;
8499 +    SimileAjax.DOM.cancelEvent(domEvt);
8500 +    return false;
8501 +};
8502 +
8503 +Timeline.DetailedEventPainter.prototype._onClickDurationEvent = function(target, domEvt, evt) {
8504 +    if ("pageX" in domEvt) {
8505 +        var x = domEvt.pageX;
8506 +        var y = domEvt.pageY;
8507 +    } else {
8508 +        var c = SimileAjax.DOM.getPageCoordinates(target);
8509 +        var x = domEvt.offsetX + c.left;
8510 +        var y = domEvt.offsetY + c.top;
8511 +    }
8512 +    this._showBubble(x, y, evt);
8513 +    this._fireOnSelect(evt.getID());
8514 +
8515 +    domEvt.cancelBubble = true;
8516 +    SimileAjax.DOM.cancelEvent(domEvt);
8517 +    return false;
8518 +};
8519 +
8520 +Timeline.DetailedEventPainter.prototype.showBubble = function(evt) {
8521 +    var elmt = this._eventIdToElmt[evt.getID()];
8522 +    if (elmt) {
8523 +        var c = SimileAjax.DOM.getPageCoordinates(elmt);
8524 +        this._showBubble(c.left + elmt.offsetWidth / 2, c.top + elmt.offsetHeight / 2, evt);
8525 +    }
8526 +};
8527 +
8528 +Timeline.DetailedEventPainter.prototype._showBubble = function(x, y, evt) {
8529 +    var div = document.createElement("div");
8530 +    var themeBubble = this._params.theme.event.bubble;
8531 +    evt.fillInfoBubble(div, this._params.theme, this._band.getLabeller());
8532 +
8533 +    SimileAjax.WindowManager.cancelPopups();
8534 +    SimileAjax.Graphics.createBubbleForContentAndPoint(div, x, y,
8535 +       themeBubble.width, null, themeBubble.maxHeight);
8536 +};
8537 +
8538 +Timeline.DetailedEventPainter.prototype._fireOnSelect = function(eventID) {
8539 +    for (var i = 0; i < this._onSelectListeners.length; i++) {
8540 +        this._onSelectListeners[i](eventID);
8541 +    }
8542 +};
8543 +/*
8544 + *  Overview Event Painter
8545 + *
8546 + */
8547 +
8548 +Timeline.OverviewEventPainter = function(params) {
8549 +    this._params = params;
8550 +    this._onSelectListeners = [];
8551 +
8552 +    this._filterMatcher = null;
8553 +    this._highlightMatcher = null;
8554 +};
8555 +
8556 +Timeline.OverviewEventPainter.prototype.initialize = function(band, timeline) {
8557 +    this._band = band;
8558 +    this._timeline = timeline;
8559 +
8560 +    this._eventLayer = null;
8561 +    this._highlightLayer = null;
8562 +};
8563 +
8564 +Timeline.OverviewEventPainter.prototype.getType = function() {
8565 +    return 'overview';
8566 +};
8567 +
8568 +Timeline.OverviewEventPainter.prototype.addOnSelectListener = function(listener) {
8569 +    this._onSelectListeners.push(listener);
8570 +};
8571 +
8572 +Timeline.OverviewEventPainter.prototype.removeOnSelectListener = function(listener) {
8573 +    for (var i = 0; i < this._onSelectListeners.length; i++) {
8574 +        if (this._onSelectListeners[i] == listener) {
8575 +            this._onSelectListeners.splice(i, 1);
8576 +            break;
8577 +        }
8578 +    }
8579 +};
8580 +
8581 +Timeline.OverviewEventPainter.prototype.getFilterMatcher = function() {
8582 +    return this._filterMatcher;
8583 +};
8584 +
8585 +Timeline.OverviewEventPainter.prototype.setFilterMatcher = function(filterMatcher) {
8586 +    this._filterMatcher = filterMatcher;
8587 +};
8588 +
8589 +Timeline.OverviewEventPainter.prototype.getHighlightMatcher = function() {
8590 +    return this._highlightMatcher;
8591 +};
8592 +
8593 +Timeline.OverviewEventPainter.prototype.setHighlightMatcher = function(highlightMatcher) {
8594 +    this._highlightMatcher = highlightMatcher;
8595 +};
8596 +
8597 +Timeline.OverviewEventPainter.prototype.paint = function() {
8598 +    var eventSource = this._band.getEventSource();
8599 +    if (eventSource == null) {
8600 +        return;
8601 +    }
8602 +
8603 +    this._prepareForPainting();
8604 +
8605 +    var eventTheme = this._params.theme.event;
8606 +    var metrics = {
8607 +        trackOffset:    eventTheme.overviewTrack.offset,
8608 +        trackHeight:    eventTheme.overviewTrack.height,
8609 +        trackGap:       eventTheme.overviewTrack.gap,
8610 +        trackIncrement: eventTheme.overviewTrack.height + eventTheme.overviewTrack.gap
8611 +    }
8612 +
8613 +    var minDate = this._band.getMinDate();
8614 +    var maxDate = this._band.getMaxDate();
8615 +
8616 +    var filterMatcher = (this._filterMatcher != null) ?
8617 +        this._filterMatcher :
8618 +        function(evt) { return true; };
8619 +    var highlightMatcher = (this._highlightMatcher != null) ?
8620 +        this._highlightMatcher :
8621 +        function(evt) { return -1; };
8622 +
8623 +    var iterator = eventSource.getEventReverseIterator(minDate, maxDate);
8624 +    while (iterator.hasNext()) {
8625 +        var evt = iterator.next();
8626 +        if (filterMatcher(evt)) {
8627 +            this.paintEvent(evt, metrics, this._params.theme, highlightMatcher(evt));
8628 +        }
8629 +    }
8630 +
8631 +    this._highlightLayer.style.display = "block";
8632 +    this._eventLayer.style.display = "block";
8633 +    // update the band object for max number of tracks in this section of the ether
8634 +    this._band.updateEventTrackInfo(this._tracks.length, metrics.trackIncrement);
8635 +};
8636 +
8637 +Timeline.OverviewEventPainter.prototype.softPaint = function() {
8638 +};
8639 +
8640 +Timeline.OverviewEventPainter.prototype._prepareForPainting = function() {
8641 +    var band = this._band;
8642 +
8643 +    this._tracks = [];
8644 +
8645 +    if (this._highlightLayer != null) {
8646 +        band.removeLayerDiv(this._highlightLayer);
8647 +    }
8648 +    this._highlightLayer = band.createLayerDiv(105, "timeline-band-highlights");
8649 +    this._highlightLayer.style.display = "none";
8650 +
8651 +    if (this._eventLayer != null) {
8652 +        band.removeLayerDiv(this._eventLayer);
8653 +    }
8654 +    this._eventLayer = band.createLayerDiv(110, "timeline-band-events");
8655 +    this._eventLayer.style.display = "none";
8656 +};
8657 +
8658 +Timeline.OverviewEventPainter.prototype.paintEvent = function(evt, metrics, theme, highlightIndex) {
8659 +    if (evt.isInstant()) {
8660 +        this.paintInstantEvent(evt, metrics, theme, highlightIndex);
8661 +    } else {
8662 +        this.paintDurationEvent(evt, metrics, theme, highlightIndex);
8663 +    }
8664 +};
8665 +
8666 +Timeline.OverviewEventPainter.prototype.paintInstantEvent = function(evt, metrics, theme, highlightIndex) {
8667 +    var startDate = evt.getStart();
8668 +    var startPixel = Math.round(this._band.dateToPixelOffset(startDate));
8669 +
8670 +    var color = evt.getColor(),
8671 +        klassName = evt.getClassName();
8672 +    if (klassName) {
8673 +      color = null;
8674 +    } else {
8675 +      color = color != null ? color : theme.event.duration.color;
8676 +    }
8677 +
8678 +    var tickElmtData = this._paintEventTick(evt, startPixel, color, 100, metrics, theme);
8679 +
8680 +    this._createHighlightDiv(highlightIndex, tickElmtData, theme);
8681 +};
8682 +
8683 +Timeline.OverviewEventPainter.prototype.paintDurationEvent = function(evt, metrics, theme, highlightIndex) {
8684 +    var latestStartDate = evt.getLatestStart();
8685 +    var earliestEndDate = evt.getEarliestEnd();
8686 +
8687 +    var latestStartPixel = Math.round(this._band.dateToPixelOffset(latestStartDate));
8688 +    var earliestEndPixel = Math.round(this._band.dateToPixelOffset(earliestEndDate));
8689 +
8690 +    var tapeTrack = 0;
8691 +    for (; tapeTrack < this._tracks.length; tapeTrack++) {
8692 +        if (earliestEndPixel < this._tracks[tapeTrack]) {
8693 +            break;
8694 +        }
8695 +    }
8696 +    this._tracks[tapeTrack] = earliestEndPixel;
8697 +
8698 +    var color = evt.getColor(),
8699 +        klassName = evt.getClassName();
8700 +    if (klassName) {
8701 +      color = null;
8702 +    } else {
8703 +      color = color != null ? color : theme.event.duration.color;
8704 +    }
8705 +
8706 +    var tapeElmtData = this._paintEventTape(evt, tapeTrack, latestStartPixel, earliestEndPixel,
8707 +      color, 100, metrics, theme, klassName);
8708 +
8709 +    this._createHighlightDiv(highlightIndex, tapeElmtData, theme);
8710 +};
8711 +
8712 +Timeline.OverviewEventPainter.prototype._paintEventTape = function(
8713 +    evt, track, left, right, color, opacity, metrics, theme, klassName) {
8714 +
8715 +    var top = metrics.trackOffset + track * metrics.trackIncrement;
8716 +    var width = right - left;
8717 +    var height = metrics.trackHeight;
8718 +
8719 +    var tapeDiv = this._timeline.getDocument().createElement("div");
8720 +    tapeDiv.className = 'timeline-small-event-tape'
8721 +    if (klassName) {tapeDiv.className += ' small-' + klassName;}
8722 +    tapeDiv.style.left = left + "px";
8723 +    tapeDiv.style.width = width + "px";
8724 +    tapeDiv.style.top = top + "px";
8725 +    tapeDiv.style.height = height + "px";
8726 +
8727 +    if (color) {
8728 +      tapeDiv.style.backgroundColor = color; // set color here if defined by event. Else use css
8729 +    }
8730 + //   tapeDiv.style.overflow = "hidden";   // now set in css
8731 + //   tapeDiv.style.position = "absolute";
8732 +    if(opacity<100) SimileAjax.Graphics.setOpacity(tapeDiv, opacity);
8733 +
8734 +    this._eventLayer.appendChild(tapeDiv);
8735 +
8736 +    return {
8737 +        left:   left,
8738 +        top:    top,
8739 +        width:  width,
8740 +        height: height,
8741 +        elmt:   tapeDiv
8742 +    };
8743 +}
8744 +
8745 +Timeline.OverviewEventPainter.prototype._paintEventTick = function(
8746 +    evt, left, color, opacity, metrics, theme) {
8747 +
8748 +    var height = theme.event.overviewTrack.tickHeight;
8749 +    var top = metrics.trackOffset - height;
8750 +    var width = 1;
8751 +
8752 +    var tickDiv = this._timeline.getDocument().createElement("div");
8753 +	  tickDiv.className = 'timeline-small-event-icon'
8754 +    tickDiv.style.left = left + "px";
8755 +    tickDiv.style.top = top + "px";
8756 +  //  tickDiv.style.width = width + "px";
8757 +  //  tickDiv.style.position = "absolute";
8758 +  //  tickDiv.style.height = height + "px";
8759 +  //  tickDiv.style.backgroundColor = color;
8760 +  //  tickDiv.style.overflow = "hidden";
8761 +
8762 +    var klassName = evt.getClassName()
8763 +    if (klassName) {tickDiv.className +=' small-' + klassName};
8764 +
8765 +    if(opacity<100) {SimileAjax.Graphics.setOpacity(tickDiv, opacity)};
8766 +
8767 +    this._eventLayer.appendChild(tickDiv);
8768 +
8769 +    return {
8770 +        left:   left,
8771 +        top:    top,
8772 +        width:  width,
8773 +        height: height,
8774 +        elmt:   tickDiv
8775 +    };
8776 +}
8777 +
8778 +Timeline.OverviewEventPainter.prototype._createHighlightDiv = function(highlightIndex, dimensions, theme) {
8779 +    if (highlightIndex >= 0) {
8780 +        var doc = this._timeline.getDocument();
8781 +        var eventTheme = theme.event;
8782 +
8783 +        var color = eventTheme.highlightColors[Math.min(highlightIndex, eventTheme.highlightColors.length - 1)];
8784 +
8785 +        var div = doc.createElement("div");
8786 +        div.style.position = "absolute";
8787 +        div.style.overflow = "hidden";
8788 +        div.style.left =    (dimensions.left - 1) + "px";
8789 +        div.style.width =   (dimensions.width + 2) + "px";
8790 +        div.style.top =     (dimensions.top - 1) + "px";
8791 +        div.style.height =  (dimensions.height + 2) + "px";
8792 +        div.style.background = color;
8793 +
8794 +        this._highlightLayer.appendChild(div);
8795 +    }
8796 +};
8797 +
8798 +Timeline.OverviewEventPainter.prototype.showBubble = function(evt) {
8799 +    // not implemented
8800 +};
8801 +/*
8802 + *  Compact Event Painter
8803 + *
8804 + */
8805 +
8806 +Timeline.CompactEventPainter = function(params) {
8807 +    this._params = params;
8808 +    this._onSelectListeners = [];
8809 +
8810 +    this._filterMatcher = null;
8811 +    this._highlightMatcher = null;
8812 +    this._frc = null;
8813 +
8814 +    this._eventIdToElmt = {};
8815 +};
8816 +
8817 +Timeline.CompactEventPainter.prototype.getType = function() {
8818 +    return 'compact';
8819 +};
8820 +
8821 +Timeline.CompactEventPainter.prototype.initialize = function(band, timeline) {
8822 +    this._band = band;
8823 +    this._timeline = timeline;
8824 +
8825 +    this._backLayer = null;
8826 +    this._eventLayer = null;
8827 +    this._lineLayer = null;
8828 +    this._highlightLayer = null;
8829 +
8830 +    this._eventIdToElmt = null;
8831 +};
8832 +
8833 +Timeline.CompactEventPainter.prototype.addOnSelectListener = function(listener) {
8834 +    this._onSelectListeners.push(listener);
8835 +};
8836 +
8837 +Timeline.CompactEventPainter.prototype.removeOnSelectListener = function(listener) {
8838 +    for (var i = 0; i < this._onSelectListeners.length; i++) {
8839 +        if (this._onSelectListeners[i] == listener) {
8840 +            this._onSelectListeners.splice(i, 1);
8841 +            break;
8842 +        }
8843 +    }
8844 +};
8845 +
8846 +Timeline.CompactEventPainter.prototype.getFilterMatcher = function() {
8847 +    return this._filterMatcher;
8848 +};
8849 +
8850 +Timeline.CompactEventPainter.prototype.setFilterMatcher = function(filterMatcher) {
8851 +    this._filterMatcher = filterMatcher;
8852 +};
8853 +
8854 +Timeline.CompactEventPainter.prototype.getHighlightMatcher = function() {
8855 +    return this._highlightMatcher;
8856 +};
8857 +
8858 +Timeline.CompactEventPainter.prototype.setHighlightMatcher = function(highlightMatcher) {
8859 +    this._highlightMatcher = highlightMatcher;
8860 +};
8861 +
8862 +Timeline.CompactEventPainter.prototype.paint = function() {
8863 +    var eventSource = this._band.getEventSource();
8864 +    if (eventSource == null) {
8865 +        return;
8866 +    }
8867 +
8868 +    this._eventIdToElmt = {};
8869 +    this._prepareForPainting();
8870 +
8871 +    var metrics = this._computeMetrics();
8872 +    var minDate = this._band.getMinDate();
8873 +    var maxDate = this._band.getMaxDate();
8874 +
8875 +    var filterMatcher = (this._filterMatcher != null) ?
8876 +        this._filterMatcher :
8877 +        function(evt) { return true; };
8878 +
8879 +    var highlightMatcher = (this._highlightMatcher != null) ?
8880 +        this._highlightMatcher :
8881 +        function(evt) { return -1; };
8882 +
8883 +    var iterator = eventSource.getEventIterator(minDate, maxDate);
8884 +
8885 +    var stackConcurrentPreciseInstantEvents = "stackConcurrentPreciseInstantEvents" in this._params && typeof this._params.stackConcurrentPreciseInstantEvents == "object";
8886 +    var collapseConcurrentPreciseInstantEvents = "collapseConcurrentPreciseInstantEvents" in this._params && this._params.collapseConcurrentPreciseInstantEvents;
8887 +    if (collapseConcurrentPreciseInstantEvents || stackConcurrentPreciseInstantEvents) {
8888 +        var bufferedEvents = [];
8889 +        var previousInstantEvent = null;
8890 +
8891 +        while (iterator.hasNext()) {
8892 +            var evt = iterator.next();
8893 +            if (filterMatcher(evt)) {
8894 +                if (!evt.isInstant() || evt.isImprecise()) {
8895 +                    this.paintEvent(evt, metrics, this._params.theme, highlightMatcher(evt));
8896 +                } else if (previousInstantEvent != null &&
8897 +                        previousInstantEvent.getStart().getTime() == evt.getStart().getTime()) {
8898 +                    bufferedEvents[bufferedEvents.length - 1].push(evt);
8899 +                } else {
8900 +                    bufferedEvents.push([ evt ]);
8901 +                    previousInstantEvent = evt;
8902 +                }
8903 +            }
8904 +        }
8905 +
8906 +        for (var i = 0; i < bufferedEvents.length; i++) {
8907 +            var compositeEvents = bufferedEvents[i];
8908 +            if (compositeEvents.length == 1) {
8909 +                this.paintEvent(compositeEvents[0], metrics, this._params.theme, highlightMatcher(evt));
8910 +            } else {
8911 +                var match = -1;
8912 +                for (var j = 0; match < 0 && j < compositeEvents.length; j++) {
8913 +                    match = highlightMatcher(compositeEvents[j]);
8914 +                }
8915 +
8916 +                if (stackConcurrentPreciseInstantEvents) {
8917 +                    this.paintStackedPreciseInstantEvents(compositeEvents, metrics, this._params.theme, match);
8918 +                } else {
8919 +                    this.paintCompositePreciseInstantEvents(compositeEvents, metrics, this._params.theme, match);
8920 +                }
8921 +            }
8922 +        }
8923 +    } else {
8924 +        while (iterator.hasNext()) {
8925 +            var evt = iterator.next();
8926 +            if (filterMatcher(evt)) {
8927 +                this.paintEvent(evt, metrics, this._params.theme, highlightMatcher(evt));
8928 +            }
8929 +        }
8930 +    }
8931 +
8932 +    this._highlightLayer.style.display = "block";
8933 +    this._lineLayer.style.display = "block";
8934 +    this._eventLayer.style.display = "block";
8935 +
8936 +    this._setOrthogonalOffset(metrics);
8937 +};
8938 +
8939 +Timeline.CompactEventPainter.prototype.softPaint = function() {
8940 +    this._setOrthogonalOffset(this._computeMetrics());
8941 +};
8942 +
8943 +Timeline.CompactEventPainter.prototype._setOrthogonalOffset = function(metrics) {
8944 +    var actualViewWidth = 2 * metrics.trackOffset + this._tracks.length * metrics.trackHeight;
8945 +    var minOrthogonalOffset = Math.min(0, this._band.getViewWidth() - actualViewWidth);
8946 +    var orthogonalOffset = Math.max(minOrthogonalOffset, this._band.getViewOrthogonalOffset());
8947 +
8948 +    this._highlightLayer.style.top =
8949 +        this._lineLayer.style.top =
8950 +            this._eventLayer.style.top =
8951 +                orthogonalOffset + "px";
8952 +};
8953 +
8954 +Timeline.CompactEventPainter.prototype._computeMetrics = function() {
8955 +    var theme = this._params.theme;
8956 +    var eventTheme = theme.event;
8957 +
8958 +    var metrics = {
8959 +        trackOffset:            "trackOffset" in this._params ? this._params.trackOffset : 10,
8960 +        trackHeight:            "trackHeight" in this._params ? this._params.trackHeight : 10,
8961 +
8962 +        tapeHeight:             theme.event.tape.height,
8963 +        tapeBottomMargin:       "tapeBottomMargin" in this._params ? this._params.tapeBottomMargin : 2,
8964 +
8965 +        labelBottomMargin:      "labelBottomMargin" in this._params ? this._params.labelBottomMargin : 5,
8966 +        labelRightMargin:       "labelRightMargin" in this._params ? this._params.labelRightMargin : 5,
8967 +
8968 +        defaultIcon:            eventTheme.instant.icon,
8969 +        defaultIconWidth:       eventTheme.instant.iconWidth,
8970 +        defaultIconHeight:      eventTheme.instant.iconHeight,
8971 +
8972 +        customIconWidth:        "iconWidth" in this._params ? this._params.iconWidth : eventTheme.instant.iconWidth,
8973 +        customIconHeight:       "iconHeight" in this._params ? this._params.iconHeight : eventTheme.instant.iconHeight,
8974 +
8975 +        iconLabelGap:           "iconLabelGap" in this._params ? this._params.iconLabelGap : 2,
8976 +        iconBottomMargin:       "iconBottomMargin" in this._params ? this._params.iconBottomMargin : 2
8977 +    };
8978 +    if ("compositeIcon" in this._params) {
8979 +        metrics.compositeIcon = this._params.compositeIcon;
8980 +        metrics.compositeIconWidth = this._params.compositeIconWidth || metrics.customIconWidth;
8981 +        metrics.compositeIconHeight = this._params.compositeIconHeight || metrics.customIconHeight;
8982 +    } else {
8983 +        metrics.compositeIcon = metrics.defaultIcon;
8984 +        metrics.compositeIconWidth = metrics.defaultIconWidth;
8985 +        metrics.compositeIconHeight = metrics.defaultIconHeight;
8986 +    }
8987 +    metrics.defaultStackIcon = "icon" in this._params.stackConcurrentPreciseInstantEvents ?
8988 +        this._params.stackConcurrentPreciseInstantEvents.icon : metrics.defaultIcon;
8989 +    metrics.defaultStackIconWidth = "iconWidth" in this._params.stackConcurrentPreciseInstantEvents ?
8990 +        this._params.stackConcurrentPreciseInstantEvents.iconWidth : metrics.defaultIconWidth;
8991 +    metrics.defaultStackIconHeight = "iconHeight" in this._params.stackConcurrentPreciseInstantEvents ?
8992 +        this._params.stackConcurrentPreciseInstantEvents.iconHeight : metrics.defaultIconHeight;
8993 +
8994 +    return metrics;
8995 +};
8996 +
8997 +Timeline.CompactEventPainter.prototype._prepareForPainting = function() {
8998 +    var band = this._band;
8999 +
9000 +    if (this._backLayer == null) {
9001 +        this._backLayer = this._band.createLayerDiv(0, "timeline-band-events");
9002 +        this._backLayer.style.visibility = "hidden";
9003 +
9004 +        var eventLabelPrototype = document.createElement("span");
9005 +        eventLabelPrototype.className = "timeline-event-label";
9006 +        this._backLayer.appendChild(eventLabelPrototype);
9007 +        this._frc = SimileAjax.Graphics.getFontRenderingContext(eventLabelPrototype);
9008 +    }
9009 +    this._frc.update();
9010 +    this._tracks = [];
9011 +
9012 +    if (this._highlightLayer != null) {
9013 +        band.removeLayerDiv(this._highlightLayer);
9014 +    }
9015 +    this._highlightLayer = band.createLayerDiv(105, "timeline-band-highlights");
9016 +    this._highlightLayer.style.display = "none";
9017 +
9018 +    if (this._lineLayer != null) {
9019 +        band.removeLayerDiv(this._lineLayer);
9020 +    }
9021 +    this._lineLayer = band.createLayerDiv(110, "timeline-band-lines");
9022 +    this._lineLayer.style.display = "none";
9023 +
9024 +    if (this._eventLayer != null) {
9025 +        band.removeLayerDiv(this._eventLayer);
9026 +    }
9027 +    this._eventLayer = band.createLayerDiv(115, "timeline-band-events");
9028 +    this._eventLayer.style.display = "none";
9029 +};
9030 +
9031 +Timeline.CompactEventPainter.prototype.paintEvent = function(evt, metrics, theme, highlightIndex) {
9032 +    if (evt.isInstant()) {
9033 +        this.paintInstantEvent(evt, metrics, theme, highlightIndex);
9034 +    } else {
9035 +        this.paintDurationEvent(evt, metrics, theme, highlightIndex);
9036 +    }
9037 +};
9038 +
9039 +Timeline.CompactEventPainter.prototype.paintInstantEvent = function(evt, metrics, theme, highlightIndex) {
9040 +    if (evt.isImprecise()) {
9041 +        this.paintImpreciseInstantEvent(evt, metrics, theme, highlightIndex);
9042 +    } else {
9043 +        this.paintPreciseInstantEvent(evt, metrics, theme, highlightIndex);
9044 +    }
9045 +}
9046 +
9047 +Timeline.CompactEventPainter.prototype.paintDurationEvent = function(evt, metrics, theme, highlightIndex) {
9048 +    if (evt.isImprecise()) {
9049 +        this.paintImpreciseDurationEvent(evt, metrics, theme, highlightIndex);
9050 +    } else {
9051 +        this.paintPreciseDurationEvent(evt, metrics, theme, highlightIndex);
9052 +    }
9053 +}
9054 +
9055 +Timeline.CompactEventPainter.prototype.paintPreciseInstantEvent = function(evt, metrics, theme, highlightIndex) {
9056 +    var commonData = {
9057 +        tooltip: evt.getProperty("tooltip") || evt.getText()
9058 +    };
9059 +
9060 +    var iconData = {
9061 +        url: evt.getIcon()
9062 +    };
9063 +    if (iconData.url == null) {
9064 +        iconData.url = metrics.defaultIcon;
9065 +        iconData.width = metrics.defaultIconWidth;
9066 +        iconData.height = metrics.defaultIconHeight;
9067 +        iconData.className = "timeline-event-icon-default";
9068 +    } else {
9069 +        iconData.width = evt.getProperty("iconWidth") || metrics.customIconWidth;
9070 +        iconData.height = evt.getProperty("iconHeight") || metrics.customIconHeight;
9071 +    }
9072 +
9073 +    var labelData = {
9074 +        text:       evt.getText(),
9075 +        color:      evt.getTextColor() || evt.getColor(),
9076 +        className:  evt.getClassName()
9077 +    };
9078 +
9079 +    var result = this.paintTapeIconLabel(
9080 +        evt.getStart(),
9081 +        commonData,
9082 +        null, // no tape data
9083 +        iconData,
9084 +        labelData,
9085 +        metrics,
9086 +        theme,
9087 +        highlightIndex
9088 +    );
9089 +
9090 +    var self = this;
9091 +    var clickHandler = function(elmt, domEvt, target) {
9092 +        return self._onClickInstantEvent(result.iconElmtData.elmt, domEvt, evt);
9093 +    };
9094 +    SimileAjax.DOM.registerEvent(result.iconElmtData.elmt, "mousedown", clickHandler);
9095 +    SimileAjax.DOM.registerEvent(result.labelElmtData.elmt, "mousedown", clickHandler);
9096 +
9097 +    this._eventIdToElmt[evt.getID()] = result.iconElmtData.elmt;
9098 +};
9099 +
9100 +Timeline.CompactEventPainter.prototype.paintCompositePreciseInstantEvents = function(events, metrics, theme, highlightIndex) {
9101 +    var evt = events[0];
9102 +
9103 +    var tooltips = [];
9104 +    for (var i = 0; i < events.length; i++) {
9105 +        tooltips.push(events[i].getProperty("tooltip") || events[i].getText());
9106 +    }
9107 +    var commonData = {
9108 +        tooltip: tooltips.join("; ")
9109 +    };
9110 +
9111 +    var iconData = {
9112 +        url: metrics.compositeIcon,
9113 +        width: metrics.compositeIconWidth,
9114 +        height: metrics.compositeIconHeight,
9115 +        className: "timeline-event-icon-composite"
9116 +    };
9117 +
9118 +    var labelData = {
9119 +        text: String.substitute(this._params.compositeEventLabelTemplate, [ events.length ])
9120 +    };
9121 +
9122 +    var result = this.paintTapeIconLabel(
9123 +        evt.getStart(),
9124 +        commonData,
9125 +        null, // no tape data
9126 +        iconData,
9127 +        labelData,
9128 +        metrics,
9129 +        theme,
9130 +        highlightIndex
9131 +    );
9132 +
9133 +    var self = this;
9134 +    var clickHandler = function(elmt, domEvt, target) {
9135 +        return self._onClickMultiplePreciseInstantEvent(result.iconElmtData.elmt, domEvt, events);
9136 +    };
9137 +
9138 +    SimileAjax.DOM.registerEvent(result.iconElmtData.elmt, "mousedown", clickHandler);
9139 +    SimileAjax.DOM.registerEvent(result.labelElmtData.elmt, "mousedown", clickHandler);
9140 +
9141 +    for (var i = 0; i < events.length; i++) {
9142 +        this._eventIdToElmt[events[i].getID()] = result.iconElmtData.elmt;
9143 +    }
9144 +};
9145 +
9146 +Timeline.CompactEventPainter.prototype.paintStackedPreciseInstantEvents = function(events, metrics, theme, highlightIndex) {
9147 +    var limit = "limit" in this._params.stackConcurrentPreciseInstantEvents ?
9148 +        this._params.stackConcurrentPreciseInstantEvents.limit : 10;
9149 +    var moreMessageTemplate = "moreMessageTemplate" in this._params.stackConcurrentPreciseInstantEvents ?
9150 +        this._params.stackConcurrentPreciseInstantEvents.moreMessageTemplate : "%0 More Events";
9151 +    var showMoreMessage = limit <= events.length - 2; // We want at least 2 more events above the limit.
9152 +                                                      // Otherwise we'd need the singular case of "1 More Event"
9153 +
9154 +    var band = this._band;
9155 +    var getPixelOffset = function(date) {
9156 +        return Math.round(band.dateToPixelOffset(date));
9157 +    };
9158 +    var getIconData = function(evt) {
9159 +        var iconData = {
9160 +            url: evt.getIcon()
9161 +        };
9162 +        if (iconData.url == null) {
9163 +            iconData.url = metrics.defaultStackIcon;
9164 +            iconData.width = metrics.defaultStackIconWidth;
9165 +            iconData.height = metrics.defaultStackIconHeight;
9166 +            iconData.className = "timeline-event-icon-stack timeline-event-icon-default";
9167 +        } else {
9168 +            iconData.width = evt.getProperty("iconWidth") || metrics.customIconWidth;
9169 +            iconData.height = evt.getProperty("iconHeight") || metrics.customIconHeight;
9170 +            iconData.className = "timeline-event-icon-stack";
9171 +        }
9172 +        return iconData;
9173 +    };
9174 +
9175 +    var firstIconData = getIconData(events[0]);
9176 +    var horizontalIncrement = 5;
9177 +    var leftIconEdge = 0;
9178 +    var totalLabelWidth = 0;
9179 +    var totalLabelHeight = 0;
9180 +    var totalIconHeight = 0;
9181 +
9182 +    var records = [];
9183 +    for (var i = 0; i < events.length && (!showMoreMessage || i < limit); i++) {
9184 +        var evt = events[i];
9185 +        var text = evt.getText();
9186 +        var iconData = getIconData(evt);
9187 +        var labelSize = this._frc.computeSize(text);
9188 +        var record = {
9189 +            text:       text,
9190 +            iconData:   iconData,
9191 +            labelSize:  labelSize,
9192 +            iconLeft:   firstIconData.width + i * horizontalIncrement - iconData.width
9193 +        };
9194 +        record.labelLeft = firstIconData.width + i * horizontalIncrement + metrics.iconLabelGap;
9195 +        record.top = totalLabelHeight;
9196 +        records.push(record);
9197 +
9198 +        leftIconEdge = Math.min(leftIconEdge, record.iconLeft);
9199 +        totalLabelHeight += labelSize.height;
9200 +        totalLabelWidth = Math.max(totalLabelWidth, record.labelLeft + labelSize.width);
9201 +        totalIconHeight = Math.max(totalIconHeight, record.top + iconData.height);
9202 +    }
9203 +    if (showMoreMessage) {
9204 +        var moreMessage = String.substitute(moreMessageTemplate, [ events.length - limit ]);
9205 +
9206 +        var moreMessageLabelSize = this._frc.computeSize(moreMessage);
9207 +        var moreMessageLabelLeft = firstIconData.width + (limit - 1) * horizontalIncrement + metrics.iconLabelGap;
9208 +        var moreMessageLabelTop = totalLabelHeight;
9209 +
9210 +        totalLabelHeight += moreMessageLabelSize.height;
9211 +        totalLabelWidth = Math.max(totalLabelWidth, moreMessageLabelLeft + moreMessageLabelSize.width);
9212 +    }
9213 +    totalLabelWidth += metrics.labelRightMargin;
9214 +    totalLabelHeight += metrics.labelBottomMargin;
9215 +    totalIconHeight += metrics.iconBottomMargin;
9216 +
9217 +    var anchorPixel = getPixelOffset(events[0].getStart());
9218 +    var newTracks = [];
9219 +
9220 +    var trackCount = Math.ceil(Math.max(totalIconHeight, totalLabelHeight) / metrics.trackHeight);
9221 +    var rightIconEdge = firstIconData.width + (events.length - 1) * horizontalIncrement;
9222 +    for (var i = 0; i < trackCount; i++) {
9223 +        newTracks.push({ start: leftIconEdge, end: rightIconEdge });
9224 +    }
9225 +    var labelTrackCount = Math.ceil(totalLabelHeight / metrics.trackHeight);
9226 +    for (var i = 0; i < labelTrackCount; i++) {
9227 +        var track = newTracks[i];
9228 +        track.end = Math.max(track.end, totalLabelWidth);
9229 +    }
9230 +
9231 +    var firstTrack = this._fitTracks(anchorPixel, newTracks);
9232 +    var verticalPixelOffset = firstTrack * metrics.trackHeight + metrics.trackOffset;
9233 +
9234 +    var iconStackDiv = this._timeline.getDocument().createElement("div");
9235 +    iconStackDiv.className = 'timeline-event-icon-stack';
9236 +    iconStackDiv.style.position = "absolute";
9237 +    iconStackDiv.style.overflow = "visible";
9238 +    iconStackDiv.style.left = anchorPixel + "px";
9239 +    iconStackDiv.style.top = verticalPixelOffset + "px";
9240 +    iconStackDiv.style.width = rightIconEdge + "px";
9241 +    iconStackDiv.style.height = totalIconHeight + "px";
9242 +    iconStackDiv.innerHTML = "<div style='position: relative'></div>";
9243 +    this._eventLayer.appendChild(iconStackDiv);
9244 +
9245 +    var self = this;
9246 +    var onMouseOver = function(domEvt) {
9247 +        try {
9248 +            var n = parseInt(this.getAttribute("index"));
9249 +            var childNodes = iconStackDiv.firstChild.childNodes;
9250 +            for (var i = 0; i < childNodes.length; i++) {
9251 +                var child = childNodes[i];
9252 +                if (i == n) {
9253 +                    child.style.zIndex = childNodes.length;
9254 +                } else {
9255 +                    child.style.zIndex = childNodes.length - i;
9256 +                }
9257 +            }
9258 +        } catch (e) {
9259 +        }
9260 +    };
9261 +    var paintEvent = function(index) {
9262 +        var record = records[index];
9263 +        var evt = events[index];
9264 +        var tooltip = evt.getProperty("tooltip") || evt.getText();
9265 +
9266 +        var labelElmtData = self._paintEventLabel(
9267 +            { tooltip: tooltip },
9268 +            { text: record.text },
9269 +            anchorPixel + record.labelLeft,
9270 +            verticalPixelOffset + record.top,
9271 +            record.labelSize.width,
9272 +            record.labelSize.height,
9273 +            theme
9274 +        );
9275 +        labelElmtData.elmt.setAttribute("index", index);
9276 +        labelElmtData.elmt.onmouseover = onMouseOver;
9277 +
9278 +        var img = SimileAjax.Graphics.createTranslucentImage(record.iconData.url);
9279 +        var iconDiv = self._timeline.getDocument().createElement("div");
9280 +        iconDiv.className = 'timeline-event-icon' + ("className" in record.iconData ? (" " + record.iconData.className) : "");
9281 +        iconDiv.style.left = record.iconLeft + "px";
9282 +        iconDiv.style.top = record.top + "px";
9283 +        iconDiv.style.zIndex = (records.length - index);
9284 +        iconDiv.appendChild(img);
9285 +        iconDiv.setAttribute("index", index);
9286 +        iconDiv.onmouseover = onMouseOver;
9287 +
9288 +        iconStackDiv.firstChild.appendChild(iconDiv);
9289 +
9290 +        var clickHandler = function(elmt, domEvt, target) {
9291 +            return self._onClickInstantEvent(labelElmtData.elmt, domEvt, evt);
9292 +        };
9293 +
9294 +        SimileAjax.DOM.registerEvent(iconDiv, "mousedown", clickHandler);
9295 +        SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler);
9296 +
9297 +        self._eventIdToElmt[evt.getID()] = iconDiv;
9298 +    };
9299 +    for (var i = 0; i < records.length; i++) {
9300 +        paintEvent(i);
9301 +    }
9302 +
9303 +    if (showMoreMessage) {
9304 +        var moreEvents = events.slice(limit);
9305 +        var moreMessageLabelElmtData = this._paintEventLabel(
9306 +            { tooltip: moreMessage },
9307 +            { text: moreMessage },
9308 +            anchorPixel + moreMessageLabelLeft,
9309 +            verticalPixelOffset + moreMessageLabelTop,
9310 +            moreMessageLabelSize.width,
9311 +            moreMessageLabelSize.height,
9312 +            theme
9313 +        );
9314 +
9315 +        var moreMessageClickHandler = function(elmt, domEvt, target) {
9316 +            return self._onClickMultiplePreciseInstantEvent(moreMessageLabelElmtData.elmt, domEvt, moreEvents);
9317 +        };
9318 +        SimileAjax.DOM.registerEvent(moreMessageLabelElmtData.elmt, "mousedown", moreMessageClickHandler);
9319 +
9320 +        for (var i = 0; i < moreEvents.length; i++) {
9321 +            this._eventIdToElmt[moreEvents[i].getID()] = moreMessageLabelElmtData.elmt;
9322 +        }
9323 +    }
9324 +    //this._createHighlightDiv(highlightIndex, iconElmtData, theme);
9325 +};
9326 +
9327 +Timeline.CompactEventPainter.prototype.paintImpreciseInstantEvent = function(evt, metrics, theme, highlightIndex) {
9328 +    var commonData = {
9329 +        tooltip: evt.getProperty("tooltip") || evt.getText()
9330 +    };
9331 +
9332 +    var tapeData = {
9333 +        start:          evt.getStart(),
9334 +        end:            evt.getEnd(),
9335 +        latestStart:    evt.getLatestStart(),
9336 +        earliestEnd:    evt.getEarliestEnd(),
9337 +        isInstant:      true
9338 +    };
9339 +
9340 +    var iconData = {
9341 +        url: evt.getIcon()
9342 +    };
9343 +    if (iconData.url == null) {
9344 +        iconData = null;
9345 +    } else {
9346 +        iconData.width = evt.getProperty("iconWidth") || metrics.customIconWidth;
9347 +        iconData.height = evt.getProperty("iconHeight") || metrics.customIconHeight;
9348 +    }
9349 +
9350 +    var labelData = {
9351 +        text:       evt.getText(),
9352 +        color:      evt.getTextColor() || evt.getColor(),
9353 +        className:  evt.getClassName()
9354 +    };
9355 +
9356 +    var result = this.paintTapeIconLabel(
9357 +        evt.getStart(),
9358 +        commonData,
9359 +        tapeData, // no tape data
9360 +        iconData,
9361 +        labelData,
9362 +        metrics,
9363 +        theme,
9364 +        highlightIndex
9365 +    );
9366 +
9367 +    var self = this;
9368 +    var clickHandler = iconData != null ?
9369 +        function(elmt, domEvt, target) {
9370 +            return self._onClickInstantEvent(result.iconElmtData.elmt, domEvt, evt);
9371 +        } :
9372 +        function(elmt, domEvt, target) {
9373 +            return self._onClickInstantEvent(result.labelElmtData.elmt, domEvt, evt);
9374 +        };
9375 +
9376 +    SimileAjax.DOM.registerEvent(result.labelElmtData.elmt, "mousedown", clickHandler);
9377 +    SimileAjax.DOM.registerEvent(result.impreciseTapeElmtData.elmt, "mousedown", clickHandler);
9378 +
9379 +    if (iconData != null) {
9380 +        SimileAjax.DOM.registerEvent(result.iconElmtData.elmt, "mousedown", clickHandler);
9381 +        this._eventIdToElmt[evt.getID()] = result.iconElmtData.elmt;
9382 +    } else {
9383 +        this._eventIdToElmt[evt.getID()] = result.labelElmtData.elmt;
9384 +    }
9385 +};
9386 +
9387 +Timeline.CompactEventPainter.prototype.paintPreciseDurationEvent = function(evt, metrics, theme, highlightIndex) {
9388 +    var commonData = {
9389 +        tooltip: evt.getProperty("tooltip") || evt.getText()
9390 +    };
9391 +
9392 +    var tapeData = {
9393 +        start:          evt.getStart(),
9394 +        end:            evt.getEnd(),
9395 +        isInstant:      false
9396 +    };
9397 +
9398 +    var iconData = {
9399 +        url: evt.getIcon()
9400 +    };
9401 +    if (iconData.url == null) {
9402 +        iconData = null;
9403 +    } else {
9404 +        iconData.width = evt.getProperty("iconWidth") || metrics.customIconWidth;
9405 +        iconData.height = evt.getProperty("iconHeight") || metrics.customIconHeight;
9406 +    }
9407 +
9408 +    var labelData = {
9409 +        text:       evt.getText(),
9410 +        color:      evt.getTextColor() || evt.getColor(),
9411 +        className:  evt.getClassName()
9412 +    };
9413 +
9414 +    var result = this.paintTapeIconLabel(
9415 +        evt.getLatestStart(),
9416 +        commonData,
9417 +        tapeData, // no tape data
9418 +        iconData,
9419 +        labelData,
9420 +        metrics,
9421 +        theme,
9422 +        highlightIndex
9423 +    );
9424 +
9425 +    var self = this;
9426 +    var clickHandler = iconData != null ?
9427 +        function(elmt, domEvt, target) {
9428 +            return self._onClickInstantEvent(result.iconElmtData.elmt, domEvt, evt);
9429 +        } :
9430 +        function(elmt, domEvt, target) {
9431 +            return self._onClickInstantEvent(result.labelElmtData.elmt, domEvt, evt);
9432 +        };
9433 +
9434 +    SimileAjax.DOM.registerEvent(result.labelElmtData.elmt, "mousedown", clickHandler);
9435 +    SimileAjax.DOM.registerEvent(result.tapeElmtData.elmt, "mousedown", clickHandler);
9436 +
9437 +    if (iconData != null) {
9438 +        SimileAjax.DOM.registerEvent(result.iconElmtData.elmt, "mousedown", clickHandler);
9439 +        this._eventIdToElmt[evt.getID()] = result.iconElmtData.elmt;
9440 +    } else {
9441 +        this._eventIdToElmt[evt.getID()] = result.labelElmtData.elmt;
9442 +    }
9443 +};
9444 +
9445 +Timeline.CompactEventPainter.prototype.paintImpreciseDurationEvent = function(evt, metrics, theme, highlightIndex) {
9446 +    var commonData = {
9447 +        tooltip: evt.getProperty("tooltip") || evt.getText()
9448 +    };
9449 +
9450 +    var tapeData = {
9451 +        start:          evt.getStart(),
9452 +        end:            evt.getEnd(),
9453 +        latestStart:    evt.getLatestStart(),
9454 +        earliestEnd:    evt.getEarliestEnd(),
9455 +        isInstant:      false
9456 +    };
9457 +
9458 +    var iconData = {
9459 +        url: evt.getIcon()
9460 +    };
9461 +    if (iconData.url == null) {
9462 +        iconData = null;
9463 +    } else {
9464 +        iconData.width = evt.getProperty("iconWidth") || metrics.customIconWidth;
9465 +        iconData.height = evt.getProperty("iconHeight") || metrics.customIconHeight;
9466 +    }
9467 +
9468 +    var labelData = {
9469 +        text:       evt.getText(),
9470 +        color:      evt.getTextColor() || evt.getColor(),
9471 +        className:  evt.getClassName()
9472 +    };
9473 +
9474 +    var result = this.paintTapeIconLabel(
9475 +        evt.getLatestStart(),
9476 +        commonData,
9477 +        tapeData, // no tape data
9478 +        iconData,
9479 +        labelData,
9480 +        metrics,
9481 +        theme,
9482 +        highlightIndex
9483 +    );
9484 +
9485 +    var self = this;
9486 +    var clickHandler = iconData != null ?
9487 +        function(elmt, domEvt, target) {
9488 +            return self._onClickInstantEvent(result.iconElmtData.elmt, domEvt, evt);
9489 +        } :
9490 +        function(elmt, domEvt, target) {
9491 +            return self._onClickInstantEvent(result.labelElmtData.elmt, domEvt, evt);
9492 +        };
9493 +
9494 +    SimileAjax.DOM.registerEvent(result.labelElmtData.elmt, "mousedown", clickHandler);
9495 +    SimileAjax.DOM.registerEvent(result.tapeElmtData.elmt, "mousedown", clickHandler);
9496 +
9497 +    if (iconData != null) {
9498 +        SimileAjax.DOM.registerEvent(result.iconElmtData.elmt, "mousedown", clickHandler);
9499 +        this._eventIdToElmt[evt.getID()] = result.iconElmtData.elmt;
9500 +    } else {
9501 +        this._eventIdToElmt[evt.getID()] = result.labelElmtData.elmt;
9502 +    }
9503 +};
9504 +
9505 +Timeline.CompactEventPainter.prototype.paintTapeIconLabel = function(
9506 +    anchorDate,
9507 +    commonData,
9508 +    tapeData,
9509 +    iconData,
9510 +    labelData,
9511 +    metrics,
9512 +    theme,
9513 +    highlightIndex
9514 +) {
9515 +    var band = this._band;
9516 +    var getPixelOffset = function(date) {
9517 +        return Math.round(band.dateToPixelOffset(date));
9518 +    };
9519 +
9520 +    var anchorPixel = getPixelOffset(anchorDate);
9521 +    var newTracks = [];
9522 +
9523 +    var tapeHeightOccupied = 0;         // how many pixels (vertically) the tape occupies, including bottom margin
9524 +    var tapeTrackCount = 0;             // how many tracks the tape takes up, usually just 1
9525 +    var tapeLastTrackExtraSpace = 0;    // on the last track that the tape occupies, how many pixels are left (for icon and label to occupy as well)
9526 +    if (tapeData != null) {
9527 +        tapeHeightOccupied = metrics.tapeHeight + metrics.tapeBottomMargin;
9528 +        tapeTrackCount = Math.ceil(metrics.tapeHeight / metrics.trackHeight);
9529 +
9530 +        var tapeEndPixelOffset = getPixelOffset(tapeData.end) - anchorPixel;
9531 +        var tapeStartPixelOffset = getPixelOffset(tapeData.start) - anchorPixel;
9532 +
9533 +        for (var t = 0; t < tapeTrackCount; t++) {
9534 +            newTracks.push({ start: tapeStartPixelOffset, end: tapeEndPixelOffset });
9535 +        }
9536 +
9537 +        tapeLastTrackExtraSpace = metrics.trackHeight - (tapeHeightOccupied % metrics.tapeHeight);
9538 +    }
9539 +
9540 +    var iconStartPixelOffset = 0;        // where the icon starts compared to the anchor pixel;
9541 +                                         // this can be negative if the icon is center-aligned around the anchor
9542 +    var iconHorizontalSpaceOccupied = 0; // how many pixels the icon take up from the anchor pixel,
9543 +                                         // including the gap between the icon and the label
9544 +    if (iconData != null) {
9545 +        if ("iconAlign" in iconData && iconData.iconAlign == "center") {
9546 +            iconStartPixelOffset = -Math.floor(iconData.width / 2);
9547 +        }
9548 +        iconHorizontalSpaceOccupied = iconStartPixelOffset + iconData.width + metrics.iconLabelGap;
9549 +
9550 +        if (tapeTrackCount > 0) {
9551 +            newTracks[tapeTrackCount - 1].end = Math.max(newTracks[tapeTrackCount - 1].end, iconHorizontalSpaceOccupied);
9552 +        }
9553 +
9554 +        var iconHeight = iconData.height + metrics.iconBottomMargin + tapeLastTrackExtraSpace;
9555 +        while (iconHeight > 0) {
9556 +            newTracks.push({ start: iconStartPixelOffset, end: iconHorizontalSpaceOccupied });
9557 +            iconHeight -= metrics.trackHeight;
9558 +        }
9559 +    }
9560 +
9561 +    var text = labelData.text;
9562 +    var labelSize = this._frc.computeSize(text);
9563 +    var labelHeight = labelSize.height + metrics.labelBottomMargin + tapeLastTrackExtraSpace;
9564 +    var labelEndPixelOffset = iconHorizontalSpaceOccupied + labelSize.width + metrics.labelRightMargin;
9565 +    if (tapeTrackCount > 0) {
9566 +        newTracks[tapeTrackCount - 1].end = Math.max(newTracks[tapeTrackCount - 1].end, labelEndPixelOffset);
9567 +    }
9568 +    for (var i = 0; labelHeight > 0; i++) {
9569 +        if (tapeTrackCount + i < newTracks.length) {
9570 +            var track = newTracks[tapeTrackCount + i];
9571 +            track.end = labelEndPixelOffset;
9572 +        } else {
9573 +            newTracks.push({ start: 0, end: labelEndPixelOffset });
9574 +        }
9575 +        labelHeight -= metrics.trackHeight;
9576 +    }
9577 +
9578 +    /*
9579 +     *  Try to fit the new track on top of the existing tracks, then
9580 +     *  render the various elements.
9581 +     */
9582 +    var firstTrack = this._fitTracks(anchorPixel, newTracks);
9583 +    var verticalPixelOffset = firstTrack * metrics.trackHeight + metrics.trackOffset;
9584 +    var result = {};
9585 +
9586 +    result.labelElmtData = this._paintEventLabel(
9587 +        commonData,
9588 +        labelData,
9589 +        anchorPixel + iconHorizontalSpaceOccupied,
9590 +        verticalPixelOffset + tapeHeightOccupied,
9591 +        labelSize.width,
9592 +        labelSize.height,
9593 +        theme
9594 +    );
9595 +
9596 +    if (tapeData != null) {
9597 +        if ("latestStart" in tapeData || "earliestEnd" in tapeData) {
9598 +            result.impreciseTapeElmtData = this._paintEventTape(
9599 +                commonData,
9600 +                tapeData,
9601 +                metrics.tapeHeight,
9602 +                verticalPixelOffset,
9603 +                getPixelOffset(tapeData.start),
9604 +                getPixelOffset(tapeData.end),
9605 +                theme.event.duration.impreciseColor,
9606 +                theme.event.duration.impreciseOpacity,
9607 +                metrics,
9608 +                theme
9609 +            );
9610 +        }
9611 +        if (!tapeData.isInstant && "start" in tapeData && "end" in tapeData) {
9612 +            result.tapeElmtData = this._paintEventTape(
9613 +                commonData,
9614 +                tapeData,
9615 +                metrics.tapeHeight,
9616 +                verticalPixelOffset,
9617 +                anchorPixel,
9618 +                getPixelOffset("earliestEnd" in tapeData ? tapeData.earliestEnd : tapeData.end),
9619 +                tapeData.color,
9620 +                100,
9621 +                metrics,
9622 +                theme
9623 +            );
9624 +        }
9625 +    }
9626 +
9627 +    if (iconData != null) {
9628 +        result.iconElmtData = this._paintEventIcon(
9629 +            commonData,
9630 +            iconData,
9631 +            verticalPixelOffset + tapeHeightOccupied,
9632 +            anchorPixel + iconStartPixelOffset,
9633 +            metrics,
9634 +            theme
9635 +        );
9636 +    }
9637 +    //this._createHighlightDiv(highlightIndex, iconElmtData, theme);
9638 +
9639 +    return result;
9640 +};
9641 +
9642 +Timeline.CompactEventPainter.prototype._fitTracks = function(anchorPixel, newTracks) {
9643 +    var firstTrack;
9644 +    for (firstTrack = 0; firstTrack < this._tracks.length; firstTrack++) {
9645 +        var fit = true;
9646 +        for (var j = 0; j < newTracks.length && (firstTrack + j) < this._tracks.length; j++) {
9647 +            var existingTrack = this._tracks[firstTrack + j];
9648 +            var newTrack = newTracks[j];
9649 +            if (anchorPixel + newTrack.start < existingTrack) {
9650 +                fit = false;
9651 +                break;
9652 +            }
9653 +        }
9654 +
9655 +        if (fit) {
9656 +            break;
9657 +        }