/****c* core/ltk
 * NAME
 *      ltk
 * FUNCTION
 *      ltk core library -- derived from lima base javascript library
 * COPYRIGHT
 *      copyright (c) 2007-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

window.ltk = window.ltk || {};

(function() {
    if ('_ltk_version' in ltk) return;
    
    var userAgent = navigator.userAgent.toLowerCase();          // useragent identification
    var uniq_id   = 0;                                          // counter for getUniqId
    
    /*
     * URL for error logger
     */
    var logger_url = '';
    
    ltk = {
        /****v* ltk/__version
         * SYNOPSIS
         */
        _ltk_version: '0.1',
        /*
         * FUNCTION
         *      ltk version
         ****
         */
        
        /****v* ltk/browser
         * SYNOPSIS
         */
        browser: {
            'version':  (userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1],
            'safari':   (/webkit/.test(userAgent)),
            'opera':    (/opera/.test(userAgent)),
            'msie':     (/msie/.test(userAgent) && !(/opera/.test(userAgent))),
            'msie6':    false /*@cc_on || @_jscript_version < 5.7 @*/,
            'mozilla':  (/mozilla/.test(userAgent) && !(/(compatible|webkit)/.test(userAgent)))
        },
        /*
         * FUNCTION
         *      browser detection
         ****
         */

        /****m* ltk/closure
         * SYNOPSIS
         */
        closure: function(param, cb) 
        /*
         * FUNCTION
         *      force creation of a closure (useful inside of loops)
         * INPUTS
         *      * param (mixed) -- parameter to make available ín created closure
         *      * cb (callback) -- callback function to create a closure of
         * OUTPUTS
         *      (closure) -- created closure
         * EXAMPLE
         *      ..  source: js
         *          var test = [];
         *
         *          for (var i = 0; i < 10; ++i) {
         *              test.push(ltk.closure(i, function(i) {
         *                  console.log(i);
         *              }));
         *          }
         *
         *          for (var p = 0, len = test.length; p < len; ++p) {
         *              test[i]();
         *          }
         ****
         */
        {
            return function() {
                var args = [param];
                args = args.concat([].splice.call(arguments, 0));       // hack, because 'arguments' is not a _real_ javascript array

                return cb.apply(window, args);
            };
        },

        /****m* ltk/proxy
         * SYNOPSIS
         */
        proxy: function(cb)
        /*
         * FUNCTION
         *      builds a proxy for a callback, see example for details
         * EXAMPLE
         *      ..  source: js
         *          var t1 = proxy(function() {
         *              return new function() {
         *                  console.log('t1 build');
         *                  this.test = function() {
         *                      console.log('t1 test');
         *                  }
         *              };
         *          });
         *          var t2 = proxy(function() {
         *              return new function() {
         *                  console.log('t2 build');
         *                  this.test = function() {
         *                      console.log('t2 test');
         *                  }
         *              };
         *          });
         *
         *          t1().test(); t1().test();
         *          t2().test(); t2().test();
         * INPUTS
         *      * cb (callback) -- callback to execute on first call
         * OUTPUTS
         *      (callback) -- callback that acts as proxy
         ****
         */
        {
            return (function() {
                var stored = null;

                return function() {
                    if (stored === null) {
                        var args = [].splice.call(arguments, 0);        // hack, because 'arguments' is not a _real_ javascript array

                        stored = cb.apply(window, args);
                    }

                    return stored;
                }     
            })();
        },

        /****m* ltk/compare
         * SYNOPSIS
         */
        compare: function(obj1, obj2)
        /*
         * FUNCTION
         *      compare two objects if they are equal
         * INPUTS
         *      * obj1 (object) -- first object
         *      * obj2 (object) -- second object
         * OUTPUTS
         *      (bool) -- returns true, if objects are equal
         ****
         */
        {
            for (p in obj1) {
                if (!(p in obj2)) { 
                    return false; 
                } else if (typeof obj1[p] !== typeof obj2[p]) {
                    return false;
                }
                
                switch (typeof obj1[p]) {
                case 'object':
                    if (!ltk.compare(obj1[p], obj2[p])) { 
                        return false 
                    }
                    break;
                case 'function':
                    break;
                default:
                    if (obj1[p] != obj2[p]) {
                        return false;
                    }
                }
            }

            for (p in obj2) {
                if (!(p in obj1)) {
                    return false;
                }
            }

            return true;            
        },

        /****m* ltk/backtrace
         * SYNOPSIS
         */
        backtrace: (function()
        /*
         * FUNCTION
         *      produce a backtrace, partly taken from:
         *      http://eriwen.com/javascript/js-stack-trace/
         *      http://eriwen.com/javascript/stacktrace-update/
         ****
         */
        {
            var r = function() {};
            
            if ('console' in window) {
                if ('trace' in console) {
                    r = function() { console.trace(); };
                } else if ('log' in console) {
                    // TODO: to be implemented later
                    r = function() {}
                }
            }
            
            return r;
        })(),

        /****m* ltk/include
         * SYNOPSIS
         */
        include: (function() {
            var included = {};

            return function(url, type)
            /*
             * FUNCTION
             *      include javascript library or css style sheet
             * INPUTS
             *      * url (string) -- URL of javascript library to import
             *      * type (string) -- optional type, may be required, if URL contains no suffix
             ****
             */
            {
                type = (url.substr(-4) == '.css'
                        ? 'css'
                        : (url.substr(-3) == '.js'
                           ? 'js'
                           : (typeof type != 'undefined'
                              ? type
                              : 'js')));
                
                var ref;
                
                if (!(url in included[url])) {
                    switch (type) {
                    case 'js':
                        // include external javascript library
                        ref = ltk.dom.create('script');
                        ref.setAttribute('type', 'text/javascript');
                        ref.setAttribute('src', filename);

                        ltk.dom.get('head').insert(ref);
                        break;
                    case 'css':
                        // include CSS style
                        ref = ltk.dom.create('style');
                        ref.setAttribute('rel',  'stylesheet');
                        ref.setAttribute('type', 'text/css');
                        ref.setAttribute('href', filename);

                        ltk.dom.get('head').insert(ref);
                        break;
                    default:
                        throw 'unknown type "' + type + '"';
                    }
                }
            }
        })(),

        /****m* ltk/addCSS
         * SYNOPSIS
         */
        addCSS: function(css)
        /*
         * FUNCTION
         *      add css styles to document
         * INPUTS
         *      * css (string) -- css styles to add to document
         ****
         */
        {
            var style = ltk.dom.create('STYLE', {'type': 'text/css'});

            if (style.styleSheet) {
                style.styleSheet.cssText = css;
            } else {
                style.setProperties({'#text': css});
            }
            
            ltk.dom.one('HEAD').appendChild(style);
        },

        /****m* ltk/clone
         * SYNOPSIS
         */
        clone: function(obj)
        /*
         * FUNCTION
         *      clone object
         * INPUTS
         *      * obj (mixed) -- an object of any type to clone
         * OUTPUTS
         *      (mixed) -- 1:1 clone of source object
         ****
         */
        {
            var i, cnt, o;

            if (typeof obj !== 'object' || obj === null) {
                return obj;
            } else {
                if (obj instanceof Array) {
                    o = [];

                    for (i = 0, cnt = obj.length; i < cnt; ++i) {
                        o[i] = ltk.clone(obj[i]);
                    }
                } else {
                    o = {};

                    for (i in obj) {
                        o[i] = ltk.clone(obj[i]);
                    }
                }
            }

            return o;
        },

        /****m* ltk/isEmpty
         * SYNOPSIS
         */
        isEmpty: function(obj)
        /*
         * FUNCTION
         *      test, if object or array is empty
         * INPUTS
         *      * obj (object) -- object to test
         * OUTPUTS
         *      (bool) -- true/false
         ****
         */
        {
            var ret = true;
            
            if (obj instanceof Array) {
                ret = (obj.length === 0);
            } else {
                for (var i in obj) {
                    if (obj.hasOwnProperty(i)) {
                        ret = false;
                        break;
                    }
                }
            }

            return ret;
        },

        /****m* ltk/extend
         * SYNOPSIS
         */
        extend: function(dest, source)
        /*
         * FUNCTION
         *      extends an object by the properties of some other object
         * INPUTS
         *      * dest (object) -- object to extend
         *      * source (object) -- properties to extend dest object with
         * OUTPUTS
         *      (object) -- extended object
         ****
         */
        {
            dest = (typeof dest != 'object'
                    ? {}
                    : dest);
                    
            for (var i in source) {
                dest[i] = source[i];
            }

            return dest;
        },

        /****m* ltk/getUniqID
         * SYNOPSIS
         */
        getUniqID: function(prefix)
        /*
         * FUNCTION
         *      generate uniq ID
         * INPUTS
         *      * prefix (string) -- (optional) prefix for uniq ID (default: '')
         * OUTPUTS
         *      (string) -- uniq ID concatenated to specified prefix
         ****
         */
        {
            prefix = (typeof prefix == 'undefined' ? '' : prefix);

            return prefix + (++uniq_id);
        },
        
        /****m* ltk/errorlog
         * SYNOPSIS
         */
        errorlog: function(enabled, url)
        /*
         * FUNCTION
         *      enable / disable error logging
         * INPUTS
         *      * enabled (bool) -- whether to enable/disable error logging
         *      * url (string) -- URL to log errors to
         * REFERENCE
         *      http://devblog.xing.com/frontend/how-to-log-javascript-errors/
         ****
         */
        {
            if (enabled) {
                logger_url = url || logger_url;
                
                if ((logger_url = url || logger_url)) {
                    window.error = function(msg, url, line) {
                        var p = '?description=' + encodeURIcomponent(msg) +
                                '&url=' + encodeURIcomponent(url) +
                                '&line=' + encodeURIcomponent(line) +
                                '&parent_url=' + encodeURIcomponent(document.location.href) +
                                '&user_agent=' + encodeURIcomponent(navigator.userAgent);
                        
                        (new Image()).src = logger_url + p;
                    }
                }
            } else {
                window.error = function(msg, url, line) {};
            }
        }
    };
})();

/****c* ltk/chain
 * NAME
 *      ltk.chain
 * FUNCTION
 *      Implement a function chain. The chain is filled by specifying callbacks
 *      using the addCallback method. The addCallback method accepts an optional
 *      timeout. If the timeout is specified, the callback is triggered in an 
 *      interval which is triggered by the specified timeout. The timout is 
 *      triggered until the callback returns ~false~. If the callback returns 
 *      ~false~, the callback will be called again and again.
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald@octris.org>
 ****
 */

;(function() {
    if ('chain' in ltk) return;
    
    /****m* chain/
     * SYNOPSIS
     */
    ltk.chain = function()
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.chain = [];
    }
    
    /****m* chain/addCallback
     * SYNOPSIS
     */
    ltk.chain.prototype.addCallback = function(cb, ms)
    /*
     * FUNCTION
     *      add a callback to the chain
     * INPUTS
     *      * cb (callback) -- callback to add
     *      * ms (int) -- (optional) timeout that triggers the callback call
     ****
     */
    {
        var vec = cb;
        var me  = this;

        if (typeof ms != 'undefined') {
            cb = function(thread, step) {
                window.setTimeout(function() {
                    if (vec(step)) {
                        // execute timeout triggered callback until callback returns ~false~
                        cb(thread, step);
                    } else {
                        thread.next();
                    }
                }, ms);
            }
        } else {
            cb = function(thread, step) {
                vec(step);
                
                thread.next();
            }
        }
        
        this.chain.push(cb);
    }
    
    /****m* chain/execute
     * SYNOPSIS
     */
    ltk.chain.prototype.execute = function()
    /*
     * FUNCTION
     *      execute the chain
     ****
     */
    {
        var me     = this;
        var thread = (new function() {
            var step = 0;
            
            this.next = function() {
                if (step < me.chain.length) {
                    ++step;

                    me.chain[step - 1](this, step);
                } else {
                    thread = undefined;
                }
            }
        });

        window.setTimeout(function() {
            thread.next();
        }, 0);
    }
})();

/****c* core/cookie
 * NAME
 *      ltk.cookie
 * FUNCTION
 *      cookie handling library
 * COPYRIGHT
 *      copyright (c) 2007-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */ 
 
;(function() {
    if ('cookie' in ltk) return;

    var instance = {};      // cookie instances

    /****m* settings/constructor
     * SYNOPSIS
     */
    ltk.cookie = function(name, expires, path, domain, secure)
    /*
     * FUNCTION
     *      singleton - returns intance of ltk.settings or creates a new instance
     * INPUTS
     *      * name (string) -- name of cookie settings are stored in
     *      * expires (int) -- (optional) expiration of settings cookie (default: 0)
     *      * path (string) -- (optional) path for cookie (default: '')
     *      * domain (string) -- (optional) domain restriction for cookie (default: '')
     *      * secure (bool) -- (optional) secure cookie true/false (default: insecure)
     ****
     */
    {
        if (!(name in instance)) {
            instance[name] = this;

            this.name = name;
        
            expires = parseInt(expires, 10);
        
            this.expires = (isNaN(expires) ? 0 : expires);
            this.path = path || '';
            this.domain = domain || '';
            this.secure = secure || false;
        }

        return instance[name];
    }

    /****m* cookie/get
     * SYNOPSIS
     */
    ltk.cookie.prototype.get = function() 
    /*
     * FUNCTION
     *      get cookie value by name
     * OUTPUTS
     *      returns cookie value
     ****
     */
    {
        var pos = document.cookie.indexOf(this.name + '=');
        var len = pos + this.name.length + 1;

        if ((!pos && this.name != document.cookie.substring(0, this.name.length)) || pos < -1) {
            return null;
        }

        var end = document.cookie.indexOf(';', len);

        if (end == -1) {
            end = document.cookie.length;
        }

        return unescape(document.cookie.substring(len, end));
    }

    /****m* cookie/set
     * SYNOPSIS
     */
    ltk.cookie.prototype.set = function(value)
    /*
     * FUNCTION
     *      sets cookie value
     * INPUTS
     *      * value (mixed) -- value to set for cookie
     ****
     */
    {
        var date;

        if (isNaN(parseInt(this.expires, 10)) || this.expires === 0) {
            date = 0;
        } else {
            date = new Date();
            date.setTime(date.getTime() + this.expires);
            date = date.toGMTString();
        }

        var str = this.name + '=' + escape(value) +
                  (this.expires ? ';expires=' + date : '') +
                  (this.path ? ';path=' + this.path : '') +
                  (this.domain ? ';domain=' + this.domain : '') +
                  (this.secure ? ';secure' : '');

        document.cookie = str;
    }

    /****m* poma_cookie/poma.cookie.remove
     * SYNOPSIS
     */
    ltk.cookie.prototype.remove = function()
    /*
     * FUNCTION
     *      removes cookie
     ****
     */
    {
        document.cookie = 
            this.name + '=' +
            (this.path ? ';path=' + this.path : '') +
            (this.domain ? ';domain=' + this.domain : '') +
            ';expires=Thu, 01-Jan-70 00:00:01 GMT';
    }
})();

/****c* ltk/dom
 * NAME
 *      ltk.dom
 * FUNCTION
 *      dom processing for LTK library
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('dom' in ltk) return;
    
    ltk.dom = {
        /****m* dom/ready
         * SYNOPSIS
         */
        ready: (function()
        /*
         * FUNCTION
         *      DOM ready implementation derived from
         *      http://code.google.com/p/domready/
         * INPUTS
         *      *   cb (callback) -- callback to register to be called when DOM is ready loaded
         ****
         */
        {
            var is_bound = false;               // remember if DOM ready events are already bound
            var is_ready = false;               // remember that DOM is ready
            var queue    = [];                  // callback queue

            /*
             * call registered callbacks when DOM is ready
             */
            function _ready() {
                if (!is_ready) {
                    // only execute first time DOM is ready
                    is_ready = true;

                    for(var i = 0, len = queue.length; i < len; ++i) {
                        queue[i]();
                    }

                    queue = [];
                }
            }

            /*
             * bind required events to determine ready DOM
             */
            function _bind() {
                if (is_bound) return;

                is_bound = true;

                // Mozilla, Opera (see further below for it) and webkit nightlies currently support this event
                if (document.addEventListener && !ltk.browser.opera) {
                    // Use the handy event callback
                    document.addEventListener("DOMContentLoaded", _ready, false);
                }

                // If IE is used and is not in a frame
                // Continually check to see if the document is ready
                if (ltk.browser.msie && window == top) {
                    (function() {
                        if (is_ready) return;
                        
                        try {
                            // If IE is used, use the trick by Diego Perini
                            // http://javascript.nwbox.com/IEContentLoaded/
                            document.documentElement.doScroll("left");
                        } catch(error) {
                            setTimeout(arguments.callee, 0);
                            return;
                        }
                        
                        _ready();
                    })();
                }

                if (ltk.browser.opera) {
                    document.addEventListener( "DOMContentLoaded", function() {
                        if (is_ready) return;
                        
                        for (var i = 0, len = document.styleSheets.length; i < len; ++i) {
                            if (document.styleSheets[i].disabled) {
                                setTimeout(arguments.callee, 0);
                                return;
                            }
                        }
                        
                        _ready();
                    }, false);
                }

                if (ltk.browser.safari) {
                    var cnt_styles;
                    
                    (function() {
                        if (is_ready) return;
                        
                        if (document.readyState != "loaded" && document.readyState != "complete") {
                            setTimeout(arguments.callee, 0);
                            return;
                        }
                        
                        if (cnt_styles === undefined) {
                            var links = document.getElementsByTagName("link");
                            
                            for (var i = 0, len = links.length; i < len; ++i) {
                                if(links[i].getAttribute('rel') == 'stylesheet') {
                                    ++cnt_styles;
                                }
                            }
                            
                            var styles = document.getElementsByTagName("style");
                            cnt_styles += styles.length;
                        }
                        
                        if (document.styleSheets.length != cnt_styles) {
                            setTimeout(arguments.callee, 0);
                            return;
                        }

                        // and execute any waiting functions
                        domReady();
                    })();
                }

                // fallback
                if (typeof window.onload != 'function') {
                    window.onload = _ready;
                } else {
                    var vec = window.onload;
                    
                    window.onload = function() {
                        vec();
                        _ready();
                    }
                }
            };

            // This is the public function that people can use to hook up ready.
            return function(cb) {
                // attach the listeners
                _bind();

                if (is_ready) {
                    // DOM is ready -- call immediately
                    cb();
                } else {
                    // add callback to queue
                    queue.push(function() { return cb(); });
                }
            }
        })(),
        
        /****m* dom/get
         * SYNOPSIS
         */
        get: function(obj, parent)
        /*
         * FUNCTION
         *      get LTK-DOM node by specified phrase or by browsers' DOM node
         * INPUTS
         *      *   obj (mixed) -- search-phrase or DOM node to return LTK-DOM node for, single value or array of values is allowed
         *      *   parent (ltk.dom.node) -- optional parent node to search in
         * OUTPUTS
         *      (array) -- array of instances of ltk.dom.node
         * REFERENCE
         *      http://sizzlejs.com/
         ****
         */
        {
            var r = new ltk.dom.nodelist([]);
            var i, len;
            
            if (!parent || !(parent instanceof ltk.dom.node)) {
                parent = document;
            } else {
                parent = parent.node;
            }
    
            if (obj instanceof Array) {
                for (i = 0, len = obj.length; i < len; ++i) {
                    r.concat(this.get(obj[i], parent));
                }
            } else {
                if (typeof obj == 'object' && obj instanceof DOMNode) {
                    // DOM node specified
                    r.concat([obj]);
                } else if (typeof obj == 'string') {
                    // selector specified
                    r.concat(Sizzle(obj, parent));
                } else if (obj instanceof Array) {
                    // array of selectors specified
                    for (i = 0, len = obj.length; i < len; ++i) {
                        r.concat(Sizzle(obj, parent));
                    }
                }
            }
    
            return r;
        },

        /****m* dom/one
         * SYNOPSIS
         */
        one: function(obj, parent)
        /*
         * FUNCTION
         *      get LTK-DOM node by specified phrase or by browsers' DOM node. Alias for first
         * INPUTS
         *      * obj (mixed) -- search-phrase or DOM node to return LTK-DOM node for
         *      * parent (ltk.dom.node) -- optional parent node to search in
         * OUTPUTS
         *      (ltk.dom.node) -- node
         ****
         */
        {
            return this.first(obj, parent);
        },

        /****m* dom/first
         * SYNOPSIS
         */
        first: function(obj, parent)
        /*
         * FUNCTION
         *      get LTK-DOM node by specified phrase or by browsers' DOM node, return first node of resulting nodelist
         * INPUTS
         *      *   obj (mixed) -- search-phrase or DOM node to return LTK-DOM node for
         *      *   parent (ltk.dom.node) -- optional parent node to search in
         * OUTPUTS
         *      (ltk.dom.node) -- node
         ****
         */
        {
            return this.get(obj, parent).first();
        },

        /****m* dom/last
         * SYNOPSIS
         */
        last: function(obj, parent)
        /*
         * FUNCTION
         *      get LTK-DOM node by specified phrase or by browsers' DOM node, return last node of resulting nodelist
         * INPUTS
         *      *   obj (mixed) -- search-phrase or DOM node to return LTK-DOM node for
         *      *   parent (ltk.dom.node) -- optional parent node to search in
         * OUTPUTS
         *      (ltk.dom.node) -- node
         ****
         */
        {
            return this.get(obj, parent).last();
        },

        /****m* dom/create
         * SYNOPSIS
         */
        create: function(tag, def)
        /*
         * FUNCTION
         *      create DOM and return LTK-DOM node as instance
         * INPUTS
         *      *   tag (string) -- tag to create node for
         *      *   def (object) -- (optional) definition for created element and child elements using dom helper
         * OUTPUTS
         *      (ltk.dom.node) -- instance of LTK-DOM node
         ****
         */
        {
            def = def || {};
            
            function _get_tag(def) {
                var tag = '';
                
                if (typeof def == 'object') {
                    for (var i in def) {
                        tag = i;
                        break;
                    }
                }
                
                return tag;
            }
            
            function _build(tag, def) {
                var i, len, tmp;

                var node = new ltk.dom.node(document.createElement(tag));
                node.setProperties(def);
                
                if ('children' in def && def['children'] instanceof Array) {
                    // iterate over children
                    for (i = 0, len = def['children'].length; i < len; ++i) {
                        if ((tmp = _get_tag(def['children'][i])) !== '') {
                            node.appendChild(_build(tmp, def['children'][i][tmp]));
                        }
                    }
                }

                return node;
            }

            return _build(tag, def);
        },
        
        /****m* dom/getScrollbarWidth
         * SYNOPSIS
         */
        getScrollbarWidth: (function()
        /*
         * FUNCTION
         *      calculate width of scrollbar
         * OUTPUTS
         *      (object) -- width of scrollbar
         * REFERENCE
         *      http://www.alexandre-gomes.com/?p=115
         ****
         */
        {
            var width = null;

            return function() {
                if (width === null) {
                    var inner;
                    var outer = ltk.dom.one('body').appendChild(ltk.dom.create('div', {
                        'styles':   {
                            'position':   'absolute',
                            'top':        '0',
                            'left':       '0',
                            'visibility': 'hidden',
                            'width':      '200px',
                            'height':     '150px',
                            'overflow':   'hidden'
                        },
                        'children': [
                            {'p': {
                                '#trigger': function(node) {
                                    inner = node;
                                },
                                'styles':   {
                                    'width':  '100%',
                                    'height': '200px'
                                }
                            }}
                        ]
                    }));

                    var w1 = inner.node.offsetWidth;
                    
                    outer.setStyle('overflow', 'scroll');
                    
                    var w2 = inner.node.offsetWidth;
                    
                    if (w1 == w2) w2 = outer.node.clientWidth;

                    outer.removeNode();

                    width = (w1 - w2);
                }
                
                return width;
            };
        })(),
        
        /****m* dom/getViewport
         * SYNOPSIS
         */
        getViewport: function()
        /*
         * FUNCTION
         *      calculate width/height (w/h) of viewport
         * OUTPUTS
         *      (object) -- w/h of viewport
         ****
         */
        {
            var viewport = {'w': null, 'h': null};

            if (window.innerHeight != window.undefined) {
                viewport.h = window.innerHeight;
                viewport.w = window.innerWidth;
            } else if (document.compatMode == 'CSS1Compat') {
                viewport.h = document.documentElement.clientHeight;
                viewport.w = document.documentElement.clientWidth;
            } else if (document.body) {
                viewport.h = document.body.clientHeight;
                viewport.w = document.body.clientWidth;
            }

            return viewport;
        }
    }
})();

/****c* dom/node
 * NAME
 *      ltk.dom.node
 * FUNCTION
 *      LTK-DOM node object
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

(function() {
    if ('node' in ltk.dom) return;

    /*
     * helper stuff for storing node data
     */
    var data_disabled = {
        'embed': true, 'object': true, 'applet': true
    };
    var data_store = {};

    /*
     * garbage collector to prevent circular references when removing DOM nodes
     * reference: http://blog.xing.com/2009/06/memory_leaks_internet_explorer/
     */
    function cleanup(e) {
        var i, len, tmp = e.attributes;
        
        if (tmp) {
            for (i = 0, len = tmp.length; i < len; ++i) {
                if (typeof e[tmp[i].name] === 'function') {
                    e[tmp[i].name] = null;
                }
            }
        }
        
        if ((tmp = e.childNodes)) {
            for (i = 0, len = tmp.length; i < len; ++i) {
                cleanup(tmp[i]);
            }
        }
    }

    /****m* node/construct
     * SYNOPSIS
     */
    ltk.dom.node = function(node)
    /*
     * FUNCTION
     *      constructor
     * INPUTS
     *      * node (DOMNode) -- instance of DOMNode
     ****
     */
    {
        this.node = node;
    }

    /****m* node/getTextContent
     * SYNOPSIS
     */
    ltk.dom.node.prototype.getTextContent = function()
    /*
     * FUNCTION
     *      cross browser implementation to return text content of a node
     * OUTPUTS
     *      (string) -- textual content of node
     ****
     */
    {
        var ret = '';
        
        if ('textContent' in this.node) {
            // Gecko; Safari Nightly (Webkit)
            ret = this.node.textContent;
        } else if ('text' in this.node) {
            // IE
            ret = this.node.text;
        } else if (this.node.childNodes.length > 0 && this.node.firstChild.nodeType == 3) {
            // Safari v1.x, Safari v2
            ret = this.node.firstChild.nodeValue;
        }
        
        return ret;
    }

    /****m* node/getContentDocument
     * SYNOPSIS
     */
    ltk.dom.node.prototype.getContentDocument = function()
    /*
     * FUNCTION
     *      return reference to iframe document root object
     * OUTPUTS
     *      (ltk.dom.node) -- instance of LTK DOM node
     ****
     */
    {
        var docroot = null;
        
        if (this.node.contentDocument) {
            // mozilla
            docroot = this.node.contentDocument;
        } else if (obj.contentWindow) {
            // IE 5.5 and up
            docroot = this.node.contentWindow;
        } else if (obj.document) {
            // IE 5
            docroot = this.node.document;
        }
        
        return (docroot ? new ltk.dom.node(docroot) : null);
    }
    
    /****m* node/getComputedStyle
     * SYNOPSIS
     */
    ltk.dom.node.prototype.getComputedStyle = function(property)
    /*
     * FUNCTION
     *      cross browser compatible implementation of getComputedStyle
     * INPUTS
     *      * property (string) -- property to return computed style of
     * OUTPUTS
     *      (mixed) -- computed style
     ****
     */
    {
        var value = '';
        var style, i;
        
        if (window.getComputedStyle) {
            // mozilla, opera, etc.
            style = window.getComputedStyle(this.node, null);
            value = style.getPropertyValue(property);
        } else if (this.node.currentStyle && property.substr(0, 1) != '-') {
            // IE
            var tmp = property.split('-');
            property = tmp[0];
            
            for (i = 1; i < tmp.length; i++) {
                property += tmp[i].substr(0, 1).toUpperCase() + tmp[i].substr(1, tmp[i].length);
            }
            
            value = this.node.currentStyle[property];
        } else if (document.defaultView) {
            // safari, konqueror
            style = document.defaultView.getComputedStyle(this.node, null);
            value = style.getPropertyValue(property);
        }
        
        if (typeof value === 'undefined') {
            value = '';
        } else if (typeof value === 'number') {
            value = toString(value);
        }

        if (value.indexOf('rgb') > -1 && value.indexOf('rgba') < 0) {
            // convert (firefox') rgb values to #xxxxxx notation
            value = value.split('(')[1].split(')')[0].split(',');
            
            for (i = 0; i < 3; i++) {
                value[i] = ('0' + parseInt(value[i], 10).toString(16)).substr(-2);
            }
            
            value = '#' + value.join('');
        }
        
        if (value.substr(0, 1) == '#' && value.length == 4) {
            // handle 3-digit color notation
            value = '#' + value.substr(1, 1) + value.substr(1, 1) +
                          value.substr(2, 1) + value.substr(2, 1) +
                          value.substr(3, 1) + value.substr(3, 1);
        }
        
        return value;
    }

    /****m* node/getBackgroundColor
     * SYNOPSIS
     */
    ltk.dom.node.prototype.getBackgroundColor = function(transparent)
    /*
     * FUNCTION
     *      try to determine the background color of current node's parent. returns #fff, if no other
     *      background could be detected. 'transparent' as color is only be returned, if first 
     *      parameter of this method is set to true.
     * INPUTS
     *      * transparent (optional) -- return 'transparent' as color (default: false)
     * OUTPUTS
     *      (string) -- color in hexadezimal format
     ****
     */
    {
        transparent = (typeof transparent != 'undefined' ? !!transparent : false);

        var color       = '#ffffff';
        var obj         = this.node;

        while (obj.parentNode) {
            obj = obj.parentNode;
            tmp = ltk.dom.node.prototype.getComputedStyle.apply({'node': obj}, 'background-color');

            if (tmp.substr(0, 1) == '#') {
                color = tmp;
                break;
            } else if (tmp == 'transparent' && transparent) {
                color = tmp;
                break;
            }
        }

        return color;
    }

    /****m* node/getOpacity
     * SYNOPSIS
     */
    ltk.dom.node.prototype.getOpacity = function()
    /*
     * FUNCTION
     *      return opacity of node
     ****
     */
    {
        var opacity = this.getComputedStyle('opacity');
        
        if (opacity.indexOf('%') >= 0) {
            opacity = parseInt(opacity, 10);
        } else if (opacity.substr(0, 1) == '.' || opacity.substr(0, 2) == '0.') {
            opacity *= 100;
        }
        
        return opacity;
    }

    /****m* node/getPos
     * SYNOPSIS
     */
    ltk.dom.node.prototype.getPos = function()
    /*
     * FUNCTION
     *      return absolute x/y position of node
     * OUTPUTS
     *      (object) -- x/y position of node
     ****
     */
    {
        var curleft = 0;
        var curtop  = 0;
        var obj     = this.node;

        if (obj.offsetParent) {
            while (obj.offsetParent) {
                curleft += obj.offsetLeft;
                curtop  += obj.offsetTop;

                obj = obj.offsetParent;
            }
        } else {
            if (obj.x) curleft += obj.x;
            if (obj.y) curtop += obj.y;
        }

        return {'x': curleft, 'y': curtop};
    }

    /****m* node/hasClass
     * SYNOPSIS
     */
    ltk.dom.node.prototype.hasClass = function(classname) 
    /*
     * FUNCTION
     *      check, if node has a specified class assigned
     * INPUTS
     *      * classname (string) -- name of class to check
     * OUTPUTS
     *      (bool) -- returns true, if class is available
     ****
     */
    {
        var pattern = new RegExp('\\b' + classname + '\\b', '');
        
        return ('className' in this.node && this.node.className.match(pattern));
    }

    /****m* node/removeClass
     * SYNOPSIS
     */
    ltk.dom.node.prototype.removeClass = function(classname) 
    /*
     * FUNCTION
     *      remove class from node
     * INPUTS
     *      * classname (string) -- name of class to remove
     ****
     */
    {
        if (!('className' in this.node)) return;
        
        var pattern = new RegExp('\\b' + classname + '\\b', '');

        this.node.className = this.node.className.replace(pattern, '');
    }

    /****m* node/addClass
     * SYNOPSIS
     */
    ltk.dom.node.prototype.addClass = function(classname) 
    /*
     * FUNCTION
     *      add class to node
     * INPUTS
     *      * classname (string) -- name of class to add
     ****
     */
    {
        if (!('className' in this.node)) {
            this.node.className = classname;
        } else {
            var pattern = new RegExp('\\b' + classname + '\\b', '');
        
            if (!this.node.className.match(pattern)) {
                this.node.className = (this.node.className !== ''
                                       ? this.node.className + ' ' + classname
                                       : classname);
            }
        }
    }

    /****m* node/replaceClass
     * SYNOPSIS
     */
    ltk.dom.node.prototype.replaceClass = function(classname1, classname2) 
    /*
     * FUNCTION
     *      perform removeClass and addClass at once
     * INPUTS
     *      * classname1 (string) -- name of class to remove
     *      * classname2 (string) -- name of class to add
     ****
     */
    {
        if (!('className' in this.node)) {
            this.node.className = classname2;
        } else {
            if (classname1 != classname2) {
                this.removeClass(classname1);
            }

            this.addClass(classname2);
        }
    }

    /****m* node/setProperties
     * SYNOPSIS
     */
    ltk.dom.node.prototype.setProperties = function(def)
    /*
     * FUNCTION
     *      set various properties of node -- attributes, styles, etc.
     * INPUTS
     *      * def (object) -- properties to set for node
     ****
     */
    {
        var me = this;
        var i;
        
        var trigger = null;
        
        for (var attr in def) {
            switch (attr) {
            case '#trigger':
                if (typeof def['#trigger'] == 'function') {
                    trigger = def['#trigger'];
                }
                break;
            case '#data':
                for (i in def['#data']) {
                    this.setData(i, def['#data'][i]);
                }
                break;
            case '#text':
                this.node.appendChild(document.createTextNode(def['#text']));
                break;
            case '#html':
                this.node.innerHTML = def['#html'];
                break;
            case 'class':
                // IE doesn't apply styles, if class attribute is applied through setAttribute
                this.node.className = def['class'];
                break;
            case 'styles':
                if (typeof def['styles'] == 'object') {
                    this.setStyles(def['styles']);
                }
                break;
            case 'disabled':
                this.node['disabled'] = !!def['disabled'];
                break;
            case 'checked':
                this.node['checked']        = !!def['checked'];
                this.node['defaultChecked'] = !!def['checked']; // IE6/IE7 HACK
                break;
            default:
                if (attr.substr(0, 2) == 'on') {
                    // apply event handler
                    if (typeof def[attr] == 'string') {
                        // IE workaround, because setAttribute doesn't work with 
                        this.node[attr] = ltk.closure(def[attr], function(js) {
                            var cb = function() {
                                eval(js);
                            };

                            cb.apply(me.node);
                        });
                    } else {
                        this.node[attr] = def[attr];
                    }
                } else if (typeof def[attr] != 'object') {
                    // other attributes
                    this.setAttribute(attr, def[attr], 0);
                }
                break;
            }
        }
        
        if (trigger !== null) {
            trigger(this);
        }
    }

    /****m* node/setStyles
     * SYNOPSIS
     */
    ltk.dom.node.prototype.setStyles = function(styles)
    /*
     * FUNCTION
     *      set styles for node
     * INPUTS
     *      * styles (object) -- style definitions to set
     ****
     */
    {
        for (var i in styles) {
            this.setStyle(i, styles[i]);
        }
    }

    /****m* node/setStyle
     * SYNOPSIS
     */
    ltk.dom.node.prototype.setStyle = function(name, value)
    /*
     * FUNCTION
     *      set style for node
     * INPUTS
     *      * name (string) -- name of style to set
     *      * value (mixed) -- value to set style to
     ****
     */
    {
        switch (name) {
        case 'float':
            this.setFloat(value);
            break;
        case 'opacity':
            this.setOpacity(value);
            break;
        case 'textShadow':
            this.setTextShadow();
            break;
        default:
            this.node.style[name] = value;
            break;
        }
    }

    /****m* node/getStyle
     * SYNOPSIS
     */
    ltk.dom.node.prototype.getStyle = function(name)
    /*
     * FUNCTION
     *      alias for getComputedStyle
     * INPUTS
     *      * name (string) -- name of style to get
     * OUTPUTS
     *      (mixed) -- computed style
     ****
     */
    {
        return this.getComputedStyle(name);
    }

    /****m* node/setOpacity
     * SYNOPSIS
     */
    ltk.dom.node.prototype.setOpacity = function(value)
    /*
     * FUNCTION
     *      set opacity for DOM node
     * INPUTS
     *      * value (string) -- amount of opacity in percent to set
     ****
     */
    {
        var obj = this.node;
    
        var _setOpacity = [
            function() {
                obj.filter.alpha.opacity = value;
            },
            function () {
                obj.style.filter = 'alpha(opacity:' + value + ')';
            },
            function () {
                obj.style.filter = 'progid:DXImageTransform.Microsoft.Alpha(opacity=' + value + ');';
            },
            function () {
                obj.style.KHTMLOpacity = (value / 100);
            },
            function () {
                obj.style.MozOpacity = (value / 100);
            },
            function () {
                obj.style.opacity = (value / 100);
            }
        ];
        
        for (var i = 0, len = _setOpacity.length; i < len; ++i) {
            try { _setOpacity[i](); } catch(e) {}
        }
    },

    /****m* node/setData
     * SYNOPSIS
     */
    ltk.dom.node.prototype.setData = function(name, data)
    /*
     * FUNCTION
     *      add data to a node
     * INPUTS
     *      * name (string) -- name of data to set
     *      * data (mixed) -- data to store in node
     * REFERENCE
     *      http://www.nealgrosskopf.com/tech/thread.php?pid=69
     ****
     */
    {
        if (this.node.nodeName in data_disabled) return;
        
        var id = '';
        
        if (!this.hasAttribute('_ltk_node_id')) {
            id = ltk.getUniqID('ltknid_');
            this.setAttribute('_ltk_node_id', id);
        } else {
            id = this.getAttribute('_ltk_node_id');
        }
        
        if (!(id in data_store)) data_store[id] = {};
        
        data_store[id][name] = data;
    }
    
    /****m* node/getData
     * SYNOPSIS
     */
    ltk.dom.node.prototype.getData = function(name)
    /*
     * FUNCTION
     *      retrieve data assigned to node using setData method
     * INPUTS
     *      * name (string) -- name of data to retrieve
     * REFERENCE
     *      http://www.nealgrosskopf.com/tech/thread.php?pid=69
     ****
     */
    {
        var id;
        
        if (this.node.nodeName in data_disabled || (!(id = this.getAttribute('_ltk_node_id'))) || !(id in data_store) || !(name in data_store[id])) return undefined;
        
        return data_store[id][name];
    }
    
    /****m* node/removeData
     * SYNOPSIS
     */
    ltk.dom.node.prototype.removeData = function(name)
    /*
     * FUNCTION
     *      remove data previous assigned to node
     * INPUTS
     *      * name (string) -- name of data to remove
     * REFERENCE
     *      http://www.nealgrosskopf.com/tech/thread.php?pid=69
     ****
     */
    {
        var id;
        
        if (this.node.nodeName in data_disabled || (!(id = this.getAttribute('_ltk_node_id'))) || !(id in data_store) || !(name in data_store[id])) return;

        delete data_store[id][name];
    }

    /****m* node/setFloat
     * SYNOPSIS
     */
    ltk.dom.node.prototype.setFloat = function(value)
    /*
     * FUNCTION
     *      cross browser implementation to set position float on node
     ****
     */
    {
        this.node.style.cssFloat   = value;
        this.node.style.styleFloat = value;
    }
    
    /****m* node/setTextShadow
     * SYNOPSIS
     */
    ltk.dom.node.prototype.setTextShadow = function()
    /*
     * FUNCTION
     *      crossbrowser implementation for textShadow
     * REFERENCE
     *      http://www.workingwith.me.uk/articles/css/cross-browser-drop-shadows
     ****
     */
    {
    }

    /****m* node/setAttribute
     * SYNOPSIS
     */
    ltk.dom.node.prototype.setAttribute = function(name, value)
    /*
     * FUNCTION
     *      set attribute for DOM node
     * INPUTS
     *      * name (string) -- name of value to set
     *      * value (mixed) -- attribute value to set
     * REFERENCE
     *      http://mykenta.blogspot.com/2006/07/standardise-ie-setattribute-part-2.html
     ****
     */
    {
        try {
            this.node.setAttribute(name, value);
        } catch(e) {
            if (ltk.browser.msie && this.node.tagName.toUpperCase() == 'INPUT') {
                switch (name.toLowerCase()) {
                case 'name':
                    this.node.outerHTML = this.node.outerHTML.replace(/name=[a-zA-Z]+/, ' ').replace('>', ' name=' + value + '>');
                    break;
                case 'type':
                    this.node.outerHTML = this.node.outerHTML.replace(/type=[a-zA-Z]+/, ' ').replace('>', ' type=' + value + '>');
                    break;
                default:
                    break;
                }
            }
        }
    }
    
    /****m* node/getAttribute
     * SYNOPSIS
     */
    ltk.dom.node.prototype.getAttribute = function(name)
    /*
     * FUNCTION
     *      return value of attribute
     * INPUTS
     *      * name (string) -- name of attribute to return value of
     * OUTPUTS
     *      (mixed) -- value of attribute
     ****
     */
    {
        try {
            return this.node.getAttribute(name);
        } catch(e) {
            return null;
        }
    }
    
    /****m* node/hasAttribute
     * SYNOPSIS
     */
    ltk.dom.node.prototype.hasAttribute = function(name)
    /*
     * FUNCTION
     *      test, if node has specified attribute
     * INPUTS
     *      * name (string) -- name of attribute to test
     * OUTPUTS
     *      (bool) -- returns true, if attribute is available, otherwise false
     ****
     */
    {
        return ('hasAttribute' in this.node ? this.node.hasAttribute(name) : this.getAttribute(name) !== null);
    }
    
    /****m* node/removeAttribute
     * SYNOPSIS
     */
    ltk.dom.node.prototype.removeAttribute = function(name)
    /*
     * FUNCTION
     *      remove attribute
     * INPUTS
     *      * name (string) -- name of attribute to remove
     ****
     */
    {
        this.node.removeAttribute(name);
    }
    
    /****m* node/childNodes
     * SYNOPSIS
     */
    ltk.dom.node.prototype.childNodes = function()
    /*
     * FUNCTION
     *      return childnodes of current node
     * OUTPUTS
     *      (ltk.dom.nodelist) -- nodelist of childnodes
     ****
     */
    {
        return new ltk.dom.nodelist(('children' in this.node && this.node.children.length > 0 ? this.node.children : []));
    }
    
    /****m* node/parentNode
     * SYNOPSIS
     */
    ltk.dom.node.prototype.parentNode = function()
    /*
     * FUNCTION
     *      return parent of current node
     * OUTPUTS
     *      (ltk.dom.node) -- parent node
     ****
     */
    {
        return new ltk.dom.node(this.node.parentNode);
    }
    
    /****m* node/closestNode
     * SYNOPSIS
     */
    ltk.dom.node.prototype.closestNode = function(tag)
    /*
     * FUNCTION
     *      get closest parent node with specified tag
     * INPUTS
     *      * tag (string) -- closest node to get
     * OUTPUTS
     *      (ltk.dom.node) -- closes node
     ****
     */
    {
        var parent; 
        tag = tag.toLowerCase();
        
        do {
            parent = this.node.parentNode;
        } while (parent.tagName.toLowerCase() != tag && parent);
        
        return (parent ? new ltk.dom.node(parent) : null);
    }
    
    /****m* node/firstChild
     * SYNOPSIS
     */
    ltk.dom.node.prototype.firstChild = function()
    /*
     * FUNCTION
     *      return firstchild of current node
     * OUTPUTS
     *      (ltk.dom.node) -- first child node
     ****
     */
    {
        return new ltk.dom.node(this.node.firstChild);
    }
    
    /****m* node/swap
     * SYNOPSIS
     */
    ltk.dom.node.prototype.swap = function(node)
    /*
     * FUNCTION
     *      swap positions of two node 
     * INPUTS
     *      * node (ltk.dom.node) -- node to swap position with
     ****
     */
    {
        var tmp = this.node.parentNode.insertBefore(document.createTextNode(''), this.node);
        
        node.node.parentNode.insertBefore(this.node, node.node);
        tmp.parentNode.insertBefore(node.node, tmp);
        tmp.parentNode.removeChild(tmp);
    }
    
    /****m* node/moveUp
     * SYNOPSIS
     */
    ltk.dom.node.prototype.moveUp = function()
    /*
     * FUNCTION
     *      move node up in DOM tree on same level
     ****
     */
    {
        if (this.node.previousSibling) {
            this.node.parentNode.insertBefore(this.node, this.node.previousSibling);
        }
    }
    
    /****m* node/moveDown
     * SYNOPSIS
     */
    ltk.dom.node.prototype.moveDown = function()
    /*
     * FUNCTION
     *      move node down in DOM tree on same level
     ****
     */
    {
        var tmp = this.node.nextSibling;
        
        if (tmp && tmp.nextSibling) {
            this.node.parentNode.insertBefore(this.node, tmp.nextSibling);
        }
    }
    
    /****m* node/insertBefore
     * SYNOPSIS
     */
    ltk.dom.node.prototype.insertBefore = function(node)
    /*
     * FUNCTION
     *      insert new node before current node
     * INPUTS
     *      * node (ltk.dom.node) -- dom node to insert
     * OUTPUTS
     *      (ltk.dom.node) -- inserted dom node
     ****
     */
    {
        this.node.parentNode.insertBefore(node.node, this.node);

        return node;
    }
    
    /****m* node/insertAfter
     * SYNOPSIS
     */
    ltk.dom.node.prototype.insertAfter = function(node)
    /*
     * FUNCTION
     *      insert new node after current node
     * INPUTS
     *      * node (ltk.dom.node) -- DOM node to insert
     * OUTPUTS
     *      (ltk.dom.node) -- inserted DOM node
     ****
     */
    {
        this.node.parentNode.insertBefore(node.node, this.node.nextSibling);
        
        return node;
    }
    
    /****m* node/nextSibling
     * SYNOPSIS
     */
    ltk.dom.node.prototype.nextSibling = function()
    /*
     * FUNCTION
     *      returns next sibling of current node
     * OUTPUTS
     *      (ltk.dom.node) -- next sibling
     ****
     */
    {
        return new ltk.dom.node(this.node.nextSibling);
    }
    
    /****m* node/previousSibling
     * SYNOPSIS
     */
    ltk.dom.node.prototype.previousSibling = function()
    /*
     * FUNCTION
     *      returns previous sibling of node
     * OUTPUTS
     *      (ltk.dom.node) -- previous sibling
     ****
     */
    {
        return new ltk.dom.node(this.node.previousSibling);
    }
    
    /****m* node/lastSibling
     * SYNOPSIS
     */
    ltk.dom.node.prototype.lastSibling = function()
    /*
     * FUNCTION
     *      returns last sibling
     * OUTPUTS
     *      (ltk.dom.node) -- last sibling
     * REFERENCE
     *      http://stackoverflow.com/questions/925307/how-to-get-lastsibling-by-javascript
     ****
     */
    {
        var tmp = this.node.parentNode.lastChild;
        
        while (tmp.nodeType != 1 && tmp.previousSibling !== null) {  
            tmp = tmp.previousSibling;
        }
        
        return (tmp.nodeType == 1 ? new ltk.dom.node(tmp) : false);
    }
    
    /****m* node/removeNode
     * SYNOPSIS
     */
    ltk.dom.node.prototype.removeNode = function() 
    /*
     * FUNCTION
     *      remove node from DOM including all child nodes
     ****
     */
    {
        cleanup(this.node);
        
        this.node.parentNode.removeChild(this.node);
    }
    
    /****m* node/replaceNode
     * SYNOPSIS
     */
    ltk.dom.node.prototype.replaceNode = function(node)
    /*
     * FUNCTION
     *      replace node with an other node
     * INPUTS
     *      * node (ltk.dom.node) -- node to replace current node with
     * OUTPUTS
     *      (ltk.dom.node) -- new node
     ****
     */
    {
        this.node.parentNode.replaceChild(node.node, this.node);
        
        return node;
    }
    
    /****m* node/removeChildren
     * SYNOPSIS
     */
    ltk.dom.node.prototype.removeChildren = function() 
    /*
     * FUNCTION
     *      remove all child nodes from node
     ****
     */
    {
        while (this.node.firstChild) {
            cleanup(this.node.firstChild);

            this.node.removeChild(this.node.firstChild);
        }
    }
    
    /****m* node/appendChild
     * SYNOPSIS
     */
    ltk.dom.node.prototype.appendChild = function(node)
    /*
     * FUNCTION
     *      append child node to current node
     * INPUTS
     *      * node (ltk.dom.node) -- node to append
     * OUTPUTS
     *      (ltk.dom.node) -- appended node
     ****
     */
    {
        this.node.appendChild(node.node);
        
        return node;
    }
})();

/****c* dom/nodelist
 * NAME
 *      ltk.dom.nodelist
 * FUNCTION
 *      LTK-DOM nodelist object
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <h.lapp@clipdealer.de>
 ****
 */

;(function() {
    if ('nodelist' in ltk.dom) return;

    /****m* nodelist/construct
     * SYNOPSIS
     */
    ltk.dom.nodelist = function(nodes)
    /*
     * FUNCTION
     *      constructor
     * INPUTS
     *      * nodes (array) -- array of nodes
     ****
     */
    {
        this.nodes = [];

        if (typeof nodes != 'undefined' && 'length' in nodes) {
            if (nodes instanceof Array) {
                this.nodes = nodes;
            } else {
                this.nodes = ltk.array.toArray(nodes);
            }
        }
        
        this.length = this.nodes.length;
    }

    /****m* nodelist/item
     * SYNOPSIS
     */
    ltk.dom.nodelist.prototype.item = function(item)
    /*
     * FUNCTION
     *      returns item with specified number
     * INPUTS
     *      * item (int) -- item to return
     * OUTPUTS
     *      (ltk.dom.node) -- instance of node object
     ****
     */
    {
        return (this.nodes.length > item
                ? new ltk.dom.node(this.nodes[item])
                : false);
    }

    /****m* nodelist/first
     * SYNOPSIS
     */
    ltk.dom.nodelist.prototype.first = function()
    /*
     * FUNCTION
     *      return first DOM node in list
     * OUTPUTS
     *      (ltk.dom.node) -- instance of node object
     ****
     */
    {
        return (this.nodes.length > 0
                ? new ltk.dom.node(this.nodes[0])
                : false);
    }

    /****m* nodelist/last
     * SYNOPSIS
     */
    ltk.dom.nodelist.prototype.last = function()
    /*
     * FUNCTION
     *      return last DOM node in list
     * OUTPUTS
     *      (ltk.dom.node) -- instance of node object
     ****
     */
    {
        return (this.nodes.length > 0
                ? new ltk.dom.node(this.nodes[this.nodes.length - 1])
                : false);
    }

    /****m* nodelist/concat
     * SYNOPSIS
     */
    ltk.dom.nodelist.prototype.concat = function(nodes)
    /*
     * FUNCTION
     *      concat list of nodes
     * INPUTS
     *      * nodes (array) -- array of nodes
     * OUTPUTS
     *      (ltk.dom.nodelist) -- instance of current nodelist
     ****
     */
    {
        if ('length' in nodes && !(nodes instanceof Array)) {
            // not an array, but a collection
            nodes = ltk.array.toArray(nodes);
        }
    
        this.nodes  = this.nodes.concat(nodes);
        this.length = this.nodes.length;

        return this;
    }

    /****m* nodelist/push
     * SYNOPSIS
     */
    ltk.dom.nodelist.prototype.push = function(node)
    /*
     * FUNCTION
     *      push node onto list
     * INPUTS
     *      * node (ltk.dom.node) -- node to push to nodelist
     ****
     */
    {
        this.nodes.push((node instanceof ltk.dom.node ? node.node : node));
        this.length = this.nodes.length;
    }

    /****m* nodelist/forEach
     * SYNOPSIS
     */
    ltk.dom.nodelist.prototype.forEach = function(cb)
    /*
     * FUNCTION
     *      execute callback for each DOM node in list. if the callback returns any value other than
     *      ~undefined~, exit loop and return value.
     * INPUTS
     *      * cb (callback) -- callback to execute
     * OUTPUTS
     *      (mixed) -- ~undefined~ or return value of callback
     ****
     */
    {
        var r = undefined;
    
        for (var i = 0, len = this.nodes.length; i < len; ++i) {
            if ((r = cb(new ltk.dom.node(this.nodes[i]), i)) !== undefined) break;
        }
        
        return r;
    }
})();

/****c* dom/layer
 * NAME
 *      ltk.dom.layer
 * FUNCTION
 *      handles layer objects with z-index -- moving objects in foreground/background
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('layer' in ltk.dom) return;
    
    var stack = {};
    var min   = 50000;

    /*
     * rearrange layers by manipulating z-index
     */
    function rearrange() {
        var z = min;

        for (var s in stack) {
            for (var i = 0, len = stack[s].nodes.length; i < len; ++i) {
                stack[s].nodes[i].setStyle('zIndex', z++);
            }
        }
    }

    /****m* layer/
     * SYNOPSIS
     */
    ltk.dom.layer = function()
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.id = ltk.getUniqID('ltk_layer_');
    }

    /****m* layer/push
     * SYNOPSIS
     */
    ltk.dom.layer.prototype.push = function(nodes)
    /*
     * FUNCTION
     *      register a set of layers
     * INPUTS
     *      * nodes (array) -- ltk.dom.nodes to register
     ****
     */
    {
        var me = this;

        stack[this.id] = {'id': this.id, 'nodes': nodes};

        rearrange();

        this.up = function() {
            // overwrite this.up to get nodes into moved stack position
            delete stack[me.id]; 
            stack[me.id] = {'id': me.id, 'nodes': nodes};; 
            
            rearrange(); 
        }
    }
    
    /****m* layer/up
     * SYNOPSIS
     */
    ltk.dom.layer.prototype.up = function()
    /*
     * FUNCTION
     *      push layers up in stack
     ****
     */
    {
    }
    
    /****m* layer/pop
     * SYNOPSIS
     */
    ltk.dom.layer.prototype.pop = function()
    /*
     * FUNCTION
     *      remove layers from stack
     ****
     */
    {
        delete stack[this.id]; 
        
        rearrange(); 
        
        for (var i in this) delete this[i]; 
    }
})();

/****c* ltk/evt
 * NAME
 *      ltk.evt
 * FUNCTION
 *      LTK event library 
 *      - basic signal/slot implementation
 *      - cross browser general, keyboard and mouse event handling
 * COPYRIGHT
 *      copyright (c) 2007-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('evt' in ltk) return;
    
    /*
     * remove handlers for attached events
     */
    var evt_cnt    = 0;
    var evt_remove = {};
    
    /*
     * keycodes
     */
    var keycodes = {
        'ESC':      27, 'TAB':      9, 'SPACE':     32, 'ENTER':    13, 'BACKSPACE':     8,
        'SCROLL':  145, 'CAPS':    20, 'NUM':      144, 'PAUSE':    19, 'INSERT':       45, 
        'HOME':     36, 'DELETE':  46, 'END':       35, 'PAGEUP':   33, 'PAGEDOWN':     34,
        'LEFT':     37, 'UP':      38, 'RIGHT':     39, 'DOWN':     40,

        'COMMA':   188, 'PERIOD': 190,

        'F1':      112, 'F2':     113, 'F3':       114, 'F4':      115, 'F5':          116, 
        'F6':      117, 'F7':     118, 'F8':       119, 'F9':      120, 'F10':         121, 
        'F11':     122, 'F12':    123
    };

    /*
     * mouse coordinates determined by observation
     */
    var mouseX = 0;
    var mouseY = 0;

    /*
     * mouse movement direction indicator
     */
    var mouseDirX = 0;
    var mouseDirY = 0;

    ltk.evt = {
        /****m* evt/addEvent
         * SYNOPSIS
         */
        addEvent: function(target, type, cb, opt) 
        /*
         * FUNCTION
         *      cross browser implementation of adding an event
         * INPUTS
         *      * target (ltk.dom.node) -- target to attach event to
         *      * type (string) -- type of event to attach
         *      * cb (callback) -- callback to execute, if event is triggered
         *      * opt (object) -- (optional) event options
         * OUTPUTS
         *      (string) -- event #ID
         ****
         */
        {
            if (!target.node) return;
            
            opt = ltk.extend({'propagate': false, 'default': false}, opt);

            // handle fake mouse events: on right click / on left click -- we assume left click for normal click handler
            if (type == 'rgtclick' || type == 'lftclick' || type == 'midclick' || type == 'click' || type == 'mousedown') {
                var vec2  = cb;

                cb = ltk.closure(type, function(type, e) {
                    e = e || window.event;
                    
                    var button = 'lftclick';
                    
                    if (!e.which) {
                        // IE
                        button = (e.button < 2 ? 'lftclick' : (e.button == 4 ? 'midclick' : 'rgtclick'));
                    } else {
                        button = (e.which < 2 ? 'lftclick' : (e.which == 2 ? 'midclick' : 'rgtclick'));
                    }
                    
                    if (button == type || (button == 'lftclick' && (type == 'click' || type == 'mousedown'))) {
                        vec2(e);
                    }
                });

                if (type == 'rgtclick') {
                    type = 'mousedown';
                } else if (type == 'lftclick' || type == 'midclick') {
                    type = 'click';
                }
            }

            // handle propagation
            var me  = this;
            var vec = cb;

            cb = function(e) {
                var ret;
                
                vec(e);

                if (!opt.propagate) { me.stopPropagation(e); ret = false; }
                if (!opt['default']) { me.preventDefault(e); ret = false; }
                
                return ret;
            }

            // generate event ID, attach event handler and generate remove handler
            var id = 'ltk_evt_' + (++evt_cnt);

            if (target.node.addEventListener) {
                type = (type == 'mousewheel' && window.gecko ? 'DOMMouseScroll' : type);

                target.node.addEventListener(type, cb, false);
                
                evt_remove[id] = function() {
                    target.node.removeEventListener(type, cb, false);
                    delete(evt_remove[id]);
                }
            } else if (target.node.attachEvent) {
                target.node.attachEvent('on' + type, cb);
                
                evt_remove[id] = function() {
                    target.node.detachEvent('on' + type, cb);
                    delete(evt_remove[id]);
                }
            } else {
                target.node['on' + type] = cb;
            }

            return id;
        },

        /****m* evt/removeEvent
         * SYNOPSIS
         */
        removeEvent: function(id)
        /*
         * FUNCTION
         *      remove event listener with specified ID
         * INPUTS
         *      * id (string) -- ID of event listener to remove
         ****
         */
        {
            if (id in evt_remove) {
                evt_remove[id]();
            }
        },

        /****m* evt/addKeyboardEvent
         * SYNOPSIS
         */
        addKeyboardEvent: function(target, shortcut, cb, opt) 
        /*
         * FUNCTION
         *      add keyboard event handler to a target object
         * INPUTS
         *      * target (ltk.dom.node) -- target to attach event to
         *      * shortcut (string) -- keyboard shortcut to listen to
         *      * cb (callback) -- callback to execute, if event is triggered
         *      * opt (object) -- (optional) event options
         * OUTPUTS
         *      (string) -- event #ID
         ****
         */
        {
            opt = ltk.extend({'propagate': false, 'default': false, 'type': 'keydown'}, opt);

            var keys = shortcut.split('+');
            var me   = this;

            return this.addEvent(target, opt.type, function(e) {
                e = e || window.event;

                if (e.keyCode) {
                    code = e.keyCode;
                } else if (e.which) {
                    code = e.which;
                } else {
                    return;
                }

                var kchar = String.fromCharCode(code);
                var pressed = 0;

                for (var i = 0, len = keys.length; i < len; ++i) {
                    switch (keys[i]) {
                    case 'CTRL':
                        if (e.ctrlKey) {
                            ++pressed;
                        }
                        break;
                    case 'SHIFT':
                        if (e.shiftKey) {
                            ++pressed;
                        }
                        break;
                    case 'ALT':
                        if (e.altKey) {
                            ++pressed;
                        }
                        break;
                    default:
                        if (keys[i].length > 1) {
                            if (keycodes[keys[i]] == code) {
                                ++pressed;
                            }
                        } else if (keys[i] == kchar) {
                            ++pressed;
                        } else {
    //                        console.log(kchar + ' ' + code);
                        }
                        break;
                    }
                }

                if (pressed == keys.length) {
                    var ret = undefined;
                    
                    if (!opt.propagate) { me.stopPropagation(e); ret = false; }
                    if (!opt['default']) { me.preventDefault(e); ret = false; }

                    var result = cb();

                    if (typeof result != 'undefined' && !result) {
                        if (opt.propagate) { me.stopPropagation(e); ret = false; }
                        if (opt['default']) { me.preventDefault(e); ret = false; }
                    }
                    
                    return ret;
                }
            }, {'propagate': opt.propagate, 'default': opt['default']});
        },

        /****m* event/stopPropagation
         * SYNOPSIS
         */
        stopPropagation: function(e) 
        /*
         * FUNCTION
         *      stops event propagation
         * INPUTS
         *      * e (object) -- event object provided by target
         ****
         */
        {
            if (typeof e == 'undefined') {
                return;
            }
            
            if ('stopPropagation' in e) {
                e.stopPropagation();
            } else if ('cancelBubble' in e) {
                e.cancelBubble = true;
            }
        },

        /****m* evt/preventDefault
         * SYNOPSIS
         */
        preventDefault: function(e) 
        /*
         * FUNCTION
         *      prevents default event handler
         * INPUTS
         *      * e (object) -- event object provided by target
         ****
         */
        {
            if (typeof e == 'undefined') {
                return;
            }
            
            if ('preventDefault' in e) {
                e.preventDefault();
            } else if ('returnValue' in e) {
                e.returnValue = false;
            }
        },

        /****m* evt/disableEnterKey
         * SYNOPSIS
         */
        disableEnterKey: function(e, targets) 
        /*
         * FUNCTION
         *      disable enter key, ref:
         *      http://www.arraystudio.com/as-workshop/disable-form-submit-on-enter-keypress.html
         * INPUTS
         *      * e (object) -- event object
         *      * targets (array) -- (optional) array for targets, the rule should apply to
         ****
         */
        {
            var key;
            var ret = true;

            if (window.event) {
                // IE
                key = window.event.keyCode;
            } else {
                // other
                key = e.which;     
            }

            if (key == 13) {
                var id = e.target.id;

                if (typeof id != 'undefined' && typeof targets != 'undefined') {
                    for (var i = 0, len = targets.length; i < len; ++i) {
                        if (id == targets[i]) {
                            ret = false;
                            break;
                        }
                    }
                } else {
                    ret = false;
                }
            }

            return ret;
        },

        /****m* evt/disableTextSelect
         * SYNOPSIS
         */
        disableTextSelect: function(target)
        /*
         * FUNCTION
         *      disable text-selection for specified target
         * INPUTS
         *      * target (ltk.dom.node) -- node to disable text-selection for
         ****
         */
        {
            target.node.onselectstart = function() { return false; }
            target.node.onmousedown   = function() { return false; }
        },

        /****m* evt/fireKeyboardEvent
         * SYNOPSIS
         */
        fireKeyboardEvent: function(target, shortcut)
        /*
         * FUNCTION
         *      cross browser implementation for simulating keyboard 
         * INPUTS
         *      * target (object) -- target to fire event on
         *      * shortcut (string) -- see addKeyboardEvent for details
         ****
         */
        {
            var evt_name = 'keypress';
            var evt      = document.createElement('KeyboardEvent');
            var keys     = shortcut.split('+')

            var modifiers = [];
            var options   = {
                'ctrl': false, 'shift': false, 'alt': false, 'meta': false, 'key': 0, 'char': 0
            }

            for (var i = 0, cnt = keys.length; i < cnt; ++i) {
                switch (keys[i]) {
                case 'CTRL':
                    options.ctrl = true;
                    modifiers.push('Control');
                    break;
                case 'SHIFT':
                    options.shift = true;
                    modifiers.push('Shift');
                    break;
                case 'ALT':
                    options.alt = true;
                    modifiers.push('Alt');
                    break;
                default:
                    if (keys[i].length > 1) {
                        if (keys[i] in keycodes) {
                            options.key = keycodes[keys[i]];
                        }
                    } else {
                        options.char = keys[i].charCodeAt(0);
                    }
                }
            }

            try {
                evt.initKeyEvent(
                    evt_name, true, true, window,             
                    options.ctrl, options.alt, options.shift, options.meta,
                    options.key, options.char
                );
            } catch(e) {
                var loc   = '0x00';
                var ident = 'U+00' + options.key.toString(16);

                evt.initKeyboardEvent(
                    evt_name, true, true, window, ident, loc, modifiers.join(' ')
                );
            }

            target.dispatchEvent(evt);
        },

        /****m* evt/fireEvent
         * SYNOPSIS
         */
        fireEvent: function(target, type)
        /*
         * FUNCTION
         *      cross browser implementation of simulating events
         * INPUTS
         *      * target (object) -- target to fire event on
         *      * type (string) -- type of event to fire
         ****
         */
        {
            var evt;

            if (document.createEventObject) {
                // IE
                evt = document.createEventObject();                    
                target.fireEvent('on' + type, evt);
            } else {
                // other browsers
                evt = document.createEvent('HTMLEvents');
                evt.initEvent(type, true, true);
                target.dispatchEvent(evt);
            }
        },

        /****m* evt/captureMove
         * SYNOPSIS
         */
        captureMove: function(evt) 
        /*
         * FUNCTION
         *      captures mouse movement -- internally stores mouse position and mouse movement direction
         * INPUTS
         *      * evt (object) -- object provided by mouse event
         ****
         */
        {
            var x = 0;
            var y = 0;

            if (!evt) {
                var evt = window.event;
            }

            if (evt.pageX || evt.pageY)     {
                x = evt.pageX;
                y = evt.pageY;
            } else if (evt.clientX || evt.clientY) {
                x = evt.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
                y = evt.clientY + document.body.scrollTop + document.documentElement.scrollTop;
            }

            mouseDirX = (x > mouseX ? +1 : x < mouseX ? -1 : 0);
            mouseDirY = (y > mouseY ? +1 : y < mouseY ? -1 : 0);

            mouseX = x;
            mouseY = y;

            this.onMouseMove(evt);
        },

        /****m* event/onMouseMove
         * SYNOPSIS
         */
        onMouseMove: function(evt)
        /*
         * FUNCTION
         *      dummy method triggered, whenever mouse is moved
         * INPUTS
         *      * evt (object) -- object provided by mouse event
         ****
         */
        {
        },

        /****m* evt/getMousePos
         * SYNOPSIS
         */
        getMousePos: function() 
        /*
         * FUNCTION
         *      returns mouse coordinates
         * OUTPUTS
         *      (array) -- x,y position of mouse
         ****
         */
        {
            return {'x': mouseX, 'y': mouseY};
        },

        /****m* evt/getMouseMoveDir
         * SYNOPSIS
         */
        getMouseMoveDir: function() 
        /*
         * FUNCTION
         *      returns mouse movement direction
         * OUTPUTS
         *      (array) -- x,y direction of mouse movement
         ****
         */
        {
            return {'x': mouseDirX, 'y': mouseDirY};
        }
    }
    
    /*
     * install mouse observation
     */
    ltk.dom.ready(function() {
        ltk.evt.addEvent(new ltk.dom.node(document), 'mousemove', function(evt) { 
            ltk.evt.captureMove(evt); 
        }, {'propagation': true, 'default': true});
    });
})();

/****c* evt/draggable
 * NAME
 *      ltk.evt.draggable
 * FUNCTION
 *      makes UI elements draggable
 * COPYRIGHT
 *      copyright (c) 2009-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('draggable' in ltk.evt) return;
    
    var vec  = {
        'mmove': null,      // original mousemove event
        'mmup':  null       // original mouseup event
    };
    var drag = false;       // drag mode

    /****m* draggable/
     * SYNOPSIS
     */
    ltk.evt.draggable = function(obj, raster, handle, direction)
    /*
     * FUNCTION
     *      constructor
     * INPUTS
     *      * obj (ltk.dom.node) -- widget to mage draggable
     *      * raster (int) -- raster to use
     *      * handle (ltk.dom.node) -- (optional) handle to use for dragging
     *      * direction (int) -- (optional) allowed dragging direction (default: X | Y)
     ****
     */
    {
        // setup new drag handle
        this.widget    = obj;
        this.handle    = handle || obj;
        this.raster    = raster || 1;
        this.range     = {'x1': null, 'y1': null, 'x2': null, 'y2': null};
        this.direction = direction || (ltk.evt.draggable.T_DIR_Y | ltk.evt.draggable.T_DIR_X);

        var me       = this;
        var startXY  = {'x': 0, 'y': 0};
        var offsetXY = {'x': 0, 'y': 0};
    
        // optionally disable either direction to drag to
        if ((this.direction & ltk.evt.draggable.T_DIR_Y) != ltk.evt.draggable.T_DIR_Y) {
            this.range['y1'] = false;
            this.range['y2'] = false;
        }
        if ((this.direction & ltk.evt.draggable.T_DIR_X) != ltk.evt.draggable.T_DIR_X) {
            this.range['x1'] = false;
            this.range['x2'] = false;
        }

        // attach events
        ltk.evt.addEvent(this.handle, 'mousedown', function(e) {
            me.onmousedown(e);
        }, {'propagate': false, 'default': false});

        /****m* draggable/setRange
         * SYNOPSIS
         */
        this.setRange = function(x1, y1, x2, y2)
        /*
         * FUNCTION
         *      set draggable range
         * INPUTS
         *      * x1 (int) -- x1
         *      * y1 (int) -- y1
         *      * x2 (int) -- x2
         *      * y2 (int) -- y2
         * OUTPUTS
         *      
         ****
         */
        {
            this.range = {
                'x1': (x1 === null ? me.range['x1'] : x1), 
                'y1': (y1 === null ? me.range['y1'] : y1), 
                'x2': (x2 === null ? me.range['x2'] : x2), 
                'y2': (y2 === null ? me.range['y2'] : y2)
            };
            
            this.range.rx = this.range.x2 - this.range.x1;
            this.range.ry = this.range.y2 - this.range.y1;
        };

        /****m* draggable/onmousedown
         * SYNOPSIS
         */
        this.onmousedown = function(e)
        /*
         * FUNCTION
         *      start dragging on mouse down
         ****
         */
        {
            if (drag) return;
    
            var me = this;
    
            vec.mmove = ltk.evt.onMouseMove;
            vec.mmup  = document.mouseup;
            
            document.onmouseup = function(e) {
                document.onmouseup = vec;
                me.onmouseup(e);
            };

            ltk.evt.onMouseMove = function(e) {
                if (typeof vec.mmove == 'function') {
                    vec.mmove();
                }
        
                if (drag) {
                    var pos = ltk.evt.getMousePos();
            
                    var y   = (offsetXY.y + pos.y - startXY.y);
                    var x   = (offsetXY.x + pos.x - startXY.x);

                    y = Math.floor(y / me.raster) * me.raster;
                    x = Math.floor(x / me.raster) * me.raster;
            
                    if (me.range.y1 !== false && me.range.y2 !== false) {
                        if (me.range.y1 === null || me.range.y2 === null) {
                            me.widget.node.style.top = y + 'px';
                        } else if (y >= me.range.y1 && y <= me.range.y2) {
                            me.widget.node.style.top = y + 'px';
                        } else if (y < me.range.y1) {
                            me.widget.node.style.top = me.range.y1 + 'px';
                        } else if (y > me.range.y2) {
                            me.widget.node.style.top = me.range.y2 + 'px';
                        }
                    }
                    if (me.range.x1 !== false && me.range.x2 !== false) {
                        // console.log(offsetXY.x, pos.x, startXY.x, x, me.range.rx);

                        if (me.range.x1 === null || me.range.x2 === null) {
                            me.widget.node.style.left = x + 'px';
                        } else if (x >= 0 && x <= me.range.rx) {
                            me.widget.node.style.left = x + 'px';
                        } else if (x < 0) {
                            me.widget.node.style.left = '0px';
                        } else if (x > me.range.rx) {
                            me.widget.node.style.left = me.range.rx + 'px';
                        }
                    }
                }
        
                me.onMouseMove(e);
            };
    
            startXY  = ltk.evt.getMousePos();
            offsetXY = {
                'x': this.widget.node.offsetLeft,
                'y': this.widget.node.offsetTop
            };

            drag = true;
            
            this.onMouseDown();
        };

        /****m* draggable/onmouseup
         * SYNOPSIS
         */
        this.onmouseup = function()
        /*
         * FUNCTION
         *      stop dragging on mouse up
         ****
         */
        {
            if (!drag) return;
    
            ltk.evt.onMouseMove = vec.mmove;
    
            drag = false;
            
            this.onMouseUp();
        }

        /****m* draggable/onMouseDown
         * SYNOPSIS
         */
        this.onMouseDown = function(e)
        /*
         * FUNCTION
         *      executed when mouse button is pressed
         ****
         */
        {
        }

        /****m* draggable/onMouseUp
         * SYNOPSIS
         */
        this.onMouseUp = function(e)
        /*
         * FUNCTION
         *      executed when mouse button is released
         ****
         */
        {
        }

        /****m* draggable/onMouseMove
         * SYNOPSIS
         */
        this.onMouseMove = function(e)
        /*
         * FUNCTION
         *      executed during dragging
         ****
         */
        {
        }
    }

    /****d* draggable/T_DIR_X, T_DIR_Y
     * SYNOPSIS
     */
    ltk.evt.draggable.T_DIR_Y = 1;
    ltk.evt.draggable.T_DIR_X = 2;
    /*
     * FUNCTION
     *      direction it's possible to drag object to
     ****
     */
})();


/****c* core/l10n
 * NAME
 *      ltk.l10n
 * FUNCTION
 *      internationalization / localisation
 * COPYRIGHT
 *      copyright (c) 2006-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('l10n' in ltk) return;

    /*
     * inline compiler
     */
    var compiled = {};      // compiled messages
    var inline   = {        // allowed inline functions
        'gender': true, 'numf': true, 'datef': true, 'monf': true,
        'comify': true, 'enum': true, 'quant': true, 'yesno': true
    };

    function compile(msg) {
        var code;
        
        code = msg.replace(/\[(?:_(\d+)|([a-zA-Z0-9]+)[ ]*((?:,[ ]*_\d+)+))?\]/g, function(str, arg_no, func, arg_nos) {
            var ret = '';
            
            if (arg_no !== '') {
                arg_no = parseInt(arg_no, 10) - 1;
                
                ret = "' + arguments[" + arg_no + "] + '";
            } else if (func in inline && inline[func]) {
                arg_nos = arg_nos.replace(/[ _]/g, '').replace(/^,/, '').split(/,/);
                
                var args = [];
                
                for (var i = 0, len = arg_nos.length; i < len; ++i) {
                    args.push('arguments[' + (parseInt(arg_nos[i], 10) - 1) + ']')
                }
                
                ret = "' + (function() {" + 
                    "return ltk.l10n." + func + "(" + args.join(', ') + ");" + 
                    "}).apply(null, arguments) + '";
            }
            
            return ret;
        });
        
        code = "compiled[msg] = function() { return '" + code + "'; }";
        eval(code);
        
        return msg;
    }

    /*
     * private stuff
     */
    var dicts = {};         // known dictionaries
    var lc    = '';         // currently set locale
    
    ltk.l10n = {
        /****m* l10n/addDict
         * SYNOPSIS
         */
        addDict: function(lc, dict)
        /*
         * FUNCTION
         *      add dictionary for translationg messages
         * INPUTS
         *      * lc (string) -- lc of dictionary to add
         *      * dict (json) -- dictionary to add
         ****
         */
        {
            if (!(lc in dicts)) {
                dicts[lc] = dict;
            } else {
                for (var i in dict) {
                    dicts[lc][i] = dict[i];
                }
            }
        },

        /****m* l10n/setLocale
         * SYNOPSIS
         */
        setLocale: function(new_lc)
        /*
         * FUNCTION
         *      change active localization and return old setting
         * INPUTS
         *      * new_lc (string) -- new locale to activate
         * OUTPUTS
         *      (string) -- old locale
         ****
         */
        {
            var ret = lc;
            lc = new_lc;

            return ret;
        },

        /****m* l10n/getLocale
         * SYNOPSIS
         */
        getLocale: function()
        /*
         * FUNCTION
         *      return active localization
         * OUTPUTS
         *      (string) -- active locale
         ****
         */
        {
            return lc;
        },

        /****m* l10n/getCountryCode
         * SYNOPSIS
         */
        getCountryCode: function()
        /*
         * FUNCTION
         *      return country code of active locale
         * OUTPUTS
         *      (string) -- country code
         ****
         */
        {
            return lc.substr(4, 2);
        },

        /****m* l10n/getLanguageCode
         * SYNOPSIS
         */
        getLanguageCode: function()
        /*
         * FUNCTION
         *      return language code of active locale
         * OUTPUTS
         *      (string) -- country code
         ****
         */
        {
            return lc.substr(0, 2);
        },

        /****m* l10n/gettext
         * SYNOPSIS
         */
        gettext: function()
        /*
         * FUNCTION
         *      translate a message
         * INPUTS
         *      * arg-1 (string) -- message to localize
         *      * arg-2 .. n (mixed) -- (optional) parameters for inline functions
         * OUTPUTS
         *      (string) -- localized message
         ****
         */
        {
            var msg  = arguments[0];
            var args = [].splice.call(arguments, 1);
            
            msg = (lc in dicts && msg in dicts[lc] && dicts[lc][msg] != ''
                   ? dicts[lc][msg]
                   : msg);

            if (!(msg in compiled)) {
                compile(msg);
            }

            return compiled[msg].apply(null, args);
        },
        
        /****m* l10n/yesno
         * SYNOPSIS
         */
        yesno: function(val, first, second)
        /*
         * FUNCTION
         *      if val, display first, otherwise second
         ****
         */
        {
            second = (typeof second != 'undefined' ? second : '');
            
            return (val ? first : second);
        },

        /****m* l10n/quant
         * SYNOPSIS
         */
        quant: function(val, first, second, third) 
        /*
         * FUNCTION
         *      quantisation
         * INPUTS
         *      * val (float) -- value to compare
         *      * first (string) -- string to return if value == 1 (or second or third not set)
         *      * second (string) -- optional string to return if value != 1
         *      * third (string) -- optional string to return if value == 0
         * OUTPUTS
         *      (string) -- string formatted
         ****
         */
        {
            var ret = first;

            if (val == 0 && typeof third != 'undefined') {
                ret = third;
            } else if (val != 1 && typeof second != 'undefined') {
                ret = second;
            }

            return ltk.string.sprintf(ret, val);
        },

        /****m* l10n/gender
         * SYNOPSIS
         */
        gender: function(val, undef, male, female) 
        /*
         * FUNCTION
         *      returns text according to specified gender
         * INPUTS
         *      * val (mixed) -- gender [mM1fFwW2nN0]
         *      * undef (string) -- string to return if gender not specified (gender == n, N or 0)
         *      * male (string) -- string to return if gender is male (gender == m, M or 1)
         *      * female (string) -- string to return if gender is female (gender == f, F, w, W or 2)
         * OUTPUTS
         *      (string) -- string according to specified gender
         ****
         */
        {
            var ret = undef;
            
            switch (val.toUpperString()) {
            case 'M':
            case '1':
                ret = male;
                break;
            case 'F':
            case 'W':
            case '2':
                ret = female;
                break;
            }

            return ret;
        },
        
        /****m* l10n/comify
         * SYNOPSIS
         */
        comify: function(list, word, sep)
        /*
         * FUNCTION
         *      writes out a list of values seperated by ', ' and the last one
         *      by a string eg: 'and' or 'or'.
         * INPUTS
         *      * list (array) -- array elements to concatenate
         *      * word (string) -- word to concatenate last item with
         *      * sep (string) -- (optional) string to use to concatenate all list items but the last
         * NOTE
         *      inspired by: http://snippets.dzone.com/posts/show/4661
         ****
         */
        {
            sep = sep || ', ';
            
            var ret = '';

            if (list.length > 0) {
                last = list.pop();
                ret  = [list.join(sep), last].join(word);
            }

            return ret;
        },

        /****m* l10n/datef, monf, numf
         * SYNOPSIS
         */
        datef: function() { return ltk.l10n.cldr.datef.apply(null, arguments); },
        monf:  function() { return ltk.l10n.cldr.monf.apply(null, arguments); },
        numf:  function() { return ltk.l10n.cldr.numf.apply(null, arguments); }
        /*
         * FUNCTION
         *      aliases for various ltk.l10n.cldr methods
         ****
         */
    };

    window['gettext'] = ltk.l10n.gettext;
    window['_'] = ltk.l10n.gettext;
})();

/****c* l10n/cldr
 * NAME
 *      ltk.l10n.cldr
 * FUNCTION
 *      library for using CLDR repository in javascript
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('cldr' in ltk.l10n) return;
    
    /*
     * various private properties
     */
    var weekdays = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];

    var preprocess = {
        'datef': {
            'dd':   '%:1:', 'd+':   '%:2:', 'EEEE': '%:3:', 'EEE':  '%:4:',
            'MMM+': '%:5:', 'MM':   '%:6:', 'M':    '%:7:', 'yyyy': '%:8:',
            'yy':   '%:9:', 'HH':  '%:10:', 'mm':  '%:11:', 'ss':  '%:12:',
            'h':   '%:13:', 'a':   '%:14:'
        }
    }

    var rules = {
        /*
         * rules for formatting date/time
         */
        'datef': {
            '%:1:': function(d, lc) {
                return ltk.string.lpad(d.getDate(), 2, '0');
            },
            // '(?<!%)d(?=([^d]|))': function(d, lc) {
            '%:2:': function(d, lc) {
                return ltk.string.lpad(d.getDate(), 2, '0');
            },
            '%:3:': function(d, lc) {
                return ltk.l10n.cldr[lc].day.wide[weekdays[d.getDay()]];
            },
            '%:4:': function(d, lc) {
                return ltk.l10n.cldr[lc].day.abbreviated[weekdays[d.getDay()]];
            },
            '%:5:': function(d, lc) {
                return ltk.l10n.cldr[lc].month.abbreviated[d.getMonth() + 1];
            },
            '%:6:': function(d, lc) {
                return ltk.string.lpad(d.getMonth() + 1, 2, '0');
            },
            '%:7:': function(d, lc) {
                return ltk.string.lpad(d.getMonth() + 1, 2, '0');
            },
            '%:8:': function(d, lc) {
                return d.getFullYear();
            },
            '%:9:': function(d, lc) {
                return d.getFullYear().toString().substr(2);
            },
            '%:10:': function(d, lc) {
                return ltk.string.lpad(d.getHours(), 2, '0');
            },
            '%:11:': function(d, lc) {
                return ltk.string.lpad(d.getMinutes(), 2, '0');
            },
            '%:12:': function(d, lc) {
                return ltk.string.lpad(d.getSeconds(), 2, '0');
            },
            '%:13:': function(d, lc) {
                return ltk.string.lpad(d.getHours() % 12 + 1, 2, '0');
            },
            '%:14:': function(d, lc) {
                return (d.getHours() < 13 ? 'AM' : 'PM');
            }
        }
    };

    /*
     * compiler
     */
    var compiled = {};
    
    function compiler(type, pattern) {
        var key = type + ':' + pattern;
        
        if (type in preprocess) {
            // preprocess pattern
            for (var rexp in preprocess[type]) {
                pattern = pattern.replace(
                    new RegExp(rexp, 'g'),
                    preprocess[type][rexp]
                );
            }
        }
        
        for (var rexp in rules[type]) {
            pattern = pattern.replace(
                new RegExp(rexp, 'g'), 
                (function() { 
                    return "' + (" + rules[type][rexp].toString() +
                           ")(date, lc) + '"; 
                })
            );
        }
        
        var code = "compiled[key] = function(date, lc) { return '" + pattern + "'; }";
        eval(code);
        
        return pattern;
    }
    
    /*
     * helper for numf
     */
    function toFixed(n, prec) {
        var k = Math.pow(10, prec);
        return '' + Math.round(n * k) / k;
    }

    ltk.l10n.cldr = {
        /****d* cldr/T_DATE_FULL, T_DATE_LONG, T_DATE_MEDIUM, T_DATE_SHORT, T_TIME_FULL, T_TIME_LONG, T_TIME_MEDIUM, T_TIME_SHORT
         * SYNOPSIS
         */
        T_DATE_FULL:   1,
        T_DATE_LONG:   2,
        T_DATE_MEDIUM: 4,
        T_DATE_SHORT:  8,

        T_TIME_FULL:   16,
        T_TIME_LONG:   32,
        T_TIME_MEDIUM: 64,
        T_TIME_SHORT:  128,

        T_DATETIME_FULL:   17,
        T_DATETIME_LONG:   34,
        T_DATETIME_MEDIUM: 68,
        T_DATETIME_SHORT:  136,
        /*
         * FUNCTION
         *      supported CLDR date/time formats
         ****
         */
     
        /****d* cldr/T_WIDE, T_ABBREVIATED, T_NARROW
         * SYNOPSIS
         */
        T_WIDE:        'wide', 
        T_ABBREVIATED: 'abbreviated',
        T_NARROW:      'narrow', 
        /*
         * FUNCTION
         *      formats for returning various values
         ****
         */
        
        /****m* cldr/getMonth
         * SYNOPSIS
         */
        getMonth: function(format, month)
        /*
         * FUNCTION
         *      return localized name of month
         * INPUTS
         *      * format (int) -- format type
         *      * month (mixed) -- (optional) timestamp or string or number of month (0-11) or datetime object or nothing
         * OUTPUTS
         *      (string) -- name of month
         ****
         */
        {
            // prepare parameter to get javascript date-object
            switch (typeof month) {
            case 'number':
                if (month > 11) month = new Date(date * 1000).getMonth();
                break;
            case 'string':
                month = Date.parse(date).getMonth();
                break;
            case 'object':
                if (month instanceof Date) {
                    month = month.getMonth();
                    break;
                }
                
                /** FALL THRU **/
            default:
                month = (new Date().getMonth());
            }

            if (format != ltk.l10n.cldr.T_WIDE && format != ltk.l10n.cldr.T_ABBREVIATED && format != ltk.l10n.cldr.T_NARROR) {
                format = ltk.l10n.cldr.T_ABBREVIATED;
            }

            return ltk.l10n.cldr[ltk.l10n.getLocale()].month[format][month + 1];
        },
        
        /****m* cldr/datef
         * SYNOPSIS
         */
        datef: function(date, format)
        /*
         * FUNCTION
         *      date formatting
         * INPUTS
         *      * date (mixed) -- (optional) timestamp or string or datetime object or nothing
         *      * format (int) -- formatting type to use
         * OUTPUTS
         *      (string) -- formatting date according to current locale setting
         ****
         */
        {
            // prepare parameter to get javascript date-object
            switch (typeof date) {
            case 'number':
                date = new Date(date * 1000);
                break;
            case 'string':
                date = Date.parse(date);
                break;
            case 'object':
                if (date instanceof Date) {
                    break;
                }
                
                /** FALL THRU **/
            default:
                date = new Date();
            }
            
            // perform datetime formatting
            var pattern = [];
            var lc      = ltk.l10n.getLocale();
            
            if (typeof format != 'number') {
                pattern.push(ltk.l10n.cldr[lc].dateformat['full']);
            } else {
                if ((format & ltk.l10n.cldr.T_DATE_FULL) == ltk.l10n.cldr.T_DATE_FULL) {
                    pattern.push(ltk.l10n.cldr[lc].dateformat['full']);
                } else if ((format & ltk.l10n.cldr.T_DATE_LONG) == ltk.l10n.cldr.T_DATE_LONG) {
                    pattern.push(ltk.l10n.cldr[lc].dateformat['long']);
                } else if ((format & ltk.l10n.cldr.T_DATE_MEDIUM) == ltk.l10n.cldr.T_DATE_MEDIUM) {
                    pattern.push(ltk.l10n.cldr[lc].dateformat['medium']);
                } else if ((format & ltk.l10n.cldr.T_DATE_SHORT) == ltk.l10n.cldr.T_DATE_SHORT) {
                    pattern.push(ltk.l10n.cldr[lc].dateformat['short']);
                }

                if ((format & ltk.l10n.cldr.T_TIME_FULL) == ltk.l10n.cldr.T_TIME_FULL) {
                    pattern.push(ltk.l10n.cldr[lc].timeformat['full']);
                } else if ((format & ltk.l10n.cldr.T_TIME_LONG) == ltk.l10n.cldr.T_TIME_LONG) {
                    pattern.push(ltk.l10n.cldr[lc].timeformat['long']);
                } else if ((format & ltk.l10n.cldr.T_TIME_MEDIUM) == ltk.l10n.cldr.T_TIME_MEDIUM) {
                    pattern.push(ltk.l10n.cldr[lc].timeformat['medium']);
                } else if ((format & ltk.l10n.cldr.T_TIME_SHORT) == ltk.l10n.cldr.T_TIME_SHORT) {
                    pattern.push(ltk.l10n.cldr[lc].timeformat['short']);
                }
            }

            pattern = pattern.join(' ');
            var key = 'datef:' + pattern;

            if (!(key in compiled)) {
                compiler('datef', pattern);
            }

            return compiled[key](date, lc);
        },
        
        /****m* cldr/monf
         * SYNOPSIS
         */
        monf: function(value)
        /*
         * FUNCTION
         *      money formatter
         * INPUTS
         *      * value (int) -- money value to format
         * OUTPUTS
         *      (string) -- formatted number
         ****
         */
        {
            var lc       = ltk.l10n.getLocale();
            var patterns = ltk.l10n.cldr[lc].currency_format['default'].split(/;/);
            var pattern, prefix, suffix, ret;
            
            if (patterns.length == 1) patterns.push(patterns[0]);
            
            if (value < 0) {
                pattern = patterns[0];
            } else {
                pattern = patterns[1];
            }

            ret = ltk.l10n.cldr.numf(value, 2);

            if (pattern.match(/^(.*?)([#0\.,]+)(.*?)$/)) {
                prefix  = RegExp.$1;
                pattern = RegExp.$2;
                suffix  = RegExp.$3;
                
                ret = prefix + ret + suffix;
            }
            
            return ret;
        },
        
        /****m* cldr/numf
         * SYNOPSIS
         */
        numf: function(value, prec)
        /*
         * FUNCTION
         *      number formatter
         * INPUTS
         *      * value (mixed) -- number to format
         *      * prec (int) -- (optional) number of decimals
         * OUTPUTS
         *      (string) -- formatted number
         ****
         */
        {
            value = +value;
            value = (!isFinite(value) ? 0 : value);
            
            if (typeof prec == 'undefined') {
                prec = value.toString().replace(/^[^.]*(\.(.*)|)$/, '$2').length;
            }
            
            var lc  = ltk.l10n.getLocale();
            var sep = (typeof ltk.l10n.cldr[lc].number.group != 'undefined'
                       ? ltk.l10n.cldr[lc].number.group
                       : ',');
            var dec = (typeof ltk.l10n.cldr[lc].number.decimal != 'undefined'
                       ? ltk.l10n.cldr[lc].number.decimal
                       : '.');

            var parts = (prec 
                         ? toFixed(value, prec) 
                         : '' + Math.round(value)).split('.');

            if (parts[0].length > 3) {
                parts[0] = parts[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
            }

            if ((parts[1] || '').length < prec) {
                parts[1]  = parts[1] || '';
                parts[1] += new Array(prec - parts[1].length + 1).join('0');
            }
            
            return parts.join(dec);
        }
    }
})();

/****c* ltk/page
 * NAME
 *      ltk.page
 * FUNCTION
 *      functionality for handling page (document)
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('page' in ltk) return;

    ltk.page = {
        /****m* page/jump
         * SYNOPSIS
         */
        jump: function(name)
        /*
         * FUNCTION
         *      jump to anchor in document
         * INPUTS
         *      *   name (string) -- name of anchor to jumpt to
         * EXAMPLE
         *      ..  source: html
         * 
         *          ...
         *          <a name="test" />
         *          <strong>test</strong><br />
         *          ...
         *
         *      ..  source: js
         *
         *          ltk.page.jump('test');
         ****
         */
        {
            window.location.hash = name;
        },
        
        /****m* page/getUrl
         * SYNOPSIS
         */
        getUrl: function()
        /*
         * FUNCTION
         *      return URL of current page
         * OUTPUTS
         *      (string) -- URL of current page
         ****
         */
        {
            return window.location.href;
        }
    }
})();

/****c* ltk/registry
 * NAME
 *      ltk.registry
 * FUNCTION
 *      global registry
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('registry' in ltk) return;

    /*
     * private registry data
     */
    var data = {};

    ltk.registry = {
        /****m* registry/set
         * SYNOPSIS
         */
        set: function(name, value, protected)
        /*
         * FUNCTION
         *      register something in registry
         * INPUTS
         *      * name (string) -- name to register
         *      * value (mixed) -- value to register
         *      * protected (bool) -- whether to protect registry from deleting/overwriting (default: false)
         ****
         */
        {
            protected = (typeof protected != 'undefined'
                         ? protected
                         : false);

            if (!(name in data) || !data[name].protected) {
                data[name] = {
                    'value':     ltk.clone(value),
                    'protected': protected
                };
            }
        },
        
        /****m* registry/get
         * SYNOPSIS
         */
        get: function(name)
        /*
         * FUNCTION
         *      return value from registry
         * INPUTS
         *      * name (string) -- name of register to retrieve
         * OUTPUTS
         *      (mixed) -- register value
         ****
         */
        {
            return (name in data
                    ? ltk.clone(data[name].value)
                    : null);
        },

        /****m* registry/pop
         * SYNOPSIS
         */
        pop: function(name)
        /*
         * FUNCTION
         *      return value from registry and delete registry entry
         * INPUTS
         *      * name (string) -- name of register to retrieve and delete
         * OUTPUTS
         *      (mixed) -- register value
         ****
         */
        {
            var ret = null;

            if (name in data) {
                ret = ltk.clone(data[name].value);

                if (!data[name].protected) {
                    delete data[name];
                }
            }

            return ret;
        },

        /****m* registry/push
         * SYNOPSIS
         */
        push: function(name, value, protected)
        /*
         * FUNCTION
         *      alias for ltk.registry.set
         * INPUTS
         *      * name (string) -- name to register
         *      * value (mixed) -- value to register
         *      * protected (bool) -- whether to protect registry from deleting/overwriting (default: false)
         ****
         */
        {
            this.set(name, value, protected);
        },

        /****m* registry/unset
         * SYNOPSIS
         */
        unset: function(name)
        /*
         * FUNCTION
         *      remove register
         * INPUTS
         *      * name (string) -- name of register to remove
         ****
         */
        {
            if (name in data && !data[name].protected) {
                delete data[name];
            }
        }
    }
})();

/****c* core/rpc
 * NAME
 *      ltk.rpc
 * FUNCTION
 *      rpc library
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('rpc' in ltk) return;

    /****m* rpc/
     * SYNOPSIS
     */
    ltk.rpc = function(url, method)
    /*
     * FUNCTION
     *      constructor
     * INPUTS
     *      * url (string) -- URL to submit request to
     *      * method (string) -- HTTP method to use for request
     ****
     */
    {
        // inherit methods from new ajax request object
        var ajax = new ltk.rpc.ajax.initRequest();
        ltk.extend(this, ajax);

        // initialization of rpc object
        this.data = {};

        this.setHeader('Content-Type', 'application/x-www-form-urlencoded');
        this.setHeader('Accept', 'text/json');

        this.setOption('url', url || 'http://');
        this.setOption('method', method || 'POST');        

        this.setOption('async', true);

        // events
        var me = this;
        
        this.loadIndicator = function(state) {
            me.whileLoading(state);
        }

        this.registerErrorHandler(null, this.onFailure);
        this.registerStateHandler(ltk.rpc.ajax.state.COMPLETE, function(data) {
            if ((data instanceof Object) && ('error' in data)) {
                me.onFailure(200, data);
            } else {
                me.onSuccess(data);            
            }
        });
    }

    /****d* rpc/method.T_GET, method.T_POST
     * SYNOPSIS
     */
    ltk.rpc.method = {
        'T_GET':  'get',
        'T_POST': 'post',
        'T_HEAD': 'head'
    };
    /*
     * FUNCTION
     *      request methods
     ****
     */
    
    /****m* rpc/setValue
     * SYNOPSIS
     */
    ltk.rpc.prototype.setValue = function(name, value)
    /*
     * FUNCTION
     *      setter for submit parameters
     * INPUTS
     *      * name (string) -- name of parameter
     *      * value (mixed) -- value of parameter
     ****
     */
    {
        this.data[name] = value;
    }

    /****m* rpc/whileLoading
     * SYNOPSIS
     */
    ltk.rpc.prototype.whileLoading = function(state)
    /*
     * FUNCTION
     *      called when ajax request is started and when it ends -- this is a dummy method, which may be overwritten
     *      by a sub-class
     * INPUTS
     *      * state (bool) -- true: request started / false: request ended
     ****
     */
    {
    }

    /****m* rpc/onFailure
     * SYNOPSIS
     */
    ltk.rpc.prototype.onFailure = function(code, error)
    /*
     * FUNCTION
     *      get's called in case of the request failed -- this is a dummy method, which may be overwritten
     *      by a sub-class
     * INPUTS
     *      * code (int) -- HTTP status code
     *      * error (string) -- error message
     ****
     */
    {
    }

    /****m* rpc/onSuccess
     * SYNOPSIS
     */
    ltk.rpc.prototype.onSuccess = function(data)
    /*
     * FUNCTION
     *      get's called when the request succeeded -- this is a dummy method, which may be overwritten
     *      by a sub-class
     * INPUTS
     *      * data (mixed) -- data returned by the request
     ****
     */
    {
    }
})();

/****c* core/ajax
 * NAME
 *      ltk.ajax
 * FUNCTION
 *      ltk ajax library
 * COPYRIGHT
 *      copyright (c) 2006-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('ajax' in ltk.rpc) return;

    /*
     * constructor for XHR object
     */
    var getXMLHttpRequest = function() {
        var methods = [
            function() {
                return new XMLHttpRequest();
            },
            function() {
                return new ActiveXObject('Msxml2.XMLHTTP');
            },
            function() {
                return new ActiveXObject('Microsoft.XMLHTTP');
            },
            function() {
                return new ActiveXObject('Microsoft.XMLHTTP.4.0');
            },
            function() {
                throw 'unable to initiate XMLHttpRequest';
            }
        ];

        var req = false;

        for (var i = 0, len = methods.length; i < len; ++i) {
            try {
                req = methods[i]();
                
                getXMLHttpRequest = methods[i];     // overwirte original function to return always same constructor for following calls 
                
                return req;
            } catch(e) {}
        }
    };

    /*
     * builds query string from complex data object
     * ported from: http://de.php.net/manual/ru/function.http-build-query.php#72911
     */
    function buildQuery(obj, key) {
        var ret = [];
        var k, v, o, i, cnt, tmp;

        o = obj;

        if (typeof obj != 'object') {
            o = [obj];
        }

        if (typeof o.length != 'undefined') {
            // array
            for (i = 0, cnt = o.length; i < cnt; ++i) {
                v = o[i];
                k = i;

                if (typeof key != 'undefined') {
                    k = key + '[' + k + ']';
                }

                if (typeof v == 'object') {
                    ret.push(buildQuery(v, k));
                } else {
                    ret.push(k + '=' + encodeURIComponent(v));
                }
            }
        } else {
            // object
            for (k in o) {
                v = o[k];
                k = encodeURIComponent(k);

                if (typeof key != 'undefined') {
                    k = key + '[' + k + ']';
                }

                if (v === null) {
                    ret.push(k + '=');
                } else if (typeof v == 'object') {
                    tmp = buildQuery(v, k);
                    //                console.log(k, tmp.length);
                    // TODO: possible bug with empty arrays -> parameter will not be submitted
                    ret.push(tmp);
                } else {
                    ret.push(k + '=' + encodeURIComponent(v));
                }
            }
        }

        return ret.join('&');
    };

    ltk.rpc.ajax = {
        /****d* ajax/state
         * SYNOPSIS
         */
        'state': {
            'UNINITIALIZED': 0,
            'LOADING':       1,
            'LOADED':        2,
            'INTERACTIVE':   3,
            'COMPLETE':      4,
            'ABORTED':       1000
        },
        /*
         * FUNCTION
         *      ajax request state definitions
         ****
         */

        /****d* ajax/response
         * SYNOPSIS
         *      <code type="javascript">
         *      ltk.rpc.ajax.resonse = {
         *          TEXT: function(req) { ... }
         *          XML: function(req) { ... }
         *          JSON: function(req) { ... }
         *      }
         *      </code>
         * FUNCTION
         *      possible ajax response types and handlers
         ****
         */
        'response': {
            TEXT: function(req) {
                return req.getRequest().responseText;
            },
            XML: function(req) {
                var contentType = req.getRequest().getResponseHeader('Content-Type');

                if (contentType != 'application/xml' && 
                    contentType != 'text/xml' &&
                    typeof req.getRequest().responseBody != 'undefined') {
                    // IE hack, which can't load XML if content-type is any other than the above
                    try {
                        req.getRequest().responseXML.async = false;
                        req.getRequest().responseXML.load(req.getRequest().responseBody);
                    } catch(e) {}
                }

                return req.getRequest().responseXML;
            },
            JSON: function(req) {
                // TODO: use 'real' JSON parser instead of eval (eval is evil)
                var ret = {};
                var tmp = req.getRequest().responseText;

                try {
                    if (tmp != '') eval('ret = ' + tmp + ';');
                } catch(e) {
                }

                return ret;
            }
        },

        'request': {
            GET:  function(data) {},
            POST: function(data) {},
            XML:  function(data) {},
            JSON: function(data) {}
        },

        /****m* ajax/initRequest
         * SYNOPSIS
         */
        'initRequest': function()
        /*
         * FUNCTION
         *      initialize ajax request / constructor for ajax object instance
         ****
         */
        {
            var req = getXMLHttpRequest();

            var options = {
                'url':      'http://',
                'async':    true,
                'method':   'POST',
                'response': ltk.rpc.ajax.response.JSON,
                'data':     null,
                'headers':  {
                    'Content-Type': 'application/x-www-form-urlencoded'
                }
            };

            return new function() {
                var is_busy    = false;
                var is_aborted = false;

                /****m* ajax/isBusy
                 * SYNOPSIS
                 */
                this.isBusy = function()
                /*
                 * FUNCTION
                 *      checks whether request connection is busy
                 ****
                 */
                {
                    return is_busy;
                }

                /****m* ajax/loadIndicator
                 * SYNOPSIS
                 */
                this.loadIndicator = function(state)
                /*
                 * FUNCTION
                 *      sets state of load indicator
                 * INPUTS
                 *      * state (int) -- current state of load indicator
                 ****
                 */
                {
                }

                /****m* ajax/getRequest
                 * SYNOPSIS
                 */
                this.getRequest = function()
                /*
                 * FUNCTION
                 *      returns request object
                 * OUTPUTS
                 *      (object) -- XMLHttpRequest object
                 ****
                 */
                {
                    return req;
                }

                /****m* ajax/setOption
                 * SYNOPSIS
                 */
                this.setOption = function(name, value)
                /*
                 * FUNCTION
                 *      overwrite option setting
                 * INPUTS
                 *      * name (string) -- name of option to overwrite
                 *      * value (mixed) -- value to set for option
                 ****
                 */
                {
                    if (name in options && name != 'headers') {
                        options[name] = value;
                    }
                }

                /****m* ajax/setHeader
                 * SYNOPSIS
                 */
                this.setHeader = function(name, value)
                /*
                 * FUNCTION
                 *      set (additional) HTTP header
                 * INPUTS
                 *      * name (string) -- name of header to set
                 *      * value (string) -- value of header to set
                 ****
                 */
                {
                    options['headers'][name] = value;
                }

                /****m* ajax/abortRequest
                 * SYNOPSIS
                 */
                this.abortRequest = function()
                /*
                 * FUNCTION
                 *      abort ajax request
                 ****
                 */
                {
                    req.abort();

                    is_busy = false;
                    is_aborted = true;
                }

                /****m* ajax/sendRequest
                 * SYNOPSIS
                 */
                this.sendRequest = function(url, data, method)
                /*
                 * FUNCTION
                 *      send request to server
                 * INPUTS
                 *      * url (string) -- URL to send request to
                 *      * data (mixed) -- (optional) data to transmit
                 *      * method (string) -- (optional) HTTP method to use for request
                 * OUTPUTS
                 *      
                 ****
                 */
                {
                    var me = this;

                    if (req) {
                        if (this.isBusy()) {
                            this.abortRequest();
                        }

                        if (typeof this['stateHandler' + ltk.rpc.ajax.state.UNINITIALIZED] != 'undefined') {
                            this['stateHandler' + ltk.rpc.ajax.state.UNINITIALIZED]();
                        }

                        // sets header according to: http://usrportage.de/archives/875-Dojo-and-the-Zend-Framework.html
                        this.setHeader('X-Requested-With', 'XMLHttpRequest');

                        // overwrite default options
                        options['url']    = url || options['url'];
                        options['data']   = data || options['data'];
                        options['method'] = method || options['method'];

                        data = (typeof options['data'] == 'object' 
                                ? buildQuery(options['data'])
                                : options['data']);

                        // initiate new request
                        req.open(
                            options['method'], 
                            options['url'] + (data && (options['method'] == 'GET') 
                                              ? '?' + data 
                                              : ''), 
                            options['async']
                        );

                        is_busy = true;

                        if (typeof this['stateHandler' + ltk.rpc.ajax.state.LOADING] != 'undefined') {
                            this['stateHandler' + ltk.rpc.ajax.state.LOADING]();
                        }

                        this.loadIndicator(true);

                        // implements onreadystatechange event - invisible fromoutside 'sendrequest'
                        req.onreadystatechange = function() {
                            if (is_aborted) {
                                is_aborted = false;
                                return;
                            }

                            try {
                                var ready = req.readyState;
                                var data = null;

                                if (ready == ltk.rpc.ajax.state.COMPLETE) {
                                    var status = (!('status' in req) ? 0 : req.status);

                                    if (status <= 0 || (status >= 200 && status < 300)) {
                                        data = options['response'](me);
                                    } else {
                                        if (typeof me['errorHandler' + status] == 'function') {
                                            // call code specific error handler, if defined
                                            me['errorHandler' + status](me);
                                        } else if (typeof me['errorHandlerDefault'] == 'function') {
                                            // call default error handler
                                            me.errorHandlerDefault(status);
                                        }
                                    }

                                    is_busy = false;

                                    me.loadIndicator(false);
                                }

                                if (typeof me['stateHandler' + ready] == 'function') {
                                    me['stateHandler' + ready](data);
                                }
                            } catch(e) {
                            };
                        }

                        if (options['method'] == 'POST') {
                            // this.setHeader('Content-Length', data.length);
                        } else {
                            data = null;
                        }

                        for (var i in options['headers']) {
                            req.setRequestHeader(i, options['headers'][i]);
                        }

                        req.send(data);
                    }
                }

                /****m* ajax/registerStateHandler
                 * SYNOPSIS
                 */
                this.registerStateHandler = function(state, cb)
                /*
                 * FUNCTION
                 *      register handler method for specified request state
                 * INPUTS
                 *      * state (string) -- name of state to define handler for
                 *      * cb (callback) -- callback to call for specified state
                 ****
                 */
                {
                    this['stateHandler' + state] = cb;
                }

                /****m* ajax/registerErrorHandler
                 * SYNOPSIS
                 */
                this.registerErrorHandler = function(httperror, cb)
                /*
                 * FUNCTION
                 *      register handler method for request error
                 * INPUTS
                 *      * httperror (int) -- HTTP status code to define handler for
                 *      * cb (callback) -- callback to call for specified error
                 ****
                 */
                {
                    if (httperror == 'null') {
                        this['errorHandlerDefault'] = cb;
                    } else {
                        this['errorHandler' + httperror] = cb;
                    }
                }
            }
        },
        
        /****m* ajax/jsonrpc
         * SYNOPSIS
         */
        'jsonrpc': function(url, data, callback)
        /*
         * FUNCTION
         *      initiate jsonrpc call
         * INPUTS
         *      * url (string) -- URL to send request to
         *      * data (mixed) -- data to send with request
         *      * callback (callback) -- callback to call when request is finished
         ****
         */
        {
            var me = this;

            data['cb'] = name(callback);

            _load(url + '/?' + buildQuery(data));

            return this;

            function _name(callback) {
                var id   = (new Date).getTime();
                var name = 'jsonrpc_' + id;
                var cb   = function(json) {
                    eval( 'delete ' + name);

                    callback(json);
                };

                eval(name + ' = cb');

                return name;
            }

            function _load(url) {
                ltk.dom.one('head').appendChild(ltk.dom.create('script', {
                    'type': 'text/javascript',
                    'src':  url
                }));
            }    
        }
    };
})();

/****c* rpc/request
 * NAME
 *      ltk.rpc.request
 * FUNCTION
 *      performs a regular form request with page reload
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('request' in ltk.rpc) return;
    
    /*
     * builds flat datamodell for form fields
     */
    function buildFields(obj, key) {
        var ret = [];
        var k, v, o, i, cnt, tmp;

        o = obj;

        if (typeof obj != 'object') {
            o = [obj];
        }

        if (o instanceof Array) {
            // array
            for (i = 0, cnt = o.length; i < cnt; ++i) {
                v = o[i];
                k = i;

                if (typeof key != 'undefined') {
                    k = key + '[' + k + ']';
                }

                if (typeof v == 'object') {
                    ret.push(buildFields(v, k));
                } else {
                    ret.push({'field': k, 'value': v});
                }
            }
        } else {
            // object
            for (k in o) {
                v = o[k];

                if (typeof key != 'undefined') {
                    k = key + '[' + k + ']';
                }

                if (v === null) {
                    ret.push({'field': k, 'value': null});
                } else if (typeof v == 'object') {
                    tmp = buildFields(v, k);
                    // console.log(k, tmp.length);
                    // TODO: possible bug with empty arrays -> parameter will not be submitted
                    ret.push(tmp);
                } else {
                    ret.push({'field': k, 'value': v});
                }
            }
        }

        return ret;
    };

    ltk.rpc.request = {
        /****m* request/submit
         * SYNOPSIS
         */
        submit: function(url, method, data)
        /*
         * FUNCTION
         *      perform request
         * INPUTS
         *      * url (string) -- URL to submit request to
         *      * method (string) -- HTTP method to use for request
         *      * data (array) -- data to send for request
         ****
         */
        {
            var body = ltk.dom.one('body');
            var form = body.appendChild(ltk.dom.create('form', {
                'styles': {'display': 'none'},
                'action': url,
                'method': method
            }));
            
            var fields = buildFields(data);
            
            for (var i = 0, len = fields.length; i < len; ++i) {
                form.appendChild(ltk.dom.create('input', {
                    'type':  'hidden',
                    'name':  fields[i]['field'],
                    'value': fields[i]['value']
                }));
            }
            
            form.node.submit();
        }
    }
})();

/****c* ltk/array
 * NAME
 *      ltk.array
 * FUNCTION
 *      array functionality
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('array' in ltk) return;
    
    ltk.array = {
        /****m* array/toArray
         * SYNOPSIS
         */
        toArray: function(iterable)
        /*
         * FUNCTION
         *      convert iterable value to array
         * INPUTS
         *      * iterable (mixed) value to convert
         * OUTPUTS
         *      (array) -- array converted from input value
         ****
         */
        {
            try {
                return Array.prototype.slice.call(iterable);
            } catch(e) {
                var len = ('length' in iterable ? iterable.length : 0);
                var ret = new Array(len);
                
                while (len--) ret[len] = iterable[len];
            }
            
            return ret;
        },
        
        /****m* array/unique
         * SYNOPSIS
         */
        unique: function(arr)
        /*
         * FUNCTION
         *      make array elements unique
         * INPUTS
         *      * arr (array) -- array to process
         * OUTPUTS
         *      (array) -- processed array
         ****
         */
        {
            var tmp = {};
            var ret = [];
        
            for (var i = 0, cnt = arr.length; i < cnt; ++i) {
                if (!arr[i] in tmp) {
                    tmp[arr[i]] = true;
                    ret.push(arr[i]);
                }
            }
        
            return ret;
        },
        
        /****m* array/intersectKeys
         * SYNOPSIS
         */
        intersectKeys: function(obj1, obj2)
        /*
         * FUNCTION
         *      create intersection of keys in both objects
         * INPUTS
         *      * obj1 (object) -- object to start with
         *      * obj2 (object) -- object to compare with
         * OUTPUTS
         *      (object) -- object items from obj1, that intersect with obj2
         ****
         */
        {
            var tmp = {};
            
            for (var i in obj1) {
                if (i in obj2) {
                    tmp[i] = obj1[i];
                }
            }
            
            return tmp;
        },
        
        /****m* array/count
         * SYNOPSIS
         */
        count: function(arr)
        /*
         * FUNCTION
         *      count items in arrays _and_ objects
         * INPUTS
         *      * arr (mixed) -- array/object to check
         * OUTPUTS
         *      (int) -- length of array/object
         ****
         */
        {
            var len = 0;
            
            if (arr instanceof Array) {
                len = arr.length;
            } else {
                for (var i in arr) ++len;
            }
            
            return len;
        },
        
        /****m* array/forEach
         * SYNOPSIS
         */
        forEach: function(arr, cb)
        /*
         * FUNCTION
         *      object / array save forEach. Executes callback for each element
         *      in the object/array. if the callback returns any value other
         *      than ~undefined~, exit loop and return value. the callback get's
         *      the following parameters:
         *      
         *      *   Object
         *          1.  item of current position
         *          2.  numeric index of current position
         *          3.  key of current position
         *
         *      *   Array
         *          1.  item of current position
         *          2.  numeric index of current position
         * INPUTS
         *      * arr (array) -- array to loop through
         *      * cb (callback) -- callback to execute
         * OUTPUTS
         *      (mixed) -- ~undefined~ or return value of callback
         ****
         */
        {
            var r = undefined;

            if (typeof arr == 'object') {
                var i = 0;
                
                if (arr instanceof Array) {
                    for (var len = arr.length; i < len; ++i) {
                        if ((r = cb(arr[i], i)) !== undefined) return r;
                    }
                } else {
                    for (var key in arr) {
                        if ((r = cb(arr[key], i++, key)) !== undefined) return r;
                    }
                }
            }
                
            return r;
        },
        
        /****m* array/filter
         * SYNOPSIS
         */
        filter: function(arr, cb)
        /*
         * FUNCTION
         *      object / array save filter. Executes callback for each element
         *      in the object/array. if the callback returns true, the value 
         *      will be included in the result array, otherwise it will not be
         *      included in the result:
         *      
         *      *   Object
         *          1.  item of current position
         *          2.  numeric index of current position
         *          3.  key of current position
         *
         *      *   Array
         *          1.  item of current position
         *          2.  numeric index of current position
         * INPUTS
         *      * arr (array) -- array to loop through
         *      * cb (callback) -- callback to execute
         * OUTPUTS
         *      (mixed) -- ~undefined~ or return value of callback
         ****
         */
        {
            var ret = null;
            
            if (typeof arr == 'object') {
                var i = 0;
                
                if (arr instanceof Array) {
                    ret = [];
                    
                    for (var len = arr.length; i < len; ++i) {
                        if (!!cb(arr[i], i)) ret.push(arr[i]);
                    }
                } else {
                    ret = {};
                    
                    for (var key in arr) {
                        if (!!cb(arr[key], i++, key)) ret[key] = arr[key];
                    }
                }
            }
            
            return ret;
        }
    };
})();

/****c* ltk/string
 * NAME
 *      ltk.string
 * FUNCTION
 *      ltk string library
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('string' in ltk) return;

    /*
     * private maps for entity encoding / decoding
     */
    var entity2char = {
        'apos': 0x0027, 'quot': 0x0022, 'amp': 0x0026, 'lt': 0x003C, 'gt': 0x003E,
        'nbsp': 0x00A0, 'iexcl': 0x00A1, 'cent': 0x00A2, 'pound': 0x00A3, 'curren': 0x00A4,
        'yen': 0x00A5, 'brvbar': 0x00A6, 'sect': 0x00A7, 'uml': 0x00A8, 'copy': 0x00A9,
        'ordf': 0x00AA, 'laquo': 0x00AB, 'not': 0x00AC, 'shy': 0x00AD, 'reg': 0x00AE,
        'macr': 0x00AF, 'deg': 0x00B0, 'plusmn': 0x00B1, 'sup2': 0x00B2, 'sup3': 0x00B3,
        'acute': 0x00B4, 'micro': 0x00B5, 'para': 0x00B6, 'middot': 0x00B7, 'cedil': 0x00B8,
        'sup1': 0x00B9, 'ordm': 0x00BA, 'raquo': 0x00BB, 'frac14': 0x00BC, 'frac12': 0x00BD,
        'frac34' :0x00BE, 'iquest':0x00BF, 'Agrave': 0x00C0, 'Aacute': 0x00C1, 'Acirc': 0x00C2,
        'Atilde': 0x00C3, 'Auml': 0x00C4, 'Aring': 0x00C5, 'AElig': 0x00C6, 'Ccedil': 0x00C7,
        'Egrave': 0x00C8, 'Eacute': 0x00C9, 'Ecirc': 0x00CA, 'Euml': 0x00CB, 'Igrave': 0x00CC,
        'Iacute': 0x00CD, 'Icirc': 0x00CE, 'Iuml': 0x00CF, 'ETH': 0x00D0, 'Ntilde': 0x00D1,
        'Ograve': 0x00D2, 'Oacute': 0x00D3, 'Ocirc': 0x00D4, 'Otilde': 0x00D5, 'Ouml': 0x00D6,
        'times': 0x00D7, 'Oslash': 0x00D8, 'Ugrave': 0x00D9, 'Uacute': 0x00DA, 'Ucirc': 0x00DB,
        'Uuml': 0x00DC, 'Yacute': 0x00DD, 'THORN':0x00DE, 'szlig': 0x00DF, 'agrave': 0x00E0,
        'aacute': 0x00E1, 'acirc': 0x00E2, 'atilde': 0x00E3, 'auml': 0x00E4, 'aring': 0x00E5,
        'aelig': 0x00E6, 'ccedil': 0x00E7, 'egrave': 0x00E8, 'eacute': 0x00E9, 'ecirc': 0x00EA,
        'euml': 0x00EB, 'igrave': 0x00EC, 'iacute': 0x00ED, 'icirc': 0x00EE, 'iuml': 0x00EF,
        'eth': 0x00F0, 'ntilde': 0x00F1, 'ograve': 0x00F2, 'oacute': 0x00F3, 'ocirc': 0x00F4,
        'otilde': 0x00F5, 'ouml': 0x00F6, 'divide': 0x00F7, 'oslash': 0x00F8, 'ugrave': 0x00F9,
        'uacute': 0x00FA, 'ucirc': 0x00FB, 'uuml': 0x00FC, 'yacute': 0x00FD, 'thorn': 0x00FE,
        'yuml': 0x00FF, 'OElig': 0x0152, 'oelig': 0x0153, 'Scaron': 0x0160, 'scaron': 0x0161,
        'Yuml': 0x0178, 'fnof': 0x0192, 'circ': 0x02C6, 'tilde': 0x02DC, 'Alpha': 0x0391,
        'Beta': 0x0392, 'Gamma': 0x0393, 'Delta': 0x0394, 'Epsilon': 0x0395, 'Zeta': 0x0396,
        'Eta': 0x0397, 'Theta': 0x0398, 'Iota': 0x0399, 'Kappa': 0x039A, 'Lambda': 0x039B,
        'Mu': 0x039C, 'Nu': 0x039D, 'Xi': 0x039E, 'Omicron': 0x039F, 'Pi': 0x03A0, 'Rho': 0x03A1,
        'Sigma': 0x03A3, 'Tau': 0x03A4, 'Upsilon': 0x03A5, 'Phi': 0x03A6, 'Chi': 0x03A7,
        'Psi': 0x03A8, 'Omega': 0x03A9, 'alpha': 0x03B1, 'beta': 0x03B2, 'gamma': 0x03B3,
        'delta': 0x03B4, 'epsilon': 0x03B5, 'zeta': 0x03B6, 'eta': 0x03B7, 'theta': 0x03B8,
        'iota': 0x03B9, 'kappa': 0x03BA, 'lambda': 0x03BB, 'mu': 0x03BC, 'nu': 0x03BD,
        'xi': 0x03BE, 'omicron': 0x03BF, 'pi': 0x03C0, 'rho': 0x03C1, 'sigmaf': 0x03C2,
        'sigma': 0x03C3, 'tau': 0x03C4, 'upsilon': 0x03C5, 'phi': 0x03C6, 'chi': 0x03C7,
        'psi': 0x03C8, 'omega': 0x03C9, 'thetasym': 0x03D1, 'upsih': 0x03D2, 'piv': 0x03D6,
        'ensp': 0x2002, 'emsp': 0x2003, 'thinsp': 0x2009, 'zwnj': 0x200C, 'zwj': 0x200D,
        'lrm': 0x200E, 'rlm': 0x200F, 'ndash': 0x2013, 'mdash': 0x2014, 'lsquo': 0x2018,
        'rsquo': 0x2019, 'sbquo': 0x201A, 'ldquo': 0x201C, 'rdquo': 0x201D, 'bdquo': 0x201E,
        'dagger': 0x2020, 'Dagger': 0x2021, 'bull': 0x2022, 'hellip': 0x2026, 'permil': 0x2030,
        'prime': 0x2032, 'Prime': 0x2033, 'lsaquo': 0x2039, 'rsaquo': 0x203A, 'oline': 0x203E,
        'frasl': 0x2044, 'euro': 0x20AC, 'image': 0x2111, 'weierp': 0x2118, 'real': 0x211C,
        'trade': 0x2122, 'alefsym': 0x2135, 'larr': 0x2190, 'uarr': 0x2191, 'rarr': 0x2192,
        'darr': 0x2193, 'harr': 0x2194, 'crarr': 0x21B5, 'lArr': 0x21D0, 'uArr': 0x21D1,
        'rArr': 0x21D2, 'dArr': 0x21D3, 'hArr': 0x21D4, 'forall': 0x2200, 'part': 0x2202,
        'exist': 0x2203, 'empty': 0x2205, 'nabla': 0x2207, 'isin': 0x2208, 'notin': 0x2209,
        'ni': 0x220B, 'prod': 0x220F, 'sum': 0x2211, 'minus': 0x2212, 'lowast': 0x2217, 'radic': 0x221A,
        'prop': 0x221D, 'infin': 0x221E, 'ang': 0x2220, 'and': 0x2227, 'or': 0x2228,
        'cap': 0x2229, 'cup': 0x222A, 'int': 0x222B, 'there4': 0x2234, 'sim': 0x223C, 'cong': 0x2245,
        'asymp': 0x2248, 'ne': 0x2260, 'equiv': 0x2261, 'le': 0x2264, 'ge': 0x2265, 'sub': 0x2282,
        'sup': 0x2283, 'nsub': 0x2284, 'sube': 0x2286, 'supe': 0x2287, 'oplus': 0x2295,
        'otimes': 0x2297, 'perp': 0x22A5, 'sdot': 0x22C5, 'lceil': 0x2308, 'rceil': 0x2309,
        'lfloor': 0x230A, 'rfloor': 0x230B, 'lang': 0x2329, 'rang': 0x232A, 'loz': 0x25CA,
        'spades': 0x2660, 'clubs': 0x2663, 'hearts': 0x2665, 'diams': 0x2666
    };

    var char2entity = {};

    for (var entity in entity2char) {
        char2entity[String.fromCharCode(entity2char[entity])] = entity;
    }

    /*
     * private sprintf functions and properties
     */
    var _sprintf = {
        'regexp': /%%|%(\d+\$)?([-+\'0 ]*)(\d+)?(\.(\d+))?([scboxXuidfegEG])/g,
        'base':   {'b': 2, 'o': 8, 'x': 16, 'X': 16, 'u': 10},
        'format': function(value, width, precision, opt) {
            if (precision) {
                value = value.slice(0, precision);
            }
            
            width = Math.max(width, value.length);
            
            return (opt.leftalign
                    ? ltk.string.rpad(value, width, opt.pad)
                    : ltk.string.lpad(value, width, opt.pad));
        }
    }

    ltk.string = {
        /****m* string/trim
         * SYNOPSIS
         */
        trim: function(str, chr)
        /*
         * FUNCTION
         *      trim on both sides of string
         * INPUTS
         *      * str (string) -- string to trim
         *      * chr (string) -- (optional) characters to trim (default: ~space~)
         * OUTPUTS
         *      (string) -- trimmed string
         ****
         */
        {
            chr = chr || ' ';
            
            return (typeof str == 'string'
                    ? str.replace(new RegExp('^[' + chr + ']+'), '').replace(new RegExp('[' + chr + ']+$'), '')
                    : str);
        },
        
        /****m* string/ltrim
         * SYNOPSIS
         */
        ltrim: function(str, chr)
        /*
         * FUNCTION
         *      trim only on left side of string
         * INPUTS
         *      * str (string) -- string to trim
         *      * chr (string) -- (optional) characters to trim (default: ~space~)
         * OUTPUTS
         *      (string) -- trimmed string
         ****
         */
        {
            chr = chr || ' ';
            
            return (typeof str == 'string'
                    ? str.replace(new RegExp('^[' + chr + ']+'), '')
                    : str);
        },
        
        /****m* string/rtrim
         * SYNOPSIS
         */
        rtrim: function(str, chr)
        /*
         * FUNCTION
         *      trim only on right side of string
         * INPUTS
         *      * str (string) -- string to trim
         *      * chr (string) -- (optional) characters to trim (default: ~space~)
         * OUTPUTS
         *      (string) -- trimmed string
         ****
         */
        {
            chr = chr || ' ';
            
            return (typeof str == 'string'
                    ? str.replace(new RegExp('[' + chr + ']+$'), '')
                    : str);
        },
        
        /****m* string/match
         * SYNOPSIS
         */
        match: function(regexp, str, m)
        /*
         * FUNCTION
         *      perform pattern matching
         * INPUTS
         *      * regexp (mixed) -- either string or instanceof RegExp
         *      * str (string) -- string to match in
         *      * m (string) -- (optional) modifiers
         * OUTPUTS
         *      (mixed) -- result of match
         ****
         */
        {
            m = m || '';
            
            return (typeof str == 'string'
                    ? (str.match((regexp instanceof RegExp 
                       ? regexp
                       : new RegExp(regexp, m))))
                    : str);
        },
        
        /****m* string/replace
         * SYNOPSIS
         */
        replace: function(regexp, str, rpl, m)
        /*
         * FUNCTION
         *      perform pattern replacing
         * INPUTS
         *      * regexp (mixed) -- either string or instanceof RegExp
         *      * str (string) -- string to match in
         *      * rpl (mixed) -- parameter to use for replacement
         *      * m (string) -- (optional) modifiers
         * OUTPUTS
         *      (mixed) -- result of match
         ****
         */
        {
            m = m || '';
            
            return str.replace((regexp instanceof RegExp 
                              ? regexp
                              : new RegExp(regexp, m)), rpl);
        },
        
        /****m* string/reverse
         * SYNOPSIS
         */
        reverse: function(str)
        /*
         * FUNCTION
         *      reverse a string
         * INPUTS
         *      * str (string) -- string to reverse
         * OUTPUTS
         *      (string) -- reversed string
         ****
         */
        {
            return str.split('').reverse().join('');
        },
        
        /****m* string/stripslashes
         * SYNOPSIS
         */
        stripslashes: function(str)
        /*
         * FUNCTION
         *      strip slashes from string
         * INPUTS
         *      * str (string) -- string to strip slashes from
         * OUTPUTS
         *      (string) -- stripped string
         ****
         */
        {
            return str.replace(/\\(.?)/g, function(s, n) {
                return (n == '0' ? '\u0000' : n);
            });
        },

        /****m* string/lpad
         * SYNOPSIS
         */
        lpad: function(str, len, chr)
        /*
         * FUNCTION
         *      pad string on the left until it reaches the specified length
         * INPUTS
         *      * str (string) -- string to pad
         *      * len (string) -- length the final string to have
         *      * chr (string) -- (optional) padding character
         * OUTPUTS
         *      (string) -- padded string
         ****
         */
        {
            chr = chr || ' ';
            tmp = (new Array(len + 1)).join(chr) + str;
            
            return tmp.substr(tmp.length - len);
        },
        
        /****m* string/rpad
         * SYNOPSIS
         */
        rpad: function(str, len, chr)
        /*
         * FUNCTION
         *      pad string on the right until it reaches the specified length
         * INPUTS
         *      * str (string) -- string to pad
         *      * len (string) -- length the final string to have
         *      * chr (string) -- (optional) padding character
         * OUTPUTS
         *      (string) -- padded string
         ****
         */
        {
            chr = chr || ' ';
            tmp = str + (new Array(len + 1)).join(chr);
            
            return tmp.substr(0, len);
        },

        /****m* string/repeat
         * SYNOPSIS
         */
        repeat: function(chr, n)
        /*
         * FUNCTION
         *      repeat a string/character 'n' times
         * INPUTS
         *      * chr (string) -- string to repeat
         *      * n (int) -- number of times to repeat string
         * OUTPUTS
         *      (string) -- repeated string
         ****
         */
        {
            return (new Array(n + 1)).join(chr);
        },

        /****m* string/entitydecode
         * SYNOPSIS
         */
        entitydecode: function(str)
        /*
         * FUNCTION
         *      convert entities to hex representation
         * INPUTS
         *      * str (string) -- string to convert entities in
         * OUTPUTS
         *      (string) -- converted string
         ****
         */
        {
            return str.replace(/&(.+?);/g, function(str, ent) {
                return String.fromCharCode((ent[0] != '#'
                                     ? entity2char[ent]
                                     : (ent[1] == 'x'
                                        ? parseInt(ent.substr(2), 16)
                                        : parseInt(ent.substr(1), 10))));
            });
        },

        /****m* string/entityencode
         * SYNOPSIS
         */
        entityencode: function(str)
        /*
         * FUNCTION
         *      encode hex character representations into entities
         * INPUTS
         *      * str (string) -- string to convert
         * OUTPUTS
         *      (string) -- converted string
         ****
         */
        {
            return str.replace(/[^\x20-\x7E]/g, function(str) {
                return (char2entity[str]
                        ? '&' + char2entity[str] + ';'
                        : str);
            });
        },
        
        /****m* string/cut
         * SYNOPSIS
         */
        cut: function(str, len, chr)
        /*
         * FUNCTION
         *      
         * INPUTS
         *      * str (string)  -- orignial string
         *      * len (int)     -- max len of str
         *      * chr (string)  -- end sign's
         * OUTPUTS
         *      * (string) -- max len string
         ****
         */
        {
            
            if (str.length <= len) {
                return str;
            }
            
            if (typeof chr == 'undefined' || chr == 'undefined') {
                chr = ' ...';
            }

            str = str.substring(0, len-Number(chr.length));
    
            return str+chr;
        },
        
        /****m* string/htmlspecialchars
         * SYNOPSIS
         */
        htmlspecialchars: function(str)
        /*
         * FUNCTION
         *      convert special characters to html entities
         * INPUTS
         *      * str (string) -- string to convert
         * OUTPUTS
         *      (string) -- converted string
         ****
         */
        {
            return str.replace(/</g, '&lt;').replace(/>/g, '&gt;');
        },
        
        /****m* string/chunk
         * SYNOPSIS
         */
        chunk: function(str, width, brk)
        /*
         * FUNCTION
         *      split a string into chunks
         * INPUTS
         *      * str (string) -- string to split
         *      * width (int) -- width of each chunk
         *      * brk (string) -- character to use for splitting
         * OUTPUTS
         *      (string) -- splitted string
         ****
         */
        {
            brk   = brk || '\r\n';
            width = parseInt(width, 10) || 76;
            
            return str.match(new RegExp('.{0,' + width + '}', 'g')).join(brk);
        },
        
        /****m* string/chunk_id
         * SYNOPSIS
         */
        chunk_id: function(id, len)
        /*
         * FUNCTION
         *      
         * INPUTS
         *      
         * OUTPUTS
         *      
         ****
         */
        {
            len = parseInt(len, 10) || 9;
            
            if (len == 6) {
                id = Math.floor(id / 1000);
            }

            return ltk.string.chunk(ltk.string.lpad(id, 9, '0'), 3, '/');
        },
        
        /****m* string/wordwrap
         * SYNOPSIS
         */
        wordwrap: function(str, width, brk, cut)
        /*
         * FUNCTION
         *      javascript implementation for PHP's wordwrap
         * INPUTS
         *      * str (string) -- string to wrap
         *      * width (int) -- max width for wrapping
         *      * brk (string) -- character to use for wrapping
         *      * cut (bool) -- cut long words
         * OUTPUTS
         *      (string) -- wrapped string
         * REFERENCE
         *      http://james.padolsey.com/javascript/wordwrap-for-javascript/
         ****
         */
        {
            brk = brk || '\n';
            width = width || 75;
            cut = cut || false;

            if (!str) { return str; }

            var regex = '.{1,' +width+ '}(\\s|$)' + (cut ? '|.{' +width+ '}|.+$' : '|\\S+?(\\s|$)');

            return str.match( RegExp(regex, 'g') ).join( brk );
        },
        
        /****m* string/sprintf
         * SYNOPSIS
         */
        sprintf: function()
        /*
         * FUNCTION
         *      string formatter compatible to PHPs sprintf
         * INPUTS
         *      * arg-1 (string) -- formatting pattern
         *      * arg-2 .. n (mixed) -- unlimited number of values to format according to pattern
         * OUTPUTS
         *      (string) -- formatted string
         ****
         */
        {
            var args = arguments, pos = 0, pattern = args[pos++];

            return pattern.replace(_sprintf.regexp, function(str, idx, flags, width, _, precision, type) {
                if (str == '%%') return '%';
                
                var opt = {
                    'sign':      '',
                    'leftalign': false,
                    'pad':       ' '
                }
                
                if (flags.length > 0) for (var i = 0, len = flags.length; i < len; ++i) {
                    switch (flags.charAt(i)) {
                    case '+': 
                        // add '+' sign to positive numbers
                        opt.sign = '+';
                        break;
                    case '-':
                        // justify left
                        opt.leftalign = true;
                        break;
                    case "'":
                        // custom padding character
                        opt.pad = flags.charAt(i + 1);
                        break;
                    case '0':
                    case ' ':
                        // use '0' or ' ' as padding character
                        opt.pad = flags.charAt(i);
                        break;
                    }
                }
                
                width     = (width ? Math.abs(width) : 0);
                precision = (precision 
                             ? Math.abs(precision)
                             : ('fFeE'.indexOf(type) !== false
                                ? 6
                                : (type == d
                                   ? 0
                                   : undefined)));

                var val = (idx ? args[idx.slice(0, -1)] : args[pos++]);
                var ret = str;

                switch (type) {
                case 'c':
                    val = String.fromCharCode(val);
                    /** FALL THRU **/
                case 's':
                    ret = _sprintf.format(val, width, precision, opt);
                    break;
                case 'b':
                case 'o':
                case 'x':
                case 'X':
                case 'u':
                    val = val >>> 0;
                    val = val.toString(_sprintf.base[type]);
                    
                    if (type == 'X') {
                        ret = _sprintf.format(val, width, precision, opt).toUpperCase();
                    } else {
                        ret = _sprintf.format(val, width, precision, opt);
                    }
                    break;
                case 'i':
                case 'd':
                    val = parseInt(val, 10);
                    val = (isNaN(val) ? 0 : val);
                    
                    ret = (val < 0 ? '-' : opt.sign) + _sprintf.format(String(Math.abs(val)), width, precision, opt);
                    break;
                case 'e':
                case 'E':
                case 'f':
                case 'F':
                case 'g':
                case 'G':
                    val = parseInt(val, 10);
                    val = (isNaN(val) ? 0 : val);
                    ret = (val < 0 ? '-' : opt.sign);
                    
                    val = Math.abs(val);
                    
                    switch (type.toLowerCase()) {
                    case 'e':
                        val = val.toExponential(precision);
                        break;
                    case 'f':
                        val = val.toFixed(precision);
                        break;
                    case 'g':
                        val = val.toPrecision(precision);
                        break;
                    }

                    if (type == type.toLowerCase()) {
                        ret += _sprintf.format(val.toString(), width, 0, opt);
                    } else {
                        ret += _sprintf.format(val.toUpperCase(), width, 0, opt);
                    }
                    break;
                }
                
                return ret;
            });
        }
    }
})();

/****c* core/blocking
 * NAME
 *      ltk.blocking.js
 * FUNCTION
 *      allows block execution of callbacks
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

var ltk = ltk || {};

(function() {
    if ('blocking' in ltk) return;
    
    /****m* blocking/__construct
     * SYNOPSIS
     */
    ltk.blocking = function()
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.blocked = false;
        this.exec    = false;
        this.queue   = [];
    }
    
    /****m* blocking/call
     * SYNOPSIS
     */
    ltk.blocking.prototype._call = function(cb)
    /*
     * FUNCTION
     *      call specified callback in blocking mode
     * INPUTS
     *      * cb (callback) -- callback to call
     ****
     */
    {
        this.queue.push(cb);
        
        if (this.exec) return;
        this.exec = true;
        
        var me = this;
        
        window.setTimeout(function() {
            while ((cb = me.queue.pop())) {
                if (me.blocked) {
                    if (wait) {
                        me.queue.push(cb);
                    }
            
                    return;
                }
        
                me.blocked = true;
        
                cb(me);
        
                me.blocked = false;
            }

            me.exec = false;
        }, 1);
    }
})();

/****c* ltk/versions
 * NAME
 *      ltk.versions
 * FUNCTION
 *      generic versions (undo) implementation
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('versions' in ltk) return;
    
    var steps = 1000;           // allowed steps for each undo
    
    /****m* versions/
     * SYNOPSIS
     */
    ltk.versions = function()
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        var buffer  = [];                       // undo buffer
        var pointer = 0;                        // pointer for undo buffer
        
        var blocking = new ltk.blocking();      // handler for "blocking calls"
        
        /** public methods with access to private properties **/

        /****m* versions/undo
         * SYNOPSIS
         */
        this.undo = function() 
        /*
         * FUNCTION
         *      implements undo functionality
         ****
         */
        {
            blocking._call(function() {
                if (buffer.length == 0 || pointer == 0) return;
                
                var item = buffer[--pointer];
                item.undo(item.data);
            });
        }
        
        /****m* versions/redo
         * SYNOPSIS
         */
        this.redo = function()
        /*
         * FUNCTION
         *      implements redo functionality
         ****
         */
        {
            blocking._call(function() {
                if (buffer.length == pointer + 1) return;

                var item = buffer[++pointer];
                item.redo(item.data);
            });
        }
        
        /****m* versions/push
         * SYNOPSIS
         */
        this.push = function(do_fnc, undo_fnc, data, exec)
        /*
         * FUNCTION
         *      push a step onto the undo buffer and optionally execute function
         * INPUTS
         *      * do_fnc (callback) -- function to perform on do/redo
         *      * undo_fnc (callback) -- function to perform on undo
         *      * data (mixed) -- (optional) additional data to save for step
         *      * exec (bool) -- (optional) execute do-function (default: true)
         ****
         */
        {
            data = data || {};
            exec = (exec !== false ? true : false);
            
            blocking._call(function() {
                buffer.slice(0, pointer + 1);
                buffer.push({
                    'redo': do_fnc,
                    'undo': undo_fnc,
                    'data': data
                });
            
                if (buffer.length > steps) {
                    buffer.shift();
                }
            
                pointer = buffer.length - 1;
                
                if (exec) do_fnc(data);
            });
        }
        
        /****m* versions/canUndo
         * SYNOPSIS
         */
        this.canUndo = function()
        /*
         * FUNCTION
         *      returns true, if undo can be performed
         * OUTPUTS
         *      (bool) -- undo status
         ****
         */
        {
            return (pointer > 0);
        }
        
        /****m* versions/canRedo
         * SYNOPSIS
         */
        this.canRedo = function()
        /*
         * FUNCTION
         *      returns true, if redo can be performed
         * OUTPUTS
         *      (bool) -- redo status
         ****
         */
        {
            return (pointer + 1 < buffer.length);
        }
        
        /****m* versions/getSize
         * SYNOPSIS
         */
        this.getSize = function()
        /*
         * FUNCTION
         *      returns number of buffer items
         * OUTPUTS
         *      (int) -- number of items in undo buffer
         ****
         */
        {
            return buffer.length;
        }
        
        /****m* versions/getItem
         * SYNOPSIS
         */
        this.getItem = function(n)
        /*
         * FUNCTION
         *      returns specified item from buffer or false, if item does not exist. if ~n~ is not specified, return item
         *      the pointer currently points to.
         * INPUTS
         *      * n (int) -- (optional) number of item to return
         * OUTPUTS
         *      (object) -- item
         ****
         */
        {
            n = (typeof n == 'undefined' ? pointer : n);
            
            return (n in buffer ? buffer[n] : false); 
        }
        
        /****m* versions/getPointer
         * SYNOPSIS
         */
        this.getPointer = function()
        /*
         * FUNCTION
         *      returns step the pointer points to
         * OUTPUTS
         *      (int) -- current pointer position
         ****
         */
        {
            return pointer;
        }
    }
    
    /****m* versions/onChange
     * SYNOPSIS
     */
    ltk.versions.prototype.onChange = function()
    /*
     * FUNCTION
     *      get's called, when redo/undo is performed
     ****
     */
    {
    }
})();

/****c* core/color
 * NAME
 *      ltk.color
 * FUNCTION
 *      color tools
 * COPYRIGHT
 *      copyright (c) 2004-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('color' in ltk) return;
    
    /****m* color/
     * SYNOPSIS
     */
    ltk.color = function(p, type) 
    /*
     * FUNCTION
     *      constructor. as parameter a 
     * INPUTS
     *      * p (mixed) -- color to initialize object with
     *      * type (int) -- (optional) color type -- if not specified, expects RGB array as first parameter
     ****
     */
    {
        type = (typeof type == 'undefined' ? ltk.color.type.RGB : type);
    
        this.r = 0;
        this.g = 0;
        this.b = 0;

        if (typeof p != 'undefined') {
            this.setColor(p, type);
        }
    }

    /****d* color/type
     * SYNOPSIS
     */
    ltk.color.type = {
        // color schemes
        RGB:    1, 
        CMYK:   2,
        HSV:    3,
        BW:     4,
        HEX:    5,
    
        // RGB color channels
        CH_R:   10,
        CH_G:   11,
        CH_B:   12,
    
        // CMYK color channels
        CH_C:   20,
        CH_M:   21,
        CH_Y:   22,
        CH_K:   23,
        
        // HSV channels
        CH_H:   30,
        CH_S:   31,
        CH_V:   32,
        
        // BW channel
        CH_BW:  40
    }
    /*
     * FUNCTION
     *      color types
     ****
     */

    /****f* color/cmyk2rgb
     * SYNOPSIS
     */
    ltk.color.cmyk2rgb = function(p) 
    /*
     * FUNCTION
     *      convert cmyk to rgb
     * INPUTS
     *      * p (array) -- [c, m, y, k]
     * OUTPUTS
     *      (array) -- [r, g, b]
     ****
     */
    {
        c = (p[0] / 100);
        m = (p[1] / 100);
        y = (p[2] / 100);
        k = (p[3] / 100);
    
        var r = Math.floor((1 - Math.min(1, c * (1 - k) + k)) * 255);
        var g = Math.floor((1 - Math.min(1, m * (1 - k) + k)) * 255);            
        var b = Math.floor((1 - Math.min(1, y * (1 - k) + k)) * 255);

        return [r, g, b];        
    }
    
    /****f* color/rgb2cmyk
     * SYNOPSIS
     */
    ltk.color.rgb2cmyk = function(p) 
    /*
     * FUNCTION 
     *      convert rgb to cmyk
     * INPUTS
     *      * p (array) -- [c, m, y, k]
     * OUTPUTS
     *      (array) -- [r, g, b]
     ****
     */
    {
        var c = (1 - Math.min(1, p[0] / 255));
        var m = (1 - Math.min(1, p[1] / 255));
        var y = (1 - Math.min(1, p[2] / 255));
        var k = Math.min(c, Math.min(m, y));
    
        c = Math.floor((c - k) * 100);
        m = Math.floor((m - k) * 100);
        y = Math.floor((y - k) * 100);
        k = Math.floor(k);
    
        return [c, m, y, k];
    }
    
    /****f* color/hsv2rgb
     * SYNOPSIS
     */
    ltk.color.hsv2rgb = function(p) 
    /*
     * FUNCTION
     *      convert hsv to rgb
     * INPUTS
     *      * p (array) -- [h, s, v]
     * OUTPUTS
     *      (array) -- [r, g, b]
     ****
     */
    {
        var h = (p[0] % 360);
        var s = p[1] / 100;
        var v = p[2] / 100;

        if (h < 0) { h += 360; }        
        h = h / 60;
    
        var i = Math.floor(h);
        var f = h - i;

        var p = v * (1 - s);
        var q = v * (1 - s * f);
        var t = v * (1 - s * (1 - f));

        if (i == 0) {
            r = v; g = t; b = p;
        } else if (i == 1) {
            r = q; g = v; b = p;
        } else if (i == 2) {
            r = p; g = v; b = t;
        } else if (i == 3) {
            r = p; g = q; b = v;
        } else if (i == 4) {
            r = t; g = p; b = v;
        } else {
            r = v; g = p; b = q;
        }
    
        return [Math.floor(r * 255), Math.floor(g * 255), Math.floor(b * 255)];
    }

    /****f* color/rgb2hsv
     * SYNOPSIS
     */
    ltk.color.rgb2hsv = function(p) 
    /*
     * FUNCTION
     *      convert rgb to hsv
     * INPUTS
     *      * p (array) -- [r, g, b]
     * OUTPUTS
     *      (array) -- [h, s, v]
     ****
     */
    {
        var r = p[0];
        var g = p[1];
        var b = p[2];
    
        var v = Math.max(r, Math.max(g, b));
        var x = Math.min(r, Math.min(g, b));

        if (v == 0) {
            return [0, 0, 0];
        }

        var s = (v - x) / v;

        var rtemp = (v - r) * 60 / (v - x);
        var gtemp = (v - g) * 60 / (v - x);
        var btemp = (v - b) * 60 / (v - x);

        if (r == v)  {
            if (g == x) {
                var h = 300 + btemp;
            } else {
                var h = 60 - gtemp;
            }
        } else if (g == v) {
            if (b == x) {
                var h = 60 + rtemp;
            } else {
                var h = 180 - btemp;  
            }
        } else {
            if (r == x) {
                var h = 180 + gtemp;
            } else {
                var h = 300 - rtemp;
            }
        }
    
        return [h, s, v];
    }

    /****f* color/bw2rgb
     * SYNOPSIS
     */
    ltk.color.bw2rgb = function(p) 
    /*
     * FUNCTION
     *      convert b/w to rgb
     * INPUTS
     *      * p (array) -- [bw]
     * OUTPUTS
     *      (array) -- [r, g, b]
     ****
     */
    {
        var rgb = Math.floor((p[0] / 100) * 255);
    
        return [rgb, rgb, rgb];
    }
    
    /****f* color/rgb2bw
     * SYNOPSIS
     */
    ltk.color.rgb2bw = function(p) 
    /*
     * FUNCTION
     *      convert rgb to b/w
     * INPUTS
     *      * p (array) -- [r, g, b]
     * OUTPUTS
     *      (array) -- [bw]
     ****
     */
    {
        var bw = Math.floor((p[0] * 0.31 + p[1] * 0.46 + p[2] * 0.23) / 255 * 100);
    
        return [bw, bw, bw];
    }

    /****f* color/rgb2hex
     * SYNOPSIS
     */
    ltk.color.rgb2hex = function(p)
    /*
     * FUNCTION
     *      convert rgb to hex string
     * INPUTS
     *      * p (array) -- [r, g, b]
     * OUTPUTS
     *      (string) -- #rrggbb
     ****
     */
    {
        var r = '0' + (p[0].toString(16));
        var g = '0' + (p[1].toString(16));
        var b = '0' + (p[2].toString(16));

        return '#'
            + r.substring(r.length - 2)
            + g.substring(g.length - 2)
            + b.substring(b.length - 2);
    }

    /****f* color/hex2rgb
     * SYNOPSIS
     */
    ltk.color.hex2rgb = function(p)
    /*
     * FUNCTION
     *      convert hext to rgb
     * INPUTS
     *      * p (string) -- #rrggbb
     * OUTPUTS
     *      (array) -- [r, g, b]
     ****
     */
    {
        p = p.replace(/^#/, '');        // remove leading '#' -- anyway we don't need it
    
        if (p.length == 3) {
            // convert 3-digit color notation to 6 digit notation
            p = p.substr(1, 1) + p.substr(1, 1) +
                p.substr(2, 1) + p.substr(2, 1) +
                p.substr(3, 1) + p.substr(3, 1);
        }

        return [
            parseInt('0x' + p.substring(0, 2), 16), 
            parseInt('0x' + p.substring(2, 4), 16), 
            parseInt('0x' + p.substring(4, 6), 16)
        ];
    }

    /****f* color/getBrightness
     * SYNOPSIS
     */
    ltk.color.getBrightness = function(p)
    /*
     * FUNCTION
     *      calculate color brightness accoring to:
     *      http://www.nbdtech.com/blog/archive/2008/04/27/Calculating-the-Perceived-Brightness-of-a-Color.aspx
     * INPUTS
     *      * p (array) -- [r, g, b]
     * OUTPUTS
     *      (int) -- brightness value´(0 - 255)
     ****
     */
    {
        return Math.floor(Math.sqrt(p[0] * p[0] * .241 + p[1] * p[1] * .691 + p[2] * p[2] * .068));
    }

    /****f* color/blend
     * SYNOPSIS
     */
    ltk.color.blend = function(a, b, alpha)
    /*
     * FUNCTION
     *      blend two colors
     * INPUTS
     *      * a (array) -- first color [r, g, b]
     *      * b (array) -- second color [r, g, b]
     *      * alpha (int) -- alpha value to use for blending
     * OUTPUTS
     *      (array) -- [r, g, b]
     ****
     */
    {
        return [
            Math.round(a[0] + (b[0] - a[0]) * alpha),
            Math.round(a[1] + (b[1] - a[1]) * alpha),
            Math.round(a[2] + (b[2] - a[2]) * alpha)
        ];
    }

    /****m* color/setColor
     * SYNOPSIS
     */
    ltk.color.prototype.setColor = function(p, type)
    /*
     * FUNCTION
     *      set color value for color object
     * INPUTS
     *      * p (mixed) -- color to set
     *      * type (int) -- type of color specified as first parameter
     ****
     */
    {
        var rgb = [];
    
        switch (parseInt(type, 10)) {
        case ltk.color.type.RGB:
            rgb = p;
            break;
        case ltk.color.type.CMYK:
            rgb = ltk.color.cmyk2rgb(p);
            break;
        case ltk.color.type.HSV:
            rgb = ltk.color.hsv2rgb(p);
            break;
        case ltk.color.type.BW:
            rgb = ltk.color.bw2rgb(p);
            break;
        case ltk.color.type.HEX:
            rgb = ltk.color.hex2rgb(p);
            break;
        
        case ltk.color.type.CH_R:
            rgb = [p, 0, 0];
            break;
        case ltk.color.type.CH_G:
            rgb = [0, p, 0];
            break;
        case ltk.color.type.CH_B:
            rgb = [0, 0, p];
            break;
        
        case ltk.color.type.CH_C:
            rgb = ltk.color.cmyk2rgb([p, 0, 0, 0]);
            break;
        case ltk.color.type.CH_M:
            rgb = ltk.color.cmyk2rgb([0, p, 0, 0]);
            break;
        case ltk.color.type.CH_Y:
            rgb = ltk.color.cmyk2rgb([0, 0, p, 0]);
            break;
        case ltk.color.type.CH_K:
            rgb = ltk.color.cmyk2rgb([0, 0, 0, p]);
            break;

        case ltk.color.type.CH_H:
            rgb = ltk.color.hsv2rgb([p, 0, 0]);
            break;
        case ltk.color.type.CH_S:
            rgb = ltk.color.hsv2rgb([0, p, 0]);
            break;
        case ltk.color.type.CH_V:
            rgb = ltk.color.hsv2rgb([0, 0, p]);
            break;

        case ltk.color.type.CH_BW:
            rgb = ltk.color.bw2rgb([p]);
            break;

        default:
            return;
        }
    
        this.r = rgb[0];
        this.g = rgb[1];
        this.b = rgb[2];
    }

    /****m* color/getColor
     * SYNOPSIS
     */
    ltk.color.prototype.getColor = function(type)
    /*
     * FUNCTION
     *      return color in the format of the specified type
     * INPUTS
     *      * type (int) -- type to return color as
     * OUTPUTS
     *      (mixed) -- color according to specified type
     ****
     */
    {
        var ret = null;
        var p   = [this.r, this.g, this.b];

        switch (parseInt(type, 10)) {
        case ltk.color.type.RGB:
            ret = p;
            break;
        case ltk.color.type.CMYK:
            ret = ltk.color.rgb2cmyk(p);
            break;
        case ltk.color.type.HSV:
            ret = ltk.color.rgb2hsv(p);
            break;
        case ltk.color.type.BW:
            ret = ltk.color.rgb2bw(p);
            break;
        case ltk.color.type.HEX:
            ret = ltk.color.rgb2hex(p);
            break;
        
        case ltk.color.type.CH_R:
            ret = this.r;
            break;
        case ltk.color.type.CH_G:
            ret = this.g;
            break;
        case ltk.color.type.CH_B:
            ret = this.b;
            break;

        case ltk.color.type.CH_C:
            ret = ltk.color.rgb2cmyk(p)[0];
            break;
        case ltk.color.type.CH_M:
            ret = ltk.color.rgb2cmyk(p)[1];
            break;
        case ltk.color.type.CH_Y:
            ret = ltk.color.rgb2cmyk(p)[2];
            break;
        case ltk.color.type.CH_K:
            ret = ltk.color.rgb2cmyk(p)[3];
            break;

        case ltk.color.type.CH_H:
            ret = ltk.color.rgb2hsv(p)[0];
            break;
        case ltk.color.type.CH_S:
            ret = ltk.color.rgb2hsv(p)[1];
            break;
        case ltk.color.type.CH_V:
            ret = ltk.color.rgb2hsv(p)[2];
            break;

        case ltk.color.type.CH_BW:
            ret = ltk.color.rgb2bw(p)[0];
            break;

        default:
            break;
        }
    
        return ret;
    }
})();

/****c* libs/ltext
 * NAME
 *      ltext.js
 * FUNCTION
 *      javascript implementation of ltext parser / renderer. This parsers purpose is
 *      to parse _ltext_ and produce a wysiwyg preview. this parser currently does not 
 *      support block plugins and table parsing ability.
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('ltext' in ltk) return;
    
    /*
     * functionality to convert emoticons
     */
    var emoticons = {
        ':lol:':        /\)\)-?\:(?= |$)/mg,
        ':yppah:':      /\)-?\:(?= |$)/mg,
        ':gnikniw:':    /\)-?;(?= |$)/mg,
        ':lfor:':       /\)\)=(?= |$)/mg,
        ':gniyrc:':     /\(\(-?\:(?= |$)/mg,
        ':das:':        /\(-?\:(?= |$)/mg,
        ':nrig:':       /D-?\:(?= |$)/mg,
        ':desufnoc:':   /\/-?\:(?= |$)/mg,
        ':uegnot:':     /[pP]-?\:(?= |$)/mg,
        ':ssik:':       /\*-?\:(?= |$)/mg,
        ':nwolc:':      /\)[oO]\:(?= |$)/mg,
        ':esirprus:':   /[oO]-?\:(?= |$)/mg,
        ':yrgna:':      /\(-?[xX](?= |$)/mg,
        ':looc:':       /\)-?B(?= |$)/mg,
        ':lived:':      /\)-?\:\>(?= |$)/mg,
        ':kcis:':       /&-?\:(?= |$)/mg
    };

    function normalize(str) {
        if (str.length < 2) return str;
    
        str = ltk.string.reverse(str);

        for (var i in emoticons) {
            str = str.replace(emoticons[i], i);
        }
    
        return ltk.string.reverse(str);
    };

    /*
     * pattern for processing inline formatting
     */
    var inline = /( |^)((\*|\~|_|@|-)([^ ]|[^ ].*?[^ ])\3(?=\W|$)|(\:)([^\s]+)\:|(\[)(.*?)\]|((https?)\:\/\/[^\s]+))/m;
    
    /****m* ltext/
     * SYNOPSIS
     */
    ltk.ltext = function()
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.tab_width = 4;
        this.headline  = {};

        this.url_handler = function(parent, url) {
            var e = parent.appendChild(document.createElement('a'));
            e.setAttribute('href', url);
            e.appendChild(document.createTextNode(url));
        };
    
        this.setHeadlineType(ltk.ltext.T_HL_DEFAULT);
    }

    /****d* ltext/T_HL_DEFAULT, T_HL_EXTENDED
     * SYNOPSIS
     */
    ltk.ltext.T_HL_DEFAULT  = 1;
    ltk.ltext.T_HL_EXTENDED = 2;
    /*
     * FUNCTION
     *      headline types
     ****
     */
    
     /*
      * headline types
      */
     var headlines = {};
     headlines[ltk.ltext.T_HL_DEFAULT] = {
         '=': 1, '-': 2, '~': 3
     };
     headlines[ltk.ltext.T_HL_EXTENDED] = {
         '#': 1, '%': 2, '=': 3, '-': 4, '~': 5
     };

    /****m* ltext/setHeadlineType
     * SYNOPSIS
     */
    ltk.ltext.prototype.setHeadlineType = function(type)
    /*
     * FUNCTION
     *      set type of headlines to match
     * INPUTS
     *      * type (int) -- type of headline to set
     ****
     */
    {
        var chars = [];
    
        for (var ch in headlines[type]) {
            chars.push(ch);
        }
    
        this.headline = {
            'pattern': '^(' + chars.join('|') + ')+$',
            'type':    type
        };
    }

    /****m* ltext/setURLHandler
     * SYNOPSIS
     */
    ltk.ltext.prototype.setURLHandler = function(cb)
    /*
     * FUNCTION
     *      set handler for processing URLs
     * INPUTS
     *      * cb (closure) -- URL processing handler
     ****
     */
    {
       this.url_handler = cb;
    }

    /****m* ltext/setTabWidth
     * SYNOPSIS
     */
    ltk.ltext.prototype.setTabWidth = function(width)
    /*
     * FUNCTION
     *      set tab width of text to parse
     * INPUTS
     *      * width (int) -- tab-width to set
     ****
     */
    {
        this.tab_width = width;
    }

    /****m* ltext/process
     * SYNOPSIS
     */
    ltk.ltext.prototype.process = function(inp) 
    /*
     * FUNCTION
     *      process input using ltext parser
     * INPUTS
     *      * inp (string) -- input source
     * OUTPUTS
     *      (DOMDocument) -- DOM tree of parset input
     ****
     */
    {
        var me = this;
        var fp = (function(str) {
            return (new function() {
                var rows = str.replace(/\r/g, '\n').replace(/\n{3,}/g, '\n\n').split(/\n/);
                var row  = 0;

                this.getNextLine = function() {
                    return (row >= rows.length
                            ? false
                            : ltk.string.rtrim(rows[row++]));
                };
            });
        })(inp);

        var doc = document.createElement('div');
        doc.setAttribute('id', ltk.getUniqID('ltext_body_'));

        this.parseBlock(doc, fp);
    
        return doc;
    }

    /****m* ltext/getIndent
     * SYNOPSIS
     */
    ltk.ltext.prototype.getIndent = function(line)
    /*
     * FUNCTION
     *      determine indent of line
     * INPUTS
     *      * line (string) -- determine line indent
     * OUTPUTS
     *      (int) -- indent of line
     ****
     */
    {
        var line_indent = ((match = ltk.string.match(/^( *)/, line))
                            ? match[1].length
                            : 0);
    
        return Math.floor(line_indent / this.tab_width);
    }

    /****m* ltext/parseBlock
     * SYNOPSIS
     */
    ltk.ltext.prototype.parseBlock = function(parent, fp)
    /*
     * FUNCTION
     *      block parser
     * INPUTS
     *      * parent (object) -- parent of elements to parse
     *      * fp (object) -- object to fetch new lines from
     ****
     */
    {   
        var indent = 0;
        var line   = '';
        var buffer = [];
        var state  = 0;
        var eat    = false;

        var line_indent, p, cdata, e, match, type;

        var buffer_cnt = function() {
            return buffer.length;
        };
        var buffer_len = function() {
            return buffer.join(' ').length;
        };
        var buffer_eat = function(chr) {
            chr = (typeof chr == 'undefined' ? ' ' : chr);
        
            var ret = buffer.join(chr);
            buffer = [];
        
            return ret;
        };

        while (true) {
            if (line == '' && (line = fp.getNextLine()) === false) {
                // no more lines to parse, but if there's still something in
                // the buffer, one more iteration is required
                if (buffer_cnt() == 0) {
                    break;
                }
            }

            // determine indent level for line
            line_indent = this.getIndent(line);
        
            // test for eat-flag, empty line or lower indent -> new paragraph
            if (eat || ltk.string.trim(line) == '' || line_indent < indent) {
                if (buffer_cnt() > 0 && state == 0) {
                    // buffer is not empty for default state -> exec inline parser
                    if (parent.nodeName != 'BLOCKQUOTE' || !parent.lastChild || parent.lastChild.nodeName != 'P') {
                        p = document.createElement('p');
                        parent.appendChild(p);
                    } else {
                        // special handling of paragraph in blockquote
                        p.appendChild(document.createTextNode(' '));
                    } 

                    this.parseInline(p, buffer_eat());
                }
            
                if (eat) {
                    eat = false;
                } else {
                    if (ltk.string.trim(line) != '' && indent > 0) {
                        // lower indent for line and continue parsing
                        --indent;
                        state = 0;

                        do {
                            parent = parent.parentNode;
                        } while (parent.nodeName == 'UL' || parent.nodeName == 'OL');
                    }

                    continue;
                }
            }

            // remove indent only if in normal parser state
            if (state == 0) {
                line = ltk.string.trim(line);
            }
        
            // block rule parser
            if (state == 0 && (match = ltk.string.match(this.headline['pattern'], line)) && buffer_cnt() > 0) {
                // headline matched
                level = headlines[this.headline['type']][match[0].substr(0, 1)];
                e = document.createElement('h' + level);

                this.parseInline(e, buffer_eat());
                parent.appendChild(e);
    
                // eat line
                line = '';
            } else if (state == 0 && (match = ltk.string.match('^(\\*|\\+|-|#) {' + (this.tab_width - 1) + '}', line))) {
                if (buffer_cnt() > 0) {
                    // buffer is not empty
                    eat = true;
                    continue;
                }
            
                // sorted / unsorted list
                type = (match[1] == '#' ? 'OL' : 'UL');
            
                if (!parent.lastChild || parent.lastChild.nodeName != type) {
                    // only create list container, if there is not already one available
                    e = document.createElement(type);
                    parent.appendChild(e);
                } else {
                    // previous element is list container of same type
                    e = parent.lastChild;
                }
        
                parent = e.appendChild(document.createElement('li'));
                ++indent;

                // expand line to fit new line indent
                line = ltk.string.repeat(' ', (indent * this.tab_width)) + line.substr(match[0].length);
            } else if (state == 0 && (match = ltk.string.match(/^---+/, line)) && buffer_cnt() == 0) {
                // section
                parent.appendChild(document.createElement('hr'));

                // eat line
                line = '';
            } else if (state == 0 && (match = ltk.string.match('^> {' + (this.tab_width - 1) + '}', line))) {
                if (buffer_cnt() > 0) {
                    // buffer is not empty
                    eat = true;
                    continue;
                }
            
                // block quote
                if (!parent.lastChild || parent.lastChild.nodeName != 'BLOCKQUOTE') {
                    // only create container, if there is not already one available
                    parent = parent.appendChild(document.createElement('blockquote'));
                } else {
                    parent = parent.lastChild;
                }

                ++indent;

                // expand line to fit new line indent
                line = ltk.string.repeat(' ', (indent * this.tab_width)) +  line.substr(match[0].length);
    /*        } else if (state == 0 && (match = ltk.string.match('/^\+(-+\+)+$/', line))) {
                // table
                line = this.parseTable(parent, fp, line); */
            } else {
                // no parser rule match -- just add line to textbuffer for later inline parsing
                buffer.push(line);

                // eat line
                line = '';
            }
        }
    }

    /****m* ltext/parseInline
     * SYNOPSIS
     */
    ltk.ltext.prototype.parseInline = function(parent, line)
    /*
     * FUNCTION
     *      inline parser, parses:
     *      * emoticons
     *      * references (links)
     *      * footnotes (TODO)
     *      * text formattings
     * INPUTS
     *      * parent (object) -- parent object to add parsed stuff to
     *      * line (string) -- text line
     ****
     */
    {
        var pos, match, e, fix;

        line = normalize(line);
    
        while (line != '') {
            if ((pos = line.search(inline)) >= 0) {
                // process inline formattings
                match = line.match(inline);
                fix   = match[1].length;
                pos  += fix;                    // fix position for emulated lookbehind
            
                if (pos > 0) {
                    // text before match
                    parent.appendChild(
                        document.createTextNode(
                            ltk.string.stripslashes(line.substr(0, pos))
                        )
                    );
                }

                pos -= fix;                     // fix position for emulated lookbehind

                line = line.substr(match[0].length + pos);
            
                if (typeof match[9] != 'undefined' && match[9].length > 0) {
                    // url
                    this.url_handler(parent, match[9]);
                } else if (typeof match[7] != 'undefined' && match[8].length > 0) {
                    // inline plugin
                    // TODO: implementation
                } else if (typeof match[5] != 'undefined' && match[6].length > 0) {
                    // smiley
                    e = parent.appendChild(document.createElement('img'));
                    e.src = '/resources_ltk/images/shim.gif';
                    e.setAttribute('alt', match[6]);
                    e.className = 'ltk_emoticon_' + match[6];
                } else if (typeof match[3] != 'undefined' && match[4].length > 0) {
                    // text formatting
                    switch (match[3]) {
                    case '*':
                        e = parent.appendChild(document.createElement('b'))
                        this.parseInline(e, match[4]);
                        break;
                    case '_':
                        e = parent.appendChild(document.createElement('u'));
                        this.parseInline(e, match[4]);
                        break;
                    case '~':
                        e = parent.appendChild(document.createElement('i'));
                        this.parseInline(e, match[4]);
                        break;
                    case '-':
                        e = parent.appendChild(document.createElement('del'));
                        this.parseInline(e, match[4]);
                        break;
                    case '@':
                        e = parent.appendChild(document.createElement('code'));
                        this.parseInline(e, match[4]);
                        break;
                    }
                }
            } else {
                parent.appendChild(document.createTextNode(ltk.string.stripslashes(line)));
                break;
            }
        }
    }

    /****m* ltext/deTab
     * SYNOPSIS
     */
    ltk.ltext.prototype.deTab = function(txt)
    /*
     * FUNCTION
     *      convert tabs to spaces
     * INPUTS
     *      * txt (string) -- text string to deTab
     * OUTPUTS
     *      (string) -- detabbed text string
     ****
     */
    {
        var tmp = txt.split(/\t/);
        var cnt = tmp.length;
    
        if (cnt == 1) {
            return txt;
        }
    
        var row = '';

        for (var i = 0; i < cnt; ++i) {
            row += tmp[i] + ltk.string.repeat(' ', this.tab_width - (tmp[i].length) % this.tab_width);
        }
    
        return ltk.string.rtrim(row);
    }
})();


/*
    http://www.JSON.org/json2.js
    2008-07-15

    Public Domain.

    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

    See http://www.JSON.org/js.html

    This file creates a global JSON object containing two methods: stringify
    and parse.

        JSON.stringify(value, replacer, space)
            value       any JavaScript value, usually an object or array.

            replacer    an optional parameter that determines how object
                        values are stringified for objects. It can be a
                        function or an array.

            space       an optional parameter that specifies the indentation
                        of nested structures. If it is omitted, the text will
                        be packed without extra whitespace. If it is a number,
                        it will specify the number of spaces to indent at each
                        level. If it is a string (such as '\t' or '&nbsp;'),
                        it contains the characters used to indent at each level.

            This method produces a JSON text from a JavaScript value.

            When an object value is found, if the object contains a toJSON
            method, its toJSON method will be called and the result will be
            stringified. A toJSON method does not serialize: it returns the
            value represented by the name/value pair that should be serialized,
            or undefined if nothing should be serialized. The toJSON method
            will be passed the key associated with the value, and this will be
            bound to the object holding the key.

            For example, this would serialize Dates as ISO strings.

                Date.prototype.toJSON = function (key) {
                    function f(n) {
                        // Format integers to have at least two digits.
                        return n < 10 ? '0' + n : n;
                    }

                    return this.getUTCFullYear()   + '-' +
                         f(this.getUTCMonth() + 1) + '-' +
                         f(this.getUTCDate())      + 'T' +
                         f(this.getUTCHours())     + ':' +
                         f(this.getUTCMinutes())   + ':' +
                         f(this.getUTCSeconds())   + 'Z';
                };

            You can provide an optional replacer method. It will be passed the
            key and value of each member, with this bound to the containing
            object. The value that is returned from your method will be
            serialized. If your method returns undefined, then the member will
            be excluded from the serialization.

            If the replacer parameter is an array, then it will be used to
            select the members to be serialized. It filters the results such
            that only members with keys listed in the replacer array are
            stringified.

            Values that do not have JSON representations, such as undefined or
            functions, will not be serialized. Such values in objects will be
            dropped; in arrays they will be replaced with null. You can use
            a replacer function to replace those with JSON values.
            JSON.stringify(undefined) returns undefined.

            The optional space parameter produces a stringification of the
            value that is filled with line breaks and indentation to make it
            easier to read.

            If the space parameter is a non-empty string, then that string will
            be used for indentation. If the space parameter is a number, then
            the indentation will be that many spaces.

            Example:

            text = JSON.stringify(['e', {pluribus: 'unum'}]);
            // text is '["e",{"pluribus":"unum"}]'


            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

            text = JSON.stringify([new Date()], function (key, value) {
                return this[key] instanceof Date ?
                    'Date(' + this[key] + ')' : value;
            });
            // text is '["Date(---current time---)"]'


        JSON.parse(text, reviver)
            This method parses a JSON text to produce an object or array.
            It can throw a SyntaxError exception.

            The optional reviver parameter is a function that can filter and
            transform the results. It receives each of the keys and values,
            and its return value is used instead of the original value.
            If it returns what it received, then the structure is not modified.
            If it returns undefined then the member is deleted.

            Example:

            // Parse the text. Values that look like ISO date strings will
            // be converted to Date objects.

            myData = JSON.parse(text, function (key, value) {
                var a;
                if (typeof value === 'string') {
                    a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
                    if (a) {
                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
                            +a[5], +a[6]));
                    }
                }
                return value;
            });

            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
                var d;
                if (typeof value === 'string' &&
                        value.slice(0, 5) === 'Date(' &&
                        value.slice(-1) === ')') {
                    d = new Date(value.slice(5, -1));
                    if (d) {
                        return d;
                    }
                }
                return value;
            });


    This is a reference implementation. You are free to copy, modify, or
    redistribute.

    This code should be minified before deployment.
    See http://javascript.crockford.com/jsmin.html

    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
    NOT CONTROL.
*/

/*jslint evil: true */

/*global JSON */

/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", call,
    charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, getUTCMinutes,
    getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length,
    parse, propertyIsEnumerable, prototype, push, replace, slice, stringify,
    test, toJSON, toString
*/

if (!this.JSON) {

// Create a JSON object only if one does not already exist. We create the
// object in a closure to avoid creating global variables.

    JSON = function () {

        function f(n) {
            // Format integers to have at least two digits.
            return n < 10 ? '0' + n : n;
        }

        Date.prototype.toJSON = function (key) {

            return this.getUTCFullYear()   + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate())      + 'T' +
                 f(this.getUTCHours())     + ':' +
                 f(this.getUTCMinutes())   + ':' +
                 f(this.getUTCSeconds())   + 'Z';
        };

        String.prototype.toJSON =
        Number.prototype.toJSON =
        Boolean.prototype.toJSON = function (key) {
            return this.valueOf();
        };

        var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
            escapeable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
            gap,
            indent,
            meta = {    // table of character substitutions
                '\b': '\\b',
                '\t': '\\t',
                '\n': '\\n',
                '\f': '\\f',
                '\r': '\\r',
                '"' : '\\"',
                '\\': '\\\\'
            },
            rep;


        function quote(string) {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.

            escapeable.lastIndex = 0;
            return escapeable.test(string) ?
                '"' + string.replace(escapeable, function (a) {
                    var c = meta[a];
                    if (typeof c === 'string') {
                        return c;
                    }
                    return '\\u' + ('0000' +
                            (+(a.charCodeAt(0))).toString(16)).slice(-4);
                }) + '"' :
                '"' + string + '"';
        }


        function str(key, holder) {

// Produce a string from holder[key].

            var i,          // The loop counter.
                k,          // The member key.
                v,          // The member value.
                length,
                mind = gap,
                partial,
                value = holder[key];

// If the value has a toJSON method, call it to obtain a replacement value.

            if (value && typeof value === 'object' &&
                    typeof value.toJSON === 'function') {
                value = value.toJSON(key);
            }

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

            if (typeof rep === 'function') {
                value = rep.call(holder, key, value);
            }

// What happens next depends on the value's type.

            switch (typeof value) {
            case 'string':
                return quote(value);

            case 'number':

// JSON numbers must be finite. Encode non-finite numbers as null.

                return isFinite(value) ? String(value) : 'null';

            case 'boolean':
            case 'null':

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.

                return String(value);

// If the type is 'object', we might be dealing with an object or an array or
// null.

            case 'object':

// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.

                if (!value) {
                    return 'null';
                }

// Make an array to hold the partial results of stringifying this object value.

                gap += indent;
                partial = [];

// If the object has a dontEnum length property, we'll treat it as an array.

                if (typeof value.length === 'number' &&
                        !(value.propertyIsEnumerable('length'))) {

// The object is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                    length = value.length;
                    for (i = 0; i < length; i += 1) {
                        partial[i] = str(i, value) || 'null';
                    }

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

                    v = partial.length === 0 ? '[]' :
                        gap ? '[\n' + gap +
                                partial.join(',\n' + gap) + '\n' +
                                    mind + ']' :
                              '[' + partial.join(',') + ']';
                    gap = mind;
                    return v;
                }

// If the replacer is an array, use it to select the members to be stringified.

                if (rep && typeof rep === 'object') {
                    length = rep.length;
                    for (i = 0; i < length; i += 1) {
                        k = rep[i];
                        if (typeof k === 'string') {
                            v = str(k, value);
                            if (v) {
                                partial.push(quote(k) + (gap ? ': ' : ':') + v);
                            }
                        }
                    }
                } else {

// Otherwise, iterate through all of the keys in the object.

                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = str(k, value);
                            if (v) {
                                partial.push(quote(k) + (gap ? ': ' : ':') + v);
                            }
                        }
                    }
                }

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

                v = partial.length === 0 ? '{}' :
                    gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                            mind + '}' : '{' + partial.join(',') + '}';
                gap = mind;
                return v;
            }
        }

// Return the JSON object containing the stringify and parse methods.

        return {
            stringify: function (value, replacer, space) {

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

                var i;
                gap = '';
                indent = '';

// If the space parameter is a number, make an indent string containing that
// many spaces.

                if (typeof space === 'number') {
                    for (i = 0; i < space; i += 1) {
                        indent += ' ';
                    }

// If the space parameter is a string, it will be used as the indent string.

                } else if (typeof space === 'string') {
                    indent = space;
                }

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

                rep = replacer;
                if (replacer && typeof replacer !== 'function' &&
                        (typeof replacer !== 'object' ||
                         typeof replacer.length !== 'number')) {
                    throw new Error('JSON.stringify');
                }

// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.

                return str('', {'': value});
            },


            parse: function (text, reviver) {

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.

                var j;

                function walk(holder, key) {

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

                    var k, v, value = holder[key];
                    if (value && typeof value === 'object') {
                        for (k in value) {
                            if (Object.hasOwnProperty.call(value, k)) {
                                v = walk(value, k);
                                if (v !== undefined) {
                                    value[k] = v;
                                } else {
                                    delete value[k];
                                }
                            }
                        }
                    }
                    return reviver.call(holder, key, value);
                }


// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

                cx.lastIndex = 0;
                if (cx.test(text)) {
                    text = text.replace(cx, function (a) {
                        return '\\u' + ('0000' +
                                (+(a.charCodeAt(0))).toString(16)).slice(-4);
                    });
                }

// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

                if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                    j = eval('(' + text + ')');

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

                    return typeof reviver === 'function' ?
                        walk({'': j}, '') : j;
                }

// If the text is not JSON parseable, then a SyntaxError is thrown.

                throw new SyntaxError('JSON.parse');
            }
        };
    }();
}

/*!
 * Sizzle CSS Selector Engine - v1.0
 *  Copyright 2009, The Dojo Foundation
 *  Released under the MIT, BSD, and GPL Licenses.
 *  More information: http://sizzlejs.com/
 */
(function(){

var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
    done = 0,
    toString = Object.prototype.toString,
    hasDuplicate = false,
    baseHasDuplicate = true;

// Here we check if the JavaScript engine is using some sort of
// optimization where it does not always call our comparision
// function. If that is the case, discard the hasDuplicate value.
//   Thus far that includes Google Chrome.
[0, 0].sort(function(){
    baseHasDuplicate = false;
    return 0;
});

var Sizzle = function(selector, context, results, seed) {
    results = results || [];
    context = context || document;

    var origContext = context;

    if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
        return [];
    }
    
    if ( !selector || typeof selector !== "string" ) {
        return results;
    }

    var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
        soFar = selector, ret, cur, pop, i;
    
    // Reset the position of the chunker regexp (start from head)
    do {
        chunker.exec("");
        m = chunker.exec(soFar);

        if ( m ) {
            soFar = m[3];
        
            parts.push( m[1] );
        
            if ( m[2] ) {
                extra = m[3];
                break;
            }
        }
    } while ( m );

    if ( parts.length > 1 && origPOS.exec( selector ) ) {
        if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
            set = posProcess( parts[0] + parts[1], context );
        } else {
            set = Expr.relative[ parts[0] ] ?
                [ context ] :
                Sizzle( parts.shift(), context );

            while ( parts.length ) {
                selector = parts.shift();

                if ( Expr.relative[ selector ] ) {
                    selector += parts.shift();
                }
                
                set = posProcess( selector, set );
            }
        }
    } else {
        // Take a shortcut and set the context if the root selector is an ID
        // (but not if it'll be faster if the inner selector is an ID)
        if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
                Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
            ret = Sizzle.find( parts.shift(), context, contextXML );
            context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
        }

        if ( context ) {
            ret = seed ?
                { expr: parts.pop(), set: makeArray(seed) } :
                Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
            set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;

            if ( parts.length > 0 ) {
                checkSet = makeArray(set);
            } else {
                prune = false;
            }

            while ( parts.length ) {
                cur = parts.pop();
                pop = cur;

                if ( !Expr.relative[ cur ] ) {
                    cur = "";
                } else {
                    pop = parts.pop();
                }

                if ( pop == null ) {
                    pop = context;
                }

                Expr.relative[ cur ]( checkSet, pop, contextXML );
            }
        } else {
            checkSet = parts = [];
        }
    }

    if ( !checkSet ) {
        checkSet = set;
    }

    if ( !checkSet ) {
        Sizzle.error( cur || selector );
    }

    if ( toString.call(checkSet) === "[object Array]" ) {
        if ( !prune ) {
            results.push.apply( results, checkSet );
        } else if ( context && context.nodeType === 1 ) {
            for ( i = 0; checkSet[i] != null; i++ ) {
                if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
                    results.push( set[i] );
                }
            }
        } else {
            for ( i = 0; checkSet[i] != null; i++ ) {
                if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
                    results.push( set[i] );
                }
            }
        }
    } else {
        makeArray( checkSet, results );
    }

    if ( extra ) {
        Sizzle( extra, origContext, results, seed );
        Sizzle.uniqueSort( results );
    }

    return results;
};

Sizzle.uniqueSort = function(results){
    if ( sortOrder ) {
        hasDuplicate = baseHasDuplicate;
        results.sort(sortOrder);

        if ( hasDuplicate ) {
            for ( var i = 1; i < results.length; i++ ) {
                if ( results[i] === results[i-1] ) {
                    results.splice(i--, 1);
                }
            }
        }
    }

    return results;
};

Sizzle.matches = function(expr, set){
    return Sizzle(expr, null, null, set);
};

Sizzle.find = function(expr, context, isXML){
    var set;

    if ( !expr ) {
        return [];
    }

    for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
        var type = Expr.order[i], match;
        
        if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
            var left = match[1];
            match.splice(1,1);

            if ( left.substr( left.length - 1 ) !== "\\" ) {
                match[1] = (match[1] || "").replace(/\\/g, "");
                set = Expr.find[ type ]( match, context, isXML );
                if ( set != null ) {
                    expr = expr.replace( Expr.match[ type ], "" );
                    break;
                }
            }
        }
    }

    if ( !set ) {
        set = context.getElementsByTagName("*");
    }

    return {set: set, expr: expr};
};

Sizzle.filter = function(expr, set, inplace, not){
    var old = expr, result = [], curLoop = set, match, anyFound,
        isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);

    while ( expr && set.length ) {
        for ( var type in Expr.filter ) {
            if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
                var filter = Expr.filter[ type ], found, item, left = match[1];
                anyFound = false;

                match.splice(1,1);

                if ( left.substr( left.length - 1 ) === "\\" ) {
                    continue;
                }

                if ( curLoop === result ) {
                    result = [];
                }

                if ( Expr.preFilter[ type ] ) {
                    match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );

                    if ( !match ) {
                        anyFound = found = true;
                    } else if ( match === true ) {
                        continue;
                    }
                }

                if ( match ) {
                    for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
                        if ( item ) {
                            found = filter( item, match, i, curLoop );
                            var pass = not ^ !!found;

                            if ( inplace && found != null ) {
                                if ( pass ) {
                                    anyFound = true;
                                } else {
                                    curLoop[i] = false;
                                }
                            } else if ( pass ) {
                                result.push( item );
                                anyFound = true;
                            }
                        }
                    }
                }

                if ( found !== undefined ) {
                    if ( !inplace ) {
                        curLoop = result;
                    }

                    expr = expr.replace( Expr.match[ type ], "" );

                    if ( !anyFound ) {
                        return [];
                    }

                    break;
                }
            }
        }

        // Improper expression
        if ( expr === old ) {
            if ( anyFound == null ) {
                Sizzle.error( expr );
            } else {
                break;
            }
        }

        old = expr;
    }

    return curLoop;
};

Sizzle.error = function( msg ) {
    throw "Syntax error, unrecognized expression: " + msg;
};

var Expr = Sizzle.selectors = {
    order: [ "ID", "NAME", "TAG" ],
    match: {
        ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
        CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
        NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
        ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
        TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
        CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
        POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
        PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
    },
    leftMatch: {},
    attrMap: {
        "class": "className",
        "for": "htmlFor"
    },
    attrHandle: {
        href: function(elem){
            return elem.getAttribute("href");
        }
    },
    relative: {
        "+": function(checkSet, part){
            var isPartStr = typeof part === "string",
                isTag = isPartStr && !/\W/.test(part),
                isPartStrNotTag = isPartStr && !isTag;

            if ( isTag ) {
                part = part.toLowerCase();
            }

            for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
                if ( (elem = checkSet[i]) ) {
                    while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}

                    checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
                        elem || false :
                        elem === part;
                }
            }

            if ( isPartStrNotTag ) {
                Sizzle.filter( part, checkSet, true );
            }
        },
        ">": function(checkSet, part){
            var isPartStr = typeof part === "string",
                elem, i = 0, l = checkSet.length;

            if ( isPartStr && !/\W/.test(part) ) {
                part = part.toLowerCase();

                for ( ; i < l; i++ ) {
                    elem = checkSet[i];
                    if ( elem ) {
                        var parent = elem.parentNode;
                        checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
                    }
                }
            } else {
                for ( ; i < l; i++ ) {
                    elem = checkSet[i];
                    if ( elem ) {
                        checkSet[i] = isPartStr ?
                            elem.parentNode :
                            elem.parentNode === part;
                    }
                }

                if ( isPartStr ) {
                    Sizzle.filter( part, checkSet, true );
                }
            }
        },
        "": function(checkSet, part, isXML){
            var doneName = done++, checkFn = dirCheck, nodeCheck;

            if ( typeof part === "string" && !/\W/.test(part) ) {
                part = part.toLowerCase();
                nodeCheck = part;
                checkFn = dirNodeCheck;
            }

            checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
        },
        "~": function(checkSet, part, isXML){
            var doneName = done++, checkFn = dirCheck, nodeCheck;

            if ( typeof part === "string" && !/\W/.test(part) ) {
                part = part.toLowerCase();
                nodeCheck = part;
                checkFn = dirNodeCheck;
            }

            checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
        }
    },
    find: {
        ID: function(match, context, isXML){
            if ( typeof context.getElementById !== "undefined" && !isXML ) {
                var m = context.getElementById(match[1]);
                return m ? [m] : [];
            }
        },
        NAME: function(match, context){
            if ( typeof context.getElementsByName !== "undefined" ) {
                var ret = [], results = context.getElementsByName(match[1]);

                for ( var i = 0, l = results.length; i < l; i++ ) {
                    if ( results[i].getAttribute("name") === match[1] ) {
                        ret.push( results[i] );
                    }
                }

                return ret.length === 0 ? null : ret;
            }
        },
        TAG: function(match, context){
            return context.getElementsByTagName(match[1]);
        }
    },
    preFilter: {
        CLASS: function(match, curLoop, inplace, result, not, isXML){
            match = " " + match[1].replace(/\\/g, "") + " ";

            if ( isXML ) {
                return match;
            }

            for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
                if ( elem ) {
                    if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
                        if ( !inplace ) {
                            result.push( elem );
                        }
                    } else if ( inplace ) {
                        curLoop[i] = false;
                    }
                }
            }

            return false;
        },
        ID: function(match){
            return match[1].replace(/\\/g, "");
        },
        TAG: function(match, curLoop){
            return match[1].toLowerCase();
        },
        CHILD: function(match){
            if ( match[1] === "nth" ) {
                // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
                var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
                    match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
                    !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);

                // calculate the numbers (first)n+(last) including if they are negative
                match[2] = (test[1] + (test[2] || 1)) - 0;
                match[3] = test[3] - 0;
            }

            // TODO: Move to normal caching system
            match[0] = done++;

            return match;
        },
        ATTR: function(match, curLoop, inplace, result, not, isXML){
            var name = match[1].replace(/\\/g, "");
            
            if ( !isXML && Expr.attrMap[name] ) {
                match[1] = Expr.attrMap[name];
            }

            if ( match[2] === "~=" ) {
                match[4] = " " + match[4] + " ";
            }

            return match;
        },
        PSEUDO: function(match, curLoop, inplace, result, not){
            if ( match[1] === "not" ) {
                // If we're dealing with a complex expression, or a simple one
                if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
                    match[3] = Sizzle(match[3], null, null, curLoop);
                } else {
                    var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
                    if ( !inplace ) {
                        result.push.apply( result, ret );
                    }
                    return false;
                }
            } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
                return true;
            }
            
            return match;
        },
        POS: function(match){
            match.unshift( true );
            return match;
        }
    },
    filters: {
        enabled: function(elem){
            return elem.disabled === false && elem.type !== "hidden";
        },
        disabled: function(elem){
            return elem.disabled === true;
        },
        checked: function(elem){
            return elem.checked === true;
        },
        selected: function(elem){
            // Accessing this property makes selected-by-default
            // options in Safari work properly
            elem.parentNode.selectedIndex;
            return elem.selected === true;
        },
        parent: function(elem){
            return !!elem.firstChild;
        },
        empty: function(elem){
            return !elem.firstChild;
        },
        has: function(elem, i, match){
            return !!Sizzle( match[3], elem ).length;
        },
        header: function(elem){
            return (/h\d/i).test( elem.nodeName );
        },
        text: function(elem){
            return "text" === elem.type;
        },
        radio: function(elem){
            return "radio" === elem.type;
        },
        checkbox: function(elem){
            return "checkbox" === elem.type;
        },
        file: function(elem){
            return "file" === elem.type;
        },
        password: function(elem){
            return "password" === elem.type;
        },
        submit: function(elem){
            return "submit" === elem.type;
        },
        image: function(elem){
            return "image" === elem.type;
        },
        reset: function(elem){
            return "reset" === elem.type;
        },
        button: function(elem){
            return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
        },
        input: function(elem){
            return (/input|select|textarea|button/i).test(elem.nodeName);
        }
    },
    setFilters: {
        first: function(elem, i){
            return i === 0;
        },
        last: function(elem, i, match, array){
            return i === array.length - 1;
        },
        even: function(elem, i){
            return i % 2 === 0;
        },
        odd: function(elem, i){
            return i % 2 === 1;
        },
        lt: function(elem, i, match){
            return i < match[3] - 0;
        },
        gt: function(elem, i, match){
            return i > match[3] - 0;
        },
        nth: function(elem, i, match){
            return match[3] - 0 === i;
        },
        eq: function(elem, i, match){
            return match[3] - 0 === i;
        }
    },
    filter: {
        PSEUDO: function(elem, match, i, array){
            var name = match[1], filter = Expr.filters[ name ];

            if ( filter ) {
                return filter( elem, i, match, array );
            } else if ( name === "contains" ) {
                return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
            } else if ( name === "not" ) {
                var not = match[3];

                for ( var j = 0, l = not.length; j < l; j++ ) {
                    if ( not[j] === elem ) {
                        return false;
                    }
                }

                return true;
            } else {
                Sizzle.error( "Syntax error, unrecognized expression: " + name );
            }
        },
        CHILD: function(elem, match){
            var type = match[1], node = elem;
            switch (type) {
                case 'only':
                case 'first':
                    while ( (node = node.previousSibling) )  {
                        if ( node.nodeType === 1 ) { 
                            return false; 
                        }
                    }
                    if ( type === "first" ) { 
                        return true; 
                    }
                    node = elem;
                case 'last':
                    while ( (node = node.nextSibling) )  {
                        if ( node.nodeType === 1 ) { 
                            return false; 
                        }
                    }
                    return true;
                case 'nth':
                    var first = match[2], last = match[3];

                    if ( first === 1 && last === 0 ) {
                        return true;
                    }
                    
                    var doneName = match[0],
                        parent = elem.parentNode;
    
                    if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
                        var count = 0;
                        for ( node = parent.firstChild; node; node = node.nextSibling ) {
                            if ( node.nodeType === 1 ) {
                                node.nodeIndex = ++count;
                            }
                        } 
                        parent.sizcache = doneName;
                    }
                    
                    var diff = elem.nodeIndex - last;
                    if ( first === 0 ) {
                        return diff === 0;
                    } else {
                        return ( diff % first === 0 && diff / first >= 0 );
                    }
            }
        },
        ID: function(elem, match){
            return elem.nodeType === 1 && elem.getAttribute("id") === match;
        },
        TAG: function(elem, match){
            return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
        },
        CLASS: function(elem, match){
            return (" " + (elem.className || elem.getAttribute("class")) + " ")
                .indexOf( match ) > -1;
        },
        ATTR: function(elem, match){
            var name = match[1],
                result = Expr.attrHandle[ name ] ?
                    Expr.attrHandle[ name ]( elem ) :
                    elem[ name ] != null ?
                        elem[ name ] :
                        elem.getAttribute( name ),
                value = result + "",
                type = match[2],
                check = match[4];

            return result == null ?
                type === "!=" :
                type === "=" ?
                value === check :
                type === "*=" ?
                value.indexOf(check) >= 0 :
                type === "~=" ?
                (" " + value + " ").indexOf(check) >= 0 :
                !check ?
                value && result !== false :
                type === "!=" ?
                value !== check :
                type === "^=" ?
                value.indexOf(check) === 0 :
                type === "$=" ?
                value.substr(value.length - check.length) === check :
                type === "|=" ?
                value === check || value.substr(0, check.length + 1) === check + "-" :
                false;
        },
        POS: function(elem, match, i, array){
            var name = match[2], filter = Expr.setFilters[ name ];

            if ( filter ) {
                return filter( elem, i, match, array );
            }
        }
    }
};

var origPOS = Expr.match.POS,
    fescape = function(all, num){
        return "\\" + (num - 0 + 1);
    };

for ( var type in Expr.match ) {
    Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
    Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
}

var makeArray = function(array, results) {
    array = Array.prototype.slice.call( array, 0 );

    if ( results ) {
        results.push.apply( results, array );
        return results;
    }
    
    return array;
};

// Perform a simple check to determine if the browser is capable of
// converting a NodeList to an array using builtin methods.
// Also verifies that the returned array holds DOM nodes
// (which is not the case in the Blackberry browser)
try {
    Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;

// Provide a fallback method if it does not work
} catch(e){
    makeArray = function(array, results) {
        var ret = results || [], i = 0;

        if ( toString.call(array) === "[object Array]" ) {
            Array.prototype.push.apply( ret, array );
        } else {
            if ( typeof array.length === "number" ) {
                for ( var l = array.length; i < l; i++ ) {
                    ret.push( array[i] );
                }
            } else {
                for ( ; array[i]; i++ ) {
                    ret.push( array[i] );
                }
            }
        }

        return ret;
    };
}

var sortOrder;

if ( document.documentElement.compareDocumentPosition ) {
    sortOrder = function( a, b ) {
        if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
            if ( a == b ) {
                hasDuplicate = true;
            }
            return a.compareDocumentPosition ? -1 : 1;
        }

        var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
        if ( ret === 0 ) {
            hasDuplicate = true;
        }
        return ret;
    };
} else if ( "sourceIndex" in document.documentElement ) {
    sortOrder = function( a, b ) {
        if ( !a.sourceIndex || !b.sourceIndex ) {
            if ( a == b ) {
                hasDuplicate = true;
            }
            return a.sourceIndex ? -1 : 1;
        }

        var ret = a.sourceIndex - b.sourceIndex;
        if ( ret === 0 ) {
            hasDuplicate = true;
        }
        return ret;
    };
} else if ( document.createRange ) {
    sortOrder = function( a, b ) {
        if ( !a.ownerDocument || !b.ownerDocument ) {
            if ( a == b ) {
                hasDuplicate = true;
            }
            return a.ownerDocument ? -1 : 1;
        }

        var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
        aRange.setStart(a, 0);
        aRange.setEnd(a, 0);
        bRange.setStart(b, 0);
        bRange.setEnd(b, 0);
        var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
        if ( ret === 0 ) {
            hasDuplicate = true;
        }
        return ret;
    };
}

// Utility function for retreiving the text value of an array of DOM nodes
Sizzle.getText = function( elems ) {
    var ret = "", elem;

    for ( var i = 0; elems[i]; i++ ) {
        elem = elems[i];

        // Get the text from text nodes and CDATA nodes
        if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
            ret += elem.nodeValue;

        // Traverse everything else, except comment nodes
        } else if ( elem.nodeType !== 8 ) {
            ret += Sizzle.getText( elem.childNodes );
        }
    }

    return ret;
};

// Check to see if the browser returns elements by name when
// querying by getElementById (and provide a workaround)
(function(){
    // We're going to inject a fake input element with a specified name
    var form = document.createElement("div"),
        id = "script" + (new Date()).getTime();
    form.innerHTML = "<a name='" + id + "'/>";

    // Inject it into the root element, check its status, and remove it quickly
    var root = document.documentElement;
    root.insertBefore( form, root.firstChild );

    // The workaround has to do additional checks after a getElementById
    // Which slows things down for other browsers (hence the branching)
    if ( document.getElementById( id ) ) {
        Expr.find.ID = function(match, context, isXML){
            if ( typeof context.getElementById !== "undefined" && !isXML ) {
                var m = context.getElementById(match[1]);
                return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
            }
        };

        Expr.filter.ID = function(elem, match){
            var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
            return elem.nodeType === 1 && node && node.nodeValue === match;
        };
    }

    root.removeChild( form );
    root = form = null; // release memory in IE
})();

(function(){
    // Check to see if the browser returns only elements
    // when doing getElementsByTagName("*")

    // Create a fake element
    var div = document.createElement("div");
    div.appendChild( document.createComment("") );

    // Make sure no comments are found
    if ( div.getElementsByTagName("*").length > 0 ) {
        Expr.find.TAG = function(match, context){
            var results = context.getElementsByTagName(match[1]);

            // Filter out possible comments
            if ( match[1] === "*" ) {
                var tmp = [];

                for ( var i = 0; results[i]; i++ ) {
                    if ( results[i].nodeType === 1 ) {
                        tmp.push( results[i] );
                    }
                }

                results = tmp;
            }

            return results;
        };
    }

    // Check to see if an attribute returns normalized href attributes
    div.innerHTML = "<a href='#'></a>";
    if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
            div.firstChild.getAttribute("href") !== "#" ) {
        Expr.attrHandle.href = function(elem){
            return elem.getAttribute("href", 2);
        };
    }

    div = null; // release memory in IE
})();

if ( document.querySelectorAll ) {
    (function(){
        var oldSizzle = Sizzle, div = document.createElement("div");
        div.innerHTML = "<p class='TEST'></p>";

        // Safari can't handle uppercase or unicode characters when
        // in quirks mode.
        if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
            return;
        }
    
        Sizzle = function(query, context, extra, seed){
            context = context || document;

            // Only use querySelectorAll on non-XML documents
            // (ID selectors don't work in non-HTML documents)
            if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {
                try {
                    return makeArray( context.querySelectorAll(query), extra );
                } catch(e){}
            }
        
            return oldSizzle(query, context, extra, seed);
        };

        for ( var prop in oldSizzle ) {
            Sizzle[ prop ] = oldSizzle[ prop ];
        }

        div = null; // release memory in IE
    })();
}

(function(){
    var div = document.createElement("div");

    div.innerHTML = "<div class='test e'></div><div class='test'></div>";

    // Opera can't find a second classname (in 9.6)
    // Also, make sure that getElementsByClassName actually exists
    if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
        return;
    }

    // Safari caches class attributes, doesn't catch changes (in 3.2)
    div.lastChild.className = "e";

    if ( div.getElementsByClassName("e").length === 1 ) {
        return;
    }
    
    Expr.order.splice(1, 0, "CLASS");
    Expr.find.CLASS = function(match, context, isXML) {
        if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
            return context.getElementsByClassName(match[1]);
        }
    };

    div = null; // release memory in IE
})();

function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
    for ( var i = 0, l = checkSet.length; i < l; i++ ) {
        var elem = checkSet[i];
        if ( elem ) {
            elem = elem[dir];
            var match = false;

            while ( elem ) {
                if ( elem.sizcache === doneName ) {
                    match = checkSet[elem.sizset];
                    break;
                }

                if ( elem.nodeType === 1 && !isXML ){
                    elem.sizcache = doneName;
                    elem.sizset = i;
                }

                if ( elem.nodeName.toLowerCase() === cur ) {
                    match = elem;
                    break;
                }

                elem = elem[dir];
            }

            checkSet[i] = match;
        }
    }
}

function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
    for ( var i = 0, l = checkSet.length; i < l; i++ ) {
        var elem = checkSet[i];
        if ( elem ) {
            elem = elem[dir];
            var match = false;

            while ( elem ) {
                if ( elem.sizcache === doneName ) {
                    match = checkSet[elem.sizset];
                    break;
                }

                if ( elem.nodeType === 1 ) {
                    if ( !isXML ) {
                        elem.sizcache = doneName;
                        elem.sizset = i;
                    }
                    if ( typeof cur !== "string" ) {
                        if ( elem === cur ) {
                            match = true;
                            break;
                        }

                    } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
                        match = elem;
                        break;
                    }
                }

                elem = elem[dir];
            }

            checkSet[i] = match;
        }
    }
}

Sizzle.contains = document.compareDocumentPosition ? function(a, b){
    return !!(a.compareDocumentPosition(b) & 16);
} : function(a, b){
    return a !== b && (a.contains ? a.contains(b) : true);
};

Sizzle.isXML = function(elem){
    // documentElement is verified for cases where it doesn't yet exist
    // (such as loading iframes in IE - #4833) 
    var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
    return documentElement ? documentElement.nodeName !== "HTML" : false;
};

var posProcess = function(selector, context){
    var tmpSet = [], later = "", match,
        root = context.nodeType ? [context] : context;

    // Position selectors must be done after the filter
    // And so must :not(positional) so we move all PSEUDOs to the end
    while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
        later += match[0];
        selector = selector.replace( Expr.match.PSEUDO, "" );
    }

    selector = Expr.relative[selector] ? selector + "*" : selector;

    for ( var i = 0, l = root.length; i < l; i++ ) {
        Sizzle( selector, root[i], tmpSet );
    }

    return Sizzle.filter( later, tmpSet );
};

// EXPOSE

window.Sizzle = Sizzle;

})();


/*
    Animator.js 1.1.9
    
    This library is released under the BSD license:

    Copyright (c) 2006, Bernard Sumption. All rights reserved.
    
    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:
    
    Redistributions of source code must retain the above copyright notice, this
    list of conditions and the following disclaimer. Redistributions in binary
    form must reproduce the above copyright notice, this list of conditions and
    the following disclaimer in the documentation and/or other materials
    provided with the distribution. Neither the name BernieCode nor
    the names of its contributors may be used to endorse or promote products
    derived from this software without specific prior written permission. 
    
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
    ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
    OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
    DAMAGE.

*/


// Applies a sequence of numbers between 0 and 1 to a number of subjects
// construct - see setOptions for parameters
function Animator(options) {
    this.setOptions(options);
    var _this = this;
    this.timerDelegate = function(){_this.onTimerEvent()};
    this.subjects = [];
    this.target = 0;
    this.state = 0;
    this.lastTime = null;
};
Animator.prototype = {
    // apply defaults
    setOptions: function(options) {
        this.options = Animator.applyDefaults({
            interval: 20,  // time between animation frames
            duration: 400, // length of animation
            onComplete: function(){},
            onStep: function(){},
            transition: Animator.tx.easeInOut
        }, options);
    },
    // animate from the current state to provided value
    seekTo: function(to) {
        this.seekFromTo(this.state, to);
    },
    // animate from the current state to provided value
    seekFromTo: function(from, to) {
        this.target = Math.max(0, Math.min(1, to));
        this.state = Math.max(0, Math.min(1, from));
        this.lastTime = new Date().getTime();
        if (!this.intervalId) {
            this.intervalId = window.setInterval(this.timerDelegate, this.options.interval);
        }
    },
    // animate from the current state to provided value
    jumpTo: function(to) {
        this.target = this.state = Math.max(0, Math.min(1, to));
        this.propagate();
    },
    // seek to the opposite of the current target
    toggle: function() {
        this.seekTo(1 - this.target);
    },
    // add a function or an object with a method setState(state) that will be called with a number
    // between 0 and 1 on each frame of the animation
    addSubject: function(subject) {
        this.subjects[this.subjects.length] = subject;
        return this;
    },
    // remove all subjects
    clearSubjects: function() {
        this.subjects = [];
    },
    // forward the current state to the animation subjects
    propagate: function() {
        var value = this.options.transition(this.state);
        for (var i=0; i<this.subjects.length; i++) {
            if (this.subjects[i].setState) {
                this.subjects[i].setState(value);
            } else {
                this.subjects[i](value);
            }
        }
    },
    // called once per frame to update the current state
    onTimerEvent: function() {
        var now = new Date().getTime();
        var timePassed = now - this.lastTime;
        this.lastTime = now;
        var movement = (timePassed / this.options.duration) * (this.state < this.target ? 1 : -1);
        if (Math.abs(movement) >= Math.abs(this.state - this.target)) {
            this.state = this.target;
        } else {
            this.state += movement;
        }
        
        try {
            this.propagate();
        } finally {
            this.options.onStep.call(this);
            if (this.target == this.state) {
                window.clearInterval(this.intervalId);
                this.intervalId = null;
                this.options.onComplete.call(this);
            }
        }
    },
    // shortcuts
    play: function() {this.seekFromTo(0, 1)},
    reverse: function() {this.seekFromTo(1, 0)},
    // return a string describing this Animator, for debugging
    inspect: function() {
        var str = "#<Animator:\n";
        for (var i=0; i<this.subjects.length; i++) {
            str += this.subjects[i].inspect();
        }
        str += ">";
        return str;
    }
}
// merge the properties of two objects
Animator.applyDefaults = function(defaults, prefs) {
    prefs = prefs || {};
    var prop, result = {};
    for (prop in defaults) result[prop] = prefs[prop] !== undefined ? prefs[prop] : defaults[prop];
    return result;
}
// make an array from any object
Animator.makeArray = function(o) {
    if (o == null) return [];
    if (!o.length) return [o];
    var result = [];
    for (var i=0; i<o.length; i++) result[i] = o[i];
    return result;
}
// convert a dash-delimited-property to a camelCaseProperty (c/o Prototype, thanks Sam!)
Animator.camelize = function(string) {
    var oStringList = string.split('-');
    if (oStringList.length == 1) return oStringList[0];
    
    var camelizedString = string.indexOf('-') == 0
        ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
        : oStringList[0];
    
    for (var i = 1, len = oStringList.length; i < len; i++) {
        var s = oStringList[i];
        camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
    }
    return camelizedString;
}
// syntactic sugar for creating CSSStyleSubjects
Animator.apply = function(el, style, options) {
    if (style instanceof Array) {
        return new Animator(options).addSubject(new CSSStyleSubject(el, style[0], style[1]));
    }
    return new Animator(options).addSubject(new CSSStyleSubject(el, style));
}
// make a transition function that gradually accelerates. pass a=1 for smooth
// gravitational acceleration, higher values for an exaggerated effect
Animator.makeEaseIn = function(a) {
    return function(state) {
        return Math.pow(state, a*2); 
    }
}
// as makeEaseIn but for deceleration
Animator.makeEaseOut = function(a) {
    return function(state) {
        return 1 - Math.pow(1 - state, a*2); 
    }
}
// make a transition function that, like an object with momentum being attracted to a point,
// goes past the target then returns
Animator.makeElastic = function(bounces) {
    return function(state) {
        state = Animator.tx.easeInOut(state);
        return ((1-Math.cos(state * Math.PI * bounces)) * (1 - state)) + state; 
    }
}
// make an Attack Decay Sustain Release envelope that starts and finishes on the same level
// 
Animator.makeADSR = function(attackEnd, decayEnd, sustainEnd, sustainLevel) {
    if (sustainLevel == null) sustainLevel = 0.5;
    return function(state) {
        if (state < attackEnd) {
            return state / attackEnd;
        }
        if (state < decayEnd) {
            return 1 - ((state - attackEnd) / (decayEnd - attackEnd) * (1 - sustainLevel));
        }
        if (state < sustainEnd) {
            return sustainLevel;
        }
        return sustainLevel * (1 - ((state - sustainEnd) / (1 - sustainEnd)));
    }
}
// make a transition function that, like a ball falling to floor, reaches the target and/
// bounces back again
Animator.makeBounce = function(bounces) {
    var fn = Animator.makeElastic(bounces);
    return function(state) {
        state = fn(state); 
        return state <= 1 ? state : 2-state;
    }
}
 
// pre-made transition functions to use with the 'transition' option
Animator.tx = {
    easeInOut: function(pos){
        return ((-Math.cos(pos*Math.PI)/2) + 0.5);
    },
    linear: function(x) {
        return x;
    },
    easeIn: Animator.makeEaseIn(1.5),
    easeOut: Animator.makeEaseOut(1.5),
    strongEaseIn: Animator.makeEaseIn(2.5),
    strongEaseOut: Animator.makeEaseOut(2.5),
    elastic: Animator.makeElastic(1),
    veryElastic: Animator.makeElastic(3),
    bouncy: Animator.makeBounce(1),
    veryBouncy: Animator.makeBounce(3)
}

// animates a pixel-based style property between two integer values
function NumericalStyleSubject(els, property, from, to, units) {
    this.els = Animator.makeArray(els);
    if (property == 'opacity' && window.ActiveXObject) {
        this.property = 'filter';
    } else {
        this.property = Animator.camelize(property);
    }
    this.from = parseFloat(from);
    this.to = parseFloat(to);
    this.units = units != null ? units : 'px';
}
NumericalStyleSubject.prototype = {
    setState: function(state) {
        var style = this.getStyle(state);
        var visibility = (this.property == 'opacity' && state == 0) ? 'hidden' : '';
        var j=0;
        for (var i=0; i<this.els.length; i++) {
            try {
                this.els[i].style[this.property] = style;
            } catch (e) {
                // ignore fontWeight - intermediate numerical values cause exeptions in firefox
                if (this.property != 'fontWeight') throw e;
            }
            if (j++ > 20) return;
        }
    },
    getStyle: function(state) {
        state = this.from + ((this.to - this.from) * state);
        if (this.property == 'filter') return "alpha(opacity=" + Math.round(state*100) + ")";
        if (this.property == 'opacity') return state;
        return Math.round(state) + this.units;
    },
    inspect: function() {
        return "\t" + this.property + "(" + this.from + this.units + " to " + this.to + this.units + ")\n";
    }
}

// animates a colour based style property between two hex values
function ColorStyleSubject(els, property, from, to) {
    this.els = Animator.makeArray(els);
    this.property = Animator.camelize(property);
    this.to = this.expandColor(to);
    this.from = this.expandColor(from);
    this.origFrom = from;
    this.origTo = to;
}

ColorStyleSubject.prototype = {
    // parse "#FFFF00" to [256, 256, 0]
    expandColor: function(color) {
        var hexColor, red, green, blue;
        hexColor = ColorStyleSubject.parseColor(color);
        if (hexColor) {
            red = parseInt(hexColor.slice(1, 3), 16);
            green = parseInt(hexColor.slice(3, 5), 16);
            blue = parseInt(hexColor.slice(5, 7), 16);
            return [red,green,blue]
        }
        if (window.DEBUG) {
            alert("Invalid colour: '" + color + "'");
        }
    },
    getValueForState: function(color, state) {
        return Math.round(this.from[color] + ((this.to[color] - this.from[color]) * state));
    },
    setState: function(state) {
        var color = '#'
                + ColorStyleSubject.toColorPart(this.getValueForState(0, state))
                + ColorStyleSubject.toColorPart(this.getValueForState(1, state))
                + ColorStyleSubject.toColorPart(this.getValueForState(2, state));
        for (var i=0; i<this.els.length; i++) {
            this.els[i].style[this.property] = color;
        }
    },
    inspect: function() {
        return "\t" + this.property + "(" + this.origFrom + " to " + this.origTo + ")\n";
    }
}

// return a properly formatted 6-digit hex colour spec, or false
ColorStyleSubject.parseColor = function(string) {
    var color = '#', match;
    if(match = ColorStyleSubject.parseColor.rgbRe.exec(string)) {
        var part;
        for (var i=1; i<=3; i++) {
            part = Math.max(0, Math.min(255, parseInt(match[i])));
            color += ColorStyleSubject.toColorPart(part);
        }
        return color;
    }
    if (match = ColorStyleSubject.parseColor.hexRe.exec(string)) {
        if(match[1].length == 3) {
            for (var i=0; i<3; i++) {
                color += match[1].charAt(i) + match[1].charAt(i);
            }
            return color;
        }
        return '#' + match[1];
    }
    return false;
}
// convert a number to a 2 digit hex string
ColorStyleSubject.toColorPart = function(number) {
    if (number > 255) number = 255;
    var digits = number.toString(16);
    if (number < 16) return '0' + digits;
    return digits;
}
ColorStyleSubject.parseColor.rgbRe = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i;
ColorStyleSubject.parseColor.hexRe = /^\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;

// Animates discrete styles, i.e. ones that do not scale but have discrete values
// that can't be interpolated
function DiscreteStyleSubject(els, property, from, to, threshold) {
    this.els = Animator.makeArray(els);
    this.property = Animator.camelize(property);
    this.from = from;
    this.to = to;
    this.threshold = threshold || 0.5;
}

DiscreteStyleSubject.prototype = {
    setState: function(state) {
        var j=0;
        for (var i=0; i<this.els.length; i++) {
            this.els[i].style[this.property] = state <= this.threshold ? this.from : this.to; 
        }
    },
    inspect: function() {
        return "\t" + this.property + "(" + this.from + " to " + this.to + " @ " + this.threshold + ")\n";
    }
}

// animates between two styles defined using CSS.
// if style1 and style2 are present, animate between them, if only style1
// is present, animate between the element's current style and style1
function CSSStyleSubject(els, style1, style2) {
    els = Animator.makeArray(els);
    this.subjects = [];
    if (els.length == 0) return;
    var prop, toStyle, fromStyle;
    if (style2) {
        fromStyle = this.parseStyle(style1, els[0]);
        toStyle = this.parseStyle(style2, els[0]);
    } else {
        toStyle = this.parseStyle(style1, els[0]);
        fromStyle = {};
        for (prop in toStyle) {
            fromStyle[prop] = CSSStyleSubject.getStyle(els[0], prop);
        }
    }
    // remove unchanging properties
    var prop;
    for (prop in fromStyle) {
        if (fromStyle[prop] == toStyle[prop]) {
            delete fromStyle[prop];
            delete toStyle[prop];
        }
    }
    // discover the type (numerical or colour) of each style
    var prop, units, match, type, from, to;
    for (prop in fromStyle) {
        var fromProp = String(fromStyle[prop]);
        var toProp = String(toStyle[prop]);
        if (toStyle[prop] == null) {
            if (window.DEBUG) alert("No to style provided for '" + prop + '"');
            continue;
        }
        
        if (from = ColorStyleSubject.parseColor(fromProp)) {
            to = ColorStyleSubject.parseColor(toProp);
            type = ColorStyleSubject;
        } else if (fromProp.match(CSSStyleSubject.numericalRe)
                && toProp.match(CSSStyleSubject.numericalRe)) {
            from = parseFloat(fromProp);
            to = parseFloat(toProp);
            type = NumericalStyleSubject;
            match = CSSStyleSubject.numericalRe.exec(fromProp);
            var reResult = CSSStyleSubject.numericalRe.exec(toProp);
            if (match[1] != null) {
                units = match[1];
            } else if (reResult[1] != null) {
                units = reResult[1];
            } else {
                units = reResult;
            }
        } else if (fromProp.match(CSSStyleSubject.discreteRe)
                && toProp.match(CSSStyleSubject.discreteRe)) {
            from = fromProp;
            to = toProp;
            type = DiscreteStyleSubject;
            units = 0;   // hack - how to get an animator option down to here
        } else {
            if (window.DEBUG) {
                alert("Unrecognised format for value of "
                    + prop + ": '" + fromStyle[prop] + "'");
            }
            continue;
        }
        this.subjects[this.subjects.length] = new type(els, prop, from, to, units);
    }
}

CSSStyleSubject.prototype = {
    // parses "width: 400px; color: #FFBB2E" to {width: "400px", color: "#FFBB2E"}
    parseStyle: function(style, el) {
        var rtn = {};
        // if style is a rule set
        if (style.indexOf(":") != -1) {
            var styles = style.split(";");
            for (var i=0; i<styles.length; i++) {
                var parts = CSSStyleSubject.ruleRe.exec(styles[i]);
                if (parts) {
                    rtn[parts[1]] = parts[2];
                }
            }
        }
        // else assume style is a class name
        else {
            var prop, value, oldClass;
            oldClass = el.className;
            el.className = style;
            for (var i=0; i<CSSStyleSubject.cssProperties.length; i++) {
                prop = CSSStyleSubject.cssProperties[i];
                value = CSSStyleSubject.getStyle(el, prop);
                if (value != null) {
                    rtn[prop] = value;
                }
            }
            el.className = oldClass;
        }
        return rtn;
        
    },
    setState: function(state) {
        for (var i=0; i<this.subjects.length; i++) {
            this.subjects[i].setState(state);
        }
    },
    inspect: function() {
        var str = "";
        for (var i=0; i<this.subjects.length; i++) {
            str += this.subjects[i].inspect();
        }
        return str;
    }
}
// get the current value of a css property, 
CSSStyleSubject.getStyle = function(el, property){
    var style;
    if(document.defaultView && document.defaultView.getComputedStyle){
        style = document.defaultView.getComputedStyle(el, "").getPropertyValue(property);
        if (style) {
            return style;
        }
    }
    property = Animator.camelize(property);
    if(el.currentStyle){
        style = el.currentStyle[property];
    }
    return style || el.style[property]
}


CSSStyleSubject.ruleRe = /^\s*([a-zA-Z\-]+)\s*:\s*(\S(.+\S)?)\s*$/;
CSSStyleSubject.numericalRe = /^-?\d+(?:\.\d+)?(%|[a-zA-Z]{2})?$/;
CSSStyleSubject.discreteRe = /^\w+$/;

// required because the style object of elements isn't enumerable in Safari
/*
CSSStyleSubject.cssProperties = ['background-color','border','border-color','border-spacing',
'border-style','border-top','border-right','border-bottom','border-left','border-top-color',
'border-right-color','border-bottom-color','border-left-color','border-top-width','border-right-width',
'border-bottom-width','border-left-width','border-width','bottom','color','font-size','font-size-adjust',
'font-stretch','font-style','height','left','letter-spacing','line-height','margin','margin-top',
'margin-right','margin-bottom','margin-left','marker-offset','max-height','max-width','min-height',
'min-width','orphans','outline','outline-color','outline-style','outline-width','overflow','padding',
'padding-top','padding-right','padding-bottom','padding-left','quotes','right','size','text-indent',
'top','width','word-spacing','z-index','opacity','outline-offset'];*/


CSSStyleSubject.cssProperties = ['azimuth','background','background-attachment','background-color','background-image','background-position','background-repeat','border-collapse','border-color','border-spacing','border-style','border-top','border-top-color','border-right-color','border-bottom-color','border-left-color','border-top-style','border-right-style','border-bottom-style','border-left-style','border-top-width','border-right-width','border-bottom-width','border-left-width','border-width','bottom','clear','clip','color','content','cursor','direction','display','elevation','empty-cells','css-float','font','font-family','font-size','font-size-adjust','font-stretch','font-style','font-variant','font-weight','height','left','letter-spacing','line-height','list-style','list-style-image','list-style-position','list-style-type','margin','margin-top','margin-right','margin-bottom','margin-left','max-height','max-width','min-height','min-width','orphans','outline','outline-color','outline-style','outline-width','overflow','padding','padding-top','padding-right','padding-bottom','padding-left','pause','position','right','size','table-layout','text-align','text-decoration','text-indent','text-shadow','text-transform','top','vertical-align','visibility','white-space','width','word-spacing','z-index','opacity','outline-offset','overflow-x','overflow-y'];


// chains several Animator objects together
function AnimatorChain(animators, options) {
    this.animators = animators;
    this.setOptions(options);
    for (var i=0; i<this.animators.length; i++) {
        this.listenTo(this.animators[i]);
    }
    this.forwards = false;
    this.current = 0;
}

AnimatorChain.prototype = {
    // apply defaults
    setOptions: function(options) {
        this.options = Animator.applyDefaults({
            // by default, each call to AnimatorChain.play() calls jumpTo(0) of each animator
            // before playing, which can cause flickering if you have multiple animators all
            // targeting the same element. Set this to false to avoid this.
            resetOnPlay: true
        }, options);
    },
    // play each animator in turn
    play: function() {
        this.forwards = true;
        this.current = -1;
        if (this.options.resetOnPlay) {
            for (var i=0; i<this.animators.length; i++) {
                this.animators[i].jumpTo(0);
            }
        }
        this.advance();
    },
    // play all animators backwards
    reverse: function() {
        this.forwards = false;
        this.current = this.animators.length;
        if (this.options.resetOnPlay) {
            for (var i=0; i<this.animators.length; i++) {
                this.animators[i].jumpTo(1);
            }
        }
        this.advance();
    },
    // if we have just play()'d, then call reverse(), and vice versa
    toggle: function() {
        if (this.forwards) {
            this.seekTo(0);
        } else {
            this.seekTo(1);
        }
    },
    // internal: install an event listener on an animator's onComplete option
    // to trigger the next animator
    listenTo: function(animator) {
        var oldOnComplete = animator.options.onComplete;
        var _this = this;
        animator.options.onComplete = function() {
            if (oldOnComplete) oldOnComplete.call(animator);
            _this.advance();
        }
    },
    // play the next animator
    advance: function() {
        if (this.forwards) {
            if (this.animators[this.current + 1] == null) return;
            this.current++;
            this.animators[this.current].play();
        } else {
            if (this.animators[this.current - 1] == null) return;
            this.current--;
            this.animators[this.current].reverse();
        }
    },
    // this function is provided for drop-in compatibility with Animator objects,
    // but only accepts 0 and 1 as target values
    seekTo: function(target) {
        if (target <= 0) {
            this.forwards = false;
            this.animators[this.current].seekTo(0);
        } else {
            this.forwards = true;
            this.animators[this.current].seekTo(1);
        }
    }
}

// an Accordion is a class that creates and controls a number of Animators. An array of elements is passed in,
// and for each element an Animator and a activator button is created. When an Animator's activator button is
// clicked, the Animator and all before it seek to 0, and all Animators after it seek to 1. This can be used to
// create the classic Accordion effect, hence the name.
// see setOptions for arguments
function Accordion(options) {
    this.setOptions(options);
    var selected = this.options.initialSection, current;
    if (this.options.rememberance) {
        current = document.location.hash.substring(1);
    }
    this.rememberanceTexts = [];
    this.ans = [];
    var _this = this;
    for (var i=0; i<this.options.sections.length; i++) {
        var el = this.options.sections[i];
        var an = new Animator(this.options.animatorOptions);
        var from = this.options.from + (this.options.shift * i);
        var to = this.options.to + (this.options.shift * i);
        an.addSubject(new NumericalStyleSubject(el, this.options.property, from, to, this.options.units));
        an.jumpTo(0);
        var activator = this.options.getActivator(el);
        activator.index = i;
        activator.onclick = function(){_this.show(this.index)};
        this.ans[this.ans.length] = an;
        this.rememberanceTexts[i] = activator.innerHTML.replace(/\s/g, "");
        if (this.rememberanceTexts[i] === current) {
            selected = i;
        }
    }
    this.show(selected);
}

Accordion.prototype = {
    // apply defaults
    setOptions: function(options) {
        this.options = Object.extend({
            // REQUIRED: an array of elements to use as the accordion sections
            sections: null,
            // a function that locates an activator button element given a section element.
            // by default it takes a button id from the section's "activator" attibute
            getActivator: function(el) {return document.getElementById(el.getAttribute("activator"))},
            // shifts each animator's range, for example with options {from:0,to:100,shift:20}
            // the animators' ranges will be 0-100, 20-120, 40-140 etc.
            shift: 0,
            // the first page to show
            initialSection: 0,
            // if set to true, document.location.hash will be used to preserve the open section across page reloads 
            rememberance: true,
            // constructor arguments to the Animator objects
            animatorOptions: {}
        }, options || {});
    },
    show: function(section) {
        for (var i=0; i<this.ans.length; i++) {
            this.ans[i].seekTo(i > section ? 1 : 0);
        }
        if (this.options.rememberance) {
            document.location.hash = this.rememberanceTexts[section];
        }
    }
}


/****c* ltk/widget
 * NAME
 *      lima_ltk_widget
 * FUNCTION
 *      base ltk widget
 * COPYRIGHT
 *      copyright (c) 2008-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('widget' in ltk) return;
    
    /****m* widget/
     * SYNOPSIS
     */
    ltk.widget = function() 
    /*
     * FUNCTION
     *      widget constructor
     ****
     */
    {
        this.disabled = false;
        this.bgcolor  = null;
    
        this.children = [];
        this.options  = {};
    }

    /****v* widget/registy
     * SYNOPSIS
     */
    ltk.widget.registry = {};
    /*
     * FUNCTION
     *      widget registry
     ****
     */

    /****f* widget/register
     * SYNOPSIS
     */
    ltk.widget.register = function(classname, obj)
    /*
     * FUNCTION
     *      register a widget identified by a classname
     * INPUTS
     *      * classname (string) -- optional namespace + ':' + name of class a widget is registered with
     *      * obj (object) -- instance of the widget
     ****
     */
    {
        var dissected = ltk.widget.dissectTag(classname);
        var ns   = dissected.ns;
        var type = dissected.type;
    
        if (!(ns in ltk.widget.registry)) {
            ltk.widget.registry[ns] = {};
        }
    
        if (!(type in ltk.widget.registry[ns])) {
            ltk.widget.registry[ns][type] = obj;
        } else {
            throw 'widget was registered before!';
        }
    }

    /****m* widget/isRegistered
     * SYNOPSIS
     */
    ltk.widget.isRegistered = function(classname)
    /*
     * FUNCTION
     *      check if a widget is registered
     * INPUTS
     *      * classname (string) -- optional namespace + ':' + name of class a widget is registered with
     * OUTPUTS
     *      (mixed) -- returns either object instance or false
     ****
     */
    {
        var dissected = ltk.widget.dissectTag(classname);
        var ns        = dissected.ns;
        var type      = dissected.type;

        return (ns in ltk.widget.registry && type in ltk.widget.registry[ns]
                ? ltk.widget.registry[ns][type]
                : false);
    }

    /****m* widget/dissectTag
     * SYNOPSIS
     */
    ltk.widget.dissectTag = function(tag)
    /*
     * FUNCTION
     *      dissect tag into namespace and widget type
     * INPUTS
     *      * tag (string) -- tag to dissect
     * OUTPUTS
     *      (object) -- {'ns': ..., 'type': ...}
     ****
     */
    {
        var dissected = {'ns': 'ltk', 'type': tag};
        var pos;
    
        if ((pos = tag.indexOf(':')) >= 0) {
            dissected.ns   = tag.substr(0, pos);
            dissected.type = tag.substr(pos + 1);
        }
    
        return dissected;
    }

    /****v* widget/container
     * SYNOPSIS
     */
    ltk.widget.prototype.container = 'DIV';
    /*
     * FUNCTION
     *      html tag, that will be created as widget container
     ****
     */

    /****v* widget/cssclass
     * SYNOPSIS
     */
    ltk.widget.prototype.cssclass = 'ltk_widget'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****v* widget/widget
     * SYNOPSIS
     */
    ltk.widget.prototype.widget = null;
    /*
     * FUNCTION
     *      widget container DOM node
     ****
     */

    /****v* widget/name
     * SYNOPSIS
     */
    ltk.widget.prototype.name = '';
    /*
     * FUNCTION
     *      name of widget
     ****
     */

    /****m* widget/destruct
     * SYNOPSIS
     */
    ltk.widget.prototype.destruct = function()
    /*
     * FUNCTION
     *      destructor called when widget is destroyed using destroy method. can be 
     *      used to clean up additional stuff when destroying widget
     ****
     */
    {
    }

    /****m* widget/dialog
     * SYNOPSIS
     */
    ltk.widget.prototype.getDialog = function()
    /*
     * FUNCTION
     *      return instance of dialog the widget is bound to, or false if the dialog instance could not be determined.
     *      this method will be overwritten when creating new widget instance from widget.build method.
     ****
     */
    {
        return false;
    }

    /****m* widget/getNode
     * SYNOPSIS
     */
    ltk.widget.prototype.getNode = function()
    /*
     * FUNCTION
     *      return node of widget
     * OUTPUTS
     *      (ltk.dom.node) -- node of widget
     ****
     */
    {
        return this.widget;
    }

    /****m* widget/referenceable
     * SYNOPSIS
     */
    ltk.widget.prototype.referenceable = function()
    /*
     * FUNCTION
     *      check whether a widget has a name assigned. if yes, it can be referenced from a dialog by using 
     *      the name
     ****
     */
    {
        return (this.widget.getAttribute('name') || this.widget.getAttribute('id'));
    }

    /****m* widget/addEvent
     * SYNOPSIS
     */
    ltk.widget.prototype.addEvent = function(type, cb)
    /*
     * FUNCTION
     *      add an event handler for a widget
     * INPUTS
     *      * type (string) -- type of event to attach
     *      * cb (callback) -- callback to execute, if event is triggered
     ****
     */
    {
        if (typeof this['on' + type] != 'undefined') {
            // attach to custom event of widget
            var vec = this['on' + type];
            var me  = this;
        
            this['on' + type] = function() {
                var args = [].splice.call(arguments, 0);
            
                cb.apply(me, args);
                vec.apply(me, args);
            }
        } else {
            // DOM event
            ltk.evt.addEvent(this.widget, type, cb);
        }
    }

    /****m* widget/removeEvent
     * SYNOPSIS
     */
    ltk.widget.prototype.removeEvent = function()
    /*
     * FUNCTION
     *      remove an event handler for a widget
     ****
     */
    {
    }

    /****m* widget/getValue
     * SYNOPSIS
     */
    ltk.widget.prototype.getValue = function()
    /*
     * FUNCTION
     *      return value or array of selected value(s), if widget represents an editable form element
     ****
     */
    {
        return null;
    }

    /****m* widget/getSpinner
     * SYNOPSIS
     */
    ltk.widget.prototype.getSpinner = function()
    /*
     * FUNCTION
     *
     * INPUTS
     *
     * OUTPUTS
     *
     ****
     */
    {
        var spinner = null;
        var me      = this;

        this.getSpinner = (function() {
            spinner = new ltk.spinner();
            spinner.attach(me.widget, {});

            return function() {
                return spinner;
            }
        })();

        return spinner;
    }

    /****m* widget/processChildren
     * SYNOPSIS
     */
    ltk.widget.prototype.processChildren = function(def, processor, parent)
    /*
     * FUNCTION
     *      initiate building of child widgets
     * INPUTS
     *      * children (array) -- child widgets
     *      * processor (callback) -- callback method for processing child widgets
     *      * parent (object) -- (optional) parent DOM node to use instead of widget
     ****
     */
    {
        var me = this;
    
        var _process = function(parent, def) {
            if (!('children' in def)) return;
        
            var j, child, widget, instance, dissected, tag;

            for (var i = 0, cnt = def['children'].length; i < cnt; ++i) {
                child = null;

                for (j in def['children'][i]) {
                    child = def['children'][i][j];
                    break;
                }

                if (child == null) {
                    // no child element
                    continue;
                }

                dissected = ltk.widget.dissectTag(j);

                if (dissected.ns == 'html') {
                    // namespace html
                    tag = new ltk.widget();
                    tag.widget = ltk.dom.create(dissected.type);
                    tag.attach = function(parent, def){
                        parent.appendChild(this.widget);
                
                        this.widget.setProperties(def);
                    
                        if ('name' in def) {
                            me.getDialog().addReference(def['name'], this.widget);
                        }
                
                        // me.processChildren(def, function(parent, instance, def) {
                        this.processChildren(def, function(parent, instance, def) {
                            instance.attach(parent, def);
                        });
                    }

                    // tag = {
                    //     'node': ltk.dom.create(dissected.type),
                    //     'attach': function(parent, def) {
                    //         console.log(parent, def);
                    //         
                    //         parent.appendChild(this.node);
                    // 
                    //         this.node.setProperties(def);
                    //     
                    //         if ('name' in def) {
                    //             me.getDialog().addReference(def['name'], this.node);
                    //         }
                    // 
                    //         // me.processChildren(def, function(parent, instance, def) {
                    //         ltk.widget.protoype.processChildren(def, function(parent, instance, def) {
                    //             instance.attach(parent, def);
                    //         });
                    //     }
                    // }

                    // console.log(processor.toString(), parent, tag, child);

                    processor(parent, tag, child);
                    
                    continue;
                }

                // ltk or other namespace
                if (!(dissected.ns in ltk.widget.registry)) {
                    throw 'unknwon namespace "' + dissected.ns + '"';
                } else if (!(dissected.type in ltk.widget.registry[dissected.ns])) {
                    throw 'unknown widget type "' + dissected.type + '" in namespace "' + dissected.ns + '"';
                }

                widget   = ltk.widget.registry[dissected.ns][dissected.type];
                instance = me.addChild(new widget());
    
                if ('name' in child) {
                    instance.setName(child['name']);
                }

                processor(parent, instance, child);
            }
        }
    
        if (typeof parent == 'undefined') {
            parent = this.widget;
        }
    
        _process(parent, def);
    }

    /****m* widget/appendChild
     * SYNOPSIS
     */
    ltk.widget.prototype.appendChild = function(parent, tag, def)
    /*
     * FUNCTION
     *      append DOM child
     * INPUTS
     *      
     * OUTPUTS
     *      
     ****
     */
    {
        var e = ltk.dom.create(tag);
        var c = this.cssclass;
    
        for (var p in def) {
            switch (p) {
            case 'class':
                // css classes
                c = c + (c != '' ? ' ' : '') + def[p];
                break;
            case 'styles':
                // additional css styles
                if (typeof def[p] == 'object') {
                    e.setStyles(def[p]);
                }
                break;
            case 'id':
                // element ID
                e.setAttribute('id', def[p]);
                break;
            case 'type':
                // change type attribute must be done _before!_ appending it to the DOM
                e.setAttribute('type', def[p]);
                break;
            default:
                // event handler
                if (p.substr(0, 2) == 'on') {
                    if (p in this) {
                        // custom event
                        this[p] = def[p];
                    } else {
                        // node event
                        e.node[p] = def[p];
                    }
                }
            }
        }
    
        e.node.className = c;

        parent.appendChild(e);

        return e;
    }

    /****m* widget/assimilate
     * SYNOPSIS
     */
    ltk.widget.prototype.assimilate = function(node)
    /*
     * FUNCTION
     *      assimilate object into LTK widget
     * INPUTS
     *      * node (ltk.dom.node) -- object to assimilate
     ****
     */
    {
        var me = this;

        (function _assimilate(node) {
            node.childNodes().forEach(function(node) {
                var attr, name, widget;
            
                if ((attr = ltk.string.trim(node.getAttribute('ltk:data'))) != '') {
                    // node has custom data assigned
                    var data = eval('(' + attr + ')');
                    
                    for (var i in data) node.setData(i, data[i]);
                }

                if ((attr = node.getAttribute('ltk')) && (widget = ltk.widget.isRegistered(attr))) {
                    // registered LTK widget -> assimilate!!!
                    var instance = me.addChild(new widget());
                    instance.widget = node;
                    instance.assimilate(node);

                    if ((name = node.getAttribute('name'))) {
                        instance.setName(name);
                    }
                } else {
                    // not registered or misc node
                    _assimilate(node);
                }
            });
        })(node);
    }

    /****m* widget/attach
     * SYNOPSIS
     */
    ltk.widget.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      build widget
     * INPUTS
     *      * parent (ltk.dom.node) -- parent DOM node to attach widget to
     *      * def (array) -- widget definition
     * OUTPUTS
     *      
     ****
     */
    {
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );    
    
        // process children
        var me = this;
    
        this.processChildren(def, function(parent, instance, def) {
            instance.attach(parent, def);
        });
    }

    /****m* widget/addChild
     * SYNOPSIS
     */
    ltk.widget.prototype.addChild = function(instance)
    /*
     * FUNCTION
     *      add child widget
     * INPUTS
     *      * instance (object) -- instance of widget to add as child
     * OUTPUTS
     *      (object) -- instance specified as parameter
     ****
     */
    {
        // add child widget instance
        this.children.push(instance);
    
        // overwrite getDialog of child widget to return correct dialog instance
        var me = this;
    
        instance.getDialog = function() {
            return me.getDialog();
        }
    
        // return instance to further use it
        return instance;
    }

    /****m* widget/setOptions
     * SYNOPSIS
     */
    ltk.widget.prototype.setOptions = function(options)
    /*
     * FUNCTION
     *      import misc options for widget
     * INPUTS
     *      * options (object) -- objects to import
     ****
     */
    {
        options = (typeof options == 'undefined' ? {} : options);
    
        this.options = ltk.extend(
            this.options, 
            options
        );
    }

    /****m* widget/setName
     * SYNOPSIS
     */
    ltk.widget.prototype.setName = function(name)
    /*
     * FUNCTION
     *      set name of widget and add named reference to 
     *      dialog. if setName changes a previous set widget
     *      name, the reference will be renamed.
     * INPUTS
     *      * name (string) -- name of widget so set
     ****
     */
    {
        var dia = this.getDialog();
    
        if (dia == false) {
            // no reference to dialog available
            return;
        }
    
        if (this.name != '') {
            // remove previous set reference to this widget
            dia.removeReference(this.name);
        }

        // add reference
        this.name = name;
    
        dia.addReference(name, this);
    }

    /****m* widget/setDisabled
     * SYNOPSIS
     */
    ltk.widget.prototype.setDisabled = function(disabled)
    /*
     * FUNCTION
     *      set widget and all child widgets to disabled
     * INPUTS
     *      * disabled (bool) -- wheter to disable/enable widget
     ****
     */
    {
        disabled = !!disabled;
    
        if (this.disabled == disabled) {
            return;
        }
    
        this.disabled = disabled;
    
        for (var i = 0, cnt = this.children.length; i < cnt; ++i) {
            this.children[i].setDisabled(disabled);
        }
    }

    /****m* widget/destroy
     * SYNOPSIS
     */
    ltk.widget.prototype.destroy = function()
    /*
     * FUNCTION
     *      destroy widget
     ****
     */
    {
        for (var i = 0, cnt = this.children.length; i < cnt; ++i) {
            this.children[i].destroy();
        }

        this.destruct();
    }
})();


/****c* widget/spinner
 * NAME
 *      ltk.spinner
 * FUNCTION
 *      spinner widget
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('spinner' in ltk) return;
    
    /*
     * tags to consider when re-ordering tab index
     */
    var tab_tags = [
        'A', 'BUTTON', 'TEXTAREA', 'INPUT', 'IFRAME'
    ];

    /*
     * show / hide select boxes (to use as IE hack, if positioning layer above form elements
     */
    function toggleFormElements(show) {
        ltk.dom.get('SELECT').forEach(function(obj) {
            obj.node.style.visibility = (show ? 'visible' : 'hidden');
        });
    }

    /*
     * workaround for an IE bug where it's necessary to disable tab indexes
     */
    function toggleTabIndexes(enable, tab_indexes) {
        if (!document.all) return;

        if (enable) {
            if (tab_indexes.length == 0) return;

            for (var i = 0, len = tab_indexes.length; i < len; ++i) {
                tab_indexes[i].ref.tabIndex = tab_indexes[i].idx;
            }
        } else {
            var me = this;

            tab_indexes = [];

            ltk.dom.get(tab_tags).forEach(function(obj) {
                tab_indexes[idx++] = {
                    'ref': obj[i],
                    'idx': obj[i].tabIndex
                }

                obj.tabIndex = '-1';
            });
        }

        return tab_indexes;
    }

    /****m* spinner/
     * SYNOPSIS
     */
    ltk.spinner = function()
    /*
     * FUNCTION
     *      spinner constructor
     ****
     */
    {
        this.overlay = null;
        this.parent  = null;
        this.layer   = new ltk.dom.layer();
    }

    ltk.widget.register('ltk:spinner', ltk.spinner);

    ltk.spinner.prototype = new ltk.widget();

    /****v* spinner/container
     * SYNOPSIS
     */
    ltk.spinner.prototype.container = 'DIV';
    /*
     * FUNCTION
     *      html tag, that will be created as widget container
     ****
     */

    /****v* spinner/cssclass
     * SYNOPSIS
     */
    ltk.spinner.prototype.cssclass = 'ltk_spinner'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* spinner/show
     * SYNOPSIS
     */
    ltk.spinner.prototype.show = function()
    /*
     * FUNCTION
     *      show spinner
     ****
     */
    {
        /*@cc_on toggleFormElements(false); @*/

        var width  = this.parent.node.offsetWidth;
        var height = this.parent.node.offsetHeight;

        this.overlay.setStyles({
            'top':        '0',
            'left':       '0',
            'width':      width + 'px',
            'height':     height + 'px',
            'visibility': 'visible'
        });

        this.widget.setStyles({
            'top':        '0',
            'left':       '0',
            'width':      width + 'px',
            'height':     height + 'px',
            'visibility': 'visible'
        });

        this.layer.up();
    }

    /****m* spinner/hide
     * SYNOPSIS
     */
    ltk.spinner.prototype.hide = function()
    /*
     * FUNCTION
     *      hide spinner
     ****
     */
    {
        this.overlay.setStyle('visibility', 'hidden');
        this.widget.setStyle('visibility', 'hidden');

        /*@cc_on toggleFormElements(true); @*/
    }

    /****m* spinner/toggle
     * SYNOPSIS
     */
    ltk.spinner.prototype.toggle = function(state)
    /*
     * FUNCTION
     *      toggle visibility of spinner
     * INPUTS
     *      * state (bool) -- true: spinner visible / false: spinner hidden
     ****
     */
    {
        if (state) {
            this.show();
        } else {
            this.hide();
        }
    }

    /****m* spinner/attach
     * SYNOPSIS
     */
    ltk.spinner.prototype.attach = function(parent, options)
    /*
     * FUNCTION
     *      attach button widget
     ****
     */
    {
        parent.setStyle('position', 'relative');

        this.parent  = parent;
        this.widget  = this.appendChild(parent, this.container, {});
        this.overlay = parent.appendChild(ltk.dom.create('DIV', {
            'class': 'ltk_spinner_overlay'    
        }));

        this.layer.push([this.overlay, this.widget]);
    }
})();

/****c* ltk/label
 * NAME
 *      lima_ltk_label
 * FUNCTION
 *      label widget
 * COPYRIGHT
 *      copyright (c) 2008-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('label' in ltk) return;
    
    /****m* label/
     * SYNOPSIS
     */
    ltk.label = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
    }

    ltk.widget.register('ltk:label', ltk.label);

    ltk.label.prototype = new ltk.widget();

    /****v* label/cssclass
     * SYNOPSIS
     */
    ltk.label.prototype.cssclass = 'ltk_label'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****v* label/container
     * SYNOPSIS
     */
    ltk.label.prototype.container = 'LABEL';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****m* label/populate
     * SYNOPSIS
     */
    ltk.label.prototype.populate = function(label)
    /*
     * FUNCTION
     *      change label content
     * INPUTS
     *      * label (string) -- text to set as new label
     ****
     */
    {
        this.widget.node.innerHTML = label;
    }

    /****m* label/setDisabled
     * SYNOPSIS
     */
    ltk.label.prototype.setDisabled = function(disabled)
    /*
     * FUNCTION
     *      disable label
     * INPUTS
     *      * disabled (bool) -- disable / enable label
     ****
     */
    {
        if (this.disabled == disabled) {
            return;
        }
    
        if (disabled) {
            this.widget.addClass('ltk_disabled');
        } else {
            this.widget.removeClass('ltk_disabled');
        }
    
        this.disabled = disabled;
    }

    /****m* label/attach
     * SYNOPSIS
     */
    ltk.label.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach label widget
     ****
     */
    {
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );    

        this.widget.node.innerHTML = ('label' in def ? def['label'] : '');
    }
})();

/****c* ltk/image
 * NAME
 *      lima_ltk_image
 * FUNCTION
 *      image widget
 * COPYRIGHT
 *      copyright (c) 2009-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('image' in ltk) return;
    
    /****m* image/
     * SYNOPSIS
     */
    ltk.image = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
    }

    ltk.widget.register('ltk:image', ltk.image);

    ltk.image.prototype = new ltk.widget();

    /****v* image/cssclass
     * SYNOPSIS
     */
    ltk.image.prototype.cssclass = 'ltk_image'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****v* image/container
     * SYNOPSIS
     */
    ltk.image.prototype.container = 'IMG';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****m* image/getValue
     * SYNOPSIS
     */
    ltk.image.prototype.getValue = function()
    /*
     * FUNCTION
     *      get value of widget
     * OUTPUTS
     *      (string) -- widget value
     ****
     */
    {
        return this.widget.src;
    }

    /****m* image/populate
     * SYNOPSIS
     */
    ltk.image.prototype.populate = function(data)
    /*
     * FUNCTION
     *      populate widget
     * INPUTS
     *      * data (mixed) -- data to populate widget with
     ****
     */
    {
        this.widget.src = data;
    }

    /****m* image/attach
     * SYNOPSIS
     */
    ltk.image.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach image widget
     ****
     */
    {
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );    

        if (typeof def['src'] == 'string') {
            this.widget.setAttribute('src', def['src']);
        }
    }
})();

/****c* ltk/button
 * NAME
 *      lima_ltk_button
 * FUNCTION
 *      button widget
 * COPYRIGHT
 *      copyright (c) 2008-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('button' in ltk) return;
    
    /****m* button/
     * SYNOPSIS
     */
    ltk.button = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
    }

    ltk.widget.register('ltk:button', ltk.button);

    ltk.button.prototype = new ltk.widget();

    /****v* button/container
     * SYNOPSIS
     */
    ltk.button.prototype.container = 'BUTTON';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****v* button/cssclass
     * SYNOPSIS
     */
    ltk.button.prototype.cssclass = 'ltk_button';
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* button/setDisabled
     * SYNOPSIS
     */
    ltk.button.prototype.setDisabled = function(disabled)
    /*
     * FUNCTION
     *      disable widget
     * INPUTS
     *      * disabled (bool) -- disable / enable label
     ****
     */
    {
        disabled = !!disabled

        if (this.disabled == disabled) {
            return;
        }

        this.widget.node.disabled = this.disabled = disabled;
    }

    /****m* button/onClick
     * SYNOPSIS
     */
    ltk.button.prototype.onClick = function()
    /*
     * FUNCTION
     *      get's called when button is clicked
     ****
     */
    {
    }

    /****m* button/assimilate
     * SYNOPSIS
     */
    ltk.button.prototype.assimilate = function(node)
    /*
     * FUNCTION
     *      assimilates button widget.
     * INPUTS
     ****
     */
    {
        var me = this;
        
        ltk.evt.addEvent(this.widget, 'click', function() {
            me.onClick();
        });
    }

    /****m* button/attach
     * SYNOPSIS
     */
    ltk.button.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach button widget
     ****
     */
    {
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );    
    
        if ('label' in def) {
            // button with label
            this.widget.node.innerHTML = ('label' in def 
                                          ? def['label'] 
                                          : '');
        } else if ('children' in def) {
            this.processChildren(def, function(parent, instance, def) {
                instance.attach(parent, def);
            });
        }
    
        this.widget.node.style.disabled = ('disabled' in def && def['disabled']);

        var me = this;

        ltk.evt.addEvent(this.widget, 'click', function() {
            me.onClick();        
        });
    }
})();

/****c* ltk/htmltag
 * NAME
 *      lima_ltk_htmltag
 * FUNCTION
 *      htmltag widget
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('htmltag' in ltk) return;
    
    /****m* htmltag/
     * SYNOPSIS
     */
    ltk.htmltag = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
    }

    ltk.widget.register('ltk:htmltag', ltk.htmltag);

    ltk.htmltag.prototype = new ltk.widget();

    /****v* htmltag/container
     * SYNOPSIS
     */
    ltk.htmltag.prototype.container = 'SPAN';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****v* htmltag/cssclass
     * SYNOPSIS
     */
    ltk.htmltag.prototype.cssclass = 'ltk_widget'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* htmltag/setDisabled
     * SYNOPSIS
     */
    ltk.htmltag.prototype.setDisabled = function(disabled)
    /*
     * FUNCTION
     *      disable widget
     * INPUTS
     *      * disabled (bool) -- disable / enable label
     ****
     */
    {
        disabled = !!disabled

        if (this.disabled == disabled) {
            return;
        }

        if ((this.disabled = disabled)) {
            this.widget.addClass('ltk_disabled');
        } else {
            this.widget.removeClass('ltk_disabled');
        }
    }

    /****m* htmltag/attach
     * SYNOPSIS
     */
    ltk.htmltag.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach htmltag widget
     ****
     */
    {   
        this.widget = this.appendChild(
            parent, 
            (typeof def['tag'] == 'string' ? def['tag'] : this.container),
            def
        );
    
        if ('html' in def) {
            // innerHTML for widget
            this.widget.innerHTML = (typeof def['html'] == 'string' ? def['html'] : '');
        } else if ('children' in def) {
            this.processChildren(def, function(parent, instance, def) {
                instance.attach(parent, def);
            });
        }
    }
})();

/****c* widget/overlay
 * NAME
 *      ltk.overlay
 * FUNCTION
 *      overlay widget
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('overlay' in ltk) return;

    /*
     * tags to consider when re-ordering tab index
     */
    var tab_tags = [
        'A', 'BUTTON', 'TEXTAREA', 'INPUT', 'IFRAME'
    ];

    /*
     * show / hide select boxes (to use as IE hack, if positioning layer above form elements
     */
    function toggleFormElements(show) {
        ltk.dom.get('SELECT').forEach(function(obj) {
            obj.node.style.visibility = (show ? 'visible' : 'hidden');
        });
    }

    /*
     * workaround for an IE bug where it's necessary to disable tab indexes
     */
    function toggleTabIndexes(enable, tab_indexes) {
        if (!document.all) return;

        if (enable) {
            if (tab_indexes.length == 0) return;

            for (var i = 0, len = tab_indexes.length; i < len; ++i) {
                tab_indexes[i].ref.tabIndex = tab_indexes[i].idx;
            }
        } else {
            var me = this;

            tab_indexes = [];

            ltk.dom.get(tab_tags).forEach(function(obj) {
                tab_indexes[idx++] = {
                    'ref': obj[i],
                    'idx': obj[i].tabIndex
                }

                obj.tabIndex = '-1';
            });
        }
        
        return tab_indexes;
    }

    /****m* overlay/
     * SYNOPSIS
     */
    ltk.overlay = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.tab_indexes = [];
    }

    ltk.widget.register('ltk:overlay', ltk.overlay);

    ltk.overlay.prototype = new ltk.widget();

    /****v* overlay/cssclass
     * SYNOPSIS
     */
    ltk.overlay.prototype.cssclass = 'ltk_overlay'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****v* overlay/container
     * SYNOPSIS
     */
    ltk.overlay.prototype.container = 'DIV';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****m* overlay/hide
     * SYNOPSIS
     */
    ltk.overlay.prototype.hide = function() 
    /*
     * FUNCTION
     *      hide expose dialog window
     ****
     */
    {
        this.widget.node.style.visibility = 'hidden';

        /*@cc_on toggleFormElements(true); @*/
    }

    /****m* overlay/show
     * SYNOPSIS
     */
    ltk.overlay.prototype.show = function() 
    /*
     * FUNCTION
     *      show expose dialog window
     ****
     */
    {
        /*@cc_on toggleFormElements(false); @*/

        this.widget.node.style.visibility = 'visible';

        this.resize();
    }

    /****m* overlay/resize
     * SYNOPSIS
     */
    ltk.overlay.prototype.resize = function()
    /*
     * FUNCTION 
     *      resize overlay to fill whole screen
     ****
     */
    {
        var body = ltk.dom.one('BODY');
        var vp   = ltk.dom.getViewport();

        var h = (vp.h > body.node.scrollHeight ? vp.h : body.node.scrollHeight);
        var w = (vp.w > body.node.scrollWidth ? vp.w : body.node.scrollWidth); 

        this.widget.node.style.height = h + 'px';
        this.widget.node.style.width  = w + 'px';
    }

    /****m* overlay/attach
     * SYNOPSIS
     */
    ltk.overlay.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach overlay widget
     ****
     */
    {
        var me = this;

        this.widget = this.appendChild(ltk.dom.one('BODY'), this.container, def);

        if (ltk.browser.msie6 && 'setExpression' in this.widget.node.style) {
            with (this.widget.node) {
                style.position = 'absolute';
                style.height   = '0';
                style.width    = '0';
                style.setExpression('width', function() { return document.documentElement.clientWidth + 'px'; });
                style.setExpression('height', function() { return document.documentElement.clientHeight + 'px'; });
            }
        }
        
        ltk.evt.addEvent(new ltk.dom.node(window), 'resize', function() {
            me.resize();
        });
    }
})();

/****c* ltk/select
 * NAME
 *      lima_ltk_select
 * FUNCTION
 *      select widget
 * COPYRIGHT
 *      copyright (c) 2008-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('select' in ltk) return;
    
    /****m* select/
     * SYNOPSIS
     */
    ltk.select = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.items = {};
        this.value = null;
    }

    ltk.widget.register('ltk:select', ltk.select);

    ltk.select.prototype = new ltk.widget();

    /****v* select/cssclass
     * SYNOPSIS
     */
    ltk.select.prototype.cssclass = 'ltk_select'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****v* select/container
     * SYNOPSIS
     */
    ltk.select.prototype.container = 'SELECT';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****m* select/onchange
     * SYNOPSIS
     */
    ltk.select.prototype.onChange = function(value)
    /*
     * FUNCTION
     *      is called, whenever the slider value is changed
     * INPUTS
     *      * value (int) -- current value
     ****
     */
    {
    }

    /****m* select/setItems
     * SYNOPSIS
     */
    ltk.select.prototype.setItems = function(items)
    /*
     * FUNCTION
     *      fill widget with items
     ****
     */
    {
        var e = this.widget;
        var v = null;
    
        this.widget.removeChildren();
    
        for (var i = 0, cnt = items.length; i < cnt; ++i) {
            type  = (typeof items[i]);
            value = items[i];
            value = (type == 'string' || type == 'number' || type == 'boolean' 
                     ? value
                     : ('value' in value
                        ? value['value']
                        : ('text' in items[i]
                           ? items[i]['text']
                           : '')));
            text  = ('text' in items[i]
                     ? items[i]['text']
                     : value);
                 
            e = ltk.dom.create('option');
            e.setAttribute('value', value);
        
            if (i == 0) {
                v = value;
            }
        
            if ('selected' in items[i]) {
                e.setAttribute('selected', 'selected');
                v = value;
            }
        
            e.node.appendChild(document.createTextNode(text));
        
            this.widget.appendChild(e);
        }
    
        this.value = v;
    }

    /****m* select/populate
     * SYNOPSIS
     */
    ltk.select.prototype.populate = function(value)
    /*
     * FUNCTION
     *      set selected item
     * INPUTS
     *      * value (string) -- item to select in widget
     ****
     */
    {
    /*    this.value = value;
    
        for (var i = 0, cnt = this.widget.length; i < cnt; ++i) {
            if (this.widget[i].value == value) {
                this.widget.node.selectedIndex = i;
                break;
            }
        } */
    }

    /****m* select/getValue
     * SYNOPSIS
     */
    ltk.select.prototype.getValue = function()
    /*
     * FUNCTION
     *      get select value
     ****
     */
    {
        return this.value;
    }

    /****m* select/setDisabled
     * SYNOPSIS
     */
    ltk.select.prototype.setDisabled = function(disabled)
    /*
     * FUNCTION
     *      disable widget
     * INPUTS
     *      * disabled (bool) -- disable / enable label
     ****
     */
    {
        disabled = !!disabled

        if (this.disabled == disabled) {
            return;
        }

        this.widget.node.disabled = this.disabled = disabled;
    }

    /****m* select/attach
     * SYNOPSIS
     */
    ltk.select.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach select widget
     ****
     */
    {
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );    

        var e, value, text, type;

        if ('items' in def && def['items'] instanceof Array) {
            this.setItems(def['items']);
        }
    
        var me = this;

        ltk.evt.addEvent(this.widget, 'change', function() {
            me.value = me.widget.node.value;
            me.onChange(me.widget.node.value);
        });
    }
})();

/****c* ltk/slider
 * NAME
 *      lima_ltk_slider
 * FUNCTION
 *      slider object
 * COPYRIGHT
 *      copyright (c) 2004-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('slider' in ltk) return;
    
    /****m* slider/
     * SYNOPSIS
     */
    ltk.slider = function()
    /*
     * FUNCTION
     *      window constructor
     ****
     */
    {
        this.slider = null;
        this.range  = {'min': 0, 'max': 1};
        this.step   = 0;
        this.prec   = 0;
        this.value  = 0;
    }

    ltk.widget.register('ltk:slider', ltk.slider);

    ltk.slider.prototype = new ltk.widget();

    /****v* slider/cssclass
     * SYNOPSIS
     */
    ltk.slider.prototype.cssclass = 'ltk_slider'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */
 
    /****v* slider/container
     * SYNOPSIS
     */
    ltk.slider.prototype.container = 'DIV';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****v* slider/T_HORIZONTAL, T_VERTICAL
     * SYNOPSIS
     */
    ltk.slider.T_HORIZONTAL = 1;
    ltk.slider.T_VERTICAL   = 2;
    /*
     * FUNCTION
     *      slider type
     ****
     */

    /****m* slider/getValue
     * SYNOPSIS
     */
    ltk.slider.prototype.getValue = function()
    /*
     * FUNCTION
     *      get value of widget
     * OUTPUTS
     *      (string) -- widget value
     ****
     */
    {
        return this.value;
    }

    /****m* slider/onchange
     * SYNOPSIS
     */
    ltk.slider.prototype.onChange = function(value)
    /*
     * FUNCTION
     *      is called, whenever the slider value is changed
     * INPUTS
     *      * value (int) -- current value
     ****
     */
    {
    }

    /****m* slider/getRange
     * SYNOPSIS
     */
    ltk.slider.prototype.getRange = function()
    /*
     * FUNCTION
     *      return range of slider
     * OUTPUTS
     *      (object) -- min / max range
     ****
     */
    {
        return this.range;
    }

    /****m* slider/setRange
     * SYNOPSIS
     */
    ltk.slider.prototype.setRange = function(min, max)
    /*
     * FUNCTION
     *      set range of slider
     * INPUTS
     *      * min (int) -- minimal value
     *      * max (int) -- maximal value
     ****
     */
    {
        var w = this.widget.node.offsetWidth - this.slider.node.offsetWidth;
        
        this.range.min = min;
        this.range.max = max;

        this.step = (this.range.max - this.range.min) / w;
    }

    /****m* slider/populate
     * SYNOPSIS
     */
    ltk.slider.prototype.populate = function(value)
    /*
     * FUNCTION
     *      populate widget
     * INPUTS
     *      * value (mixed) -- value to populate widget with
     ****
     */
    {
        value = parseInt(value, 10);
    
        if (isNaN(value) || value < this.range.min) {
            value = this.range.min;
        } else if (value > this.range.max) {
            value = this.range.max;
        }

        if (value != this.value) {
            this.value = value;
        
            this.slider.node.style.left = (this.value / this.step) + 'px';
    
            value = this.value.toFixed(this.prec);
        
            this.onChange(value);
        }
    }

    /****m* slider/attach
     * SYNOPSIS
     */
    ltk.slider.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach slider widget
     ****
     */
    {
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );

        if ('precision' in def) {
            this.prec = def['precision'];
        }

        // build slider track
        var track = ltk.dom.create('div');
        track.node.className = 'ltk_slider_track';
        this.widget.appendChild(track);
    
        var bg = ltk.dom.create('div');
        bg.node.className = 'ltk_slider_track_background';
        bg.node.innerHTML = '&nbsp;';
        track.appendChild(bg);

        // build draggable slider
        this.slider = ltk.dom.create('img');
        this.slider.node.src = '/resources_ltk/images/shim.gif';
        this.widget.appendChild(this.slider);
        this.slider.node.className = 'ltk_draggable';
    
        ltk.evt.disableTextSelect(this.slider);
        var drag = new ltk.evt.draggable(this.slider, 1);
        var me   = this;

        // function for initializing slider
        if ('min' in def) {
            this.range.min = def['min'];
        }
        if ('max' in def) {
            this.range.max = def['max'];
        }

        var x1, x2, w;
    
        function init() {
            x1 = me.widget.getPos().x;
            w  = me.widget.node.offsetWidth - me.slider.node.offsetWidth;
            x2 = x1 + w;

            drag.setRange(x1, false, x2, false);
    
            me.step = (me.range.max - me.range.min) / w;
        }

        ltk.evt.addEvent(this.slider, 'mousedown', function() {
            init();
        });

        drag.onMouseMove = function() {
            me.value = ((me.slider.getPos().x - x1) * me.step);
        
            var value = me.value.toFixed(me.prec);
        
            me.onChange(value);
        }

        // initialize slider
        ltk.dom.ready(init);
    }
})();

/****c* ltk/textline
 * NAME
 *      lima_ltk_textline
 * FUNCTION
 *      textline widget
 * COPYRIGHT
 *      copyright (c) 2009-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('textline' in ltk) return;
    
    /****m* textline/
     * SYNOPSIS
     */
    ltk.textline = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
    }

    ltk.widget.register('ltk:textline', ltk.textline);

    ltk.textline.prototype = new ltk.widget();

    /****v* textline/cssclass
     * SYNOPSIS
     */
    ltk.textline.prototype.cssclass = 'ltk_textline'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****v* textline/container
     * SYNOPSIS
     */
    ltk.textline.prototype.container = 'INPUT';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****m* textline/getValue
     * SYNOPSIS
     */
    ltk.textline.prototype.getValue = function()
    /*
     * FUNCTION
     *      get value of widget
     * OUTPUTS
     *      (string) -- widget value
     ****
     */
    {
        return this.widget.node.value;
    }

    /****m* textline/populate
     * SYNOPSIS
     */
    ltk.textline.prototype.populate = function(data)
    /*
     * FUNCTION
     *      populate widget
     * INPUTS
     *      * data (mixed) -- data to populate widget with
     ****
     */
    {
        this.widget.node.value = data;
    }

    /****m* textline/onChange
     * SYNOPSIS
     */
    ltk.textline.prototype.onChange = function(value)
    /*
     * FUNCTION
     *      called whenever the value is changed
     * INPUTS
     *      * value (mixed) -- current value
     ****
     */
    {
    }

    /****m* textline/attach
     * SYNOPSIS
     */
    ltk.textline.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach textline widget
     ****
     */
    {
        if (!('type' in def)) {
            def['type'] = 'text';
        } else if (def['type'] != 'text' && def['type'] != 'hidden' && def['type'] != 'password') {
            def['type'] = 'text';
        }

        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );

        if (!('value' in def)) {
            def['value'] = '';
        }

        this.widget.node.setAttribute('value', def['value']);

        var me = this;
    
        ltk.evt.addEvent(this.widget, 'change', function() {
            me.onChange(me.widget.node.value);
        });
    }
})();

/****c* ltk/growable
 * NAME
 *      lima_ltk_growable
 * FUNCTION
 *      growable widget
 * COPYRIGHT
 *      copyright (c) 2009-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('growable' in ltk) return;

    /****m* growable/
     * SYNOPSIS
     */
    ltk.growable = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.template = [];
        this.rows     = {};
        this.cnt_rows = 0;
        this.max_rows = 999;
    }

    ltk.widget.register('ltk:growable', ltk.growable);

    ltk.growable.prototype = new ltk.widget();

    /****v* growable/cssclass
     * SYNOPSIS
     */
    ltk.growable.prototype.cssclass = 'ltk_growable'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* growavle/populate
     * SYNOPSIS
     */
    ltk.growable.prototype.populate = function(data)
    /*
     * FUNCTION
     *      grow to number of rows in data array and populate child widgets
     * INPUTS
     *      * data (array) -- data to populate widgets with
     ****
     */
    {
        for (var i = 0, cnt = data.length; i < cnt; ++i) {
        }
    }

    /****m* growable/getValue
     * SYNOPSIS
     */
    ltk.growable.prototype.getValue = function()
    /*
     * FUNCTION
     *      collect values of child widgets
     * OUTPUTS
     *      (array) -- array of values
     ****
     */
    {
        var data = [];
    
        for (var i = 0, cnt = this.rows.length; i < cnt; ++i) {
            data.push(this.rows[i].getValue());
        }
    
        return data;
    }

    /****m* growable/addRow
     * SYNOPSIS
     */
    ltk.growable.prototype.addRow = function(behind)
    /*
     * FUNCTION
     *      add a row of widgets
     * INPUTS
     *      * behind (int) -- add row behind idx
     * OUTPUTS
     *      (dialog) -- instance of new row
     ****
     */
    {
        if (this.cnt_rows >= this.max_rows) {
            // max rows reached
            return;
        }
    
        var me  = this;

        // each row of widgets is an own subdialog
        var tpl = ltk.clone(this.template);
        var idx = ++this.cnt_rows;
    
        var dia = new ltk.dialog();
        dia.container = this.container;
        dia.attach = function(parent, def) {
            this.widget = this.appendChild(
                parent, 
                this.container, 
                def
            );    

            this.processChildren(def, function(parent, instance, def) {
                instance.attach(parent, def);
            }, this.widget);
        }
        dia.attach(this.widget, {'children': tpl});

        if ('ltk_growable_add' in dia.refs && 'ltk_growable_remove' in dia.refs) {
            dia.refs['ltk_growable_add'].onclick = function() {
                me.addRow(idx);
            }
            dia.refs['ltk_growable_remove'].onclick = function() {
                me.removeRow(idx);
            }
        }

        this.rows[idx] = dia;
    
        return dia;
    }

    /****m* growable/removeRow
     * SYNOPSIS
     */
    ltk.growable.prototype.removeRow = function(idx)
    /*
     * FUNCTION
     *      remove row from growable list
     * INPUTS
     *      * idx (int) -- ID of row to remove
     ****
     */
    {
        if (!(idx in this.rows) || this.cnt_rows == 1) {
            // unknown row or only one row left
            return;
        }
    
        this.rows[idx].destroy();
        delete this.rows[idx];
    
        --this.cnt_rows;
    }

    /****m* growable/attach
     * SYNOPSIS
     */
    ltk.growable.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach growable widget
     ****
     */
    {
        this.widget   = parent;
        this.template = def['children'];
    
        if ('addrow' in def && def['addrow']) {
            this.addRow();
        }
    }
})();

/****c* ltk/checkbox
 * NAME
 *      lima_ltk_checkbox
 * FUNCTION
 *      checkcheckbox widget
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('checkbox' in ltk) return;
    
    ltk.checkbox = function() {
        this.items = {};            // checkbox items
    }

    ltk.widget.register('ltk:checkbox', ltk.checkbox);

    ltk.checkbox.prototype = new ltk.widget();

    /****v* checkbox/cssclass
     * SYNOPSIS
     */
    ltk.checkbox.prototype.cssclass = 'ltk_checkbox'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* checkbox/getValue
     * SYNOPSIS
     */
    ltk.checkbox.prototype.getValue = function()
    /*
     * FUNCTION
     *      get checkbox value
     ****
     */
    {
        var ret = {};
    
        for (var i in this.items) {
            if (this.items[i].node.checked) {
                ret[i] = this.items[i].value;
            }
        }
    
        return ret;
    }

    /****m* checkbox/populate
     * SYNOPSIS
     */
    ltk.checkbox.prototype.populate = function(data)
    /*
     * FUNCTION
     *      populate widget/activate specified checkbox items and deactivate all other ones
     * INPUTS
     *      * data (array) -- checkbox items to activate
     ****
     */
    {
        for (var i in data) {
            if (i in this.items) {
                this.items[i].node.checked = !!this.items[i];
            }
        }
    }

    /****m* checkbox/attach
     * SYNOPSIS
     */
    ltk.checkbox.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      build checkbox
     *
     *      {'checkbox': {
     *          items: []
     *      }}
     ****
     */
    {
        if (!('items' in def)) return;
    
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );    

        var id = ltk.getUniqID('ltk_chk_');
        var me = this;
        var item;
        
        for (var i = 0, cnt = def['items'].length; i < cnt; ++i) {
            item = def['items'][i]['item'];
            
            this.widget.appendChild(ltk.dom.create('span', {
                'styles':   {'display': 'block'},
                'children': [
                    {'input': {
                        'id':       id + '_' + i,
                        'type':     'checkbox',
                        'value':    item['value'],
                        '#trigger': ltk.closure(item, function(item, node) {
                            me.items[item['name']] = node;

                            node.setProperties({'checked': ('checked' in item && !!item['checked'])});
                        })
                    }},
                    {'label': {
                        'for':      id + '_' + i,
                        '#html':    ' ' + item['label'],
                        '#trigger': function(node) {
                            ltk.evt.disableTextSelect(node);
                        }
                    }}
                ]
            }));
        }
    }
})();

/****c* ltk/dropdown
 * NAME
 *      lima_ltk_dropdown
 * FUNCTION
 *      dropdown widget
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('dropdown' in ltk) return;
    
    /****m* dropdown/
     * SYNOPSIS
     */
    ltk.dropdown = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
    }

    ltk.widget.register('ltk:dropdown', ltk.dropdown);

    ltk.dropdown.prototype = new ltk.widget();

    /****v* dropdown/container
     * SYNOPSIS
     */
    ltk.dropdown.prototype.container = 'UL';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****v* dropdown/cssclass
     * SYNOPSIS
     */
    ltk.dropdown.prototype.cssclass = 'ltk_dropdown'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* dropdown/setDisabled
     * SYNOPSIS
     */
    ltk.dropdown.prototype.setDisabled = function(disabled)
    /*
     * FUNCTION
     *      disable widget
     * INPUTS
     *      * disabled (bool) -- disable / enable label
     ****
     */
    {
        disabled = !!disabled

        if (this.disabled == disabled) {
            return;
        }

        this.widget.disabled = this.disabled = disabled;
    }

    /****m* dropdown/attach
     * SYNOPSIS
     */
    ltk.dropdown.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach dropdown widget
     ****
     */
    {
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );

        function build(parent, items) {
            var li, ul, a, item;
        
            for (var i = 0, cnt = items.length; i < cnt; ++i) {
                item = items[i]['item'];
            
                li = parent.appendChild(ltk.dom.create('li'));
            
            /*@cc_on
                li.node.onmouseover = ltk.closure(li, function(li) {
                    li.addClass('hover');
                });
                li.node.onmouseout = ltk.closure(li, function(li) {
                    li.removeClass('hover');
                });
            @*/

                a = li.appendChild(ltk.dom.create('a'));
                a.node.innerHTML = item['label'];
            
                if ('onclick' in item) {
                    li.onclick = ltk.closure(item['onclick'], function(cb) {
                        cb();
                    });
                }
            
                if ('items' in item) {
                    ul = li.appendChild(ltk.dom.create('ul'));
                    ul.node.style.zIndex = 20000;
                
                    build(ul, item['items']);
                }
            }
        }

        build(this.widget, def['items']);
    }
})();

/****c* ltk/editable
 * NAME
 *      ltk.editable
 * FUNCTION
 *      editable widget
 * COPYRIGHT
 *      copyright (c) 2009-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('editable' in ltk) return;
    
    /*
     * forbidden containers to make editable
     */
    var forbidden = {
        'table': false, 'tr': false, 'td': false, 'tbody': false, 'thead': false, 'tfoot': false, 'th': false
    };
    
    /*
     * helper
     */
    function create(instance, node, opt) {
        var type = ('type' in opt ? opt['type'] : 'singleline');
        var edit;
        
        switch (type) {
        case 'multiline':
            edit = ltk.dom.create('TEXTAREA', {
                'class': 'ltk_editable'
            });
            break;
        default:
            edit = ltk.dom.create('INPUT', {
                'class': 'ltk_editable'
            });
            break;
        }

        if ('onChange' in opt && typeof opt['onChange'] == 'function') {
            instance.onChange = opt['onChange'];
        }

        var href = '';

        if (node.node.tagName.toLowerCase() == 'a') {
            // we need special behaviour vor A-tag
            ltk.evt.addEvent(node, 'mouseover', function() {
                href = node.getAttribute('href');
                node.removeAttribute('href');
            }, {'propagate': true});
            ltk.evt.addEvent(node, 'mouseout', function() {
                node.setAttribute('href', href);
            }, {'propagate': true});
        }

        ltk.evt.addKeyboardEvent(edit, 'ESC', function() {
            node.node.innerHTML = ltk.string.htmlspecialchars(instance.undo);
            edit.replaceNode(node);
            
            if (href != '') node.setAttribute('href', href);
        }, {'propagate': true, 'default': true});
        ltk.evt.addKeyboardEvent(edit, 'ENTER', function() {
            var value = edit.node.value;
            
            if (ltk.string.trim(value) == '') {
                node.node.innerHTML = '&nbsp;';
            } else {
                node.node.innerHTML = ltk.string.htmlspecialchars(value);
            }

            edit.replaceNode(node);
            
            if (instance.undo != value) instance.onChange(value);

            if (href != '') node.setAttribute('href', href);
        }, {'propagate': true, 'default': true});

        // stop propagation for click events in edit field
        ltk.evt.addEvent(edit, 'mousedown', function() {}, {'propagate': false, 'default': true});

        ltk.evt.addEvent(edit, 'blur', function() {
            var value = edit.node.value;
            
            if (ltk.string.trim(value) == '') {
                node.node.innerHTML = '&nbsp;';
            } else {
                node.node.innerHTML = ltk.string.htmlspecialchars(value);
            }

            edit.replaceNode(node);
            
            if (instance.undo != value) instance.onChange(value);

            if (href != '') node.setAttribute('href', href);
        });

        ltk.evt.addEvent(node, 'dblclick', function() {
            if (!instance.activated) return;
            
            instance.undo = ltk.string.entitydecode(node.node.innerHTML);
            node.node.innerHTML = '';

            edit.node.value = instance.undo;

            node.replaceNode(edit);
            edit.node.focus();
            edit.node.select();
        });
    }
    
    /****m* editable/
     * SYNOPSIS
     */
    ltk.editable = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.undo = '';         // content to set, if ESC is pressed
        this.activated = true;  // whether widget functionality is activated
    }

    ltk.widget.register('ltk:editable', ltk.editable);

    ltk.editable.prototype = new ltk.widget();

    /****v* editable/cssclass
     * SYNOPSIS
     */
    ltk.editable.prototype.cssclass = 'ltk_editable'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****v* editable/container
     * SYNOPSIS
     */
    ltk.editable.prototype.container = 'INPUT';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****m* editable/getValue
     * SYNOPSIS
     */
    ltk.editable.prototype.getValue = function()
    /*
     * FUNCTION
     *      get value of widget
     * OUTPUTS
     *      (string) -- widget value
     ****
     */
    {
        return this.widget.node.value;
    }

    /****m* editable/populate
     * SYNOPSIS
     */
    ltk.editable.prototype.populate = function(data)
    /*
     * FUNCTION
     *      populate widget
     * INPUTS
     *      * data (mixed) -- data to populate widget with
     ****
     */
    {
        this.widget.node.value = data;
    }

    /****m* editable/onChange
     * SYNOPSIS
     */
    ltk.editable.prototype.onChange = function(value)
    /*
     * FUNCTION
     *      called whenever the value is changed
     * INPUTS
     *      * value (mixed) -- new value
     ****
     */
    {
    }

    /****m* editable/activate
     * SYNOPSIS
     */
    ltk.editable.prototype.activate = function()
    /*
     * FUNCTION
     *      activate widget functionality
     ****
     */
    {
        this.activated = true;
    }

    /****m* editable/deactivate
     * SYNOPSIS
     */
    ltk.editable.prototype.deactivate = function()
    /*
     * FUNCTION
     *      deactivate widget functionality
     ****
     */
    {
        this.activated = false;
    }

    /****m* editable/assimilate
     * SYNOPSIS
     */
    ltk.editable.prototype.assimilate = function(node)
    /*
     * FUNCTION
     *      assimilate a node to be editable
     * INPUTS
     *      * node (ltk.dom.node) -- DOM node to assimilate
     ****
     */
    {
        var opt = {};
        var tmp;
        
        if (node.node.tagName.toLowerCase in forbidden) return;
        
        if (node.hasAttribute('ltk:editable') && (tmp = ltk.string.trim(node.getAttribute('ltk:editable'))) != '') {
            opt = eval('(' + tmp + ')');
        }
        
        create(this, node, opt);        
    }

    /****m* editable/attach
     * SYNOPSIS
     */
    ltk.editable.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach editable widget
     ****
     */
    {
    }
})();

/****c* widget/input
 * NAME
 *      ltk.input
 * FUNCTION
 *      input widget -- either HTML5 or emulation
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('input' in ltk) return;
    
    /****m* input/
     * SYNOPSIS
     */
    ltk.input = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
    }

    ltk.widget.register('ltk:input', ltk.input);

    ltk.input.prototype = new ltk.widget();

    /****v* input/cssclass
     * SYNOPSIS
     */
    ltk.input.prototype.cssclass = 'ltk_input'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****v* input/container
     * SYNOPSIS
     */
    ltk.input.prototype.container = 'INPUT';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****v* input/map
     * SYNOPSIS
     */
    ltk.input.map = {
        'email':  'text',
        'search': 'text',
        'url':    'text'
    };
    /*
     * FUNCTION
     *      defines which input types to map to a standard input field, to which type in that case
     ****
     */

    /****m* input/getValue
     * SYNOPSIS
     */
    ltk.input.prototype.getValue = function()
    /*
     * FUNCTION
     *      get value of widget
     * OUTPUTS
     *      (string) -- widget value
     ****
     */
    {
        return this.widget.node.value;
    }

    /****m* input/populate
     * SYNOPSIS
     */
    ltk.input.prototype.populate = function(data)
    /*
     * FUNCTION
     *      populate widget
     * INPUTS
     *      * data (mixed) -- data to populate widget with
     ****
     */
    {
        this.widget.node.value = data;
    }

    /****m* input/onChange
     * SYNOPSIS
     */
    ltk.input.prototype.onChange = function(value)
    /*
     * FUNCTION
     *      called whenever the value is changed
     * INPUTS
     *      * value (mixed) -- current value
     ****
     */
    {
    }

    ;(function() {
        var checked = {};
    
        /****f* input/check
         * SYNOPSIS
         */
        ltk.input.check = function(type)
        /*
         * FUNCTION
         *      check for availability of specified HTML5 type
         * INPUTS
         *      * type (string) -- input type to check availability of
         * OUTPUTS
         *      (bool) -- returns true, if HTML5 type is supported by browser
         ****
         */
        {
            type = type.toLowerCase();
        
            if (type in checked) return checked[type];
        
            var e = document.createElement('input');
            e.type = type;
        
            return checked[type] = (e.type.toLowerCase() == type);
        }
    })();

    /****m* input/attach
     * SYNOPSIS
     */
    ltk.input.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach input widget
     ****
     */
    {
        if (!('type' in def)) {
            def['type'] = 'text';
        }

        var valid = ltk.input.check(def['type']);

        if (!('value' in def)) {
            def['value'] = '';
        }

        if (valid || def['type'] in ltk.input.map) {
            // valid widget, or mapped to html input tag
            this.widget = this.appendChild(
                parent, 
                this.container, 
                def
            );
    
            this.widget.setAttribute('type', (valid ? def['type'] : ltk.input.map[def['type']]));
    
            var me = this;
    
            ltk.evt.addEvent(this.widget, 'change', function() {
                me.onChange(me.widget.node.value);
            });
        }
    
        if (!valid) {
            switch (def['type']) {
            case 'range':
                // range -> slider fallback
                ltk.extend(this, new ltk.slider());
                this.attach(parent, def);
                break;
            }
        }
    }
})();

/****c* ltk/radiobox
 * NAME
 *      lima_ltk_radiobox
 * FUNCTION
 *      checkradiobox widget
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('radiobox' in ltk) return;
    
    ltk.radiobox = function() {
        this.items = {};            // radiobox items
    }

    ltk.widget.register('ltk:radiobox', ltk.radiobox);

    ltk.radiobox.prototype = new ltk.widget();

    /****v* radiobox/cssclass
     * SYNOPSIS
     */
    ltk.radiobox.prototype.cssclass = 'ltk_radiobox'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* radiobox/getValue
     * SYNOPSIS
     */
    ltk.radiobox.prototype.getValue = function()
    /*
     * FUNCTION
     *      get radiobox value
     ****
     */
    {
        var ret = {};
    
        for (var i in this.items) {
            if (this.items[i].node.checked) {
                ret[i] = this.items[i].value;
            }
        }
    
        return ret;
    }

    /****m* radiobox/populate
     * SYNOPSIS
     */
    ltk.radiobox.prototype.populate = function(data)
    /*
     * FUNCTION
     *      populate widget/activate specified radiobox items and deactivate all other ones
     * INPUTS
     *      * data (array) -- radiobox items to activate
     ****
     */
    {
        for (var i in data) {
            if (i in this.items) {
                this.items[i].node.checked = !!this.items[i];
            }
        }
    }

    /****m* radiobox/attach
     * SYNOPSIS
     */
    ltk.radiobox.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      build radiobox
     *
     *      {'radiobox': {
     *          items: []
     *      }}
     ****
     */
    {
        if (!('items' in def)) return;
    
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );    

        var id = ltk.getUniqID('ltk_chk_');
        var me = this;
        var item;
        
        for (var i = 0, cnt = def['items'].length; i < cnt; ++i) {
            item = def['items'][i]['item'];
            
            this.widget.appendChild(ltk.dom.create('span', {
                'styles':   {'display': 'block'},
                'children': [
                    {'input': {
                        'id':       id + '_' + i,
                        'name':     id,
                        'type':     'radio',
                        'value':    item['value'],
                        '#trigger': ltk.closure(item, function(item, node) {
                            me.items[item['name']] = node;

                            node.setProperties({'checked': ('checked' in item && !!item['checked'])});
                        })
                    }},
                    {'label': {
                        'for':      id + '_' + i,
                        '#html':    ' ' + item['label'],
                        '#trigger': function(node) {
                            ltk.evt.disableTextSelect(node);
                        }
                    }}
                ]
            }));
        }
    }
})();

/****c* ltk/selectbox
 * NAME
 *      lima_ltk_selectbox
 * FUNCTION
 *      selectbox widget
 * COPYRIGHT
 *      copyright (c) 2008-2010 by Daniel Garding
 * AUTHOR
 *      Daniel Garding <daniel.garding@clipdealer.de>
 ****
 */

;(function() {
    if ('selectbox' in ltk) return;

    /****m* selectbox/
     * SYNOPSIS
     */
    ltk.selectbox = function()
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.choosebtn = {};
        
        this.opt = {};
        
        this.id = ltk.getUniqID('ltk_selectbox_');
    }

    ltk.widget.register('ltk:selectbox', ltk.selectbox);

    ltk.selectbox.prototype = new ltk.widget();

    /****v* selectbox/container
     * SYNOPSIS
     */
    ltk.selectbox.prototype.container = 'select';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****v* selectbox/cssclass
     * SYNOPSIS
     */
    ltk.selectbox.prototype.cssclass = 'ltk_selectbox';
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* selectbox/setDisabled
     * SYNOPSIS
     */
    ltk.selectbox.prototype.setDisabled = function(disabled)
    /*
     * FUNCTION
     *      disable widget
     * INPUTS
     *      * disabled (bool) -- disable / enable label
     ****
     */
    {
        disabled = !!disabled

        if (this.disabled == disabled) {
            return;
        }

        this.widget.node.disabled = this.disabled = disabled;
    }

    /****m* selectbox/onClick
     * SYNOPSIS
     */
    ltk.selectbox.prototype.mouseDown = function()
    /*
     * FUNCTION
     *      get's called when selectbox is clicked
     ****
     */
    {
        var me = this;
        
        var contentWidth = (this.widget.node.offsetWidth < 200 ? 200 : this.widget.node.offsetWidth)+ 'px';
        
        window.setTimeout(function(){
            me.widget.setStyle('display', 'none');
        }, 1);

        if (!ltk.dom.one('#' + this.id)) {
            var toogle = 'odd';

            var li = [];

            var data = {};
            data.item = [];

            var itemLength = this.widget.node.options.length;

            var lineHeight = 15;
            var contentHeight = (itemLength * lineHeight > 100 ? 200 : 60);

            for (var i = 0; i < itemLength; ++i) {
                data.item[i] = {
                    'text' : this.widget.node.options[i].text,
                    'value' : this.widget.node.options[i].value,
                    'index' : this.widget.node.options[i].index
                };
            }

            ltk.array.forEach(data.item, function(item) {
                if (toogle == 'odd') {
                    toogle = 'even';
                } else {
                    toogle = 'odd';
                }

                // multi or single list
                if (item.index != 0) {
                    if(!('type' in me.opt) && (me.opt.type != 'multi')) {
                        li.push({'li' : {
                            'id' : item.index,
                            'class' : toogle,
                            'value' : item.value,
                            '#html' : item.text,
                            'styles' : {'lineHeight' : lineHeight+'px'},
                            '#trigger' : function(node){
                                ltk.evt.addEvent(node, 'click', function(){
                                    me.selectedItem = node.node;
                                    ltk.dom.one('#' + me.id).setStyle('display', 'none');
                                    me.widget.node.options[me.selectedItem.id].selected = 'selected';
                                    me.widget.setStyle('display', 'block');
                                });
                            }
                        }});
                    } else {
                        li.push({'li' : {
                            'class' : toogle,
                            'styles' : {'lineHeight' : lineHeight+'px'},
                            'children' : [
                                {'input' : {
                                    'type' : 'checkbox',
                                    'value' : item.value,
                                    'id' : item.index,
                                    'name' : item.text
                                }},
                                {'label' : {
                                    '#html' : item.text
                                }}
                            ]
                        }});

                        me.choosebtn = {'button' : {
                            '#html' : _('W&auml;hlen'),
                            '#trigger' : function(node){
                                ltk.evt.addEvent(node, 'click', function(){
                                    setTimeout(function(){
                                        updateWidget();
                                    }, 200);

                                    me.widget.setStyle('display', 'block');
                                    ltk.dom.one('#' + me.id).setStyle('display', 'none');
                                });
                            }
                        }};
                    }
                }
            });

            this.widget.insertAfter(ltk.dom.create('div', {
                'id' : this.id,
                'class' : 'ltk_selectbox_outbox clear',
                'styles' : {'width' : contentWidth},
                'children' : [
                    { 'div' : {
                        'class' : 'ltk_selectbox_content',
                        'styles' : {'height' : contentHeight+'px'},
                        'children' : [
                            { 'ul' : {
                                'class' : 'ltk_selectbox_list',
                                'children' : li
                            }}
                        ]
                    }},
                    { 'div' : {
                        'class' : 'ltk_selectbox_buttonbar',
                        'children' : [
                            { 'span' : {
                                'class' : 'ltk_selectbox_selectAll',
                                '#html' : _('beliebig'),
                                '#trigger' : function(node){
                                    ltk.evt.addEvent(node, 'click', function(){
                                        ltk.dom.one('#' + me.id).setStyle('display', 'none');
                                        me.widget.node.options[0].selected = 'selected';
                                        me.widget.setStyle('display', 'block');
                                    });
                                }
                            }},
                            me.choosebtn
                        ]
                    }}
                ]
            }));
        } else {
            ltk.dom.one('#' + this.id).setStyle('display', 'block');
        }

        var updateWidget = function(){
            var selectedItems = getInputFields();

            var names = '';
            var texts = '';

            if (selectedItems.length > 0) {
                selectedItems.forEach(function(item){
                    texts += item.name + ', ';
                    names += item.value + ', ';
                });
            } else {                
                names = data.item[0].text;
            }

            me.widget.node.options[0].text = ltk.string.rtrim(texts, ', ');
            me.widget.node.options[0].value = ltk.string.rtrim(names, ', ');
            me.widget.node.options[0].setAttribute('selected', 'selected');
        }

        var getInputFields = function() {
            var list = ltk.dom.one('.ltk_selectbox_list').childNodes();

            var selectedItems = [];

            list.forEach(function(item){
                if( item.childNodes().nodes[0].checked ) {
                    selectedItems.push(item.childNodes().nodes[0]);
                }
            });
            
            return selectedItems;
        }
    }

    ltk.selectbox.prototype.assimilate = function(node)
    {
        if (node.hasAttribute('ltk:selectbox') && (tmp = ltk.string.trim(node.getAttribute('ltk:selectbox'))) != '') {
            this.opt = eval('(' + tmp + ')');
        }
        
        var me = this;
        ltk.evt.addEvent(this.widget, 'mousedown', function(e) {
            me.mouseDown();
        });
    }
    
    /****m* selectbox/attach
     * SYNOPSIS
     */
    ltk.selectbox.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach selectbox widget
     ****
     */
    {
    }
})();

/****c* ltk/table
 * NAME
 *      lima_ltk_table
 * FUNCTION
 *      lima LTK dynamic table widget
 * COPYRIGHT
 *      copyright (c) 2008-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 * REFERENCES
 *      *   drag and drop inspired by http://www.isocra.com/2007/07/dragging-and-dropping-table-rows-in-javascript/
 ****
 */

;(function() {
    if ('table' in ltk) return;

    /*
     * contextmenu for table
     */
    var getContextmenu = ltk.proxy(function() {
        var dialog = new ltk.contextmenu();
        dialog.attach(ltk.dom.one('body').appendChild(ltk.dom.create('DIV')), {});
        
        return dialog;
    });
    
    /****m* table/
     * SYNOPSIS
     */
    ltk.table = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.activated = true;
        
        this.rows = new ltk.dom.nodelist();             // contains all draggable rows
    }

    ltk.widget.register('ltk:table', ltk.table);

    ltk.table.prototype = new ltk.widget();

    /****m* table/populate
     * SYNOPSIS
     */
    ltk.table.prototype.populate = function(data)
    /*
     * FUNCTION
     *      populate widget
     ****
     */
    {
    }

    /****m* table/styleColumn
     * SYNOPSIS
     */
    ltk.table.prototype.doColumn = function(col_no, cb)
    /*
     * FUNCTION
     *      style column
     ****
     */
    {
        for (var i = 0, cnt = this.refs.length; i < cnt; ++i) {
            cb(this.refs[i][col_no]);
        }
    }

    /****m* table/onchange
     * SYNOPSIS
     */
    ltk.table.prototype.onchange = function(value)
    /*
     * FUNCTION
     *      onchange event
     ****
     */
    {
    }

    /****m* table/onAction
     * SYNOPSIS
     */
    ltk.table.prototype.onAction = function(action, node)
    /*
     * FUNCTION
     *      an action occured on a specific row of the table
     * INPUTS
     *      * action (string) -- name of action triggered
     *      * node (ltk.dom.node) -- node, the action occured
     ****
     */
    {
    }

    /****m* table/getValue
     * SYNOPSIS
     */
    ltk.table.prototype.getValue = function()
    /*
     * FUNCTION
     *      get table value
     ****
     */
    {
        return this.widget.value;
    }

    /****m* table/activate
     * SYNOPSIS
     */
    ltk.table.prototype.activate = function()
    /*
     * FUNCTION
     *      activate widget functionality
     ****
     */
    {
        this.activated = true;
    }

    /****m* table/deactivate
     * SYNOPSIS
     */
    ltk.table.prototype.deactivate = function()
    /*
     * FUNCTION
     *      deactivate widget functionality
     ****
     */
    {
        this.activated = false;
    }

    /*
     * try to find a drop target node
     */
    function getTarget(y, type) {
        var ret = this.rows.forEach(function(row) {
            var xy, h, opt;

            if (!(opt = row.getData('ltk:table')) || opt.sort != type) {
                // drag and target nodes not of the same sort-type
                return;
            }

            if (row.node.offsetHeight == 0) {
                xy = row.firstChild().getPos();
                h  = parseInt(row.firstChild().node.offsetHeight, 10) / 2;
            } else {
                xy = row.getPos();
                h  = parseInt(row.node.offsetHeight, 10) / 2;
            }

            if (y > (xy.y - h) && y < (xy.y + h)) {
                return row;
            }
        });

        return (ret === undefined ? false : ret);
    }
    
    /****m* table/addNodes
     * SYNOPSIS
     */
    ltk.table.prototype.addNodes = function(rows)
    /*
     * FUNCTION
     *      add nodes to table
     * INPUTS
     *      * rows (ltk.dom.nodelist) -- list of nodes to add
     ****
     */
    {
        var me = this;
        
        rows.forEach(function(node) {
            var opt = {};
            var tmp;
            
            if (node.hasAttribute('ltk:table') && (tmp = ltk.string.trim(node.getAttribute('ltk:table'))) != '') {
                opt = eval('(' + tmp + ')');
            }
            
            // table row modification
            if ('modify' in opt) {
                ltk.evt.addEvent(node, 'rgtclick', function() {
                    var contextmenu = getContextmenu();
                    contextmenu.populate(opt['modify']);
                    contextmenu.setVisible(true);
                    contextmenu.onAction = function(action) {
                        me.onAction(action, node);
                    }
                });
            }

            // table row drag-sorting
            if (!('sort' in opt)) return;

            me.rows.push(node);
            
            node.setData('ltk:table', opt);
            
            ltk.evt.addEvent(node, 'mouseover', (function() {
                var activated = me.activated;
                
                return function() {
                    if (me.activated != activated) {
                        if (me.activated) {
                            node.addClass('ltk_table_draggable');
                            activated = true;
                        } else {
                            node.removeClass('ltk_table_draggable');
                            activated = false;
                        }
                    }
                };
            })());
            
            node.addClass('ltk_table_draggable');
            
            var dnd = new ltk.evt.draggable(node);
            dnd.onMouseDown = function() {
                if (!me.activated) return;
                
                node.addClass('ltk_table_drag');
            }
            dnd.onMouseUp = function() {
                if (!me.activated) return;
                
                node.removeClass('ltk_table_drag');
            }
            dnd.onMouseMove = function() {
                if (!me.activated) return;
                
                var dir = ltk.evt.getMouseMoveDir();
                
                if (dir.y != 0) {
                    var type = node.getData('ltk:table').sort;
                    var row  = getTarget.call(me, ltk.evt.getMousePos().y, type);
                    var mov  = node;
            
                    if (row && row.node != node.node) {
                        if (type != 'row') {
                            mov = node.parentNode();
                            row = row.parentNode();
                        }
                        
                        if (dir.y < 0) {
                            row.insertBefore(mov);
                        } else {
                            row.insertAfter(mov);
                        }
                    }
                }
            }
        });
    }
    
    /****m* table/assimilate
     * SYNOPSIS
     */
    ltk.table.prototype.assimilate = function(node)
    /*
     * FUNCTION
     *      assimilate existing table
     * INPUTS
     *      * node (ltk.dom.node) -- node to assimilate
     ****
     */
    {
        var me = this;
        var tr = new ltk.dom.nodelist();
        
        for (var i = 0, len = node.node.tBodies.length; i < len; ++i) {
            tr.concat(node.node.tBodies[i].rows);
        }

        this.addNodes(tr);

        // assimilate children
        ltk.widget.prototype.assimilate.call(this, node);
    }
})();

/****c* ltk/textarea
 * NAME
 *      lima_ltk_textarea
 * FUNCTION
 *      textarea widget
 * COPYRIGHT
 *      copyright (c) 2008-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('textarea' in ltk) return;
    
    /****m* textarea/
     * SYNOPSIS
     */
    ltk.textarea = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
    }

    ltk.widget.register('ltk:textarea', ltk.textarea);

    ltk.textarea.prototype = new ltk.widget();

    /****v* textarea/cssclass
     * SYNOPSIS
     */
    ltk.textarea.prototype.cssclass = 'ltk_textarea'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****v* textarea/container
     * SYNOPSIS
     */
    ltk.textarea.prototype.container = 'TEXTAREA';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****m* textarea/getValue
     * SYNOPSIS
     */
    ltk.textarea.prototype.getValue = function()
    /*
     * FUNCTION
     *      get value of widget
     * OUTPUTS
     *      (string) -- widget value
     ****
     */
    {
        return this.widget.value;
    }

    /****m* textarea/populate
     * SYNOPSIS
     */
    ltk.textarea.prototype.populate = function(data)
    /*
     * FUNCTION
     *      populate widget
     * INPUTS
     *      * data (mixed) -- data to populate widget with
     ****
     */
    {
        this.widget.value = data;
    }

    /****m* textarea/attach
     * SYNOPSIS
     */
    ltk.textarea.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      build widget
     * INPUTS
     *      * parent (object) -- parent DOM node to attach widget to
     *      * def (array) -- widget definition
     * OUTPUTS
     *      
     ****
     */
    {
        var div = parent.appendChild(ltk.dom.create('DIV'));
        div.className = 'ltk_form_wrapper';

        this.widget = this.appendChild(
            div, 
            this.container, 
            def
        );
    
        if (typeof def['rows'] == 'number') {
            this.widget.setAttribute('rows', def['rows']);
        }
    }
})();

/****c* ltk/textedit
 * NAME
 *      lima_ltk_textedit
 * FUNCTION
 *      textedit widget -- enriched text edit with tabs support (using ALT+TAB) and
 *      auto-indenting
 * COPYRIGHT
 *      copyright (c) 2009-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('textedit' in ltk) return;
    
    /****m* textedit/
     * SYNOPSIS
     */
    ltk.textedit = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.tab_width = 2;
        this.caret_pos = null;
        this.textarea  = {};
        
        this.options   = {
            'versions_interval': 50
        };
    }

    ltk.widget.register('ltk:textedit', ltk.textedit);

    ltk.textedit.prototype = new ltk.widget();

    /****v* textedit/container
     * SYNOPSIS
     */
    ltk.textedit.prototype.container = 'DIV';
    /*
     * FUNCTION
     *      
     ****
     */

    /****v* textedit/cssclass
     * SYNOPSIS
     */
    ltk.textedit.prototype.cssclass = 'ltk_textedit';
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* textedit/getCaretPos
     * SYNOPSIS
     */
    ltk.textedit.prototype.getCaretPos = function()
    /*
     * FUNCTION
     *      determine caret position for textarea
     * OUTPUTS
     *      (int) -- caret position
     ****
     */
    {
        var pos = 0;
    
        if (document.selection) {
            // IE Support
            this.widget.focus();
        
            var sel = document.selection.createRange();
            sel.moveStart('character', -this.widget.value.length);
            pos = sel.text.length;
        } else if (this.widget.selectionStart || this.widget.selectionStart == '0'){
            // Firefox support
            pos = this.widget.selectionStart;
        }
    
        return pos;
    }

    /****m* textedit/setCaretPos
     * SYNOPSIS
     */
    ltk.textedit.prototype.setCaretPos = function(pos)
    /*
     * FUNCTION
     *      set caret position in textarea
     * INPUTS
     *      * pos (int) -- position to set caret to
     ****
     */
    {
        if(this.widget.setSelectionRange) {
            this.widget.focus();
            this.widget.setSelectionRange(pos, pos);
        } else if (this.widget.createTextRange) {
            var range = this.widget.createTextRange();
            range.collapse(true);
            range.moveEnd('character', pos);
            range.moveStart('character', pos);
            range.select();
        }
    }

    /****m* textedit/setCaretPosition
     * SYNOPSIS
     */
    //ltk.textedit.prototype.setCaretPosition = function(obj, pos)
    /*
     * FUNCTION
     *      set textarea caret position
     * INPUTS
     *      
     ****
     */
    /*{
        if (obj != null) {
            if(obj.createTextRange) {
                var range = obj.createTextRange();
                range.move('character', pos);
                range.select();
            } else if(obj.selectionStart) {
                obj.focus();
                obj.setSelectionRange(pos, pos);
            } else {
                obj.focus();
            }
        }
    }*/

    /****m* textedit/getValue
     * SYNOPSIS
     */
    ltk.textedit.prototype.getValue = function()
    /*
     * FUNCTION
     *      get value of widget
     * OUTPUTS
     *      (string) -- return textual content of widget
     ****
     */
    {
        return this.textarea.value;
    }

    /****m* textedit/populate
     * SYNOPSIS
     */
    ltk.textedit.prototype.populate = function(str)
    /*
     * FUNCTION
     *      fill widget with string
     * INPUTS
     *      * str (string) -- value to fill widget with
     ****
     */
    {
        this.textarea.value = str;
    }

    /****m* textedit/attach
     * SYNOPSIS
     */
    ltk.textedit.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach textedit widget
     ****
     */
    {
        var name = ltk.getUniqID('ltk_textedit_');
        var me   = this;

        var versions = new ltk.versions();

        this.setOptions(def['options']);

        function storeCaret() {
            if ('createTextRange' in me.textarea) {
                me.textarea.caretPos = document.selection.createRange().duplicate();
            }
        }

        function replace(text) {
            if ('caretPos' in me.textarea && 'createTextRange' in me.textarea) {
                // IE
                var caret = me.textarea.caretPos;

                caret.text = (caret.text.charAt(caret.text.length - 1) == ' ' 
                              ? text + ' ' 
                              : text);
                caret.select();
            } else if ('selectionStart' in me.textarea) {
                // mozilla
                var start  = me.textarea.value.substr(0, me.textarea.selectionStart);
                var end    = me.textarea.value.substr(me.textarea.selectionEnd);
                var scroll = me.textarea.scrollTop;

                me.textarea.value = start + text + end;

                if (me.textarea.setSelectionRange) {
                    me.textarea.focus();
                    me.textarea.setSelectionRange(start.length + text.length, start.length + text.length);
                }
            
                me.textarea.scrollTop = scroll;
            } else {
                // other
                me.textarea.value += text;
                me.textarea.focus(me.textarea.value.length - 1);
            }
        }

        function enclose(pre, post) {
            post = (typeof post != 'undefined' ? post : pre);
        
            if ('caretPos' in me.textarea && 'createTextRange' in me.textarea) {
                // IE
                var caret = me.textarea.caretPos;
                var len   = caret.text.length;

                caret.text = (caret.text.charAt(caret.text.length - 1) == ' ' 
                              ? pre + caret.text + post + ' ' 
                              : pre + caret.text + post);

                if (len == 0) {
                    caret.moveStart('character', -post.length);
                    caret.moveEnd('character', -post.length);
                    caret.select();
                } else {
                    me.textarea.focus(caret);
                }
            } else if ('selectionStart' in me.textarea) {
                var start     = me.textarea.value.substr(0, me.textarea.selectionStart);
                var end       = me.textarea.value.substr(me.textarea.selectionEnd);
                var selection = me.textarea.value.substr(me.textarea.selectionStart, me.textarea.selectionEnd - me.textarea.selectionStart);
                var pos       = me.textarea.selectionStart;
                var scroll    = me.textarea.scrollTop;

                me.textarea.value = start + pre + selection + post + end;

                if (me.textarea.setSelectionRange) {
                    if (selection.length == 0) {
                        me.textarea.setSelectionRange(pos + pre.length, pos + pre.length);
                    } else {
                        me.textarea.setSelectionRange(pos, pos + pre.length + selection.length + post.length);
                    }
                
                    me.textarea.focus();
                }
            
                me.textarea.scrollTop = scroll;
            } else {
                // other
                me.textarea.value += pre + post;
                me.textarea.focus(me.textarea.value.length - 1);
            }
        }

        def['children'] = [
            {'toolbar': {
                'items': [
                    {
                        'image':   'text_bold',
                        'onclick': function() { enclose('*'); }
                    }, {
                        'image':   'text_italic',
                        'onclick': function() { enclose('~'); }
                    }, {
                        'image':   'text_underline',
                        'onclick': function() { enclose('_'); }
                    }, {
                        'image':   'text_strikethrough',
                        'onclick': function() { enclose('-'); }
                    }, '-', {
                        'image':   'text_heading_1',
                        'onclick': function() { enclose('\n.h1 ', '\n'); }
                    }, {
                        'image':   'text_heading_2',
                        'onclick': function() { enclose('\n.h2 ', ''); }
                    }, {
                        'image':   'text_heading_3',
                        'onclick': function() { enclose('\n.h3 ', ''); }
                    }, '-', {
                        'image':   'text_list_numbers',
                        'onclick': function() { enclose('\n# ', '\n'); }
                    }, {
                        'image':   'text_list_bullets',
                        'onclick': function() { enclose('\n* ', '\n'); }
                    }, {
                        'image':   'text_horizontalrule',
                        'onclick': function() { replace('\n----\n'); }
                    }, '-', {
                        'image':   'image_add',
                        'onclick': function() {
                            var media_id = '';
                        
                            while (media_id != null && media_id.replace(/[^0-9]/g, '') === '') {
                                media_id = prompt('Media to include -- enter ID of media', media_id);
                            }
                        
                            if (media_id != null) {
                                replace('[media:' + media_id + ']');
                            }
                        }
                    }, '-',
                    {
                        'image':   'arrow_undo',
                        'onclick': function() { versions.undo(); }
                    },
                    {
                        'image':   'arrow_redo',
                        'onclick': function() { versions.redo(); }
                    }
                ]
            }},
            {'textarea': {
                'name': name,
                'rows': (typeof def['rows'] == 'number'
                         ? def['rows']
                         : 5)
            }}
        ];

        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );
    
        this.processChildren(def, function(parent, instance, def) {
            instance.attach(parent, def);
        });
    
        var obj = this.textarea = this.getDialog().getWidget(name).widget.node;
    
        var spc_width = '';
        var tab_width = ('tab_width' in def
                         ? def['tab_width']
                         : this.tab_width);

        for (var i = 0; i < tab_width; ++i) {
            spc_width += ' ';
        }
 
        /** handle caret in various cases **/
        obj.onselect = function() { storeCaret(); }
        obj.onclick  = function() { storeCaret(); }
        obj.onkeyup  = function() { storeCaret(); }
        obj.onchange = function() { storeCaret(); }
 
        /** handle undo buffer **/
        window.setInterval(function() {
            var size = versions.getSize();
            var item = versions.getItem();
            var text = me.textarea.value;
            var redo, undo;
            
            if (size > 0 && item && item.data.value == text) return;
            
            if (!item) {
                undo = function() { me.textarea.value = ''; };
            } else {
                undo = item.redo; 
            }
            
            versions.push(
                function(data) {
                    me.textarea.value = data.value;
                },
                undo,
                {'value': text},
                false
            );
        }, this.options.versions_interval);
    }
})();


/****c* ltk/dialog
 * NAME
 *      lima_ltk_dialog
 * FUNCTION
 *      ltk dialog
 * COPYRIGHT
 *      copyright (c) 2008-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('dialog' in ltk) return;
    
    /****m* dialog/
     * SYNOPSIS
     */
    ltk.dialog = function()
    /*
     * FUNCTION
     *      dialog constructor
     ****
     */
    {
        this.refs     = {};     // named references to widgets
        this.children = [];
    
        this.label    = null;   // label node
    
        this.options  = {
            'label': false      // whether dialog has a label
        };   
    }

    ltk.widget.register('ltk:dialog', ltk.dialog);

    ltk.dialog.prototype = new ltk.widget();

    /****v* dialog/cssclass
     * SYNOPSIS
     */
    ltk.dialog.prototype.cssclass = 'ltk_dialog'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* dialog/destroy
     * SYNOPSIS
     */
    ltk.dialog.prototype.destroy = function()
    /*
     * FUNCTION
     *      destruct and remove child widgets
     ****
     */
    {
        ltk.widget.prototype.destroy.call(this);
    
        this.widget.removeChild();
    }

    /****m* dialog/getDialog
     * SYNOPSIS
     */
    ltk.dialog.prototype.getDialog = function()
    /*
     * FUNCTION
     *      return instance of dialog
     ****
     */
    {
        return this;
    }

    /****m* dialog/addReference
     * SYNOPSIS
     */
    ltk.dialog.prototype.addReference = function(name, widget)
    /*
     * FUNCTION
     *      adds named reference of widget
     * INPUTS
     *      * name (string) -- name the widget should be referenced by
     *      * widget (object) -- instance widget object to add
     ****
     */
    {
        this.refs[name] = widget;
    }

    /****m* dialog/removeReference
     * SYNOPSIS
     */
    ltk.dialog.prototype.removeReference = function(name)
    /*
     * FUNCTION
     *      remove named reference of widget
     * INPUTS
     *      * name (string) -- name of the widget to remove
     ****
     */
    {
        if (name in this.refs) {
            delete this.refs[name];
        }
    }

    /****m* dialog/getWidget
     * SYNOPSIS
     */
    ltk.dialog.prototype.getWidget = function(name)
    /*
     * FUNCTION
     *      returns reference to a widget, specified by name
     ****
     */
    {
        var ret = null;
    
        if (name in this.refs) {
            ret = this.refs[name];
        } else {
            ltk.backtrace();
            throw 'unable to reference widget identified by name "' + name + '"!';
        }
    
        return ret;
    }

    /****m* dialog/getValue
     * SYNOPSIS
     */
    ltk.dialog.prototype.getValue = function()
    /*
     * FUNCTION
     *      collect all values of the dialog/form and return array
     ****
     */
    {
        var ret = {};
        var tmp;
    
        for (var i in this.refs) {
            if (this.refs[i] == this) {
                continue;
            }

            tmp = this.refs[i].getValue();
        
            if (tmp != null) {
                ret[i] = tmp;
            }
        }
    
        return ret;
    }

    /****m* dialog/setVisible
     * SYNOPSIS
     */
    ltk.dialog.prototype.setVisible = function(visible)
    /*
     * FUNCTION
     *      show / hide dialog
     * INPUTS
     *      * visible (bool) -- whether to show/hide dialog
     ****
     */
    {
        this.widget.node.style.display = (!!visible ? 'block' : 'none');
    }

    /****m* dialog/isVisible
     * SYNOPSIS
     */
    ltk.dialog.prototype.isVisible = function()
    /*
     * FUNCTION
     *      get visibility of dialog
     * OUTPUTS
     *      (bool) -- returns true, if dialog is visibile otherweise false
     ****
     */
    {
        var display = this.widget.node.style.display;
        
        return !(display == 'none');
    }

    /****m* dialog/setLabel
     * SYNOPSIS
     */
    ltk.dialog.prototype.setLabel = function(label)
    /*
     * FUNCTION
     *      change label of dialog (if a label is defined)
     * INPUTS
     *      * label (string) -- new label to set
     * OUTPUTS
     *      (string) -- old label
     ****
     */
    {
        var r = '';
    
        if (this.options.label) {
            r = this.label.node.innerHTML;
            this.label.node.innerHTML = label;
        }
    
        return r;
    }

    /****m* dialog/populate
     * SYNOPSIS
     */
    ltk.dialog.prototype.populate = function(data)
    /*
     * FUNCTION
     *      populate dialog with data
     ****
     */
    {
        for (var i in data) {
            if (i in this.refs && this.refs[i] != this) {
                if ('innerHTML' in this.refs[i]) {
                    this.refs[i].innerHTML = data[i];
                } else if ('populate' in this.refs[i]) {
                    this.refs[i].populate(data[i]);
                }
            }
        }
    }

    /****m* dialog/reset
     * SYNOPSIS
     */
    ltk.dialog.prototype.reset = function()
    /*
     * FUNCTION
     *      clear all elements in a dialog from data
     ****
     */
    {
        for (var i in this.refs) {
            if ('innerHTML' in this.refs[i]) {
                this.refs[i].innerHTML = '';
            } else {
                this.refs[i].populate('');
            }
        }
    }

    /****m* dialog/onLoad
     * SYNOPSIS
     */
    ltk.dialog.prototype.onLoad = function()
    /*
     * FUNCTION
     *      gets executed, after dialog was built
     ****
     */
    {
    }

    /****m* dialog/assimilate
     * SYNOPSIS
     */
    ltk.dialog.prototype.assimilate = function(node)
    /*
     * FUNCTION
     *      assimilate node as dialog
     * INPUTS
     *      * node (ltk.dom.node) -- node to assimilate
     ****
     */
    {
        this.widget = node;
        
        // assimilate children
        ltk.widget.prototype.assimilate.call(this, node);
    }
    
    /****m* dialog/attach
     * SYNOPSIS
     */
    ltk.dialog.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach button widget
     ****
     */
    {
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );    
    
        if ('rounded' in def && def['rounded']) {
            this.widget.addClass('ltk_dialog_rounded');
        }
    
        if ('visible' in def && !def['visible']) {
            this.widget.setStyle('display', 'none');
        }
    
        if ('shadow' in def && def['shadow']) {
            this.widget.addClass('ltk_dialog_shadow');
        }
    
        if ((this.options.label && 'label' in def)) {
            this.label = this.widget.appendChild(ltk.dom.create('label', {
                'class': 'ltk_dialog_label',
                '#html': 'label'
            }));
        }
    
        var div = ltk.dom.create('div');
        div.node.className = 'ltk_dialog_container';
        this.widget.appendChild(div);
    
        this.processChildren(def, function(parent, instance, def) {
            instance.attach(parent, def);
        }, div);
    
        this.onLoad();
    }
})();

/****c* ltk/window
 * NAME
 *      lima_ltk_window
 * FUNCTION
 *      ltk window
 * COPYRIGHT
 *      copyright (c) 2009-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('window' in ltk) return;
    
    /****m* window/
     * SYNOPSIS
     */
    ltk.window = function()
    /*
     * FUNCTION
     *      window constructor
     ****
     */
    {
        this.refs     = {};     // named references to widgets
        this.children = [];
        this.id       = ltk.getUniqID('ltk_window_');

        this.posXY = null;
        this.layer = new ltk.dom.layer();
    }

    ltk.window.prototype = new ltk.dialog();

    /****v* window/cssclass
     * SYNOPSIS
     */
    ltk.window.prototype.cssclass = 'ltk_window'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* window/setPosXY
     * SYNOPSIS
     */
    ltk.window.prototype.setPosXY = function(x, y)
    /*
     * FUNCTION
     *      set window position
     * INPUTS
     *      * x (int) -- x position (top left corner)
     *      * y (int) -- y position (top left corner)
     ****
     */
    {
        this.widget.node.style.top  = y + 'px';
        this.widget.node.style.left = x + 'px';

        this.posXY = {'x': x, 'y': y};
    }

    /****m* window/toggleWidgets
     * SYNOPSIS
     */
    ltk.dialog.prototype.toggleWidgets = function(visible)
    /*
     * FUNCTION
     *      this function toggles visibility of special widgets which might cause problems
     *      in IE when window is set visibility hidden, like the select box.
     * INPUTS
     *      * visible (bool) -- whether to show/hide window
     ****
     */
    {
        for (var i in this.refs) {
            if (this.refs[i] == this || !('container' in this.refs[i])) {
                continue;
            }

            if (this.refs[i].container == 'SELECT') {
                this.refs[i].widget.node.style.display = (!!visible ? 'block' : 'none');
            }
        }
    }

    /****m* window/setVisible
     * SYNOPSIS
     */
    ltk.window.prototype.setVisible = function(visible)
    /*
     * FUNCTION
     *      show / hide window
     * INPUTS
     *      * visible (bool) -- whether to show/hide window
     ****
     */
    {
        if (visible && this.posXY == null) {
            // center window on first display
            var view = ltk.dom.getViewport();
            var w    = this.widget.node.offsetWidth;
            var h    = this.widget.node.offsetHeight;

            var x = Math.floor((view.w - w) / 2);
            var y = Math.floor((view.h - h) / 2);

            this.setPosXY(x, y);
        }

        this.toggleWidgets(visible);

        this.widget.node.style.visibility = (!!visible ? 'visible' : 'hidden');
    }

    /****m* window/isVisible
     * SYNOPSIS
     */
    ltk.window.prototype.isVisible = function()
    /*
     * FUNCTION
     *      show visibility state
     * OUTPUTS
     *      (bool) -- returns true, if state is visible
     ****
     */
    {
        var visibility = this.widget.node.style.visibility;
        
        return !(visibility == 'hidden');
    }

    /****m* window/selectWindow
     * SYNOPSIS
     */
    ltk.window.prototype.selectWindow = function()
    /*
     * FUNCTION
     *      select this window instance -- modifies z-index of window to move
     *      it to the top and lower all other windows
     ****
     */
    {
        this.layer.up();
    }

    /****m* window/attach
     * SYNOPSIS
     */
    ltk.window.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach button widget
     ****
     */
    {
        var raster = 1;
        var me     = this;

        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );    

        this.layer.push([this.widget]);

        this.widget.addClass('ltk_dialog');

        if ('rounded' in def && def['rounded']) {
            this.widget.addClass('ltk_dialog_rounded');
        }

        if ('shadow' in def && def['shadow']) {
            this.widget.addClass('ltk_dialog_shadow');
        }

        var label = ltk.dom.create('label');
        label.node.className = 'ltk_dialog_label';
        label.node.innerHTML = def['label'];
        this.widget.appendChild(label);

        var div = ltk.dom.create('div');
        div.node.className = 'ltk_dialog_container';
        div.node.style.position = 'relative';

        this.widget.node.style.visibility = 'hidden';    
        this.widget.appendChild(div);

        if ('draggable' in def && def['draggable']) {
            ltk.evt.disableTextSelect(label);
            label.addClass('ltk_draggable');

            var evt1 = new ltk.evt.draggable(this.widget, raster, label);
            var vec1 = evt1.onmousedown; 
            evt1.onmousedown = function(e) {
                vec1.apply(evt1, [e]);
                me.selectWindow();
            }
        }

        if ('resizable' in def && def['resizable']) {
            var resizer = ltk.dom.create('div');
            resizer.node.className = 'ltk_resizable';
            this.widget.appendChild(resizer);

            var img = ltk.dom.create('img');
            img.node.src = '/resources_ltk/images/resize.gif';
            resizer.appendChild(img);

            ltk.evt.disableTextSelect(img);

            var evt2 = new ltk.evt.resizable(this.widget, raster, img);
            var vec2 = evt2.onmousedown; 
            evt2.onmousedown = function(e) {
                vec2.apply(evt2, [e]);
                me.selectWindow();
            }
        }

        this.processChildren(def, function(parent, instance, def) {
            instance.attach(parent, def);
        }, div);

        if ('visible' in def && def['visible']) {
            this.setVisible(true);
        }

        this.onLoad();
    }
})();

/****c* ltk/modal
 * NAME
 *      lima_ltk_modal
 * FUNCTION
 *      ltk modal
 * COPYRIGHT
 *      copyright (c) 2007-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('modal' in ltk) return;
    
    /*
     * calculate (centered) position of modal window, e.g. to update position on window resize
     */
    function calcWindowPos(div) {
        var body = ltk.dom.one('BODY');
        var vp   = ltk.dom.getViewport();

        var h = vp.h;
        var w = vp.w;
    
        var left = (window.XMLHttpRequest == null 
                    ? ((e = document.documentElement.scrollLeft)
                        ? e : document.body.scrollLeft)
                    : 0);
        var top  = (window.XMLHttpRequest == null 
                    ? ((e = document.documentElement.scrollTop)
                        ? e : document.body.scrollTop)
                    : 0); 
    
        return {
            'x': Math.max((left + (w - div.node.offsetWidth) / 2), 0),
            'y': Math.max((top + (h - div.node.offsetHeight) / 2), 0)
        };
    }
    
    /*
     * set x/y position of modal window
     */
    function setWindowPos(div, xy) {
        div.node.style.left = xy.x + 'px';
        div.node.style.top  = xy.y + 'px';
    }
    
    /****m* modal/
     * SYNOPSIS
     */
    ltk.modal = function()
    /*
     * FUNCTION
     *      modal constructor
     ****
     */
    {
        this.layer      = new ltk.dom.layer();
        this.overlay    = null;
        this._container = null;
    }

    ltk.modal.prototype = new ltk.window();

    /****v* modal/cssclass
     * SYNOPSIS
     */
    ltk.modal.prototype.cssclass = 'ltk_modal'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* modal/hide
     * SYNOPSIS
     */
    ltk.modal.prototype.hide = function() 
    /*
     * FUNCTION
     *      hide modal dialog window
     ****
     */
    {
        this.setVisible(false);
        this.overlay.hide();
    }

    /****m* modal/show
     * SYNOPSIS
     */
    ltk.modal.prototype.show = function() 
    /*
     * FUNCTION
     *      show modal dialog window
     ****
     */
    {
        this.overlay.show();
        this.setVisible(true);
        this.layer.up();
    }

    /****m* widget/getSpinner
     * SYNOPSIS
     */
    ltk.modal.prototype.getSpinner = function()
    /*
     * FUNCTION
     *      overwrites getSpinner from superclass widget, because a spinner must be attached different in here.
     * INPUTS
     *
     * OUTPUTS
     *
     ****
     */
    {
        var spinner = null;
        var me      = this;

        this.getSpinner = (function() {
            spinner = new ltk.spinner();
            spinner.attach(me._container, {});

            return function() {
                return spinner;
            }
        })();

        return spinner;
    }

    /****m* modal/attach
     * SYNOPSIS
     */
    ltk.modal.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach button widget
     ****
     */
    {
        // widget
        var me = this;
    
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );    
    
        this.widget.addClass('ltk_dialog');
    
        if ('rounded' in def && def['rounded']) {
            this.widget.addClass('ltk_modal_rounded');
        }
    
        if ('shadow' in def && def['shadow']) {
            this.widget.addClass('ltk_dialog_shadow');
        }
    
        var label = ltk.dom.create('label');
        label.node.className = 'ltk_dialog_label';
        label.node.innerHTML = def['label'];
        this.widget.appendChild(label);
    
        var div = ltk.dom.create('div');
        div.node.className = 'ltk_dialog_container';
        this._container = this.widget.appendChild(div);
    
        if (ltk.browser.msie6 && 'setExpression' in this.widget.node.style) {
            this.widget.node.style.position = 'absolute';
        
            this.widget.node.style.setExpression('left', function() { return calcWindowPos(me.widget).x + 'px'; });
            this.widget.node.style.setExpression('top', function() { return calcWindowPos(me.widget).y + 'px'; });
        }

        this.processChildren(def, function(parent, instance, def) {
            instance.attach(parent, def);
        }, div);
    
        // overlay
        this.overlay = new ltk.overlay();
        this.overlay.attach(null, {});
        this.overlay.resize = function() {
            ltk.overlay.prototype.resize.call(me.overlay);
            
            setWindowPos(me.widget, calcWindowPos(me.widget));
        }
    
        // initialize layer manager
        this.layer.push([this.overlay.widget, this.widget]);
    
        // send notification, that dialog is loaded
        this.onLoad();
    }
})();

/****c* ltk/application
 * NAME
 *      lima_ltk_application
 * FUNCTION
 *      provides special application container, which can only be instantiated one time
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('application' in ltk) return;
    
    /****m* application/
     * SYNOPSIS
     */
    ltk.application = (function()
    /*
     * FUNCTION
     *      application constructor
     ****
     */
    {
        var instance = null;
        var dialog   = null;

        var styles = {
            'height':       '100%', 
            'width':        '100%',
            'marginTop':    '0',
            'marginLeft':   '0',
            'marginRight':  '0',
            'marginBottom': '0'
        };
    
        return function() {
            if (instance != null) {
                throw 'Application can only be instantiated one time!';
            } else {
                // create application
                instance = this;

                var html = ltk.dom.one('html');
                var body = ltk.dom.one('body');
            
                // create instance of dialog which will append child always to document body
                dialog = new ltk.dialog();
                dialog.cssclass = 'ltk_application';
                dialog.attach = function(parent, def) {
                    ltk.dialog.prototype.attach.call(dialog, body, def);
                }
            
                // prepare body to be a fullscreen browser app
                html.setStyles(styles);
                body.setStyles(styles);
            
                // return dialog instance instead of application instance
                return dialog;
            }
        }
    })();
})();

/****c* ltk/dataentry
 * NAME
 *      lima_ltk_dataentry
 * FUNCTION
 *      ltk dataentry
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('dataentry' in ltk) return;
    
    /****m* dataentry/
     * SYNOPSIS
     */
    ltk.dataentry = function()
    /*
     * FUNCTION
     *      dataentry constructor
     ****
     */
    {
    }

    ltk.dataentry.prototype = new ltk.modal();

    /****d* dataentry/T_OK, T_CANCEL
     * SYNOPSIS
     */
    ltk.dataentry.action = {
        'T_OK':     -1,
        'T_CANCEL': -2,
    }
    /*
     * FUNCTION
     *      action button, that was clicked
     ****
     */

    /****m* dataentry/onAction
     * SYNOPSIS
     */
    ltk.dataentry.prototype.onAction = function(action)
    /*
     * FUNCTION
     *      get's called, when a prompt button is clicked
     * INPUTS
     *      * action (int) -- value of action button, that was clicked
     ****
     */
    {
    }

    /****m* dataentry/attach
     * SYNOPSIS
     */
    ltk.dataentry.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach button widget
     ****
     */
    {
        var me = this;
        
        if ('onAction' in def) {
            this.onAction = def['onAction'];
        }

        def['children'] = [
            {'vbox': {
                'children': def['children'] || []
            }},
            {'hbox': {
                'width':    [30, 30, 30],
                'children': [
                    {'html:span': {}},
                    {'button': {
                        'label':   _('Ok'),
                        'onclick': function() { me.hide(); me.onAction(ltk.dataentry.action.T_OK); }
                    }},
                    {'button': {
                        'label':   _('Cancel'),
                        'onclick': function() { me.hide(); me.onAction(ltk.dataentry.action.T_CANCEL); }
                    }}
                ]
            }}
        ];

        ltk.modal.prototype.attach.call(this, parent, def);
    }
})();

/****c* ltk/panel
 * NAME
 *      lima_ltk_panel
 * FUNCTION
 *      ltk panel
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('panel' in ltk) return;
    
    /****m* panel/
     * SYNOPSIS
     */
    ltk.panel = function()
    /*
     * FUNCTION
     *      panel constructor
     ****
     */
    {
        this.refs     = {};     // named references to widgets
        this.children = [];
    }

    ltk.panel.prototype = new ltk.dialog();

    /****v* panel/cssclass
     * SYNOPSIS
     */
    ltk.panel.prototype.cssclass = 'ltk_panel'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* panel/attach
     * SYNOPSIS
     */
    ltk.panel.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach button widget
     ****
     */
    {
        var me = this;
    
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );    
    
        this.widget.addClass('ltk_dialog');
    
        if ('rounded' in def && def['rounded']) {
            this.widget.addClass('ltk_dialog_rounded');
        }
    
        if ('shadow' in def && def['shadow']) {
            this.widget.addClass('ltk_dialog_shadow');
        }
    
        var label = document.createElement('label');
        label.className = 'ltk_dialog_label';
        label.innerHTML = def['label'];
        this.widget.appendChild(label);
    
        var div = this.widget.appendChild(document.createElement('div'));
        div.className = 'ltk_dialog_container';
        div.style.position = 'relative';

        this.processChildren(def, function(parent, instance, def) {
            instance.attach(parent, def);
        }, div);
    
        this.onLoad();
    }
})();


/****c* ltk/grid
 * NAME
 *      lima_ltk_grid
 * FUNCTION
 *      layout grid
 * COPYRIGHT
 *      copyright (c) 2008-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 * EXAMPLE
 *      {'grid': {
 *          'columns': ...,     // number of columns
 *          'width': [...],     // column width (array)
 *          'separator': ...,   // column separator
 *          'header': ...,      // number of header rows
 *          'children': []      // child elements
 *      }}
 ****
 */

;(function() {
    if ('grid' in ltk) return;
    
    /****m* grid/
     * SYNOPSIS
     */
    ltk.grid = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.children = [];
    }

    ltk.widget.register('ltk:grid', ltk.grid);

    ltk.grid.prototype = new ltk.widget();

    /****v* grid/cssclass
     * SYNOPSIS
     */
    ltk.grid.prototype.cssclass = 'ltk_grid';
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****v* grid/container
     * SYNOPSIS
     */
    ltk.grid.prototype.container = 'TABLE';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****m* grid/onRenderRow
     * SYNOPSIS
     */
    ltk.grid.prototype.onRenderRow = function(row_no, row_obj)
    /*
     * FUNCTION
     *      adds possibility to enhance row element eg. with events, styles etc.
     * INPUTS
     *      * row_no (int) -- number of current row
     *      * row_obj (object) -- DOM object node of current row
     ****
     */
    {
    }

    /****m* grid/onRenderCell
     * SYNOPSIS
     */
    ltk.grid.prototype.onRenderCell = function(row_no, col_no, cell_obj)
    /*
     * FUNCTION
     *      adds possibility to enhance cell element eg. with events, styles etc.
     * INPUTS
     *      * row_no (int) -- number of current row
     *      * col_no (int) -- number of current column
     *      * cell_obj (object) -- DOM object node of current cell
     ****
     */
    {
    }

    /****m* grid/attach
     * SYNOPSIS
     */
    ltk.grid.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach grid widget
     ****
     */
    {
        var width = ('width' in def && def['width'] instanceof Array
                     ? def['width'] 
                     : []);
        var cols  = ('columns' in def
                     ? def['columns']
                     : 1);
           
        // render
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );    
    
        var part = ltk.dom.create('TBODY');
        var rows = Math.ceil(def['children'].length / cols);
        var row  = 0;
        var i    = 0;
        var me   = this;
        var tr;

        this.widget.appendChild(part);
    
        this.processChildren(def, function(parent, instance, def) {
            var cell = (i % cols);
            var td;
        
            if (cell == 0) {
                ++row;
                tr = ltk.dom.create('tr');
            
                me.onRenderRow(row, tr);
            }
        
            td = ltk.dom.create('td');
            td.setAttribute('valign', 'top');
        
            if (cell in width) {
                td.setAttribute('width', width[cell] + '%');
            }
        
            me.onRenderCell(row, (cell + 1), td);
        
            tr.appendChild(td);
        
            parent.appendChild(tr);

            instance.attach(td, def);

            ++i;
        }, part);
    }
})();

/****c* ltk/hbox
 * NAME
 *      lima_ltk_hbox
 * FUNCTION
 *      container for horizontal layouts
 * COPYRIGHT
 *      copyright (c) 2008-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('hbox' in ltk) return;
    
    /****m* hbox/
     * SYNOPSIS
     */
    ltk.hbox = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.children = [];
    }

    ltk.widget.register('ltk:hbox', ltk.hbox);

    ltk.hbox.prototype = new ltk.grid();

    /****v* hbox/cssclass
     * SYNOPSIS
     */
    ltk.hbox.prototype.cssclass = 'ltk_hbox';
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* hbox/attach
     * SYNOPSIS
     */
    ltk.hbox.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach hbox widget
     ****
     */
    {
        def['columns'] = def['children'].length;
    
        if (!('width' in def && def['width'] instanceof Array)) {
            def['width'] = [];
        }

        ltk.grid.prototype.attach.call(this, parent, def);
    }
})();

/****c* ltk/vbox
 * NAME
 *      lima_ltk_vbox
 * FUNCTION
 *      container for vertical layouts
 * COPYRIGHT
 *      copyright (c) 2009-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('vbox' in ltk) return;
    
    /****m* vbox/
     * SYNOPSIS
     */
    ltk.vbox = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.children = [];
    }

    ltk.widget.register('ltk:vbox', ltk.vbox);

    ltk.vbox.prototype = new ltk.widget();

    /****v* vbox/cssclass
     * SYNOPSIS
     */
    ltk.vbox.prototype.cssclass = 'ltk_vbox'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* vbox/attach
     * SYNOPSIS
     */
    ltk.vbox.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach grid widget
     ****
     */
    {
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );    
    
        this.processChildren(def, function(parent, instance, def) {
            var row = parent.appendChild(ltk.dom.create('div'));
        
            instance.attach(row, def);
        }, this.widget);
    }
})();

/****c* ltk/group
 * NAME
 *      lima_ltk_group
 * FUNCTION
 *      group container
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('group' in ltk) return;
    
    /****m* group/
     * SYNOPSIS
     */
    ltk.group = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.children = [];
    }

    ltk.widget.register('ltk:group', ltk.group);

    ltk.group.prototype = new ltk.widget();

    /****v* group/container
     * SYNOPSIS
     */
    ltk.group.prototype.container = 'FIELDSET';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****v* group/cssclass
     * SYNOPSIS
     */
    ltk.group.prototype.cssclass = 'ltk_group'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* group/setDisabled
     * SYNOPSIS
     */
    ltk.group.prototype.setDisabled = function(disabled)
    /*
     * FUNCTION
     *      set widget and all child widgets to disabled
     * INPUTS
     *      * disabled (bool) -- wheter to disable/enable widget
     ****
     */
    {
        disabled = !!disabled;
    
        console.log('group disable ', disabled)
    
        if (this.disabled == disabled) {
            return;
        }
    
        this.disabled = disabled;
    
        if (this.disabled) {
            this.widget.addClass('ltk_disabled');
        } else {
            this.widget.removeClass('ltk_disabled');
        }
    
        for (var i = 0, cnt = this.children.length; i < cnt; ++i) {
            this.children[i].setDisabled(disabled);
        }
    }

    /****m* group/attach
     * SYNOPSIS
     */
    ltk.group.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach group widget
     ****
     */
    {
        var group = {'children': []};

        if (typeof def['name'] == 'string') {
            group['name'] = def['name'];
            delete def['name'];
        }
    
        if (typeof def['label'] == 'string') {
            group.children.push({'html:legend': {'#html': def['label']}});
            delete def['label'];
        } else {
            group.children.push({'html:legend': ''});
        }

        if (typeof def['columns'] != 'number') {
            def['columns'] = 1;
        }

        group.children.push({'grid': def});

        ltk.widget.prototype.attach.call(this, parent, group);
    }
})();

/****c* ltk/list
 * NAME
 *      ltk.nestedlist
 * FUNCTION
 *      nested list widget with draggable items
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('nestedlist' in ltk) return;
    
    /****m* nestedlist/
     * SYNOPSIS
     */
    ltk.nestedlist = function()
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
    }

    ltk.widget.register('ltk:nestedlist', ltk.nestedlist);

    ltk.nestedlist.prototype = new ltk.widget();

    /****v* nestedlist/cssclass
     * SYNOPSIS
     */
    ltk.nestedlist.prototype.cssclass = 'ltk_nestedlist'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****v* nestedlist/container
     * SYNOPSIS
     */
    ltk.nestedlist.prototype.container = 'UL';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****m* nestedlist/processChildren
     * SYNOPSIS
     */
    ltk.nestedlist.prototype.processChildren = function(def, processor, parent)
    /*
     * FUNCTION
     *      initiate building of child widgets
     * INPUTS
     *      * children (array) -- child widgets
     *      * processor (callback) -- callback method for processing child widgets
     *      * parent (object) -- (optional) parent DOM node to use instead of widget
     ****
     */
    {
        var me = this;
    
        var _process = function(parent, def) {
            if (!('children' in def)) return;
        
            var j, child, widget, instance, dissected, tag;

            for (var i = 0, cnt = def['children'].length; i < cnt; ++i) {
                child = null;

                for (j in def['children'][i]) {
                    child = def['children'][i][j];
                    break;
                }

                if (child == null) {
                    // no child element
                    continue;
                }

                dissected = ltk.widget.dissectTag(j);

                if (dissected.ns == 'html') {
                    // namespace html
                    tag = {
                        'node': ltk.dom.create(dissected.type),
                        'attach': function(parent, def) {
                            parent.appendChild(this.node);

                            this.node.setProperties(def);
                        
                            if ('name' in def) {
                                me.getDialog().addReference(def['name'], this.node);
                            }
                        }
                    }

                    processor(parent, tag, child);
                    
                    continue;
                }

                // ltk or other namespace
                if (!(dissected.ns in ltk.widget.registry)) {
                    throw 'unknwon namespace "' + dissected.ns + '"';
                } else if (!(dissected.type in ltk.widget.registry[dissected.ns])) {
                    throw 'unknown widget type "' + dissected.type + '" in namespace "' + dissected.ns + '"';
                }

                widget   = ltk.widget.registry[dissected.ns][dissected.type];
                instance = me.addChild(new widget());
    
                if ('name' in child) {
                    instance.setName(child['name']);
                }

                processor(parent, instance, child);
            }
        }
    
        if (typeof parent == 'undefined') {
            parent = this.widget;
        }
    
        _process(parent, def);
    }

    /****m* nestedlist/attach
     * SYNOPSIS
     */
    ltk.nestedlist.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      build nestedlist layout
     ****
     */
    {
        // process children
        var me = this;

        this.processChildren(def, function(parent, instance, def) {
            instance.attach(parent, def);
        });
    }
})();

/****c* ltk/splitter
 * NAME
 *      lima_ltk_splitter
 * FUNCTION
 *      layout splitter
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('splitter' in ltk) return;
    
    /****m* splitter/
     * SYNOPSIS
     */
    ltk.splitter = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.children = [];
    
        this.type = null;       // type of splitter (horizontal/vertical)
    
        this.size    = 0;       // total size of widget (width -or- height)
        this.handles = [];      // split handles
        this.panes   = [];      // inner elements
        this.content = [];      // inner content containers
    }

    ltk.widget.register('ltk:splitter', ltk.splitter);

    ltk.splitter.prototype = new ltk.widget();

    /****d* splitter/handlesize
     * SYNOPSIS
     */
    ltk.splitter.handlesize = 6;
    /*
     * FUNCTION
     *      size of splitter handle in pixel (width -or- height)
     ****
     */

    /****d* splitter/minsize
     * SYNOPSIS
     */
    ltk.splitter.minsize = 50;
    /*
     * FUNCTION
     *      minimal size of a split pane element
     ****
     */

    /****d* splitter/T_SPLIT_H, T_SPLIT_V
     * SYNOPSIS
     */
    ltk.splitter.T_SPLIT_H = 1;
    ltk.splitter.T_SPLIT_V = 2;
    /*
     * FUNCTION
     *      type of splitter: vertical or horizontal splitting
     ****
     */

    /****v* splitter/cssclass
     * SYNOPSIS
     */
    ltk.splitter.prototype.cssclass = 'ltk_splitter'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****v* splitter/container
     * SYNOPSIS
     */
    ltk.splitter.prototype.container = 'DIV';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****m* splitter/setDraggable
     * SYNOPSIS
     */
    ltk.splitter.prototype.setDraggable = function(n)
    /*
     * FUNCTION
     *      allows to change draggable setting for a splitter
     * INPUTS
     *      * n (int) -- number of column to change draggable setting of
     ****
     */
    {
        var me  = this;
        var dir = (this.type == ltk.splitter.T_SPLIT_H
                    ? lima.events.draggable.T_DIR_X
                    : lima.events.draggable.T_DIR_Y);
    
        var evt = new lima.events.draggable(this.handles[n], null, null, dir);
        var vec1;
    
        lima.addClass(me.handles[n], 'ltk_splitter_handle_draggable');
    
        if (this.type == ltk.splitter.T_SPLIT_H) {
            // overwrite onMouseMove to resize split pane columns
            evt.onMouseMove = function(e) {
                var left = (parseInt(me.handles[n].style.left, 10) + ltk.splitter.handlesize);
            
                me.panes[n + 1].style.left = left + 'px';
                me.panes[n].style.right    = (me.size - (left - ltk.splitter.handlesize)) + 'px';
            }

            // overwrite onmousedown to add additional styles and define the range the splitter may be moved in
            vec1 = evt.onmousedown;
            evt.onmousedown = function(e) {
                var x1 = parseInt(me.panes[n].style.left, 10) + ltk.splitter.minsize;
                var x2 = (me.size - parseInt(me.panes[n + 1].style.right, 10)) - ltk.splitter.minsize;
            
                evt.setRange(x1, null, x2, null);

                vec1.apply(evt, [e]);

                lima.addClass(me.handles[n], 'ltk_splitter_handle_drag');
            }
        } else {
            // overwrite onMouseMove to resize split pane columns
            evt.onMouseMove = function(e) {
                var top = (parseInt(me.handles[n].style.top, 10) + ltk.splitter.handlesize);
            
                me.panes[n + 1].style.top = top + 'px';
                me.panes[n].style.bottom  = (me.size - (top + ltk.splitter.handlesize)) + 'px';
            }

            // overwrite onmousedown to add additional styles and define the range the splitter may be moved in
            vec1 = evt.onmousedown;
            evt.onmousedown = function(e) {
                var y1 = parseInt(me.panes[n].style.top, 10) + ltk.splitter.minsize;
                var y2 = (me.size - parseInt(me.panes[n + 1].style.bottom, 10)) - ltk.splitter.minsize;
            
                evt.setRange(null, y1, null, y2);

                vec1.apply(evt, [e]);

                lima.addClass(me.handles[n], 'ltk_splitter_handle_drag');
            }
        }

        // overwrite onmouseup to remove additional styles
        var vec2 = evt.onmouseup;
        evt.onmouseup = function(e) {
            vec2.apply(evt, [e]);

            lima.removeClass(me.handles[n], 'ltk_splitter_handle_drag');
        }
    }

    /****m* splitter/calcSizes
     * SYNOPSIS
     */
    ltk.splitter.prototype.calcSizes = function(e, total)
    /*
     * FUNCTION
     *      calculate sizes of each element
     * INPUTS
     *      * e (array) -- splitter columns -or- rows
     *      * total (int) -- total size width -or- height of splitter pane
     * OUTPUTS
     *      (array) -- width of all splitter columns -or- rows
     ****
     */
    {
        var handle = Math.floor(ltk.splitter.handlesize / 2);
        var max    = 0;
        var sizes  = [];

        // calculate max size of all elements, which have width attribute
        for (var i = 0, cnt = div = e.length; i < cnt; ++i) {
            if (typeof e[i]['size'] == 'number' && e[i]['size'] > ltk.splitter.minsize) {
                max += e[i]['size'];
                --div;
            }
        }
    
        // reset max, if max is bigger than total size of splitter pane
        max = (max > total ? 0 : max);
    
        // calculate width of elements without own width attribute
        var s = Math.floor((total - max) / div);
        var h = handle;
    
        // build width values for all elements
        for (i = 0; i < cnt; ++i) {
            h = (i > 0 && i < cnt - 1 ? handle * 2 : handle);
        
            sizes.push((e[i]['size'] > ltk.splitter.minsize 
                        ? e[i]['size'] - h 
                        : s - h));
        }
    
        return sizes;
    }

    /****m* splitter/attach
     * SYNOPSIS
     */
    ltk.splitter.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach splitter widget
     ****
     */
    {
        var me = this;

        if ('columns' in def) {
            this.type = ltk.splitter.T_SPLIT_H;
            this.cssclass = 'ltk_hsplitter';
        } else if ('rows' in def) {
            this.type = ltk.splitter.T_SPLIT_V;
            this.cssclass = 'ltk_vsplitter';
        } else {
            throw 'no columns -or- rows defined!';
        }
    
        if (!('handles' in def)) {
            throw 'no handles defined';
        }
    
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );    

        // build inner containers and splitter
        var e, evt, vec1, vec2;
        var s, i, cnt;

        if (this.type == ltk.splitter.T_SPLIT_H) {
            // horizontal splitter
            this.size = this.widget.offsetWidth;
        
            var s = this.calcSizes(def['columns'], this.size);
            var l = 0;

            for (i = 0, cnt = s.length; i < cnt; ++i) {
                e = this.widget.appendChild(document.createElement('div'));
                e.className   = 'ltk_splitter_element';
                e.style.left  = l + 'px';
                e.style.right = (this.size - l - s[i]) + 'px';
    /*            if (i == 0) e.style.backgroundColor = '#ffff00';
                if (i == 1) e.style.backgroundColor = '#00ff00';
                if (i == 2) e.style.backgroundColor = '#0000ff';
                if (i == 3) e.style.backgroundColor = '#ff00ff'; */
            
                l += s[i];
            
                this.panes.push(e);

                e = e.appendChild(document.createElement('div'));
                e.className = 'ltk_splitter_container';
            
                this.content.push(e);

                if (i < cnt - 1) {
                    // add split handle between to split pane columns
                    e = this.widget.appendChild(document.createElement('div'));
                    e.className   = 'ltk_hsplitter_handle';
                    e.style.left  = l + 'px';
                    e.style.width = ltk.splitter.handlesize + 'px';
                    e.innerHTML   = '&nbsp;';

                    this.handles.push(e);
                
                    if (def['handles'][i]['draggable']) {
                        // splitter is draggable
                        this.setDraggable(i);
                    }
                }
            
                l += ltk.splitter.handlesize;
            }
        } else if (this.type == ltk.splitter.T_SPLIT_V) {
            // vertical splitter
            this.size = this.widget.offsetHeight;

            var s = this.calcSizes(def['rows'], this.size);
            var t = 0;

            for (i = 0, cnt = s.length; i < cnt; ++i) {
                e = this.widget.appendChild(document.createElement('div'));
                e.className    = 'ltk_splitter_element';
                e.style.top    = t + 'px';
                e.style.bottom = (this.size - t - s[i]) + 'px';
    /*            if (i == 0) e.style.backgroundColor = '#ffff00';
                if (i == 1) e.style.backgroundColor = '#00ff00';
                if (i == 2) e.style.backgroundColor = '#0000ff';
                if (i == 3) e.style.backgroundColor = '#ff00ff'; */

                t += s[i];

                this.panes.push(e);

                e = e.appendChild(document.createElement('div'));
                e.className = 'ltk_splitter_container';

                this.content.push(e);

                if (i < cnt - 1) {
                    // add split handle between to split pane columns
                    e = this.widget.appendChild(document.createElement('div'));
                    e.className    = 'ltk_vsplitter_handle';
                    e.style.top    = t + 'px';
                    e.style.height = ltk.splitter.handlesize + 'px';
                    e.innerHTML    = '&nbsp;';

                    this.handles.push(e);

                    if (def['handles'][i]['draggable']) {
                        // splitter is draggable
                        this.setDraggable(i);
                    }
                }

                t += ltk.splitter.handlesize;
            }
        } else {
            throw 'unknown splitter type!';
        }
    
        // render children
        var i = 0;
    
        this.processChildren(def, function(parent, instance, def) {
            var cell = (i % cnt);
        
            instance.attach(me.content[cell], def);

            ++i;
        }, null);
    }
})();


/****c* ltk/calendar
 * NAME
 *      ltk.calendar
 * FUNCTION
 *      calendar component
 * COPYRIGHT
 *      copyright (c) 2009-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('calendar' in ltk) return;

    /****m* calendar/
     * SYNOPSIS
     */
    ltk.calendar = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.ranges     = {};
        this.date       = new Date();
        this.navigation = false;
        this.id         = ltk.getUniqID('ltk_calendar_');
    }

    ltk.widget.register('calendar', ltk.calendar);

    /****v* calendar/T_SELECT_DAY, T_SELECT_RANGE
     * SYNOPSIS
     */
    ltk.calendar.T_SELECT_DAY   = 1;
    ltk.calendar.T_SELECT_RANGE = 2;
    /*
     * FUNCTION
     *      select types
     ****
     */

    /****v* calendar/T_RANGE_MARKED
     * SYNOPSIS
     */
    ltk.calendar.T_RANGE_MARKED = 1;
    /*
     * FUNCTION
     *      range types
     ****
     */

    /****v* calendar/month
     * SYNOPSIS
     */
    ltk.calendar.month = [
        _('January'), _('February'), _('March'), _('April'), _('May'),
        _('June'), _('July'), _('August'), _('September'), _('October'),
        _('November'), _('December')
    ];
    /*
     * FUNCTION
     *      month names 0..11
     ****
     */

    /****v* calendar/weekday
     * SYNOPSIS
     */
    ltk.calendar.weekday = [
        _('Monday'), _('Tuesday'), _('Wednesday'), 
        _('Thursday'), _('Friday'), _('Saturday'),
        _('Sunday')
    ];
    /*
     * FUNCTION
     *      calendar weekdays 0..6, monday is first day of week
     ****
     */

    ltk.calendar.prototype = new ltk.widget();

    /****v* calendar/cssclass
     * SYNOPSIS
     */
    ltk.calendar.prototype.cssclass = 'ltk_calendar';
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****v* calendar/container
     * SYNOPSIS
     */
    ltk.calendar.prototype.container = 'TABLE';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****m* calendar/getValue
     * SYNOPSIS
     */
    ltk.calendar.prototype.getValue = function()
    /*
     * FUNCTION
     *      return current selected date
     * OUTPUTS
     *      (object) -- current date
     ****
     */
    {
        return this.date;
    }

    /****m* calendar/populate
     * SYNOPSIS
     */
    ltk.calendar.prototype.populate = function(date)
    /*
     * FUNCTION
     *      populate calendar
     * INPUTS
     *      * date (object) -- date to populate calendar with
     ****
     */
    {
        if (this.dete.getMonth() != date.getMonth() || this.date.getFullYear() != date.getFullYear()) {
            this.date = date;
            this.rebuild();
        } else {
            this.date = date;
        }
    }

    /****m* calendar/onChange
     * SYNOPSIS
     */
    ltk.calendar.prototype.onChange = function(date)
    /*
     * FUNCTION
     *      onchange event handler
     * INPUTS
     *      * date (Date) -- date object with current selected date
     ****
     */
    {
    }

    /****m* calendar/navDate
     * SYNOPSIS
     */
    ltk.calendar.prototype.navDate = function(date)
    /*
     * FUNCTION
     *      navigate to specified date
     * INPUTS
     *      * date (Date) -- (optional) date to navigate to -- today is used, if no date is specified
     ****
     */
    {
        date = (date || new Date());
    
        this.date = new Date(date.getFullYear(), date.getMonth());

        this.rebuild();
    }

    /****m* calendar/navYear
     * SYNOPSIS
     */
    ltk.calendar.prototype.navYear = function(dir)
    /*
     * FUNCTION
     *      navigate year by increasing / decreasing
     * INPUTS
     *      * dir (int) -- +1/-1
     ****
     */
    {
        dir = (dir > 0 ? +1 : -1);
    
        if (dir == 0) {
            return;
        }
    
        var month = this.date.getMonth();
        var year  = this.date.getFullYear();

        this.date = new Date(year + dir, month);
    
        this.rebuild();
    }

    /****m* calendar/onChangeMonth
     * SYNOPSIS
     */
    ltk.calendar.prototype.navMonth = function(dir)
    /*
     * FUNCTION
     *      navigate month by increasing / decreasing
     * INPUTS
     *      * dir (int) -- +1/-1
     ****
     */
    {
        dir = (dir > 0 ? +1 : -1);
    
        if (dir == 0) {
            return;
        }
    
        var month = this.date.getMonth();
        var year  = this.date.getFullYear();
        var next  = (month + dir) % 12;

        if (dir > 0) {
            this.date = new Date((next < month ? year + 1 : year), next);
        } else if (dir < 0) {
            this.date = new Date((next > month ? year - 1 : year), next);
        }
    
        this.rebuild();
    }

    /****m* calendar/getDateDiff
     * SYNOPSIS
     */
    ltk.calendar.getDateDiff = function(dobj1, dobj2)
    /*
     * FUNCTION
     *      get difference between two dates in days
     * INPUTS
     *      * dobj1 (object) -- first date object
     *      * dobj2 (object) -- second date object
     * OUTPUTS
     *      (int) -- difference in days
     ****
     */
    {
        var diff = ltk.calendar.cmpDates(dobj1, dobj2);
    
        return (diff / (24 * 60 * 60 * 1000));
    }

    /****m* calendar/cmpDates
     * SYNOPSIS
     */
    ltk.calendar.cmpDates = function(dobj1, dobj2)
    /*
     * FUNCTION
     *      compare two date objects
     * INPUTS
     *      * dobj1 -- first date object
     *      * dobj2 -- second date object
     * OUTPUTS
     *      -1: dobj1 < dobj2; 0: dobj1 = dobj2; +1: dobj1>dobj2
     ****
     */
    {
        var d1 = Date.UTC(dobj1.getFullYear(), dobj1.getMonth(), dobj1.getDate());
        var d2 = Date.UTC(dobj2.getFullYear(), dobj2.getMonth(), dobj2.getDate());

        return (d1 - d2);
    }

    /****f* calendar/getDaysOfMonth
     * SYNOPSIS
     */
    ltk.calendar.prototype.getDaysOfMonth = function(dobj)
    /*
     * FUNCTION
     *      determine number of days of month of specified Date object
     * INPUTS
     *      * dobj -- date object
     * OUTPUTS
     *      returns the number of days of a month
     ****
     */
    {
        var days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
        var m = dobj.getMonth();

        return (m == 1 && ltk.calendar.isLeapYear(dobj) ? 29 : days[m]);
    }

    /****f* calendar/isLeapYear
     * SYNOPSIS
     */
    ltk.calendar.isLeapYear = function(dobj)
    /*                                    
     * FUNCTION
     *      determines if year of specified date object is a leap year
     * INPUTS
     *      * dobj -- date object
     * OUTPUTS
     *      returns true if year is a leap year, otherwise false
     ****
     */
    {
        var y = dobj.getFullYear();

        return (y % 4 == 0 ? (y % 100 != 0 || y % 400 == 0) : false);
    }

    /****m* calendar/setRange
     * SYNOPSIS
     */
    ltk.calendar.prototype.setRange = function(r_start, r_end)
    /*
     * FUNCTION
     *      set range
     * INPUTS
     *      * r_start (object) -- start date
     *      * r_end (object) -- end date
     ****
     */
    {
        r_start = (Date.UTC(r_start.getFullYear(), r_start.getMonth(), r_start.getDate()) / (24 * 60 * 60 * 1000));
        r_end = (Date.UTC(r_end.getFullYear(), r_end.getMonth(), r_end.getDate()) / (24 * 60 * 60 * 1000));

        var days  = this.getDaysOfMonth(this.date);
        var start = -(this.date.getDay() == 0 ? 6 : this.date.getDay() - 1);
        var end   = Math.ceil((-start + days) / 7) * 7 + start;

        var date = (Date.UTC(this.date.getFullYear(), this.date.getMonth(), 1) / (24 * 60 * 60 * 1000));
        var obj;

        for (i = start; i < end; i++) {
            if (i >= 0 && i < days) {
                obj = ltk.dom.one('#' + this.id + '_' + (i + 1));

                obj.removeClass('(ltk_calgroup_select_one|ltk_calgroup_select_between|ltk_calgroup_select_first|ltk_calgroup_select_last)');

                if (date == r_start) {
                    if (r_start == r_end) {
                        obj.addClass('ltk_calgroup_select_one');
                    } else {
                        obj.addClass('ltk_calgroup_select_first');
                    }
                } else if (date == r_end) {
                    obj.addClass('ltk_calgroup_select_last');
                } else if (date > r_start && date < r_end) {
                    obj.addClass('ltk_calgroup_select_between');
                }
            
                ++date;
            }
        }
    }

    /****m* calendar/rebuild
     * SYNOPSIS
     */
    ltk.calendar.prototype.rebuild = function()
    /*
     * FUNCTION
     *      rebuild calendar
     * INPUTS
     *      
     * OUTPUTS
     *      
     ****
     */
    {
        this.widget.removeChildren();
    
        var cal_date = new Date(this.date.getFullYear(), this.date.getMonth());
        var me       = this;

        // build calendar head -- month display / switch
        var part = this.widget.appendChild(ltk.dom.create('THEAD'));
        var tr   = part.appendChild(ltk.dom.create('TR'));
        var th   = ltk.dom.create('TH');
        th.setAttribute('colSpan', 7);
        th.node.className = 'ltk_calendar_month';
    
        if (this.navigation) {
            th.appendChild(ltk.dom.create('div', {
                'styles':   {'float': 'right'},
                'children': [
                    {'a': {
                        '#html':   '&laquo',
                        'class':   'ltk_button',
                        'href':    'javascript://',
                        'onclick': function() {
                            me.navYear(-1);
                        }
                    }},
                    {'a': {
                        '#html':   '&#139;',
                        'class':   'ltk_button',
                        'href':    'javascript://',
                        'onclick': function() {
                            me.navMonth(-1);
                        }
                    }},
                    {'a': {
                        '#html':   '&bull;',
                        'class':   'ltk_button',
                        'href':    'javascript://',
                        'onclick': function() {
                            me.navDate(new Date());
                        }
                    }},
                    {'a': {
                        '#html':   '&#155;',
                        'class':   'ltk_button',
                        'href':    'javascript://',
                        'onclick': function() {
                            me.navMonth(+1);
                        }
                    }},
                    {'a': {
                        '#html':   '&raquo',
                        'class':   'ltk_button',
                        'href':    'javascript://',
                        'onclick': function() {
                            me.navYear(+1);
                        }
                    }}
                ]
            }));
        
            th.appendChild(ltk.dom.create('span', {
                '#html':  ltk.calendar.month[this.date.getMonth()] + ' ' + this.date.getFullYear()
            }));
        } else {
            th.node.innerHTML = ltk.calendar.month[this.date.getMonth()] + ' ' + this.date.getFullYear();
        }
    
        tr.appendChild(th);
    
        // build weekday view
        tr = ltk.dom.create('TR');
    
        for (var i = 0; i < 7; ++i) {
            th = tr.appendChild(ltk.dom.create('TH', {
                'class': 'ltk_calendar_weekday',
                '#html': ltk.calendar.weekday[i].substr(0, 1)
            }));
        }
    
        part.appendChild(tr);
    
        // build calendar
        var c = 0;
        var td;
        var id;
    
        part = ltk.dom.create('TBODY');
    
        var today = new Date();
        var days  = this.getDaysOfMonth(this.date);
        var day   = 0;

        var start = -(this.date.getDay() == 0 ? 6 : this.date.getDay() - 1);
        var end   = Math.ceil((-start + days) / 7) * 7 + start;
        end += (42 - (end - start));

        for (i = start; i < end; i++) {
            if (c % 7 == 0) {
                tr = part.appendChild(ltk.dom.create('TR'));
            }
        
            day = (i + 1);
            id  = this.id + '_' + day;
        
            td = tr.appendChild(ltk.dom.create('TD', {
                'id':    id,
                'class': 'ltk_calendar_day'
            }));
        
            if (i >= 0 && i < days) {
                cal_date.setDate(day);

                if (ltk.calendar.cmpDates(cal_date, today) < 0) {
                    if (c % 7 == 5 || c % 7 == 6) {
                        td.addClass('ltk_calendar_weekend');
                    } else {
                        td.addClass('ltk_calendar_past');
                    }

                    td.node.innerHTML = day;
                    td.node.onclick   = ltk.closure({'day': day, 'container': td}, function(p) {
                        if (this.select == ltk.calendar.T_SELECT_DAY) {
                            ltk.dom.one(me.id + '_' + me.date.getDate()).removeClass('ltk_calendar_selected');
                            p.container.addClass('ltk_calendar_selected');
                        }
                    
                        me.date.setDate(p.day);
                        me.onChange(me.date);
                    });
                } else {
                    if (ltk.calendar.cmpDates(cal_date, today) == 0) {
                        td.addClass('ltk_calendar_today');
                    } else if (c % 7 == 5 || c % 7 == 6) {
                        td.addClass('ltk_calendar_weekend');
                    }

                    td.node.innerHTML = day;
                    td.node.onclick   = ltk.closure({'day': day, 'container': td}, function(p) {
                        if (ltk.calendar.cmpDates(cal_date, today) == 0) {
                            ltk.dom.one(me.id + '_' + me.date.getDate()).removeClass('ltk_calendar_selected');
                            p.container.addClass('ltk_calendar_selected');
                        }
                    
                        me.date.setDate(p.day);
                        me.onChange(me.date);
                    });
                }
            
                if (this.select == ltk.calendar.T_SELECT_DAY && ltk.calendar.cmpDates(this.date, cal_date) == 0) {
                    td.addClass('ltk_calendar_selected');
                }
            } else {
                td.node.innerHTML = '&nbsp;';
            }
        
            ++c;
        }
    
        this.widget.appendChild(part);
    }

    /****m* calendar/attach
     * SYNOPSIS
     */
    ltk.calendar.prototype.attach = function(parent, def) 
    /*
     * FUNCTION
     *      attach calendar widget
     ****
     */
    {
        // var container = parent.appendChild(document.createElement('div'));
        // container.className = 'ltk_border';
        // 
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );
    
        this.date = (typeof def['date'] == 'object' && def['date'] instanceof Date
                     ? def['date']
                     : this.date);
    
        this.select = ('select'  in def
                       ? def['select']
                       : ltk.calendar.T_SELECT_DAY);
    
        this.navigation = ('navigation' in def && !!def['navigation']);

        this.rebuild();    
    }
})();

/****c* ltk/calendargroup
 * NAME
 *      ltk.calendargroup
 * FUNCTION
 *      calendargroup component
 * COPYRIGHT
 *      copyright (c) 2009-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('calendargroup' in ltk) return;
    
    /****m* calendargroup/
     * SYNOPSIS
     */
    ltk.calendargroup = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.sub_dialog = null;
        this.terms      = [];
    }

    ltk.widget.register('calendargroup', ltk.calendargroup);

    ltk.calendargroup.prototype = new ltk.widget();

    /****v* calendargroup/cssclass
     * SYNOPSIS
     */
    ltk.calendargroup.prototype.cssclass = 'ltk_calendargroup'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* calendargroup/populate
     * SYNOPSIS
     */
    ltk.calendargroup.prototype.populate = function(data)
    /*
     * FUNCTION
     *      populate calendargroup
     * INPUTS
     *      * data (array) -- data to populate calendargroup with
     * TODO
     *      implementation
     ****
     */
    {
    }

    /****m* calendargroup/getValue
     * SYNOPSIS
     */
    ltk.calendargroup.prototype.getValue = function()
    /*
     * FUNCTION
     *      return calendargroup values
     * OUTPUTS
     *      (array) -- data of calendargroup
     ****
     */
    {
        var ret = {};
        var cnt = this.terms.length;
    
        if (cnt >= 1) {
            ret['start'] = this.terms[0].date;
            ret['end']   = (cnt == 2
                            ? this.terms[1].date
                            : ret['start']);
        }

        return ret;
    }

    /****m* calendargroup/setRange
     * SYNOPSIS
     */
    ltk.calendargroup.prototype.refreshRange = function()
    /*
     * FUNCTION
     *      refresh selected calendargroup range
     ****
     */
    {
        var cnt = this.terms.length;
        var start, end;
    
        if (cnt == 0) {
            return;
        }
    
        if (cnt >= 1) {
            start = this.terms[0];
            end   = (cnt == 2 ? this.terms[1] : start);
        }
    
        for (var i = 1; i <= 3; ++i) {
            this.sub_dialog.getWidget(
                this.name + '_calendar_' + i
            ).setRange(start.date, end.date);
        }    
    }

    /****m* calendargroup/setTerm
     * SYNOPSIS
     */
    ltk.calendargroup.prototype.setTerm = function(cal)
    /*
     * FUNCTION
     *      set term for calendergroup
     * INPUTS
     *      * cal (object) -- calendar which executed setTerm
     ****
     */
    {
        var date    = cal.getValue();
        var cal_obj = {
            'calendar':    cal,
            'date':        new Date(
                date.getFullYear(), 
                date.getMonth(),
                date.getDate()
            )
        };
        var name = this.name;
    
        if (this.terms.length != 1) {
            // first term
            this.terms = [cal_obj];
        
            for (var i = 1; i <= 3; ++i) {
                this.sub_dialog.getWidget(
                    name + '_calendar_' + i
                ).setRange(cal_obj.date, cal_obj.date);
            }
        } else {
            // second term
            var diff = ltk.calendar.cmpDates(this.terms[0].date, date);
        
            if (diff > 0) {
                // swap dates
                this.terms.push(this.terms[0]);
                this.terms[0] = cal_obj;
            } else if (diff <= 0) {
                this.terms.push(cal_obj);
            }
        
            for (var i = 1; i <= 3; ++i) {
                this.sub_dialog.getWidget(
                    name + '_calendar_' + i
                ).setRange(this.terms[0].date, this.terms[1].date);
            }
        }
    }

    /****m* calendargroup/attach
     * SYNOPSIS
     */
    ltk.calendargroup.prototype.attach = function(parent, def) 
    /*
     * FUNCTION
     *      attach calendar widget
     ****
     */
    {
        var name = ('name' in def
                    ? def['name']
                    : ltk.getUniqID('ltk_calgroup_'));

        this.setName(name);

        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );

        var me = this;

        var today = new Date();
        var month = today.getMonth();
        var year  = today.getFullYear();
        var prev  = (month - 1) % 12;
        var next  = (month + 1) % 12;

        var dia = new ltk.dialog();
        dia.attach(this.widget, {
            'rounded':   true,
            'children':  [
                {'grid': {
                    'columns':  3,
                    'width':    [33, 33, 33],
                    'children': [
                        {'calendar': {
                            'name':       name + '_calendar_1',
                            'navigation': false,
                            'select':     ltk.calendar.T_SELECT_RANGE,
                            'date':       new Date((prev > month ? year - 1 : year), prev),
                            'onChange':   function(date) {
                                me.setTerm(dia.getWidget(name + '_calendar_1'));
                            }
                        }},
                        {'calendar': {
                            'name':       name + '_calendar_2',
                            'navigation': true,
                            'select':     ltk.calendar.T_SELECT_RANGE,
                            'date':       new Date(today.getFullYear(), today.getMonth()),
                            'onChange':   function(date) {
                                me.setTerm(dia.getWidget(name + '_calendar_2'));
                            }
                        }},
                        {'calendar': {
                            'name':       name + '_calendar_3',
                            'navigation': false,
                            'select':     ltk.calendar.T_SELECT_RANGE,
                            'date':       new Date((next < month ? year + 1 : year), next),
                            'onChange':   function(date) {
                                me.setTerm(dia.getWidget(name + '_calendar_3'));
                            }
                        }}
                    ]
                }}
            ]
        });
    
        this.sub_dialog = dia;

        // overwrite navigation methods of calendar #2
        var cal = [
            dia.getWidget(name + '_calendar_1'),
            dia.getWidget(name + '_calendar_2'),
            dia.getWidget(name + '_calendar_3')
        ];

        cal[1].navDate = function(date) {
            var year  = date.getFullYear();
            var month = date.getMonth();
            var prev  = (month - 1) % 12;
            var next  = (month + 1) % 12;
        
            cal[0].navDate(new Date((prev > month ? year - 1 : year), prev));
            ltk.calendar.prototype.navDate.apply(cal[1], [date]);
            cal[2].navDate(new Date((next < month ? year + 1 : year), next));
        
            me.refreshRange();
        }
        cal[1].navYear = function(dir) {
            dir = (dir > 0 ? +1 : -1);

            if (dir == 0) {
                return;
            }

            cal[0].navYear(dir);
            ltk.calendar.prototype.navYear.apply(cal[1], [dir]);
            cal[2].navYear(dir);
        
            me.refreshRange();
        }
        cal[1].navMonth = function(dir) {
            dir = (dir > 0 ? +1 : -1);

            if (dir == 0) {
                return;
            }

            cal[0].navMonth(dir);
            ltk.calendar.prototype.navMonth.apply(cal[1], [dir]);
            cal[2].navMonth(dir);
        
            me.refreshRange();
        }
    }
})();

/****c* ltk/toolbar
 * NAME
 *      ltk.toolbar
 * FUNCTION
 *      toolbar component
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('toolbar' in ltk) return;
    
    /****m* toolbar/
     * SYNOPSIS
     */
    ltk.toolbar = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.children = [];
    
        this.groups = {};
    }

    ltk.widget.register('toolbar', ltk.toolbar);

    ltk.toolbar.prototype = new ltk.widget();

    /****v* toolbar/cssclass
     * SYNOPSIS
     */
    ltk.toolbar.prototype.cssclass = 'ltk_toolbar';
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****v* toolbar/container
     * SYNOPSIS
     */
    ltk.toolbar.prototype.container = 'DIV';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****m* toolbar/getValue
     * SYNOPSIS
     */
    ltk.toolbar.prototype.getValue = function()
    /*
     * FUNCTION
     *      return current selected date
     * OUTPUTS
     *      (object) -- current date
     ****
     */
    {
    }

    /****m* toolbar/populate
     * SYNOPSIS
     */
    ltk.toolbar.prototype.populate = function(date)
    /*
     * FUNCTION
     *      populate toolbar
     * INPUTS
     *      * date (object) -- date to populate toolbar with
     ****
     */
    {
    }

    /****m* toolbar/addGroup
     * SYNOPSIS
     */
    ltk.toolbar.prototype.addGroup = function(name, items)
    /*
     * FUNCTION
     *      add toolbar group
     * INPUTS
     *      * name (string) -- name of group to add
     *      * items (array) -- array of toolbar items to add
     * OUTPUTS
     *      (array) -- child elements for toolbar
     ****
     */
    {
        var btn, prop;
        var me  = this;
        var sep = false;
        var ret = [];
        var id, src;
    
        this.groups[name] = {
            'enabled':     true,
            'setDisabled': function(cb) {
                this.enabled = !this.enabled;
                cb();
            }
        };

        for (var i = 0, cnt = items.length; i < cnt; ++i) {
            if (typeof items[i] == 'object') {
                id = ltk.getUniqID('ltk_toolbar_');
            
                if (items[i]['image'].substr(0, 1) == '/') {
                    src = 'url(' + items[i]['image'] + ')';
                } else {
                    src = 'url(/resources_ltk/silk/' + items[i]['image'] + '.png)';
                }
            
                btn = {
                    'id':     id,
                    'src':    '/resources_ltk/images/shim.gif',
                    'styles': {'backgroundImage': src},
                
                    'onmouseover': ltk.closure({'id': id, 'item': items[i]}, function(params) {
                        if (me.groups[name].enabled) {
                            ltk.dom.one('#' + params.id).addClass('ltk_button_mover');
                            
                            if ('tooltip' in params.item) {
                                ltk.tooltip.show(params.item['tooltip'], ltk.dom.one('#' + params.id));
                            }
                        }
                    }),
                
                    'onmouseout': ltk.closure(id, function(id) {
                        ltk.dom.one('#' + id).removeClass('ltk_button_mover');
                    }),
                
                    'onmousedown': ltk.closure({'name': name, 'id': id}, function(params) {
                        if (me.groups[params.name].enabled) {
                            ltk.dom.one('#' + params.id).addClass('ltk_button_clicked');
                        }
                    }),
                
                    'onmouseup': ltk.closure(id, function(id) {
                        ltk.dom.one('#' + id).removeClass('ltk_button_clicked');
                    }),
                
                    'onclick': function() {
                    }
                };

                if (sep) {
                    btn['class'] += ' ltk_separator';

                    sep = false;
                }

                for (prop in items[i]) {
                    if (prop == 'onclick') {
                        // wrap onclick handler to control firing according to state of group (enabled/disabled)
                        btn[prop] = ltk.closure({'name': name, 'cb': items[i][prop], 'id': id}, function(params) {
                            if (me.groups[params.name].enabled) {
                                params.cb(ltk.dom.one('#' + params.id));
                            }
                        });
                    } else if (prop != 'image') {
                        btn[prop] = items[i][prop];
                    }
                }
            
                ret.push({'image': btn});
            } else {
                sep = true;
                continue;
            }
        }
    
        return ret;
    }

    /****m* toolbar/attach
     * SYNOPSIS
     */
    ltk.toolbar.prototype.attach = function(parent, def) 
    /*
     * FUNCTION
     *      attach toolbar widget
     ****
     */
    {
        def['children'] = [];
    
        var btn, prop, name;
        var sep = false;
    
        if (!('groups' in def || 'items' in def)) {
            throw 'unknown toolbar layout';
        } else if ('items' in def) {
            def['groups'] = [{'items': def['items']}];
        }
    
        for (var i = 0, cnt = def['groups'].length; i < cnt; ++i) {
            if (typeof def['groups'][i] == 'object') {
                name = ('name' in def['groups'][i]
                        ? def['groups'][i]['name']
                        : ltk.getUniqID('ltk_toolbar_group_'));

                def['children'].push({'htmltag': {
                    'tag':      'span',
                    'name':     name,
                    'class':    (i > 0 ? 'ltk_separator' : ''),
                    'children': this.addGroup(
                        name,
                        def['groups'][i]['items']
                    )
                }});
            }
        }

        delete def['groups'];

        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );
    
        this.processChildren(def, function(parent, instance, def) {
            instance.attach(parent, def);
        });

        // attach events
        var dia = this.getDialog();
        var me  = this;
    
        for (i in this.groups) {
            dia.getWidget(i).setDisabled = ltk.closure(
                {'name': i, 'widget': dia.getWidget(i)},
                function(params, disable) {
                    me.groups[params['name']].enabled = !disable;
                
                    if (disable) {
                        params['widget'].widget.replaceClass('ltk_enabled', 'ltk_disabled');
                    } else {
                        params['widget'].widget.replaceClass('ltk_disabled', 'ltk_enabled');
                    }
                
                    params['widget'].disabled = disable;
                }
            );
        }
    }
})();

/****c* ltk/tooltip
 * NAME
 *      ltk.tooltip
 * FUNCTION
 *      tooltip component
 * COPYRIGHT
 *      copyright (c) 2010 by Daniel Garding
 * AUTHOR
 *      Daniel Garding <d.garding@clipdealer.de>
 ****
 */
 ;(function() {
    if ('tooltip' in ltk) return;
    
    /*
     * create container for dialog
     */
    var container = (function() {
        var _container = null;

        return function() {
            if (_container == null) {
                _container = ltk.dom.one('BODY').appendChild(ltk.dom.create('DIV', {'class': 'ltk_tooltip'}));
            }

            return _container;
        }
    })();
    
    var tt = null;

    ltk.tooltip = {
        show: function(obj, node){
            if (!(node instanceof ltk.dom.node)) {
                node = new ltk.dom.node(node);
            }
            
            var id = ltk.evt.addEvent(node, 'mouseout', function(){
                ltk.evt.removeEvent(id);
                tt.remove();
            });
            
            tt = new tooltip(obj, node);
            tt.render();
        }
    }
    
    /* ToolTip Class */
    function tooltip(obj, node){
        this.widget  = null;
        this.obj = obj;
        
        var pos = ltk.evt.getMousePos();
        this.x = pos.x;
        this.y = pos.y;
        
        this.speed = 5;
    }
    
    tooltip.prototype.render = function(){
        var that = this;
        
        container().appendChild(ltk.dom.create('div', {
            '#trigger': function(node) {
                that.widget  = node;
                that.opacity = node.getOpacity();
            },
            'class' : 'tt',
            'styles' : {
                'visibility' : 'visible', 'display' : 'block', 'position' : 'absolute', 'top': that.y+'px', 'left': that.x+'px', 'opacity': '0'
            },
            'children' : [
                {'div' : {
                    'class' : 'tt-content',
                    'children' : [
                        that.obj
                    ]
                }}
            ]
        }));
        
        that.fadeIn(that.opacity);
    }

    tooltip.prototype.remove = function() {
        this.fadeOut(this.widget.getOpacity());
    }
    
    tooltip.prototype.fadeIn = function(value) {
        var that = this;
        
        window.setTimeout(function(){
            that.widget.setStyle('opacity', value);
            if(value != 100+Number(that.speed)) {
                value = (Number(value) + Number(that.speed));
                that.fadeIn(value);
            }
        }, 10);
    }
    
    tooltip.prototype.fadeOut = function(value){
        if (value <= 1) {
            value = value * 100;            
        }
        
        var that = this;
        
        window.setTimeout(function(){
            that.widget.setStyle('opacity', value);
            if(value > 1) {
                value = Number(value) - Number(that.speed);
                that.fadeOut(--value);
            } else {
                that.widget.removeNode();
            }
        }, 10);
    }
    /* ToolTip Class*/
    
 })();
/****c* ltk/codemirror
 * NAME
 *      lima_ltk_codemirror
 * FUNCTION
 *      ltk codemirror integration
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('codemirror' in ltk) return;
    
    var font_width  = 0;
    var _codemirror = 1;            // codemirror version
    
    /*
     * helper function to calculate font width
     */
    function getFontWidth() {
        if (font_width == 0) {
            var div = ltk.dom.one('body').appendChild(ltk.dom.create('div', {
                'styles': {
                    'font-family':  'Courier New, monospaces',
                    'position':     'absolute',
                    'visibility':   'hidden',
                    'height':       'auto',
                    'width':        'auto'
                }
            }));
            div.node.innerHTML = 'O';
            font_width = (div.node.clientWidth + 1);
        }
        
        return font_width;
    }
    
    
    
    /****m* codemirror/
     * SYNOPSIS
     */
    ltk.codemirror = function()
    /*
     * FUNCTION
     *      codemirror constructor
     ****
     */
    {
        // default options
        this.options = {
            'mode':        'ltext',     // type of codemirror editor
            'livepreview': 'false',     // whether livepreview for ltext is enabled
            'liveupdate':  500,         // normal livepreview update cycle -- update with half a second lag
            'tab_width':   4            // tab width in editor
        };
    
        this.ltext = {};
        
        this.initialized = false;
    
        // misc
        this.children = [];
    
        this.editor  = null;    // instance of codemirror
        this.preview = null;    // DOM node of preview container
    }

    ltk.widget.register('codemirror', ltk.codemirror);

    ltk.codemirror.prototype = new ltk.widget();

    /****d* codemirror/texts
     * SYNOPSIS
     */
    ltk.codemirror.texts = {
        'headline': _('headline')
    };
    /*
     * FUNCTION
     *      texts
     ****
     */

    var editors = {};

    /****m* codemirror/addEditor
     * SYNOPSIS
     */
    ltk.codemirror.addEditor = function(mode, def)
    /*
     * FUNCTION
     *      adds an editor configuration for specified mode.
     ****
     */
    {
        editors[mode] = def;
    }

    /****v* codemirror/cssclass
     * SYNOPSIS
     */
    ltk.codemirror.prototype.cssclass = 'ltk_codemirror';
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* codemirror/getVersion
     * SYNOPSIS
     */
    ltk.codemirror.prototype.getVersion = function()
    /*
     * FUNCTION
     *      Returns version of codemirror.
     * OUTPUTS
     *      (int) -- version number
     ****
     */
    {
        return _codemirror;
    }
    
    /****m* codemirror/getToolbarItems
     * SYNOPSIS
     */
    ltk.codemirror.prototype.getToolbarItems = function(mode)
    /*
     * FUNCTION
     *      returns toolbar items for specified mode.
     ****
     */
    {
        if (mode in editors) {
            return editors[mode].getToolbarItems.apply(this);
        } else {
            return [];
        }
    }

    /****m* codemirror/formatInline
     * SYNOPSIS
     */
    ltk.codemirror.prototype.formatInline = function(open, close)
    /*
     * FUNCTION
     *      add inline formatting
     * INPUTS
     *      * fmt (string) -- formatting to add
     ****
     */
    {
        if (_codemirror == 2) {
            var selection = this.editor.getSelection();
            
            close = close || open;
            
            if (selection === '') {
                // nothing selected
                var pos = this.editor.getCursor(true);
            
                this.editor.replaceSelection(open + selection + close);
                this.editor.setCursor(pos.line, pos.ch + open.length);
            } else {
                // selection
                this.editor.replaceSelection(open + selection + close);
            }
            
            this.ltext.updated = true;
        } else if (_codemirror == 1) {
            var selection = this.editor.selection();
        
            if (selection === '') {
                // nothing selected
                var pos = this.editor.cursorPosition(true);
 
                this.editor.insertIntoLine(pos.line, pos.character, open);
                this.editor.insertIntoLine(pos.line, pos.character + open.length, close);
                this.editor.selectLines(pos.line, pos.character + open.length);
            } else {
                // selection
                this.editor.replaceSelection(open + selection + close);
            }

            this.ltext.updated = true;    
        }
    }

    /****m* codemirror/replaceSelection
     * SYNOPSIS
     */
    ltk.codemirror.prototype.replaceSelection = function(rpl, posend)
    /*
     * FUNCTION
     *      replace selected test
     * INPUTS
     *      * posend (bool) -- whether to reposition cursor to the end of the inserted string (default: false)
     ****
     */
    {
        var pos;
        
        posend = (typeof posend == 'undefined' ? false : posend);
        
        this.editor.replaceSelection(rpl);
        
        if (posend) {
            if (_codemirror == 1) {
                pos = this.editor.cursorPosition(true);
                this.editor.selectLines(pos.line, pos.character + rpl.length);
            } else if (_codemirror == 2) {
                pos = this.editor.getCursor(true);
                this.editor.setCursor(pos.line, pos.ch + rpl.length);
            }
        }
    }

    /****m* codemirror/getValue
     * SYNOPSIS
     */
    ltk.codemirror.prototype.getValue = function()
    /*
     * FUNCTION
     *      return value from editor
     ****
     */
    {
        return (_codemirror == 1 
                ? this.editor.getCode()
                : (_codemirror == 2
                    ? this.editor.getValue()
                    : ''));
    }

    /****m* codemirror/populate
     * SYNOPSIS
     */
    ltk.codemirror.prototype.populate = function(value)
    /*
     * FUNCTION
     *      fill editor with value.
     ****
     */
    {
        if (_codemirror == 2) {
            var font_width = getFontWidth();
            var area_width = this.widget.node.offsetWidth;
            var wrap       = Math.floor(area_width / font_width) - 5;
            
            value = ltk.string.wordwrap(value, wrap, '\n', true);
        
            this.editor.setValue(value);
        } else if (_codemirror == 1) {
            if (!this.initialized) {
                // not yet initialized, keep trying
                var me = this;
                
                window.setTimeout(function() {
                    me.populate(value);
                }, 100);
            } else {
                this.editor.setCode(value);
            }
        }
    }

    /****m* codemirror/assimilate
     * SYNOPSIS
     */
    ltk.codemirror.prototype.assimilate = function(node)
    /*
     * FUNCTION
     *      build the codemirror component inside the specified node.
     ****
     */
    {
        var def = {};
        
        if (node.hasAttribute('ltk:codemirror') && (tmp = ltk.string.trim(node.getAttribute('ltk:codemirror'))) != '') {
            def = eval('(' + tmp + ')');
        }
        
        this.attach(node, def);
    }

    /****m* codemirror/attach
     * SYNOPSIS
     */
    ltk.codemirror.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach codemirror widget
     ****
     */
    {
        // initialize
        var name = ltk.getUniqID('ltk_codemirror_');

        this.setOptions(def['options']);

        if ('livepreview' in def) {
            this.options.livepreview = def['livepreview'];
        }

        // build widget
        def['children'] = [
            {'toolbar': {
                'groups': this.getToolbarItems(this.options.mode)
            }}
        ];

        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );
    
        this.processChildren(def, function(parent, instance, def) {
            instance.attach(parent, def);
        });
    
        // build additional components
        var dia = this.getDialog();
        var me  = this;
    
        if (me.options.mode == 'ltext') {
            dia.getWidget('block').setDisabled(false);
            dia.getWidget('inline').setDisabled(true);
        }

        this.editor = new CodeMirror(this.widget.node, {
            height:          '350px',
            basefiles:       ['basefiles.js'],
            parserfile:      'parse' + me.options.mode + '.js',
            stylesheet:      '/resources_ltk/ltk/CodeMirror/css/' + me.options.mode + 'colors.css',
            path:            '/resources_ltk/ltk/CodeMirror/js/',
            autoMatchParens: false,
            indentUnit:      4,
            tabMode:         'default',
            enterMode:       'keep',
            indentUnit:      0,
            onLoad:         function(editor) {
                // get's called, when editor is ready initialized
                me.initialized = true;
            },
            cursorActivity: function(node) {
                if (me.options.mode == 'ltext') {
                    var p1, p2, same;
            
                    me.ltext.updated = true;
            
                    p1 = me.editor.cursorPosition(true);
                    p2 = me.editor.cursorPosition(false);
                
                    same = (p1.character === p2.character);
                
                    dia.getWidget('block').setDisabled(!same);
                    dia.getWidget('inline').setDisabled(same);
                }
            
                return true;
            }
        });

        if (this.options.mode == 'ltext') {
            // this.editor.grabKeys(function(e) {
            //     console.log(e);
            // });

            this.ltext = {
                'ltext':    new ltk.ltext(),
                'interval': null,
                'updated':  false,
                'blocked':  false
            };

            this.ltext.ltext.setTabWidth(this.options.tab_width);

            this.preview = this.widget.appendChild(ltk.dom.create('div'));
            this.preview.appendChild(ltk.dom.create('div'));
        
            if (this.options.livepreview) {
                this.ltext.interval = window.setInterval(function() {
                    me.livePreview();
                }, this.options.liveupdate);
            }
        }
    }
})();

/****c* ltk/colorpicker
 * NAME
 *      ltk.colorpicker
 * FUNCTION
 *      colorpicker component
 * COPYRIGHT
 *      copyright (c) 2009-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('colorpicker' in ltk) return;
    
    // colorpicker palettes
    var names = {};
    names[ltk.color.type.RGB]  = _('RGB');
    names[ltk.color.type.CMYK] = _('CMYK');
    names[ltk.color.type.HSV]  = _('HSV');
    names[ltk.color.type.BW]   = _('Grayscale');
    
    var palettes = {};
    palettes[ltk.color.type.RGB] = [
        {
            'label':   _('Red'), 
            'min':       0, 
            'max':     255, 
            'channel': ltk.color.type.CH_R
        }, 
        {
            'label':   _('Green'), 
            'min':       0, 
            'max':     255, 
            'channel': ltk.color.type.CH_G
        },
        {
            'label':   _('Blue'), 
            'min':       0, 
            'max':     255, 
            'channel': ltk.color.type.CH_B
        }
    ];
    palettes[ltk.color.type.CMYK] = [
        {
            'label':   _('Cyan'), 
            'min':       0, 
            'max':     100, 
            'channel': ltk.color.type.CH_C
        }, 
        {
            'label':   _('Magenta'), 
            'min':       0, 
            'max':     100, 
            'channel': ltk.color.type.CH_M
        },
        {
            'label':   _('Yellow'), 
            'min':       0, 
            'max':     100, 
            'channel': ltk.color.type.CH_Y
        },
        {
            'label':   _('Kobalt'), 
            'min':       0, 
            'max':     100, 
            'channel': ltk.color.type.CH_K
        }
    ];
    palettes[ltk.color.type.HSV] = [
        {
            'label':   _('Hue'), 
            'min':       0, 
            'max':     360, 
            'channel': ltk.color.type.CH_H
        }, 
        {
            'label':   _('Saturation'), 
            'min':       0, 
            'max':     100, 
            'channel': ltk.color.type.CH_S
        },
        {
            'label':   _('Value'), 
            'min':       0, 
            'max':     100, 
            'channel': ltk.color.type.CH_V
        }
    ];
    palettes[ltk.color.type.BW] = [
        {
            'label':   _('Black'), 
            'min':       0, 
            'max':     100, 
            'channel': ltk.color.type.CH_BW
        }
    ];
    
    /*
     * helper functions
     */
    function showRgbGauge(type) {
        dat = getChannels.call(this, type);
        col = (new ltk.color(dat, type)).getColor(ltk.color.type.RGB);
        
        this.w_refs.g_rgb.populate(col);
    }
    
    function getValue(ch) {
        var num = parseInt(this.w_refs['v_' + ch].getValue(), 10);
        
        return (isNaN(num) ? 0 : num);
    }
    
    function getChannels(type) {
        var data = [];
        var num;
        
        for (var i = 0, cnt = palettes[type].length; i < cnt; ++i) {
            data.push(getValue.call(this, i));
        }
        
        return data;
    }
    
    function setPalette(type, channels) {
        var pal = palettes[type];
        var dat, col;
        
        var filter = false;
        var me     = this;
        
        if (typeof channels != 'undefined') {
            col = channels;
        } else if (this.palette != null) {
            dat = getChannels.call(this, this.palette);
            col = (new ltk.color(dat, this.palette)).getColor(type);
        } else {
            col = [0, 0, 0, 0];
        }
        
        if (type == ltk.color.type.HSV) {
            filter = function(type, data) {
                var ret;
                
                if (type == ltk.color.type.CH_H) {
                    ret = [ltk.color.type.HSV, [data, 100, 100]];
                    
                    me.w_refs.g_1.populate(getValue.call(me, 1));
                    me.w_refs.g_2.populate(getValue.call(me, 2));
                } else if (type == ltk.color.type.CH_S) {
                    ret = [ltk.color.type.HSV, [getValue.call(me, 0), data, 100]];
                } else if (type == ltk.color.type.CH_V) {
                    ret = [ltk.color.type.HSV, [getValue.call(me, 0), 100, data]];
                } else {
                    ret = [type, data];
                }

                return ret;
            }
        }
        
        for (var i = 0; i < 4; ++i) {
            if (typeof pal[i] == 'undefined') {
                this.rows[i].setStyle('display', 'none');
            } else {
                this.rows[i].setStyle('display', this.tr_display);
                
                this.w_refs['s_' + i].setRange(pal[i]['min'], pal[i]['max']);
                this.w_refs['g_' + i].setType(pal[i]['channel'], filter);
                
                this.w_refs['l_' + i].populate(pal[i]['label']);
                this.w_refs['v_' + i].populate(col[i]);
                this.w_refs['s_' + i].populate(col[i]);
                this.w_refs['g_' + i].populate(col[i]);
            }
        }
        
        showRgbGauge.call(this, type);
        
        this.palette = type;
    }
    
    /****m* colorpicker/
     * SYNOPSIS
     */
    ltk.colorpicker = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.sub_dialog = null;
        this.tolerance  = false;
        
        this.palettes   = [ltk.color.type.RGB, ltk.color.type.CMYK, ltk.color.type.HSV, ltk.color.type.BW];
        
        this.palette    = null;
        this.tr_display = 'block';
        this.rows       = [];
        
        this.w_refs       = {};
    }

    ltk.widget.register('colorpicker', ltk.colorpicker);

    ltk.colorpicker.prototype = new ltk.widget();

    /****v* colorpicker/cssclass
     * SYNOPSIS
     */
    ltk.colorpicker.prototype.cssclass = 'ltk_colorpicker'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */
    
    /****m* colorpicker/populate
     * SYNOPSIS
     */
    ltk.colorpicker.prototype.populate = function(data)
    /*
     * FUNCTION
     *      populate colorpicker with values
     * INPUTS
     *      * data (array) -- array to populate colorpicker with
     ****
     */
    {
        if ('type' in data) {
            this.palette = data['type']
        }
        
        this.tolerance = ('tolerance' in data && data['tolerance'] >= 0);
        
        if ('rgb' in data) {
            setPalette.call(this, this.palette, (new ltk.color(data['rgb'], ltk.color.type.RGB)).getColor(this.palette));
        }
    }

    /****m* colorpicker/getValue
     * SYNOPSIS
     */
    ltk.colorpicker.prototype.getValue = function()
    /*
     * FUNCTION
     *      return colorpicker values
     * OUTPUTS
     *      (array) -- colorpicker values
     ****
     */
    {
        var dat = getChannels.call(this, this.palette);
        var rgb = (new ltk.color(dat, this.palette)).getColor(ltk.color.type.RGB);
        
        return {
            'type':      this.palette,
            'tolerance': (this.tolerance ? 0 : -1),
            'channels':  dat,
            'rgb':       rgb
        };
    }

    /****m* colorpicker/attach
     * SYNOPSIS
     */
    ltk.colorpicker.prototype.attach = function(parent, def) 
    /*
     * FUNCTION
     *      attach colorpicker widget
     ****
     */
    {
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );

        if ('palettes' in def) {
            this.palettes = [];
            
            for (var i = 0, cnt = def['palettes'].length; i < cnt; ++i) {
                if (def['palettes'][i] in palettes) {
                    this.palettes.push(def['palettes'][i]);
                }
            }
        }
    
        this.tolerance = def['tolerance'] || false;

        var me = this;
        var i;

        // initialize sub dialog
        var dia = new ltk.dialog();
        dia.container = this.container;
        
        // build child widgets for sub dialog
        var children = [
            {'label': {
                'label': _('Color')
            }},
            {'html:span': {'#html': '&nbsp;'}},
            {'html:span': {'#html': '&nbsp;'}},
            {'colorpicker:gauge': {
                'name': 'rgb_gauge',
                'type': ltk.color.type.RGB
            }}
        ];
        
        for (i = 0; i < 4; ++i) {
            children.push({'label': {
                'label': '&nbsp;',
                'name':  'label_' + i
            }});
            children.push({'slider': {
                'name':      'slider_' + i,
                'min':       0,
                'max':       100,
                'precision': 0,
                'value':     0,
                'onChange':  (function(name) {
                    return function(value) { 
                        dia.getWidget('value_' + name).populate(value); 
                        dia.getWidget('gauge_' + name).populate(value);

                        showRgbGauge.call(me, me.palette);
                    }
                })(i)
            }});
            children.push({'textline': {
                'name':     'value_' + i,
                'styles':   {'width': '50px'},
                'value':    0,
                'onChange': (function(name) {
                    return function(value) { 
                        dia.getWidget('slider_' + name).populate(value); 
                        dia.getWidget('gauge_' + name).populate(value);

                        showRgbGauge.call(me, me.palette);
                    }
                })(i)
            }});
            children.push({'colorpicker:gauge': {
                'name': 'gauge_' + i,
                'type': 0
            }});
        }
        
        // display slider for tolerance only if tolerance is enabled
        if (this.tolerance) {
            children.push({'label': {
                'label': _('Tolerance')
            }});
            children.push({'slider': {
                'name':      't_slider',
                'min':         0,
                'max':       100,
                'precision':   0,
                'onChange': function(value) { 
                    dia.getWidget('t_value').populate(value); 
                }
            }});
            children.push({'textline': {
                'name':     't_value',
                'styles':   {'width': '50px'},
                'onChange': function(value) { 
                    dia.getWidget('t_slider').populate(value); 
                }
            }});
            children.push({'html:span': {'#html': '&nbsp;'}});
        }
        
        // build dialog
        var items = [];
        var value = '';
        var cnt;
        
        for (i = 0, cnt = this.palettes.length; i < cnt; ++i) {
            if (i == 0) value = this.palettes[i];
            
            items.push({
                'value': this.palettes[i],
                'text':  names[this.palettes[i]]
            });
        }
        
        dia.attach(this.widget, {'children': [
            {'select': {
                'value': value,
                'items': items,
                'onChange': function(value) {
                    setPalette.call(me, value);
                }
            }},
            {'grid': {
                'columns': 4,
                'width':   [30, 40, 25, 5],
                'onRenderRow': function(row_no, row_obj) {
                    if (row_no >= 2 && row_no <= 5) {
                        me.rows.push(row_obj);
                    }
                },
                'children': children
            }}
        ]});

        // save references
        this.w_refs['g_rgb'] = dia.getWidget('rgb_gauge');
        
        for (i = 0; i < 4; ++i) {
            this.w_refs['l_' + i] = dia.getWidget('label_' + i);
            this.w_refs['s_' + i] = dia.getWidget('slider_' + i);
            this.w_refs['v_' + i] = dia.getWidget('value_' + i);
            this.w_refs['g_' + i] = dia.getWidget('gauge_' + i);
        }

        // detect table row display type
        me.tr_display = ltk.dom.first('TR', dia.getNode()).getStyle('display');

        // show default colorpicker palette
        ltk.dom.ready(function() {
            setPalette.call(me, ltk.color.type.RGB);
        });
    }
    
    /****c* colorpicker/gauge
     * NAME
     *      ltk.colorpicker.gauge
     * FUNCTION
     *      rgb gauge for colorpicker
     * COPYRIGHT
     *      copyright (c) 2009-2010 by Harald Lapp
     * AUTHOR
     *      Harald Lapp <harald.lapp@gmail.com>
     ****
     */

    /****m* gauge/
     * SYNOPSIS
     */
    ltk.colorpicker.gauge = function()
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.type   = ltk.color.type.RGB;
        this.color  = null;
        this.filter = function(type, data) {
            return [type, data];
        }
    }

    ltk.widget.register('colorpicker:gauge', ltk.colorpicker.gauge);

    ltk.colorpicker.gauge.prototype = new ltk.image();

    /****m* gauge/setType
     * SYNOPSIS
     */
    ltk.colorpicker.gauge.prototype.setType = function(type, filter)
    /*
     * FUNCTION
     *      set type of gauge
     * INPUTS
     *      * type (int) -- type of gauge
     *      * filter (callback) -- (optional) filter to apply to the data
     ****
     */
    {
        this.type   = type;
        this.filter = (typeof filter != 'function'
                        ? function(type, data) { return [type, data]; }
                        : filter);
    }

    /****m* gauge/getValue
     * SYNOPSIS
     */
    ltk.colorpicker.gauge.prototype.getValue = function()
    /*
     * FUNCTION
     *      get value of widget
     * OUTPUTS
     *      (string) -- widget value
     ****
     */
    {
        return this.color.getColor(this.type);
    }

    /****m* gauge/populate
     * SYNOPSIS
     */
    ltk.colorpicker.gauge.prototype.populate = function(data)
    /*
     * FUNCTION
     *      populate widget
     * INPUTS
     *      * data (mixed) -- data to populate widget with
     ****
     */
    {
        var tmp = this.filter(this.type, data);
        
        this.color.setColor(tmp[1], tmp[0]);
        var rgb = this.color.getColor(ltk.color.type.RGB);
    
        this.widget.node.style.backgroundColor = 'rgb(' + rgb.join(', ') + ')';
    }

    /****m* gauge/attach
     * SYNOPSIS
     */
    ltk.colorpicker.gauge.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      build widget
     * INPUTS
     *      * parent (object) -- parent DOM node to attach widget to
     *      * def (array) -- widget definition
     * OUTPUTS
     *      
     ****
     */
    {
        // initialize
        this.type      = def['type'];
        this.color     = new ltk.color([100, 100, 100]);

        // build widget
        def['styles'] = {
            'border':           '1px solid #000',
            'background-color': '#fff'
        }
    
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );
    
        this.widget.setAttribute('src',    '/resources/images/shim.gif');
        this.widget.setAttribute('width',  15);
        this.widget.setAttribute('height', 15);
    }
})();

/****c* ltk/contextmenu
 * NAME
 *      ltk.contextmenu
 * FUNCTION
 *      ltk contextmenu
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('contextmenu' in ltk) return;
    
    /****m* contextmenu/
     * SYNOPSIS
     */
    ltk.contextmenu = function()
    /*
     * FUNCTION
     *      contextmenu constructor
     ****
     */
    {
        this.menu  = null;
        this.layer = new ltk.dom.layer();
    }

    ltk.widget.register('contextmenu', ltk.contextmenu);

    ltk.contextmenu.prototype = new ltk.window();

    /****v* contextmenu/contextmenus
     * SYNOPSIS
     */
    ltk.contextmenus = {};
    /*
     * FUNCTION
     *      holds all defined contextmenus
     ****
     */

    /****v* contextmenu/cssclass
     * SYNOPSIS
     */
    ltk.contextmenu.prototype.cssclass = 'ltk_contextmenu'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

     /****m* contextmenu/populate
      * SYNOPSIS
      */
     ltk.contextmenu.prototype.populate = function(items)
     /*
      * FUNCTION
      *      populate contextmenu with menu items
      * INPUTS
      *      * items (array) -- menu items
      ****
      */
     {
         this.menu.removeChildren();
         
         var me = this;
         var li;
         
         for (var i = 0, len = items.length; i < len; ++i) {
             li = this.menu.appendChild(ltk.dom.create('LI', {
                 '#html': items[i].label
             }));
             
             ltk.evt.addEvent(li, 'click', ltk.closure({'node': li, 'action': items[i].action}, function(params) {
                 me.setVisible(false);
                 me.onAction(params.action);
             }));
             ltk.evt.addEvent(li, 'mouseover', ltk.closure(li, function(li) {
                 li.addClass('hover');
             }));
             ltk.evt.addEvent(li, 'mouseout', ltk.closure(li, function(li) {
                 li.removeClass('hover');
             }));
         }
     }

     /****m* contextmenu/onAction
      * SYNOPSIS
      */
     ltk.contextmenu.prototype.onAction = function(action)
     /*
      * FUNCTION
      *      gets called, when a menu item is clicked
      * INPUTS
      *      * action (string) -- name of action of clicked menu item
      ****
      */
     {
     }

    /****m* contextmenu/setVisible
     * SYNOPSIS
     */
    ltk.contextmenu.prototype.setVisible = function(visible)
    /*
     * FUNCTION
     *      show / hide window
     * INPUTS
     *      * visible (bool) -- whether to show/hide window
     ****
     */
    {
        if (visible) {
            var pos = ltk.evt.getMousePos();
            this.setPosXY(pos.x - 5, pos.y - 5);

            this.layer.up();
        }
    
        this.widget.setStyle('visibility', (visible ? 'visible' : 'hidden'));
    }

    /****m* contextmenu/attach
     * SYNOPSIS
     */
    ltk.contextmenu.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach button widget
     ****
     */
    {
        var raster = 1;
        var me     = this;
    
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );    
    
        this.layer.push([this.widget]);

        this.widget.addClass('ltk_dialog_shadow');
    
        this.widget.appendChild(ltk.dom.create('div', {
            'class':    'ltk_dialog_container',
            'styles':   {'position': 'relative'},
            'children': [
                {'UL': {
                    '#trigger': function(node) {
                        me.menu = node;
                    }
                }}
            ]
        }));
        
        if ('items' in def) this.populate(def['items']);
    
        ltk.evt.addEvent(this.widget, 'mouseout', function() {
            me.setVisible(false);
        });
    
        this.widget.setStyle(
            'visibility', 
            ('visible' in def && def['visible'] ? 'visible' : 'hidden')
        );
    }
})();

/****c* ltk/datagrid
 * NAME
 *      lima_ltk_datagrid
 * FUNCTION
 *      lima LTK dynamic datagrid widget
 *      
 *      ..  source: js      
 *      
 *          {'datagrid': {
 *              'columns': [{       // column definitions
 *                  'field': field-name,
 *                  'label': header-label,
 *                  'align': text-align
 *              }, ...]‚
 *              'header': ...,      // number of header rows
 *              'width': ...,       // column width
 *              'separator': ...    // true/false
 *          }}
 * COPYRIGHT
 *      copyright (c) 2009-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('datagrid' in ltk) return;
    
    /****m* datagrid/
     * SYNOPSIS
     */
    ltk.datagrid = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.body    = null;                    // container for datagrid content
        this.foot    = null;                    // container for datagrid sums
    
        this.columns  = {};                     // datagrid columns
        this.data     = [];                     // datagrid data
    
        // datagrid sorting
        this.sort = {
            'column':    null,                  // column to sort
            'direction': -1                     // direction (1: ASC, -1: DESC)
        };                         
    
        // datagrid selecting
        this.selectable = true;                 // whether datagrid rows are selectable
    
        this.select = {
            'row_selected': null,               // selected row object
            'row_no':       -1                  // selected row number
        }
    
        // datagrid editing
        this.editable = false;                  // whether datagrid is editable
    
        this.edit = {
            'menu':      null,                  // context menu für right mouse button click
            'dialog':    null,                  // edit dialog
            'row_saved': null,                  // saved original row
            'row_no':    -1                     // row which is currently edited (number)
        };
    }

    /****v* datagrid/container
     * SYNOPSIS
     */
    ltk.datagrid.container = 'TABLE';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    ltk.widget.register('ltk:datagrid', ltk.datagrid);

    ltk.datagrid.prototype = new ltk.widget();

    /****v* datagrid/container
     * SYNOPSIS
     */
    ltk.datagrid.prototype.container = 'TABLE';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****v* datagrid/cssclass
     * SYNOPSIS
     */
    ltk.datagrid.prototype.cssclass = 'ltk_datagrid'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* datagrid/onColumnSelect
     * SYNOPSIS
     */
    ltk.datagrid.prototype.onColumnSelect = function(col_no)
    /*
     * FUNCTION
     *      event hook for datagrid header click
     ****
     */
    {
    }

    /****m* datagrid/deleteRows
     * SYNOPSIS
     */
    ltk.datagrid.prototype.deleteRows = function(from, cnt)
    /*
     * FUNCTION
     *      deletes datagrid rows
     * INPUT
     *      * from (int) -- start deleting at row
     *      * cnt (int) -- number of rows to delete
     ****
     */
    {
        this.leaveRow();
    
        var data = this.data.splice(from, cnt);

        this.populate(data);
    }

    /****m* datagrid/insertRow
     * SYNOPSIS
     */
    ltk.datagrid.prototype.insertRows = function(from, cnt, data)
    /*
     * FUNCTION
     *      insert datagrid rows
     * INPUT
     *      * from (int) -- start inserting at row
     *      * cnt (int) -- number of rows to insert
     *      * data (array) -- (optional) data to insert
     ****
     */
    {
        this.leaveRow();
    
        // prepare data
        if (typeof data == 'undefined') {
            data = [];
        }

        var d_cnt = data.length;

        if (d_cnt < cnt) {
            var tmp = {};

            for (var n in this.columns) {
                tmp[n] = '';
            }
        
            for (var i = d_cnt; i < cnt; ++i) {
                data.push(tmp);
            }
        }

        if (from == 0) {
            // insert at the beginnig
            data = [].concat(data, this.data.slice());
        } else if (from >= this.data.length) {
            // at the end of the array
            data = [].concat(this.data.slice(), data);
        } else {
            // in the middle of the array
            data = [].concat(
                this.data.slice(0, from),
                data,
                this.data.slice(from)
            );
        }

        this.populate(data);
    }

    /****m* datagrid/sortColumn
     * SYNOPSIS
     */
    ltk.datagrid.prototype.sortColumn = function(column)
    /*
     * FUNCTION
     *      sort column
     * INPUTS
     *      * column (string) -- name of column to sort
     ****
     */
    {
        var me = this;
    
        if (this.sort.column != column) {
            this.sort.column = column;
            this.sort.dir    = 1;
        } else {
            this.sort.dir = (0 - this.sort.dir);
        }

        this.leaveRow();
    
        this.data.sort(function(a, b) {
            return (me.sort.dir > 0 
                    ? (a[column] > b[column] ? 1 : -1)
                    : (a[column] < b[column] ? 1 : -1));
        });
    
        this.populate(this.data);
    }

    /****m* datagrid/selectRow
     * SYNOPSIS
     */
    ltk.datagrid.prototype.selectRow = function(row, no)
    /*
     * FUNCTION
     *      select a row and highlight it
     * INPUTS
     *      * row (object) -- line to edit
     *      * no (int) -- number of line to edit
     ****
     */
    {
        if (this.select.row_no == no) {
            // row already selected
            return;
        } else if (this.select.row_no >= 0) {
            // there's a previous selected row
            this.select.row_selected.widget.removeClass('ltk_selected');
        }
    
        row.widget.addClass('ltk_selected');
    
        this.select.row_selected = {
            'dialog': row,
            'widget': row.widget
        };
        this.select.row_no = no;

        this.leaveRow();

        this.onSelectRow(row, no);
    }

    /****m* datagrid/onSelectRow
     * SYNOPSIS
     */
    ltk.datagrid.prototype.onSelectRow = function(row, no)
    /*
     * FUNCTION
     *      event handler executed after selecting a row
     * INPUTS
     *      * row (object) -- line to edit
     *      * no (int) -- number of line to edit
     ****
     */
    {
    }

    /****m* datagrid/getRowData
     * SYNOPSIS
     */
    ltk.datagrid.prototype.getRowData = function(row)
    /*
     * FUNCTION
     *      return data of a specified row, or of current selected row,
     *      if first parameter is missing. returns false, if no data
     *      is available.
     * INPUTS
     *      * row (int) -- (optional) row to return data of
     ****
     */
    {
        if (typeof row == 'undefined') {
            row = this.select.row_no;
        }
    
        if (row < 0 || !(row in this.data)) {
            return false;
        } else {
            return this.data[row];
        }
    }

    /****m* datagrid/getRow
     * SYNOPSIS
     */
    ltk.datagrid.prototype.getRow = function()
    /*
     * FUNCTION
     *      return number of selected row
     * OUTPUTS
     *      (int) -- row number
     ****
     */
    {
        var row = this.select.row_no;
    
        return (row >= 0 && row in this.data
                ? row
                : false);
    }

    /****m* datagrid/editRow
     * SYNOPSIS
     */
    ltk.datagrid.prototype.editRow = function(row, no)
    /*
     * FUNCTION
     *      switch to edit mode for specified row
     * INPUTS
     *      * row (object) -- line to edit
     *      * no (int) -- number of line to edit
     ****
     */
    {
        if (this.edit.row_no == no) {
            // same row is already in edit mode
            return;
        } else if (this.edit.row_no >= 0) {
            // there's another row in edit mode
            this.edit.dialog.widget = this.edit.dialog.widget.parentNode.replaceChild(
                this.edit.row_saved.widget, this.edit.dialog.widget
            );
        
            this.data[this.edit.row_no] = this.edit.dialog.getValue();
            this.edit.row_saved.dialog.populate(this.data[this.edit.row_no]);
    
            this.refreshSums();
        }

        this.edit.row_saved = {
            'dialog': row,
            'widget': row.widget.parentNode.replaceChild(this.edit.dialog.widget, row.widget)
        };
    
        this.edit.dialog.populate(this.data[no]);
    
        this.edit.dialog.widget.style.display = 'table-row';

        this.edit.row_no = no;
    }

    /****m* datagrid/leaveRow
     * SYNOPSIS
     */
    ltk.datagrid.prototype.leaveRow = function(cancel)
    /*
     * FUNCTION
     *      leave edit mode of row currently in edit mode
     * INPUTS
     *      * cancel (bool) -- (optional) whether to forget changes in edited row (default: false)
     ****
     */
    {
        if (this.edit.row_no >= 0) {
            // there's another row in edit mode
            this.edit.dialog.widget = this.edit.dialog.widget.parentNode.replaceChild(
                this.edit.row_saved.widget, this.edit.dialog.widget
            );
        
            if (typeof cancel == 'undefined' || !cancel) {
                this.data[this.edit.row_no] = this.edit.dialog.getValue();
                this.edit.row_saved.dialog.populate(this.data[this.edit.row_no]);

                this.refreshSums();
            }

            this.edit.row_no = -1;
            this.edit.row_saved = {};
        }    
    }

    /****m* datagrid/populate
     * SYNOPSIS
     */
    ltk.datagrid.prototype.populate = function(data)
    /*
     * FUNCTION
     *      fill datagrid with data -- remove any already available data
     ****
     */
    {
        var me = this;

        // clear current contents
        this.body.removeChildren();

        // build dialog for edit row
        if (this.editable) {
            this.edit.dialog.rebuild();
        }

        // create cell definition for a row
        var cells = [];
        var row;
    
        for (var name in this.columns) {
            cells.push({
                'html:td': {
                    'name': name
                }
            });
        }
    
        // build datagrid rows as growable table
        var growable = this.addChild(new ltk.growable());
        growable.container = 'TR';
        growable.getDialog = function() {
            me.getDialog();
        }
        growable.attach(this.body, {'children': cells});

        for (var i = 0, cnt = data.length; i < cnt; ++i) {
            row = growable.addRow();
        
            if (this.editable) {
                ltk.evt.addEvent(row.widget, 'dblclick', ltk.closure({'row': row, 'no': i}, function(params) {
                    me.editRow(params.row, params.no);
                }), false);
                ltk.evt.addEvent(row.widget, 'rgtclick', ltk.closure({'row': row, 'no': i}, function(params) {
                    me.edit.menu.setVisible(true);
                }), false);
            }

            if (this.selectable) {
                ltk.evt.addEvent(row.widget, 'lftclick', ltk.closure({'row': row, 'no': i}, function(params) {
                    me.selectRow(params.row, params.no);
                }), false);
            }
        
            row.populate = function(data) {
                /* overwrite populate of sub dialog with own function */
                var tmp;
            
                for (var name in data) {
                    if (!(name in me.columns)) {
                        continue;
                    }

                    tmp = me.columns[name];
                
                    this.refs[name].node.className = 'T' + tmp['type'];
                    this.refs[name].node.innerHTML = 
                        (data[name] != ''
                         ? sprintf(tmp['format'], new String(data[name]))
                         : '&nbsp;');
                }
            }

            row.populate(data[i]);
        }

        this.data = data;
    
        this.refreshSums();
    }

    /****m* datagrid/refreshSums
     * SYNOPSIS
     */
    ltk.datagrid.prototype.refreshSums = function()
    /*
     * FUNCTION
     *      refresh sums in tfoot -- if available
     ****
     */
    {
        if (this.foot == null) {
            return;
        }
    
        var td  = ltk.dom.get('TD', this.foot);
        var i   = 0;
        var cnt = this.data.length;
        var r, sum, tmp;
    
        for (var name in this.columns) {
            tmp = this.columns[name];
        
            if (tmp['type'] == 'number' && tmp['sum']) {
                // calculate column and output sum
                sum = 0;

                for (r = 0; r < cnt; ++r) {
                    sum += Math.round(Number(this.data[r][name]) * tmp['mul']);
                }

                td[i].node.innerHTML = sprintf(tmp['format'], new String(sum / tmp['mul']));
            }
     
            ++i;
        }
    }

    /****m* datagrid/onchange
     * SYNOPSIS
     */
    ltk.datagrid.prototype.onchange = function(value)
    /*
     * FUNCTION
     *      onchange event
     ****
     */
    {
    }

    /****m* datagrid/getValue
     * SYNOPSIS
     */
    ltk.datagrid.prototype.getValue = function()
    /*
     * FUNCTION
     *      get datagrid value
     ****
     */
    {
        return this.widget.node.value;
    }

    /****m* datagrid/onRenderRow
     * SYNOPSIS
     */
    ltk.datagrid.prototype.onRenderRow = function(row_no, row_obj)
    /*
     * FUNCTION
     *      adds possibility to enhance row element eg. with events, styles etc.
     * INPUTS
     *      * row_no (int) -- number of current row
     *      * row_obj (object) -- DOM object node of current row
     ****
     */
    {
    }

    /****m* datagrid/onRenderCell
     * SYNOPSIS
     */
    ltk.datagrid.prototype.onRenderCell = function(row_no, col_no, cell_obj)
    /*
     * FUNCTION
     *      adds possibility to enhance cell element eg. with events, styles etc.
     * INPUTS
     *      * row_no (int) -- number of current row
     *      * col_no (int) -- number of current column
     *      * cell_obj (object) -- DOM object node of current cell
     ****
     */
    {
    }

    /****m* datagrid/attach
     * SYNOPSIS
     */
    ltk.datagrid.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      build data datagrid
     ****
     */
    {
        var sum = false;
        var me  = this;

        var i, cnt, tmp;
    
        this.editable = ('editable' in def && !!def['editable']);
    
        // calculate column width
        var width = ('width' in def
                     ? def['width']
                     : []);
    
        var max = 0;

        for (i = 0, cnt = def['columns'].length; i < cnt; ++i) {
            max += (typeof width[i] == 'number' && width[i] > 0
                    ? width[i]
                    : width[i] = 0);
        }
    
        max = (max > 100 ? 0 : max);
    
        var w = ((100 - max) / cnt);
    
        for (i = 0; i < cnt; ++i) {
            width[i] = (width[i] > 0 ? width[i] : w);
        }
    
        // render datagrid header
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );    
    
        var thead = this.widget.appendChild(ltk.dom.create('THEAD'));
        var tr    = thead.appendChild(ltk.dom.create('TR'));
    
        var th, e; 
    
        // editable functions
        if (this.editable) {
            var toolbar = new ltk.toolbar();
            toolbar.container = 'TH';
            toolbar.attach(tr, {
                'items': [
                    {
                        'name':  'insert_row_above',
                        'image': 'insert_above',
                        'onclick': function() {
                            console.log('insert above');

                            me.insertRows(
                                (me.select.row_no >= 0
                                 ? me.select.row_no
                                 : 0),
                                1
                            );
                        }
                    },
                    {
                        'name':  'insert_row_below',
                        'image': 'insert_below',
                        'onclick': function() {
                            console.log('insert below');

                            me.insertRows(me.select.row_no + 1, 1);
                        }
                    },
                    {
                        'name':  'delete_row',
                        'image': 'delete',
                        'onclick': function() {
                            console.log('delete');
                        
                            if (me.select.row_no >= 0) {
                                me.deleteRows(me.select.row_no, 1);
                            }
                        }
                    }
                ]
            });
            toolbar.widget.setAttribute('colspan', cnt);
        
            tr = thead.appendChild(ltk.dom.create('TR'));
        }
    
        // column headers
        this.columns = {};
    
        for (i = 0, cnt = def['columns'].length; i < cnt; ++i) {
            tmp = def['columns'][i];
        
            this.columns[tmp['name']] = {
                'column':   i,
                'width':    width[i],
                'name':     tmp['name'],
                'label':    (typeof tmp['label'] == 'string'
                             ? tmp['label']
                             : tmp['name']),
                'type':     (typeof tmp['type'] == 'string'
                             ? tmp['type']
                             : 'string'),
                'format':   (typeof tmp['format'] == 'string'
                             ? tmp['format']
                             : '%s')
            }
            this.columns[tmp['name']]['sum'] = 
                ('sum' in tmp && !!tmp['sum'] && this.columns[tmp['name']]['type'] == 'number'
                 ? sum = true
                 : false);
            this.columns[tmp['name']]['mul'] = (sum 
                             ? (typeof tmp['mul'] == 'number'
                                ? tmp['mul']
                                : 1000)
                             : 0);

            th = ltk.dom.create('TH');
            th.node.innerHTML = this.columns[tmp['name']]['label'];

            th.setAttribute('width', this.columns[tmp['name']]['width'] + '%');
            tr.appendChild(th);
        }
    
        // render datagrid filter
        if ('filter' in def && def['filter']) {
            var filter = [];
            var name = ltk.getUniqID('ltk_growable');
        
            for (var i = 0, cnt = def['columns'].length; i < cnt; ++i) {
                filter.push({
                    'html:th': {
                        'children': [
                            {'textline': {
                                'name': def['columns'][i]['field']
                            }}
                        ]
                    }
                });
            }
        
            filter.push({
                'html:th': {
                    'children': [
                        {'button': {
                            'label': '+',
                            'name': 'ltk_growable_add'
                        }},
                        {'button': {
                            'label': '-',
                            'name': 'ltk_growable_remove'
                        }}
                    ]
                }
            });
        
            var growable = this.addChild(new ltk.growable());
            growable.container = 'TR';
            growable.setName   = name;
            growable.getDialog = function() {
                me.getDialog();
            }
            growable.attach(thead, {'addrow': true, 'children': filter});
        }
    
        // draw tfoot for sums if required
        if (sum) {
            var td, cl;
            this.foot = this.widget.appendChild(ltk.dom.create('TFOOT'));
            tr        = this.foot.appendChild(ltk.dom.create('TR'));
        
            for (i = 0, cnt = def['columns'].length; i < cnt; ++i) {
                cl = 'T' + this.columns[def['columns'][i]['name']]['type'];
            
                if (this.columns[def['columns'][i]['name']]['sum']) {
                    cl += ' Tsum';
                }

                td = ltk.dom.create('TD', {
                    'class': cl,
                    '#html': '&nbsp;'
                });

                td.setAttribute('width', width[i] + '%');
                tr.appendChild(td);
            }
        }

        // selectable datagrid
        this.selectable = (!('selectable' in def) || !!def['selectable']);

        // editable datagrid
        if (this.editable) {
            this.selectable = true;     // always selectable, when datagrid is editable
        
            /* add context menu for right click */
            this.edit.menu = new ltk.contextmenu();
            this.edit.menu.attach(ltk.dom.one('BODY'), {
                'items': [
                    {'name': 'insert', 'value': 1},
                    {'name': 'remove', 'value': 2}
                ]
            });
        
            /* setup method for rebuilding edit dialog */
            this.edit.dialog = new ltk.dialog();

            this.edit.dialog.container = 'TR';

            var cells = [];

            for (var n in this.columns) {
                cells.push({
                    'htmltag': {
                        'tag':      'td',
                        'class':    'ltk_textline',
                        'children': [
                            {'textline': {
                                'name': n
                            }}
                        ]
                    }
                });
            }

            this.edit.dialog.rebuild = function() {
                this.attach(me.body, {
                    'styles':   {'display': 'none'},
                    'children': cells
                });

                // attach keyboard events to textlines
                var widget, name, first, last, prev;

                var me2 = this;
                var cnt = cells.length;
                var i   = 0;
            
                for (name in me.columns) {
                    widget = this.getWidget(name).widget;
                
                    ltk.evt.addKeyboardEvent(widget, 'ENTER', function() {
                        me.leaveRow();
                    });
                    ltk.evt.addKeyboardEvent(widget, 'ESC', function() {
                        me.leaveRow(true);
                    });
                
                    if (i == 0) {
                        first = name;
                    }
                
                    ++i;
                
                    if (i == cnt) {
                        last = name;
                    }
                
                    prev = name;
                }
            
                ltk.evt.addKeyboardEvent(
                    this.getWidget(first).widget,
                    'SHIFT+TAB', 
                    function() {
                        me2.getWidget(last).widget.focus();
                    }
                );
                ltk.evt.addKeyboardEvent(
                    this.getWidget(last).widget,
                    'TAB', 
                    function() {
                        me2.getWidget(first).widget.focus();
                    }
                );
            }

            /* attach */
            this.edit.dialog.attach = function(parent, def) {
                this.widget = this.appendChild(
                    parent, 
                    this.container, 
                    def
                );    

                this.processChildren(def, function(parent, instance, def) {
                    instance.attach(parent, def);
                }, this.widget);
            }
        }

        // define datagrid body for data
        this.body = this.widget.appendChild(ltk.dom.create('TBODY'));
    }
})();

/****c* ltk/flexpaper
 * NAME
 *      lima_ltk_flexpaper
 * FUNCTION
 *      ltk flexpaper integration
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('flexpaper' in ltk) return;
    
    /****m* flexpaper/
     * SYNOPSIS
     */
    ltk.flexpaper = function()
    /*
     * FUNCTION
     *      flexpaper constructor
     ****
     */
    {
        this.swfobject = null;

        this.options = {
            'version':          9,
            'allowfullscreen':  true,
        
            'width':            600,
            'height':           500,
            'scale':            1
        }
    }

    ltk.widget.register('flexpaper', ltk.flexpaper);

    ltk.flexpaper.prototype = new ltk.widget();

    /****v* flexpaper/cssclass
     * SYNOPSIS
     */
    ltk.flexpaper.prototype.cssclass = 'ltk_flexpaper'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* flexpaper/attach
     * SYNOPSIS
     */
    ltk.flexpaper.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach button widget
     ****
     */
    {
        this.setOptions(def['options']);

        // build widget
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );
    
        // create mediaplayer object
        var so = new SWFObject(
            '/resources_ltk/flash/FlexPaperViewer.swf', 
            def['name'],
            this.options.width,
            this.options.height,
            this.options.version
        );
    
        so.addParam('allowfullscreen',      this.options.allowfullscreen);
        so.addVariable('height',            this.options.height);
        so.addVariable('width',             this.options.width);
    
        if (!this.options.navigation) {
            so.addVariable('displayheight',     this.options.height);
            so.addVariable('displaywidth',      this.options.width);
        }
    
        so.addVariable('SwfFile', def['file']);
        so.addVariable('Scale',   this.options.scale);
        so.write(this.widget.node);
    
        this.swfobject = so;
    }
})();

/****c* component/gantt
 * NAME
 *      ltk.gantt
 * FUNCTION
 *      gantt diagram
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('gantt' in ltk) return;

    /*
     * misc date functions
     */
    function _isLeapYear(dobj) {
        var y = dobj.getFullYear();

        return (y % 4 == 0 ? (y % 100 != 0 || y % 400 == 0) : false);
    }
    function _getDaysOfMonth(dobj) {
        var days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
        var m = dobj.getMonth();

        return (m == 1 && _isLeapYear(dobj) ? 29 : days[m]);
    }
    function _parse(datestr) {
        var ret = Date.parse(datestr);
        var m;

        if (isNaN(ret)) {
            if (m = datestr.match(/^(\d{4})-(\d{2})-(\d{2})/)) {
                ret = new Date(m[1], m[2] - 1, m[3]);
            }
        }
        
        return ret;
    }

    /*
     * item grouping
     */
    var group = function(parent) {
        parent = (typeof parent != 'undefined' ? parent : null);
        
        var items     = [];
        var collapsed = [];
        var type      = '';
        
        this.addChild = function() {
            return new group(this);
        }
        this.addItem = function(node) {
            items.push(node);
        
            if (parent != null) parent.addItem(node);
        }
        this.toggle = function() {
            var i, len, tmp;
            
            if (collapsed.length == 0) {
                // collapse
                for (i = 0, len = items.length; i < len; ++i) {
                    tmp = items[i].getStyle('display')
                    
                    if (tmp == 'table-row' || tmp == 'block') {
                        type = tmp;
                        
                        collapsed.push(items[i]);
                        
                        items[i].setStyle('display', 'none');
                    }
                }
            } else {
                // expand
                for (i = 0, len = collapsed.length; i < len; ++i) {
                    collapsed[i].setStyle('display', type);
                }
                
                collapsed = [];
            }
            
            return (collapsed.length > 0);
        }
    }

    /****m* gantt/
     * SYNOPSIS
     */
    ltk.gantt = function()
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.id   = ltk.getUniqID('ltk_gantt_');        // uniq ID of gantt chart
        this.data = [];                                 // gantt chart data
        
        this.timeline = {
            'start': 0,
            'end':   0,
            'days':  0
        };
    }

    ltk.widget.register('gantt', ltk.gantt);

    ltk.gantt.prototype = new ltk.widget();

    /****v* gantt/cssclass
     * SYNOPSIS
     */
    ltk.gantt.prototype.cssclass = 'ltk_gantt'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****v* gantt/container
     * SYNOPSIS
     */
    ltk.gantt.prototype.container = 'TABLE';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****m* gantt/populate
     * SYNOPSIS
     */
    ltk.gantt.prototype.populate = function(data)
    /*
     * FUNCTION
     *      populate gantt chart
     * INPUTS
     *      * data (array) -- array of data to populate gantt chart with
     ****
     */
    {
        this.data = ltk.clone(data);

        /*
         * add missing values to data
         */
        var range = (function _walk(data) {
            var tmp;
            var start = -1;
            var end   = -1;
            
            var tasks    = 0;
            var progress = 0;
            // var end = start = Math.floor((new Date()).getTime() / 1000);

            for (var i = 0, len = data.length; i < len; ++i) {
                switch (data[i]['type']) {
                case 'task':
                    ++tasks;
                
                    if (!('progress' in data[i])) {
                        data[i]['progress'] = 0;
                    }

                    progress += data[i]['progress'];

                    data[i]['start'] = ('start' in data[i]
                                        ? Math.floor(_parse(data[i]['start']) / 1000)
                                        : Math.floor(Date().getTime() / 1000));
                                        
                    data[i]['end'] = ('end' in data[i]
                                      ? Math.floor(_parse(data[i]['end']) / 1000)
                                      : data[i]['start']);

                    if (data[i]['start'] > data[i]['end']) {
                        tmp = data[i]['end']; data[i]['end'] = data[i]['start']; data[i]['start'] = tmp;
                    }

                    data[i]['days'] = Math.floor((data[i]['end'] - data[i]['start']) / 86400);
                    
                    if (!('resource' in data[i])) {
                        data[i]['resource'] = '';
                    }

                    start = (start < 0 ? data[i]['start'] : Math.min(data[i]['start'], start));
                    end   = (end   < 0 ? data[i]['end'] : Math.max(data[i]['end'], end));
                    
                    break;
                case 'milestone':
                    data[i]['progress'] = 0;
                    data[i]['resource'] = '';

                    data[i]['start'] = ('start' in data[i]
                                        ? Math.floor(_parse(data[i]['start']) / 1000)
                                        : Math.floor(Date().getTime() / 1000));
                                    
                    data[i]['end']  = data[i]['start'];
                    data[i]['days'] = 1;
                
                    start = (start < 0 ? data[i]['start'] : Math.min(data[i]['start'], start));
                    end   = (end   < 0 ? data[i]['end'] : Math.max(data[i]['end'], end));
                
                    break;
                case 'group':
                    tmp = _walk(data[i]['items']);

                    data[i]['start']    = tmp.start;
                    data[i]['end']      = tmp.end;
                    data[i]['days']     = Math.floor((tmp.end - tmp.start) / 86400);
                    data[i]['progress'] = Math.floor((tmp.progress / (tmp.tasks * 100)) * 100);
                    data[i]['resource'] = '';
                    
                    progress += tmp.progress;
                    tasks    += tmp.tasks;
                    
                    start = (start < 0 ? tmp.start : Math.min(tmp.start, start));
                    end   = (end   < 0 ? tmp.end : Math.max(tmp.end, end));
                    break;
                }
            }
            
            return {'start': start, 'end': end, 'tasks': tasks, 'progress': progress};
        })(this.data);

        this.timeline.start = range.start;
        this.timeline.end   = range.end;
        this.timeline.days  = Math.floor((range.end - range.start) / 86400);

        this.rebuild();
    }

    /****m* gantt/rebuild
     * SYNOPSIS
     */
    ltk.gantt.prototype.rebuild = function()
    /*
     * FUNCTION
     *      rebuild gantt chart
     ****
     */
    {
        var me   = this;
        var refs = {};
        
        this.widget.removeChildren();
    
        // build basic table structure
        this.widget.appendChild(ltk.dom.create('TBODY', {
            'children': [
                {'TR': {
                    'children': [
                        {'TD': {
                            'vAlign':   'top',
                            'children': [
                                {'TABLE': {
                                    'class':    'ltk_gantt_items',
                                    'children': [
                                        {'TBODY': {
                                            '#trigger': function(node) {
                                                refs['items'] = node;
                                            },
                                            'children': [
                                                {'TR': {
                                                    'children': [
                                                        {'TH': {
                                                            'colSpan': 6,
                                                            '#html':   '&nbsp;'
                                                        }}
                                                    ]
                                                }},
                                                {'TR': {
                                                    'children': [
                                                        {'TH': {
                                                            '#html': _('Name')
                                                        }},
                                                        {'TH': {
                                                            '#html': _('Resource')
                                                        }},
                                                        {'TH': {
                                                            '#html': _('Duration')
                                                        }},
                                                        {'TH': {
                                                            '#html': _('Progress')
                                                        }},
                                                        {'TH': {
                                                            '#html': _('Start')
                                                        }},
                                                        {'TH': {
                                                            '#html': _('End')
                                                        }}
                                                    ]
                                                }}
                                            ]
                                        }}
                                    ]
                                }}
                            ]
                        }},
                        {'TD': {
                            'valign':   'top',
                            'children': [
                                {'DIV': {
                                    'class':    'ltk_gantt_timeline',
                                    'children': [
                                        {'TABLE': {
                                            'class':    'ltk_gantt_timeline',
                                            'children': [
                                                {'COLGROUP': {
                                                    '#trigger': function(node) {
                                                        refs['colgroup'] = node;
                                                    }
                                                }},
                                                {'TBODY': {
                                                    '#trigger': function(node) {
                                                        refs['timeline'] = node;
                                                    },
                                                    'children': [
                                                        {'TR': {
                                                            '#trigger': function(node) {
                                                                refs['timeline_unit1'] = node;
                                                            }
                                                        }},
                                                        {'TR': {
                                                            '#trigger': function(node) {
                                                                refs['timeline_unit2'] = node;
                                                            }
                                                        }}
                                                    ]
                                                }}
                                            ]
                                        }}
                                    ]
                                }}
                            ]
                        }}
                    ]
                }}
            ]
        }));
        
        // build timeline
        (function() {
            var day  = 1;
            var dofm = 0;
            var days = Math.floor((me.timeline.end - me.timeline.start) / 86400);
            var pos  = me.timeline.start;
            var tmp, span;

            var timeline = ltk.dom.one('table.ltk_gantt_timeline', this.widget);
            timeline.setStyle('width', (days * 30) + 'px');
            
            for (var i = 0; i < days; ++i) {
                if (day > dofm) {
                    tmp  = new Date(pos * 1000);
                    dofm = _getDaysOfMonth(tmp);
                    day  = tmp.getDate();
                    span = Math.min(dofm - day + 1, days -i);
                    
                    refs['timeline_unit1'].appendChild(ltk.dom.create('TH', {
                        'colSpan': span,
                        '#html':   (span < 3 ? '&nbsp;' : ltk.l10n.cldr.getMonth(ltk.l10n.cldr.T_WIDE, tmp))
                    }));
                }

                refs['timeline_unit2'].appendChild(ltk.dom.create('TH', {
                    '#html': day
                }));
                
                refs['colgroup'].appendChild(ltk.dom.create('COL', {
                    'width': '30'
                }));
                
                ++day; pos += 86400;
            }
        })();
        
        // fill gantt chart with data 
        (function(data) {
            var row   = 0;
            var level = 0;
            
            /*
             * diagram rebuilder
             */
            (function _rebuild(data, group) {
                var tr, task, c, colSpan, td, child_group, id;
                
                for (var i = 0, len = data.length; i < len; ++i) {
                    ++row;
                    
                    if (data[i]['type'] == 'group') {
                        child_group = group.addChild();
                        id = ltk.getUniqID('ltk_gantt_group_');
                        
                        td = {'TD': {
                            'styles': {'paddingLeft': ((level * 15) + 2) + 'px'},
                            'children': [
                                {'A': {
                                    'id':      id,
                                    'href':    'javascript://',
                                    'class':   'ltk_collapsible_expanded',
                                    '#html':   data[i].name,
                                    'onclick': ltk.closure({'group': child_group, 'id': id}, function(params) {
                                        if (params.group.toggle()) {
                                            ltk.dom.one('#' + params.id).replaceClass('ltk_collapsible_expanded', 'ltk_collapsible_collapsed');
                                        } else {
                                            ltk.dom.one('#' + params.id).replaceClass('ltk_collapsible_collapsed', 'ltk_collapsible_expanded');
                                        }
                                    }),
                                    'onfocus': function() {
                                        this.blur();
                                    }
                                }}
                            ]
                        }}
                    } else {
                        td = {'TD': {
                            'styles': {'paddingLeft': ((level * 15) + 2) + 'px'},
                            '#html':  data[i].name
                        }}
                    }

                    refs['items'].appendChild(ltk.dom.create('TR', {
                        '#trigger': function(node) {
                            group.addItem(node);
                        },
                        'children': [
                            td,
                            {'TD': {
                                '#html': data[i].resource
                            }},
                            {'TD': {
                                '#html': data[i].days
                            }},
                            {'TD': {
                                '#html': data[i].progress
                            }},
                            {'TD': {
                                '#html': ltk.l10n.cldr.datef(ltk.l10n.cldr.T_DATE_SHORT, data[i].start)
                            }},
                            {'TD': {
                                '#html': ltk.l10n.cldr.datef(ltk.l10n.cldr.T_DATE_SHORT, data[i].end)
                            }}
                        ]
                    }));
            
                    tr = refs['timeline'].appendChild(ltk.dom.create('TR', {
                        '#trigger': function(node) {
                            group.addItem(node);
                        }
                    }));
                    
                    for (c = 0; c < me.timeline.days; ++c) {
                        if (Math.floor((data[i]['start'] - me.timeline.start) / 86400) == c) {
                            switch (data[i]['type']) {
                            case 'task':
                                tr.appendChild(ltk.dom.create('TD', {
                                    'colSpan':  data[i]['days'],
                                    'children': [
                                        {'DIV': {
                                            'class':    'ltk_gantt_task',
                                            'children': [
                                                {'DIV': {
                                                    'class':  'ltk_gantt_progress',
                                                    'styles': {'width': data[i]['progress'] + '%'}
                                                }}
                                            ]
                                        }}
                                    ]
                                }));
                                break;
                            case 'milestone':
                                tr.appendChild(ltk.dom.create('TD', {
                                    'children': [
                                        {'DIV': {
                                            'class': 'ltk_gantt_milestone'
                                        }}
                                    ]
                                }));
                                break;
                            case 'group':
                                tr.appendChild(ltk.dom.create('TD', {
                                    'colSpan':  data[i]['days'],
                                    'children': [
                                        {'DIV': {
                                            'styles':   {
                                                'height':     '20px',
                                                'background': 'url(/resources_ltk/widgets/gantt/boundary_left.gif) bottom left no-repeat'
                                            },
                                            'children': [
                                                {'DIV': {
                                                    'styles':   {
                                                        'height':     '20px',
                                                        'background': 'url(/resources_ltk/widgets/gantt/boundary_right.gif) bottom right no-repeat'
                                                    },
                                                    'children': [
                                                        {'DIV': {
                                                            'class':    'ltk_gantt_group',
                                                            'children': [
                                                                {'DIV': {
                                                                    'class':  'ltk_gantt_progress',
                                                                    'styles': {'width': data[i]['progress'] + '%'}
                                                                }}
                                                            ]
                                                        }}
                                                    ]
                                                }}
                                            ]
                                        }}
                                    ]
                                }));
                                break;
                            }
                        
                            c += (data[i]['days'] - 1);
                        } else {
                            // normal cell
                            tr.appendChild(ltk.dom.create('TD', {'#html': '&nbsp;'}));
                        }
                    }
                    
                    if (data[i]['type'] == 'group') {
                        ++level;
                        _rebuild(data[i]['items'], child_group);
                        --level;
                    }
                }
            })(data, new group());
        })(this.data);
    }

    /****m* gantt/attach
     * SYNOPSIS
     */
    ltk.gantt.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach 
     * INPUTS
     *      
     * OUTPUTS
     *      
     ****
     */
    {
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );
    }
})();

/****c* ltk/mediaplayer
 * NAME
 *      lima_ltk_mediaplayer
 * FUNCTION
 *      ltk mediaplayer integration
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('mediaplayer' in ltk) return;
    
    /****m* mediaplayer/
     * SYNOPSIS
     */
    ltk.mediaplayer = function()
    /*
     * FUNCTION
     *      mediaplayer constructor
     ****
     */
    {
        this.swfobject = null;
    
        this.options = {
            'version':          9,
            'allowfullscreen':  true,
        
            'width':            402,
            'height':           20,
            'image':            '/resources_ltk/images/shim.gif',
            'autostart':        false,
            'repeat':           false,
            'navigation':       true
        }
    
        this.settings = {
            'searchbar':        true,
            'showicons':        true,
            'shownavigation':   true,
            'thumbsinplaylist': false,
            'showdigits':       true,
            'playlist':         false
        }
    }

    ltk.widget.register('mediaplayer', ltk.mediaplayer);

    ltk.mediaplayer.prototype = new ltk.widget();

    /****v* mediaplayer/cssclass
     * SYNOPSIS
     */
    ltk.mediaplayer.prototype.cssclass = 'ltk_mediaplayer'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* widget/setOptions
     * SYNOPSIS
     */
    ltk.mediaplayer.prototype.setOptions = function(options)
    /*
     * FUNCTION
     *      import misc options for widget
     * INPUTS
     *      * options (object) -- objects to import
     ****
     */
    {
        options = options || {};

        var offset = 20;
    
        if ('navigation' in options && !options['navigation']) {
            offset = 0;
    
            this.settings = {
                'searchbar':        false,
                'showicons':        true,
                'shownavigation':   false,
                'thumbsinplaylist': false,
                'showdigits':       false,
                'playlist':         false
            }
        }
        
        if (typeof options['height'] == 'number') {
            options['height'] += offset;
        }

        this.options = ltk.extend(this.options, options);
    }

    /****m* mediaplayer/attach
     * SYNOPSIS
     */
    ltk.mediaplayer.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach button widget
     ****
     */
    {
        if ('name' in def) {
            def['name'] = ltk.getUniqID('ltk_mediaplayer_');
        }

        this.setOptions(def['options']);
    
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );
    
        // create mediaplayer object
        var so = new SWFObject(
            '/resources_ltk/flash/mediaplayer.swf', 
            def['name'],
            this.options.width,
            this.options.height,
            this.options.version
        );
    
        so.addParam('allowfullscreen',      this.options.allowfullscreen);
        so.addVariable('height',            this.options.height);
        so.addVariable('width',             this.options.width);
    
        if (!this.options.navigation) {
            so.addVariable('displayheight',     this.options.height);
            so.addVariable('displaywidth',      this.options.width);
        }
    
        so.addVariable('file',              def['file']);
        so.addVariable('image',             this.options.image);
        so.addVariable('searchbar',         this.settings.searchbar);
        so.addVariable('showicons',         this.settings.showicons);
        so.addVariable('shownavigation',    this.settings.shownavigation);
        so.addVariable('thumbsinplaylist',  this.settings.thumbsinplaylist);
        so.addVariable('showdigits',        this.settings.showdigits);
        so.addVariable('playlist',          this.settings.playlist);
        so.addVariable('autostart',         this.options.autostart);
        so.addVariable('repeat',            this.options.repeat);
        so.write(this.widget.node);
    
        this.swfobject = so;
    }
})();

/****c* ltk/messagebox
 * NAME
 *      ltk.messagebox
 * FUNCTION
 *      ltk messagebox component
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('messagebox' in ltk) return;
    
    /****m* messagebox/
     * SYNOPSIS
     */
    ltk.messagebox = function()
    /*
     * FUNCTION
     *      messagebox constructor
     ****
     */
    {
        this.layer = new ltk.dom.layer();
    }

    ltk.messagebox.prototype = new ltk.modal();

    /****d* messagebox/T_INFO, T_WARNING, T_CRITICAL
     * SYNOPSIS
     */
    ltk.messagebox.type = {
        'T_INFO':     1,
        'T_WARNING':  2,
        'T_CRITICAL': 3
    }
    /*
     * FUNCTION
     *      types of messageboxes
     ****
     */

    /****d* messagebox/T_OK, T_OKCANCEL, T_YESNO, T_YESNOCANCEL
     * SYNOPSIS
     */
    ltk.messagebox.buttons = {
        'T_OK':          1,
        'T_OKCANCEL':    2,
        'T_YESNO':       3,
        'T_YESNOCANCEL': 4,
        'T_RETRYCANCEL': 5
    }
    /*
     * FUNCTION
     *      types of buttons to show in messagebox
     ****
     */

    /****d* messagebox/T_OK, T_CANCEL, T_YES, T_NO, T_RETRY
     * SYNOPSIS
     */
    ltk.messagebox.action = {
        'T_OK':     -1,
        'T_CANCEL': -2,
        'T_YES':    -3,
        'T_NO':     -4,
        'T_RETRY':  -5
    }
    /*
     * FUNCTION
     *      action button, that was clicked
     ****
     */

    /****m* messagebox/onAction
     * SYNOPSIS
     */
    ltk.messagebox.prototype.onAction = function(action)
    /*
     * FUNCTION
     *      get's called, when a messagebox button is clicked
     * INPUTS
     *      * action (int) -- value of action button, that was clicked
     ****
     */
    {
    }

    /****m* messagebox/attach
     * SYNOPSIS
     */
    ltk.messagebox.prototype.attach = function(parent, options)
    /*
     * FUNCTION
     *      attach button widget
     ****
     */
    {
        var symbol  = '/resources_ltk/images/messagebox_warning.png';
        var buttons = [
            {'button': {
                'label': 'OK',
                'onClick': function() { me.hide(); me.onAction(ltk.messagebox.action.T_OK); }
            }}
        ];
        var width = [100];
        var me    = this;

        options = options || {};

        if ('onAction' in options) {
            me.onAction = options['onAction'];
        }

        if ('type' in options) {
            switch (parseInt(options.type, 10)) {
            case 1:
                symbol = '/resources_ltk/images/messagebox_info.png';
                break;
            case 3:
                symbol = '/resources_ltk/images/messagebox_critical.png';
                break;
            }
        }

        if ('buttons' in options) {
            switch (parseInt(options.buttons, 10)) {
            case 2:
                buttons = [
                    {'button': {
                        'label':   _('Ok'),
                        'onClick': function() { me.hide(); me.onAction(ltk.messagebox.action.T_OK); }
                    }},
                    {'button': {
                        'label':   _('Cancel'),
                        'onClick': function() { me.hide(); me.onAction(ltk.messagebox.action.T_CANCEL); }
                    }}
                ];
                width = [50, 50];
                break;
            case 3:
                buttons = [
                    {'button': {
                        'label': _('Yes'),
                        'onClick': function() { me.hide(); me.onAction(ltk.messagebox.action.T_YES); }
                    }},
                    {'button': {
                        'label': _('No'),
                        'onClick': function() { me.hide(); me.onAction(ltk.messagebox.action.T_NO); }
                    }}
                ];
                width = [50, 50];
                break;
            case 4:
                buttons = [
                    {'button': {
                        'label': _('Yes'),
                        'onClick': function() { me.hide(); me.onAction(ltk.messagebox.action.T_YES); }
                    }},
                    {'button': {
                        'label': _('No'),
                        'onClick': function() { me.hide(); me.onAction(ltk.messagebox.action.T_NO); }
                    }},
                    {'button': {
                        'label': _('Cancel'),
                        'onClick': function() { me.hide(); me.onAction(ltk.messagebox.action.T_CANCEL); }
                    }}
                ];
                width = [33, 33, 34];
                break;
            case 5:
                buttons = [
                    {'button': {
                        'label':   _('Retry'),
                        'onClick': function() { me.hide(); me.onAction(ltk.messagebox.action.T_RETRY); }
                    }},
                    {'button': {
                        'label':   _('Cancel'),
                        'onClick': function() { me.hide(); me.onAction(ltk.messagebox.action.T_CANCEL); }
                    }}
                ];
                width = [50, 50];
                break;
            }
        }
    
        var def = {
            'label':    options.label,
            'styles':   {'width': '300px'},
            'children': [
                {'grid': {
                    'columns':  2,
                    'width':    [1, 99],
                    'onRenderCell': function(row_no, col_no, cell_obj) {
                        if (row_no == 1 && col_no == 2) {
                            cell_obj.node.style.verticalAlign = 'middle';
                        }
                    },
                    'children': [
                        {'hbox': {
                            'children': [
                                {'image': {
                                    'styles': {'margin': '5px'},
                                    'src':    symbol
                                }}
                            ]
                        }},
                        {'label': {
                            'label': options.message
                        }},
                        {'html:span': {}},
                        {'hbox': {
                            'width':    width, 
                            'children': buttons
                        }}
                    ]
                }}
            ]
        };
    
        ltk.modal.prototype.attach.call(this, parent, def);
    }
})();

/****c* ltk/notify
 * NAME
 *      ltk.notify
 * FUNCTION
 *      notification API for LTK
 * COPYRIGHT
 *      copyright (c) 2008-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('notify' in ltk) return;
    
    /*
     * container for notifications
     */
    var container = null;
    
    ltk.dom.ready(function() {
        container = ltk.dom.one('BODY').appendChild(ltk.dom.create('DIV', {
            'class': 'ltk_notify'
        }));
    });

    /*
     * public notify object
     */
    ltk.notify = {
        /****m* notify/createMessage
         * SYNOPSIS
         */
        createMessage: function(msg, options)
        /*
         * FUNCTION
         *      create a notifier message
         * INPUTS
         *      * msg (string) -- message to be displayed in notifier
         *      * options (object) -- (optional) options
         ****
         */
        {
            options = ltk.extend({'visible': ltk.notify.visible}, options || {});
            
            var m = new message(msg, options);
        
            messages.push(m);
        
            m.render();
        },
        
        /****v* notify/visible, hide, steps
         * SYNOPSIS
         */
        visible: 5,
        hide:    2,
        steps:   10
        /*
         * FUNCTION
         *      settings of durations for each notification step
         ****
         */
    }

    /*
     * notify message class
     */
    var messages = [];
    
    function message(msg, options) {
        this.widget  = null;
        this.msg     = msg;
        this.timer   = null;
        this.opacity = null;
        this.options = options;
    }
    
    message.prototype.render = function() {
        var me = this;
        
        container.appendChild(ltk.dom.create('DIV', {
            '#trigger': function(node) {
                me.widget  = node;
                me.opacity = node.getOpacity();
            },
            'class':    'ltk_notify_msg_frame',
            'children': [
                {'div': {
                    'class': 'ltk_notify_msg',
                    '#html': this.msg
                }}
            ]
        }));
    
        window.setTimeout(function() {
            var decr = (me.opacity / (ltk.notify.hide * ltk.notify.steps));
            var hide = ltk.notify.hide * ltk.notify.steps;
        
            me.fade(decr, hide);
        }, 1000 * this.options.visible);
    }

    message.prototype.fade = function(decr, hide) {
        this.opacity -= decr;
        hide--;
    
        if (this.opacity < 0 || hide <= 0) {
            this.opacity = 0;
        }
    
        this.widget.setOpacity(this.opacity);

        if (hide <= 0 || this.opacity <= 0) {
            this.remove();
        } else {
            if (this.opacity <= 5) {
                this.widget.setStyle('position', 'absolute');
            }
        
            var me = this;
        
            window.setTimeout(function() {
                me.fade(decr, hide);
            }, 1000 / ltk.notify.steps);
        }
    }

    message.prototype.remove = function() {
        this.widget.removeNode();
        delete(this);
    }
})();

/****c* ltk/prompt
 * NAME
 *      ltk.prompt
 * FUNCTION
 *      ltk prompt component
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('prompt' in ltk) return;
    
    /****m* prompt/
     * SYNOPSIS
     */
    ltk.prompt = function()
    /*
     * FUNCTION
     *      prompt constructor
     ****
     */
    {
        this.id    = ltk.getUniqID('ltk_prompt_');
        this.layer = new ltk.dom.layer();
    }

    ltk.prompt.prototype = new ltk.modal();

    /****d* prompt/T_OK, T_CANCEL
     * SYNOPSIS
     */
    ltk.prompt.action = {
        'T_OK':     -1,
        'T_CANCEL': -2
    };
    /*
     * FUNCTION
     *      action button, that was clicked
     ****
     */

    /****m* prompt/onAction
     * SYNOPSIS
     */
    ltk.prompt.prototype.onAction = function(action)
    /*
     * FUNCTION
     *      get's called, when a prompt button is clicked
     * INPUTS
     *      * action (int) -- value of action button, that was clicked
     ****
     */
    {
    }

    /****m* prompt/populate
     * SYNOPSIS
     */
    ltk.prompt.prototype.populate = function(data)
    /*
     * FUNCTION
     *      implements population of prompt
     * INPUTS
     *      * data (mixed) -- data to populate prompt dialog with
     ****
     */
    {
        this.getWidget(this.id + '_textline').populate(data);
    }

    /****m* prompt/getValue
     * SYNOPSIS
     */
    ltk.prompt.prototype.getValue = function()
    /*
     * FUNCTION
     *      implements getValue of prompt
     * OUTPUTS
     *      value of prompt
     ****
     */
    {
        return this.getWidget(this.id + '_textline').getValue();
    }

    /****m* prompt/show
     * SYNOPSIS
     */
    ltk.prompt.prototype.show = function()
    /*
     * FUNCTION
     *      show prompt dialog -- focus textline, when dialog is displayed
     ****
     */
    {
        ltk.modal.prototype.show.call(this);

        this.getDialog().getWidget(this.id + '_textline').getNode().node.focus();
    }
    
    /****m* prompt/hide
     * SYNOPSIS
     */
    ltk.prompt.prototype.hide = function()
    /*
     * FUNCTION
     *      hide prompt
     ****
     */
    {
        ltk.modal.prototype.hide.call(this);

        this.getDialog().getWidget(this.id + '_textline').getNode().node.blur();
    }

    /****m* prompt/attach
     * SYNOPSIS
     */
    ltk.prompt.prototype.attach = function(parent, options)
    /*
     * FUNCTION
     *      attach button widget
     ****
     */
    {
        var me = this;

        options = options || {};

        if ('onAction' in options) {
            me.onAction = options['onAction'];
        }
        
        options.message = options.message || '';

        var def = {
            'label':    options.label,
            'styles':   {'width': '300px'},
            'children': [
                {'grid': {
                    'columns':  2,
                    'width':    [1, 99],
                    'onRenderCell': function(row_no, col_no, cell_obj) {
                        if (row_no == 1 && col_no == 2) {
                            cell_obj.node.style.verticalAlign = 'middle';
                        }
                    },
                    'children': [
                        {'hbox': {
                            'children': [
                                {'image': {
                                    'styles': {'margin': '5px'},
                                    'src':    '/resources_ltk/images/messagebox_info.png'
                                }},
                            ]
                        }},
                        {'vbox': {
                            'children': [
                                {'label': {
                                    'label': options.message
                                }},
                                {'textline': {
                                    'name':   this.id + '_textline',
                                    'styles': {'marginTop': '5px', 'marginBottom': '5px'}
                                }}
                            ]
                        }},
                        {'html:span': {}},
                        {'hbox': {
                            'width':    [50, 50],
                            'children': [
                                {'button': {
                                    'label':   _('Ok'),
                                    'onClick': function() { me.hide(); me.onAction(ltk.prompt.action.T_OK); }
                                }},
                                {'button': {
                                    'label':   _('Cancel'),
                                    'onClick': function() { me.hide(); me.onAction(ltk.prompt.action.T_CANCEL); }
                                }}
                            ]
                        }}
                    ]
                }}
            ]
        };

        ltk.modal.prototype.attach.call(this, parent, def);

        ltk.evt.addKeyboardEvent(
            this.getDialog().getWidget(this.id + '_textline').getNode(),
            'ENTER',
            function() {
                me.hide(); me.onAction(ltk.prompt.action.T_OK);
            },
            {'propagate': true, 'default': true}
        );
    }
})();

/****c* ltk/tagedit
 * NAME
 *      ltk.tagedit
 * FUNCTION
 *      tag editor widget
 * COPYRIGHT
 *      copyright (c) 2008-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('tagedit' in ltk) return;
    
    /****m* tagedit/
     * SYNOPSIS
     */
    ltk.tagedit = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.tag_id = ltk.getUniqID('ltk_tagedit_tag') + '_';
        this.tags = [];
    }

    ltk.widget.register('tagedit', ltk.tagedit);

    ltk.tagedit.prototype = new ltk.widget();

    /****v* tagedit/cssclass
     * SYNOPSIS
     */
    ltk.tagedit.prototype.cssclass = 'ltk_tagedit'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* tagedit/attach
     * SYNOPSIS
     */
    ltk.tagedit.prototype.attach = function(parent, def) 
    /*
     * FUNCTION
     *      attach tagedit widget
     ****
     */
    {
        var name = ltk.getUniqID('ltk_tagedit_');
        var me   = this;
    
        this.widget = this.appendChild(
            parent, 
            this.container, 
            def
        );    

        // build dialog
        var dia = new ltk.dialog();
        dia.attach(this.widget, {
            'children': [
                {'hbox': {
                    'width':        [99, 1],
                    'onRenderCell': function(row_no, col_no, cell_obj) {
                        cell_obj.node.style.verticalAlign = 'middle';
                    },
                    'children': [
                        {'textline': {
                            'name': name + '_tag_input'
                        }},
                        {'image': {
                            'src':      '/resources_ltk/silk/add.png',
                            'onclick':  function() {
                                me.addTag(dia.getWidget(name + '_tag_input').getValue());
                            }
                        }}
                    ]
                }},
                {'htmltag': {
                    'tag':   'div',
                    'name':  name + '_tag_output',
                    'class': 'ltk_tagedit_box'
                }}
            ]
        });

        this.input_obj   = dia.getDialog().getWidget(name + '_tag_input').widget;
        this.display_obj = dia.getDialog().getWidget(name + '_tag_output').widget;

        // misc
        this.separator = ('separator' in def && def['separator'].length == 1
                          ? def['separator']
                          : ',');
        this.tags = [];

        // setup tagedit environment
        var me = this;

        ltk.evt.addKeyboardEvent(this.input_obj, 'ENTER', function() {
            me.addTag(me.input_obj.node.value);

            return false;
        }, {'propagate': true, 'default': true});
        ltk.evt.addKeyboardEvent(this.input_obj, 'COMMA', function() {
            me.addTag(me.input_obj.node.value);

            return false;
        }, {'propagate': true, 'default': true});
    
        this.display_obj.removeChildren();
    }

    /****m* tagedit/tagSort
     * SYNOPSIS
     */
    ltk.tagedit.tagSort = function(a, b) 
    /*
     * FUNCTION
     *      implements correct sorting algorithm for sorting a tag list used together
     *      with javascripts native sort method
     * INPUTS
     *      * a (string) -- first word
     *      * b (string) -- second word
     * OUTPUTS
     *      (int) -- sorting option
     ****
     */
    {
        a.tag.replace(/ä/, 'a');
        a.tag.replace(/ö/, 'o');
        a.tag.replace(/ü/, 'u');
        a.tag.replace(/ß/, 's');
        b.tag.replace(/ä/, 'a');
        b.tag.replace(/ö/, 'o');
        b.tag.replace(/ü/, 'u');
        b.tag.replace(/ß/, 's');
    
        return (a.tag == b.tag ? 0 : (a.tag > b.tag ? 1 : -1));
    }

    /****m* tagedit/populate
     * SYNOPSIS
     */
    ltk.tagedit.prototype.populate = function(tags)
    /*
     * FUNCTION
     *      populate tag editor
     * INPUTS
     *      * tags (array) -- array of tags to set for language
     ****
     */
    {
        this.tags = [];
        var tmp;
    
        for (var i = 0, cnt = tags.length; i < cnt; ++i) {
            tmp = tags[i].replace(/^\s/, '').replace(/\s$/, '');
        
            if (tmp != '') {
                this.tags[i] = {
                    'tag':      tmp,
                    'selected': false
                };
            }
        }

        this.tags.sort(ltk.tagedit.tagSort);
        this.refreshList();
    }

    /****m* tagedit/getValue
     * SYNOPSIS
     */
    ltk.tagedit.prototype.getValue = function()
    /*
     * FUNCTION
     *      return value(s) of widget
     * OUTPUTS
     *      (array) -- tags
     ****
     */
    {
        var tags = [];
    
        for (var i = 0, cnt = this.tags.length; i < cnt; ++i) {
            tags.push(this.tags[i]['tag']);
        }
    
        return tags;
    }

    /****m* tagedit/refreshList
     * SYNOPSIS
     */
    ltk.tagedit.prototype.refreshList = function()
    /*
     * FUNCTION
     *      refresh tag list
     ****
     */
    {
        this.display_obj.removeChildren();

        var tmp_id, tmp;
        var me   = this;
        var tags = [];
        var c;
    
        for (var i = 0, cnt = this.tags.length; i < cnt; ++i) {
            tmp_id = this.tag_id + (i + 1);
    
            tags.push(this.tags[i]['tag']);
    
            if (this.tags[i]['selected']) {
                tmp =  
                    {'span': {
                        '#html': this.tags[i]['tag'],
                        'class': 'ltk_tagedit_tag'
                    }};
                c = 'ltk_tagedit_selectedtagholder';
            } else {
                tmp =
                    {'a': {
                        '#html':    this.tags[i]['tag'],
                        'class':    'ltk_tagedit_tag',
                        'href':     'javascript://',
                        'onclick':  ltk.closure((i + 1), function(id) {
                            me.selectTag(id);
                        })
                    }};
                c = 'ltk_tagedit_tagholder';
            }
    
            this.display_obj.appendChild(ltk.dom.create('div', {
                'id':       tmp_id,
                'class':    c, 
                'children': [
                    tmp,
                    {'img': {
                        'src':      '/resources_ltk/images/shim.gif',
                        'class':    'ltk_tagedit_icon',
                        'onclick':  ltk.closure((i + 1), function(id) {
                            me.removeTag(id);
                        })
                    }}
                ]
            }));
        }

        this.onRefreshList(tags);
    }

    /****m* tagedit/onRefreshList
     * SYNOPSIS
     */
    ltk.tagedit.prototype.onRefreshList = function(tags) 
    /*
     * FUNCTION
     *      refreshlist event handler
     * INPUTS
     *      * tags (array) -- array of tags, which are available after refresh
     ****
     */
    {
    }

    /****m* tagedit/addTag
     * SYNOPSIS
     */
    ltk.tagedit.prototype.addTag = function(tag)
    /*
     * FUNCTION
     *      add tag
     * INPUTS
     *      * tag (string) -- tag to add
     ****
     */
    {
        // first check, if tag is already in taglist
        tag = tag.toLowerCase().replace(/^\s+/, '').replace(/\s+$/, '');
    
        if (tag == '') {
            return;
        }

        var regexp = new RegExp(this.separator);

        if (tag.search(regexp) >= 0) {
            var tags = tag.split(regexp);
        
            for (var i = 0, cnt = tags.length; i < cnt; ++i) {
                this.addTag(tags[i]);
            }
        
            return;
        }
    
        for (var i = 0, ins = true, cnt = this.tags.length; i < cnt; ++i) {
            if (this.tags[i]['tag'] == tag) {
                ins = false;
                break;
            }
        }
    
        this.input_obj.node.value = '';
    
        if (ins) {
            // add new tag
            this.tags.push({
                'tag':      tag,
                'selected': false
            });
        
            this.tags.sort(ltk.tagedit.tagSort);
    
            this.refreshList();
        }
    }

    /****m* tagedit/removeTag
     * SYNOPSIS
     */
    ltk.tagedit.prototype.removeTag = function(id)
    /*
     * FUNCTION
     *      remove tag from tag list of current set language
     * INPUTS
     *      * id (int) -- ID of tag to remove
     ****
     */
    {
        --id;
    
        if (id in this.tags) {
            this.tags.splice(id, 1);

            this.refreshList();
        }
    }

    /****m* tagedit/selectTag
     * SYNOPSIS
     */
    ltk.tagedit.prototype.selectTag = function(id)
    /*
     * FUNCTION
     *      select a tag from list
     * INPUTS
     *      * id (int) -- ID of tag to select
     ****
     */
    {
        --id;

        if (id in this.tags) {
            var me = this;
            var tmp;

            for (var i = 0, cnt = this.tags.length; i < cnt; ++i) {
                if (i == id && !this.tags[i].selected) {
                    // select new element
                    this.tags[i].selected = true;
         
                    tmp = ltk.dom.one('#' + this.tag_id + (i + 1)).firstChild().replaceNode(
                        ltk.dom.create('span', {
                            '#html': this.tags[i]['tag'],
                            'class': 'ltk_tagedit_tag'
                        })
                    );

                    tmp.parentNode().node.className = 'ltk_tagedit_selectedtagholder';

                    // fire event handler
                    this.onSelect(i + 1, this.tags[i]['tag']);
                } else if (i != id && this.tags[i].selected) {
                    // unselect old element
                    this.tags[i].selected = false;

                    tmp = ltk.dom.one('#' + this.tag_id + (i + 1)).firstChild().replaceNode(
                        ltk.dom.create('a', {
                            '#html':    this.tags[i]['tag'],
                            'class':    'ltk_tagedit_tag',
                            'href':     'javascript://',
                            'onclick':  ltk.closure((i + 1), function(id) {
                                me.selectTag(id);
                            })
                        })
                    );

                    tmp.parentNode().node.className = 'ltk_tagedit_tagholder';
                }
            }
        }
    }

    /****m* tagedit/onSelect
     * SYNOPSIS
     */
    ltk.tagedit.prototype.onSelect = function(id, tag)
    /*
     * FUNCTION
     *      event is fired, when tag is selected
     * INPUTS
     *      * id (int) -- id of selected tag
     *      * tag (string) -- name of selected tag
     ****
     */
    {
    }
})();

/****c* widget/scrollbox
 * NAME
 *      lima_ltk_scrollbox
 * FUNCTION
 *      scrollbox widget
 * COPYRIGHT
 *      copyright (c) 2010 by Clipdealer gmbH
 * AUTHOR
 *      Daniel Garding <d.garding@clipdealer.de>
 ****
 */



/****c* ltk/voting
 * NAME
 *      ltk.voting
 * FUNCTION
 *      voting tool
 * COPYRIGHT
 *      copyright (c) 2008-2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('voting' in ltk) return;
    
    /****m* voting/
     * SYNOPSIS
     */
    ltk.voting = function() 
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
    }

    ltk.widget.register('ltk:voting', ltk.voting);

    ltk.voting.prototype = new ltk.widget();

    /****v* voting/cssclass
     * SYNOPSIS
     */
    ltk.voting.prototype.cssclass = 'ltk_voting'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****v* voting/container
     * SYNOPSIS
     */
    ltk.voting.prototype.container = 'DIV';
    /*
     * FUNCTION
     *      name of container
     ****
     */

    /****m* voting/getValue
     * SYNOPSIS
     */
    ltk.voting.prototype.getValue = function()
    /*
     * FUNCTION
     *      get value of widget
     * OUTPUTS
     *      (string) -- widget value
     ****
     */
    {
        return this.value;
    }

    /****m* voting/onClick
     * SYNOPSIS 
     */
    ltk.voting.prototype.onClick = function(value) 
    /*
     * FUNCTION
     *      dummy method called when onclick handler is fired
     * INPUTS
     *      * value (int) -- value of vote
     ****
     */
    {
    }

    /****m* voting/populate
     * SYNOPSIS
     */
    ltk.voting.prototype.populate = function(value) 
    /*
     * FUNCTION
     *      set voted value and move indicator
     * INPUTS
     *      * value (int) -- value of vote
     ****
     */
    {
        this.value = value;
    
        this.showVoting(value);
    }

    /****m* voting/showVoting
     * SYNOPSIS
     */
    ltk.voting.prototype.showVoting = function(value) 
    /*
     * FUNCTION
     *      show voted value
     * INPUTS
     *      * value (int) -- value of vote
     ****
     */
    {
        this.indicator.node.style.width = (value * 17) + 'px';
        this.indicator.node.style.backgroundColor = this.cactive;
    }

    /****m* voting/showIndicator
     * SYNOPSIS
     */
    ltk.voting.prototype.showIndicator = function(value) 
    /*
     * FUNCTION
     *      show indicator when user votes
     * INPUTS
     *      * value (int) -- current set value
     ****
     */
    {
        this.indicator.node.style.width = (value * 17) + 'px';
        this.indicator.node.style.backgroundColor = this.chighlight;
    }

    /****m* voting/deactivate
     * SYNOPSIS
     */
    ltk.voting.prototype.deactivate = function() 
    /*
     * FUNCTION
     *      deactivate voting tool
     ****
     */
    {
        this.active = false;
    }

    /****m* voting/activate
     * SYNOPSIS
     */
    ltk.voting.prototype.activate = function() 
    /*
     * FUNCTION
     *      activate voting tool
     ****
     */
    {
        this.active = true;
    }

    /****m* voting/assimilate
     * SYNOPSIS
     */
    ltk.voting.prototype.assimilate = function(node)
    /*
     * FUNCTION
     *      assimilate a node to become a voting tool
     ****
     */
    {
        var opt = {};
        
        if (node.hasAttribute('ltk:voting') && (tmp = ltk.string.trim(node.getAttribute('ltk:voting'))) != '') {
            opt = eval('(' + tmp + ')');
        }
        
        this.indicator  = ltk.dom.one('#' + opt['indicator_id']);
        this.cactive    = opt['active'];
        this.chighlight = opt['highlight'];
    
        this.value      = 0;
        this.active     = true;
    
        // attach events to voting stars
        var me = this;

        ltk.dom.get('div', ltk.dom.one('#' + opt['stars_id'])).forEach(function(node, i) {
            ltk.evt.addEvent(node, 'mouseover', function() {
                if (me.active) me.showIndicator(i + 1);
            });
            
            ltk.evt.addEvent(node, 'mouseout', function() {
                if (me.active) me.showVoting(me.value);
            });
            
            ltk.evt.addEvent(node, 'click', function() {
                if (me.active) {
                    me.value = (i + 1);

                    me.showIndicator(me.value);
                    me.onClick(me.value);
                }
            });
        });
    }
    
    /****m* voting/attach
     * SYNOPSIS
     */
    ltk.voting.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach voting widget
     ****
     */
    {
    }
})();


/* CodeMirror main module (http://codemirror.net/)
 *
 * Implements the CodeMirror constructor and prototype, which take care
 * of initializing the editor frame, and providing the outside interface.
 */

// The CodeMirrorConfig object is used to specify a default
// configuration. If you specify such an object before loading this
// file, the values you put into it will override the defaults given
// below. You can also assign to it after loading.
var CodeMirrorConfig = window.CodeMirrorConfig || {};

var CodeMirror = (function(){
  function setDefaults(object, defaults) {
    for (var option in defaults) {
      if (!object.hasOwnProperty(option))
        object[option] = defaults[option];
    }
  }
  function forEach(array, action) {
    for (var i = 0; i < array.length; i++)
      action(array[i]);
  }
  function createHTMLElement(el) {
    if (document.createElementNS && document.documentElement.namespaceURI !== null)
      return document.createElementNS("http://www.w3.org/1999/xhtml", el)
    else
      return document.createElement(el)
  }

  // These default options can be overridden by passing a set of
  // options to a specific CodeMirror constructor. See manual.html for
  // their meaning.
  setDefaults(CodeMirrorConfig, {
    stylesheet: [],
    path: "",
    parserfile: [],
    basefiles: ["util.js", "stringstream.js", "select.js", "undo.js", "editor.js", "tokenize.js"],
    iframeClass: null,
    passDelay: 200,
    passTime: 50,
    lineNumberDelay: 200,
    lineNumberTime: 50,
    continuousScanning: false,
    saveFunction: null,
    onLoad: null,
    onChange: null,
    undoDepth: 50,
    undoDelay: 800,
    disableSpellcheck: true,
    textWrapping: true,
    readOnly: false,
    width: "",
    height: "300px",
    minHeight: 100,
    onDynamicHeightChange: null,
    autoMatchParens: false,
    markParen: null,
    unmarkParen: null,
    parserConfig: null,
    tabMode: "indent", // or "spaces", "default", "shift"
    enterMode: "indent", // or "keep", "flat"
    electricChars: true,
    reindentOnLoad: false,
    activeTokens: null,
    onCursorActivity: null,
    lineNumbers: false,
    firstLineNumber: 1,
    onLineNumberClick: null,
    indentUnit: 2,
    domain: null,
    noScriptCaching: false,
    incrementalLoading: false
  });

  function addLineNumberDiv(container, firstNum) {
    var nums = createHTMLElement("div"),
        scroller = createHTMLElement("div");
    nums.style.position = "absolute";
    nums.style.height = "100%";
    if (nums.style.setExpression) {
      try {nums.style.setExpression("height", "this.previousSibling.offsetHeight + 'px'");}
      catch(e) {} // Seems to throw 'Not Implemented' on some IE8 versions
    }
    nums.style.top = "0px";
    nums.style.left = "0px";
    nums.style.overflow = "hidden";
    container.appendChild(nums);
    scroller.className = "CodeMirror-line-numbers";
    nums.appendChild(scroller);
    scroller.innerHTML = "<div>" + firstNum + "</div>";
    return nums;
  }

  function frameHTML(options) {
    if (typeof options.parserfile == "string")
      options.parserfile = [options.parserfile];
    if (typeof options.basefiles == "string")
      options.basefiles = [options.basefiles];
    if (typeof options.stylesheet == "string")
      options.stylesheet = [options.stylesheet];

    var sp = " spellcheck=\"" + (options.disableSpellcheck ? "false" : "true") + "\"";
    var html = ["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\"><html" + sp + "><head>"];
    // Hack to work around a bunch of IE8-specific problems.
    html.push("<meta http-equiv=\"X-UA-Compatible\" content=\"IE=EmulateIE7\"/>");
    var queryStr = options.noScriptCaching ? "?nocache=" + new Date().getTime().toString(16) : "";
    forEach(options.stylesheet, function(file) {
      html.push("<link rel=\"stylesheet\" type=\"text/css\" href=\"" + file + queryStr + "\"/>");
    });
    forEach(options.basefiles.concat(options.parserfile), function(file) {
      if (!/^https?:/.test(file)) file = options.path + file;
      html.push("<script type=\"text/javascript\" src=\"" + file + queryStr + "\"><" + "/script>");
    });
    html.push("</head><body style=\"border-width: 0;\" class=\"editbox\"" + sp + "></body></html>");
    return html.join("");
  }

  var internetExplorer = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent);

  function CodeMirror(place, options) {
    // Use passed options, if any, to override defaults.
    this.options = options = options || {};
    setDefaults(options, CodeMirrorConfig);

    // Backward compatibility for deprecated options.
    if (options.dumbTabs) options.tabMode = "spaces";
    else if (options.normalTab) options.tabMode = "default";
    if (options.cursorActivity) options.onCursorActivity = options.cursorActivity;

    var frame = this.frame = createHTMLElement("iframe");
    if (options.iframeClass) frame.className = options.iframeClass;
    frame.frameBorder = 0;
    frame.style.border = "0";
    frame.style.width = '100%';
    frame.style.height = '100%';
    // display: block occasionally suppresses some Firefox bugs, so we
    // always add it, redundant as it sounds.
    frame.style.display = "block";

    var div = this.wrapping = createHTMLElement("div");
    div.style.position = "relative";
    div.className = "CodeMirror-wrapping";
    div.style.width = options.width;
    div.style.height = (options.height == "dynamic") ? options.minHeight + "px" : options.height;
    // This is used by Editor.reroutePasteEvent
    var teHack = this.textareaHack = createHTMLElement("textarea");
    div.appendChild(teHack);
    teHack.style.position = "absolute";
    teHack.style.left = "-10000px";
    teHack.style.width = "10px";
    teHack.tabIndex = 100000;

    // Link back to this object, so that the editor can fetch options
    // and add a reference to itself.
    frame.CodeMirror = this;
    if (options.domain && internetExplorer) {
      this.html = frameHTML(options);
      frame.src = "javascript:(function(){document.open();" +
        (options.domain ? "document.domain=\"" + options.domain + "\";" : "") +
        "document.write(window.frameElement.CodeMirror.html);document.close();})()";
    }
    else {
      frame.src = "javascript:;";
    }

    if (place.appendChild) place.appendChild(div);
    else place(div);
    div.appendChild(frame);
    if (options.lineNumbers) this.lineNumbers = addLineNumberDiv(div, options.firstLineNumber);

    this.win = frame.contentWindow;
    if (!options.domain || !internetExplorer) {
      this.win.document.open();
      this.win.document.write(frameHTML(options));
      this.win.document.close();
    }
  }

  CodeMirror.prototype = {
    init: function() {
      // Deprecated, but still supported.
      if (this.options.initCallback) this.options.initCallback(this);
      if (this.options.onLoad) this.options.onLoad(this);
      if (this.options.lineNumbers) this.activateLineNumbers();
      if (this.options.reindentOnLoad) this.reindent();
      if (this.options.height == "dynamic") this.setDynamicHeight();
    },

    getCode: function() {return this.editor.getCode();},
    setCode: function(code) {this.editor.importCode(code);},
    selection: function() {this.focusIfIE(); return this.editor.selectedText();},
    reindent: function() {this.editor.reindent();},
    reindentSelection: function() {this.focusIfIE(); this.editor.reindentSelection(null);},

    focusIfIE: function() {
      // in IE, a lot of selection-related functionality only works when the frame is focused
      if (this.win.select.ie_selection && document.activeElement != this.frame)
        this.focus();
    },
    focus: function() {
      this.win.focus();
      if (this.editor.selectionSnapshot) // IE hack
        this.win.select.setBookmark(this.win.document.body, this.editor.selectionSnapshot);
    },
    replaceSelection: function(text) {
      this.focus();
      this.editor.replaceSelection(text);
      return true;
    },
    replaceChars: function(text, start, end) {
      this.editor.replaceChars(text, start, end);
    },
    getSearchCursor: function(string, fromCursor, caseFold) {
      return this.editor.getSearchCursor(string, fromCursor, caseFold);
    },

    undo: function() {this.editor.history.undo();},
    redo: function() {this.editor.history.redo();},
    historySize: function() {return this.editor.history.historySize();},
    clearHistory: function() {this.editor.history.clear();},

    grabKeys: function(callback, filter) {this.editor.grabKeys(callback, filter);},
    ungrabKeys: function() {this.editor.ungrabKeys();},

    setParser: function(name, parserConfig) {this.editor.setParser(name, parserConfig);},
    setSpellcheck: function(on) {this.win.document.body.spellcheck = on;},
    setStylesheet: function(names) {
      if (typeof names === "string") names = [names];
      var activeStylesheets = {};
      var matchedNames = {};
      var links = this.win.document.getElementsByTagName("link");
      // Create hashes of active stylesheets and matched names.
      // This is O(n^2) but n is expected to be very small.
      for (var x = 0, link; link = links[x]; x++) {
        if (link.rel.indexOf("stylesheet") !== -1) {
          for (var y = 0; y < names.length; y++) {
            var name = names[y];
            if (link.href.substring(link.href.length - name.length) === name) {
              activeStylesheets[link.href] = true;
              matchedNames[name] = true;
            }
          }
        }
      }
      // Activate the selected stylesheets and disable the rest.
      for (var x = 0, link; link = links[x]; x++) {
        if (link.rel.indexOf("stylesheet") !== -1) {
          link.disabled = !(link.href in activeStylesheets);
        }
      }
      // Create any new stylesheets.
      for (var y = 0; y < names.length; y++) {
        var name = names[y];
        if (!(name in matchedNames)) {
          var link = this.win.document.createElement("link");
          link.rel = "stylesheet";
          link.type = "text/css";
          link.href = name;
          this.win.document.getElementsByTagName('head')[0].appendChild(link);
        }
      }
    },
    setTextWrapping: function(on) {
      if (on == this.options.textWrapping) return;
      this.win.document.body.style.whiteSpace = on ? "" : "nowrap";
      this.options.textWrapping = on;
      if (this.lineNumbers) {
        this.setLineNumbers(false);
        this.setLineNumbers(true);
      }
    },
    setIndentUnit: function(unit) {this.win.indentUnit = unit;},
    setUndoDepth: function(depth) {this.editor.history.maxDepth = depth;},
    setTabMode: function(mode) {this.options.tabMode = mode;},
    setEnterMode: function(mode) {this.options.enterMode = mode;},
    setLineNumbers: function(on) {
      if (on && !this.lineNumbers) {
        this.lineNumbers = addLineNumberDiv(this.wrapping,this.options.firstLineNumber);
        this.activateLineNumbers();
      }
      else if (!on && this.lineNumbers) {
        this.wrapping.removeChild(this.lineNumbers);
        this.wrapping.style.paddingLeft = "";
        this.lineNumbers = null;
      }
    },

    cursorPosition: function(start) {this.focusIfIE(); return this.editor.cursorPosition(start);},
    firstLine: function() {return this.editor.firstLine();},
    lastLine: function() {return this.editor.lastLine();},
    nextLine: function(line) {return this.editor.nextLine(line);},
    prevLine: function(line) {return this.editor.prevLine(line);},
    lineContent: function(line) {return this.editor.lineContent(line);},
    setLineContent: function(line, content) {this.editor.setLineContent(line, content);},
    removeLine: function(line){this.editor.removeLine(line);},
    insertIntoLine: function(line, position, content) {this.editor.insertIntoLine(line, position, content);},
    selectLines: function(startLine, startOffset, endLine, endOffset) {
      this.win.focus();
      this.editor.selectLines(startLine, startOffset, endLine, endOffset);
    },
    nthLine: function(n) {
      var line = this.firstLine();
      for (; n > 1 && line !== false; n--)
        line = this.nextLine(line);
      return line;
    },
    lineNumber: function(line) {
      var num = 0;
      while (line !== false) {
        num++;
        line = this.prevLine(line);
      }
      return num;
    },
    jumpToLine: function(line) {
      if (typeof line == "number") line = this.nthLine(line);
      this.selectLines(line, 0);
      this.win.focus();
    },
    currentLine: function() { // Deprecated, but still there for backward compatibility
      return this.lineNumber(this.cursorLine());
    },
    cursorLine: function() {
      return this.cursorPosition().line;
    },
    cursorCoords: function(start) {return this.editor.cursorCoords(start);},

    activateLineNumbers: function() {
      var frame = this.frame, win = frame.contentWindow, doc = win.document, body = doc.body,
          nums = this.lineNumbers, scroller = nums.firstChild, self = this;
      var barWidth = null;

      nums.onclick = function(e) {
        var handler = self.options.onLineNumberClick;
        if (handler) {
          var div = (e || window.event).target || (e || window.event).srcElement;
          var num = div == nums ? NaN : Number(div.innerHTML);
          if (!isNaN(num)) handler(num, div);
        }
      };

      function sizeBar() {
        if (frame.offsetWidth == 0) return;
        for (var root = frame; root.parentNode; root = root.parentNode){}
        if (!nums.parentNode || root != document || !win.Editor) {
          // Clear event handlers (their nodes might already be collected, so try/catch)
          try{clear();}catch(e){}
          clearInterval(sizeInterval);
          return;
        }

        if (nums.offsetWidth != barWidth) {
          barWidth = nums.offsetWidth;
          frame.parentNode.style.paddingLeft = barWidth + "px";
        }
      }
      function doScroll() {
        nums.scrollTop = body.scrollTop || doc.documentElement.scrollTop || 0;
      }
      // Cleanup function, registered by nonWrapping and wrapping.
      var clear = function(){};
      sizeBar();
      var sizeInterval = setInterval(sizeBar, 500);

      function ensureEnoughLineNumbers(fill) {
        var lineHeight = scroller.firstChild.offsetHeight;
        if (lineHeight == 0) return;
        var targetHeight = 50 + Math.max(body.offsetHeight, Math.max(frame.offsetHeight, body.scrollHeight || 0)),
            lastNumber = Math.ceil(targetHeight / lineHeight);
        for (var i = scroller.childNodes.length; i <= lastNumber; i++) {
          var div = createHTMLElement("div");
          div.appendChild(document.createTextNode(fill ? String(i + self.options.firstLineNumber) : "\u00a0"));
          scroller.appendChild(div);
        }
      }

      function nonWrapping() {
        function update() {
          ensureEnoughLineNumbers(true);
          doScroll();
        }
        self.updateNumbers = update;
        var onScroll = win.addEventHandler(win, "scroll", doScroll, true),
            onResize = win.addEventHandler(win, "resize", update, true);
        clear = function(){
          onScroll(); onResize();
          if (self.updateNumbers == update) self.updateNumbers = null;
        };
        update();
      }

      function wrapping() {
        var node, lineNum, next, pos, changes = [], styleNums = self.options.styleNumbers;

        function setNum(n, node) {
          // Does not typically happen (but can, if you mess with the
          // document during the numbering)
          if (!lineNum) lineNum = scroller.appendChild(createHTMLElement("div"));
          if (styleNums) styleNums(lineNum, node, n);
          // Changes are accumulated, so that the document layout
          // doesn't have to be recomputed during the pass
          changes.push(lineNum); changes.push(n);
          pos = lineNum.offsetHeight + lineNum.offsetTop;
          lineNum = lineNum.nextSibling;
        }
        function commitChanges() {
          for (var i = 0; i < changes.length; i += 2)
            changes[i].innerHTML = changes[i + 1];
          changes = [];
        }
        function work() {
          if (!scroller.parentNode || scroller.parentNode != self.lineNumbers) return;

          var endTime = new Date().getTime() + self.options.lineNumberTime;
          while (node) {
            setNum(next++, node.previousSibling);
            for (; node && !win.isBR(node); node = node.nextSibling) {
              var bott = node.offsetTop + node.offsetHeight;
              while (scroller.offsetHeight && bott - 3 > pos) {
                var oldPos = pos;
                setNum("&nbsp;");
                if (pos <= oldPos) break;
              }
            }
            if (node) node = node.nextSibling;
            if (new Date().getTime() > endTime) {
              commitChanges();
              pending = setTimeout(work, self.options.lineNumberDelay);
              return;
            }
          }
          while (lineNum) setNum(next++);
          commitChanges();
          doScroll();
        }
        function start(firstTime) {
          doScroll();
          ensureEnoughLineNumbers(firstTime);
          node = body.firstChild;
          lineNum = scroller.firstChild;
          pos = 0;
          next = self.options.firstLineNumber;
          work();
        }

        start(true);
        var pending = null;
        function update() {
          if (pending) clearTimeout(pending);
          if (self.editor.allClean()) start();
          else pending = setTimeout(update, 200);
        }
        self.updateNumbers = update;
        var onScroll = win.addEventHandler(win, "scroll", doScroll, true),
            onResize = win.addEventHandler(win, "resize", update, true);
        clear = function(){
          if (pending) clearTimeout(pending);
          if (self.updateNumbers == update) self.updateNumbers = null;
          onScroll();
          onResize();
        };
      }
      (this.options.textWrapping || this.options.styleNumbers ? wrapping : nonWrapping)();
    },

    setDynamicHeight: function() {
      var self = this, activity = self.options.onCursorActivity, win = self.win, body = win.document.body,
          lineHeight = null, timeout = null, vmargin = 2 * self.frame.offsetTop;
      body.style.overflowY = "hidden";
      win.document.documentElement.style.overflowY = "hidden";
      this.frame.scrolling = "no";

      function updateHeight() {
        var trailingLines = 0, node = body.lastChild, computedHeight;
        while (node && win.isBR(node)) {
          if (!node.hackBR) trailingLines++;
          node = node.previousSibling;
        }
        if (node) {
          lineHeight = node.offsetHeight;
          computedHeight = node.offsetTop + (1 + trailingLines) * lineHeight;
        }
        else if (lineHeight) {
          computedHeight = trailingLines * lineHeight;
        }
        if (computedHeight) {
          if (self.options.onDynamicHeightChange)
            computedHeight = self.options.onDynamicHeightChange(computedHeight);
          if (computedHeight)
            self.wrapping.style.height = Math.max(vmargin + computedHeight, self.options.minHeight) + "px";
        }
      }
      setTimeout(updateHeight, 300);
      self.options.onCursorActivity = function(x) {
        if (activity) activity(x);
        clearTimeout(timeout);
        timeout = setTimeout(updateHeight, 100);
      };
    }
  };

  CodeMirror.InvalidLineHandle = {toString: function(){return "CodeMirror.InvalidLineHandle";}};

  CodeMirror.replace = function(element) {
    if (typeof element == "string")
      element = document.getElementById(element);
    return function(newElement) {
      element.parentNode.replaceChild(newElement, element);
    };
  };

  CodeMirror.fromTextArea = function(area, options) {
    if (typeof area == "string")
      area = document.getElementById(area);

    options = options || {};
    if (area.style.width && options.width == null)
      options.width = area.style.width;
    if (area.style.height && options.height == null)
      options.height = area.style.height;
    if (options.content == null) options.content = area.value;

    function updateField() {
      area.value = mirror.getCode();
    }
    if (area.form) {
      if (typeof area.form.addEventListener == "function")
        area.form.addEventListener("submit", updateField, false);
      else
        area.form.attachEvent("onsubmit", updateField);
      if (typeof area.form.submit == "function") {
        var realSubmit = area.form.submit;
        function wrapSubmit() {
          updateField();
          // Can't use realSubmit.apply because IE6 is too stupid
          area.form.submit = realSubmit;
          area.form.submit();
          area.form.submit = wrapSubmit;
        }
        area.form.submit = wrapSubmit;
      }
    }

    function insert(frame) {
      if (area.nextSibling)
        area.parentNode.insertBefore(frame, area.nextSibling);
      else
        area.parentNode.appendChild(frame);
    }

    area.style.display = "none";
    var mirror = new CodeMirror(insert, options);
    mirror.save = updateField;
    mirror.toTextArea = function() {
      updateField();
      area.parentNode.removeChild(mirror.wrapping);
      area.style.display = "";
      if (area.form) {
        if (typeof area.form.submit == "function")
          area.form.submit = realSubmit;
        if (typeof area.form.removeEventListener == "function")
          area.form.removeEventListener("submit", updateField, false);
        else
          area.form.detachEvent("onsubmit", updateField);
      }
    };

    return mirror;
  };

  CodeMirror.isProbablySupported = function() {
    // This is rather awful, but can be useful.
    var match;
    if (window.opera)
      return Number(window.opera.version()) >= 9.52;
    else if (/Apple Computer, Inc/.test(navigator.vendor) && (match = navigator.userAgent.match(/Version\/(\d+(?:\.\d+)?)\./)))
      return Number(match[1]) >= 3;
    else if (document.selection && window.ActiveXObject && (match = navigator.userAgent.match(/MSIE (\d+(?:\.\d*)?)\b/)))
      return Number(match[1]) >= 6;
    else if (match = navigator.userAgent.match(/gecko\/(\d{8})/i))
      return Number(match[1]) >= 20050901;
    else if (match = navigator.userAgent.match(/AppleWebKit\/(\d+)/))
      return Number(match[1]) >= 525;
    else
      return null;
  };

  return CodeMirror;
})();


/****c* bbcode/bbcode_editor
 * NAME
 *      bbcode_me.js
 * FUNCTION
 *      BBCode specific stuff for codemirror component.
 * COPYRIGHT
 *      copyright (c) 2011 by ClipDealer GmbH
 * AUTHOR
 *      Harald Lapp <h.lapp@clipdealer.de>
 ****
 */

;(function() {
    if (!('codemirror' in ltk)) return;
    
    /*
     * create container for dialog
     */
    function container() {
        return ltk.dom.one('BODY').appendChild(ltk.dom.create('DIV'));
    }

    ltk.codemirror.addEditor('bbcode', {
        getToolbarItems: function() {
            var me = this;
            
            return [{
                'items': [{
                    'tooltip': {'span': {'#html': _('fett')}},
                    'image':   'text_bold',
                    'onclick': function() {
                        me.formatInline('[b]', '[/b]');
                    }
                }, {
                    'tooltip': {'span': {'#html': _('kursiv')}},
                    'image':   'text_italic',
                    'onclick': function() {
                        me.formatInline('[i]', '[/i]');
                    }
                }, {
                    'tooltip': {'span': {'#html': _('unterstrichen')}},
                    'image':   'text_underline',
                    'onclick': function() {
                        me.formatInline('[u]', '[/u]');
                    }
                }, {
                    'tooltip': {'span': {'#html': _('durchgestrichen')}},
                    'image':   'text_strikethrough',
                    'onclick': function() {
                        me.formatInline('[s]', '[/s]');
                    }
                }, {
                    'tooltip': {'span': {'#html': _('proportional')}},
                    'image':   'text_allcaps',
                    'onclick': function() {
                        me.formatInline('[tt]', '[/tt]');
                    }
                }, {
                    'tooltip': {'span': {'#html': _('hochgestellt')}},
                    'image':   'text_superscript',
                    'onclick': function() {
                        me.formatInline('[sup]', '[/sup]');
                    }
                }, {
                    'tooltip': {'span': {'#html': _('tiefgestellt')}},
                    'image':   'text_subscript',
                    'onclick': function() {
                        me.formatInline('[sub]', '[/sub]');
                    }
                }, 
                '-',
                {
                    'tooltip': {'span': {'#html': _('&Uuml;berschrift 1')}},
                    'image':   'text_heading_1',
                    'onclick': function() {
                        me.formatInline('[h1]', '[/h1]\n\n');
                    }
                },
                {
                    'tooltip': {'span': {'#html': _('&Uuml;berschrift 2')}},
                    'image':   'text_heading_2',
                    'onclick': function() {
                        me.formatInline('[h2]', '[/h2]\n\n');
                    }
                },
                {
                    'tooltip': {'span': {'#html': _('&Uuml;berschrift 3')}},
                    'image':   'text_heading_3',
                    'onclick': function() {
                        me.formatInline('[h3]', '[/h3]\n\n');
                    }
                },
                '-',
                {
                    'tooltip': {'span': {'#html': _('linksb&uuml;ndig')}},
                    'image':   'text_align_left',
                    'onclick': function() {
                        me.formatInline('[left]', '[/left]');
                    }
                },
                {
                    'tooltip': {'span': {'#html': _('zentriert')}},
                    'image':   'text_align_center',
                    'onclick': function() {
                        me.formatInline('[center]', '[/center]');
                    }
                },
                {
                    'tooltip': {'span': {'#html': _('rechtsb&uuml;ndig')}},
                    'image':   'text_align_right',
                    'onclick': function() {
                        me.formatInline('[right]', '[/right]');
                    }
                },
                {
                    'tooltip': {'span': {'#html': _('Blocksatz')}},
                    'image':   'text_align_justify',
                    'onclick': function() {
                        me.formatInline('[justify]', '[/justify]');
                    }
                },
                '-',
                {
                    'tooltip': {'span': {'#html': _('zitieren')}},
                    'image':   'comment',
                    'onclick': function() {
                        me.formatInline('[quote]', '[/quote]\n\n');
                    }
                },
                {
                    'tooltip': {'span': {'#html': _('Liste')}},
                    'image':   'text_list_bullets',
                    'onclick': function() {
                        me.formatInline('[list][li]', '[/li][/list]\n\n');
                    }
                },
                '-',
                {
                    'tooltip': {'span': {'#html': _('Gallerie')}},
                    'image':   'application_view_tile',
                    'onclick': function() {
                        me.formatInline('[gallery]', '[/gallery]');
                    }
                },
                // {
                //     'tooltip': {'span': {'#html': _('Trennlinie')}},
                //     'image':   'text_horizontalrule',
                //     'onclick': function() {
                //         me.replaceSelection('\n\n[hr]\n\n', true);
                //     }
                // },
                '-',
                {
                    'tooltip': {'span': {'#html': _('Link einf&uuml;gen')}},
                    'image':   'link',
                    'onclick': (function() {
                        var p = ltk.proxy(function() {
                            var prompt = new ltk.prompt();
                            prompt.attach(container(), {
                                'label':    _('URL des Links eingeben'),
                                'onAction': function(action) {
                                    if (action == ltk.prompt.action.T_OK) {
                                        var value = ltk.string.trim(prompt.getValue());
                                        
                                        if (ltk.string.match(/^https?:\/\//i, value)){
                                            // URL
                                            me.replaceSelection('[url]' + value + '[/url]', true);
                                        }
                                    }

                                    me.editor.focus();
                                }
                            });
                            
                            return prompt;
                        });
                        
                        return function() {
                            switch (me.getVersion()) {
                            case 1:
                                p().populate(me.editor.selection());
                                break;
                            case 2:
                                p().populate(me.editor.getSelection());
                                break;
                            }
                            p().show();
                        }
                    })()
                },
                {
                    'tooltip': {'span': {'#html': _('Bild oder Photo einf&uuml;gen')}},
                    'image':   'photo_link',
                    'onclick': (function() {
                        var p = ltk.proxy(function() {
                            var prompt = new ltk.prompt();
                            prompt.attach(container(), {
                                'label':    _('http URL des Bildes oder Media-ID'),
                                'onAction': function(action) {
                                    if (action == ltk.prompt.action.T_OK) {
                                        var value = ltk.string.trim(prompt.getValue());
                                        
                                        if (ltk.string.match(/^\d+$/, value)) {
                                            // media ID
                                            me.replaceSelection('[media]' + value + '[/media]', true);
                                        } else if (ltk.string.match(/^https?:\/\//i, value)){
                                            // URL
                                            me.replaceSelection('[img]' + value + '[/img]', true);
                                        }
                                    }

                                    me.editor.focus();
                                }
                            });
                            
                            return prompt;
                        });
                        
                        return function() {
                            switch (me.getVersion()) {
                            case 1:
                                p().populate(me.editor.selection());
                                break;
                            case 2:
                                p().populate(me.editor.getSelection());
                                break;
                            }
                            p().show();
                        }
                    })()
                },
                {
                    'tooltip': {'span': {'#html': _('Histogramm einf&uuml;gen')}},
                    'image':   'chart_curve_link',
                    'onclick': (function() {
                        var p = ltk.proxy(function() {
                            var prompt = new ltk.prompt();
                            prompt.attach(container(), {
                                'label':    _('Media-ID f&uuml;r Histogram'),
                                'onAction': function(action) {
                                    if (action == ltk.prompt.action.T_OK) {
                                        var value = ltk.string.trim(prompt.getValue());
                                        
                                        if (ltk.string.match(/^\d+$/, value)) {
                                            // media ID
                                            me.replaceSelection('[histogram]' + value + '[/histogram]', true);
                                        }
                                    }

                                    me.editor.focus();
                                }
                            });
                            
                            return prompt;
                        });
                        
                        return function() {
                            switch (me.getVersion()) {
                            case 1:
                                p().populate(me.editor.selection());
                                break;
                            case 2:
                                p().populate(me.editor.getSelection());
                                break;
                            }
                            p().show();
                        }
                    })()
                },
                {
                    'tooltip': {'span': {'#html': _('Smilies')}},
                    'image':   'emoticon_smile',
                    'onclick': (function() {
                        var p = ltk.proxy(function() {
                            var modal = new ltk.modal();
                            
                            function draw(smiley) {
                                modal.hide();
                                
                                me.replaceSelection(smiley);
                                me.editor.focus();
                            }
                            
                            modal.attach(container(), {
                                'label':    _('Emoticons'),
                                'styles':   {'width': '225px'},
                                'children': [
                                    {'toolbar': {
                                        'groups': [{'items': [
                                            {'image': '/resources/smilies/smiley.gif', 'onclick': function() { draw(':)'); }},
                                            {'image': '/resources/smilies/wink.gif', 'onclick': function() { draw(';)'); }},
                                            {'image': '/resources/smilies/cheesy.gif', 'onclick': function() { draw(':D'); }},
                                            {'image': '/resources/smilies/grin.gif', 'onclick': function() { draw(';D'); }},
                                            {'image': '/resources/smilies/angry.gif', 'onclick': function() { draw('>:('); }},
                                            {'image': '/resources/smilies/sad.gif', 'onclick': function() { draw(':('); }},
                                            {'image': '/resources/smilies/shocked.gif', 'onclick': function() { draw(':o'); }},
                                            {'image': '/resources/smilies/cool.gif', 'onclick': function() { draw('8)'); }}
                                        ]}]
                                    }},
                                    {'toolbar': {
                                        'groups': [{'items': [
                                            {'image': '/resources/smilies/huh.gif', 'onclick': function() { draw('???'); }},
                                            {'image': '/resources/smilies/rolleyes.gif', 'onclick': function() { draw('::)'); }},
                                            {'image': '/resources/smilies/tongue.gif', 'onclick': function() { draw(':P'); }},
                                            {'image': '/resources/smilies/embarassed.gif', 'onclick': function() { draw(':-['); }},
                                            {'image': '/resources/smilies/lipsrsealed.gif', 'onclick': function() { draw(':-X'); }},
                                            {'image': '/resources/smilies/undecided.gif', 'onclick': function() { draw(':-\\'); }},
                                            {'image': '/resources/smilies/kiss.gif', 'onclick': function() { draw(':-*'); }},
                                            {'image': '/resources/smilies/cry.gif', 'onclick': function() { draw(':\'('); }}
                                        ]}]
                                    }},
                                    {'html:span': {}},
                                    {'hbox': {
                                        'width':    [25, 50, 25],
                                        'children': [
                                            {'html:span': {}},
                                            {'button': {
                                                'label':   _('Cancel'),
                                                'onClick': function() { modal.hide(); }
                                            }},
                                            {'html:span': {}}
                                        ]
                                    }}
                                ]
                            });
                            
                            return modal;
                        });
                        
                        return function() {
                            p().show();
                        }
                    })()
                }]
            }, {
                'items': [
                    // {
                    //     'image':   'find',
                    //     'onclick': function() {
                    //     }
                    // },
                    // {
                    //     'image':   'text_replace',
                    //     'onclick': function() {
                    //     }
                    // },
                    // '-',
                    {
                        'image':   'arrow_undo',
                        'onclick': function() {
                            me.editor.undo();
                        }
                    },
                    {
                        'image':   'arrow_redo',
                        'onclick': function() {
                            me.editor.redo();
                        }
                    }
                ]
            }];
        }
    });
})();


CodeMirror.defineMode("bbcode", function(config, parserConfig) {
    var indentUnit = config.indentUnit;
    var Kludges = {
        autoSelfClosers: {},
        doNotIndent:     {},
        allowUnquoted:   true
    };

    // Return variables for tokenizers
    var tagName, type;

    function inText(stream, state) {
        function chain(parser) {
            state.tokenize = parser;
            return parser(stream, state);
        }

        var ch = stream.next();
        
        if (ch == "[") {
            type = stream.eat("/") ? "closeTag": "openTag";
            stream.eatSpace();
            tagName = "";
            var c;
            while ((c = stream.eat(/[^\s\u00a0=\[\]\"\'\/?]/))) tagName += c;
            state.tokenize = inTag;
            return "bbc-tag";
        } else {
            stream.eatWhile(/[^\[]/);
            return null;
        }
    }

    function inTag(stream, state) {
        var ch = stream.next();
        if (ch == "]" || (ch == "/" && stream.eat("]"))) {
            state.tokenize = inText;
            type = ch == "]" ? "endTag": "selfcloseTag";
            return "bbc-tag";
        } else if (ch == "=") {
            type = "equals";
            return null;
        } else if (/[\'\"]/.test(ch)) {
            state.tokenize = inAttribute(ch);
            return state.tokenize(stream, state);
        } else {
            stream.eatWhile(/[^\s\u00a0=\[\]\"\'\/?]/);
            return "bbc-word";
        }
    }

    function inAttribute(quote) {
        return function(stream, state) {
            while (!stream.eol()) {
                if (stream.next() == quote) {
                    state.tokenize = inTag;
                    break;
                }
            }
            return "bbc-attribute";
        };
    }

    function inBlock(style, terminator) {
        return function(stream, state) {
            while (!stream.eol()) {
                if (stream.match(terminator)) {
                    state.tokenize = inText;
                    break;
                }
                stream.next();
            }
            return style;
        };
    }

    var curState, setStyle;

    function pass() {
        for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]);
    }
    function cont() {
        pass.apply(null, arguments);
        return true;
    }

    function pushContext(tagName, startOfLine) {
        var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent);
        curState.context = {
            prev: curState.context,
            tagName: tagName,
            indent: curState.indented,
            startOfLine: startOfLine,
            noIndent: noIndent
        };
    }
    function popContext() {
        if (curState.context) curState.context = curState.context.prev;
    }

    function element(type) {
        if (type == "openTag") {
            curState.tagName = tagName;
            return cont(attributes, endtag(curState.startOfLine));
        } else if (type == "closeTag") {
            popContext();
            return cont(endclosetag);
        } else return cont();
    }
    function endtag(startOfLine) {
        return function(type) {
            if (type == "selfcloseTag" || (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase())))
            return cont();

            if (type == "endTag") {
                pushContext(curState.tagName, startOfLine);
                return cont();
            }

            return cont();
        };
    }
    function endclosetag(type) {
        if (type == "endTag") return cont();
        return pass();
    }

    function attributes(type) {
        if (type == "bbc-word") {
            setStyle = "bbc-attname";
            return cont(attributes);
        }
        if (type == "equals") return cont(attvalue, attributes);
        return pass();
    }
    function attvalue(type) {
        if (type == "bbc-word" && Kludges.allowUnquoted) {
            setStyle = "bbc-attribute";
            return cont();
        }
        if (type == "bbc-attribute") return cont();
        return pass();
    }

    return {
        startState: function() {
            return {
                tokenize: inText,
                cc: [],
                indented: 0,
                startOfLine: true,
                tagName: null,
                context: null
            };
        },

        token: function(stream, state) {
            if (stream.sol()) {
                state.startOfLine = true;
                state.indented = stream.indentation();
            }
            if (stream.eatSpace()) return null;

            setStyle = type = tagName = null;
            var style = state.tokenize(stream, state);
            if ((style || type) && style != "bbc-comment") {
                curState = state;
                while (true) {
                    var comb = state.cc.pop() || element;
                    if (comb(type || style)) break;
                }
            }
            state.startOfLine = false;
            return setStyle || style;
        },

        indent: function(state, textAfter) {
            var context = state.context;
            if (context && context.noIndent) return 0;
            if (context && /^<\//.test(textAfter))
            context = context.prev;
            while (context && !context.startOfLine)
            context = context.prev;
            if (context) return context.indent + indentUnit;
            else return 0;
        },

        electricChars: "/"
    };
});


function sprintf ( ) {
    // Return a formatted string  
    // 
    // version: 909.322
    // discuss at: http://phpjs.org/functions/sprintf
    // +   original by: Ash Searle (http://hexmen.com/blog/)
    // + namespaced by: Michael White (http://getsprink.com)
    // +    tweaked by: Jack
    // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +      input by: Paulo Ricardo F. Santos
    // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +      input by: Brett Zamir (http://brett-zamir.me)
    // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // *     example 1: sprintf("%01.2f", 123.1);
    // *     returns 1: 123.10
    // *     example 2: sprintf("[%10s]", 'monkey');
    // *     returns 2: '[    monkey]'
    // *     example 3: sprintf("[%'#10s]", 'monkey');
    // *     returns 3: '[####monkey]'
    var regex = /%%|%(\d+\$)?([-+\'#0 ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([scboxXuidfegEG])/g;
    var a = arguments, i = 0, format = a[i++];

    // pad()
    var pad = function (str, len, chr, leftJustify) {
        if (!chr) {chr = ' ';}
        var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr);
        return leftJustify ? str + padding : padding + str;
    };

    // justify()
    var justify = function (value, prefix, leftJustify, minWidth, zeroPad, customPadChar) {
        var diff = minWidth - value.length;
        if (diff > 0) {
            if (leftJustify || !zeroPad) {
                value = pad(value, minWidth, customPadChar, leftJustify);
            } else {
                value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length);
            }
        }
        return value;
    };

    // formatBaseX()
    var formatBaseX = function (value, base, prefix, leftJustify, minWidth, precision, zeroPad) {
        // Note: casts negative numbers to positive ones
        var number = value >>> 0;
        prefix = prefix && number && {'2': '0b', '8': '0', '16': '0x'}[base] || '';
        value = prefix + pad(number.toString(base), precision || 0, '0', false);
        return justify(value, prefix, leftJustify, minWidth, zeroPad);
    };

    // formatString()
    var formatString = function (value, leftJustify, minWidth, precision, zeroPad, customPadChar) {
        if (precision != null) {
            value = value.slice(0, precision);
        }
        return justify(value, '', leftJustify, minWidth, zeroPad, customPadChar);
    };

    // doFormat()
    var doFormat = function (substring, valueIndex, flags, minWidth, _, precision, type) {
        var number;
        var prefix;
        var method;
        var textTransform;
        var value;

        if (substring == '%%') {return '%';}

        // parse flags
        var leftJustify = false, positivePrefix = '', zeroPad = false, prefixBaseX = false, customPadChar = ' ';
        var flagsl = flags.length;
        for (var j = 0; flags && j < flagsl; j++) {
            switch (flags.charAt(j)) {
                case ' ': positivePrefix = ' '; break;
                case '+': positivePrefix = '+'; break;
                case '-': leftJustify = true; break;
                case "'": customPadChar = flags.charAt(j+1); break;
                case '0': zeroPad = true; break;
                case '#': prefixBaseX = true; break;
            }
        }

        // parameters may be null, undefined, empty-string or real valued
        // we want to ignore null, undefined and empty-string values
        if (!minWidth) {
            minWidth = 0;
        } else if (minWidth == '*') {
            minWidth = +a[i++];
        } else if (minWidth.charAt(0) == '*') {
            minWidth = +a[minWidth.slice(1, -1)];
        } else {
            minWidth = +minWidth;
        }

        // Note: undocumented perl feature:
        if (minWidth < 0) {
            minWidth = -minWidth;
            leftJustify = true;
        }

        if (!isFinite(minWidth)) {
            throw new Error('sprintf: (minimum-)width must be finite');
        }

        if (!precision) {
            precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : undefined;
        } else if (precision == '*') {
            precision = +a[i++];
        } else if (precision.charAt(0) == '*') {
            precision = +a[precision.slice(1, -1)];
        } else {
            precision = +precision;
        }

        // grab value using valueIndex if required?
        value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++];

        switch (type) {
            case 's': return formatString(String(value), leftJustify, minWidth, precision, zeroPad, customPadChar);
            case 'c': return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad);
            case 'b': return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
            case 'o': return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
            case 'x': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
            case 'X': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad).toUpperCase();
            case 'u': return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
            case 'i':
            case 'd':
                number = parseInt(+value, 10);
                prefix = number < 0 ? '-' : positivePrefix;
                value = prefix + pad(String(Math.abs(number)), precision, '0', false);
                return justify(value, prefix, leftJustify, minWidth, zeroPad);
            case 'e':
            case 'E':
            case 'f':
            case 'F':
            case 'g':
            case 'G':
                number = +value;
                prefix = number < 0 ? '-' : positivePrefix;
                method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())];
                textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2];
                value = prefix + Math.abs(number)[method](precision);
                return justify(value, prefix, leftJustify, minWidth, zeroPad)[textTransform]();
            default: return substring;
        }
    };

    return format.replace(regex, doFormat);
}    //    (new String(sum / tmp['mul'])).sprintf(tmp['format']);


/**
 * SWFObject v1.5: Flash Player detection and embed - http://blog.deconcept.com/swfobject/
 *
 * SWFObject is (c) 2007 Geoff Stearns and is released under the MIT License:
 * http://www.opensource.org/licenses/mit-license.php
 *
 */
if(typeof deconcept=="undefined"){var deconcept=new Object();}if(typeof deconcept.util=="undefined"){deconcept.util=new Object();}if(typeof deconcept.SWFObjectUtil=="undefined"){deconcept.SWFObjectUtil=new Object();}deconcept.SWFObject=function(_1,id,w,h,_5,c,_7,_8,_9,_a){if(!document.getElementById){return;}this.DETECT_KEY=_a?_a:"detectflash";this.skipDetect=deconcept.util.getRequestParameter(this.DETECT_KEY);this.params=new Object();this.variables=new Object();this.attributes=new Array();if(_1){this.setAttribute("swf",_1);}if(id){this.setAttribute("id",id);}if(w){this.setAttribute("width",w);}if(h){this.setAttribute("height",h);}if(_5){this.setAttribute("version",new deconcept.PlayerVersion(_5.toString().split(".")));}this.installedVer=deconcept.SWFObjectUtil.getPlayerVersion();if(!window.opera&&document.all&&this.installedVer.major>7){deconcept.SWFObject.doPrepUnload=true;}if(c){this.addParam("bgcolor",c);}var q=_7?_7:"high";this.addParam("quality",q);this.setAttribute("useExpressInstall",false);this.setAttribute("doExpressInstall",false);var _c=(_8)?_8:window.location;this.setAttribute("xiRedirectUrl",_c);this.setAttribute("redirectUrl","");if(_9){this.setAttribute("redirectUrl",_9);}};deconcept.SWFObject.prototype={useExpressInstall:function(_d){this.xiSWFPath=!_d?"expressinstall.swf":_d;this.setAttribute("useExpressInstall",true);},setAttribute:function(_e,_f){this.attributes[_e]=_f;},getAttribute:function(_10){return this.attributes[_10];},addParam:function(_11,_12){this.params[_11]=_12;},getParams:function(){return this.params;},addVariable:function(_13,_14){this.variables[_13]=_14;},getVariable:function(_15){return this.variables[_15];},getVariables:function(){return this.variables;},getVariablePairs:function(){var _16=new Array();var key;var _18=this.getVariables();for(key in _18){_16[_16.length]=key+"="+_18[key];}return _16;},getSWFHTML:function(){var _19="";if(navigator.plugins&&navigator.mimeTypes&&navigator.mimeTypes.length){if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","PlugIn");this.setAttribute("swf",this.xiSWFPath);}_19="<embed type=\"application/x-shockwave-flash\" src=\""+this.getAttribute("swf")+"\" width=\""+this.getAttribute("width")+"\" height=\""+this.getAttribute("height")+"\" style=\""+this.getAttribute("style")+"\"";_19+=" id=\""+this.getAttribute("id")+"\" name=\""+this.getAttribute("id")+"\" ";var _1a=this.getParams();for(var key in _1a){_19+=[key]+"=\""+_1a[key]+"\" ";}var _1c=this.getVariablePairs().join("&");if(_1c.length>0){_19+="flashvars=\""+_1c+"\"";}_19+="/>";}else{if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","ActiveX");this.setAttribute("swf",this.xiSWFPath);}_19="<object id=\""+this.getAttribute("id")+"\" classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" width=\""+this.getAttribute("width")+"\" height=\""+this.getAttribute("height")+"\" style=\""+this.getAttribute("style")+"\">";_19+="<param name=\"movie\" value=\""+this.getAttribute("swf")+"\" />";var _1d=this.getParams();for(var key in _1d){_19+="<param name=\""+key+"\" value=\""+_1d[key]+"\" />";}var _1f=this.getVariablePairs().join("&");if(_1f.length>0){_19+="<param name=\"flashvars\" value=\""+_1f+"\" />";}_19+="</object>";}return _19;},write:function(_20){if(this.getAttribute("useExpressInstall")){var _21=new deconcept.PlayerVersion([6,0,65]);if(this.installedVer.versionIsValid(_21)&&!this.installedVer.versionIsValid(this.getAttribute("version"))){this.setAttribute("doExpressInstall",true);this.addVariable("MMredirectURL",escape(this.getAttribute("xiRedirectUrl")));document.title=document.title.slice(0,47)+" - Flash Player Installation";this.addVariable("MMdoctitle",document.title);}}if(this.skipDetect||this.getAttribute("doExpressInstall")||this.installedVer.versionIsValid(this.getAttribute("version"))){var n=(typeof _20=="string")?document.getElementById(_20):_20;n.innerHTML=this.getSWFHTML();return true;}else{if(this.getAttribute("redirectUrl")!=""){document.location.replace(this.getAttribute("redirectUrl"));}}return false;}};deconcept.SWFObjectUtil.getPlayerVersion=function(){var _23=new deconcept.PlayerVersion([0,0,0]);if(navigator.plugins&&navigator.mimeTypes.length){var x=navigator.plugins["Shockwave Flash"];if(x&&x.description){_23=new deconcept.PlayerVersion(x.description.replace(/([a-zA-Z]|\s)+/,"").replace(/(\s+r|\s+b[0-9]+)/,".").split("."));}}else{if(navigator.userAgent&&navigator.userAgent.indexOf("Windows CE")>=0){var axo=1;var _26=3;while(axo){try{_26++;axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash."+_26);_23=new deconcept.PlayerVersion([_26,0,0]);}catch(e){axo=null;}}}else{try{var axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");}catch(e){try{var axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");_23=new deconcept.PlayerVersion([6,0,21]);axo.AllowScriptAccess="always";}catch(e){if(_23.major==6){return _23;}}try{axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");}catch(e){}}if(axo!=null){_23=new deconcept.PlayerVersion(axo.GetVariable("$version").split(" ")[1].split(","));}}}return _23;};deconcept.PlayerVersion=function(_29){this.major=_29[0]!=null?parseInt(_29[0]):0;this.minor=_29[1]!=null?parseInt(_29[1]):0;this.rev=_29[2]!=null?parseInt(_29[2]):0;};deconcept.PlayerVersion.prototype.versionIsValid=function(fv){if(this.major<fv.major){return false;}if(this.major>fv.major){return true;}if(this.minor<fv.minor){return false;}if(this.minor>fv.minor){return true;}if(this.rev<fv.rev){return false;}return true;};deconcept.util={getRequestParameter:function(_2b){var q=document.location.search||document.location.hash;if(_2b==null){return q;}if(q){var _2d=q.substring(1).split("&");for(var i=0;i<_2d.length;i++){if(_2d[i].substring(0,_2d[i].indexOf("="))==_2b){return _2d[i].substring((_2d[i].indexOf("=")+1));}}}return "";}};deconcept.SWFObjectUtil.cleanupSWFs=function(){var _2f=document.getElementsByTagName("OBJECT");for(var i=_2f.length-1;i>=0;i--){_2f[i].style.display="none";for(var x in _2f[i]){if(typeof _2f[i][x]=="function"){_2f[i][x]=function(){};}}}};if(deconcept.SWFObject.doPrepUnload){if(!deconcept.unloadSet){deconcept.SWFObjectUtil.prepUnload=function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){};window.attachEvent("onunload",deconcept.SWFObjectUtil.cleanupSWFs);};window.attachEvent("onbeforeunload",deconcept.SWFObjectUtil.prepUnload);deconcept.unloadSet=true;}}if(!document.getElementById&&document.all){document.getElementById=function(id){return document.all[id];};}var getQueryParamValue=deconcept.util.getRequestParameter;var FlashObject=deconcept.SWFObject;var SWFObject=deconcept.SWFObject;

/****c* ltk/expose
 * NAME
 *      lima_ltk_expose
 * FUNCTION
 *      implements expose effect
 * COPYRIGHT
 *      copyright (c) 2010 by Harald Lapp
 * AUTHOR
 *      Harald Lapp <harald.lapp@gmail.com>
 ****
 */

;(function() {
    if ('expose' in ltk) return;
    
    /****m* expose/
     * SYNOPSIS
     */
    ltk.expose = function()
    /*
     * FUNCTION
     *      expose constructor
     ****
     */
    {
        this.layer = new ltk.dom.layer();
    }

    ltk.expose.prototype = new ltk.widget();

    /****v* expose/cssclass
     * SYNOPSIS
     */
    ltk.expose.prototype.cssclass = 'ltk_expose'
    /*
     * FUNCTION
     *      classname to apply to widget container
     ****
     */

    /****m* expose/hide
     * SYNOPSIS
     */
    ltk.expose.prototype.hide = function() 
    /*
     * FUNCTION
     *      show unexposed
     ****
     */
    {
        this.widget.removeClass('ltk_expose');
        this.overlay.hide();
    }

    /****m* expose/show
     * SYNOPSIS
     */
    ltk.expose.prototype.show = function() 
    /*
     * FUNCTION
     *      show exposed
     ****
     */
    {
        this.overlay.show();
        this.widget.addClass('ltk_expose');

        this.layer.up();
    }

    /****m* expose/attach
     * SYNOPSIS
     */
    ltk.expose.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach button widget
     ****
     */
    {
        this.widget  = parent;
        this.overlay = new ltk.overlay();
        this.overlay.attach(null, {});
    
        // initialize layer manager
        this.layer.push([this.overlay.widget, this.widget]);
    }
})();

/****c* ltk/animation
 * NAME
 *      lima_ltk_animation
 * FUNCTION
 *      animation widget
 * COPYRIGHT
 *      copyright (c) 2008-2010 by Daniel Garding
 * AUTHOR
 *      Daniel Garding <daniel.garding@clipdealer.de>
 ****
 */
;(function() {
    if ('animation' in ltk) return;

    /****m* animation/
     * SYNOPSIS
     */
    ltk.animation = function()
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
        this.choosebtn = {};
        
        this.opt = {};
        
        this.flag = false;
        
        this.id = ltk.getUniqID('ltk_animation_');
        
        this.params = {};
        
    }
    
    /****m* expose/attach
     * SYNOPSIS
     */
    ltk.animation.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach button widget
     ****
     */
    {
        this.widget  = parent;
    }
    
    
    ltk.animation.prototype.fade = function(speed)
    {
        var that = this;
        var currentOpacity = ltk.dom.first('div', that.widget).getOpacity()*100;
        var target = currentOpacity > 0 ? 0 : 100;        
        var speed = (typeof speed == 'undefined') ? 10 : speed;
        
        function f() {
            if(target == currentOpacity) {
                that.flag = true;
                return true;
            }

            if (target < currentOpacity) {
                currentOpacity = currentOpacity - speed < 0 ? 0 : currentOpacity - speed;
                ltk.dom.first('div', that.widget).setOpacity(currentOpacity);
                setTimeout(f, 1);
            } else if (target >= currentOpacity){
                currentOpacity = currentOpacity + speed > 100 ? 100 : currentOpacity + speed;
                ltk.dom.first('div', that.widget).setOpacity(currentOpacity);
                setTimeout(f, 1);
            }
          };
          f(speed);
          
          return true;
    }
    
    ltk.animation.prototype.moveHeight = function(speed)
    {
        var that = this;

        var currentBoxHeight = parseInt(this.widget.node.offsetHeight, 10);

        if (!('box' in this.params)) {
            //this.params.box = {'height' : parseInt(ltk.dom.first('div', this.widget).getStyle('height'), 10)};
            this.params.box = {'height' : parseInt(ltk.dom.first('div', this.widget).node.offsetHeight, 10)};
        }
        
        var target = currentBoxHeight != 0 ? 0 : this.params.box.height;
        var speed = (typeof speed === 'undefined') ? 10 : speed;
        speed = speed * 3;
        
        function m(){
            if (target == currentBoxHeight) {
                return;
            }
            
            if (currentBoxHeight >= target) {
                currentBoxHeight = (currentBoxHeight - speed) < 0 ? 0 : currentBoxHeight - speed;
                if (currentBoxHeight == 0) ltk.dom.first('div', that.widget).setStyle('display', 'none');
                ltk.dom.first('div', that.widget).setStyle('height', currentBoxHeight+'px');
                setTimeout(m, 1);
            } else if (currentBoxHeight <= target) {
                ltk.dom.first('div', that.widget).setStyle('display', 'block');
                currentBoxHeight = (currentBoxHeight + speed) > target ? target : currentBoxHeight + speed;
                ltk.dom.first('div', that.widget).setStyle('height', currentBoxHeight+'px');
                setTimeout(m, 1);
            }
        }
        m();
        
        return true;
    }


    ltk.animation.prototype.toggle = function(speed)
    {
        if (!this.widget.node) return;

        this.moveHeight(speed);
        this.fade(speed);
    }
    
    var box;
    var i = 0;
    var n;
    ltk.animation.prototype.clop = function(btnNode, toggle, speed){
        var content = this.widget.childNodes().nodes[0];
        var boxHeight = content.offsetHeight;
        var me;

        switch(toggle){
            case 'close':
                this.flag = false;
                this.moveVerticalSize(20);
                this.fade(1);
                            // var that = this;
                            // 
                            // var i = setInterval(function(){
                            //     if (that.flag == true) {
                            //         clearInterval(i);
                            //         that.widget.setStyle('display', 'none');
                            //     }
                            // }, 1);
                // return;
                // n = 1.0;
                // if(i <= 0){
                //     this.box = boxHeight;
                //     me = this;
                //     var btn = btnNode;
                //     i++;
                // }
                // 
                // if(boxHeight >= 0) {
                //     var drawBox = function(){
                //         boxHeight -= 20;
                //         n -= 0.025;
                //         boxHeight = (boxHeight < 0 ? 0 : boxHeight);
                //         /*@cc_on @*/
                //         /*@if (@_jscript_version <= 5.7)
                //             content.style.display = 'none';
                //         /*@end @*/
                //         content.style.height = boxHeight+"px";
                //         content.style.opacity = n;
                // 
                //         if(boxHeight > 0) {
                //             setTimeout(function(){
                //                 drawBox();
                //             }, 20);
                //         }
                //     }
                //     drawBox();
                    
                    // if(boxHeight == 0) {
                    //     btn.innerHTML = '&ouml;ffnen';
                    //     n = boxHeight;
                    //     boxHeight = null;
                    //     content.style.opacity = n;
                    // } else {
                    //     setTimeout(function(){
                    //         drawBox();
                    //     }, 20);
                    // };     
                // }
                
            break;

            case 'open':
                this.widget.setStyle('display', 'block');
                this.moveVerticalSize(20);
                this.fade(5);
                return;

                if (typeof this.box == 'undefined') {
                    this.box = content.offsetHeight;
                    boxHeight = 0;
                    content.style.height = boxHeight;
                    content.style.visibility = 'visible';
                    content.style.position = '';
                    
                    var me = this;
                    var n = 0;
                    var drawBox = function(){
                        if(boxHeight < me.box){
                            boxHeight += 20;
                            n += 0.050;
                            content.style.opacity = n;
                            content.style.height = boxHeight+'px';
                            setTimeout(function(){
                                drawBox();
                            }, 35);
                        }
                    }
                    drawBox();
                    
                } else {
                    if(boxHeight < this.box) {
                        var me = this;
                        var n = 0;
                        content.style.visibility = 'visible';
                        content.style.position = '';
                        console.log(speed);
                        var drawBox = function(){
                            boxHeight += 20;
                            n += 0.025;
                            content.style.height = boxHeight+'px';                            
                            content.style.opacity = n;
                            if (boxHeight < me.box) {
                                setTimeout(function(){
                                    drawBox();
                                }, 20);
                            }
                        }
                        drawBox();
                        // btn.innerHTML = 'schliessen';
                    } else {
                        n = 1.0;
                        content.style.opacity = n;
                        /*@cc_on @*/
                        /*@if (@_jscript_version <= 5.7)
                            content.style.display = 'block';
                        /*@end @*/
                    }
                }
            break;
        }
        //clop(btnNode, this.widget.childNodes().nodes[0], 'close');
    }
    
    ltk.animation.prototype.open = function(btnNode){
        clop(btnNode, this.widget.childNodes().nodes[0], 'open');
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    // var clop = function(btn, content, toggle){
    //     var boxHeight = content.offsetHeight;
    //     var box = boxHeight;
    //     var me = this;
    //     var n = 1.0;
    // 
    //     this.switcher = function(){
    //         if(toggle == 'close') {
    //             if(boxHeight >= 0) {
    //                 boxHeight -= 10;
    //                 n -= 0.025;
    //                 boxHeight = (boxHeight < 0 ? 0 : boxHeight);
    //                 /*@cc_on @*/
    //                 /*@if (@_jscript_version <= 5.7)
    //                     content.style.display = 'none';
    //                 /*@end @*/
    //                 content.style.height = boxHeight+"px";
    //                 content.style.opacity = n;
    //                 if(boxHeight == 0) {
    //                     btn.innerHTML = '&ouml;ffnen';
    //                     n = boxHeight;
    //                     boxHeight = null;
    //                     content.style.opacity = n;
    //                 } else {
    //                     setTimeout(function(){
    //                         me.switcher();
    //                     }, 20);
    //                 }
    //             }
    //     
    //         } else {
    //             if(boxHeight < box-20) {
    //                 boxHeight += 10;
    //                 n += 0.025;
    //                 content.style.height = boxHeight+"px";
    //                 content.style.opacity = n;
    //                 setTimeout(function(){
    //                     me.switcher();
    //                 }, 20);
    //                 btn.innerHTML = 'schliessen';
    //             } else {
    //                 n = 1.0;
    //                 content.style.opacity = n;
    //                 /*@cc_on @*/
    //                 /*@if (@_jscript_version <= 5.7)
    //                     content.style.display = 'block';
    //                 /*@end @*/
    //             }
    //         }
    //     }
    //     
    //     this.switcher();
    // }

    
})();

/****c* ltk/slide
 * NAME
 *      lima_ltk_slide
 * FUNCTION
 *      slider
 * COPYRIGHT
 *      copyright (c) 2010 by Clipdealer gmbH
 * AUTHOR
 *      Daniel Garding <d.garding@clipdealer.de>
 ****
 */

;(function(){
    if('slide' in ltk) return;
    
    /****m* slide/
     * SYNOPSIS
     */
    ltk.slide = function()
    /*
     * FUNCTION
     *      constructor
     ****
     */
    {
    }
    
    /****m* slide/attach
     * SYNOPSIS
     */
    ltk.slide.prototype.attach = function(parent, def)
    /*
     * FUNCTION
     *      attach button widget
     ****
     */
    {
        this.widget  = parent;
    }
    
    ltk.slide.prototype.move = function(node)
    {
        img = this.widget.childNodes().nodes[0].firstChild;
        
        setTimeout(function(){
            for (var i=0; i < 710; i = i + 0.1) {
                img.style.paddingTop = i+'px';
            }
        }, 500);
        
    }
})();


