build/zebkit.js

(function(){

'use strict';

(function() {

    /**
     * WEB environment implementation. Provides elementary API zebkit needs to perform an
     * environment specific operations.
     * @class environment
     * @access package
     */
    var zebkitEnvironment = function() {
        var pkg    = {},
            hostRe = /([a-zA-Z]+)\:\/\/([^/:]+)/,
            isFF   = typeof navigator !== 'undefined' &&
                     navigator.userAgent.toLowerCase().indexOf('firefox') >= 0;

        function $sleep() {
            var r = new XMLHttpRequest(),
                t = (new Date()).getTime().toString(),
                i = window.location.toString().lastIndexOf("?");
            r.open('GET', window.location + (i > 0 ? "&" : "?") + t, false);
            r.send(null);
        }

        function $Request() {
            this.responseText = this.statusText = "";
            this.onreadystatechange = this.responseXml = null;
            this.readyState = this.status = 0;
        }

        $Request.prototype.open = function(method, url, async, user, password) {
            var m = url.match(hostRe);
            if (window.location.scheme.toLowerCase() === "file:" ||
                  (m    !== null &&
                   m[2] !== undefined &&
                   m[2].toLowerCase() === window.location.host.toLowerCase()))
            {
                this._request = new XMLHttpRequest();
                this._xdomain = false;

                var $this = this;
                this._request.onreadystatechange = function() {
                    $this.readyState = $this._request.readyState;
                    if ($this._request.readyState === 4) {
                        $this.responseText = $this._request.responseText;
                        $this.responseXml  = $this._request.responseXml;
                        $this.status       = $this._request.status;
                        $this.statusText   = $this._request.statusText;
                    }

                    if ($this.onreadystatechange) {
                        $this.onreadystatechange();
                    }
                };

                return this._request.open(method, url, (async !== false), user, password);
            } else {
                this._xdomain = true;
                this._async = (async === true);
                this._request = new XDomainRequest();
                return this._request.open(method, url);
            }
        };

        $Request.prototype.send = function(data) {
            if (this._xdomain) {
                var originalReq = this._request,
                    $this       = this;

                //!!!! handler has to be defined after
                //!!!! open method has been called and all
                //!!!! four handlers have to be defined
                originalReq.ontimeout = originalReq.onprogress = function () {};

                originalReq.onerror = function() {
                    $this.readyState = 4;
                    $this.status = 404;
                    if ($this._async && $this.onreadystatechange) {
                        $this.onreadystatechange();
                    }
                };

                originalReq.onload  = function() {
                    $this.readyState = 4;
                    $this.status = 200;

                    if ($this._async && $this.onreadystatechange) {
                        $this.onreadystatechange(originalReq.responseText, originalReq);
                    }
                };

                //!!! set time out zero to prevent data lost
                originalReq.timeout = 0;

                if (this._async === false) {
                    originalReq.send(data);

                    while (this.status === 0) {
                        $sleep();
                    }

                    this.readyState = 4;
                    this.responseText = originalReq.responseText;

                } else {
                    //!!!  short timeout to make sure bloody IE is ready
                    setTimeout(function () {
                       originalReq.send(data);
                    }, 10);
                }
            } else  {
                return this._request.send(data);
            }
        };

        $Request.prototype.abort = function(data) {
            return this._request.abort();
        };

        $Request.prototype.setRequestHeader = function(name, value) {
            if (this._xdomain) {
                if (name === "Content-Type") {
                    //!!!
                    // IE8 and IE9 anyway don't take in account the assignment
                    // IE8 throws exception every time a value is assigned to
                    // the property
                    // !!!
                    //this._request.contentType = value;
                    return;
                } else {
                    throw new Error("Method 'setRequestHeader' is not supported for " + name);
                }
            } else {
                this._request.setRequestHeader(name, value);
            }
        };

        $Request.prototype.getResponseHeader = function(name) {
            if (this._xdomain) {
                throw new Error("Method is not supported");
            }
            return this._request.getResponseHeader(name);
        };

        $Request.prototype.getAllResponseHeaders = function() {
            if (this._xdomain) {
                throw new Error("Method is not supported");
            }
            return this._request.getAllResponseHeaders();
        };

        /**
         * Build HTTP request that provides number of standard methods, fields and listeners:
         *
         *    - "open(method, url [,async])" - opens the given URL
         *    - "send(data)"   - sends data
         *    - "status"       - HTTP status code
         *    - "statusText"   - HTTP status text
         *    - "responseText" - response text
         *    - "readyState"   - request ready state
         *    - "onreadystatechange()" - ready state listener
         *
         * @return {Object} an HTTP request object
         * @method getHttpRequest
         */
        pkg.getHttpRequest = function() {
            var r = new XMLHttpRequest();
            if (isFF) {
                r.__send = r.send;
                r.send = function(data) {
                    // !!! FF can throw NS_ERROR_FAILURE exception instead of
                    // !!! returning 404 File Not Found HTTP error code
                    // !!! No request status, statusText are defined in this case
                    try {
                        return this.__send(data);
                    } catch(e) {
                        if (!e.message || e.message.toUpperCase().indexOf("NS_ERROR_FAILURE") < 0) {
                            // exception has to be re-instantiate to be Error class instance
                            throw new Error(e.toString());
                        }
                    }
                };
            }
            return ("withCredentials" in r) ? r  // CORS is supported out of box
                                            : new $Request(); // IE
        };

        pkg.parseXML = function(s) {
            function rmws(node) {
                if (node.childNodes !== null) {
                    for (var i = node.childNodes.length; i-- > 0;) {
                        var child= node.childNodes[i];
                        if (child.nodeType === 3 && child.data.match(/^\s*$/) !== null) {
                            node.removeChild(child);
                        }

                        if (child.nodeType === 1) {
                            rmws(child);
                        }
                    }
                }
                return node;
            }

            if (typeof DOMParser !== "undefined") {
                return rmws((new DOMParser()).parseFromString(s, "text/xml"));
            } else {
                for (var n in { "Microsoft.XMLDOM":0, "MSXML2.DOMDocument":1, "MSXML.DOMDocument":2 }) {
                    var p = null;
                    try {
                        p = new ActiveXObject(n);
                        p.async = false;
                    } catch (e) {
                        continue;
                    }

                    if (p === null) {
                        throw new Error("XML parser is not available");
                    }
                    p.loadXML(s);
                    return p;
                }
            }
            throw new Error("No XML parser is available");
        };

        /**
         * Loads an image by the given URL.
         * @param  {String|HTMLImageElement} img an image URL or image object
         * @param  {Function} success a call back method to be notified when the image has
         * been successfully loaded. The method gets an image as its parameter.
         * @param {Function} [error] a call back method to be notified if the image loading
         * has failed. The method gets an image instance as its parameter and an exception
         * that describes an error has happened.
         *
         * @example
         *      // load image
         *      zebkit.environment.loadImage("test.png", function(image) {
         *           // handle loaded image
         *           ...
         *      }, function (img, exception) {
         *          // handle error
         *          ...
         *      });
         *
         * @return {HTMLImageElement}  an image
         * @method loadImage
         */
        pkg.loadImage = function(ph, success, error) {
            var img = null;
            if (ph instanceof Image) {
                img = ph;
            } else {
                img = new Image();
                img.crossOrigin = '';
                img.crossOrigin ='anonymous';
                img.src = ph;
            }

            if (img.complete === true && img.naturalWidth !== 0) {
                success.call(this, img);
            } else {
                var pErr  = img.onerror,
                    pLoad = img.onload,
                    $this = this;

                img.onerror = function(e) {
                    img.onerror = null;
                    try {
                        if (error !== undefined) {
                            error.call($this, img, new Error("Image '" + ph + "' cannot be loaded " + e));
                        }
                    } finally {
                        if (typeof pErr === 'function') {
                            img.onerror = pErr;
                            pErr.call(this, e);
                        }
                    }
                };

                img.onload  = function(e) {
                    img.onload = null;
                    try {
                        success.call($this, img);
                    } finally {
                        if (typeof pLoad === 'function') {
                            img.onload = pLoad;
                            pLoad.call(this, e);
                        }
                    }
                };
            }

            return img;
        };

        /**
         * Parse JSON string
         * @param {String} json a JSON string
         * @method parseJSON
         * @return {Object} parsed JSON as an JS Object
         */
        pkg.parseJSON = JSON.parse;

        /**
         * Convert the given JS object into an JSON string
         * @param {Object} jsonObj an JSON JS object to be converted into JSON string
         * @return {String} a JSON string
         * @method stringifyJSON
         *
         */
        pkg.stringifyJSON = JSON.stringify;

        /**
         * Call the given callback function repeatedly with the given calling interval.
         * @param {Function} cb a callback function to be called
         * @param {Integer}  time an interval in milliseconds the given callback
         * has to be called
         * @return {Integer} an run interval id
         * @method setInterval
         */
        pkg.setInterval = function (cb, time) {
            return window.setInterval(cb, time);
        };

        /**
         * Clear the earlier started interval calling
         * @param  {Integer} id an interval id
         * @method clearInterval
         */
        pkg.clearInterval = function (id) {
            return window.clearInterval(id);
        };

        if (typeof window !== 'undefined') {
            var $taskMethod = window.requestAnimationFrame       ||
                              window.webkitRequestAnimationFrame ||
                              window.mozRequestAnimationFrame    ||
                              function(callback) { return setTimeout(callback, 35); };


            pkg.decodeURIComponent = window.decodeURIComponent;
            pkg.encodeURIComponent = window.encodeURIComponent;

        } else {
            pkg.decodeURIComponent = function(s) { return s; } ;
            pkg.encodeURIComponent = function(s) { return s; } ;
        }

        /**
         * Request to run a method as an animation task.
         * @param  {Function} f the task body method
         * @method  animate
         */
        pkg.animate = function(f){
            return $taskMethod.call(window, f);
        };

        function buildFontHelpers() {
            //  font metrics API
            var e = document.getElementById("zebkit.fm");
            if (e === null) {
                e = document.createElement("div");
                e.setAttribute("id", "zebkit.fm");  // !!! position fixed below allows to avoid 1px size in HTML layout for "zebkit.fm" element
                e.setAttribute("style", "visibility:hidden;line-height:0;height:1px;vertical-align:baseline;position:fixed;");
                e.innerHTML = "<span id='zebkit.fm.text' style='display:inline;vertical-align:baseline;'>&nbsp;</span>" +
                              "<img id='zebkit.fm.image' style='width:1px;height:1px;display:inline;vertical-align:baseline;' width='1' height='1'/>";
                document.body.appendChild(e);
            }
            var $fmCanvas = document.createElement("canvas").getContext("2d"),
                $fmText   = document.getElementById("zebkit.fm.text"),
                $fmImage  = document.getElementById("zebkit.fm.image");

            $fmImage.onload = function() {
                // TODO: hope the base64 specified image load synchronously and
                // checking it with "join()"
            };

            // set 1x1 transparent picture
            $fmImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII%3D';

            pkg.fontMeasure = $fmCanvas;

            pkg.fontStringWidth = function(font, str) {
                if (str.length === 0) {
                    return 0;
                } else {
                    if ($fmCanvas.font !== font) {
                        $fmCanvas.font = font;
                    }
                    return Math.round($fmCanvas.measureText(str).width);
                }
            };

            pkg.fontMetrics = function(font) {
                if ($fmText.style.font !== font) {
                    $fmText.style.font = font;
                }

                var height = $fmText.offsetHeight;
                //!!!
                // Something weird is going sometimes in IE10 !
                // Sometimes the property offsetHeight is 0 but
                // second attempt to access to the property gives
                // proper result
                if (height === 0) {
                    height = $fmText.offsetHeight;
                }

                return {
                    height : height,
                    ascent : $fmImage.offsetTop - $fmText.offsetTop + 1
                };
            };
        }

        if (typeof document !== 'undefined') {
            document.addEventListener("DOMContentLoaded", buildFontHelpers);
        }

        return pkg;
    };

    if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
        module.exports.zebkitEnvironment = zebkitEnvironment;

        // TODO:
        // typeof the only way to make environment visible is makling it global
        // since module cannot be applied in the ase of browser context
        if (typeof global !== 'undefined') {
            global.zebkitEnvironment = zebkitEnvironment;
        }
    } else {
        window.zebkitEnvironment = zebkitEnvironment;
    }
})();
/**
 * Promise-like sequential tasks runner (D-then). Allows developers to execute
 * number of steps (async and sync) in the exact order they have been called with
 * the class instance. The idea of the runner implementation is making the
 * code more readable and plain nevertheless it includes asynchronous parts:
 * @example
 *
 *     var r = new zebkit.DoIt();
 *
 *     // step 1
 *     r.then(function() {
 *         // call three asynchronous HTTP GET requests to read three files
 *         // pass join to every async. method to be notified when the async.
 *         // part is completed
 *         asyncHttpCall("http://test.com/a.txt", this.join());
 *         asyncHttpCall("http://test.com/b.txt", this.join());
 *         asyncHttpCall("http://test.com/c.txt", this.join());
 *     })
 *     .  // step 2
 *     then(function(r1, r2, r3) {
 *         // handle completely read on previous step files
 *         r1.responseText  // "a.txt" file content
 *         r2.responseText  // "b.txt" file content
 *         r3.responseText  // "c.txt" file content
 *     })
 *     . // handle error
 *     catch(function(e) {
 *         // called when an exception has occurred
 *         ...
 *     });
 *
 *
 * @class zebkit.DoIt
 * @param {Boolean} [ignore] flag to rule error ignorance
 * @constructor
 */
function DoIt(body, ignore) {
    this.recover();

    if (arguments.length === 1) {
        if (body !== undefined && body !== null && (typeof body === "boolean" || body.constructor === Boolean)) {
            this.$ignoreError = body;
            body = null;
        } else {
            this.then(body);
        }
    } else if (arguments.length === 2) {
        this.$ignoreError = ignore;
        this.then(body);
    }
}

DoIt.prototype = {
    /**
     * Indicates if the error has to be ignored
     * @attribute $ignoreError
     * @private
     * @type {Boolean}
     */
    $ignoreError : false,

    // TODO: not stable API
    recover : function(body) {
        if (this.$error !== null) {
            var err = this.$error;
            this.$error = null;
            this.$tasks   = [];
            this.$results = [];
            this.$taskCounter = this.$level = this.$busy = 0;

            if (arguments.length === 1) {
                body.call(this, err);
            }
        }
        return this;
    },

    /**
     * Restart the do it object to clear error that has happened and
     * continue tasks that has not been run yet because of the error.
     * @method  restart
     * @chainable
     */
    restart : function() {
        if (this.$error !== null) {
            this.$error = null;
        }
        this.$schedule();
        return this;
    },

    /**
     * Run the given method as one of the sequential step of the doit execution.
     * @method  then
     * @param  {Function} body a method to be executed. The method can get results
     * of previous step execution as its arguments. The method is called in context
     * of a DoIt instance.
     * @chainable
     */
    then : function(body, completed) {
        var level = this.$level;  // store level then was executed for the given task
                                  // to be used to compute correct the level inside the
                                  // method below
        if (body instanceof DoIt) {
            if (body.$error !== null) {
                this.error(body.$error);
            } else {
                var $this = this;
                this.then(function() {
                    var jn = $this.join();
                    body.then(function() {
                        if (arguments.length > 0) {
                            // also pass result to body DoIt
                            this.join.apply(this, arguments);
                        }
                    }, function() {
                        if ($this.$error === null) {
                            jn.apply($this, arguments);
                        }
                    }).catch(function(e) {
                        $this.error(e);
                    });
                });
            }

            return this;
        } else {
            var task = function() {
                // clean results of execution of a previous task

                this.$busy = 0;
                var pc = this.$taskCounter, args = null, r;

                if (this.$error === null) {
                    if (this.$results[level] !== undefined) {
                        args = this.$results[level];
                    }

                    this.$taskCounter    = 0;  // we have to count the tasks on this level
                    this.$level          = level + 1;
                    this.$results[level] = [];

                    // it is supposed the call is embedded with other call, no need to
                    // catch it one more time
                    if (level > 0) {
                        r = body.apply(this, args);
                    } else {
                        try {
                            r = body.apply(this, args);
                        } catch(e) {
                            this.error(e);
                        }
                    }

                    // this.$busy === 0 means we have called synchronous task
                    // and make sure the task has returned a result
                    if (this.$busy === 0 && this.$error === null && r !== undefined) {
                        this.$results[level] = [ r ];
                    }
                }

                if (level === 0) {
                    // zero level is responsible for handling exception
                    try {
                        this.$schedule();
                    } catch(e) {
                        this.error(e);
                    }
                } else {
                    this.$schedule();
                }

                this.$level = level; // restore level
                this.$taskCounter = pc;  // restore counter

                // TODO: not a graceful solution. It has been done to let call "join" out
                // outside of body. Sometimes it is required to provide proper level of
                // execution since join calls schedule
                if (typeof completed === 'function') {
                    if (level === 0) {
                        try {
                            if (args === null) {
                                completed.call(this);
                            } else {
                                completed.apply(this, args);
                            }
                        } catch(e) {
                            this.error(e);
                        }
                    } else {
                        if (args === null) {
                            completed.call(this);
                        } else {
                            completed.apply(this, args);
                        }
                    }
                }
                if (args !== null) {
                    args.length = 0;
                }
            };

            if (this.$error === null) {
                if (level === 0 && this.$busy === 0) {
                    if (this.$results[level] !== null &&
                        this.$results[level] !== undefined &&
                        this.$results[level].length > 0)
                    {
                        task.apply(this, this.$results[level]);
                    } else {
                        task.call(this);
                    }
                } else {
                    // put task in list
                    if (this.$level > 0) {
                        this.$tasks.splice(this.$taskCounter++, 0, task);
                    } else {
                        this.$tasks.push(task);
                    }
                }
            }
        }

        if (this.$level === 0) {
            this.$schedule();
        }

        return this;
    },

    $ignored : function(e) {
        this.dumpError(e);
    },

    /**
     * Force to fire error.
     * @param  {Error} [e] an error to be fired
     * @method error
     * @chainable
     */
    error : function(e, pr) {
        if (arguments.length === 0) {
            if (this.$error !== null) {
                this.dumpError(e);
            }
        } else {
            if (this.$error === null) {
                if (this.$ignoreError) {
                    this.$ignored(e);
                } else {
                    this.$taskCounter = this.$level = this.$busy = 0;
                    this.$error   = e;
                    this.$results = [];
                }

                this.$schedule();
            } else if (arguments.length < 2 || pr === true) {
                this.dumpError(e);
            }
        }

        return this;
    },

    /**
     * Wait for the given doit redness.
     * @param  {zebkit.DoIt} r a runner
     * @example
     *
     *      var async = new DoIt().then(function() {
     *          // imagine we do asynchronous ajax call
     *          ajaxCall("http://test.com/data", this.join());
     *      });
     *
     *      var doit = new DoIt().till(async).then(function(res) {
     *          // handle result that has been fetched
     *          // by "async" do it
     *          ...
     *      });
     *
     * @chainable
     * @method till
     */
    till : function(r) {
        // wait till the given DoIt is executed
        this.then(function() {
            var $this = this,
                jn    = this.join(), // block execution of the runner
                res   = arguments.length > 0 ? Array.prototype.slice.call(arguments) : []; // save arguments to restore it later

            // call "doit" we are waiting for
            r.then(function() {
                if ($this.$error === null) {
                    // unblock the doit that waits for the runner we are in and
                    // restore its arguments
                    if (res.length > 0) {
                        jn.apply($this, res);
                    } else {
                        jn.call($this);
                    }

                    // preserve arguments for the next call
                    if (arguments.length > 0) {
                        this.join.apply(this, arguments);
                    }
                }
            }).catch(function(e) {
                // delegate error to a waiting runner
                $this.error(e);
            });
        });

        return this;
    },

    /**
     * Returns join callback for asynchronous parts of the doit. The callback
     * has to be requested and called by an asynchronous method to inform the
     * doit the given method is completed.
     * @example
     *
     *      var d = new DoIt().then(function() {
     *          // imagine we call ajax HTTP requests
     *          ajaxCall("http://test.com/data1", this.join());
     *          ajaxCall("http://test.com/data2", this.join());
     *      }).then(function(res1, res2) {
     *          // handle results of ajax requests from previous step
     *          ...
     *      });
     *
     * @return {Function} a method to notify doit the given asynchronous part
     * has been completed. The passed to the method arguments will be passed
     * to the next step of the runner.         *
     * @method join
     */
    join : function() {
        // if join is called outside runner than level is set to 0
        var level = this.$level === 0 ? 0 : this.$level - 1;

        if (arguments.length > 0) {
            this.$results[level] = [];
            for(var i = 0; i < arguments.length; i++) {
                this.$results[level][i] = arguments[i];
            }
        } else {
            // TODO: join uses busy flag to identify the result index the given join will supply
            // what triggers a potential result overwriting  problem (jn2 overwrite jn1  result):
            //    var jn1 = join(); jn1();
            //    var jn2 = join(); jn2();

            var $this = this,
                index = this.$busy++;

            return function() {
                if ($this.$results[level] === null || $this.$results[level] === undefined) {
                    $this.$results[level] = [];
                }

                // since error can occur and times variable
                // can be reset to 0 we have to check it
                if ($this.$busy > 0) {
                    var i = 0;

                    if (arguments.length > 0) {
                        $this.$results[level][index] = [];
                        for(i = 0; i < arguments.length; i++) {
                            $this.$results[level][index][i] = arguments[i];
                        }
                    }

                    if (--$this.$busy === 0) {
                        // collect result
                        if ($this.$results[level].length > 0) {
                            var args = $this.$results[level],
                                res  = [];

                            for(i = 0; i < args.length; i++) {
                                Array.prototype.push.apply(res, args[i]);
                            }
                            $this.$results[level] = res;
                        }

                        // TODO: this code can bring to unexpected scheduling for a situation when
                        // doit is still in then:
                        //    then(function () {
                        //        var jn1 = join();
                        //        ...
                        //        jn1()  // unexpected scheduling of the next then since busy is zero
                        //        ...
                        //        var jn2 = join(); // not actual
                        //    })

                        $this.$schedule();
                    }
                }
            };
        }
    },

    /**
     * Method to catch error that has occurred during the doit sequence execution.
     * @param  {Function} [body] a callback to handle the error. The method
     * gets an error that has happened as its argument. If there is no argument
     * the error will be printed in output. If passed argument is null then
     * no error output is expected.
     * @chainable
     * @method catch
     */
    catch : function(body) {
        var level = this.$level;  // store level then was executed for the given task
                                  // to be used to compute correct the level inside the
                                  // method below

        var task = function() {
            // clean results of execution of a previous task

            this.$busy = 0;
            var pc = this.$taskCounter;
            if (this.$error !== null) {
                this.$taskCounter = 0;  // we have to count the tasks on this level
                this.$level       = level + 1;

                try {
                    if (typeof body === 'function') {
                        body.call(this, this.$error);
                    } else if (body === null) {

                    } else {
                        this.dumpError(this.$error);
                    }
                } catch(e) {
                    this.$level       = level; // restore level
                    this.$taskCounter = pc;    // restore counter
                    throw e;
                }
            }

            if (level === 0) {
                try {
                    this.$schedule();
                } catch(e) {
                    this.error(e);
                }
            } else {
                this.$schedule();
            }

            this.$level       = level; // restore level
            this.$taskCounter = pc;    // restore counter
        };

        if (this.$level > 0) {
            this.$tasks.splice(this.$taskCounter++, 0, task);
        } else {
            this.$tasks.push(task);
        }

        if (this.$level === 0) {
            this.$schedule();
        }

        return this;
    },

    /**
     * Throw an exception if an error has happened before the method call,
     * otherwise do nothing.
     * @method  throw
     * @chainable
     */
    throw : function() {
        return this.catch(function(e) {
            throw e;
        });
    },

    $schedule : function() {
        if (this.$tasks.length > 0 && this.$busy === 0) {
            this.$tasks.shift().call(this);
        }
    },

    end : function() {
        this.recover();
    },

    dumpError: function(e) {
        if (typeof console !== "undefined" && console.log !== undefined) {
            if (e === null || e === undefined) {
                console.log("Unknown error");
            } else {
                console.log((e.stack ? e.stack : e));
            }
        }
    }
};

// Environment specific stuff
var $exports     = {},
    $zenv        = {},
    $global      = (typeof window !== "undefined" && window !== null) ? window
                                                                     : (typeof global !== 'undefined' ? global
                                                                                                      : this),
    $isInBrowser = typeof navigator !== "undefined",
    isIE         = $isInBrowser && (Object.hasOwnProperty.call(window, "ActiveXObject") ||
                                  !!window.ActiveXObject ||
                                  window.navigator.userAgent.indexOf("Edge") > -1),
    isFF         = $isInBrowser && window.mozInnerScreenX !== null,
    isMacOS      = $isInBrowser && navigator.platform.toUpperCase().indexOf('MAC') !== -1,
    $FN          = null;

/**
 * Reference to global space.
 * @attribute $global
 * @private
 * @readOnly
 * @type {Object}
 * @for zebkit
 */

if (parseInt.name !== "parseInt") {
    $FN = function(f) {  // IE stuff
        if (f.$methodName === undefined) { // test if name has been earlier detected
            var mt = f.toString().match(/^function\s+([^\s(]+)/);
                f.$methodName = (mt === null) ? ''
                                              : (mt[1] === undefined ? ''
                                                                     : mt[1]);
        }
        return f.$methodName;
    };
} else {
    $FN = function(f) {
        return f.name;
    };
}

function $export() {
    for (var i = 0; i < arguments.length; i++) {
        var arg = arguments[i];
        if (typeof arg === 'function') {
            $exports[$FN(arg)] = arg;
        } else {
            for (var k in arg) {
                if (arg.hasOwnProperty(k)) {
                    $exports[k] = arg[k];
                }
            }
        }
    }
}


if (typeof zebkitEnvironment === 'function') {
    $zenv = zebkitEnvironment();
} else if (typeof window !== 'undefined') {
    $zenv = window;
}

// Map class definition for old browsers
function $Map() {
    var Map = function() {
        this.keys   = [];
        this.values = [];
        this.size   = 0 ;
    };

    Map.prototype = {
        set : function(key, value) {
            var i = this.keys.indexOf(key);
            if (i < 0) {
                this.keys.push(key);
                this.values.push(value);
                this.size++;
            } else {
               this.values[i] = value;
            }
            return this;
         },

        delete: function(key) {
            var i = this.keys.indexOf(key);
            if (i < 0) {
               return false;
            }

            this.keys.splice(i, 1);
            this.values.splice(i, 1);
            this.size--;
            return true;
        },

        get : function(key) {
            var i = this.keys.indexOf(key);
            return i < 0 ? undefined : this.values[i];
        },

        clear : function() {
            this.keys = [];
            this.keys.length = 0;
            this.values = [];
            this.values.length = 0;
            this.size = 0;
        },

        has : function(key) {
            return this.keys.indexOf(key) >= 0;
        },

        forEach: function(callback, context) {
            var $this = arguments.length < 2 ? this : context;
            for(var i = 0 ; i < this.size; i++) {
                callback.call($this, this.values[i], this.keys[i], this);
            }
        }
    };
    return Map;
}

// ES6 Map is class
if (typeof Map === 'undefined' && (typeof $global !== 'undefined' || typeof $global.Map === "undefined")) {
    $global.Map = $Map();
}

function GET(url) {
    var req = $zenv.getHttpRequest();
    req.open("GET", url, true);

    return new DoIt(function() {
        var jn    = this.join(),
            $this = this;

        req.onreadystatechange = function() {
            if (req.readyState === 4) {
                // evaluate HTTP response
                if (req.status >= 400 || req.status < 100) {
                    var e = new Error("HTTP error '" + req.statusText + "', code = " + req.status + " '" + url + "'");
                    e.status     = req.status;
                    e.statusText = req.statusText;
                    e.readyState = req.readyState;
                    $this.error(e);
                } else {
                    jn(req);
                }
            }
        };

        try {
            req.send(null);
        } catch(e) {
            this.error(e);
        }
    });
}

// Micro file system
var ZFS = {
    catalogs : {},

    load: function(pkg, files) {
        var catalog = this.catalogs[pkg];
        if (catalog === undefined) {
            catalog = {};
            this.catalogs[pkg] = catalog;
        }

        for(var file in files) {
            catalog[file] = files[file];
        }
    },

    read : function(uri) {
        var p = null;
        for(var catalog in this.catalogs) {
            var pkg   = zebkit.byName(catalog),
                files = this.catalogs[catalog];

            if (pkg === null) {
                throw new ReferenceError("'" + catalog + "'");
            }

            p = new URI(uri).relative(pkg.$url);
            if (p !== null && files[p] !== undefined && files[p] !== null) {
                return files[p];
            }
        }
        return null;
    },

    GET: function(uri) {
        var f = ZFS.read(uri);
        if (f !== null) {
            return new DoIt(function() {
                return {
                    status      : 200,
                    statusText  : "",
                    extension   : f.ext,
                    responseText: f.data
                };
            });
        } else {
            return GET(uri);
        }
    }
};

/**
 * Dump the given error to output.
 * @param  {Exception | Object} e an error.
 * @method dumpError
 * @for  zebkit
 */
function dumpError(e) {
    if (typeof console !== "undefined" && typeof console.log !== "undefined") {
        var msg = "zebkit.err [";
        if (typeof Date !== 'undefined') {
            var date = new Date();
            msg = msg + date.getDate()   + "/" +
                  (date.getMonth() + 1) + "/" +
                  date.getFullYear() + " " +
                  date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds();
        }
        if (e === null || e === undefined) {
            console.log("Unknown error");
        } else {
            console.log(msg + " : " + e);
            console.log((e.stack ? e.stack : e));
        }
    }
}


/**
 * Load image or complete the given image loading.
 * @param  {String|Image} ph path or image to complete loading.
 * @param  {Boolean} [fireErr] flag to force or preserve error firing.
 * @return {zebkit.DoIt}
 * @method image
 * @for  zebkit
 */
function image(ph, fireErr) {
    if (arguments.length < 2) {
        fireErr = false;
    }
    var doit   = new DoIt(),
        jn     = doit.join(),
        marker = "data:image";

    if (isString(ph) && ph.length > marker.length) {
        // use "for" instead of "indexOf === 0"
        var i = 0;
        for(; i < marker.length && marker[i] === ph[i]; i++) {}

        if (i < marker.length) {
            var file = ZFS.read(ph);
            if (file !== null) {
                ph = "data:image/" + file.ext +  ";base64," + file.data;
            }
        }
    }

    $zenv.loadImage(ph,
        function(img) {
            jn(img);
        },
        function(img, e) {
            if (fireErr === true) {
                doit.error(e);
            } else {
                jn(img);
            }
        }
    );
    return doit;
}

//  Faster match operation analogues:
//  Math.floor(f)  =>  ~~(a)
//  Math.round(f)  =>  (f + 0.5) | 0

/**
 * Check if the given value is string
 * @param {Object} v a value.
 * @return {Boolean} true if the given value is string
 * @method isString
 * @for zebkit
 */
function isString(o)  {
    return o !== undefined && o !== null &&
          (typeof o === "string" || o.constructor === String);
}

/**
 * Check if the given value is number
 * @param {Object} v a value.
 * @return {Boolean} true if the given value is number
 * @method isNumber
 * @for zebkit
 */
function isNumber(o)  {
    return o !== undefined && o !== null &&
          (typeof o === "number" || o.constructor === Number);
}

/**
 * Check if the given value is boolean
 * @param {Object} v a value.
 * @return {Boolean} true if the given value is boolean
 * @method isBoolean
 * @for zebkit
 */
function isBoolean(o) {
    return o !== undefined && o !== null &&
          (typeof o === "boolean" || o.constructor === Boolean);
}

/**
 * Test if the given value has atomic type (String, Number or Boolean).
 * @param  {Object}  v a value
 * @return {Boolean} true if the value has atomic type
 * @method  isAtomic
 * @for zebkit
 */
function isAtomic(v) {
    return v === null || v === undefined ||
           (typeof v === "string"  || v.constructor === String)  ||
           (typeof v === "number"  || v.constructor === Number)  ||
           (typeof v === "boolean" || v.constructor === Boolean)  ;
}


Number.isInteger = Number.isInteger || function(value) {
    return typeof value === "number" &&
           isFinite(value) &&
           Math.floor(value) === value;
};


/**
 * Get property value for the given object and the specified property path
 * @param  {Object} obj  a target object.
 * as the target object
 * @param  {String} path property path.
 * @param  {Boolean} [useGetter] says too try getter method when it exists.
 * By default the parameter is false
 * @return {Object} a property value, return undefined if property cannot
 * be found
 * @method  getPropertyValue
 * @for  zebkit
 */
function getPropertyValue(obj, path, useGetter) {
    // if (arguments.length < 3) {
    //     useGetter = false;
    // }

    path = path.trim();
    if (path === undefined || path.length === 0) {
        throw new Error("Invalid field path: '" + path + "'");
    }

    // if (obj === undefined || obj === null) {
    //     throw new Error("Undefined target object");
    // }

    var paths = null,
        m     = null,
        p     = null;

    if (path.indexOf('.') > 0) {
        paths = path.split('.');

        for(var i = 0; i < paths.length; i++) {
            p = paths[i];

            if (obj !== undefined && obj !== null &&
                ((useGetter === true && (m = getPropertyGetter(obj, p))) || obj.hasOwnProperty(p)))
            {
                if (useGetter === true && m !== null) {
                    obj = m.call(obj);
                } else {
                    obj = obj[p];
                }
            } else {
                return undefined;
            }
        }
    } else {
        if (useGetter === true) {
            m = getPropertyGetter(obj, path);
            if (m !== null) {
                return m.call(obj);
            }
        }

        if (obj.hasOwnProperty(path) === true) {
            obj = obj[path];
        } else {
            return undefined;
        }
    }

    // detect object value factory
    if (obj !== null && obj !== undefined && obj.$new !== undefined) {
        return obj.$new();
    } else {
        return obj;
    }
}

/**
 * Get a property setter method if it is declared with the class of the specified object for the
 * given property. Setter is a method whose name matches the following pattern: "set<PropertyName>"
 * where the first letter of the property name is in upper case. For instance setter method for
 * property "color" has to have name "setColor".
 * @param  {Object} obj an object instance
 * @param  {String} name a property name
 * @return {Function}  a method that can be used as a setter for the given property
 * @method  getPropertySetter
 * @for zebkit
 */
function getPropertySetter(obj, name) {
    var pi = obj.constructor.$propertySetterInfo,
        m  = null;

    if (pi !== undefined) {
        if (pi[name] === undefined) {
            m = obj[ "set" + name[0].toUpperCase() + name.substring(1) ];
            pi[name] = (typeof m  === "function") ? m : null;
        }
        return pi[name];
    } else {
        // if this is not a zebkit class
        m = obj[ "set" + name[0].toUpperCase() + name.substring(1) ];
        return (typeof m  === "function") ? m : null;
    }
}

/**
 * Get a property getter method if it is declared with the class of the specified object for the
 * given property. Getter is a method whose name matches the following patterns: "get<PropertyName>"
 * or "is<PropertyName>" where the first letter of the property name is in upper case. For instance
 * getter method for property "color" has to have name "getColor".
 * @param  {Object} obj an object instance
 * @param  {String} name a property name
 * @return {Function}  a method that can be used as a getter for the given property
 * @method  getPropertyGetter
 * @for zebkit
 */
function getPropertyGetter(obj, name) {
    var pi = obj.constructor.$propertyGetterInfo,
        m  = null,
        suffix = null;

    if (pi !== undefined) {
        if (pi[name] === undefined) {
            suffix = name[0].toUpperCase() + name.substring(1);
            m  = obj[ "get" + suffix];
            if (typeof m !== 'function') {
                m = obj[ "is" + suffix];
            }
            pi[name] = (typeof m  === "function") ? m : null;
        }
        return pi[name];
    } else {
        suffix = name[0].toUpperCase() + name.substring(1);
        m      = obj[ "get" + suffix];
        if (typeof m !== 'function') {
            m = obj[ "is" + suffix];
        }
        return (typeof m === 'function') ? m : null;
    }
}

/**
 * Populate the given target object with the properties set. The properties set
 * is a dictionary that keeps properties names and its corresponding values.
 * Applying of the properties to an object does the following:
 *
 *
 *   - Detects if a property setter method exits and call it to apply
 *     the property value. Otherwise property is initialized as a field.
 *     Setter method is a method that matches "set<PropertyName>" pattern.
 *
 *   - Ignores properties whose names start from "$" character, equals "clazz"
 *     and properties whose values are function.
 *
 *   - Remove properties from the target object for properties that start from "-"
 *     character.
 *
 *   - Uses factory "$new" method to create a property value if the method can be
 *     detected in the property value.
 *
 *   - Apply properties recursively for properties whose names end with '/'
 *     character.
 *
 *
 * @param  {Object} target a target object
 * @param  {Object} props  a properties set
 * @return {Object} an object with the populated properties set.
 * @method  properties
 * @for  zebkit
 */
function properties(target, props) {
    for(var k in props) {
        // skip private properties( properties that start from "$")
        if (k !== "clazz" && k[0] !== '$' && props.hasOwnProperty(k) && props[k] !== undefined && typeof props[k] !== 'function') {
            if (k[0] === '-') {
                delete target[k.substring(1)];
            } else {
                var pv        = props[k],
                    recursive = k[k.length - 1] === '/',
                    tv        = null;

                // value factory detected
                if (pv !== null && pv.$new !== undefined) {
                    pv = pv.$new();
                }

                if (recursive === true) {
                    k = k.substring(0, k.length - 1);
                    tv = target[k];

                    // it is expected target value can be traversed recursively
                    if (pv !== null && (tv === null || tv === undefined || !(tv instanceof Object))) {
                        throw new Error("Target value is null, undefined or not an object. '" +
                                         k + "' property cannot be applied as recursive");
                    }
                } else {
                    tv = target[k];
                }

                if (recursive === true) {
                    if (pv === null) { // null value can be used to flush target value
                        target[k] = pv;
                    } else if (tv.properties !== undefined) {
                        tv.properties(pv); // target value itself has properties method
                    } else {
                        properties(tv, pv);
                    }
                } else {
                    var m = getPropertySetter(target, k);
                    if (m === null) {
                        target[k] = pv;  // setter doesn't exist, setup it as a field
                    } else {
                        // property setter is detected, call setter to
                        // set the property value
                        if (Array.isArray(pv)) {
                            m.apply(target, pv);
                        } else {
                            m.call(target, pv);
                        }
                    }
                }
            }
        }
    }
    return target;
}

// ( (http) :// (host)? (:port)? (/)? )? (path)? (?query_string)?
//
//  [1] scheme://host/
//  [2] scheme
//  [3] host
//  [4]  port
//  [5] /
//  [6] path
//  [7] ?query_string
//
var $uriRE = /^(([a-zA-Z]+)\:\/\/([^\/:]+)?(\:[0-9]+)?(\/)?)?([^?]+)?(\?.+)?/;

/**
 * URI class. Pass either a full uri (as a string or zebkit.URI) or number of an URI parts
 * (scheme, host, etc) to construct it.
 * @param {String} [uri] an URI.
 * @param {String} [scheme] a scheme.
 * @param {String} [host] a host.
 * @param {String|Integer} [port] a port.
 * @param {String} [path] a path.
 * @param {String} [qs] a query string.
 * @constructor
 * @class zebkit.URI
 */
function URI(uri) {
    if (arguments.length > 1) {
        if (arguments[0] !== null) {
            this.scheme = arguments[0].toLowerCase();
        }

        if (arguments[1] !== null) {
            this.host = arguments[1];
        }

        var ps = false;
        if (arguments.length > 2) {
            if (isNumber(arguments[2])) {
                this.port = arguments[2];
            } else if (arguments[2] !== null) {
                this.path = arguments[2];
                ps = true;
            }
        }

        if (arguments.length > 3) {
            if (ps === true) {
                this.qs = arguments[3];
            } else {
                this.path = arguments[3];
            }
        }

        if (arguments.length > 4) {
            this.qs = arguments[4];
        }
    } else if (uri instanceof URI) {
        this.host   = uri.host;
        this.path   = uri.path;
        this.qs     = uri.qs;
        this.port   = uri.port;
        this.scheme = uri.scheme;
    } else {
        if (uri === null || uri.trim().length === 0) {
            throw new Error("Invalid empty URI");
        }

        var m = uri.match($uriRE);
        if (m === null) {
            throw new Error("Invalid URI '" + uri + "'");
        }

        // fetch scheme
        if (m[1] !== undefined) {
            this.scheme = m[2].toLowerCase();

            if (m[3] === undefined) {
                if (this.scheme !== "file") {
                    throw new Error("Invalid host name : '" + uri + "'");
                }
            } else {
                this.host = m[3];
            }

            if (m[4] !== undefined) {
                this.port = parseInt(m[4].substring(1), 10);
            }
        }

        // fetch path
        if (m[6] !== undefined) {
            this.path = m[6];
        } else if (m[1] !== undefined) {
            this.path = "/";
        }

        if (m[7] !== undefined && m[7].length > 1) {
            this.qs = m[7].substring(1).trim();
        }
    }

    if (this.path !== null) {
        this.path = URI.normalizePath(this.path);

        if ((this.host !== null || this.scheme !== null) && this.path[0] !== '/') {
            this.path = "/" + this.path;
        }
    }

    if (this.scheme !== null) {
        this.scheme = this.scheme.toLowerCase();
    }

    if (this.host !== null) {
        this.host = this.host.toLowerCase();
    }

    /**
     * URI path.
     * @attribute path
     * @type {String}
     * @readOnly
     */

    /**
     * URI host.
     * @attribute host
     * @type {String}
     * @readOnly
     */

    /**
     * URI port number.
     * @attribute port
     * @type {Integer}
     * @readOnly
     */

    /**
     * URI query string.
     * @attribute qs
     * @type {String}
     * @readOnly
     */

     /**
      * URI scheme (e.g. 'http', 'ftp', etc).
      * @attribute scheme
      * @type {String}
      * @readOnly
      */
}

URI.prototype = {
    scheme   : null,
    host     : null,
    port     : -1,
    path     : null,
    qs       : null,

    /**
     * Serialize URI to its string representation.
     * @method  toString
     * @return {String} an URI as a string.
     */
    toString : function() {
        return (this.scheme !== null ? this.scheme + "://" : '') +
               (this.host !== null ? this.host : '' ) +
               (this.port !== -1   ? ":" + this.port : '' ) +
               (this.path !== null ? this.path : '' ) +
               (this.qs   !== null ? "?" + this.qs : '' );
    },

    /**
     * Get a parent URI.
     * @method getParent
     * @return {zebkit.URI} a parent URI.
     */
    getParent : function() {
        if (this.path === null) {
            return null;
        } else {
            var i = this.path.lastIndexOf('/');
            return (i < 0 || this.path === '/') ? null
                                                : new URI(this.scheme,
                                                          this.host,
                                                          this.port,
                                                          this.path.substring(0, i),
                                                          this.qs);
        }
    },

    /**
     * Append the given parameters to a query string of the URI.
     * @param  {Object} obj a dictionary of parameters to be appended to
     * the URL query string
     * @method appendQS
     */
    appendQS : function(obj) {
        if (obj !== null) {
            if (this.qs === null) {
                this.qs = '';
            }

            if (this.qs.length > 0) {
                this.qs = this.qs + "&" + URI.toQS(obj);
            } else {
                this.qs = URI.toQS(obj);
            }
        }
    },

    /**
     * Test if the URI is absolute.
     * @return {Boolean} true if the URI is absolute.
     * @method isAbsolute
     */
    isAbsolute : function() {
        return URI.isAbsolute(this.toString());
    },

    /**
     * Join URI with the specified path
     * @param  {String} p* relative paths
     * @return {String} an absolute URI
     * @method join
     */
    join : function() {
        var args = Array.prototype.slice.call(arguments);
        args.splice(0, 0, this.toString());
        return URI.join.apply(URI, args);
    },

    /**
     * Test if the given URL is file path.
     * @return {Boolean} true if the URL is file path
     * @method isFilePath
     */
    isFilePath : function() {
        return this.scheme === null || this.scheme === 'file';
    },

    /**
     * Get an URI relative to the given URI.
     * @param  {String|zebkit.URI} to an URI to that the relative URI has to be detected.
     * @return {String} a relative URI
     * @method relative
     */
    relative : function(to) {
        if ((to instanceof URI) === false) {
            to = new URI(to);
        }

        if (this.isAbsolute()                                                      &&
            to.isAbsolute()                                                        &&
            this.host === to.host                                                  &&
            this.port === to.port                                                  &&
            (this.scheme === to.scheme || (this.isFilePath() && to.isFilePath()) ) &&
            (this.path.indexOf(to.path) === 0 && (to.path.length === this.path.length ||
                                                  (to.path.length === 1 && to.path[0] === '/') ||
                                                  this.path[to.path.length] ===  '/'     )))
        {
            return (to.path.length === 1 && to.path[0] === '/') ? this.path.substring(to.path.length)
                                                                : this.path.substring(to.path.length + 1);
        } else {
            return null;
        }
    }
};

/**
 * Test if the given string is absolute path or URI.
 * @param  {String|zebkit.URI}  u an URI
 * @return {Boolean} true if the string is absolute path or URI.
 * @method isAbsolute
 * @static
 */
URI.isAbsolute = function(u) {
    return u[0] === '/' || /^[a-zA-Z]+\:\/\//i.test(u);
};

/**
 * Test if the given string is URL.
 * @param  {String|zebkit.URI}  u a string to be checked.
 * @return {Boolean} true if the string is URL
 * @method isURL
 * @static
 */
URI.isURL = function(u) {
    return /^[a-zA-Z]+\:\/\//i.test(u);
};

/**
 * Get a relative path.
 * @param  {String|zebkit.URI} base a base path
 * @param  {String|zebkit.URI} path a path
 * @return {String} a relative path
 * @method relative
 * @static
 */
URI.relative = function(base, path) {
    if ((path instanceof URI) === false) {
        path = new URI(path);
    }
    return path.relative(base);
};

/**
 * Parse the specified query string of the given URI.
 * @param  {String|zebkit.URI} url an URI
 * @param  {Boolean} [decode] pass true if query string has to be decoded.
 * @return {Object} a parsed query string as a dictionary of parameters
 * @method parseQS
 * @static
 */
URI.parseQS = function(qs, decode) {
    if (qs instanceof URI) {
        qs = qs.qs;
        if (qs === null) {
            return null;
        }
    } else if (qs[0] === '?') {
        qs = qs.substring(1);
    }

    var mqs      = qs.match(/[a-zA-Z0-9_.]+=[^?&=]+/g),
        parsedQS = {};

    if (mqs !== null) {
        for(var i = 0; i < mqs.length; i++) {
            var q = mqs[i].split('='),
                k = q[0].trim(),
                v = decode === true ? $zenv.decodeURIComponent(q[1])
                                    : q[1];

            if (parsedQS.hasOwnProperty(k)) {
                var p = parsedQS[k];
                if (Array.isArray(p) === false) {
                    parsedQS[k] = [ p ];
                }
                parsedQS[k].push(v);
            } else {
                parsedQS[k] = v;
            }
        }
    }
    return parsedQS;
};


URI.decodeQSValue = function(value) {
    if (Array.isArray(value)) {
        var r = [];
        for(var i = 0; i < value.length; i++) {
            r[i] = URI.decodeQSValue(value[i]);
        }
        return r;
    } else {
        value = value.trim();
        if (value[0] === "'") {
            value = value.substring(1, value.length - 1);
        } else if (value === "true" || value === "false") {
            value = (value === "true");
        } else if (value === "null") {
            value = null;
        } else if (value === "undefined") {
            value = undefined;
        } else {
            var num = (value.indexOf('.') >= 0) ? parseFloat(value)
                                                : parseInt(value, 10);
            if (isNaN(num) === false) {
                value = num;
            }
        }
        return value;
    }
};

URI.normalizePath = function(p) {
    if (p !== null && p.length > 0) {
        p = p.trim().replace(/[\\]+/g, '/');
        for (; ; ) {
            var len = p.length;
            p = p.replace(/[^./]+[/]+\.\.[/]+/g, '');
            p = p.replace(/[\/]+/g, '/');
            if (p.length == len) {
                break;
            }
        }

        var l = p.length;
        if (l > 1 && p[l - 1] === '/') {
            p = p.substring(0, l - 1);
        }
    }

    return p;
};

/**
 * Convert the given dictionary of parameters to a query string.
 * @param  {Object} obj a dictionary of parameters
 * @param  {Boolean} [encode] pass true if the parameters values have to be
 * encoded
 * @return {String} a query string built from parameters list
 * @static
 * @method toQS
 */
URI.toQS = function(obj, encode) {
    if (isString(obj) || isBoolean(obj) || isNumber(obj)) {
        return "" + obj;
    }

    var p = [];
    for(var k in obj) {
        if (obj.hasOwnProperty(k)) {
            p.push(k + '=' + (encode === true ? $zenv.encodeURIComponent(obj[k].toString())
                                              : obj[k].toString()));
        }
    }
    return p.join("&");
};

/**
 * Join the given  paths
 * @param  {String|zebkit.URI} p* relative paths
 * @return {String} a joined path as string
 * @method join
 * @static
 */
URI.join = function() {
    if (arguments.length === 0) {
        throw new Error("No paths to join");
    }

    var uri = new URI(arguments[0]);
    for(var i = 1; i < arguments.length; i++) {
        var p = arguments[i];

        if (p === null || p.length === 0) {
            throw new Error("Empty sub-path is not allowed");
        }

        if (URI.isAbsolute(p)) {
            throw new Error("Absolute path '" + p + "' cannot be joined");
        }

        if (p instanceof URI) {
            p = arguments[i].path;
        } else {
            p = new URI(p).path;
        }

        if (p.length === 0) {
            throw new Error("Empty path cannot be joined");
        }

        uri.path = uri.path + (uri.path === '/' ? '' : "/") + p;
    }

    uri.path = URI.normalizePath(uri.path);
    return uri.toString();
};

$export(
    URI,        isNumber, isString, $Map, isAtomic,
    dumpError,  image,    getPropertySetter,
    getPropertyValue,     getPropertyGetter,
    properties, GET,      isBoolean, DoIt,
    { "$global"    : $global,
      "$FN"        : $FN,
      "ZFS"        : ZFS,
      "environment": $zenv,
      "isIE"       : isIE,
      "isFF"       : isFF,
      "isMacOS"    : isMacOS }
);



var $$$        = 11,   // hash code counter
    $caller    = null, // currently called method reference
    $cachedO   = {},   // class cache
    $cachedE   = [],
    $cacheSize = 7777,
    CNAME      = '$',
    CDNAME     = '';

function $toString() {
    return this.$hash$;
}

function $ProxyMethod(name, f, clazz) {
    if (f.methodBody !== undefined) {
        throw new Error("Proxy method '" + name + "' cannot be wrapped");
    }

    var a = function() {
        var cm = $caller;
        $caller = a;
        // don't use finally section it is slower than try-catch
        try {
            var r = f.apply(this, arguments);
            $caller = cm;
            return r;
        } catch(e) {
            $caller = cm;
            console.log(name + "(" + arguments.length + ") " + (e.stack ? e.stack : e));
            throw e;
        }
    };

    a.methodBody = f;
    a.methodName = name;
    a.boundTo    = clazz;
    return a;
}

/**
 * Get an object by the given key from cache (and cached it if necessary)
 * @param  {String} key a key to an object. The key is hierarchical reference starting with the global
 * name space as root. For instance "test.a" key will fetch $global.test.a object.
 * @return {Object}  an object
 * @for  zebkit
 * @private
 * @method  $cache
 */
function $cache(key) {
    if ($cachedO.hasOwnProperty(key) === true) {
        // read cached entry
        var e = $cachedO[key];
        if (e.i < ($cachedE.length-1)) { // cached entry is not last one

            // move accessed entry to the list tail to increase its access weight
            var pn = $cachedE[e.i + 1];
            $cachedE[e.i]   = pn;
            $cachedE[++e.i] = key;
            $cachedO[pn].i--;
        }
        return e.o;
    }

    // don't cache global objects
    if ($global.hasOwnProperty(key)) {
        return $global[key];
    }

    var ctx = $global, i = 0, j = 0;
    for( ;ctx !== null && ctx !== undefined; ) {
        i = key.indexOf('.', j);

        if (i < 0) {
            ctx = ctx[key.substring(j, key.length)];
            break;
        }

        ctx = ctx[key.substring(j, i)];
        j = i + 1;
    }

    if (ctx !== null && ctx !== undefined) {
        if ($cachedE.length >= $cacheSize) {
            // cache is full, replace first element with the new one
            var n = $cachedE[0];
            $cachedE[0]   = key;
            $cachedO[key] = { o: ctx, i: 0 };
            delete $cachedO[n];
        } else {
            $cachedO[key] = { o: ctx, i: $cachedE.length };
            $cachedE[$cachedE.length] = key;
        }
        return ctx;
    }

    throw new Error("Reference '" + key + "' not found");
}

// copy methods from source to destination
function $cpMethods(src, dest, clazz) {
    var overriddenAbstractMethods = 0;
    for(var name in src) {
        if (name   !==  CNAME        &&
            name   !== "clazz"       &&
            src.hasOwnProperty(name)   )
        {
            var method = src[name];
            if (typeof method === "function" && method !== $toString) {
                if (name === "$prototype") {
                    method.call(dest, clazz);
                } else {
                    // TODO analyze if we overwrite existent field
                    if (dest[name] !== undefined) {
                        // abstract method is overridden, let's skip abstract method
                        // stub implementation
                        if (method.$isAbstract === true) {
                            overriddenAbstractMethods++;
                            continue;
                        }

                        if (dest[name].boundTo === clazz) {
                            throw new Error("Method '" + name + "(...)'' bound to this class already exists");
                        }
                    }

                    if (method.methodBody !== undefined) {
                        dest[name] = $ProxyMethod(name, method.methodBody, clazz);
                    } else {
                        dest[name] = $ProxyMethod(name, method, clazz);
                    }

                    // save information about abstract method
                    if (method.$isAbstract === true) {
                        dest[name].$isAbstract = true;
                    }
                }
            }
        }
    }
    return overriddenAbstractMethods;
}

// return function that is meta class
//  instanceOf      - parent template function (can be null)
//  templateConstructor - template function,
//  inheritanceList     - parent class and interfaces
function $make_template(instanceOf, templateConstructor, inheritanceList) {
    // supply template with unique identifier that is returned with toString() method
    templateConstructor.$hash$   = "$zEk$" + ($$$++);
    templateConstructor.toString = $toString;
    templateConstructor.prototype.clazz = templateConstructor; // instances of the template has to point to the template as a class

    templateConstructor.clazz = templateConstructor.constructor = instanceOf;

    /**
     *  Unique string hash code. The property is not defined if the class was not
     *  maid hashable by calling "hashable()" method.
     *  @attribute $hash$
     *  @private
     *  @type {String}
     *  @for  zebkit.Class
     *  @readOnly
     */

    /**
     * Dictionary of all inherited interfaces where key is unique interface hash code and the value
     * is interface itself.
     * @private
     * @readOnly
     * @for zebkit.Class
     * @type {Object}
     * @attribute $parents
     * @type {Object}
     */
    templateConstructor.$parents = {};

    // instances of the constructor also has to be unique
    // so force toString method population
    templateConstructor.prototype.constructor = templateConstructor; // set constructor of instances to the template

    // setup parent entities
    if (arguments.length > 2 && inheritanceList.length > 0) {
        for(var i = 0; i < inheritanceList.length; i++) {
            var toInherit = inheritanceList[i];
            if (toInherit === undefined         ||
                toInherit === null              ||
                typeof toInherit !== "function" ||
                toInherit.$hash$ === undefined    )
            {
                throw new ReferenceError("Invalid parent class or interface:" + toInherit);
            }

            if (templateConstructor.$parents[toInherit.$hash$] !== undefined) {
                var inh = '<unknown>';

                // try to detect class or interface name
                if (toInherit !== null && toInherit !== undefined) {
                    if (toInherit.$name !== null && toInherit.$name !== undefined) {
                        inh = toInherit.$name;
                    } else {
                        inh = toInherit;
                    }
                }
                throw Error("Duplicated inheritance: " + toInherit );
            }

            templateConstructor.$parents[toInherit.$hash$] = toInherit;

            // if parent has own parents copy the parents references
            for(var k in toInherit.$parents) {
                if (templateConstructor.$parents[k] !== undefined) {
                    throw Error("Duplicate inherited class or interface: " + k);
                }

                templateConstructor.$parents[k] = toInherit.$parents[k];
            }
        }
    }
    return templateConstructor;
}

/**
 * Clone the given object. The method tries to perform deep cloning by
 * traversing the given object structure recursively. Any part of an
 * object can be marked as not cloneable by adding  "$notCloneable"
 * field that equals to true. Also at any level of object structure
 * the cloning can be customized with adding "$clone" method. In this
 * case the method will be used to clone the part of object.
 * clonable
 * @param  {Object} obj an object to be cloned
 * @return {Object} a cloned object
 * @method  clone
 * @for  zebkit
 */
function clone(obj, map) {
    // clone atomic type
    // TODO: to speedup cloning we don't use isString, isNumber, isBoolean
    if (obj === null || obj === undefined || obj.$notCloneable === true ||
                                            (typeof obj === "string"  || obj.constructor === String  ) ||
                                            (typeof obj === "boolean" || obj.constructor === Boolean ) ||
                                            (typeof obj === "number"  || obj.constructor === Number  )    )
    {
        return obj;
    }

    map = map || new Map();
    var t = map.get(obj);
    if (t !== undefined) {
        return t;
    }

    // clone with provided custom "clone" method
    if (obj.$clone !== undefined) {
        return obj.$clone(map);
    }

    // clone array
    if (Array.isArray(obj)) {
        var naobj = [];

        map.set(obj, naobj);
        map[obj] = naobj;

        for(var i = 0; i < obj.length; i++) {
            naobj[i] = clone(obj[i], map);
        }
        return naobj;
    }

    // clone class
    if (obj.clazz === Class) {
        var clazz = Class(obj, []);
        clazz.inheritProperties = true;
        return clazz;
    }

    // function cannot be cloned
    if (typeof obj === 'function' || obj.constructor !==  Object) {
        return obj;
    }

    var nobj = {};
    map.set(obj, nobj); // keep one instance of cloned for the same object

    // clone object fields
    for(var k in obj) {
        if (obj.hasOwnProperty(k) === true) {
            nobj[k] = clone(obj[k], map);
        }
    }

    return nobj;
}

/**
 * Instantiate a new class instance of the given class with the specified constructor
 * arguments.
 * @param  {Function} clazz a class
 * @param  {Array} [args] an arguments list
 * @return {Object}  a new instance of the given class initialized with the specified arguments
 * @method newInstance
 * @for  zebkit
 */
function newInstance(clazz, args) {
    if (arguments.length > 1 && args.length > 0) {
        var f = function () {};
        f.prototype = clazz.prototype;
        var o = new f();
        clazz.apply(o, args);
        return o;
    }

    return new clazz();
}

function $make_proto(props, superProto) {
    if (superProto === null) {
        return function $prototype(clazz) {
            for(var k in props) {
                if (props.hasOwnProperty(k)) {
                    this[k] = props[k];
                }
            }
        };
    } else {
        return function $prototype(clazz) {
            superProto.call(this, clazz);
            for(var k in props) {
                if (props.hasOwnProperty(k)) {
                    this[k] = props[k];
                }
            }
        };
    }
}

/**
 * Interface is way to share common functionality by avoiding multiple inheritance.
 * It allows developers to mix number of methods to different classes. For instance:

      // declare "I" interface that contains one method a
      var I = zebkit.Interface([
          function a() {

          }
      ]);

      // declare "A" class
      var A = zebkit.Class([]);

      // declare "B" class that inherits class A and mix interface "I"
      var B = zebkit.Class(A, I, []);

      // instantiate "B" class
      var b = new B();
      zebkit.instanceOf(b, I);  // true
      zebkit.instanceOf(b, A);  // true
      zebkit.instanceOf(b, B);  // true

      // call mixed method
      b.a();

 * @return {Function} an interface
 * @param {Array} [methods] list of methods declared in the interface
 * @constructor
 * @class  zebkit.Interface
 */
var Interface = $make_template(null, function() {
    var $Interface = $make_template(Interface, function() {
        // Clone interface  parametrized with the given properties set
        if (typeof this === 'undefined' || this.constructor !== $Interface) {  // means the method execution is not a result of "new" method
            if (arguments.length !== 1) {
                throw new Error("Invalid number of arguments. Properties set is expected");
            }

            if (arguments[0].constructor !== Object) {
                throw new Error("Invalid argument type. Properties set is expected");
            }

            var iclone = $Interface.$clone();
            iclone.prototype.$prototype = $make_proto(arguments[0],
                                                     $Interface.prototype.$prototype);
            return iclone;
        } else {
            // Create a class that inherits the interface and instantiate it
            if (arguments.length > 1) {
                throw new Error("One or zero argument is expected");
            }
            return new (Class($Interface, arguments.length > 0 ? arguments[0] : []))();
        }
    });

    if (arguments.length > 1) {
        throw new Error("Invalid number of arguments. List of methods or properties is expected");
    }

    // abstract method counter, not used now, but can be used in the future
    // to understand if the given class override all abstract methods (should be
    // controlled in the places of "$cpMethods" call)
    $Interface.$abstractMethods = 0;

    var arg = arguments.length === 0 ? [] : arguments[0];
    if (arg.constructor === Object) {
        arg = [ $make_proto(arg, null) ];
    } else if (Array.isArray(arg) === false) {
        throw new Error("Invalid argument type. List of methods pr properties is expected");
    }

    if (arg.length > 0) {
        var  proto      = $Interface.prototype,
             isAbstract = false;

        for(var i = 0; i < arg.length; i++) {
            var method = arg[i];

            if (method === "abstract") {
                isAbstract = true;
            } else {
                if (typeof method !== "function") {
                    throw new Error("Method is expected instead of " + method);
                }

                var name = $FN(method);
                if (name === CDNAME) {
                    throw new Error("Constructor declaration is not allowed in interface");
                }

                if (proto[name] !== undefined) {
                    throw new Error("Duplicated interface method '" + name + "(...)'");
                }

                if (name === "$clazz") {
                    method.call($Interface, $Interface);
                } else if (isAbstract === true) {
                    (function(name) {
                        proto[name] = function() {
                            throw new Error("Abstract method '" + name + "(...)' is not implemented");
                        };

                        // mark method as abstract
                        proto[name].$isAbstract = true;

                        // count abstract methods
                        $Interface.$abstractMethods++;
                    })(name);
                } else {
                    proto[name] = method;
                }
            }
        }
    }

    /**
     * Private implementation of an interface cloning.
     * @return {zebkit.Interface} a clone of the interface
     * @method $clone
     * @private
     */
    $Interface.$clone = function() {
        var iclone = Interface(), k = null; // create interface

        // clone interface level variables
        for(k in this) {
            if (this.hasOwnProperty(k)) {
                iclone[k] = clone(this[k]);
            }
        }

        // copy methods from proto
        var proto = this.prototype;
        for(k in proto) {
            if (k !== "clazz" && proto.hasOwnProperty(k) === true) {
                iclone.prototype[k] = clone(proto[k]);
            }
        }

        return iclone;
    };

    $Interface.clazz.$name = "zebkit.Interface"; // assign name
    return $Interface;
});

/**
 * Core method method to declare a zebkit class following easy OOP approach. The easy OOP concept
 * supports the following OOP features:
 *
 *
 *  __Single class inheritance.__ Any class can extend an another zebkit class

        // declare class "A" that with one method "a"
        var A = zebkit.Class([
            function a() { ... }
        ]);

        // declare class "B" that inherits class "A"
        var B = zebkit.Class(A, []);

        // instantiate class "B" and call method "a"
        var b = new B();
        b.a();


* __Class method overriding.__ Override a parent class method implementation

        // declare class "A" that with one method "a"
        var A = zebkit.Class([
            function a() { ... }
        ]);

        // declare class "B" that inherits class "A"
        // and overrides method a with an own implementation
        var B = zebkit.Class(A, [
            function a() { ... }
        ]);

* __Constructors.__ Constructor is a method with empty name

        // declare class "A" that with one constructor
        var A = zebkit.Class([
            function () { this.variable = 100; }
        ]);

        // instantiate "A"
        var a = new A();
        a.variable // variable is 100

* __Static methods and variables declaration.__ Static fields and methods can be defined
    by declaring special "$clazz" method whose context is set to declared class

        var A = zebkit.Class([
            // special method where static stuff has to be declared
            function $clazz() {
                // declare static field
                this.staticVar = 100;
                // declare static method
                this.staticMethod = function() {};
            }
        ]);

        // access static field an method
        A.staticVar      // 100
        A.staticMethod() // call static method

* __Access to super class context.__ You can call method declared in a parent class

        // declare "A" class with one class method "a(p1,p2)"
        var A = zebkit.Class([
            function a(p1, p2) { ... }
        ]);

        // declare "B" class that inherits "A" class and overrides "a(p1,p2)" method
        var B = zebkit.Class(A, [
            function a(p1, p2) {
                // call "a(p1,p2)" method implemented with "A" class
                this.$super(p1,p2);
            }
        ]);

 *
 *  One of the powerful feature of zebkit easy OOP concept is possibility to instantiate
 *  anonymous classes and interfaces. Anonymous class is an instance of an existing
 *  class that can override the original class methods with own implementations, implements
 *  own list of interfaces and methods. In other words the class instance customizes class
 *  definition for the particular instance of the class;

        // declare "A" class
        var A = zebkit.Class([
            function a() { return 1; }
        ]);

        // instantiate anonymous class that add an own implementation of "a" method
        var a = new A([
            function a() { return 2; }
        ]);
        a.a() // return 2

 * @param {zebkit.Class} [inheritedClass] an optional parent class to be inherited
 * @param {zebkit.Interface} [inheritedInterfaces]* an optional list of interfaces for
 * the declared class to be mixed in the class
 * @param {Array} methods list of declared class methods. Can be empty array.
 * @return {Function} a class definition
 * @constructor
 * @class zebkit.Class
 */
function $mixing(clazz, methods) {
    if (Array.isArray(methods) === false) {
        throw new Error("Methods array is expected (" + methods + ")");
    }

    var names = {};
    for(var i = 0; i < methods.length; i++) {
        var method     = methods[i],
            methodName = $FN(method);

        // detect if the passed method is proxy method
        if (method.methodBody !== undefined) {
            throw new Error("Proxy method '" + methodName + "' cannot be mixed in a class");
        }

        // map user defined constructor to internal constructor name
        if (methodName === CDNAME) {
            methodName = CNAME;
        } else if (methodName[0] === '$') {
            // populate prototype fields if a special method has been defined
            if (methodName === "$prototype") {
                method.call(clazz.prototype, clazz);
                if (clazz.prototype[CDNAME]) {
                    clazz.prototype[CNAME] = clazz.prototype[CDNAME];
                    delete clazz.prototype[CDNAME];
                }
                continue;
            }

            // populate class level fields if a special method has been defined
            if (methodName === "$clazz") {
                method.call(clazz);
                continue;
            }
        }

        if (names[methodName] === true) {
            throw new Error("Duplicate declaration of '" + methodName+ "(...)' method");
        }

        var existentMethod = clazz.prototype[methodName];
        if (existentMethod !== undefined && typeof existentMethod !== 'function') {
            throw new Error("'" + methodName + "(...)' method clash with a field");
        }

        // if constructor doesn't have super definition than let's avoid proxy method
        // overhead
        if (existentMethod === undefined && methodName === CNAME) {
            clazz.prototype[methodName] = method;
        } else {
            // Create and set proxy method that is bound to the given class
            clazz.prototype[methodName] = $ProxyMethod(methodName, method, clazz);
        }

        // save method we have already added to check double declaration error
        names[methodName] = true;
    }
}

// Class methods to be populated in all classes
var classTemplateFields = {
    /**
     * Makes the class hashable. Hashable class instances are automatically
     * gets unique hash code that is returned with its overridden "toString()"
     * method. The hash code is stored in special "$hash$" field. The feature
     * can be useful when you want to store class instances in "{}" object
     * where key is the hash and the value is the instance itself.
     * @method hashable
     * @chainable
     * @for zebkit.Class
     */
    hashable : function() {
        if (this.$uniqueness !== true) {
            this.$uniqueness = true;
            this.prototype.toString = $toString;
        }
        return this;
    },

    /**
     * Makes the class hashless. Prevents generation of hash code for
     * instances of the class.
     * @method hashless
     * @chainable
     * @for zebkit.Class
     */
    hashless : function() {
        if (this.$uniqueness === true) {
            this.$uniqueness = false;
            this.prototype.toString = Object.prototype.toString;
        }
        return this;
    },

    /**
     * Extend the class with new method and implemented interfaces.
     * @param {zebkit.Interface} [interfaces]*  number of interfaces the class has to implement.
     * @param {Array} methods set of methods the given class has to be extended.
     * @method extend
     * @chainable
     * @for zebkit.Class
     */
    // add extend method later to avoid the method be inherited as a class static field
    extend : function() {
        var methods   = arguments[arguments.length - 1],
            hasMethod = Array.isArray(methods);

        // inject class
        if (hasMethod && this.$isExtended !== true) {
            // create intermediate class
            var A = this.$parent !== null ? Class(this.$parent, [])
                                          : Class([]);

            // copy this class prototypes methods to intermediate class A and re-define
            // boundTo to the intermediate class A if they were bound to source class
            // methods that have been  moved from source class to class have to be re-bound
            // to A class
            for(var name in this.prototype) {
                if (name !== "clazz" && this.prototype.hasOwnProperty(name) ) {
                    var f = this.prototype[name];
                    if (typeof f === 'function') {
                        A.prototype[name] = f.methodBody !== undefined ? $ProxyMethod(name, f.methodBody, f.boundTo)
                                                                       : f;

                        if (A.prototype[name].boundTo === this) {
                            A.prototype[name].boundTo = A;
                            if (f.boundTo === this) {
                                f.boundTo = A;
                            }
                        }
                    }
                }
            }

            this.$parent = A;
            this.$isExtended = true;
        }

        if (hasMethod) {
            $mixing(this, methods);
        }

        // add passed interfaces
        for(var i = 0; i < arguments.length - (hasMethod ? 1 : 0); i++) {
            var I = arguments[i];
            if (I === null || I === undefined || I.clazz !== Interface) {
                throw new Error("Interface is expected");
            }

            if (this.$parents[I.$hash$] !== undefined) {
                throw new Error("Interface has been already inherited");
            }

            $cpMethods(I.prototype, this.prototype, this);
            this.$parents[I.$hash$] = I;
        }

        return this;
    },

    /**
     * Tests if the class inherits the given class or interface.
     * @param  {zebkit.Class | zebkit.Interface} clazz a class or interface.
     * @return {Boolean} true if the class or interface is inherited with
     * the class.
     * @method  isInherit
     * @for  zebkit.Class
     */
    isInherit : function(clazz) {
        if (this !== clazz) {
            // detect class
            if (clazz.clazz === this.clazz) {
                for (var p = this.$parent; p !== null; p = p.$parent) {
                    if (p === clazz) {
                        return true;
                    }
                }
            } else { // detect interface
                if (this.$parents[clazz.$hash$] === clazz) {
                    return true;
                }
            }
        }
        return false;
    },

    /**
     * Create an instance of the class
     * @param  {Object} [arguments]* arguments to be passed to the class constructor
     * @return {Object} an instance of the class.
     * @method newInstance
     * @for  zebkit.Class
     */
    newInstance : function() {
        return arguments.length === 0 ? newInstance(this)
                                      : newInstance(this, arguments);
    },

    /**
     * Create an instance of the class
     * @param  {Array} args an arguments array
     * @return {Object} an instance of the class.
     * @method newInstancea
     * @for  zebkit.Class
     */
    newInstancea : function(args) {
        return arguments.length === 0 ? newInstance(this)
                                      : newInstance(this, args);
    }
};

// methods are populated in all instances of zebkit classes
var classTemplateProto = {
    /**
     * Extend existent class instance with the given methods and interfaces
     * For example:

        var A = zebkit.Class([ // declare class A that defines one "a" method
            function a() {
                console.log("A:a()");
            }
        ]);

        var a = new A();
        a.a();  // show "A:a()" message

        A.a.extend([
            function b() {
                console.log("EA:b()");
            },

            function a() {   // redefine "a" method
                console.log("EA:a()");
            }
        ]);

        a.b(); // show "EA:b()" message
        a.a(); // show "EA:a()" message

     * @param {zebkit.Interface} [interfaces]* interfaces to be implemented with the
     * class instance
     * @param {Array} methods list of methods the class instance has to be extended
     * with
     * @method extend
     * @for zebkit.Class.zObject
     */
    extend : function() {
        var clazz = this.clazz,
            l = arguments.length,
            f = arguments[l - 1],
            hasArray = Array.isArray(f),
            i = 0;

        // replace the instance class with a new intermediate class
        // that inherits the replaced class. it is done to support
        // $super method calls.
        if (this.$isExtended !== true) {
            clazz = Class(clazz, []);
            this.$isExtended = true;         // mark the instance as extended to avoid double extending.
            clazz.$name = this.clazz.$name;
            this.clazz = clazz;
        }

        if (hasArray) {
            var init = null;
            for(i = 0; i < f.length; i++) {
                var n = $FN(f[i]);
                if (n === CDNAME) {
                    init = f[i];  // postpone calling initializer before all methods will be defined
                } else {
                    if (this[n] !== undefined && typeof this[n] !== 'function') {
                        throw new Error("Method '" + n + "' clash with a property");
                    }
                    this[n] = $ProxyMethod(n, f[i], clazz);
                }
            }

            if (init !== null) {
                init.call(this);
            }
            l--;
        }

        // add new interfaces if they has been passed
        for (i = 0; i < arguments.length - (hasArray ? 1 : 0); i++) {
            if (arguments[i].clazz !== Interface) {
                throw new Error("Invalid argument " + arguments[i] + " Interface is expected.");
            }

            var I = arguments[i];
            if (clazz.$parents[I.$hash$] !== undefined) {
                throw new Error("Interface has been already inherited");
            }

            $cpMethods(I.prototype, this, clazz);
            clazz.$parents[I.$hash$] = I;
        }
        return this;
    },

    /**
     * Call super method implementation.
     * @param {Function} [superMethod]? optional parameter that should be a method of the class instance
     * that has to be called
     * @param {Object} [args]* arguments list to pass the executed method
     * @return {Object} return what super method returns
     * @method $super
     * @example
     *
     *    var A = zebkit.Class([
     *        function a(p) { return 10 + p; }
     *    ]);
     *
     *    var B = zebkit.Class(A, [
     *        function a(p) {
     *            return this.$super(p) * 10;
     *        }
     *    ]);
     *
     *    var b = new B();
     *    b.a(10) // return 200
     *
     * @for zebkit.Class.zObject
     */
    $super : function() {
       if ($caller !== null) {
            for (var $s = $caller.boundTo.$parent; $s !== null; $s = $s.$parent) {
                var m = $s.prototype[$caller.methodName];
                if (m !== undefined) {
                    return m.apply(this, arguments);
                }
            }

            // handle method not found error
            var cln = this.clazz && this.clazz.$name ? this.clazz.$name + "." : "";
            throw new ReferenceError("Method '" +
                                     cln +
                                     ($caller.methodName === CNAME ? "constructor"
                                                                   : $caller.methodName) + "(" + arguments.length + ")" + "' not found");
        } else {
            throw new Error("$super is called outside of class context");
        }
    },

    // TODO: not stable API
    $supera : function(args) {
       if ($caller !== null) {
            for (var $s = $caller.boundTo.$parent; $s !== null; $s = $s.$parent) {
                var m = $s.prototype[$caller.methodName];
                if (m !== undefined) {
                    return m.apply(this, args);
                }
            }

            // handle method not found error
            var cln = this.clazz && this.clazz.$name ? this.clazz.$name + "." : "";
            throw new ReferenceError("Method '" +
                                     cln +
                                     ($caller.methodName === CNAME ? "constructor"
                                                                   : $caller.methodName) + "(" + arguments.length + ")" + "' not found");
        } else {
            throw new Error("$super is called outside of class context");
        }
    },

    // TODO: not stable API, $super that doesn't throw exception is there is no super implementation
    $$super : function() {
       if ($caller !== null) {
            for(var $s = $caller.boundTo.$parent; $s !== null; $s = $s.$parent) {
                var m = $s.prototype[$caller.methodName];
                if (m !== undefined) {
                    return m.apply(this, arguments);
                }
            }
        } else {
            throw new Error("$super is called outside of class context");
        }
    },

    /**
     * Get a first super implementation of the given method in a parent classes hierarchy.
     * @param  {String} name a name of the method
     * @return {Function} a super method implementation
     * @method  $getSuper
     * @for  zebkit.Class.zObject
     */
    $getSuper : function(name) {
       if ($caller !== null) {
            for(var $s = $caller.boundTo.$parent; $s !== null; $s = $s.$parent) {
                var m = $s.prototype[name];
                if (typeof m === 'function') {
                    return m;
                }
            }
            return null;
        }
        throw new Error("$super is called outside of class context");
    },

    $genHash : function() {
        if (this.$hash$ === undefined) {
            this.$hash$ = "$ZeInGen" + ($$$++);
        }
        return this.$hash$;
    },

    $clone : function(map) {
        map = map || new Map();

        var f = function() {};
        f.prototype = this.constructor.prototype;
        var nobj = new f();
        map.set(this, nobj);

        for(var k in this) {
            if (this.hasOwnProperty(k)) {
                // obj's layout is obj itself
                var t = map.get(this[k]);
                if (t !== undefined) {
                    nobj[k] = t;
                } else {
                    nobj[k] = clone(this[k], map);
                }
            }
        }

        // speed up clearing resources
        map.clear();

        nobj.constructor = this.constructor;

        if (nobj.$hash$ !== undefined) {
            nobj.$hash$ = "$zObj_" + ($$$++);
        }

        nobj.clazz = this.clazz;
        return nobj;
    }
};

// create Class template what means we define a function (meta class) that has to be used to define
// Class. That means we define a function that returns another function that is a Class
var Class = $make_template(null, function() {
    if (arguments.length === 0) {
        throw new Error("No class method list was found");
    }

    if (Array.isArray(arguments[arguments.length - 1]) === false) {
        throw new Error("No class methods have been passed");
    }

    if (arguments.length > 1 && typeof arguments[0] !== "function")  {
        throw new ReferenceError("Invalid parent class or interface '" + arguments[0] + "'");
    }

    var classMethods = arguments[arguments.length - 1],
        parentClass  = null,
        toInherit    = [];

    // detect parent class in inheritance list as the first argument that has "clazz" set to Class
    if (arguments.length > 0 && (arguments[0] === null || arguments[0].clazz === Class)) {
        parentClass = arguments[0];
    }

    // use instead of slice for performance reason
    for(var i = 0; i < arguments.length - 1; i++) {
        toInherit[i] = arguments[i];

        // let's make sure we inherit interface
        if (parentClass === null || i > 0) {
            if (toInherit[i] === undefined || toInherit[i] === null) {
                throw new ReferenceError("Undefined inherited interface [" + i + "] " );
            } else if (toInherit[i].clazz !== Interface) {
                throw new ReferenceError("Inherited interface is not an Interface ( [" + i + "] '" + toInherit[i] + "'')");
            }
        }
    }

    // define Class (function) that has to be used to instantiate the class instance
    var classTemplate = $make_template(Class, function() {
        if (classTemplate.$uniqueness === true) {
            this.$hash$ = "$ZkIo" + ($$$++);
        }

        if (arguments.length > 0) {
            var a = arguments[arguments.length - 1];

            // anonymous is customized class instance if last arguments is array of functions
            if (Array.isArray(a) === true && typeof a[0] === 'function') {
                a = a[0];

                // prepare arguments list to declare an anonymous class
                var args = [ classTemplate ],      // first of all the class has to inherit the original class
                    k    = arguments.length - 2;

                // collect interfaces the anonymous class has to implement
                for(; k >= 0 && arguments[k].clazz === Interface; k--) {
                    args.push(arguments[k]);
                }

                // add methods list
                args.push(arguments[arguments.length - 1]);

                var cl = Class.apply(null, args),  // declare new anonymous class
                    // create a function to instantiate an object that will be made the
                    // anonymous class instance. The intermediate object is required to
                    // call constructor properly since we have arguments as an array
                    f  = function() {};

                cl.$name = classTemplate.$name; // the same class name for anonymous
                f.prototype = cl.prototype; // the same prototypes

                var o = new f();

                // call constructor
                // use array copy instead of cloning with slice for performance reason
                // (Array.prototype.slice.call(arguments, 0, k + 1))
                args = [];
                for (var i = 0; i < k + 1; i++) {
                    args[i] = arguments[i];
                }
                cl.apply(o, args);

                // set constructor field for consistency
                o.constructor = cl;
                return o;
            }
        }

        // call class constructor
        if (this.$ !== undefined) { // TODO: hard-coded constructor name to speed up
            return this.$.apply(this, arguments);
        }
    }, toInherit);

    /**
     *  Internal attribute that caches properties setter references.
     *  @attribute $propertySetterInfo
     *  @type {Object}
     *  @private
     *  @for zebkit.Class
     *  @readOnly
     */

    // prepare fields that caches the class properties. existence of the property
    // force getPropertySetter method to cache the method
    classTemplate.$propertySetterInfo = {};


    classTemplate.$propertyGetterInfo = {};


    /**
     *  Reference to a parent class
     *  @attribute $parent
     *  @type {zebkit.Class}
     *  @protected
     *  @readOnly
     */

    // copy parents prototype methods and fields into
    // new class template
    classTemplate.$parent = parentClass;
    if (parentClass !== null) {
        for(var k in parentClass.prototype) {
            if (parentClass.prototype.hasOwnProperty(k)) {
                var f = parentClass.prototype[k];
                classTemplate.prototype[k] = (f !== undefined &&
                                              f !== null &&
                                              f.hasOwnProperty("methodBody")) ? $ProxyMethod(f.methodName, f.methodBody, f.boundTo)
                                                                              : f;
            }
        }
    }

    /**
     * The instance class.
     * @attribute clazz
     * @type {zebkit.Class}
     */
    classTemplate.prototype.clazz = classTemplate;

    // check if the method has been already defined in the class
    if (classTemplate.prototype.properties === undefined) {
        classTemplate.prototype.properties = function(p) {
            return properties(this, p);
        };
    }

    // populate class template prototype methods and fields
    for(var ptf in classTemplateProto) {
        classTemplate.prototype[ptf] = classTemplateProto[ptf];
    }

    // copy methods from interfaces before mixing class methods
    if (toInherit.length > 0) {
        for(var idx = toInherit[0].clazz === Interface ? 0 : 1; idx < toInherit.length; idx++) {
            var ic = toInherit[idx];
            $cpMethods(ic.prototype, classTemplate.prototype, classTemplate);

            // copy static fields from interface to the class
            for(var sk in ic) {
                if (sk[0] !== '$' &&
                    ic.hasOwnProperty(sk) === true &&
                    classTemplate.hasOwnProperty(sk) === false)
                {
                    classTemplate[sk] = clone(ic[sk]);
                }
            }
        }
    }

    // initialize uniqueness field with false
    classTemplate.$uniqueness = false;

    // inherit static fields from parent class
    if (parentClass !== null) {
        for (var key in parentClass) {
            if (key[0] !== '$' &&
                parentClass.hasOwnProperty(key) &&
                classTemplate.hasOwnProperty(key) === false)
            {
                classTemplate[key] = clone(parentClass[key]);
            }
        }

        // inherit uni
        if (parentClass.$uniqueness === true) {
            classTemplate.hashable();
        }
    }

    // add class declared methods after the previous step to get a chance to
    // overwrite class level definitions
    $mixing(classTemplate, classMethods);


    // populate class level methods and fields into class template
    for (var tf in classTemplateFields) {
        classTemplate[tf] = classTemplateFields[tf];
    }

    // assign proper name to class
    classTemplate.clazz.$name = "zebkit.Class";

    // copy methods from interfaces
    if (toInherit.length > 0) {
        // notify inherited class and interfaces that they have been inherited with the given class
        for(var j = 0; j < toInherit.length; j++) {
            if (typeof toInherit[j].inheritedWidth === 'function') {
                toInherit[j].inheritedWidth(classTemplate);
            }
        }
    }

    return classTemplate;
});

/**
 * Get class by the given class name
 * @param  {String} name a class name
 * @return {Function} a class. Throws exception if the class cannot be
 * resolved by the given class name
 * @method forName
 * @throws Error
 * @for  zebkit.Class
 */
Class.forName = function(name) {
    return $cache(name);
};


/**
 * Test if the given object is instance of the specified class or interface. It is preferable
 * to use this method instead of JavaScript "instanceof" operator whenever you are dealing with
 * zebkit classes and interfaces.
 * @param  {Object} obj an object to be evaluated
 * @param  {Function} clazz a class or interface
 * @return {Boolean} true if a passed object is instance of the given class or interface
 * @method instanceOf
 * @for  zebkit
 */
function instanceOf(obj, clazz) {
    if (clazz !== null && clazz !== undefined) {
        if (obj === null || obj === undefined)  {
            return false;
        } else if (obj.clazz === undefined) {
            return (obj instanceof clazz);
        } else {
            return obj.clazz !== null &&
                   (obj.clazz === clazz ||
                    obj.clazz.$parents[clazz.$hash$] !== undefined);
        }
    }

    throw new Error("instanceOf(): null class");
}

/**
 * Dummy class that implements nothing but can be useful to instantiate
 * anonymous classes with some on "the fly" functionality:
 *
 *     // instantiate and use zebkit class with method "a()" implemented
 *     var ac = new zebkit.Dummy([
 *          function a() {
 *             ...
 *          }
 *     ]);
 *
 *     // use it
 *     ac.a();
 *
 * @constructor
 * @class zebkit.Dummy
 */
var Dummy = Class([]);


$export(clone, instanceOf, newInstance,
        { "Class": Class, "Interface" : Interface, "Dummy": Dummy, "CDNAME": CDNAME, "CNAME" : CNAME });

/**
 * JSON object loader class is a handy way to load hierarchy of objects encoded with
 * JSON format. The class supports standard JSON types plus it extends JSON with a number of
 * features that helps to make object creation more flexible. Zson allows developers
 * to describe creation of any type of object. For instance if you have a class "ABC" with
 * properties "prop1", "prop2", "prop3" you can use instance of the class as a value of
 * a JSON property as follow:
 *
 *      { "instanceOfABC": {
 *              "@ABC"  : [],
 *              "prop1" : "property 1 value",
 *              "prop2" : true,
 *              "prop3" : 200
 *          }
 *      }
 *
 *  And than:
 *
 *       // load JSON mentioned above
 *       zebkit.Zson.then("abc.json", function(zson) {
 *           zson.get("instanceOfABC");
 *       });
 *
 *  Features the JSON zson supports are listed below:
 *
 *    - **Access to hierarchical properties** You can use dot notation to get a property value. For
 *    instance:
 *
 *     { "a" : {
 *            "b" : {
 *                "c" : 100
 *            }
 *         }
 *     }
 *
 *     zebkit.Zson.then("abc.json", function(zson) {
 *         zson.get("a.b.c"); // 100
 *     });
 *
 *
 *    - **Property reference** Every string JSON value that starts from "@" considers as reference to
 *    another property value in the given JSON.
 *
 *     {  "a" : 100,
 *        "b" : {
 *            "c" : "%{a.b}"
 *        }
 *     }
 *
 *    here property "b.c" equals to 100 since it refers to  property "a.b"
 *     *
 *    - **Class instantiation**  Property can be easily initialized with an instantiation of required class. JSON
 *    zson considers all properties whose name starts from "@" character as a class name that has to be instantiated:
 *
 *     {  "date": {
 *           { "@Date" : [] }
 *         }
 *     }
 *
 *   Here property "date" is set to instance of JS Date class.
 *
 *   - **Factory classes** JSON zson follows special pattern to describe special type of property whose value
 *   is re-instantiated every time the property is requested. Definition of the property value is the same
 *   to class instantiation, but the name of class has to prefixed with "*" character:
 *
 *
 *     {  "date" : {
 *           "@ *Date" : []
 *        }
 *     }
 *
 *
 *   Here, every time you call get("date") method a new instance of JS date object will be returned. So
 *   every time will have current time.
 *
 *   - **JS Object initialization** If you have an object in your code you can easily fulfill properties of the
 *   object with JSON zson. For instance you can create zebkit UI panel and adjust its background, border and so on
 *   with what is stored in JSON:
 *
 *
 *     {
 *       "background": "red",
 *       "borderLayout": 0,
 *       "border"    : { "@zebkit.draw.RoundBorder": [ "black", 2 ] }
 *     }
 *
 *     var pan = new zebkit.ui.Panel();
 *     new zebkit.Zson(pan).then("pan.json", function(zson) {
 *         // loaded and fulfill panel
 *         ...
 *     });
 *
 *
 *   - **Expression** You can evaluate expression as a property value:
 *
 *
 *     {
 *         "a": { ".expr":  "100*10" }
 *     }
 *
 *
 *   Here property "a" equals 1000
 *
 *
 *   - **Load external resources** You can combine Zson from another Zson:
 *
 *
 *     {
 *         "a": "%{<json> embedded.json}",
 *         "b": 100
 *     }
 *
 *
 *   Here property "a" is loaded with properties set with loading external "embedded.json" file
 *
 * @class zebkit.Zson
 * @constructor
 * @param {Object} [obj] a root object to be loaded with
 * the given JSON configuration
 */
var Zson = Class([
    function (root) {
        if (arguments.length > 0) {
            this.root = root;
        }

        /**
         * Map of aliases and appropriate classes
         * @attribute classAliases
         * @protected
         * @type {Object}
         * @default {}
         */
        this.classAliases = {};
    },

    function $clazz() {
        /**
         * Build zson from the given json file
         * @param  {String|Object}   json a JSON or path to JSOn file
         * @param  {Object}   [root] an object to be filled with the given JSON
         * @param  {Function} [cb]   a callback function to catch the JSON loading is
         * completed
         * @return {zebkit.DoIt} a promise to catch result
         * @method  then
         * @static
         */
        this.then = function(json, root, cb) {
            if (typeof root === 'function') {
                cb   = root;
                root = null;
            }

            var zson = arguments.length > 1 && root !== null ? new Zson(root)
                                                             : new Zson();

            if (typeof cb === 'function') {
                return zson.then(json, cb);
            } else {
                return zson.then(json);
            }
        };
    },

    function $prototype() {
        /**
         * URL the JSON has been loaded from
         * @attribute  uri
         * @type {zebkit.URI}
         * @default null
         */
        this.uri = null;

        /**
         * Object that keeps loaded and resolved content of a JSON
         * @readOnly
         * @attribute root
         * @type {Object}
         * @default {}
         */
        this.root = null;

        /**
         * Original JSON as a JS object
         * @attribute content
         * @protected
         * @type {Object}
         * @default null
         */
        this.content = null;

        /**
         * The property says if the object introspection is required to try find a setter
         * method for the given key. For instance if an object is loaded with the
         * following JSON:

         {
            "color": "red"
         }

         * the introspection will cause zson class to try finding "setColor(c)" method in
         * the loaded with the JSON object and call it to set "red" property value.
         * @attribute usePropertySetters
         * @default true
         * @type {Boolean}
         */
        this.usePropertySetters = true;

        /**
         * Cache busting flag.
         * @attribute cacheBusting
         * @type {Boolean}
         * @default false
         */
        this.cacheBusting = false;

        /**
         * Internal variables set
         * @attribute $variables
         * @protected
         * @type {Object}
         */
        this.$variables = null;

        /**
         * Base URI to be used to build paths to external resources. The path is
         * used for references that occur in zson.
         * @type {String}
         * @attribute baseUri
         * @default null
         */
        this.baseUri = null;

        /**
         * Get a property value by the given key. The property name can point to embedded fields:
         *
         *      new zebkit.Zson().then("my.json", function(zson) {
         *          zson.get("a.b.c");
         *      });
         *
         *
         * @param  {String} key a property key.
         * @return {Object} a property value
         * @throws Error if property cannot be found and it  doesn't start with "?"
         * @method  get
         */
        this.get = function(key) {
            if (key === null || key === undefined) {
                throw new Error("Null key");
            }

            var ignore = false;
            if (key[0] === '?') {
                key = key.substring(1).trim();
                ignore = true;
            }

            if (ignore) {
                try {
                    return getPropertyValue(this.root, key);
                } catch(e) {
                    if ((e instanceof ReferenceError) === false) {
                        throw e;
                    }
                }
            } else {
                return getPropertyValue(this.root, key);
            }
        };

        /**
         * Call the given method defined with the Zson class instance and
         * pass the given arguments to the method.
         * @param  {String} name a method name
         * @param  {Object} d arguments
         * @return {Object} a method execution result
         * @method callMethod
         */
        this.callMethod = function(name, d) {
            var m  = this[name.substring(1).trim()],
                ts = this.$runner.$tasks.length,
                bs = this.$runner.$busy;

            if (typeof m !== 'function') {
                throw new Error("Method '" + name + "' cannot be found");
            }

            var args = this.buildValue(Array.isArray(d) ? d
                                                        : [ d ]),
                $this = this;

            if (this.$runner.$tasks.length === ts &&
                this.$runner.$busy === bs           )
            {
                var res = m.apply(this, args);
                if (res instanceof DoIt) {
                    return new DoIt().till(this.$runner).then(function() {
                        var jn = this.join();
                        res.then(function(res) {
                            jn(res);
                            return res;
                        }).then(function(res) {
                            return res;
                        });
                    }).catch(function(e) {
                        $this.$runner.error(e);
                    });
                } else {
                    return res;
                }
            } else {
                return new DoIt().till(this.$runner).then(function() {
                    if (args instanceof DoIt) {
                        var jn = this.join();
                        args.then(function(res) {
                            jn(res);
                            return res;
                        });
                    } else {
                        return args;
                    }
                }).then(function(args) {
                    var res = m.apply($this, args);
                    if (res instanceof DoIt) {
                        var jn = this.join();
                        res.then(function(res) {
                            jn(res);
                            return res;
                        });
                    } else {
                        return res;
                    }
                }).then(function(res) {
                    return res;
                }).catch(function(e) {
                    $this.$runner.error(e);
                });
            }
        };

        this.$resolveRef = function(target, names) {
            var fn = function(ref, rn) {
                rn.then(function(target) {
                    if (target !== null && target !== undefined && target.hasOwnProperty(ref) === true) {
                        var v = target[ref];
                        if (v instanceof DoIt) {
                            var jn = this.join();
                            v.then(function(res) {
                                jn.call(rn, res);
                                return res;
                            });
                        } else {
                            return v;
                        }
                    } else {
                        return undefined;
                    }
                });
            };

            for (var j = 0; j < names.length; j++) {
                var ref = names[j];

                if (target.hasOwnProperty(ref)) {
                    var v = target[ref];

                    if (v instanceof DoIt) {
                        var rn      = new DoIt(),
                            trigger = rn.join();

                        for(var k = j; k < names.length; k++) {
                            fn(names[k], rn);
                        }

                        trigger.call(rn, target);
                        return rn;
                    } else {
                        target = target[ref];
                    }

                } else {
                    return undefined;
                }
            }

            return target;
        };

        this.$buildArray = function(d) {
            var hasAsync = false;
            for (var i = 0; i < d.length; i++) {
                var v = this.buildValue(d[i]);
                if (v instanceof DoIt) {
                    hasAsync = true;
                    this.$assignValue(d, i, v);
                } else {
                    d[i] = v;
                }
            }

            if (hasAsync) {
                return new DoIt().till(this.$runner).then(function() {
                    return d;
                });
            } else {
                return d;
            }
        };

        /**
         * Build a class instance.
         * @param  {String} classname a class name
         * @param  {Array|null|Object} args  a class constructor arguments
         * @param  {Object} props properties to be applied to class instance
         * @return {Object|zebkit.DoIt}
         * @method $buildClass
         * @private
         */
        this.$buildClass = function(classname, args, props) {
            var clz       = null,
                busy      = this.$runner.$busy,
                tasks     = this.$runner.$tasks.length;

            classname = classname.trim();

            // '?' means optional class instance.
            if (classname[0] === '?') {
                classname = classname.substring(1).trim();
                try {
                    clz = this.resolveClass(classname[0] === '*' ? classname.substring(1).trim()
                                                                 : classname);
                } catch (e) {
                    return null;
                }
            } else {
                clz = this.resolveClass(classname[0] === '*' ? classname.substring(1).trim()
                                                             : classname);
            }

            args = this.buildValue(Array.isArray(args) ? args
                                                       : [ args ]);

            if (classname[0] === '*') {
                return (function(clazz, args) {
                    return {
                        $new : function() {
                            return newInstance(clazz, args);
                        }
                    };
                })(clz, args);
            }

            var props = this.buildValue(props);

            // let's do optimization to avoid unnecessary overhead
            // equality means nor arguments neither properties has got async call
            if (this.$runner.$busy === busy && this.$runner.$tasks.length === tasks) {
                var inst = newInstance(clz, args);
                this.merge(inst, props, true);
                return inst;
            } else {
                var $this = this;
                return new DoIt().till(this.$runner).then(function() {
                    var jn1 = this.join(),  // create all join here to avoid result overwriting
                        jn2 = this.join();

                    if (args instanceof DoIt) {
                        args.then(function(res) {
                            jn1(res);
                            return res;
                        });
                    } else {
                        jn1(args);
                    }

                    if (props instanceof DoIt) {
                        props.then(function(res) {
                            jn2(res);
                            return res;
                        });
                    } else {
                        jn2(props);
                    }
                }).then(function(args, props) {
                    var inst = newInstance(clz, args);
                    $this.merge(inst, props, true);
                    return inst;
                });
            }
        };

        this.$qsToVars = function(uri) {
            var qs   = null,
                vars = null;

            if ((uri instanceof URI) === false) {
                qs = new URI(uri.toString()).qs;
            } else {
                qs = uri.qs;
            }

            if (qs !== null || qs === undefined) {
                qs = URI.parseQS(qs);
                for(var k in qs) {
                    if (vars === null) {
                        vars = {};
                    }
                    vars[k] = URI.decodeQSValue(qs[k]);
                }
            }

            return vars;
        };

        this.$buildRef = function(d) {
            var idx = -1;

            if (d[2] === "<" || d[2] === '.' || d[2] === '/') { //TODO: not complete solution that cannot detect URLs
                var path  = null,
                    type  = null,
                    $this = this;

                if (d[2] === '<') {
                    // if the referenced path is not absolute path and the zson has been also
                    // loaded by an URL than build the full URL as a relative path from
                    // BAG URL
                    idx = d.indexOf('>');
                    if (idx <= 4) {
                        throw new Error("Invalid content type in URL '" + d + "'");
                    }

                    path = d.substring(idx + 1, d.length - 1).trim();
                    type = d.substring(3, idx).trim();
                } else {
                    path = d.substring(2, d.length - 1).trim();
                    type = "json";
                }

                if (type === 'js') {
                    return this.expr(path);
                }

                if (URI.isAbsolute(path) === false) {
                    if (this.baseUri !== null) {
                        path = URI.join(this.baseUri, path);
                    } else if (this.uri !== null) {
                        var pURL = new URI(this.uri).getParent();
                        if (pURL !== null) {
                            path = URI.join(pURL, path);
                        }
                    }
                }

                if (type === "json") {
                    var bag = new this.clazz();
                    bag.usePropertySetters = this.usePropertySetters;
                    bag.$variables         = this.$qsToVars(path);
                    bag.cacheBusting       = this.cacheBusting;

                    var bg = bag.then(path).catch();
                    this.$runner.then(bg.then(function(res) {
                        return res.root;
                    }));
                    return bg;
                } else if (type === 'img') {
                    if (this.uri !== null && URI.isAbsolute(path) === false) {
                        path = URI.join(new URI(this.uri).getParent(), path);
                    }
                    return image(path, false);
                } else if (type === 'txt') {
                    return new ZFS.GET(path).then(function(r) {
                        return r.responseText;
                    }).catch(function(e) {
                        $this.$runner.error(e);
                    });
                } else {
                    throw new Error("Invalid content type " + type);
                }

            } else {
                // ? means don't throw exception if reference cannot be resolved
                idx = 2;
                if (d[2] === '?') {
                    idx++;
                }

                var name = d.substring(idx, d.length - 1).trim(),
                    names   = name.split('.'),
                    targets = [ this.$variables, this.content, this.root, $global];

                for(var i = 0; i < targets.length; i++) {
                    var target = targets[i];
                    if (target !== null) {
                        var value = this.$resolveRef(target, names);
                        if (value !== undefined) {
                            return value;
                        }
                    }
                }

                if (idx === 2) {
                    throw new Error("Reference '" + name + "' cannot be resolved");
                } else {
                    return d;
                }
            }
        };

        /**
         * Build a value by the given JSON description
         * @param  {Object} d a JSON description
         * @return {Object} a value
         * @protected
         * @method buildValue
         */
        this.buildValue = function(d) {
            if (d === undefined || d === null || d instanceof DoIt ||
                (typeof d === "number"   || d.constructor === Number)  ||
                (typeof d === "boolean"  || d.constructor === Boolean)    )
            {
                return d;
            }

            if (Array.isArray(d)) {
                return this.$buildArray(d);
            }

            if (typeof d === "string" || d.constructor === String) {
                if (d[0] === '%' && d[1] === '{' && d[d.length - 1] === '}') {
                    return this.$buildRef(d);
                } else {
                    return d;
                }
            }

            var k = null;

            if (d.hasOwnProperty("class") === true) {
                k = d["class"];
                delete d["class"];

                if (isString(k) === false) {
                    var kk = null;
                    for (kk in k) {
                        return this.$buildClass(kk, k[kk], d);
                    }
                }
                return this.$buildClass(k, [], d);
            }

            // test whether we have a class definition
            for (k in d) {
                // handle class definition
                if (k[0] === '@' && d.hasOwnProperty(k) === true) {
                    var args = d[k];
                    delete d[k]; // delete class name
                    return this.$buildClass(k.substring(1), args, d);
                }

                //!!!!  trust the name of class occurs first what in general
                //      cannot be guaranteed by JSON spec but we can trust
                //      since many other third party applications stands
                //      on it too :)
                break;
            }

            for (k in d) {
                if (d.hasOwnProperty(k)) {
                    var v = d[k];

                    // special field name that says to call method to create a
                    // value by the given description
                    if (k[0] === "." || k[0] === '#') {
                        delete d[k];
                        if (k[0] === '#') {
                            this.callMethod(k, v);
                        } else {
                            return this.callMethod(k, v);
                        }
                    } else if (k[0] === '%') {
                        delete d[k];
                        this.mixin(d, this.$buildRef(k));
                    } else {
                        this.$assignValue(d, k, this.buildValue(v));
                    }
                }
            }

            return d;
        };

        this.$assignValue = function(o, k, v) {
            o[k] = v;
            if (v instanceof DoIt) {
                this.$runner.then(v.then(function(res) {
                    o[k] = res;
                    return res;
                }));
            }
        };

        this.$assignProperty = function(o, m, v) {
            // setter has to be placed in queue to let
            // value resolves its DoIts
            this.$runner.then(function(res) {
                if (Array.isArray(v)) {
                    m.apply(o, v);
                } else {
                    m.call (o, v);
                }
                return res;
            });
        };

        /**
         * Merge values of the given destination object with the values of
         * the specified  source object.
         * @param  {Object} dest  a destination object
         * @param  {Object} src   a source object
         * @param  {Boolean} [recursively] flag that indicates if the complex
         * properties of destination object has to be traversing recursively.
         * By default the flag is true. The destination property value is
         * considered not traversable if its class defines "mergeable" property
         * that is set top true.
         * @return {Object} a merged destination object.
         * @protected
         * @method merge
         */
        this.merge = function(dest, src, recursively) {
            if (arguments.length < 3) {
                recursively = true;
            }

            for (var k in src) {
                if (src.hasOwnProperty(k)) {
                    var sv = src [k],
                        dv = dest[k];

                    if (this.usePropertySetters === true) {
                        var m = getPropertySetter(dest, k);
                        if (m !== null) {
                            this.$assignProperty(dest, m, sv);
                            continue;
                        }
                    }

                    if (isAtomic(dv) || Array.isArray(dv) ||
                        isAtomic(sv) || Array.isArray(sv) ||
                        sv.clazz !== undefined              )
                    {
                        this.$assignValue(dest, k, sv);
                    } else if (recursively === true) {
                        if (dv !== null && dv !== undefined && dv.clazz !== undefined && dv.clazz.mergeable === false) {
                            this.$assignValue(dest, k, sv);
                        } else {
                            this.merge(dv, sv);
                        }
                    }
                }
            }
            return dest;
        };

        this.mixin = function(dest, src) {
            if (src instanceof DoIt) {
                var $this = this;
                this.$runner.then(src.then(function(src) {
                    for (var k in src) {
                        if (src.hasOwnProperty(k) && (dest[k] === undefined || dest[k] === null)) {
                            $this.$assignValue(dest, k, src[k]);
                        }
                    }
                }));
            } else {
                for (var k in src) {
                    if (src.hasOwnProperty(k) && (dest[k] === undefined || dest[k] === null)) {
                        this.$assignValue(dest, k, src[k]);
                    }
                }
            }
        };

        /**
         * Called every time the given class name has to be transformed into
         * the class object (constructor) reference. The method checks if the given class name
         * is alias that is mapped with the zson to a class.
         * @param  {String} className a class name
         * @return {Function} a class reference
         * @method resolveClass
         * @protected
         */
        this.resolveClass = function(className) {
            return this.classAliases.hasOwnProperty(className) ? this.classAliases[className]
                                                               : Class.forName(className);
        };

        /**
         * Adds class aliases
         * @param {Object} aliases dictionary where key is a class alias that can be referenced
         * from JSON and the value is class itself (constructor)
         * @method  addClassAliases
         */
        this.addClassAliases = function(aliases) {
            for(var k in aliases) {
                this.classAliases[k] = Class.forName(aliases[k].trim());
            }
        };

        this.expr = function(expr) {
            if (expr.length > 300) {
                throw new Error("Out of evaluated script limit");
            }

            return eval("'use strict';" + expr);
        };

        /**
         * Load and parse the given JSON content.
         * @param  {String|Object} json a JSON content. It can be:
         *    - **String**
         *       - JSON string
         *       - URL to a JSON
         *    - **Object** JavaScript object
         * @return {zebkit.DoIt} a reference to the runner
         * @method then
         * @example
         *
         *     // load JSON in zson from a remote site asynchronously
         *     new zebkit.Zson().then("http://test.com/test.json", function(zson) {
         *             // zson is loaded and ready for use
         *             zson.get("a.c");
         *         }
         *     ).catch(function(error) {
         *         // handle error
         *         ...
         *     });
         */
        this.then = function(json, fn) {
            if (json === null || json === undefined || (isString(json) && json.trim().length === 0)) {
                throw new Error("Null content");
            }

            this.$runner = new DoIt();

            var $this = this;
            this.$runner.then(function() {
                if (isString(json)) {
                    json = json.trim();

                    // detect if the passed string is not a JSON, but URL
                    if ((json[0] !== '[' || json[json.length - 1] !== ']') &&
                        (json[0] !== '{' || json[json.length - 1] !== '}')   )
                    {
                        $this.$variables = $this.$qsToVars(json);

                        $this.uri = json;

                        if ($this.cacheBusting === false) {
                            $this.uri = $this.uri + (json.lastIndexOf("?") > 0 ? "&" : "?") + (new Date()).getTime().toString();
                        }

                        var join = this.join();
                        ZFS.GET($this.uri).then(function(r) {
                            join.call($this, r.responseText);
                        }).catch(function(e) {
                            $this.$runner.error(e);
                        });
                    } else {
                        return json;
                    }
                } else {
                    return json;
                }
            }).then(function(json) { // populate JSON content
                if (isString(json)) {
                    try {
                        if ($this.uri !== null && typeof jsyaml !== 'undefined') {
                            var uri = new URI($this.uri);
                            if (uri.path !== null && uri.path.toLowerCase().indexOf(".yaml") === uri.path.length - 5) {
                                $this.content = jsyaml.load(json.trim());
                            }
                        }

                        if ($this.content === null) {
                            $this.content = $zenv.parseJSON(json.trim());
                        }
                    } catch(e) {
                        throw new Error("JSON format error: " + e);
                    }
                } else {
                    $this.content = json;
                }

                $this.$assignValue($this, "content", $this.buildValue($this.content));
            }).then(function() {
                if ($this.root !== null) {
                    $this.merge($this.root, $this.content);
                } else {
                    $this.root = $this.content;
                }

                return $this;
            });

            if (typeof $this.completed === 'function') {
                this.$runner.then(function() {
                    $this.completed.call($this);
                    return $this;
                });
            }

            if (arguments.length > 1) {
                this.$runner.then(fn);
            }

            return this.$runner;
        };
    }
]);

$export({ "Zson" : Zson } );
/**
 *  Finds an item by xpath-like simplified expression applied to a tree-like structure.
 *  Passed tree-like structure doesn't have a special requirements except every item of
 *  the structure have to define its kids by exposing "kids" field. The field is array
 *  of children elements:
 *
 *      // example of tree-like structure
 *      var treeLikeRoot = {
 *          value : "Root",
 *          kids : [
 *              { value: "Item 1" },
 *              { value: "Item 2" }
 *          ]
 *      };
 *
 *      zebkit.findInTree(treeLikeRoot,
 *          "/item1",
 *          function(foundElement) {
 *             ...
 *             // returning true means stop lookup
 *             return true;
 *          },
 *          function(item, fragment) {
 *              return item.value === fragment;
 *          });
 *
 *
 * The find method traverse the tree-like structure according to the xpath-like
 * expression. To understand if the given tree item confronts with the currently
 * traversing path fragment a special equality method has to be passed. The method
 * gets the traversing tree item and a string path fragment. The method has to
 * decide if the given tree item complies the specified path fragment.
 *
 * @param  {Object} root a tree root element. If the element has a children elements
 * the children have to be stored in "kids" field as an array.
 * @param  {String}  path a path-like expression. The path has to satisfy number of
 * requirements:
 *
 *   - has to start with "." or "/" or "//" character
 *   - has to define path part after "/" or "//"
 *   - path part can be either "*" or a name
 *   - the last path that starts from '@' character is considered as an attribute
 *     value requester In this case an attribute value will be returned.
 *   - optionally an attribute or/and its value can be defined as "[@<attr_name>=<attr_value>]"
 *   - attribute value is optional and can be boolean (true or false), integer, null
 *     or string value
 *   - string attribute value has to be wrapped with single quotes
 *
 *
 * For examples:
 *
 *   - "//*" traverse all tree elements
 *   - "//*[@a=10]" traverse all tree elements that has an attribute "a" that equals 10
 *   - "//*[@a]" traverse all tree elements that has an attribute "a" defined
 *   - "/Item1/Item2" find an element by exact path
 *   - ".//" traverse all tree elements including the root element
 *   - "./Item1/@k" value of property 'k' for a tree node found with "./Item1" path
 *
 * @param  {Function} cb callback function that is called every time a new tree element
 * matches the given path fragment. The function has to return true if the tree look up
 * has to be interrupted
 * @param  {Function}  [eq]  an equality function. The function gets current evaluated
 * tree element and a path fragment against which the tree element has to be evaluated.
 * It is expected the method returns boolean value to say if the given passed tree
 * element matches the path fragment. If the parameter is not passed or null then default
 * equality method is used. The default method expects a tree item has "path" field that
 * is matched with  given path fragment.
 * @method findInTree
 * @for  zebkit
 */

var PATH_RE = /^[.]?(\/[\/]?)([^\[\/]+)(\[\s*\@([a-zA-Z_][a-zA-Z0-9_\.]*)\s*(\=\s*[0-9]+|\=\s*true|\=\s*false|\=\s*null|\=\s*\'[^']*\')?\s*\])?/,
    DEF_EQ  =  function(n, fragment) { return n.value === fragment; };

function findInTree(root, path, cb, eq) {
    if (root === null || root === undefined) {
        throw new Error("Null tree root");
    }

    path = path.trim();
    if (path[0] === '#') {  // id shortcut
        path = "//*[@id='" + path.substring(1).trim() + "']";
    } else if (path === '.') { // current node shortcut
        return cb.call(root, root);
    } else if (path[0] === '.' && path[1] === '/') { // means we have to include root in search
        if (path[2] !== '@') {
            root = { kids: [ root ] };
        }
        path = path.substring(1);
    }

    // no match method has been defined, let use default method
    // to match the given node to the current path fragment
    if (eq === null || arguments.length < 4) {  // check null first for perf.
        eq = DEF_EQ;
    }

    return $findInTree(root, path, cb, eq, null);
}

function $findInTree(root, path, cb, eq, m) {
    if (path[0] === '/' && path[1] === '/' && path[2] === '@') {
        path = "//*" + path.substring(1);
    }

    var pathValue,
        pv         = undefined,
        isTerminal = false;

    if (path[0] === '/' && path[1] === '@') {
        if (m === null || m[0].length !== m.input.length) {
            m = path.match(PATH_RE);

            if (m === null) {
                throw new Error("Cannot resolve path '" + path + "'");
            }

            // check if the matched path is not terminal
            if (m[0].length !== path.length) {
                path = path.substring(m[0].length);  // cut found fragment from the path
            }
        }

        pathValue = m[2].trim();
        if (pathValue[1] === '{') {
            if (pathValue[pathValue.length - 1] !== '}') {
                throw new Error("Invalid properties aggregation expression '" + pathValue + "'");
            }

            pv = {};
            var names = pathValue.substring(2, pathValue.length - 1).split(',');
            for (var ni = 0; ni < names.length; ni++) {
                var name = names[ni].trim();
                pv[name] = getPropertyValue(root, name, true);
            }
        } else {
            pv = getPropertyValue(root, pathValue.substring(1), true);
        }

        if (m[0].length === m.input.length) {  // terminal path
            if (pv !== undefined && cb.call(root, pv) === true) {
                return true;
            }
        } else {
            if (isAtomic(pv)) {
                throw new Error("Atomic typed node cannot be traversed");
            } else if (pv !== null && pv !== undefined) {
                if ($findInTree(pv, path, cb, eq, m) === true) {
                    return true;
                }
            }
        }
    } else if (root.kids !== undefined &&   // a node has children
               root.kids !== null      &&
               root.kids.length > 0       ) {

        var ppath = path;
        //
        // m == null                      : means this is the first call of the method
        // m[0].length !== m.input.length : means this is terminal part of the path
        //
        if (m === null || m[0].length !== m.input.length) {
            m = path.match(PATH_RE);

            if (m === null) {
                throw new Error("Cannot resolve path '" + path + "'");
            }

            // check if the matched path is not terminal
            if (m[0].length !== path.length) {
                path = path.substring(m[0].length);  // cut found fragment from the path
            }

            // normalize attribute value
            if (m[3] !== undefined && m[5] !== undefined) {
                m[5] = m[5].substring(1).trim();

                if (m[5][0] === "'") {
                    m[5] = m[5].substring(1, m[5].length - 1);
                } else if (m[5] === "true") {
                    m[5] = true;
                } else if (m[5] === "false") {
                    m[5] = false;
                } else if (m[5] === "null") {
                    m[5] = null;
                } else {
                    var vv = parseInt(m[5], 10);
                    if (isNaN(vv) === false) {
                        m[5] = vv;
                    }
                }
            }
        }

        if (m[0].length === m.input.length) {
            isTerminal = true;
        }
        pathValue = m[2].trim();

        // traverse root kid nodes
        for (var i = 0; i < root.kids.length ; i++) {
            var kid     = root.kids[i],
                isMatch = false;
                                        // XOR
            if (pathValue === "*" || (eq(kid, pathValue) ? pathValue[0] !== '!' : pathValue[0] === '!') === true) {
                if (m[3] !== undefined) { // has attributes
                    var attrName = m[4].trim();

                    // leave if attribute doesn't match
                    if (kid[attrName] !== undefined && (m[5] === undefined || kid[attrName] === m[5])) {
                        isMatch = true;
                    }
                } else {
                    isMatch = true;
                }
            }

            if (isTerminal === true) {
                // node match then call callback and leave if the callback says to do it
                if (isMatch === true) {
                    if (cb.call(root, kid) === true) {
                        return true;
                    }
                }

                if (m[1] === "//") {
                    if ($findInTree(kid, path, cb, eq, m) === true) {
                       return true;
                    }
                }
            } else {
                // not a terminal and match, then traverse kid
                if (isMatch === true) {
                    if ($findInTree(kid, path, cb, eq, m) === true) {
                        return true;
                    }
                }

                // not a terminal and recursive traversing then do it
                // with previous path
                if (m[1] === "//") {
                    if ($findInTree(kid, ppath, cb, eq, m) === true) {
                        return true;
                    }
                }
            }
        }
    }

    return false;
}

/**
 * Interface that provides path search functionality for a tree-like structure.
 * @class  zebkit.PathSearch
 * @interface zebkit.PathSearch
 */
var PathSearch = Interface([
    function $prototype() {
        /**
         *  Method to match two element in tree.
         *  @protected
         *  @attribute $matchPath
         *  @type {Function}
         */
         this.$matchPath = null;

        /**
         * Find children items or values with the passed path expression.
         * @param  {String} path path expression. Path expression is simplified form
         * of XPath-like expression. See  {{#crossLink "findInTree"}}findInTree{{/crossLink}}
         * method to get more details.
         *
         * @param {Function} [cb] function that is called every time a new children
         * component has been found. If callback has not been passed then the method
         * return first found item or null. If the callback has been passed as null
         * then all found elements will be returned as array.
         * @method byPath
         * @return {Object} found children item/property value or null if no children
         * items were found
         */
        this.byPath = function(path, cb) {
            if (arguments.length === 2) {
                if (arguments[1] === null) {
                    var r = [];
                    findInTree(this, path, function(n) {
                        r.push(n);
                        return false;
                    }, this.$matchPath !== null ? this.$matchPath
                                                : null);
                    return r;
                } else {
                    findInTree(this, path, cb, this.$matchPath !== null ? this.$matchPath
                                                                        : null);
                }
            } else {
                var res = null;
                findInTree(this, path, function(n) {
                    res = n;
                    return true;
                }, this.$matchPath !== null ? this.$matchPath : null);
                return res;
            }
        };
    }
]);

$export(findInTree, { "PathSearch": PathSearch } );
/**
 * Abstract event class.
 * @class zebkit.Event
 * @constructor
 */
var Event = Class([
    function $prototype() {
        /**
         * Source of an event
         * @attribute source
         * @type {Object}
         * @default null
         * @readOnly
         */
        this.source = null;
    }
]);

/**
 * This method allows to declare a listeners container class for the given
 * dedicated event types.
 *
 *     // create listener container to keep three different events
 *     // handlers
 *     var MyListenerContainerClass = zebkit.ListenersClass("event1",
 *                                                          "event2",
 *                                                          "event3");
 *     // instantiate listener class container
 *     var listeners = new MyListenerContainerClass();
 *
 *     // add "event1" listener
 *     listeners.add(function event1() {
 *         ...
 *     });
 *
 *     // add "event2" listener
 *     listeners.add(function event2() {
 *        ...
 *     });
 *
 *     // add listener for both event1 and event2 events
 *     listeners.add(function() {
 *        ...
 *     });
 *
 *     // and firing event1 to registered handlers
 *     listeners.event1(...);
 *
 *     // and firing event2 to registered handlers
 *     listeners.event2(...);
 *
 * @for zebkit
 * @method ListenersClass
 * @param {String} [events]* events types the listeners container has to support
 * @return {zebkit.Listener} a listener container class
 */
var $NewListener = function() {
    var clazz = function() {};
    clazz.eventNames = arguments.length === 0 ? [ "fired" ]
                                              : Array.prototype.slice.call(arguments);

    clazz.ListenersClass = function() {
        var args = this.eventNames.slice(); // clone
        for(var i = 0; i < arguments.length; i++) {
            args.push(arguments[i]);
        }
        return $NewListener.apply(this, args);
    };

    if (clazz.eventNames.length === 1) {
        var $ename = clazz.eventNames[0];

        clazz.prototype.v = null;

        clazz.prototype.add = function() {
            var ctx = this,
                l   = arguments[arguments.length - 1]; // last arguments are handler(s)

            if (typeof l !== 'function') {
                ctx = l;
                l   = l[$ename];

                if (typeof l !== "function") {
                    return null;
                }
            }

            if (arguments.length > 1 && arguments[0] !== $ename) {
                throw new Error("Unknown event type :" + $ename);
            }

            if (this.v === null) {
                this.v = [];
            }

            this.v.push(ctx, l);
            return l;
        };

        clazz.prototype.remove = function(l) {
            if (this.v !== null) {
                if (arguments.length === 0) {
                    // remove all
                    this.v.length = 0;
                } else {
                    var name = arguments.length > 1 || zebkit.isString(arguments[0]) ? arguments[0]
                                                                                     : null,
                        fn   = arguments.length > 1 ? arguments[1]
                                                    : (name === null ? arguments[0] : null),
                        i    = 0;

                    if (name !== null && name !== $ename) {
                        throw new Error("Unknown event type :" + name);
                    }

                    if (fn === null) {
                        this.v.length = 0;
                    } else {
                        while ((i = this.v.indexOf(fn)) >= 0) {
                            if (i % 2 > 0) {
                                i--;
                            }
                            this.v.splice(i, 2);
                        }
                    }
                }
            }
        };

        clazz.prototype.hasHandler = function(l) {
            if (zebkit.isString(l)) {
                return this.v !== null && l === $ename && this.v.length > 0;
            } else {
                return this.v.length > 0 && this.v.indexOf(l) >= 0;
            }
        };

        clazz.prototype[$ename] = function() {
            if (this.v !== null) {
                for (var i = 0; i < this.v.length; i += 2) {
                    if (this.v[i + 1].apply(this.v[i], arguments) === true) {
                        return true;
                    }
                }
            }
            return false;
        };

        clazz.prototype.hasEvent = function(nm) {
            return nm === $ename;
        };
    } else {
        var names = {};
        for(var i = 0; i < clazz.eventNames.length; i++) {
            names[clazz.eventNames[i]] = true;
        }

        clazz.prototype.$methods = null;

        clazz.prototype.add = function(l) {
            if (this.$methods === null) {
                this.$methods = {};
            }

            var n   = null,
                k   = null,
                nms = this.$names !== undefined ? this.$names : names;

            if (arguments.length > 1) {
                n = arguments[0];
                l = arguments[arguments.length - 1]; // last arguments are handler(s)
            }

            if (typeof l === 'function') {
                if (n !== null && nms[n] === undefined) {
                    throw new Error("Unknown event type " + n);
                }

                if (n === null) {
                    for(k in nms) {
                        if (this.$methods[k] === undefined) {
                            this.$methods[k] = [];
                        }
                        this.$methods[k].push(this, l);
                    }
                } else {
                    if (this.$methods[n] === undefined) {
                        this.$methods[n] = [];
                    }
                    this.$methods[n].push(this, l);
                }
            } else {
                var b = false;
                for (k in nms) {
                    if (typeof l[k] === "function") {
                        b = true;
                        if (this.$methods[k] === undefined) {
                            this.$methods[k] = [];
                        }
                        this.$methods[k].push(l, l[k]);
                    }
                }

                if (b === false) {
                    return null;
                }
            }
            return l;
        };

        clazz.prototype.hasHandler = function(l) {
            if (zebkit.isString(l)) {
                return this.$methods !== null &&
                       this.$methods.hasOwnProperty(l) &&
                       this.$methods[l].length > 0;
            } else {
                for(var k in this.$methods) {
                    var v = this.$methods[k];
                    if (v.indexOf(l) >= 0) {
                        return true;
                    }
                }
                return false;
            }
        };

        clazz.prototype.addEvents = function() {
            if (this.$names === undefined) {
                this.$names = {};
                for (var k in names) {
                    this.$names[k] = names[k];
                }
            }

            for(var i = 0; i < arguments.length; i++) {
                var name = arguments[i];

                if (name === null || name === undefined || this[name] !== undefined) {
                    throw new Error("Invalid " + name + " (event name)");
                }

                this[name] = (function(name) {
                    return function() {
                        // typeof is faster then hasOwnProperty under nodejs
                        if (this.$methods !== null && this.$methods[name] !== undefined) {
                            var c = this.$methods[name];
                            for(var i = 0; i < c.length; i += 2) {
                                if (c[i + 1].apply(c[i], arguments) === true) {
                                    return true;
                                }
                            }
                        }
                        return false;
                    };
                })(name);

                this.$names[name] = true;
            }
        };

        // populate methods that has to be called to send appropriate events to
        // registered listeners
        clazz.prototype.addEvents.apply(clazz.prototype, clazz.eventNames);

        clazz.prototype.remove = function() {
            if (this.$methods !== null) {
                var k = null;
                if (arguments.length === 0) {
                    for(k in this.$methods) {
                        if (this.$methods[k] !== undefined) {
                            this.$methods[k].length = 0;
                        }
                    }
                    this.$methods = {};
                } else {
                    var name = arguments.length > 1 || zebkit.isString(arguments[0]) ? arguments[0]
                                                                                     : null,
                        fn   = arguments.length > 1 ? arguments[1]
                                                    : (name === null ? arguments[0] : null),
                        i    = 0,
                        v    = null;

                    if (name !== null) {
                        if (this.$methods[name] !== undefined) {
                            if (fn === null) {
                                this.$methods[name].length = 0;
                                delete this.$methods[name];
                            } else {
                                v = this.$methods[name];
                                while ((i = v.indexOf(fn)) >= 0) {
                                    if (i % 2 > 0) {
                                        i--;
                                    }
                                    v.splice(i, 2);
                                }

                                if (v.length === 0) {
                                    delete this.$methods[name];
                                }
                            }
                        }
                    } else {
                        for (k in this.$methods) {
                            v = this.$methods[k];
                            while ((i = v.indexOf(fn)) >= 0) {
                                if (i % 2 > 0) {
                                    i--;
                                }
                                v.splice(i, 2);
                            }

                            if (v.length === 0) {
                                delete this.$methods[k];
                            }
                        }
                    }
                }
            }
        };

        clazz.prototype.hasEvent = function(nm) {
            return (this.$names !== undefined && this.$names[nm] !== undefined) || names[nm] !== undefined;
        };
    }

    return clazz;
};

/**
 * Listeners container class that can be handy to store number of listeners
 * for one type of event.
 * @param {String} [eventName] an event name the listeners container has been
 * created. By default "fired" is default event name. Event name is used to fire
 * the given event to a listener container.
 * @constructor
 * @class zebkit.Listeners
 * @example
 *
 *      // create container with a default event name
 *      var  container = new Listeners();
 *
 *      // register a listener
 *      var  listener = container.add(function(param1, param2) {
 *          // handle fired event
 *      });
 *
 *      ...
 *      // fire event
 *      container.fired(1, 2, 3);
 *
 *      // remove listener
 *      container.remove(listener);
 *
 * @extends zebkit.Listener
 */


/**
 * Add listener
 * @param {Function|Object} l a listener method or object.
 * @return {Function} a listener that has been registered in the container. The result should
 * be used to un-register the listener
 * @method  add
 */


/**
 * Remove listener or all registered listeners from the container
 * @param {Function} [l] a listener to be removed. If the argument has not been specified
 * all registered in the container listeners will be removed
 * @method  remove
 */
var Listeners = $NewListener();

/**
 * Event producer interface. This interface provides number of methods
 * to register, un-register, fire events. It follows on/off notion like
 * JQuery does it. It is expected an event producer class implementation
 * has a special field  "_" that keeps listeners.
 *
 *     var MyClass = zebkit.Class(zebkit.EventProducer, [
 *         function() {
 *             // "fired" events listeners container
 *             this._ = new zebkit.Listeners();
 *         }
 *     ]);
 *
 *     var a = new MyClass();
 *     a.on("fired", function(arg) {
 *         // handle "fired" events
 *     });
 *
 *     a.fire(10);
 *
 * @class zebkit.EventProducer
 * @interface zebkit.EventProducer
 */
var EventProducer = Interface([
    function $prototype() {
        // on(event, path, cb)  handle the given event for all elements identified with the path
        // on(cb)               handle all events
        // on(path | event, cb) handle the given event or all events for elements matched with the path


        /**
         * Register listener for the given events types or/and the given nodes in tree-like
         * structure or listen all events types.
         * @param {String} [eventName] an event type name to listen. If the event name is not passed
         * then listen all events types.
         * @param {String} [path] a xpath-like path to traversing elements in tree and register event
         * handlers for the found elements. The parameter can be used if the interface is implemented
         * with tree-like structure (for instance zebkit UI components).
         * @param {Function|Object} cb a listener method or an object that contains number of methods
         * to listen the specified events types.
         * @example
         *     var comp = new zebkit.ui.Panel();
         *     comp.add(new zebkit.ui.Button("Test 1").setId("c1"));
         *     comp.add(new zebkit.ui.Button("Test 2").setId("c2"));
         *     ...
         *     // register event handler for children components of "comp"
         *     comp.on("/*", function() {
         *         // handle button fired event
         *         ...
         *     });
         *
         *     // register event handler for button component with id equals "c1"
         *     comp.on("#c1", function() {
         *         // handle button fired event
         *         ...
         *     });
         *
         * @method on
         */
        this.on = function() {
            var cb = arguments[arguments.length - 1],  // callback or object
                pt = null,                             // path
                nm = null;                             // event name

            if (cb === null || (typeof cb === "string" || cb.constructor === String)) {
                throw new Error("Invalid event handler");
            }

            if (arguments.length === 1) {
                if (this._ === undefined) {
                    if (this.clazz.Listeners !== undefined) {
                        this._ = new this.clazz.Listeners();
                    } else {
                        return false;
                    }
                }
                return this._.add(cb);
            } else if (arguments.length === 2) {
                if (arguments[0] === null) {
                    throw new Error("Invalid event or path");
                } else if (arguments[0][0] === '.' || arguments[0][0] === '/' || arguments[0][0] === '#') { // a path detected
                    pt = arguments[0];
                } else {
                    if (this._ === undefined) {
                        if (this.clazz.Listeners !== undefined) {
                            this._ = new this.clazz.Listeners();
                        } else {
                            return false;
                        }
                    }
                    return this._.add(arguments[0], cb);
                }
            } else if (arguments.length === 3) {
                pt = arguments[1];
                nm = arguments[0];
                if (pt === null) {
                    if (this._ === undefined) {
                        if (this.clazz.Listeners !== undefined) {
                            this._ = new this.clazz.Listeners();
                        } else {
                            return false;
                        }
                    }
                    return this._.add(nm, cb);
                }
            }

            if (instanceOf(this, PathSearch) === false) {
                throw new Error("Path search is not supported");
            }

            this.byPath(pt, function(node) {
                // try to initiate
                if (node._ === undefined && node.clazz.Listeners !== undefined) {
                    node._ = new node.clazz.Listeners();
                }

                if (node._ !== undefined) {
                    if (nm !== null) {
                        if (node._[nm] !== undefined) {
                            node._.add(nm, cb);
                        }
                    } else {
                        node._.add(cb);
                    }
                }
                return false;
            });

            return cb;
        };

        // off()            remove all events handler
        // off(event)       remove the event handler
        // off(event, path)  remove the event handler for all nodes detected with the path
        // off(path)
        // off(cb)
        // off(path, cb)
        //
        /**
         * Stop listening the given event type.
         * @param {String} [eventName] an event type name to stop listening. If the event name is not passed
         * then stop listening all events types.
         * @param {String} [path] a xpath-like path to traversing elements in tree and stop listening
         * the event type for the found in the tree elements. The parameter can be used if the interface
         * is implemented with tree-like structure (for instance zebkit UI components).
         * @param [cb] remove the given event handler.
         * @method off
         */
        this.off = function() {
            var pt = null,  // path
                fn = null,  // handler
                nm = null;  // event name or listener

            if (arguments.length === 0) {
                if (this._ !== undefined) {
                    return this._.remove();
                } else {
                    return;
                }
            } else if (arguments.length === 1) {
                if (isString(arguments[0]) && (arguments[0][0] === '.' || arguments[0][0] === '/' || arguments[0][0] === '#')) {
                    pt = arguments[0];
                } else {
                    if (this._ !== undefined) {
                        return this._.remove(arguments[0]);
                    } else {
                        return;
                    }
                }
            } else if (arguments.length === 2) {
                if (isString(arguments[1])) { // detect path
                    pt = arguments[1];
                    nm = arguments[0];
                } else {
                    if (isString(arguments[1])) {
                        nm = arguments[1];
                    } else {
                        fn = arguments[1];
                    }

                    if (arguments[0][0] === '.' || arguments[0][0] === '/' || arguments[0][0] === '#') {
                        pt = arguments[0];
                    } else {
                        throw new Error("Path is expected");
                    }
                }
            }

            this.byPath(pt, function(node) {
                if (node._ !== undefined) {
                    if (fn !== null) {
                        node._.remove(fn);
                    } else if (nm !== null) {
                        if (node._[nm] !== undefined) {
                            node._.remove(nm);
                        }
                    } else {
                        node._.remove();
                    }
                }
                return false;
            });
        };

        /**
         * Fire event with the given parameters.
         * @param {String} name an event name
         * @param {String} [path]  a path if the event has to be send to multiple destination in the tree
         * @param {Object|Array}  [params] array of parameters or single parameter to be passed to an event
         * handler or handlers.
         * @method fire
         */
        this.fire = function(name) {
            if (arguments.length > 0 && arguments.length < 3) {
                if (this._ !== undefined) {
                    if (this._.hasEvent(name) === false) {
                        throw new Error("Listener doesn't support '" + name + "' event");
                    }

                    if (arguments.length === 2) {
                        Array.isArray(arguments[1]) ? this._[name].apply(this._, arguments[1])
                                                    : this._[name].call(this._, arguments[1]);
                    } else {
                        this._[name].call(this._);
                    }
                }
            } else if (arguments.length === 3) {
                var args = arguments[2];
                this.byPath(arguments[1], function(n) {
                    if (n._ !== undefined && n._.hasEvent(name)) {
                        var ec = n._;
                        if (args !== null && Array.isArray(args)) {
                            ec[name].apply(ec, args);
                        } else {
                            ec[name].call(ec, args);
                        }
                    }
                    return false;
                });
            } else {
                throw new Error("Invalid number of arguments");
            }
        };
    }
]);

// class instance method
classTemplateProto.isEventFired = function(name) {
    if (this.clazz.Listeners === undefined) {
        return false;
    }

    if (arguments.length === 0) {
        name = "fired";
    }

    var names = this.clazz.Listeners.eventNames;
    if (names.length === 1) {
        return names[0] === name;
    }

    for(var i = 0; i < names.length; i++) {
        if (names[i] === name) {
            return true;
        }
    }
    return false;
};

/**
 * Extends zebkit.Class with the given events support.
 * @param {String} [args]* list of events names
 * @method events
 * @for  zebkit.Class
 */
classTemplateFields.events = function() {
    if (arguments.length === 0) {
        throw new Error("No an event name was found");
    }

    var args = Array.prototype.slice.call(arguments),
        c    = args.length;

    // collect events the class already declared
    if (this.Listeners !== undefined) {
        for (var i = 0; i < this.Listeners.eventNames.length; i++) {
            var en = this.Listeners.eventNames[i];
            if (args.indexOf(en) < 0) {
                args.push(en);
            }
        }
    }

    if (this.Listeners === undefined || c !== args.length) {
        this.Listeners = $NewListener.apply($NewListener, args);
    }

    if (this.isInherit(EventProducer) === false) {
        this.extend(EventProducer);
    }

    return this;
};


$export({
    "Event"          : Event,
    "Listeners"      : Listeners,
    "ListenersClass" : $NewListener,
    "EventProducer"  : EventProducer
});
/**
 * This class represents a font and provides basic font metrics like height, ascent. Using
 * the class developers can compute string width.
 *
 *     // plain font
 *     var f = new zebkit.Font("Arial", 14);
 *
 *     // bold font
 *     var f = new zebkit.Font("Arial", "bold", 14);
 *
 *     // defining font with CSS font name
 *     var f = new zebkit.Font("100px Futura, Helvetica, sans-serif");
 *
 * @constructor
 * @param {String} name a name of the font. If size and style parameters has not been passed
 * the name is considered as CSS font name that includes size and style
 * @param {String} [style] a style of the font: "bold", "italic", etc
 * @param {Integer} [size] a size of the font
 * @class zebkit.Font
 */
var Font = Class([
    function(family, style, size) {
        if (arguments.length === 1) {
            this.size = this.clazz.decodeSize(family);
            if (this.size === null) {
                // trim
                family = family.trim();

                // check if a predefined style has been used
                if (family === "bold" || family === "italic") {
                    this.style = family;
                } else {  // otherwise handle it as CSS-like font style
                    // try to parse font if possible
                    var re = /([a-zA-Z_\- ]+)?(([0-9]+px|[0-9]+em)\s+([,\"'a-zA-Z_ \-]+))?/,
                        m  = family.match(re);

                    if (m[4] !== undefined) {
                        this.family = m[4].trim();
                    }

                    if (m[3] !== undefined) {
                        this.size = m[3].trim();
                    }

                    if (m[1] !== undefined) {
                        this.style = m[1].trim();
                    }

                    this.s = family;
                }
            }
        } else if (arguments.length === 2) {
            this.family = family;
            this.size   = this.clazz.decodeSize(style);
            this.style  = this.size === null ? style : null;
        } else if (arguments.length === 3) {
            this.family = family;
            this.style  = style;
            this.size   = this.clazz.decodeSize(size);
        }

        if (this.size === null) {
            this.size = this.clazz.size + "px";
        }

        if (this.s === null) {
            this.s = ((this.style !== null) ? this.style + " ": "") +
                     this.size + " " +
                     this.family;
        }

        var mt = $zenv.fontMetrics(this.s);

        /**
         * Height of the font
         * @attribute height
         * @readOnly
         * @type {Integer}
         */
        this.height = mt.height;

        /**
         * Ascent of the font
         * @attribute ascent
         * @readOnly
         * @type {Integer}
         */
        this.ascent = mt.ascent;
    },

    function $clazz() {

        // default values
        this.family = "Arial, Helvetica";
        this.style  =  null;
        this.size   =  14;

        this.mergeable = false;

        this.decodeSize = function(s, defaultSize) {
            if (arguments.length < 2) {
                defaultSize = this.size;
            }

            if (typeof s === "string" || s.constructor === String) {
                var size = Number(s);
                if (isNaN(size)) {
                    var m = s.match(/^([0-9]+)(%)$/);
                    if (m !== null && m[1] !== undefined && m[2] !== undefined) {
                        size = Math.floor((defaultSize * parseInt(m[1], 10)) / 100);
                        return size + "px";
                    } else {
                        return /^([0-9]+)(em|px)$/.test(s) === true ? s : null;
                    }
                } else {
                    if (s[0] === '+') {
                        size = defaultSize + size;
                    } else if (s[0] === '-') {
                        size = defaultSize - size;
                    }
                    return size + "px";
                }
            }
            return s === null ? null : s + "px";
        };
    },

    function $prototype(clazz) {
        this.s = null;

        /**
         *  Font family.
         *  @attribute family
         *  @type {String}
         *  @default null
         */
        this.family = clazz.family;

        /**
         *  Font style (for instance "bold").
         *  @attribute style
         *  @type {String}
         *  @default null
         */
        this.style = clazz.style;
        this.size  = clazz.size;

        /**
         * Returns CSS font representation
         * @return {String} a CSS representation of the given Font
         * @method toString
         * @for zebkit.Font
         */
        this.toString = function() {
            return this.s;
        };

        /**
         * Compute the given string width in pixels basing on the
         * font metrics.
         * @param  {String} s a string
         * @return {Integer} a string width
         * @method stringWidth
         */
        this.stringWidth = function(s) {
            if (s.length === 0) {
                return 0;
            } else {
                var fm = $zenv.fontMeasure;
                if (fm.font !== this.s) {
                    fm.font = this.s;
                }

                return Math.round(fm.measureText(s).width);
            }
        };

        /**
         * Calculate the specified substring width
         * @param  {String} s a string
         * @param  {Integer} off fist character index
         * @param  {Integer} len length of substring
         * @return {Integer} a substring size in pixels
         * @method charsWidth
         * @for zebkit.Font
         */
        this.charsWidth = function(s, off, len) {
            var fm = $zenv.fontMeasure;
            if (fm.font !== this.s) {
                fm.font = this.s;
            }

            return Math.round((fm.measureText(len === 1 ? s[off]
                                                        : s.substring(off, off + len))).width );
        };

        /**
         * Resize font and return new instance of font class with new size.
         * @param  {Integer | String} size can be specified in pixels as integer value or as
         * a percentage from the given font:
         * @return {zebkit.Font} a font
         * @for zebkit.Font
         * @method resize
         * @example
         *
         * ```javascript
         * var font = new zebkit.Font(10); // font 10 pixels
         * font = font.resize("200%"); // two times higher font
         * ```
         */
        this.resize = function(size) {
            var nsize = this.clazz.decodeSize(size, this.height);
            if (nsize === null) {
                throw new Error("Invalid font size : " + size);
            }
            return new this.clazz(this.family, this.style, nsize);
        };

        /**
         * Restyle font and return new instance of the font class
         * @param  {String} style a new style
         * @return {zebkit.Font} a font
         * @method restyle
         */
        this.restyle = function(style) {
            return new this.clazz(this.family, style, this.height + "px");
        };
    }
]);

function $font() {
    if (arguments.length === 1) {
        if (instanceOf(arguments[0], Font)) {
            return arguments[0];
        } if (Array.isArray(arguments[0])) {
            return Font.newInstance.apply(Font, arguments[0]);
        } else {
            return new Font(arguments[0]);
        }
    } else if (arguments.length > 1) {
        return Font.newInstance.apply(Font, arguments);
    } else {
        throw Error("No an argument has been defined");
    }
}

$export( { "Font" : Font }, $font );

function $ls(callback, all) {
    for (var k in this) {
        var v = this[k];
        if (this.hasOwnProperty(k) && (v instanceof Package) === false)  {
            if ((k[0] !== '$' && k[0] !== '_') || all === true) {
                if (callback.call(this, k, this[k]) === true) {
                    return true;
                }
            }
        }
    }
    return false;
}

function $lsall(fn) {
    return $ls.call(this, function(k, v) {
        if (v === undefined) {
            throw new Error(fn + "," + k);
        }
        if (v !== null && v.clazz === Class) {
            // class is detected, set the class name and ref to the class package
            if (v.$name === undefined) {
                v.$name = fn + k;
                v.$pkg  = getPropertyValue($global, fn.substring(0, fn.length - 1));

                if (v.$pkg === undefined) {
                    throw new ReferenceError(fn);
                }
            }
            return $lsall.call(v, v.$name + ".");
        }
    });
}

/**
 *  Package is a special class to declare zebkit packages. Global variable "zebkit" is
 *  root package for all other packages. To declare a new package use "zebkit" global
 *  variable:
 *
 *      // declare new "mypkg" package
 *      zebkit.package("mypkg", function(pkg, Class) {
 *          // put the package entities in
 *          pkg.packageVariable = 10;
 *          ...
 *      });
 *      ...
 *
 *      // now we can access package and its entities directly
 *      zebkit.mypkg.packageVariable
 *
 *      // or it is preferable to wrap a package access with "require"
 *      // method
 *      zebkit.require("mypkg", function(mypkg) {
 *          mypkg.packageVariable
 *      });
 *
 *  @class zebkit.Package
 *  @constructor
 */
function Package(name, parent) {
    /**
     * URL the package has been loaded
     * @attribute $url
     * @readOnly
     * @type {String}
     */
    this.$url = null;

    /**
     * Name of the package
     * @attribute $name
     * @readOnly
     * @type {String}
     */
    this.$name = name;

    /**
     * Package configuration parameters.
     * @attribute $config
     * @readOnly
     * @private
     * @type {Object}
     */
    this.$config = {};

    this.$ready = new DoIt();

    /**
     * Reference to a parent package
     * @attribute $parent
     * @private
     * @type {zebkit.Package}
     */
    this.$parent = arguments.length < 2 ? null : parent;
}

/**
 * Get or set configuration parameter.
 * @param {String} [name] a parameter name.
 * @param {Object} [value] a parameter value.
 * @param {Boolean} [overwrite] boolean flag that indicates if the
 * parameters value have to be overwritten if it exists
 * @method  config
 */
Package.prototype.config = function(name, value, overwrite) {
    if (arguments.length === 0) {
        return this.$config;
    } else if (arguments.length === 1 && isString(arguments[0])) {
        return this.$config[name];
    } else  {
        if (isString(arguments[0])) {
            var old = this.$config[name];
            if (value === undefined) {
                delete this.$config[name];
            } else if (arguments.length < 3 || overwrite === true) {
                this.$config[name] = value;
            } else if (this.$config.hasOwnProperty(name) === false) {
                this.$config[name] = value;
            }
            return old;
        } else {
            overwrite = arguments.length > 1 ? value : false;
            for (var k in arguments[0]) {
                this.config(k, arguments[0][k], overwrite);
            }
        }
    }
};

/**
 * Detect the package location and store the location into "$url"
 * package field
 * @private
 * @method $detectLocation
 */
Package.prototype.$detectLocation = function() {
    if (typeof __dirname !== 'undefined') {
        this.$url = __dirname;
    } else if (typeof document !== "undefined") {
        //
        var s  = document.getElementsByTagName('script'),
            ss = s[s.length - 1].getAttribute('src'),
            i  = ss === null ? -1 : ss.lastIndexOf("/"),
            a  = document.createElement('a');

        a.href = (i > 0) ? ss.substring(0, i + 1)
                         : document.location.toString();

        this.$url = a.href.toString();
    }
};

/**
 * Get full name of the package. Full name includes not the only the given
 * package name, but also all parent packages separated with "." character.
 * @return {String} a full package name
 * @method fullname
 */
Package.prototype.fullname = function() {
    var n = [ this.$name ], p = this;
    while (p.$parent !== null) {
        p = p.$parent;
        n.unshift(p.$name);
    }
    return n.join(".");
};

/**
 * Find a package with the given file like path relatively to the given package.
 * @param {String} path a file like path
 * @return {String} path a path
 * @example
 *
 *      // declare "zebkit.test" package
 *      zebkit.package("test", function(pkg, Class) {
 *          ...
 *      });
 *      ...
 *
 *      zebkit.require("test", function(test) {
 *          var parent = test.cd(".."); // parent points to zebkit package
 *          ...
 *      });
 *
 * @method cd
 */
Package.prototype.cd = function(path) {
    if (path[0] === '/') {
        path = path.substring(1);
    }

    var paths = path.split('/'),
        pk    = this;

    for (var i = 0; i < paths.length; i++) {
        var pn = paths[i];
        if (pn === "..") {
            pk = pk.$parent;
        } else {
            pk = pk[pn];
        }

        if (pk === undefined || pk === null) {
            throw new Error("Package path '" + path + "' cannot be resolved");
        }
    }

    return pk;
};

/**
 * List the package sub-packages.
 * @param  {Function} callback    callback function that gets a sub-package name and the
 * sub-package itself as its arguments
 * @param  {boolean}  [recursively]  indicates if sub-packages have to be traversed recursively
 * @method packages
 */
Package.prototype.packages = function(callback, recursively) {
    for (var k in this) {
        var v = this[k];
        if (k !== "$parent" && this.hasOwnProperty(k) && v instanceof Package) {

            if (callback.call(this, k, v) === true || (recursively === true && v.packages(callback, recursively) === true)) {
                return true;
            }
        }
    }
    return false;
};

/**
 * Get a package by the specified name.
 * @param  {String} name a package name
 * @return {zebkit.Package} a package
 * @method byName
 */
Package.prototype.byName = function(name) {
    if (this.fullname() === name) {
        return this;
    } else  {
        var i = name.indexOf('.');
        if (i > 0) {
            var vv = getPropertyValue(this, name.substring(i + 1), false);
            return vv === undefined ? null : vv;
        } else {
            return null;
        }
    }
};

/**
 * List classes, variables and interfaces defined in the given package.
 * If second parameter "all" passed to the method is false, the method
 * will skip package entities whose name starts from "$" or "_" character.
 * These entities are considered as private ones. Pay attention sub-packages
 * are not listed.
 * @param  {Function} cb a callback method that get the package entity key
 * and the entity value as arguments.
 * @param  {Boolean}  [all] flag that specifies if private entities are
 * should be listed.
 * @method ls
 */
Package.prototype.ls = function(cb, all) {
    return $ls.call(this, cb, all);
};

/**
 * Build import JS code string that can be evaluated in a local space to make visible
 * the given package or packages classes, variables and methods.
 * @example
 *
 *     (function() {
 *         // make visible variables, classes and methods declared in "zebkit.ui"
 *         // package in the method local space
 *         eval(zebkit.import("ui"));
 *
 *         // use imported from "zebkit.ui.Button" class without necessity to specify
 *         // full path to it
 *         var bt = new Button("Ok");
 *     })();
 *
 * @param {String} [pkgname]* names of packages to be imported
 * @return {String} an import string to be evaluated in a local JS space
 * @method  import
 * @deprecated Usage of the method has to be avoided. Use zebkit.require(...) instead.
 */
Package.prototype.import = function() {
    var code = [];
    if (arguments.length > 0) {
        for(var i = 0; i < arguments.length; i++) {
            var v = getPropertyValue(this, arguments[i]);
            if ((v instanceof Package) === false) {
                throw new Error("Package '" + arguments[i] + " ' cannot be found");
            }
            code.push(v.import());
        }

        return code.length > 0 ?  code.join(";") : null;
    } else {
        var fn = this.fullname();
        this.ls(function(k, v) {
            code.push(k + '=' + fn + '.' + k);
        });

        return code.length > 0 ?  "var " + code.join(",") + ";" : null;
    }
};

/**
 * This method has to be used to start building a zebkit application. It
 * expects a callback function where an application code has to be placed and
 * number of required for the application packages names.  The call back gets
 * the packages instances as its arguments. The method guarantees the callback
 * is called at the time zebkit and requested packages are loaded, initialized
 * and ready to be used.
 * @param {String} [packages]* name or names of packages to make visible
 * in callback method
 * @param {Function} [callback] a method to be called. The method is called
 * in context of the given package and gets requested packages passed as the
 * method arguments in order they have been requested.
 * @method  require
 * @example
 *
 *     zebkit.require("ui", function(ui) {
 *         var b = new ui.Button("Ok");
 *         ...
 *     });
 *
 */
Package.prototype.require = function() {
    var pkgs  = [],
        $this = this,
        fn    = arguments[arguments.length - 1];

    if (typeof fn !== 'function') {
        throw new Error("Invalid callback function");
    }

    for(var i = 0; isString(arguments[i]) && i < arguments.length; i++) {
        var pkg = getPropertyValue(this, arguments[i]);
        if ((pkg instanceof Package) === false) {
            throw new Error("Package '" + arguments[i] + "' cannot be found");
        }
        pkgs.push(pkg);
    }

    return this.then(function() {
        fn.apply($this, pkgs);
    });
};

/**
 * Detect root package.
 * @return {zebkit.Package} a root package
 * @method getRootPackage
 */
Package.prototype.getRootPackage = function() {
    var rootPkg = this;
    while (rootPkg.$parent !== null) {
        rootPkg = rootPkg.$parent;
    }
    return rootPkg;
};

var $textualFileExtensions = [
        "txt", "json", "htm", "html", "md", "properties", "conf", "xml", "java", "js", "css", "scss", "log"
    ],
    $imageFileExtensions = [
        "jpg", "jpeg", "png", "tiff", "gif", "ico", "exif", "bmp"
    ];

/**
 * This method loads resources (images, textual files, etc) and call callback
 * method with completely loaded resources as input arguments.
 * @example
 *
 *     zebkit.resources(
 *         "http://test.com/image1.jpg",
 *         "http://test.com/text.txt",
 *         function(image, text) {
 *             // handle resources here
 *             ...
 *         }
 *     );
 *
 * @param  {String} paths*  paths to resources to be loaded
 * @param  {Function} cb callback method that is executed when all listed
 * resources are loaded and ready to be used.
 * @method resources
 */
Package.prototype.resources = function() {
    var args  = Array.prototype.slice.call(arguments),
        $this = this,
        fn    = args.pop();

    if (typeof fn !== 'function') {
        throw new Error("Invalid callback function");
    }

    this.then(function() {
        for(var i = 0; i < args.length ; i++) {
            (function(path, jn) {
                var m    = path.match(/^(\<[a-z]+\>\s*)?(.*)$/),
                    type = "txt",
                    p    = m[2].trim();

                if (m[1] !== undefined) {
                    type = m[1].trim().substring(1, m[1].length - 1).trim();
                } else {
                    var li = p.lastIndexOf('.');
                    if (li > 0) {
                        var ext = p.substring(li + 1).toLowerCase();
                        if ($textualFileExtensions.indexOf(ext) >= 0) {
                            type = "txt";
                        } else if ($imageFileExtensions.indexOf(ext) >= 0) {
                            type = "img";
                        }
                    }
                }

                if (type === "img") {
                    $zenv.loadImage(p, function(img) {
                        jn(img);
                    }, function(img, e) {
                        jn(null);
                    });
                } else if (type === "txt") {
                    ZFS.GET(p).then(function(req) {
                        jn(req.responseText);
                    }).catch(function(e) {
                        jn(null);
                    });
                } else {
                    jn(null);
                }

            })(args[i], this.join());
        }
    }).then(function() {
        fn.apply($this, arguments);
    });
};

/**
 * This method helps to sync accessing to package entities with the
 * package internal state. For instance package declaration can initiate
 * loading resources that happens asynchronously. In this case to make sure
 * the package completed loading its configuration we should use package
 * "then" method.
 * @param  {Function} f a callback method where we can safely access the
 * package entities
 * @chainable
 * @private
 * @example
 *
 *     zebkit.then(function() {
 *         // here we can make sure all package declarations
 *         // are completed and we can start using it
 *     });
 *
 * @method  then
 */
Package.prototype.then = function(f) {
    this.$ready.then(f).catch(function(e) {
        dumpError(e);
        // re-start other waiting tasks
        this.restart();
    });
    return this;
};

Package.prototype.join = function() {
    return this.$ready.join.apply(this.$ready, arguments);
};

/**
 * Method that has to be used to declare packages.
 * @param  {String}   name     a name of the package
 * @param  {Function} [callback] a call back method that is called in package
 * context. The method has to be used to populate the given package classes,
 * interfaces and variables.
 * @param  {String|Boolean} [config] a path to configuration JSON file or boolean flag that says
 * to perform configuration using package as configuration name
 * @example
 *     // declare package "zebkit.log"
 *     zebkit.package("log", function(pkg) {
 *         // declare the package class Log
 *         pkg.Log = zebkit.Class([
 *              function error() { ... },
 *              function warn()  { ... },
 *              function info()  { ... }
 *         ]);
 *     });
 *
 *     // later on you can use the declared package stuff as follow
 *     zebkit.require("log", function(log) {
 *         var myLog = new log.Log();
 *         ...
 *         myLog.warn("Warning");
 *     });
 *
 * @return {zebkit.Package} a package
 * @method package
 */
Package.prototype.package = function(name, callback, path) {
    // no arguments than return the package itself
    if (arguments.length === 0) {
        return this;
    } else {
        var target = this;

        if (typeof name !== 'function') {
            if (name === undefined || name === null) {
                throw new Error("Null package name");
            }

            name = name.trim();
            if (name.match(/^[a-zA-Z_][a-zA-Z0-9_]+(\.[a-zA-Z_][a-zA-Z0-9_]+)*$/) === null) {
                throw new Error("Invalid package name '" + name + "'");
            }

            var names = name.split('.');
            for(var i = 0, k = names[0]; i < names.length; i++, k = k + '.' + names[i]) {
                var n = names[i],
                    p = target[n];

                if (p === undefined) {
                    p = new Package(n, target);
                    target[n] = p;
                } else if ((p instanceof Package) === false) {
                    throw new Error("Requested package '" + name +  "' conflicts with variable '" + n + "'");
                }
                target = p;
            }
        } else {
            path    = callback;
            callback = name;
        }

        // detect url later then sooner since
        if (target.$url === null) {
            target.$detectLocation();
        }

        if (typeof callback === 'function') {
            this.then(function() {
                callback.call(target, target, typeof Class !== 'undefined' ? Class : null);
            }).then(function() {
                // initiate configuration loading if it has been requested
                if (path !== undefined && path !== null) {
                    var jn = this.join();
                    if (path === true) {
                        var fn = target.fullname();
                        path = fn.substring(fn.indexOf('.') + 1) + ".json";
                        target.configWithRs(path, jn);
                    } else {
                        target.configWith(path, jn);
                    }
                }
            }).then(function(r) {
                if (r instanceof Error) {
                    this.error(r);
                } else {
                    // initiate "clazz.$name" resolving
                    $lsall.call(target, target.fullname() + ".");
                }
            });
        }

        return target;
    }
};


function resolvePlaceholders(path, env) {
    // replace placeholders in dir path
    var ph = path.match(/\%\{[a-zA-Z$][a-zA-Z0-9_$.]*\}/g);
    if (ph !== null) {
        for (var i = 0; i < ph.length; i++) {
            var p = ph[i],
                v = env[p.substring(2, p.length - 1)];

            if (v !== null && v !== undefined) {
                path = path.replace(p, v);
            }
        }
    }
    return path;
}

/**
 * Configure the given package with the JSON.
 * @param  {String | Object} path a path to JSON or JSON object
 * @param  {Function} [cb] a callback method
 * @method configWith
 */
Package.prototype.configWith = function(path, cb) {
    // catch error to keep passed callback notified
    try {
        if ((path instanceof URI || isString(path)) && URI.isAbsolute(path) === false) {
            path = URI.join(this.$url, path);
        }
    } catch(e) {
        if (arguments.length > 1 && cb !== null) {
            cb.call(this, e);
            return;
        } else {
            throw e;
        }
    }

    var $this = this;
    if (arguments.length > 1 && cb !== null) {
        new Zson($this).then(path, function() {
            cb.call(this, path);
        }).catch(function(e) {
            cb.call(this, e);
        });
    } else {
        this.getRootPackage().then(function() { // calling the guarantees it will be called when previous actions are completed
            this.till(new Zson($this).then(path)); // now we can trigger other loading action
        });
    }
};

/**
 * Configure the given package with the JSON.
 * @param  {String | Object} path a path to JSON or JSON object
 * @param  {Function} [cb] a callback
 * @method configWithRs
 */
Package.prototype.configWithRs = function(path, cb) {
    if (URI.isAbsolute(path)) {
        throw new Error("Absulute path cannot be used");
    }

    var pkg = this;
    // detect root package (common sync point) and package that
    // defines path to resources
    while (pkg !== null && (pkg.$config.basedir === undefined || pkg.$config.basedir === null)) {
        pkg = pkg.$parent;
    }

    if (pkg === null) {
        path = URI.join(this.$url, "rs", path);
    } else {
        // TODO: where config placeholders have to be specified
        path = URI.join(resolvePlaceholders(pkg.$config.basedir, pkg.$config), path);
    }

    return arguments.length > 1 ? this.configWith(path, cb)
                                : this.configWith(path);
};


$export(Package);
/**
 * This is the core package that provides powerful easy OOP concept, packaging
 * and number of utility methods. The package doesn't have any dependencies
 * from others zebkit packages and can be used independently. Briefly the
 * package possibilities are listed below:

   - **easy OOP concept**. Use "zebkit.Class" and "zebkit.Interface" to declare
     classes and interfaces

    ```JavaScript
        // declare class A
        var ClassA = zebkit.Class([
            function() { // class constructor
                ...
            },
            // class method
            function a(p1, p2, p3) { ... }
        ]);

        var ClassB = zebkit.Class(ClassA, [
            function() {  // override cotoString.nanstructor
                this.$super(); // call super constructor
            },

            function a(p1, p2, p3) { // override method "a"
                this.$super(p1, p2, p3);  // call super implementation of method "a"
            }
        ]);

        var b = new ClassB(); // instantiate classB
        b.a(1,2,3); // call "a"

        // instantiate anonymous class with new method "b" declared and
        // overridden method "a"
        var bb = new ClassB([
            function a(p1, p2, p3) { // override method "a"
                this.$super(p1, p2, p3);  // call super implementation of method "a"
            },

            function b() { ... } // declare method "b"
        ]);

        b.a();
        b.b();
    ```

   - **Packaging.** Zebkit uses Java-like packaging system where your code is bundled in
      the number of hierarchical packages.

    ```JavaScript
        // declare package "zebkit.test"
        zebkit.package("test", function(pkg) {
            // declare class "Test" in the package
            pkg.Test = zebkit.Class([ ... ]);
        });

        ...
        // Later on use class "Test" from package "zebkit.test"
        zebkit.require("test", function(test) {
            var test = new test.Test();
        });
    ```

    - **Resources loading.** Resources should be loaded with a special method to guarantee
      its proper loading in zebkit sequence and the loading completeness.

    ```JavaScript
        // declare package "zebkit.test"
        zebkit.resources("http://my.com/test.jpg", function(img) {
            // handle completely loaded image here
            ...
        });

        zebkit.package("test", function(pkg, Class) {
            // here we can be sure all resources are loaded and ready
        });
    ```

   - **Declaring number of core API method and classes**
      - **"zebkit.DoIt"** - improves Promise like alternative class
      - **"zebkit.URI"** - URI helper class
      - **"zebkit.Dummy"** - dummy class
      - **instanceOf(...)** method to evaluate zebkit classes and and interfaces inheritance.
        The method has to be used instead of JS "instanceof" operator to provide have valid
        result.
      - **zebkit.newInstance(...)** method
      - **zebkit.clone(...)**  method
      - etc

 * @class zebkit
 * @access package
 */

// =================================================================================================
//
//   Zebkit root package declaration
//
// =================================================================================================
var zebkit = new Package("zebkit");

/**
 * Reference to zebkit environment. Environment is basic, minimal API
 * zebkit and its components require.
 * @for  zebkit
 * @attribute environment
 * @readOnly
 * @type {Environment}
 */

// declaring zebkit as a global variable has to be done before calling "package" method
// otherwise the method cannot find zebkit to resolve class names
//
// nodejs
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
    module.exports = zebkit;
}

$global.zebkit = zebkit;

// collect exported entities in zebkit package space
zebkit.package(function(pkg) {
    for(var exp in $exports) {
        pkg[exp] = $exports[exp];
    }
});

if ($isInBrowser) {

    // collect query string parameters
    try {
        var uri = new URI(document.URL);
        if (uri.qs !== null) {
            var params = URI.parseQS(uri.qs);
            for (var k in params) {
                zebkit.config(k, URI.decodeQSValue(params[k]));
            }

            var cacheBusting = zebkit.config("zson.cacheBusting");
            if (cacheBusting !== undefined && cacheBusting !== null) {
                Zson.prototype.cacheBusting = cacheBusting;
            }
        }
    } catch(e) {
        dumpError(e);
    }

    zebkit.then(function() {
        var jn        = this.join(),
            $interval = $zenv.setInterval(function () {
            if (document.readyState === "complete") {
                $zenv.clearInterval($interval);
                jn(zebkit);
            }
        }, 50);
    });
}
})();
zebkit.package("util", function(pkg, Class) {
    /**
     * Number of different utilities methods and classes.
     * @class zebkit.util
     * @access package
     */

    /**
     * Validate the specified value to be equal one of the given values
     * @param  {value} value a value to be validated
     * @param  {Object} [allowedValues]* a number of valid values to test against
     * @throws Error if the value doesn't match any valid value
     * @for  zebkit.util
     * @method  validateValue
     * @example
     *      // test if the alignment is equal one of the possible values
     *      // throws error otherwise
     *      zebkit.util.validateValue(alignment, "top", "left", "right", "bottom");
     * @protected
     */
    pkg.validateValue = function(value) {
        if (arguments.length < 2) {
            throw new Error("Invalid arguments list. List of valid values is expected");
        }

        for(var i = 1; i < arguments.length; i++) {
            if (arguments[i] === value) {
                return value;
            }
        }

        var values = Array.prototype.slice.call(arguments).slice(1);
        throw new Error("Invalid value '" + value + "',the following values are expected: " + values.join(','));
    };

    /**
     * Compare two dates.
     * @param  {Date} d1 a first date
     * @param  {Date} d2 a second sate
     * @return {Integer} 0 if the two dates are equal, -1 if d1 < d2, 1 if d1 > d2,
     * null if one of the date is null
     * @method compareDates
     */
    pkg.compareDates = function(d1, d2) {
        if (arguments.length === 2 && d1 === d2) {
            return 0;
        }

        // exclude null dates
        if (d1 === null || d2 === null) {
            return null;
        }

        var day1, month1, year1,
            day2, month2, year2,
            i = 1;

        if (d1 instanceof Date) {
            day1   = d1.getDate();
            month1 = d1.getMonth();
            year1  = d1.getFullYear();
        } else {
            day1   = arguments[0];
            month1 = arguments[1];
            year1  = arguments[2];
            i = 3;
        }

        d2 = arguments[i];
        if (d2 instanceof Date) {
            day2   = d2.getDate();
            month2 = d2.getMonth();
            year2  = d2.getFullYear();
        } else {
            day2   = arguments[i];
            month2 = arguments[i + 1];
            year2  = arguments[i + 2];
        }

        if (day1 === day2 && month1 === month2 && year1 === year2) {
            return 0;
        } else if (year1 > year2 ||
                   (year1 === year2 && month1 > month2) ||
                   (year1 === year2 && month1 === month2 && day1 > day2))
        {
            return 1;
        } else {
            return -1;
        }
    };

    /**
     * Validate the given date
     * @param  {Date} date a date to be validated
     * @return {Boolean} true if the date is valid
     * @method validateDate
     */
    pkg.validateDate = function(day, month, year) {
        var d = (arguments.length < 3) ? (arguments.length === 1 ? day : new Date(month, day))
                                       : new Date(year, month, day);
        if (d.isValid() === false) {
            throw new Error("Invalid date : " + d);
        }
    };

    pkg.format = function(s, obj, ph) {
        if (arguments.length < 3) {
            ph = '';
        }

        var rg = /\$\{([0-9]+\s*,)?(.?,)?([a-zA-Z_][a-zA-Z0-9_]*)\}/g,
            r  = [],
            i  = 0,
            j  = 0,
            m  = null;

        while ((m = rg.exec(s)) !== null) {
            r[i++] = s.substring(j, m.index);

            j = m.index + m[0].length;

            var v  = obj[m[3]],
                mn = "get" + m[3][0].toUpperCase() + m[3].substring(1),
                f  = obj[mn];

            if (typeof f === "function") {
                v = f.call(obj);
            }

            if (m[1] !== undefined) {
                var ml  = parseInt(m[1].substring(0, m[1].length - 1).trim()),
                    ph2 = m[2] !== undefined ? m[2].substring(0, m[2].length - 1) : ph;

                if (v === null || v === undefined) {
                    ph2 = ph;
                    v = "";
                } else {
                    v = "" + v;
                }

                for(var k = v.length; k < ml; k++) {
                    v = ph2 + v;
                }
            }

            if (v === null || v === undefined) {
                v = ph;
            }

            r[i++] = v;
        }

        if (i > 0) {
            if (j < s.length) {
                r[i++] = s.substring(j);
            }

            return pkg.format(r.join(''), obj, ph);
        }

        return s;
    };

    /**
     * Compute intersection of the two given rectangular areas
     * @param  {Integer} x1 a x coordinate of the first rectangular area
     * @param  {Integer} y1 a y coordinate of the first rectangular area
     * @param  {Integer} w1 a width of the first rectangular area
     * @param  {Integer} h1 a height of the first rectangular area
     * @param  {Integer} x2 a x coordinate of the first rectangular area
     * @param  {Integer} y2 a y coordinate of the first rectangular area
     * @param  {Integer} w2 a width of the first rectangular area
     * @param  {Integer} h2 a height of the first rectangular area
     * @param  {Object}  r  an object to store result
     *
     *      { x: {Integer}, y:{Integer}, width:{Integer}, height:{Integer} }
     *
     * @method intersection
     * @for zebkit.util
     */
    pkg.intersection = function(x1,y1,w1,h1,x2,y2,w2,h2,r){
        r.x = x1 > x2 ? x1 : x2;
        r.width = Math.min(x1 + w1, x2 + w2) - r.x;
        r.y = y1 > y2 ? y1 : y2;
        r.height = Math.min(y1 + h1, y2 + h2) - r.y;
    };

    /**
     * Test if two rectangular areas have intersection
     * @param  {Integer} x1 a x coordinate of the first rectangular area
     * @param  {Integer} y1 a y coordinate of the first rectangular area
     * @param  {Integer} w1 a width of the first rectangular area
     * @param  {Integer} h1 a height of the first rectangular area
     * @param  {Integer} x2 a x coordinate of the first rectangular area
     * @param  {Integer} y2 a y coordinate of the first rectangular area
     * @param  {Integer} w2 a width of the first rectangular area
     * @param  {Integer} h2 a height of the first rectangular area
     * @return {Boolean} true if the given two rectangular areas intersect
     *
     * @method isIntersect
     * @for zebkit.util
     */
    pkg.isIntersect = function(x1,y1,w1,h1,x2,y2,w2,h2){
        return (Math.min(x1 + w1, x2 + w2) - (x1 > x2 ? x1 : x2)) > 0 &&
               (Math.min(y1 + h1, y2 + h2) - (y1 > y2 ? y1 : y2)) > 0;
    };

    /**
     * Unite two rectangular areas to one rectangular area.
     * @param  {Integer} x1 a x coordinate of the first rectangular area
     * @param  {Integer} y1 a y coordinate of the first rectangular area
     * @param  {Integer} w1 a width of the first rectangular area
     * @param  {Integer} h1 a height of the first rectangular area
     * @param  {Integer} x2 a x coordinate of the first rectangular area
     * @param  {Integer} y2 a y coordinate of the first rectangular area
     * @param  {Integer} w2 a width of the first rectangular area
     * @param  {Integer} h2 a height of the first rectangular area
     * @param  {Object}  r  an object to store result
     *
     *      { x: {Integer}, y:{Integer}, width:{Integer}, height:{Integer} }
     *
     * @method unite
     * @for zebkit.util
     */
    pkg.unite = function(x1,y1,w1,h1,x2,y2,w2,h2,r){
        r.x = x1 < x2 ? x1 : x2;
        r.y = y1 < y2 ? y1 : y2;
        r.width  = Math.max(x1 + w1, x2 + w2) - r.x;
        r.height = Math.max(y1 + h1, y2 + h2) - r.y;
    };

    var letterRE = /[A-Za-z]/;
    pkg.isLetter = function (ch) {
        if (ch.length !== 1) {
            throw new Error("Incorrect character");
        }
        return letterRE.test(ch);
    };


    /**
     * Useful class to track a virtual cursor position in a structure that has dedicated number of lines
     * where every line has a number of elements. The structure metric has to be described by providing
     * an instance of zebkit.util.Position.Metric interface that discovers how many lines the structure
     * has and how many elements every line includes.
     * @param {zebkit.util.Position.Metric} m a position metric
     * @constructor
     * @class zebkit.util.Position
     */

    /**
     * Fire when a virtual cursor position has been updated
     *
     *      position.on(function(src, prevOffset, prevLine, prevCol) {
     *          ...
     *      });
     *
     * @event posChanged
     * @param {zebkit.util.Position} src an object that triggers the event
     * @param {Integer} prevOffest a previous virtual cursor offset
     * @param {Integer} prevLine a previous virtual cursor line
     * @param {Integer} prevCol a previous virtual cursor column in the previous line
     */
    pkg.Position = Class([
        function(pi){
            /**
             * Shows if the position object is in valid state.
             * @private
             * @type {Boolean}
             * @attribute isValid
             */
            this.isValid = false;

            /**
             * Current virtual cursor line position
             * @attribute currentLine
             * @type {Integer}
             * @readOnly
             */

            /**
             * Current virtual cursor column position
             * @attribute currentCol
             * @type {Integer}
             * @readOnly
             */

            /**
             * Current virtual cursor offset
             * @attribute offset
             * @type {Integer}
             * @readOnly
             */

            this.currentLine = this.currentCol = this.offset = 0;
            this.setMetric(pi);
        },

        function $clazz() {
            /**
             * Position metric interface. This interface is designed for describing
             * a navigational structure that consists on number of lines where
             * every line consists of number of elements
             * @class zebkit.util.Position.Metric
             * @interface zebkit.util.Position.Metric
             */

            /**
             * Get number of lines to navigate through
             * @return {Integer} a number of lines
             * @method  getLines
             */

             /**
              * Get a number of elements in the given line
              * @param {Integer} l a line index
              * @return {Integer} a number of elements in a line
              * @method  getLineSize
              */

             /**
              * Get a maximal element index (a last element of a last line)
              * @return {Integer} a maximal element index
              * @method  getMaxOffset
              */

            this.Metric = zebkit.Interface([
                "abstract",
                    function getLines()     {},
                    function getLineSize()  {},
                    function getMaxOffset() {}
            ]);
        },

        /**
         *  @for zebkit.util.Position
         */
        function $prototype() {
            /**
             * Set the specified virtual cursor offsest
             * @param {Integer} o an offset, pass null to set position to indefinite state.
             *
             *   - if offset is null than offset will set to -1 (undefined state)
             *   - if offset is less than zero than offset will be set to zero
             *   - if offset is greater or equal to maximal possible offset it will be set to maximal possible offset
             *
             *  @return {Integer} an offset that has been set
             * @method setOffset
             */
            this.setOffset = function(o){
                if (o < 0) {
                    o = 0;
                } else if (o === null) {
                    o = -1;
                } else {
                    var max = this.metrics.getMaxOffset();
                    if (o >= max) {
                        o = max;
                    }
                }

                if (o !== this.offset){
                    var prevOffset = this.offset,
                        prevLine   = this.currentLine,
                        prevCol    = this.currentCol,
                        p          = this.getPointByOffset(o);

                    this.offset = o;
                    if (p !== null){
                        this.currentLine = p[0];
                        this.currentCol  = p[1];
                    } else {
                        this.currentLine = this.currentCol = -1;
                    }
                    this.isValid = true;
                    this.fire("posChanged", [this, prevOffset, prevLine, prevCol]);
                }

                return o;
            };

            /**
             * Seek virtual cursor offset with the given shift
             * @param {Integer} off a shift
             * @return {Integer} an offset that has been set
             * @method seek
             */
            this.seek = function(off) {
                return this.setOffset(this.offset + off);
            };

            /**
             * Set the virtual cursor line and the given column in the line
             * @param {Integer} r a line
             * @param {Integer} c a column in the line
             * @method setRowCol
             */
            this.setRowCol = function(r, c) {
                if (r !== this.currentLine || c !== this.currentCol){
                    var prevOffset = this.offset,
                        prevLine = this.currentLine,
                        prevCol = this.currentCol;

                    this.offset = this.getOffsetByPoint(r, c);
                    this.currentLine = r;
                    this.currentCol = c;
                    this.fire("posChanged", [this, prevOffset, prevLine, prevCol]);
                }
            };

            /**
             * Special method to inform the position object that its state has to be adjusted
             * because of the given portion of data had been inserted .
             * @param  {Integer} off  an offset the insertion has happened
             * @param  {Integer} size a length of the inserted portion
             * @protected
             * @method  removed
             */
            this.inserted = function(off, size) {
                if (this.offset >= 0 && off <= this.offset){
                    this.isValid = false;
                    this.setOffset(this.offset + size);
                }
            };

            /**
             * Special method to inform the position object that its state has to be adjusted
             * because of the given portion of data had been removed.
             * @param  {Integer} off  an offset the removal has happened
             * @param  {Integer} size a length of the removed portion
             * @protected
             * @method  removed
             */
            this.removed = function (off, size){
                if (this.offset >= 0 && this.offset >= off){
                    this.isValid = false;
                    this.setOffset(this.offset >= (off + size) ? this.offset - size
                                                               : off);
                }
            };

            /**
             * Calculate a line and line column by the given offset.
             * @param  {Integer} off an offset
             * @return {Array} an array that contains a line as the first
             * element and a column in the line as the second element.
             * @method getPointByOffset
             */
            this.getPointByOffset = function(off){
                if (off >= 0) {
                    var m   = this.metrics,
                        max = m.getMaxOffset();

                    if (off > max) {
                        throw new Error("Out of bounds:" + off);
                    } else if (max === 0) {
                        return [(m.getLines() > 0 ? 0 : -1), 0];
                    } else if (off === 0) {
                        return [0, 0];
                    }

                    var d = 0, sl = 0, so = 0;
                    if (this.isValid === true && this.offset !== -1) {
                        sl = this.currentLine;
                        so = this.offset - this.currentCol;
                        if (off > this.offset) {
                            d = 1;
                        } else if (off < this.offset) {
                            d = -1;
                        } else {
                            return [sl, this.currentCol];
                        }
                    } else {
                        d = (Math.floor(max / off) === 0) ? -1 : 1;
                        if (d < 0) {
                            sl = m.getLines() - 1;
                            so = max - m.getLineSize(sl);
                        }
                    }

                    for(; sl < m.getLines() && sl >= 0; sl += d){
                        var ls = m.getLineSize(sl);
                        if (off >= so && off < so + ls) {
                            return [sl, off - so];
                        }
                        so += d > 0 ? ls : -m.getLineSize(sl - 1);
                    }
                }
                return null;
            };

            /**
             * Calculate an offset by the given line and column in the line
             * @param  {Integer} row a line
             * @param  {Integer} col a column in the line
             * @return {Integer} an offset
             * @method getOffsetByPoint
             */
            this.getOffsetByPoint = function (row, col){
                var startOffset = 0, startLine = 0, m = this.metrics, i = 0;

                if (row >= m.getLines()) {
                    throw new RangeError(row);
                }

                if (col >= m.getLineSize(row)) {
                    throw new RangeError(col);
                }

                if (this.isValid === true && this.offset !==  -1) {
                    startOffset = this.offset - this.currentCol;
                    startLine = this.currentLine;
                }

                if (startLine <= row) {
                    for(i = startLine;i < row; i++) {
                        startOffset += m.getLineSize(i);
                    }
                } else {
                    for(i = startLine - 1;i >= row; i--) {
                        startOffset -= m.getLineSize(i);
                    }
                }
                return startOffset + col;
            };

            /**
             * Seek virtual cursor to the next position. How the method has to seek to the next position
             * has to be denoted by one of the following constants:

        - **"begin"** seek cursor to the begin of the current line
        - **"end"** seek cursor to the end of the current line
        - **"up"** seek cursor one line up
        - **"down"** seek cursor one line down

             * If the current virtual position is not known (-1) the method always sets
             * it to the first line, the first column in the line (offset is zero).
             * @param  {Integer} t   an action the seek has to be done
             * @param  {Integer} num number of seek actions
             * @method seekLineTo
             */
            this.seekLineTo = function(t,num){
                if (this.offset < 0){
                    this.setOffset(0);
                } else {
                    if (arguments.length === 1) {
                        num = 1;
                    }

                    var prevOffset = this.offset,
                        prevLine   = this.currentLine,
                        prevCol    = this.currentCol,
                        maxCol     = 0,
                        i          = 0;

                    switch(t) {
                        case "begin":
                            if (this.currentCol > 0){
                                this.offset -= this.currentCol;
                                this.currentCol = 0;
                            } break;
                        case "end":
                            maxCol = this.metrics.getLineSize(this.currentLine);
                            if (this.currentCol < (maxCol - 1)) {
                                this.offset += (maxCol - this.currentCol - 1);
                                this.currentCol = maxCol - 1;
                            } break;
                        case "up":
                            if (this.currentLine > 0) {
                                this.offset -= (this.currentCol + 1);
                                this.currentLine--;
                                for(i = 0; this.currentLine > 0 && i < (num - 1); i++, this.currentLine--) {
                                    this.offset -= this.metrics.getLineSize(this.currentLine);
                                }

                                maxCol = this.metrics.getLineSize(this.currentLine);
                                if (this.currentCol < maxCol) {
                                    this.offset -= (maxCol - this.currentCol - 1);
                                } else {
                                    this.currentCol = maxCol - 1;
                                }
                            } break;
                        case "down":
                            if (this.currentLine < (this.metrics.getLines() - 1)) {
                                this.offset += (this.metrics.getLineSize(this.currentLine) - this.currentCol);
                                this.currentLine++;
                                var size = this.metrics.getLines() - 1;
                                for (i = 0; this.currentLine < size && i < (num - 1); i++ ,this.currentLine++ ) {
                                    this.offset += this.metrics.getLineSize(this.currentLine);
                                }

                                maxCol = this.metrics.getLineSize(this.currentLine);
                                if (this.currentCol < maxCol) {
                                    this.offset += this.currentCol;
                                } else {
                                    this.currentCol = maxCol - 1;
                                    this.offset += this.currentCol;
                                }
                            } break;
                        default: throw new Error("" + t);
                    }

                    this.fire("posChanged", [this, prevOffset, prevLine, prevCol]);
                }
            };

            /**
             * Set position metric. Metric describes how many lines
             * and elements in these line the virtual cursor can be navigated
             * @param {zebkit.util.Position.Metric} p a position metric
             * @method setMetric
             */
            this.setMetric = function(p) {
                if (p === null || p === undefined) {
                    throw new Error("Null metric");
                }

                if (p !== this.metrics){
                    this.metrics = p;
                    this.setOffset(null);
                }
            };
        }
    ]).events("posChanged");

    /**
     * Single column position implementation. More simple and more fast implementation of
     * position class for the cases when only one column is possible.
     * @param {zebkit.util.Position.Metric} m a position metric
     * @constructor
     * @class zebkit.util.SingleColPosition
     * @extends zebkit.util.Position
     */
    pkg.SingleColPosition = Class(pkg.Position, [
        function $prototype() {
            this.setRowCol = function(r,c) {
                this.setOffset(r);
            };

            this.setOffset = function(o) {
                if (o < 0) { o = 0;
                } else if (o === null) {
                    o = -1;
                } else {
                    var max = this.metrics.getMaxOffset();
                    if (o >= max) {
                        o = max;
                    }
                }

                if (o !== this.offset) {
                    var prevOffset = this.offset,
                        prevLine   = this.currentLine,
                        prevCol    = this.currentCol;

                    this.currentLine = this.offset = o;
                    this.isValid = true;
                    this.fire("posChanged", [this, prevOffset, prevLine, prevCol]);
                }

                return o;
            };

            this.seekLineTo = function(t, num){
                if (this.offset < 0){
                    this.setOffset(0);
                } else {
                    if (arguments.length === 1) {
                        num = 1;
                    }

                    switch(t) {
                        case "begin":
                        case "end": break;
                        case "up":
                            if (this.offset > 0) {
                                this.setOffset(this.offset - num);
                            } break;
                        case "down":
                            if (this.offset < (this.metrics.getLines() - 1)){
                                this.setOffset(this.offset + num);
                            } break;
                        default: throw new Error("" + t);
                    }
                }
            };
        }
    ]);

    /**
     * Task set is light-weight class to host number of callbacks methods that
     * are called within a context of one JS interval method execution. The
     * class manages special tasks queue to run it one by one as soon as a
     * dedicated interval for the given task is elapsed

        var tasks = new zebkit.util.TasksSet();

        tasks.run(function(t) {
            // task1 body
            ...
            if (condition) {
                t.shutdown();
            }
        }, 1000, 200);

        tasks.run(function(t) {
            // task2 body
            ...
            if (condition) {
                t.shutdown();
            }
        }, 2000, 300);

     * @constructor
     * @param  {Integer} [maxTasks] maximal possible number of active tasks in queue.
     * @class zebkit.util.TasksSet
     */
    pkg.TasksSet = Class([
        function(c) {
            this.tasks = Array(arguments.length > 0 ? c : 5);

            // pre-fill tasks pool
            for(var i = 0; i < this.tasks.length; i++) {
                this.tasks[i] = new this.clazz.Task(this);
            }
        },

        function $clazz() {
            /**
             * Task class
             * @class zebkit.util.TasksSet.Task
             * @for zebkit.util.TasksSet.Task
             * @param {zebkit.util.TasksSet} tasksSet a reference to tasks set that manages the task
             * @constructor
             */
            this.Task = Class([
                function(set) {
                    /**
                     * Reference to a tasks set that owns the task
                     * @type {zebkit.util.TasksSet}
                     * @attribute taskSet
                     * @private
                     * @readOnly
                     */
                    this.taskSet = set;

                    /**
                     * Indicates if the task is executed (active)
                     * @type {Boolean}
                     * @attribute isStarted
                     * @readOnly
                     */
                    this.isStarted = false;
                },

                function $prototype() {
                    this.task = null;
                    this.ri = this.si  = 0;

                    /**
                     * Shutdown the given task.
                     * @return {Boolean} true if the task has been stopped
                     * @method shutdown
                     */
                    this.shutdown = function() {
                        return this.taskSet.shutdown(this);
                    };

                    /**
                     * Pause the given task.
                     * @return {Boolean} true if the task has been paused
                     * @method pause
                     */
                    this.pause = function() {
                        if (this.task === null) {
                            throw new Error("Stopped task cannot be paused");
                        }

                        if (this.isStarted === true) {
                            this.isStarted = false;
                            return true;
                        } else {
                            return false;
                        }
                    };

                    /**
                     * Resume the given task
                     * @param {Integer} [startIn] a time in milliseconds to resume the task
                     * @return {Boolean} true if the task has been resumed
                     * @method resume
                     */
                    this.resume = function(t) {
                        if (this.task === null) {
                            throw new Error("Stopped task cannot be paused");
                        }

                        this.si = arguments.length > 0 ? t : 0;
                        if (this.isStarted === true) {
                            return false;
                        } else {
                            this.isStarted = true;
                            return true;
                        }
                    };
                }
            ]);
        },

        /**
         *  @for  zebkit.util.TasksSet
         */
        function $prototype() {
            /**
             * Interval
             * @attribute quantum
             * @private
             * @type {Number}
             * @default 40
             */
            this.quantum = 40;

            /**
             * pid of executed JS interval method callback
             * @attribute pid
             * @private
             * @type {Number}
             * @default -1
             */
            this.pid = -1;

            /**
             * Number of run in the set tasks
             * @attribute count
             * @private
             * @type {Number}
             * @default 0
             */
            this.count = 0;

            /**
             * Shut down all active at the given moment tasks
             * body and the given context.
             * @method shutdownAll
             */
            this.shutdownAll = function() {
                for(var i = 0; i < this.tasks.length; i++) {
                    this.shutdown(this.tasks[i]);
                }
            };

            /**
             * Shutdown the given task
             * @param  {zebkit.util.TasksSet.Task} t a task
             * @return {Boolean}  true if the task has been stopped, false if the task has not been started
             * to be stopped
             * @protected
             * @method shutdown
             */
            this.shutdown = function(t) {
                if (t.task !== null) {
                    this.count--;
                    t.task = null;
                    t.isStarted = false;
                    t.ri = t.si = 0;
                    return true;
                } else {
                    if (this.count === 0 && this.pid  >= 0) {
                        zebkit.environment.clearInterval(this.pid);
                        this.pid = -1;
                    }

                    return false;
                }
            };

            /**
             * Take a free task from tasks pool and run it once in the specified period of time.
             * @param  {Function|Object} f a task function that has to be executed. The task method gets the task
             * context as its argument. You can pass an object as the argument if the object has "run" method
             * implemented. In this cases "run" method will be used as the task body.
             * @param  {Integer} [startIn]  time in milliseconds the task has to be executed in
             * @method runOnce
             */
            this.runOnce = function(f, startIn) {
                this.run(f, startIn, -1);
            };

            /**
             * Take a free task from pool and run it with the specified body and the given context.
             * @param  {Function|Object} f a task function that has to be executed. The task method gets the task
             * context as its argument. You can pass an object as the argument if the object has "run" method
             * implemented. In this cases "run" method will be used as the task body.
             * @param {Integer} [si]  time in milliseconds the task has to be executed
             * @param {Integer} [ri]  the time in milliseconds the task has to be periodically repeated
             * @return {zebkit.util.Task} an allocated task
             * @example

        var tasks = new zebkit.util.TasksSet();

        // execute task
        var task = tasks.run(function (t) {
            // do something
            ...
            // complete task if necessary
            t.shutdown();
        }, 100, 300);

        // pause task
        task.pause(1000, 2000);

        ...
        // resume task in a second
        task.resume(1000);

             * @example

        var tasks = new zebkit.util.TasksSet();

        var a = new zebkit.Dummy([
            function run() {
                // task body
                ...
            }
        ]);

        // execute task
        var task = tasks.runOnce(a);

             * @method run
             */
            this.run = function(f, si, ri){
                if (f === null || f === undefined) {
                    throw new Error("" + f);
                }

                var $this = this;
                function dispatcher() {
                    var c = 0;
                    for(var i = 0; i < $this.tasks.length; i++) {
                        var t = $this.tasks[i];

                        // count paused or run tasks
                        if (t.task !== null) {  // means task has been shutdown
                            c++;
                        }

                        if (t.isStarted === true) {
                            if (t.si <= 0) {
                                try {
                                    if (t.task.run !== undefined) {
                                        t.task.run(t);
                                    } else {
                                        t.task(t);
                                    }

                                    if (t.ri < 0) {
                                        t.shutdown();
                                    }
                                } catch(e) {
                                    zebkit.dumpError(e);
                                }

                                t.si += t.ri;
                            } else {
                                t.si -= $this.quantum;
                            }
                        }
                    }

                    if (c === 0 && $this.pid >= 0) {
                        zebkit.environment.clearInterval($this.pid);
                        $this.pid = -1;
                    }
                }

                // find free and return free task
                for(var i = 0; i < this.tasks.length; i++) {
                    var j = (i + this.count) % this.tasks.length,
                        t = this.tasks[j];

                    if (t.task === null) {
                        // initialize internal variables start in and repeat in
                        // arguments
                        t.si = (arguments.length > 1) ? si : 0;
                        t.ri = (arguments.length > 2) ? ri : -1;
                        t.isStarted = true;
                        t.task = f;
                        this.count++;

                        if (this.count > 0 && this.pid < 0) {
                            this.pid = zebkit.environment.setInterval(dispatcher, this.quantum);
                        }

                        return t;
                    }
                }

                throw new Error("Out of active tasks limit (" +  this.tasks.length + ")");
            };
        }
    ]);

    /**
     * Predefined default tasks set.
     * @attribute tasksSet
     * @type {zebkit.util.TasksSet}
     * @for zebkit.util
     */
    pkg.tasksSet = new pkg.TasksSet(7);
});
zebkit.package("data", function(pkg, Class) {
    /**
     * Collection of various data models. The models are widely used by zebkit UI
     * components as part of model-view-controller approach, but the package doesn't depend on
     * zebkit UI and can be used independently.
     *
     *      var model = new zebkit.data.TreeModel();
     *      model.on("itemInserted", function(model, item) {
     *          // handle item inserted tree model event
     *          ...
     *      });
     *
     *      model.add(model.root, new zebkit.data.Item("Child 1"));
     *      model.add(model.root, new zebkit.data.Item("Child 2"));
     *
     * @class zebkit.data
     * @access package
     */

    pkg.descent = function descent(a, b) {
        if (a === undefined || a === null) {
            return 1;
        } else {
            return zebkit.isString(a) ? a.localeCompare(b) : a - b;
        }
    };

    pkg.ascent = function ascent(a, b) {
        if (b === null || b === undefined) {
            return 1;
        } else {
            return zebkit.isString(b) ? b.localeCompare(a) : b - a;
        }
    };

    /**
     * Data model is marker interface. It has no methods implemented, but the interface
     * is supposed to be inherited with data models implementations
     * @class zebkit.data.DataModel
     * @interface zebkit.data.DataModel
     */
    pkg.DataModel = zebkit.Interface();

    /**
     * Abstract text model class
     * @class zebkit.data.TextModel
     * @uses zebkit.data.DataModel
     * @uses zebkit.EventProducer
     */

    /**
     * Get the given string line stored in the model
     * @method getLine
     * @param  {Integer} line a line number
     * @return {String}  a string line
     */

    /**
     * Get wrapped by the text model original text string
     * @method getValue
     * @return {String} an original text
     */

    /**
     * Get number of lines stored in the text model
     * @method getLines
     * @return {Integer} a number of lines
     */

    /**
     * Get number of characters stored in the model
     * @method getTextLength
     * @return {Integer} a number of characters
     */

    /**
     * Write the given string in the text model starting from the specified offset
     * @method write
     * @param  {String} s a string to be written into the text model
     * @param  {Integer} offset an offset starting from that the passed
     * string has to be written into the text model
     */

    /**
     * Remove substring from the text model.
     * @method remove
     * @param  {Integer} offset an offset starting from that a substring
     * will be removed
     * @param  {Integer} size a size of a substring to be removed
     */

    /**
     * Fill the text model with the given text
     * @method  setValue
     * @param  {String} text a new text to be set for the text model
     */

    /**
     * Fired when the text model has been updated: a string has been
     * inserted or removed

        text.on(function(e) {
            ...
        });

     *
     * @event textUpdated
     * @param {zebkit.data.TextEvent} e a text model event
     */
    pkg.TextModel = Class(pkg.DataModel, [
        function $prototype() {
            this.replace = function(s, off, size) {
                if (s.length === 0) {
                    return this.remove(off, size);
                } else if (size === 0) {
                    return this.write(s, off);
                } else {
                    var b = this.remove(off, size, false);
                    return this.write(s, off) && b;
                }
            };
        }
    ]).events("textUpdated");

    /**
     * Text model event class.
     * @constructor
     * @class zebkit.data.TextEvent
     * @extends zebkit.Event
     */
    pkg.TextEvent = Class(zebkit.Event, [
        function $prototype() {
            /**
             * Event id.
             * @attribute id
             * @type {String}
             */
            this.id = null;

            /**
             * First line number that has participated in the event action.
             * @attribute line
             * @type {Integer}
             */
            this.line = 0;

            /**
             * Number of lines that have participated in the event action.
             * @attribute lines
             * @type {Integer}
             */
            this.lines = 0;

            /**
             * Offset in a text.
             * @attribute offset
             * @type {Integer}
             */
            this.offset = 0;

            /**
             * Number of characters.
             * @attribute size
             * @type {Integer}
             */
            this.size = 0;

            this.isLastStep = true;

            /**
             * Fill the event with the give parameters
             * @param  {zebkit.data.TextModel} src  a source of the event
             * @param  {String} id an id of the event ("remove", "insert")
             * @param  {Integer} line a first line
             * @param  {Integer} lines  a number of lines
             * @param  {Integer} offset an offset
             * @param  {Integer} size   a number of characters
             * @method $fillWith
             * @chainable
             * @protected
             */
            this.$fillWith = function(src, id, line, lines, offset, size) {
                this.isLastStep   = true;
                this.source       = src;
                this.id           = id;
                this.line         = line;
                this.lines        = lines;
                this.offset       = offset;
                this.size         = size;
                return this;
            };
        }
    ]);

    var TE_STUB = new pkg.TextEvent();

    /**
     * Multi-lines text model implementation
     * @class zebkit.data.Text
     * @param  {String}  [s] the specified text the model has to be filled
     * @constructor
     * @extends zebkit.data.TextModel
     */
    pkg.Text = Class(pkg.TextModel, [
        function(s) {
            /**
             * Array of lines
             * @attribute lines
             * @type {zebkit.data.Text.Line[]}
             * @private
             * @readOnly
             */
            this.$lines = [ new this.clazz.Line("") ];
            this.setValue(arguments.length === 0 || s === null ? "" : s);
        },

        function $clazz() {
            this.Line = function(s) {
                this.$s = s;
            };

            //  toString for array.join method
            this.Line.prototype.toString = function() {
                return this.$s;
            };
        },

        function $prototype() {
            /**
             * Text length
             * @attribute textLength
             * @private
             * @readOnly
             * @type {Integer}
             */
            this.textLength = 0;

            /**
             * Detect line by offset starting from the given line and offset.
             * @param  {Integer} [start]       start line
             * @param  {Integer} [startOffset] start offset of the start line
             * @param  {Integer} o             offset to detect line
             * @private
             * @method calcLineByOffset
             * @return {Array}  an array that consists of two elements: detected line index and its offset
             */
            this.calcLineByOffset = function(start, startOffset, o) {
                if (arguments.length === 1) {
                    startOffset = start = 0;
                }

                for(; start < this.$lines.length; start++){
                    var line = this.$lines[start].$s;
                    if (o >= startOffset && o <= startOffset + line.length){
                        return [start, startOffset];
                    }
                    startOffset += (line.length + 1);
                }
                return [];
            };

            /**
             * Calculate an offset in the text the first character of the specified line.
             * @param  {Integer} line a line index
             * @return {Integer} an offset
             * @protected
             * @method  calcLineOffset
             */
            this.calcLineOffset = function(line) {
                var off = 0;
                for(var i = 0; i < line; i++){
                    off += (this.$lines[i].$s.length + 1);
                }
                return off;
            };

            this.$lineTags = function(i) {
                return this.$lines[i];
            };

            this.getLine = function(line) {
                if (line < 0 || line >= this.$lines.length) {
                    throw RangeError(line);
                }
                return this.$lines[line].$s;
            };

            this.getValue = function() {
                return this.$lines.join("\n");
            };

            this.toString = function() {
                return this.$lines.join("\n");
            };

            this.getLines = function () {
                return this.$lines.length;
            };

            this.getTextLength = function() {
                return this.textLength;
            };

            /**
             * Remove number of text lines starting form the specified line
             * @param  {Integer} start a starting line to remove text lines
             * @param  {Integer} [size]  a number of lines to be removed. If the
             * argument is not passed number equals 1
             * @method removeLines
             */
            this.removeLines = function(start, size) {
                if (start < 0 || start >= this.$lines.length) {
                    throw new RangeError(start);
                }

                if (arguments.length === 1) {
                    size = 1;
                } else if (size <= 0) {
                    throw new Error("Invalid number of lines : " + size);
                }

                // normalize number required lines to be removed
                if ((start + size) > this.$lines.length) {
                    size = this.$lines.length - start;
                }

                var end  = start + size - 1,            // last line to be removed
                    off  = this.calcLineOffset(start),  // offset of the first line to be removed
                    olen = start !== end ? this.calcLineOffset(end) + this.$lines[end].$s.length + 1 - off
                                         : this.$lines[start].$s.length + 1;


                // if this is the last line we have to correct offset to point to "\n" character in text
                if (start === this.$lines.length - 1) {
                    off--;
                }

                this.$lines.splice(start, size);
                this.fire("textUpdated", TE_STUB.$fillWith(this, "remove", start, size, off, olen));
            };

            /**
             * Insert number of lines starting from the given starting line
             * @param  {Integer} startLine a starting line to insert lines
             * @param  {String}  [lines]*  string lines to inserted
             * @method  insertLines
             */
            this.insertLines = function(startLine) {
                if (startLine < 0 || startLine > this.$lines.length) {
                    throw new RangeError(startLine);
                }

                var off = this.calcLineOffset(startLine), offlen = 0;
                if (startLine === this.$lines.length) {
                    off--;
                }

                for(var i = 1; i < arguments.length; i++) {
                    offlen += arguments[i].length + 1;
                    this.$lines.splice(startLine + i - 1, 0, new this.clazz.Line(arguments[i]));
                }
                this.fire("textUpdated", TE_STUB.$fillWith(this, "insert", startLine, arguments.length - 1, off, offlen));
            };

            this.write = function (s, offset, b) {
                if (s.length > 0) {
                    var slen    = s.length,
                        info    = this.calcLineByOffset(0, 0, offset),
                        line    = this.$lines[info[0]].$s,
                        j       = 0,
                        lineOff = offset - info[1],
                        tmp     = line.substring(0, lineOff) + s + line.substring(lineOff);

                    for(; j < slen && s[j] !== '\n'; j++) {

                    }

                    if (j >= slen) { // means the update has occurred withing one line
                        this.$lines[info[0]].$s = tmp;
                        j = 1;
                    } else {
                        this.$lines.splice(info[0], 1); // remove line
                        j = this.parse(info[0], tmp);   // re-parse the updated part of text
                    }

                    if (slen > 0) {
                        this.textLength += slen;
                        TE_STUB.$fillWith(this, "insert", info[0], j, offset, slen);
                        if (arguments.length > 2) {
                            TE_STUB.isLastStep = b;
                        }
                        this.fire("textUpdated", TE_STUB);
                        return true;
                    }
                }
                return false;
            };

            this.remove = function(offset, size, b) {
                if (size > 0) {
                    var i1   = this.calcLineByOffset(0, 0, offset),
                        i2   = this.calcLineByOffset(i1[0], i1[1], offset + size),
                        l1   = this.$lines[i1[0]].$s,
                        l2   = this.$lines[i2[0]].$s,
                        off1 = offset - i1[1],
                        off2 = offset + size - i2[1],
                        buf  = l1.substring(0, off1) + l2.substring(off2);

                    if (i2[0] === i1[0]) {
                        this.$lines.splice(i1[0], 1, new this.clazz.Line(buf));
                    } else {
                        this.$lines.splice(i1[0], i2[0] - i1[0] + 1);
                        this.$lines.splice(i1[0], 0, new this.clazz.Line(buf));
                    }

                    if (size > 0) {
                        this.textLength -= size;
                        TE_STUB.$fillWith(this, "remove", i1[0], i2[0] - i1[0] + 1, offset, size);
                        if (arguments.length > 2) {
                            TE_STUB.isLastStep = b;
                        }
                        this.fire("textUpdated", TE_STUB);
                        return true;
                    }
                }
                return false;
            };

            this.parse = function (startLine, text) {
                var size          = text.length,
                    prevIndex     = 0,
                    prevStartLine = startLine;

                for(var index = 0; index <= size; prevIndex = index, startLine++) {
                    var fi = text.indexOf("\n", index);
                    index = (fi < 0 ? size : fi);
                    this.$lines.splice(startLine, 0, new this.clazz.Line(text.substring(prevIndex, index)));
                    index++;
                }

                return startLine - prevStartLine;
            };

            this.setValue = function(text) {
                var old = this.getValue();
                if (old !== text) {
                    if (old.length > 0) {
                        var numLines = this.getLines(), txtLen = this.getTextLength();
                        this.$lines.length = 0;
                        this.$lines = [ new this.clazz.Line("") ];
                        TE_STUB.$fillWith(this, "remove", 0, numLines, 0, txtLen);
                        TE_STUB.isLastStep = false;
                        this.fire("textUpdated", TE_STUB);
                    }

                    this.$lines = [];
                    this.parse(0, text);
                    this.textLength = text.length;
                    this.fire("textUpdated", TE_STUB.$fillWith(this, "insert", 0, this.getLines(), 0, this.textLength));
                    return true;
                }
                return false;
            };
        }
    ]);

    /**
     * Single line text model implementation
     * @param  {String}  [s] the specified text the model has to be filled
     * @param  {Integer} [max] the specified maximal text length
     * @constructor
     * @class zebkit.data.SingleLineTxt
     * @extends zebkit.data.TextModel
     */
    pkg.SingleLineTxt = Class(pkg.TextModel, [
        function (s, max) {
            if (arguments.length > 1) {
                this.maxLen = max;
            }
            this.setValue(arguments.length === 0 || s === null ? "" : s);
        },

        function $prototype() {
            this.$buf    = "";
            this.extra  =  0;

            /**
             * Maximal text length. -1 means the text is not restricted
             * regarding its length.
             * @attribute maxLen
             * @type {Integer}
             * @default -1
             * @readOnly
             */
            this.maxLen = -1;

            this.$lineTags = function(i) {
                return this;
            };

            this.getValue = function(){
                return this.$buf;
            };

            this.toString = function() {
                return this.$buf;
            };

            /**
             * Get number of lines stored in the text model. The model
             * can have only one line
             * @method getLines
             * @return {Integer} a number of lines
             */
            this.getLines = function(){
                return 1;
            };

            this.getTextLength = function(){
                return this.$buf.length;
            };

            this.getLine = function(line){
                if (line !== 0) {
                    throw new RangeError(line);
                }
                return this.$buf;
            };

            this.write = function(s, offset, b) {
                // cut to the first new line character
                var j = s.indexOf("\n");
                if (j >= 0) {
                    s = s.substring(0, j);
                }

                var l = (this.maxLen > 0 && (this.$buf.length + s.length) >= this.maxLen) ? this.maxLen - this.$buf.length
                                                                                          : s.length;
                if (l !== 0) {
                    var nl = (offset === this.$buf.length ?  this.$buf + s.substring(0, l)  // append
                                                          :  this.$buf.substring(0, offset) +
                                                             s.substring(0, l) +
                                                             this.$buf.substring(offset));

                    this.$buf = nl;
                    if (l > 0) {
                        TE_STUB.$fillWith(this, "insert", 0, 1, offset, l);
                        if (arguments.length > 2) {
                            TE_STUB.isLastStep = b;
                        }
                        this.fire("textUpdated", TE_STUB);
                        return true;
                    }
                }
                return false;
            };

            this.remove = function(offset, size, b) {
                if (size > 0 && offset < this.$buf.length) {

                    // normalize size
                    if (offset + size > this.$buf.length) {
                        size = this.$buf.length - offset;
                    }

                    if (size > 0)  {
                        // build new cut line
                        var nl = this.$buf.substring(0, offset) +
                                 this.$buf.substring(offset + size);

                        if (nl.length !== this.$buf.length) {
                            this.$buf = nl;
                            TE_STUB.$fillWith(this, "remove", 0, 1, offset, size);
                            if (arguments.length > 2) {
                                TE_STUB.isLastStep = b;
                            }
                            this.fire("textUpdated", TE_STUB);
                            return true;
                        }
                    }
                }
                return false;
            };

            this.setValue = function(text){
                // cut to next line
                var i = text.indexOf('\n');
                if (i >= 0) {
                    text = text.substring(0, i);
                }

                if (this.$buf === null || this.$buf !== text) {
                    if (this.$buf !== null && this.$buf.length > 0) {
                        TE_STUB.$fillWith(this, "remove", 0, 1, 0, this.$buf.length);
                        TE_STUB.isLastStep = false;
                        this.fire("textUpdated", TE_STUB);
                    }

                    if (this.maxLen > 0 && text.length > this.maxLen) {
                        text = text.substring(0, this.maxLen);
                    }

                    this.$buf = text;
                    this.fire("textUpdated", TE_STUB.$fillWith(this, "insert", 0, 1, 0, text.length));
                    return true;
                }

                return false;
            };

            /**
             * Set the given maximal length the text can have
             * @method setMaxLength
             * @param  {Integer} max a maximal length of text
             */
            this.setMaxLength = function (max){
                if (max !== this.maxLen){
                    this.maxLen = max;
                    this.setValue("");
                }
            };
        }
    ]);

    /**
     * List model class
     * @param  {Array} [a] an array the list model has to be initialized with
     * @example

          // create list model that contains three integer elements
          var l = new zebkit.data.ListModel([1,2,3]);
          l.on("elementInserted", function(list, element, index) {
              // handle list item inserted event
              ...
          })
          ...
          l.add(10)

     * @constructor
     * @class zebkit.data.ListModel
     * @uses zebkit.data.DataModel
     * @uses zebkit.EventProducer
     */

     /**
      * Fired when a new element has been added to the list model

         list.on("elementInserted", function(src, o, i) {
             ...
         });

      * @event elementInserted
      * @param {zebkit.data.ListModel} src a list model that triggers the event
      * @param {Object}  o an element that has been added
      * @param {Integer} i an index at that the new element has been added
      */

     /**
      * Fired when an element has been removed from the list model

         list.on("elementRemoved", function(src, o, i) {
             ...
         });

      * @event elementRemoved
      * @param {zebkit.data.ListModel} src a list model that triggers the event
      * @param {Object}  o an element that has been removed
      * @param {Integer} i an index at that the element has been removed
      */

     /**
      * Fired when an element has been re-set

         list.on("elementSet", function(src, o, p, i) {
             ...
         });

      * @event elementSet
      * @param {zebkit.data.ListModel} src a list model that triggers the event
      * @param {Object}  o an element that has been set
      * @param {Object}  p a previous element
      * @param {Integer} i an index at that the element has been re-set
      */
    pkg.ListModel = Class(pkg.DataModel, zebkit.EventProducer,[
        function() {
            this.$data = (arguments.length === 0) ? [] : arguments[0];
        },

        function $prototype() {
            /**
             * Get an item stored at the given location in the list
             * @method get
             * @param  {Integer} i an item location
             * @return {object}  a list item
             */
            this.get = function(i) {
                if (i < 0 || i >= this.$data.length) {
                    throw new RangeError(i);
                }
                return this.$data[i];
            };

            /**
             * Add the given item to the end of the list
             * @method add
             * @param  {Object} o an item to be added
             */
            this.add = function(o) {
                this.$data.push(o);
                this.fire("elementInserted", [this, o, this.$data.length - 1]);
            };

            /**
             * Remove all elements from the list model
             * @method removeAll
             */
            this.removeAll = function() {
                var size = this.$data.length;
                for(var i = size - 1; i >= 0; i--) {
                    this.removeAt(i);
                }
            };

            /**
             * Remove an element at the given location of the list model
             * @method removeAt
             * @param {Integer} i a location of an element to be removed from the list
             */
            this.removeAt = function(i) {
                var re = this.$data[i];
                this.$data.splice(i, 1);
                this.fire("elementRemoved", [this, re, i]);
            };

            /**
             * Remove the given element from the list
             * @method remove
             * @param {Object} o an element to be removed from the list
             */
            this.remove = function(o) {
                for(var i = 0;i < this.$data.length; i++) {
                    if (this.$data[i] === o) {
                        this.removeAt(i);
                    }
                }
            };

            /**
             * Insert the given element into the given position of the list
             * @method insert
             * @param {Integer} i a position at which the element has to be inserted into the list
             * @param {Object} o an element to be inserted into the list
             */
            this.insert = function(i, o){
                if (i < 0 || i > this.$data.length) {
                    throw new RangeError(i);
                }
                this.$data.splice(i, 0, o);
                this.fire("elementInserted", [this, o, i]);
            };

            /**
             * Get number of elements stored in the list
             * @method count
             * @return {Integer} a number of element in the list
             */
            this.count = function () {
                return this.$data.length;
            };

            /**
             * Set the new element at the given position
             * @method setAt
             * @param  {Integer} i a position
             * @param  {Object} o a new element to be set as the list element at the given position
             * @return {Object}  previous element that was stored at the given position
             */
            this.setAt = function(i, o) {
                if (i < 0 || i >= this.$data.length) {
                    throw new RangeError(i);
                }
                var pe = this.$data[i];
                this.$data[i] = o;
                this.fire("elementSet", [this, o, pe, i]);
                return pe;
            };

            /**
             * Check if the element is in the list
             * @method contains
             * @param  {Object} o an element to be checked
             * @return {Boolean} true if the element is in the list
             */
            this.contains = function (o){
                return this.indexOf(o) >= 0;
            };

            /**
             * Get position the given element is stored in the list
             * @method indexOf
             * @param  {Object} o an element
             * @return {Integer} the element position. -1 if the element cannot be found in the list
             */
            this.indexOf = function(o){
                return this.$data.indexOf(o);
            };
        }
    ]).events("elementInserted", "elementRemoved", "elementSet");

    /**
     * Tree model item class. The structure is used by tree model to store
     * tree items values, parent and children item references.
     * @class zebkit.data.Item
     * @param  {Object} [v] the item value
     * @constructor
     */
    pkg.Item = Class([
        function(v) {
            /**
             * Array of children items of the item element
             * @attribute kids
             * @type {Array}
             * @default []
             * @readOnly
             */
            this.kids = [];

            if (arguments.length > 0) {
                this.value = v;
            }
        },

        function $prototype() {
            /**
             * Reference to a parent item
             * @attribute parent
             * @type {zebkit.data.Item}
             * @default null
             * @readOnly
             */
             this.parent = null;

             /**
              * The tree model item value. It is supposed the value should be updated
              * via execution of "setValue(...)" method of a tree model the item
              * belongs to.
              * @attribute value
              * @default null
              * @type {Object}
              * @readOnly
              */
             this.value = null;
        }
    ]).hashable();

    /**
     * Tree model class. The class is simple and handy way to keep hierarchical structure.
     *
     * @param  {zebkit.data.Item|Object} [r] a root item. As the argument you can pass "zebkit.data.Item" or
     * a JavaScript object. In the second case you can describe the tree as it is shown in example below:
     * @example

         // create tree model initialized with tree structure passed as
         // special formated JavaScript object. The tree will look as follow:
         //  "Root"
         //    |
         //    +--- "Root kid 1"
         //    +--- "Root kid 2"
         //            |
         //            +--- "Kid of kid 2"
         var tree = new zebkit.data.TreeModel({
            value:"Root",
            kids: [
                "Root kid 1",
                {
                    value: "Root kid 2",
                    kids:  [ "Kid of kid 2"]
                }
            ]
         });
         ...
         // reg item modified events handler
         tree.on("itemModified", function(tree, item, prevValue) {
             // catch item value modification
             ...
         });

         // item value has to be updated via tree model API
         tree.setValue(tree.root.kids[0], "new value");

     * @class zebkit.data.TreeModel
     * @uses zebkit.data.DataModel
     * @uses zebkit.EventProducer
     * @constructor
     */

    /**
     * Fired when the tree model item value has been updated.

     tree.on("itemModified", function(src, item, prevValue) {
         ...
     });

     * @event itemModified
     * @param {zebkit.data.TreeModel} src a tree model that triggers the event
     * @param {zebkit.data.Item}  item an item whose value has been updated
     * @param {Object} prevValue a previous value the item has had
     */

    /**
     * Fired when the tree model item has been removed

     tree.on("itemRemoved", function(src, item) {
        ...
     });

     * @event itemRemoved
     * @param {zebkit.data.TreeModel} src a tree model that triggers the event
     * @param {zebkit.data.Item}  item an item that has been removed from the tree model
     */

    /**
     * Fired when the tree model item has been inserted into the model

     tree.on("itemInserted", function(src, item) {{
        ...
     });

     * @event itemInserted
     * @param {zebkit.data.TreeModel} src a tree model that triggers the event
     * @param {zebkit.data.Item}  item an item that has been inserted into the tree model
     */
    pkg.TreeModel = Class(pkg.DataModel, [
        function(r) {
            if (arguments.length === 0) {
                this.root = new pkg.Item();
            } else {
                this.root = zebkit.instanceOf(r, pkg.Item) ? r : this.clazz.create(r);
            }
        },

        function $clazz() {
            /**
             * Create tree model item hierarchy by the given JavaScript object.
             * @param  {Object} r
             * @return {zebkit.data.Item} a built items hierarchy
             * @example
             *
             *      // create the following items hierarchy:
             *      //  "Root"
             *      //    +--- "Kid 1"
             *      //    |      +--- "Kid 1.1"
             *      //    |      |       +--- "Kid 1.1.1"
             *      //    |      +--- "Kid 2.2"
             *      //    +--- "Kid 2"
             *      //    |        +--- "Kid 2.1"
             *      //    |        +--- "Kid 2.2"
             *      //    |        +--- "Kid 2.3"
             *      //    +--- "Kid 3"
             *      //
             *      var rootItem = zebkit.data.TreeModel.create({
             *          value : "Root",
             *          kids  : [
             *              {   value : "Kid 1"
             *                  kids  : [
             *                      {  value: "Kid 1.1",
             *                         kids : "Kid 1.1.1"
             *                      },
             *                      "Kid 2.2"
             *                  ]
             *              },
             *              {   value: "Kid 2",
             *                  kids : ["Kid 2.1", "Kid 2.2", "Kid 2.3"]
             *              },
             *              "Kid 3"
             *          ]
             *      });
             *
             * @static
             * @method create
             */
            this.create = function(r, p) {
                var item = new pkg.Item(r.hasOwnProperty("value")? r.value : r);
                item.parent = arguments.length < 2 ? null : p;
                if (r.kids !== undefined && r.kids !== null) {
                    for(var i = 0; i < r.kids.length; i++) {
                        item.kids[i] = this.create(r.kids[i], item);
                    }
                }
                return item;
            };

            /**
             * Find the first tree item (starting from the specified root item) whose value equals the given value.
             * @param  {zebkit.data.Item} root a root item of the tree
             * @param  {Object} value a value to evaluate
             * @return {zebkit.data.Item} a found tree item
             * @static
             * @method findOne
             */
            this.findOne = function(root, value) {
                var res = null;
                this.find(root, value, function(item) {
                    res = item;
                    return true;
                });
                return res;
            };

            /**
             * Find all items (starting from the specified root item) whose value equals the given value.
             * @param  {zebkit.data.Item} root a root item of the tree
             * @param  {Object} value a value to evaluate
             * @param  {Function} [cb] a callback method that is called for every tree item whose value matches
             * the specified one. The method gets the found item as its argument. The method can return true
             * if the tree traversing has to be interrupted.
             * @return {Array} a list of all found item whose value matches the specified one. The array is returned
             * only if no callback method has been passed to the method.
             * @example
             *
             *      // create tree items
             *      var rootItem = zebkit.data.TreeModel.create({
             *          value: "Root",
             *          kids : [ "Kid 1", "Kid 2", "Kid 1", "Kid 3", "Kid 1" ]
             *      });
             *
             *      // find all items that have its value set to "Kid 1" and return
             *      // it as array
             *      var items = zebkit.data.TreeModel.find(rootItem, "Kid 1");
             *
             *      // find the first two "Kid 1" item in the tree using callback
             *      var items = [];
             *      zebkit.data.TreeModel.find(rootItem, "Kid 1", function(item) {
             *          items.push(item);
             *
             *          // stop the tree traversing as soon as we found two items
             *          return items.length > 1;
             *      });
             *
             * @static
             * @method find
             */
            this.find = function(root, value, cb) {
                if (arguments.length < 3) {
                    var res = [];
                    this.find(root, value, function(item) {
                        res.push(item);
                        return false;
                    });
                    return res;
                }

                if (root.value === value) {
                    if (cb.call(this, root) === true) {
                        return true;
                    }
                }

                if (root.kids !== undefined && root.kids !== null) {
                    for (var i = 0; i < root.kids.length; i++) {
                        if (this.find(root.kids[i], value, cb)) {
                            return true;
                        }
                    }
                }
                return false;
            };

            this.print = function(root, render, shift) {
                if (zebkit.instanceOf(root, pkg.TreeModel)) {
                    root = root.root;
                }

                if (arguments.length < 2) {
                    shift  = "";
                    render = null;
                } else if (arguments.length === 2) {
                    if (zebkit.isString(render)) {
                        shift  = render;
                        render = null;
                    } else {
                        shift = "";
                    }
                }

                var b = root.kids !== undefined && root.kids !== null;

                if (render !== null) {
                    render(root);
                }

                if (b) {
                    shift = shift + "    ";
                    for (var i = 0; i < root.kids.length; i++) {
                        this.print(root.kids[i], render, shift);
                    }
                }
            };
        },

        function $prototype() {
            /**
             * Reference to the tree model root item
             * @attribute root
             * @type {zebkit.data.Item}
             * @readOnly
             */
            this.root = null;

            /**
             * Iterate over tree hierarchy starting from its root element
             * @param  {zebkit.data.Item} r a root element to start traversing the tree model
             * @param  {Function} f a callback function that is called for every tree item traversed item.
             * The callback gets tree model and the item as its arguments
             * @method iterate
             */
            this.iterate = function(r, f) {
                var res = f.call(this, r);
                if (res === 1 || res === 2) { //TODO: make it clear what is a mening of the res ?
                    return r;
                }

                for (var i = 0; i < r.kids.length; i++) {
                    res = this.iterate(r.kids[i], f);
                    if (res === 2) {
                        return res;
                    }
                }
            };

            /**
             * Update a value of the given tree model item with the new one
             * @method setValue
             * @param  {zebkit.data.Item} item an item whose value has to be updated
             * @param  {Object} v   a new item value
             */
            this.setValue = function(item, v){
                var prev = item.value;
                item.value = v;
                this.fire("itemModified", [this, item, prev]);
            };

            /**
             * Add the new item to the tree model as a children element of the given parent item
             * @method add
             * @param  {zebkit.data.Item} [to] a parent item to which the new item has to be added.
             * If it has not been passed the node will be added to root.
             * @param  {Object|zebkit.data.Item} an item or value of the item to be
             * added to the parent item of the tree model
             */
            this.add = function(to,item) {
                if (arguments.length < 2) {
                    to = this.root;
                }

                this.insert(to, item, to.kids.length);
            };

            /**
             * Insert the new item to the tree model as a children element at the
             * given position of the parent element
             * @method insert
             * @param  {zebkit.data.Item} to a parent item to which the new item
             * has to be inserted
             * @param  {Object|zebkit.data.Item} an item or value of the item to be
             * inserted to the parent item
             * @param  {Integer} i a position the new item has to be inserted into
             * the parent item
             */
            this.insert = function(to, item, i) {
                if (i < 0 || to.kids.length < i) {
                    throw new RangeError(i);
                }

                if (zebkit.isString(item)) {
                    item = new pkg.Item(item);
                }
                to.kids.splice(i, 0, item);
                item.parent = to;
                this.fire("itemInserted", [this, item]);

                // !!!
                // it is necessary to analyze if the inserted item has kids and
                // generate inserted event for all kids recursively
            };

            /**
             * Remove the given item from the tree model
             * @method remove
             * @param  {zebkit.data.Item} item an item to be removed from the tree model
             */
            this.remove = function(item){
                if (item === this.root) {
                    this.root = null;
                } else {
                    if (item.kids !== undefined) {
                        for(var i = item.kids.length - 1; i >= 0; i--) {
                            this.remove(item.kids[i]);
                        }
                    }
                    item.parent.kids.splice(item.parent.kids.indexOf(item), 1);
                }

                // preserve reference to parent when we call a listener
                try {
                    this.fire("itemRemoved", [this, item]);
                } catch(e) {
                    item.parent = null;
                    throw e;
                }
                item.parent = null;
            };

            /**
             * Remove all children items from the given item of the tree model
             * @method removeKids
             * @param  {zebkit.data.Item} item an item from that all children items have to be removed
             */
            this.removeKids = function(item) {
                for(var i = item.kids.length - 1; i >= 0; i--) {
                    this.remove(item.kids[i]);
                }
            };
        }
    ]).events("itemModified", "itemRemoved", "itemInserted");

    /**
     *  Matrix model class.
     *  @constructor
     *  @param  {Array} [data] the given data as two dimensional array
     *  @param  {Integer} [rows] a number of rows
     *  @param  {Integer} [cols] a number of columns
     *  @class zebkit.data.Matrix
     *  @uses zebkit.EventProducer
     *  @uses zebkit.data.DataModel
     *  @example
     *
     *      // create matrix with 10 rows and 5 columns
     *      var matrix = zebkit.data.Matrix(10, 5);
     *
     *      matrix.get(0,0);
     *      matrix.put(0,0, "Cell [0,0]");
     *
     *  @example
     *
     *      // create matrix with 3 rows and 5 columns
     *      var matrix = zebkit.data.Matrix([
     *          [ 0, 1, 2, 3, 4 ],  // row 0
     *          [ 0, 1, 2, 3, 4 ],  // row 1
     *          [ 0, 1, 2, 3, 4 ],  // row 2
     *          [ 0, 1, 2, 3, 4 ],  // row 3
     *          [ 0, 1, 2, 3, 4 ]   // row 4
     *      ]);
     *
     *  @example
     *
     *      // create matrix with 0 rows and 0 columns
     *      var matrix = zebkit.data.Matrix();
     *
     *      // setting value for cell (2, 4) will change
     *      // matrix size to 2 rows and 3 columns
     *      matrix.put(2, 4, "Cell [row = 2, col = 4]");
     */

    /**
     * Fired when the matrix model size (number of rows or columns) is changed.

      matrix.on("matrixResized", function(src, pr, pc) {
          ...
      });

     * @event matrixResized
     * @param {zebkit.data.Matrix} src a matrix that triggers the event
     * @param {Integer}  pr a previous number of rows
     * @param {Integer}  pc a previous number of columns
     */

    /**
     * Fired when the matrix model cell has been updated.

      matrix.on("cellModified", function(src, row, col, old) {
         ...
      });

     * @event cellModified
     * @param {zebkit.data.Matrix} src a matrix that triggers the event
     * @param {Integer}  row an updated row
     * @param {Integer}  col an updated column
     * @param {Object}  old a previous cell value
     */

    /**
     * Fired when the matrix data has been re-ordered.

      matrix.on("matrixSorted", function(src, sortInfo) {
         ...
      });

     * @event matrixSorted
     * @param {zebkit.data.Matrix} src a matrix that triggers the event
     * @param {Object}  sortInfo a new data order info. The information
     * contains:
     *
     *      {
     *         func: sortFunction,
     *         name: sortFunctionName,
     *         col : sortColumn
     *      }
     *
     */

    /**
     * Fired when a row has been inserted into the matrix.

      matrix.on("matrixRowInserted", function(src, rowIndex) {
         ...
      });

     * @event matrixColInserted
     * @param {zebkit.data.Matrix} src a matrix that triggers the event
     * @param {Integer}  rowIndex a row that has been inserted
     * contains:
     */

    /**
     * Fired when a column has been inserted into the matrix.

      matrix.on("matrixColInserted", function(src, colIndex) {
         ...
      });

     * @event matrixColInserted
     * @param {zebkit.data.Matrix} src a matrix that triggers the event
     * @param {Integer}  colIndex a column that has been inserted
     * contains:
     */
    pkg.Matrix = Class(pkg.DataModel, [
        function() {
            /**
             * Number of rows in the matrix model
             * @attribute rows
             * @type {Integer}
             * @readOnly
             */

            /**
             * Number of columns in the matrix model
             * @attribute cols
             * @type {Integer}
             * @readOnly
             */

            /**
             * The multi-dimensional embedded arrays to host matrix data
             * @attribute $objs
             * @type {Array}
             * @readOnly
             * @private
             */

            if (arguments.length === 1) {
                this.$objs = arguments[0];
                this.cols = (this.$objs.length > 0) ? this.$objs[0].length : 0;
                this.rows = this.$objs.length;
            } else {
                this.$objs = [];
                this.rows = this.cols = 0;
                if (arguments.length > 1) {
                    this.setRowsCols(arguments[0], arguments[1]);
                }
            }
        },

        function $prototype() {
            /**
             * Get a matrix model cell value at the specified row and column
             * @method get
             * @param  {Integer} row a cell row
             * @param  {Integer} col a cell column
             * @return {Object}  matrix model cell value
             */
            this.get = function (row,col){
                if (row < 0 || row >= this.rows) {
                    throw new RangeError(row);
                }

                if (col < 0 || col >= this.cols) {
                    throw new RangeError(col);
                }

                return this.$objs[row] === undefined ? undefined : this.$objs[row][col];
            };

            /**
             * Get the given column data as an array object
             * @param  {Integer} col a column
             * @return {Array}  a column data
             * @method getCol
             */
            this.getCol = function(col) {
                var r = [];
                for (var i = 0; i < this.rows ; i++) {
                    r[i] = this.get(i, col);
                }
                return r;
            };

            /**
             * Get the given row data as an array object
             * @param  {Integer} row a row
             * @return {Array}  a row data
             * @method getRow
             */
            this.getRow = function(row) {
                var r = [];
                for (var i = 0; i < this.cols ; i++) {
                    r[i] = this.get(row, i);
                }
                return r;
            };

            /**
             * Get a matrix model cell value by the specified index
             * @method geti
             * @param  {Integer} index a cell index
             * @return {Object}  matrix model cell value
             */
            this.geti = function(i) {
                return this.get(Math.floor(i / this.cols), i % this.cols);
            };

            /**
             * Set the specified by row and column cell value. If the specified row or column
             * is greater than the matrix model has the model size will be adjusted to new one.
             * @method put
             * @param  {Integer} row a cell row
             * @param  {Integer} col a cell column
             * @param  {Object} obj a new cell value
             * @chainable
             */
            this.put = function(row,col,obj){
                var nr = this.rows,
                    nc = this.cols;

                if (row >= nr) {
                    nr += (row - nr + 1);
                }

                if (col >= nc) {
                    nc += (col - nc + 1);
                }

                this.setRowsCols(nr, nc);
                var old = this.$objs[row] !== undefined ? this.$objs[row][col] : undefined;
                if (old === undefined || obj !== old) {
                    // allocate array if no data for the given row exists
                    if (this.$objs[row] === undefined) {
                        this.$objs[row] = [];
                    }
                    this.$objs[row][col] = obj;
                    this.fire("cellModified", [this, row, col, old]);
                }

                return this;
            };

            /**
             * Set the specified by index cell value. The index identifies cell starting from [0,0]
             * cell till [rows,columns]. If the index is greater than size of model the model size
             * will be adjusted to new one.
             * @method puti
             * @param  {Integer} i a cell row
             * @param  {Object} obj a new cell value
             * @chainable
             */
            this.puti = function(i, obj){
                this.put( Math.floor(i / this.cols),
                          i % this.cols, obj);
                return this;
            };

            /**
             * Set the given number of rows and columns the model has to have.
             * @method setRowsCols
             * @param  {Integer} rows a new number of rows
             * @param  {Integer} cols a new number of columns
             * @chainable
             */
            this.setRowsCols = function(rows, cols){
                if (rows !== this.rows || cols !== this.cols){
                    var pc = this.cols,
                        pr = this.rows;

                    this.cols = cols;
                    this.rows = rows;

                    // re-locate matrix space
                    if (this.$objs.length > rows) {
                        this.$objs.length = rows;   // shrink number of rows
                    }

                    // shrink columns
                    if (pc > cols) {
                        for(var i = 0; i < this.$objs.length; i++) {
                            // check if data for columns has been allocated and the size
                            // is greater than set number of columns
                            if (this.$objs[i] !== undefined && this.$objs[i].length > cols) {
                                this.$objs[i].length = cols;
                            }
                        }
                    }

                    this.fire("matrixResized", [this, pr, pc]);
                }
                return this;
            };

             /**
             * Set the given number of rows the model has to have.
             * @method setRows
             * @param  {Integer} rows a new number of rows
             * @chainable
             */
            this.setRows = function(rows) {
                this.setRowsCols(rows, this.cols);
                return this;
            };

            /**
             * Set the given number of columns the model has to have.
             * @method setCols
             * @param  {Integer} cols a new number of columns
             * @chainable
             */
            this.setCols = function(cols) {
                this.setRowsCols(this.rows, cols);
                return this;
            };

            /**
             * Remove specified number of rows from the model starting
             * from the given row.
             * @method removeRows
             * @param  {Integer} begrow a start row
             * @param  {Integer} count  a number of rows to be removed
             * @chainable
             */
            this.removeRows = function(begrow, count) {
                if (arguments.length === 1) {
                    count = 1;
                }

                if (begrow < 0 || begrow + count > this.rows) {
                    throw new RangeError(begrow);
                }

                this.$objs.splice(begrow, count);
                this.rows -= count;
                this.fire("matrixResized", [this, this.rows + count, this.cols]);
                return this;
            };

            /**
             * Remove specified number of columns from the model starting
             * from the given column.
             * @method removeCols
             * @param  {Integer}  begcol a start column
             * @param  {Integer} count  a number of columns to be removed
             * @chainable
             */
            this.removeCols = function (begcol, count){
                if (arguments.length === 1) {
                    count = 1;
                }

                if (begcol < 0 || begcol + count > this.cols) {
                    throw new RangeError(begcol);
                }

                for(var i = 0; i < this.$objs.length; i++) {
                    if (this.$objs[i] !== undefined && this.$objs[i].length > 0) {
                        this.$objs[i].splice(begcol, count);
                    }
                }

                this.cols -= count;
                this.fire("matrixResized", [this, this.rows, this.cols + count]);
                return this;
            };

            /**
             * Insert the given number of rows at the specified row
             * @param  {Integer} row   a starting row to insert
             * @param  {Integer} count a number of rows to be added
             * @method insertRows
             * @chainable
             */
            this.insertRows = function(row, count) {
                if (arguments.length === 1) {
                    count = 1;
                }

                var i = 0;
                if (row <= this.$objs.length - 1) {
                    for(i = 0; i < count; i++) {
                        this.$objs.splice(row, 0, undefined);
                        this.fire("matrixRowInserted", [this, row + i]);
                    }
                } else {
                    for(i = 0; i < count; i++) {
                        this.fire("matrixRowInserted", [this, row + i]);
                    }
                }

                this.rows += count;
                this.fire("matrixResized", [this, this.rows - count, this.cols]);
                return this;
            };

            /**
             * Insert the given number of columns at the specified column
             * @param  {Integer} col   a starting column to insert
             * @param  {Integer} count a number of columns to be added
             * @method insertCols
             * @chainable
             */
            this.insertCols = function(col, count) {
                if (arguments.length === 1) {
                    count = 1;
                }

                if (this.$objs.length  > 0) {
                    for(var j = 0; j < count; j++) {
                        for(var i = 0; i < this.rows; i++) {
                            if (this.$objs[i] !== undefined && j <= this.$objs[i].length) {
                                this.$objs[i].splice(col, 0, undefined);
                            }
                        }
                        this.fire("matrixColInserted", [this, col + j]);
                    }
                }

                this.cols += count;
                this.fire("matrixResized", [this, this.rows, this.cols - count]);
                return this;
            };

            /**
             * Insert the column data at the given column
             * @param  {Integer} col a column
             * @param  {Array} [data] a column data
             * @method insertCol
             * @chainable
             */
            this.insertCol = function(col, data) {
                this.insertCols(col, 1);

                if (arguments.length > 0) {
                    for (var i = 0; i < Math.min(data.length, this.rows); i++) {
                        this.put(i, col, data[i]);
                    }
                }
                return this;
            };

            /**
             * Insert the row data at the given row
             * @param  {Integer} row a row
             * @param  {Array} [data] a row data
             * @method insertRow
             * @chainable
             */
            this.insertRow = function(row, data) {
                this.insertRows(row, 1);

                if (arguments.length > 0) {
                    for (var i = 0; i < Math.min(data.length, this.cols); i++) {
                        this.put(row, i, data[i]);
                    }
                }
                return this;
            };

            /**
             * Sort the given column of the matrix model.
             * @param  {Integer} col a column to be re-ordered
             * @param  {Function} [f] an optional sort function. The name of the function
             * is grabbed to indicate type of the sorting the method does. For instance:
             * "descent", "ascent".
             * @method sortCol
             */
            this.sortCol = function(col, f) {
                if (arguments.length < 2) {
                    f = pkg.descent;
                }

                this.$objs.sort(function(a, b) {
                    return f(a[col], b[col]);
                });

                this.fire("matrixSorted", [ this, { col : col,
                                            func: f,
                                            name: zebkit.$FN(f).toLowerCase() }]);
            };
        }
    ]).events("matrixResized", "cellModified",
              "matrixSorted",  "matrixRowInserted",
              "matrixColInserted");
});
zebkit.package("io", function(pkg, Class) {
    /**
     * The module provides number of classes to help to communicate with remote services and servers by HTTP,
     * JSON-RPC, XML-RPC protocols.
     *
     *       // shortcut method to perform HTTP GET request
     *       zebkit.io.GET("http://test.com").then(function(req) {
     *           // handle request
     *           req.responseText
     *           ...
     *       }).catch(function(exception) {
     *           // handle error
     *       });
     *
     * @class zebkit.io
     * @access package
     */

    // TODO: Web dependencies:
    //    -- Uint8Array
    //    -- ArrayBuffer

    // !!!
    // b64 is supposed to be used with binary stuff, applying it to utf-8 encoded data can bring to error
    // !!!

    var HEX    = "0123456789ABCDEF",
        b64str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

    /**
     * Generate UUID of the given length
     * @param {Integer} [size] the generated UUID length. The default size is 16 characters.
     * @return {String} an UUID
     * @method  UID
     * @for  zebkit.io
     */
    pkg.UID = function(size) {
        if (arguments.length === 0) {
            size = 16;
        }
        var id = "";
        for (var i = 0; i < size; i++) {
            id = id + HEX[~~(Math.random() * 16)];
        }
        return id;
    };

    /**
     * Encode the given string into base64
     * @param  {String} input a string to be encoded
     * @method  b64encode
     * @for zebkit.io
     */
    pkg.b64encode = function(input) {
        var out = [], i = 0, len = input.length, c1, c2, c3;
        if (input instanceof ArrayBuffer) {
            input = new Uint8Array(input);
        }
        input.charCodeAt = function(i) { return this[i]; };

        if (Array.isArray(input)) {
            input.charCodeAt = function(i) { return this[i]; };
        }

        while(i < len) {
            c1 = input.charCodeAt(i++) & 0xff;
            out.push(b64str.charAt(c1 >> 2));
            if (i === len) {
                out.push(b64str.charAt((c1 & 0x3) << 4), "==");
                break;
            }

            c2 = input.charCodeAt(i++);
            out.push(b64str.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)));
            if (i === len) {
                out.push(b64str.charAt((c2 & 0xF) << 2), "=");
                break;
            }

            c3 = input.charCodeAt(i++);
            out.push(b64str.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)), b64str.charAt(c3 & 0x3F));
        }

        return out.join('');
    };

    /**
     * Decode the base64 encoded string
     * @param {String} input base64 encoded string
     * @return {String} a string
     * @for zebkit.io
     * @method b64decode
     */
    pkg.b64decode = function(input) {
        var output = [], chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

        while ((input.length % 4) !== 0) {
            input += "=";
        }

        for(var i=0; i < input.length;) {
            enc1 = b64str.indexOf(input.charAt(i++));
            enc2 = b64str.indexOf(input.charAt(i++));
            enc3 = b64str.indexOf(input.charAt(i++));
            enc4 = b64str.indexOf(input.charAt(i++));

            chr1 = (enc1 << 2) | (enc2 >> 4);
            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
            chr3 = ((enc3 & 3) << 6) | enc4;
            output.push(String.fromCharCode(chr1));
            if (enc3 !== 64) {
                output.push(String.fromCharCode(chr2));
            }
            if (enc4 !== 64) {
                output.push(String.fromCharCode(chr3));
            }
        }
        return output.join('');
    };

    pkg.dateToISO8601 = function(d) {
        function pad(n) { return n < 10 ? '0'+ n : n; }
        return [ d.getUTCFullYear(), '-', pad(d.getUTCMonth()+1), '-', pad(d.getUTCDate()), 'T', pad(d.getUTCHours()), ':',
                 pad(d.getUTCMinutes()), ':', pad(d.getUTCSeconds()), 'Z'].join('');
    };

    // http://webcloud.se/log/JavaScript-and-ISO-8601/
    pkg.ISO8601toDate = function(v) {
        var regexp = ["([0-9]{4})(-([0-9]{2})(-([0-9]{2})", "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?",
                      "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?"].join(''), d = v.match(new RegExp(regexp)),
                      offset = 0, date = new Date(d[1], 0, 1);

        if (d[3]) {
            date.setMonth(d[3] - 1);
        }

        if (d[5]) {
            date.setDate(d[5]);
        }

        if (d[7])  {
            date.setHours(d[7]);
        }

        if (d[8])  {
            date.setMinutes(d[8]);
        }

        if (d[10]) {
            date.setSeconds(d[10]);
        }

        if (d[12]) {
            date.setMilliseconds(Number("0." + d[12]) * 1000);
        }

        if (d[14]) {
            offset = (Number(d[16]) * 60) + Number(d[17]);
            offset *= ((d[15] === '-') ? 1 : -1);
        }

        offset -= date.getTimezoneOffset();
        date.setTime(Number(date) + (offset * 60 * 1000));
        return date;
    };

    /**
     * HTTP request class. This class provides API to generate different
     * (GET, POST, etc) HTTP requests
     * @class zebkit.io.HTTP
     * @constructor
     * @param {String} url an URL to a HTTP resource
     */
    pkg.HTTP = Class([
        function(url) {
            this.url = url;
            this.header = {};
        },

        function $prototype() {
            /**
             * Perform HTTP GET request with the given query parameters.
             * @param {Object} [q] a dictionary of query parameters
             * @return {zebkit.DoIt} an object to get response
             * @example
             *
             *     // GET request with the number of query parameters
             *     var result = zebkit.io.HTTP("google.com").GET({
             *         param1: "var1",
             *         param3: "var2",
             *         param3: "var3"
             *     }).then(function(req) {
             *         // handle response
             *         req.responseText;
             *     }).catch(function(e)  {
             *         // handle error
             *         ...
             *     });
             *
             * @method GET
             */
            this.GET = function(q) {
                var u = this.url + ((arguments.length === 0 || q === null) ? ''
                                                                           : ((this.url.indexOf("?") > 0) ? '&'
                                                                                                          : '?') + zebkit.URI.toQS(q, true));
                return this.SEND("GET", u);
            };

            /**
             * Perform HTTP POST request with the give data to be sent.
             * @param {String|Object} d a data to be sent by HTTP POST request.  It can be
             * either a parameters set or a string.
             * @return {zebkit.DoIt} an object to get response
             * @example
             *
             *     // asynchronously send POST
             *     zebkit.io.HTTP("google.com").POST("Hello").then(function(req) {
             *         // handle HTTP GET response ...
             *     }).catch(function(e) {
             *         // handle error ...
             *     });
             *
             * Or you can pass a number of parameters to be sent:
             *
             *     // send parameters synchronously by HTTP POST request
             *     zebkit.io.HTTP("google.com").POST({
             *         param1: "val1",
             *         param2: "val3",
             *         param3: "val3"
             *     }).then(function(req) {
             *          // handle HTTP GET response ...
             *     }).catch(function(e) {
             *          // handle error ...
             *    });
             *
             * @method POST
             */
            this.POST = function(d) {
                // if the passed data is simple dictionary object encode it as POST
                // parameters
                //
                // TODO: think also about changing content type
                // "application/x-www-form-urlencoded; charset=UTF-8"
                if (d !== null && zebkit.isString(d) === false && d.constructor === Object) {
                    d = zebkit.URI.toQS(d, false);
                }

                return this.SEND("POST", this.url, d);
            };

            /**
             * Universal HTTP request method that can be used to generate a HTTP request with
             * any HTTP method to the given URL with the given data to be sent asynchronously.
             * @param {String}   method   an HTTP method (GET, POST, DELETE, PUT, etc)
             * @param {String}   url      an URL
             * @param {String}   [data]   a data to be sent to the given URL
             * @return {zebkit.DoIt} an object to handle result
             * @method SEND
             */
            this.SEND = function(method, url, data) {
                var req = zebkit.environment.getHttpRequest();

                req.open(method, url, true);
                for (var k in this.header) {
                    req.setRequestHeader(k, this.header[k]);
                }

                return new zebkit.DoIt(function() {
                    var jn    = this.join(),
                        $this = this;

                    req.onreadystatechange = function() {
                        if (req.readyState === 4) {
                            // evaluate http response
                            if (req.status >= 400 || req.status < 100) {
                                var e = new Error("HTTP error '" + req.statusText + "', code = " + req.status + " '" + url + "'");
                                e.status     = req.status;
                                e.statusText = req.statusText;
                                e.readyState = req.readyState;
                                $this.error(e);
                            } else {
                                jn(req);
                            }
                        }
                    };

                    try {
                        req.send(arguments.length > 2 ? data : null);
                    } catch(e) {
                        this.error(e);
                    }
                });
            };
        }
    ]);

    /**
     * Shortcut method to perform HTTP GET requests.

        zebkit.io.GET("http://test.com").then(function(request) {
            // handle result ...
        }).catch(function(e) {
            // handle error ...
        });

        var res = zebkit.io.GET("http://test.com", {
            param1 : "var1",
            param1 : "var2",
            param1 : "var3"
        }).then(function(req) {
            // handle result ...
        });

     * @param {String|Object} url an URL
     * @param {Object} [parameters] a dictionary of query parameters
     * @return  {zebkit.DoIt} an object to handle result
     * @method GET
     * @for zebkit.io
     */
    pkg.GET = function(url) {
        var http = new pkg.HTTP(url);
        return http.GET.apply(http, Array.prototype.slice.call(arguments, 1));
    };

    /**
     * Shortcut method to perform HTTP POST requests.

        zebkit.io.POST("http://test.com", null).then(function(request) {
            // handle result
            ...
        }).catch(function(e) {
            // handle error ...
        });

        var res = zebkit.io.POST("http://test.com", {
            param1 : "var1",
            param1 : "var2",
            param1 : "var3"
        }).then(function(request) {
            // handle result
            ...
        });

        zebkit.io.POST("http://test.com", "request").then(function(request) {
            // handle error
            ...
        });

     * @param {String} url an URL
     * @param {Object} [data] a data or form data parameters
     * @return  {zebkit.DoIt} an object to handle result
     * @method  POST
     * @for zebkit.io
     */
    pkg.POST = function(url) {
        var http = new pkg.HTTP(url);
        return http.POST.apply(http, Array.prototype.slice.call(arguments, 1));
    };

    /**
     * A remote service connector class. It is supposed the class has to be extended with
     * different protocols like RPC, JSON etc. The typical pattern of connecting to
     * a remote service is shown below:

        // create service connector that has two methods "a()" and "b(param1)"
        var service = new zebkit.io.Service("http://myservice.com", [
            "a", "b"
        ]);

        // call the methods of the remote service
        service.a();
        service.b(10);

     * Also the methods of a remote service can be called asynchronously. In this case
     * a callback method has to be passed as the last argument of called remote methods:

        // create service connector that has two methods "a()" and "b(param1)"
        var service = new zebkit.io.Service("http://myservice.com", [
            "a", "b"
        ]);

        // call "b" method from the remote service asynchronously
        service.b(10, function(res) {
            // handle a result of the remote method execution here
            ...
        });
     *
     * Ideally any specific remote service extension of "zebkit.io.Service"
     * class has to implement two methods:

        - **encode** to say how the given remote method with passed parameters have
        to be transformed into a concrete service side protocol (JSON, XML, etc)
        - **decode** to say how the specific service response has to be converted into
        JavaScript object

     * @class zebkit.io.Service
     * @constructor
     * @param {String} url an URL of remote service
     * @param {Array} methods a list of methods names the remote service provides
     */
    pkg.Service = Class([
        function(url, methods) {
            var $this = this;
            /**
             * Remote service url
             * @attribute url
             * @readOnly
             * @type {String}
             */
            this.url = url;

            /**
             * Remote service methods names
             * @attribute methods
             * @readOnly
             * @type {Array}
             */
            if (Array.isArray(methods) === false) {
                methods = [ methods ];
            }

            for(var i = 0; i < methods.length; i++) {
                (function() {
                    var name = methods[i];
                    $this[name] = function() {
                        var args = Array.prototype.slice.call(arguments);
                        return this.send(url, this.encode(name, args)).then(function(req) {
                            if (req.status === 200) {
                                return $this.decode(req.responseText);
                            } else {
                                this.error(new Error("Status: " + req.status + ", '" + req.statusText + "'"));
                            }
                        });
                    };
                })();
            }
        },

        function $prototype() {
            this.contentType = null;

             /**
              * Send the given data to the given url and return a response. Callback
              * function can be passed for asynchronous result handling.
              * @protected
              * @param  {String}   url an URL
              * @param  {String}   data  a data to be send
              * @return {zebkit.DoIt}  a result
              * @method  send
              */
            this.send = function(url, data) {
                var http = new pkg.HTTP(url);
                if (this.contentType !== null) {
                    http.header['Content-Type'] = this.contentType;
                }
                return http.POST(data);
            };
        }

        /**
         * Transforms the given remote method execution with the specified parameters
         * to service specific protocol.
         * @param {String} name a remote method name
         * @param {Array} args an passed to the remote method arguments
         * @return {String} a remote service specific encoded string
         * @protected
         * @method encode
         */

        /**
         * Transforms the given remote method response to a JavaScript
         * object.
         * @param {String} name a remote method name
         * @return {Object} a result of the remote method calling as a JavaScript
         * object
         * @protected
         * @method decode
         */
    ]);

    /**
     * Build invoke method that calls a service method.
     * @param  {zebkit.Class} clazz a class
     * @param  {String} url an URL
     * @param  {String} a service method name
     * @return {Function} a wrapped method to call RPC method with
     * @private
     * @method  invoke
     * @static
     */
    pkg.Service.invoke = function(clazz, url, method) {
        var rpc = new clazz(url, method);
        return function() {
            return rpc[method].apply(rpc, arguments);
        };
    };

    /**
     * The class is implementation of JSON-RPC remote service connector.

        // create JSON-RPC connector to a remote service that
        // has three remote methods
        var service = new zebkit.io.JRPC("json-rpc.com", [
            "method1", "method2", "method3"
        ]);

        // synchronously call remote method "method1"
        service.method1();

        // asynchronously call remote method "method1"
        service.method1(function(res) {
            ...
        });

     * @class zebkit.io.JRPC
     * @constructor
     * @param {String} url an URL of remote service
     * @param {Array} methods a list of methods names the remote service provides
     * @extends zebkit.io.Service
     */
    pkg.JRPC = Class(pkg.Service, [
        function $prototype() {
            this.version     = "2.0";
            this.contentType = "application/json; charset=ISO-8859-1;";

            this.encode = function(name, args) {
                return zebkit.environment.stringifyJSON({
                    jsonrpc : this.version,
                    method  : name,
                    params  : args,
                    id      : pkg.UID() });
            };

            this.decode = function(r) {
                if (r === null || r.length === 0) {
                    throw new Error("Empty JSON result string");
                }

                r = zebkit.environment.parseJSON(r);
                if (r.error !== undefined) {
                    throw new Error(r.error.message);
                }

                if (r.result === undefined || r.id === undefined) {
                    throw new Error("Wrong JSON response format");
                }
                return r.result;
            };
        }
    ]);

    /**
     * Shortcut to call the specified method of a JSON-RPC service.
     * @param  {String} url an URL
     * @param  {String} method a method name
     * @for zebkit.io.JRPC
     * @static
     * @method invoke
     */
    pkg.JRPC.invoke = function(url, method) {
        return pkg.Service.invoke(pkg.JRPC, url, method);
    };

    pkg.Base64 = function(s) {
        if (arguments.length > 0) {
            this.encoded = pkg.b64encode(s);
        }
    };

    pkg.Base64.prototype.toString = function() { return this.encoded; };
    pkg.Base64.prototype.decode   = function() { return pkg.b64decode(this.encoded); };

    /**
     * The class is implementation of XML-RPC remote service connector.

        // create XML-RPC connector to a remote service that
        // has three remote methods
        var service = new zebkit.io.XRPC("xmlrpc.com", [
            "method1", "method2", "method3"
        ]);

        // synchronously call remote method "method1"
        service.method1();

        // asynchronously call remote method "method1"
        service.method1(function(res) {
            ...
        });

     * @class zebkit.io.XRPC
     * @constructor
     * @extends zebkit.io.Service
     * @param {String} url an URL of remote service
     * @param {Array} methods a list of methods names the remote service provides
     */
    pkg.XRPC = Class(pkg.Service, [
        function $prototype() {
            this.contentType = "text/xml";

            this.encode = function(name, args) {
                var p = ["<?xml version=\"1.0\"?>\n<methodCall><methodName>", name, "</methodName><params>"];
                for(var i=0; i < args.length;i++) {
                    p.push("<param>");
                    this.encodeValue(args[i], p);
                    p.push("</param>");
                }
                p.push("</params></methodCall>");
                return p.join('');
            };

            this.encodeValue = function(v, p)  {
                if (v === null) {
                    throw new Error("Null is not allowed");
                }

                if (zebkit.isString(v)) {
                    v = v.replace("<", "&lt;");
                    v = v.replace("&", "&amp;");
                    p.push("<string>", v, "</string>");
                } else {
                    if (zebkit.isNumber(v)) {
                        if (Math.round(v) === v) {
                            p.push("<i4>", v.toString(), "</i4>");
                        } else {
                            p.push("<double>", v.toString(), "</double>");
                        }
                    } else {
                        if (zebkit.isBoolean(v)) {
                            p.push("<boolean>", v?"1":"0", "</boolean>");
                        } else {
                            if (v instanceof Date)  {
                                p.push("<dateTime.iso8601>", pkg.dateToISO8601(v), "</dateTime.iso8601>");
                            } else {
                                if (Array.isArray(v))  {
                                    p.push("<array><data>");
                                    for(var i=0;i<v.length;i++) {
                                        p.push("<value>");
                                        this.encodeValue(v[i], p);
                                        p.push("</value>");
                                    }
                                    p.push("</data></array>");
                                } else {
                                    if (v instanceof pkg.Base64) {
                                        p.push("<base64>", v.toString(), "</base64>");
                                    } else {
                                        p.push("<struct>");
                                        for (var k in v) {
                                            if (v.hasOwnProperty(k)) {
                                                p.push("<member><name>", k, "</name><value>");
                                                this.encodeValue(v[k], p);
                                                p.push("</value></member>");
                                            }
                                        }
                                        p.push("</struct>");
                                    }
                                }
                            }
                        }
                    }
                }
            };

            this.decodeValue = function (node) {
                var tag = node.tagName.toLowerCase(), i = 0;

                if (tag === "struct") {
                     var p = {};
                     for(i = 0; i < node.childNodes.length; i++) {
                        var member = node.childNodes[i],  // <member>
                            key    = member.childNodes[0].childNodes[0].nodeValue.trim(); // <name>/text()
                        p[key] = this.decodeValue(member.childNodes[1].childNodes[0]);   // <value>/<xxx>
                    }
                    return p;
                }

                if (tag === "array") {
                    var a = [];
                    node = node.childNodes[0]; // <data>
                    for(i = 0; i < node.childNodes.length; i++) {
                        a[i] = this.decodeValue(node.childNodes[i].childNodes[0]); // <value>
                    }
                    return a;
                }

                var v = node.childNodes[0].nodeValue.trim();
                switch (tag) {
                    case "datetime.iso8601": return pkg.ISO8601toDate(v);
                    case "boolean": return v === "1";
                    case "int":
                    case "i4":     return parseInt(v, 10);
                    case "double": return Number(v);
                    case "base64":
                        var b64 = new pkg.Base64();
                        b64.encoded = v;
                        return b64;
                    case "string": return v;
                }
                throw new Error("Unknown tag " + tag);
            };

            this.decode = function(r) {
                var p = zebkit.environment.parseXML(r),
                    c = p.getElementsByTagName("fault");

                if (c.length > 0) {
                    var err = this.decodeValue(c[0].getElementsByTagName("struct")[0]);
                    throw new Error(err.faultString);
                }

                c = p.getElementsByTagName("methodResponse")[0];
                c = c.childNodes[0].childNodes[0]; // <params>/<param>
                if (c.tagName.toLowerCase() === "param") {
                    return this.decodeValue(c.childNodes[0].childNodes[0]); // <value>/<xxx>
                }
                throw new Error("Incorrect XML-RPC response");
            };
        }
    ]);

    /**
     * Shortcut to call the specified method of a XML-RPC service.
     * @param  {String} url an URL
     * @param  {String} method a method name
     * @for zebkit.io.XRPC
     * @method invoke
     * @static
     */
    pkg.XRPC.invoke = function(url, method) {
        return pkg.Service.invoke(pkg.XRPC, url, method);
    };
});

zebkit.package("layout", function(pkg, Class) {
    /**
     * Layout package provides number of classes, interfaces, methods and variables that allows
     * developers easily implement rules based layouting of hierarchy of rectangular elements.
     * The package has no relation to any concrete UI, but it can be applied to a required UI
     * framework very easily. In general layout manager requires an UI component to provide:
     *    - **setLocation(x,y)** method
     *    - **setSize(w,h)** method
     *    - **setBounds()** method
     *    - **getPreferredSize(x,y)** method
     *    - **getTop(), getBottom(), getRight(), getLeft()** methods
     *    - **constraints** read only property
     *    - **width, height, x, y** read only metrics properties
     *    - **kids** read only property that keep all children components
     *
     * @access package
     * @class zebkit.layout
     */

     /**
      * Find a direct children element for the given children component
      * and the specified parent component
      * @param  {zebkit.layout.Layoutable} parent  a parent component
      * @param  {zebkit.layout.Layoutable} child  a children component
      * @return {zebkit.layout.Layoutable}  a direct children component
      * @method getDirectChild
      * @for  zebkit.layout
      */
    pkg.getDirectChild = function(parent, child) {
        for(; child !== null && child.parent !== parent; child = child.parent) {}
        return child;
    };

    /**
     * Layout manager interface is simple interface that all layout managers have to
     * implement. One method has to calculate preferred size of the given component and
     * another one method has to perform layouting of children components of the given
     * target component.
     * @class zebkit.layout.Layout
     * @interface zebkit.layout.Layout
     */

    /**
     * Calculate preferred size of the given component
     * @param {zebkit.layout.Layoutable} t a target layoutable component
     * @method calcPreferredSize
     */

    /**
     * Layout children components of the specified layoutable target component
     * @param {zebkit.layout.Layoutable} t a target layoutable component
     * @method doLayout
     */
    pkg.Layout = new zebkit.Interface([
        "abstract",
            function doLayout(target) {},
            function calcPreferredSize(target) {}
    ]);

    /**
     * Find a direct component located at the given location of the specified parent component
     * and the specified parent component
     * @param  {Integer} x a x coordinate relatively to the parent component
     * @param  {Integer} y a y coordinate relatively to the parent component
     * @param  {zebkit.layout.Layoutable} parent  a parent component
     * @return {zebkit.layout.Layoutable} an index of direct children component
     * or -1 if no a children component can be found
     * @method getDirectAt
     * @for  zebkit.layout
     */
    pkg.getDirectAt = function(x, y, p){
        for(var i = 0;i < p.kids.length; i++){
            var c = p.kids[i];
            if (c.isVisible === true && c.x <= x && c.y <= y && c.x + c.width > x && c.y + c.height > y) {
                return i;
            }
        }
        return -1;
    };

    /**
     * Get a top (the highest in component hierarchy) parent component
     * of the given component
     * @param  {zebkit.layout.Layoutable} c a component
     * @return {zebkit.layout.Layoutable}  a top parent component
     * @method getTopParent
     * @for  zebkit.layout
     */
    pkg.getTopParent = function(c){
        for(; c !== null && c.parent !== null; c = c.parent) {}
        return c;
    };

    /**
     * Translate the given relative location into the parent relative location.
     * @param  {Integer} [x] a x coordinate relatively  to the given component
     * @param  {Integer} [y] a y coordinate relatively  to the given component
     * @param  {zebkit.layout.Layoutable} c a component
     * @param  {zebkit.layout.Layoutable} [p] a parent component
     * @return {Object} a relative to the given parent UI component location:
     *
     *       { x:{Integer}, y:{Integer} }
     *
     * @method toParentOrigin
     * @for  zebkit.layout
     */
    pkg.toParentOrigin = function(x,y,c,p){
        if (arguments.length === 1) {
            c = x;
            x = y = 0;
            p = null;
        } else if (arguments.length < 4) {
            p = null;
        }

        while (c !== null && c !== p) {
            x += c.x;
            y += c.y;
            c = c.parent;
        }

        if (c === null) {
            //throw new Error("Invalid params");
        }

        return { x:x, y:y };
    };

    /**
     * Convert the given component location into relative
     * location of the specified children component successor.
     * @param  {Integer} x a x coordinate relatively to the given
     * component
     * @param  {Integer} y a y coordinate relatively to the given
     * component
     * @param  {zebkit.layout.Layoutable} p a component
     * @param  {zebkit.layout.Layoutable} c a children successor component
     * @return {Object} a relative location
     *
     *      { x:{Integer}, y:{Integer} }
     *
     * @method toChildOrigin
     * @for  zebkit.layout
     */
    pkg.toChildOrigin = function(x, y, p, c){
        while(c !== p){
            x -= c.x;
            y -= c.y;
            c = c.parent;
        }
        return { x:x, y:y };
    };

    /**
     * Calculate maximal preferred width and height of
     * children component of the given target component.
     * @param  {zebkit.layout.Layoutable} target a target component
     * @return {Object} a maximal preferred width and height
     *
     *       { width:{Integer}, height:{Integer} }
     *
     * @method getMaxPreferredSize
     * @for zebkit.layout
     */
    pkg.getMaxPreferredSize = function(target) {
        var maxWidth  = 0,
            maxHeight = 0;

        for(var i = 0;i < target.kids.length; i++) {
            var l = target.kids[i];
            if (l.isVisible === true){
                var ps = l.getPreferredSize();
                if (ps.width > maxWidth) {
                    maxWidth = ps.width;
                }

                if (ps.height > maxHeight) {
                    maxHeight = ps.height;
                }
            }
        }
        return { width: maxWidth, height: maxHeight };
    };

    pkg.$align = function(a, cellSize, compSize) {
        if (a === "left" || a === "top" || a === "stretch") {
            return 0;
        } else if (a === "right" || a === "bottom") {
            return cellSize - compSize;
        } else if (a === "center") {
            return Math.floor((cellSize - compSize) / 2);
        } else {
            zebkit.dumpError("Invalid alignment '" + a + "'");
            return 0;
        }
    };

    /**
     * Test if the given parent component is ancestor of the specified component.
     * @param  {zebkit.layout.Layoutable}  p a parent component
     * @param  {zebkit.layout.Layoutable}  c a component
     * @return {Boolean} true if the given parent is ancestor of the specified component
     * @for  zebkit.layout
     * @method  isAncestorOf
     */
    pkg.isAncestorOf = function(p, c){
        for(; c !== null && c !== p; c = c.parent) {}
        return c !== null;
    };

    /**
     * Layoutable class defines rectangular component that has elementary metrical properties like width,
     * height and location and can be a participant of layout management process. Layoutable component is
     * container that can contains other layoutable component as its children. The children components are
     * ordered by applying a layout manager of its parent component.
     * @class zebkit.layout.Layoutable
     * @constructor
     * @uses zebkit.layout.Layout
     * @uses zebkit.EventProducer
     * @uses zebkit.PathSearch
     */
    pkg.Layoutable = Class(pkg.Layout, zebkit.EventProducer, zebkit.PathSearch, [
        function() {
            /**
             *  Reference to children components
             *  @attribute kids
             *  @type {Array}
             *  @default empty array
             *  @readOnly
             */
            this.kids = [];

            /**
            * Layout manager that is used to order children layoutable components
            * @attribute layout
            * @default itself
            * @readOnly
            * @type {zebkit.layout.Layout}
            */
            this.layout = this;
        },

        function $prototype() {
            /**
             * x coordinate
             * @attribute x
             * @default 0
             * @readOnly
             * @type {Integer}
             */

            /**
            * y coordinate
            * @attribute y
            * @default 0
            * @readOnly
            * @type {Integer}
            */

            /**
            * Width of rectangular area
            * @attribute width
            * @default 0
            * @readOnly
            * @type {Integer}
            */

            /**
            * Height of rectangular area
            * @attribute height
            * @default 0
            * @readOnly
            * @type {Integer}
            */

            /**
            * Indicate a layoutable component visibility
            * @attribute isVisible
            * @default true
            * @readOnly
            * @type {Boolean}
            */

            /**
            * Indicate a layoutable component validity
            * @attribute isValid
            * @default false
            * @readOnly
            * @type {Boolean}
            */

            /**
            * Reference to a parent layoutable component
            * @attribute parent
            * @default null
            * @readOnly
            * @type {zebkit.layout.Layoutable}
            */

            this.x = this.y = this.height = this.width = this.cachedHeight = 0;

            this.psWidth = this.psHeight = this.cachedWidth = -1;
            this.isLayoutValid = this.isValid = false;

            this.layout = null;

            /**
             * The component layout constraints. The constraints is specific to
             * the parent component layout manager value that customizes the
             * children component layouting on the parent component.
             * @attribute constraints
             * @default null
             * @type {Object}
             */
            this.constraints = this.parent = null;
            this.isVisible = true;

            this.$matchPath = function(node, name) {

                if (name[0] === '~') {
                    return node.clazz !== undefined &&
                           node.clazz !== null &&
                           zebkit.instanceOf(node, zebkit.Class.forName(name.substring(1)));
                } else {
                    return node.clazz !== undefined &&
                           node.clazz.$name !== undefined &&
                           node.clazz.$name === name;
                }
            };

            /**
             * Set the given id for the component
             * @param {String} id an ID to be set
             * @method setId
             * @chainable
             */
            this.setId = function(id) {
                this.id = id;
                return this;
            };

            /**
             * Set the component properties. This is wrapper for "properties" method to supply
             * properties setter method.
             * @param  {String} [path]  a path to find children components
             * @param  {Object} props a dictionary of properties to be applied
             * @method setProperties
             */
            this.setProperties = function() {
                this.properties.apply(this, arguments);
                return this;
            };

            /**
             * Apply the given set of properties to the given component or a number of children
             * its components.
             * @example
             *
             *     var c = new zebkit.layout.Layoutable();
             *     c.properties({
             *         width: [100, 100],
             *         location: [10,10],
             *         layout: new zebkit.layout.BorderLayout()
             *     })
             *
             *     c.add(new zebkit.layout.Layoutable()).add(zebkit.layout.Layoutable())
             *                                          .add(zebkit.layout.Layoutable());
             *     c.properties("//*", {
             *         size: [100, 200]
             *     });
             *
             * @param  {String} [path]  a path to find children components
             * @param  {Object} props a dictionary of properties to be applied
             * @chainable
             * @method properties
             */
            this.properties = function(path, props) {
                if (arguments.length === 1) {
                    return zebkit.properties(this, path);
                }

                this.byPath(path, function(kid) {
                    zebkit.properties(kid, props);
                });
                return this;
            };

            /**
             * Set the given property to the component or children component
             * specified by the given path (optionally).
             * @param  {String} [path]  a path to find children components
             * @param  {String} name a property name
             * @param  {object} value a property value
             * @chainable
             * @method property
             */
            this.property = function() {
                var p = {};
                if (arguments.length > 2) {
                    p[arguments[1]] = arguments[2];
                    return this.properties(arguments[0], p);
                } else {
                    p[arguments[0]] = arguments[1];
                    return this.properties(p);
                }
            };

            /**
             * Validate the component metrics. The method is called as a one step of the component validation
             * procedure. The method causes "recalc" method execution if the method has been implemented and
             * the component is in invalid state. It is supposed the "recalc" method has to be implemented by
             * a component as safe place where the component metrics can be calculated. Component metrics is
             * individual for the given component properties that has influence to the component preferred
             * size value. In many cases the properties calculation has to be minimized what can be done by
             * moving the calculation in "recalc" method
             * @method validateMetric
             * @protected
             */
            this.validateMetric = function(){
                if (this.isValid === false) {
                    if (typeof this.recalc === 'function') {
                        this.recalc();
                    }
                    this.isValid = true;
                }
            };

            /**
             * By default there is no any implementation of "recalc" method in the layoutable component. In other
             * words the method doesn't exist. Developer should implement the method if the need a proper and
             * efficient place  to calculate component properties that have influence to the component preferred
             * size. The "recalc" method is called only when it is really necessary to compute the component metrics.
             * @method recalc
             * @protected
             */

            /**
             * Invalidate the component layout. Layout invalidation means the component children components have to
             * be placed with the component layout manager. Layout invalidation causes a parent component layout is
             * also invalidated.
             * @method invalidateLayout
             * @protected
             */
            this.invalidateLayout = function(){
                this.isLayoutValid = false;
                if (this.parent !== null) {
                    this.parent.invalidateLayout();
                }
            };

            /**
             * Invalidate component layout and metrics.
             * @method invalidate
             */
            this.invalidate = function(){
                this.isLayoutValid = this.isValid  = false;
                this.cachedWidth = -1;
                if (this.parent !== null) {
                    this.parent.invalidate();
                }
            };

            /**
             * Force validation of the component metrics and layout if it is not valid
             * @method validate
             */
            this.validate = function() {
                if (this.isValid === false) {
                    this.validateMetric();
                }

                if (this.width > 0 && this.height > 0 &&
                    this.isLayoutValid === false &&
                    this.isVisible === true)
                {
                    this.layout.doLayout(this);
                    for (var i = 0; i < this.kids.length; i++) {
                        this.kids[i].validate();
                    }
                    this.isLayoutValid = true;
                    if (this.laidout !== undefined) {
                        this.laidout();
                    }
                }
            };

            /**
             * The method can be implemented to be informed every time the component has completed to layout
             * its children components
             * @method laidout
             */

            /**
             * Get preferred size. The preferred size includes  top, left, bottom and right paddings and
             * the size the component wants to have
             * @method getPreferredSize
             * @return {Object} return size object the component wants to
             * have as the following structure:
             *
             *     {width:{Integer}, height:{Integer}} object
             *
             */
            this.getPreferredSize = function(){
                this.validateMetric();

                if (this.cachedWidth < 0) {
                    var ps = (this.psWidth < 0 || this.psHeight < 0) ? this.layout.calcPreferredSize(this)
                                                                     : { width:0, height:0 };

                    ps.width  = this.psWidth  >= 0 ? this.psWidth
                                                   : ps.width  + this.getLeft() + this.getRight();
                    ps.height = this.psHeight >= 0 ? this.psHeight
                                                   : ps.height + this.getTop()  + this.getBottom();
                    this.cachedWidth  = ps.width;
                    this.cachedHeight = ps.height;
                    return ps;
                }
                return { width:this.cachedWidth,
                         height:this.cachedHeight };
            };

            /**
             * Get top padding.
             * @method getTop
             * @return {Integer} top padding in pixel
             */
            this.getTop = function ()  { return 0; };

            /**
             * Get left padding.
             * @method getLeft
             * @return {Integer} left padding in pixel
             */
            this.getLeft = function ()  { return 0; };

            /**
             * Get bottom padding.
             * @method getBottom
             * @return {Integer} bottom padding in pixel
             */
            this.getBottom = function ()  { return 0; };

            /**
             * Get right padding.
             * @method getRight
             * @return {Integer} right padding in pixel
             */
            this.getRight = function ()  { return 0; };

            /**
             * Set the parent component.
             * @protected
             * @param {zebkit.layout.Layoutable} o a parent component
             * @method setParent
             * @protected
             */
            this.setParent = function(o) {
                if (o !== this.parent){
                    this.parent = o;
                    this.invalidate();
                }
            };

            /**
             * Set the given layout manager that is used to place
             * children component. Layout manager is simple class
             * that defines number of rules concerning the way
             * children components have to be ordered on its parent
             * surface.
             * @method setLayout
             * @param {zebkit.ui.Layout} m a layout manager
             * @chainable
             */
            this.setLayout = function (m){
                if (m === null || m === undefined) {
                    throw new Error("Null layout");
                }

                if (this.layout !== m){
                    this.layout = m;
                    this.invalidate();
                }

                return this;
            };

            /**
             * Internal implementation of the component preferred size calculation.
             * @param  {zebkit.layout.Layoutable} target a component for that the metric has to be calculated
             * @return {Object} a preferred size. The method always
             * returns { width:10, height:10 } as the component preferred
             * size
             * @private
             * @method calcPreferredSize
             */
            this.calcPreferredSize = function (target){
                return { width:10, height:10 };
            };

            /**
             * By default layoutbable component itself implements layout manager to order its children
             * components. This method implementation does nothing, so children component will placed
             * according locations and sizes they have set.
             * @method doLayout
             * @private
             */
            this.doLayout = function (target) {};

            /**
             * Detect index of a children component.
             * @param  {zebkit.ui.Layoutbale} c a children component
             * @method indexOf
             * @return {Integer}
             */
            this.indexOf = function (c){
                return this.kids.indexOf(c);
            };

            /**
             * Insert the new children component at the given index with the specified layout constraints.
             * The passed constraints can be set via a layoutable component that is inserted. Just
             * set "constraints" property of in inserted component.
             * @param  {Integer} i an index at that the new children component has to be inserted
             * @param  {Object} constr layout constraints of the new children component
             * @param  {zebkit.layout.Layoutbale} d a new children layoutable component to be added
             * @return {zebkit.layout.Layoutable} an inserted children layoutable component
             * @method insert
             */
            this.insert = function(i, constr, d){
                if (d.constraints !== null) {
                    constr = d.constraints;
                } else {
                    d.constraints = constr;
                }

                if (i === this.kids.length) {
                    this.kids.push(d);
                } else {
                    this.kids.splice(i, 0, d);
                }
                d.setParent(this);

                if (this.kidAdded !== undefined) {
                    this.kidAdded(i, constr, d);
                }
                this.invalidate();
                return d;
            };

            /**
             * The method can be implemented to be informed every time a new component
             * has been inserted into the component
             * @param  {Integer} i an index at that the new children component has been inserted
             * @param  {Object} constr layout constraints of the new children component
             * @param  {zebkit.layout.Layoutbale} d a new children layoutable component that has
             * been added
             * @method kidAdded
             */

            /**
             * Set the layoutable component location. Location is x, y coordinates relatively to
             * a parent component
             * @param  {Integer} xx x coordinate relatively to the layoutable component parent
             * @param  {Integer} yy y coordinate relatively to the layoutable component parent
             * @method setLocation
             * @chainable
             */
            this.setLocation = function (xx,yy){
                if (xx !== this.x || this.y !== yy) {
                    var px = this.x, py = this.y;
                    this.x = xx;
                    this.y = yy;
                    if (this.relocated !== undefined) {
                        this.relocated(px, py);
                    }
                }
                return this;
            };

            /**
             * The method can be implemented to be informed every time the component
             * has been moved
             * @param  {Integer} px x previous coordinate of moved children component
             * @param  {Integer} py y previous coordinate of moved children component
             * @method relocated
             */


            /**
             * Set the layoutable component bounds. Bounds defines the component location and size.
             * @param  {Integer} x x coordinate relatively to the layoutable component parent
             * @param  {Integer} y y coordinate relatively to the layoutable component parent
             * @param  {Integer} w a width of the component
             * @param  {Integer} h a height of the component
             * @method setBounds
             * @chainable
             */
            this.setBounds = function(x, y, w, h) {
                this.setLocation(x, y);
                this.setSize(w, h);
                return this;
            };

            /**
             * Set the layoutable component size.
             * @param  {Integer} w a width of the component
             * @param  {Integer} h a height of the component
             * @method setSize
             * @chainable
             */
            this.setSize = function(w,h) {
                if (w !== this.width || h !== this.height) {
                    var pw = this.width,
                        ph = this.height;
                    this.width = w;
                    this.height = h;
                    this.isLayoutValid = false;
                    if (this.resized !== undefined) {
                        this.resized(pw, ph);
                    }
                }
                return this;
            };

            /**
             * The method can be implemented to be informed every time the component
             * has been resized
             * @param  {Integer} w a previous width of the component
             * @param  {Integer} h a previous height of the component
             * @method resized
             */

            /**
             * Get a children layoutable component by the given path (optionally)
             * and the specified constraints.
             * @param  {String} [p] a path.
             * @param  {zebkit.layout.Layoutable} c a constraints
             * @return {zebkit.layout.Layoutable} a children component
             * @method byConstraints
             */
            this.byConstraints = function(constr) {
                if (arguments.length === 2) {
                    var res = null;
                    constr = arguments[1];
                    this.byPath(arguments[0], function(kid) {
                        if (kid.constraints === constr) {
                            res = kid;
                            return true;
                        } else {
                            return false;
                        }
                    });
                    return res;
                } else {
                    if (this.kids.length > 0){
                        for(var i = 0; i < this.kids.length; i++ ){
                            var l = this.kids[i];
                            if (constr === l.constraints) {
                                return l;
                            }
                        }
                    }
                    return null;
                }
            };

            /**
             * Set the component constraints without invalidating the component and its parents components
             * layouts and metrics. It is supposed to be used for internal use
             * @protected
             * @param {Object} c a constraints
             * @chainable
             * @method $setConstraints
             */
            this.$setConstraints = function(c) {
                this.constraints = c;
                return this;
            };

            /**
             * Remove the given children component.
             * @param {zebkit.layout.Layoutable} c a children component to be removed
             * @method remove
             * @return {zebkit.layout.Layoutable} a removed children component
             */
            this.remove = function(c) {
                return this.removeAt(this.kids.indexOf(c));
            };

            /**
             * Remove a children component at the specified position.
             * @param {Integer} i a children component index at which it has to be removed
             * @method removeAt
             * @return {zebkit.layout.Layoutable} a removed children component
             */
            this.removeAt = function (i){
                var obj = this.kids[i],
                    ctr = obj.constraints;

                obj.setParent(null);
                if (obj.constraints !== null) {
                    obj.constraints = null;
                }

                this.kids.splice(i, 1);

                if (this.kidRemoved !== undefined) {
                    this.kidRemoved(i, obj, ctr);
                }

                this.invalidate();
                return obj;
            };

            /**
             * Remove the component from its parent if it has a parent
             * @param {Integer} [after] timeout in milliseconds the component has
             * to be removed
             * @method removeMe
             */
            this.removeMe = function(after) {
                var i = -1;
                if (this.parent !== null && (i = this.parent.indexOf(this)) >= 0) {
                    if (arguments.length > 0 && after > 0) {
                        var $this = this;
                        zebkit.util.tasksSet.runOnce(function() {
                            $this.removeMe();
                        }, after);
                    } else {
                        this.parent.removeAt(i);
                    }
                }
            };

            /**
             * Remove a component by the given constraints.
             * @param {Object} ctr a constraints
             * @return {zebkit.layout.Layoutable} a removed component
             * @method removeByConstraints
             */
            this.removeByConstraints = function(ctr) {
                var c = this.byConstraints(ctr);
                if (c !== null) {
                    return this.remove(c);
                } else {
                    return null;
                }
            };

            /**
             * Replace the component with the new one. The replacement keeps
             * layout constraints of the replaced component if other constraints
             * value is not passed to the method.
             * @param  {String} [ctr] a new constraints
             * @param  {zebkit.layout.Layoutable} c a replacement component
             * @chainable
             * @method replaceMe
             */
            this.replaceMe = function(ctr, c) {
                if (this.parent !== null) {
                    if (arguments.length === 1) {
                        c = ctr;
                        c.constraints = this.constraints;
                    } else {
                        c.constraints = ctr;
                    }
                    this.parent.setAt(this.parent.kids.indexOf(this), c);
                }
                return this;
            };

            /**
             * The method can be implemented to be informed every time a children component
             * has been removed
             * @param {Integer} i a children component index at which it has been removed
             * @param  {zebkit.layout.Layoutable} c a children component that has been removed
             * @method kidRemoved
             */

            /**
             * Set the specified preferred size the component has to have. Component preferred size is
             * important thing that is widely used to layout the component. Usually the preferred
             * size is calculated by a concrete component basing on its metrics. For instance, label
             * component calculates its preferred size basing on text size. But if it is required
             * the component preferred size can be fixed with the desired value.
             * @param  {Integer} w a preferred width. Pass "-1" as the
             * argument value to not set preferred width
             * @param  {Integer} h a preferred height. Pass "-1" as the
             * argument value to not set preferred height
             * @chainable
             * @method setPreferredSize
             */
            this.setPreferredSize = function(w, h) {
                if (arguments.length === 1) {
                    h = w;
                }

                if (w !== this.psWidth || h !== this.psHeight){
                    this.psWidth  = w;
                    this.psHeight = h;
                    this.invalidate();
                }
                return this;
            };

            /**
             * Set preferred width.
             * @param {Integer} w a preferred width
             * @chainable
             * @method setPreferredWidth
             */
            this.setPreferredWidth = function(w) {
                if (w !== this.psWidth){
                    this.psWidth = w;
                    this.invalidate();
                }
                return this;
            };

            /**
             * Set preferred height.
             * @param {Integer} h a preferred height
             * @chainable
             * @method setPreferredHeigh
             */
            this.setPreferredHeight = function(h) {
                if (h !== this.psHeight){
                    this.psHeight = h;
                    this.invalidate();
                }
                return this;
            };

            /**
             * Get accumulated vertical (top and bottom) padding.
             * @return {Integer} a vertical padding
             * @method getVerPadding
             */
            this.getVerPadding = function() {
                return this.getTop() + this.getBottom();
            };

            /**
             * Get accumulated horizontal (top and bottom) padding.
             * @return {Integer} a horizontal padding
             * @method getHorPadding
             */
            this.getHorPadding = function() {
                return this.getLeft() + this.getRight();
            };

            /**
             * Replace a children component at the specified index
             * with the given new children component
             * @param  {Integer} i an index of a children component to be replaced
             * @param  {zebkit.layout.Layoutable} d a new children
             * @return {zebkit.layout.Layoutable} a previous component that has
             * been re-set with the new one
             * @method setAt
             */
            this.setAt = function(i, d) {
                var constr = this.kids[i].constraints,
                    pd     = this.removeAt(i);

                if (d !== null) {
                    this.insert(i, constr, d);
                }
                return pd;
            };

            /**
             * Set the component by the given constraints or add new one with the given constraints
             * @param {Object} constr a layout constraints
             * @param {zebkit.layout.Layoutable} c a component to be added
             * @return {zebkit.layout.Layoutable} a previous component that has
             * been re-set with the new one
             * @method setByConstraints
             */
            this.setByConstraints = function(constr, c) {
                var prev = this.byConstraints(constr);
                if (prev === null) {
                    return this.add(constr, c);
                } else {
                    return this.setAt(this.indexOf(prev), c);
                }
            };

            /**
             * Add the new children component with the given constraints
             * @param  {Object} constr a constraints of a new children component
             * @param  {zebkit.layout.Layoutable} d a new children component to
             * be added
             * @method add
             * @return {zebkit.layout.Layoutable} added layoutable component
             */
            this.add = function(constr,d) {
                return (arguments.length === 1) ? this.insert(this.kids.length, null, constr)
                                                : this.insert(this.kids.length, constr, d);
            };
        }
    ]);

    /**
     *  Layout manager implementation that places layoutbale components on top of
     *  each other stretching its to fill all available parent component space.
     *  Components that want to have be sized according to its preferred sizes
     *  have to have its constraints set to "usePsSize".
     *  @example
     *
     *      var pan = new zebkit.ui.Panel();
     *      pan.setStackLayout();
     *
     *      // label component will be stretched over all available pan area
     *      pan.add(new zebkit.ui.Label("A"));
     *
     *      // button component will be sized according to its preferred size
     *      // and aligned to have centered vertical and horizontal alignments
     *      pan.add("usePsSize", new zebkit.ui.Button("Ok"));
     *
     *
     *  @class zebkit.layout.StackLayout
     *  @uses zebkit.layout.Layout
     *  @constructor
     */
    pkg.StackLayout = Class(pkg.Layout, [
        function $prototype() {
            this.calcPreferredSize = function (target){
                return pkg.getMaxPreferredSize(target);
            };

            this.doLayout = function(t){
                var top  = t.getTop(),
                    hh   = t.height - t.getBottom() - top,
                    left = t.getLeft(),
                    ww   = t.width - t.getRight() - left;

                for(var i = 0;i < t.kids.length; i++){
                    var l = t.kids[i];
                    if (l.isVisible === true) {
                        var ctr = l.constraints === null ? null : l.constraints;

                        if (ctr === "usePsSize") {
                            var ps = l.getPreferredSize();
                            l.setBounds(left + Math.floor((ww - ps.width )/2),
                                        top  + Math.floor((hh - ps.height)/2),
                                        ps.width, ps.height);
                        } else {
                            l.setBounds(left, top, ww, hh);
                        }
                    }
                }
            };
        }
    ]);

    /**
     *  Layout manager implementation that logically splits component area into five areas: top, bottom,
     *  left, right and center. Top and bottom components are stretched to fill all available space
     *  horizontally and are sized to have preferred height horizontally. Left and right components are
     *  stretched to fill all available space vertically and are sized to have preferred width vertically.
     *  Center component is stretched to occupy all available space taking in account top, left, right
     *  and bottom components.
     *
     *      // create panel with border layout
     *      var p = new zebkit.ui.Panel(new zebkit.layout.BorderLayout());
     *
     *      // add children UI components with top, center and left constraints
     *      p.add("top",    new zebkit.ui.Label("Top"));
     *      p.add("center", new zebkit.ui.Label("Center"));
     *      p.add("left",   new zebkit.ui.Label("Left"));
     *
     *
     * Construct the layout with the given vertical and horizontal gaps.
     * @param  {Integer} [hgap] horizontal gap. The gap is a horizontal distance between laid out components
     * @param  {Integer} [vgap] vertical gap. The gap is a vertical distance between laid out components
     * @constructor
     * @class zebkit.layout.BorderLayout
     * @uses zebkit.layout.Layout
     */
    pkg.BorderLayout = Class(pkg.Layout, [
        function(hgap,vgap){
            if (arguments.length > 0) {
                this.hgap = this.vgap = hgap;
                if (arguments.length > 1) {
                    this.vgap = vgap;
                }
            }
        },

        function $prototype() {
            /**
             * Horizontal gap (space between components)
             * @attribute hgap
             * @default 0
             * @readOnly
             * @type {Integer}
             */

            /**
             * Vertical gap (space between components)
             * @attribute vgap
             * @default 0
             * @readOnly
             * @type {Integer}
             */
            this.hgap = this.vgap = 0;

            this.calcPreferredSize = function (target){
                var center = null, left = null,  right = null, top = null, bottom = null, d = null;
                for(var i = 0; i < target.kids.length; i++){
                    var l = target.kids[i];
                    if (l.isVisible === true){
                        switch(l.constraints) {
                           case null:
                           case undefined:
                           case "center"    : center = l; break;
                           case "top"       : top    = l; break;
                           case "bottom"    : bottom = l; break;
                           case "left"      : left   = l; break;
                           case "right"     : right  = l; break;
                           default: throw new Error("Invalid constraints: " + l.constraints);
                        }
                    }
                }

                var dim = { width:0, height:0 };
                if (right !== null) {
                    d = right.getPreferredSize();
                    dim.width  = d.width + this.hgap;
                    dim.height = (d.height > dim.height ? d.height: dim.height );
                }

                if (left !== null) {
                    d = left.getPreferredSize();
                    dim.width += d.width + this.hgap;
                    dim.height = d.height > dim.height ? d.height : dim.height;
                }

                if (center !== null) {
                    d = center.getPreferredSize();
                    dim.width += d.width;
                    dim.height = d.height > dim.height ? d.height : dim.height;
                }

                if (top !== null) {
                    d = top.getPreferredSize();
                    dim.width = d.width > dim.width ? d.width : dim.width;
                    dim.height += d.height + this.vgap;
                }

                if (bottom !== null) {
                    d = bottom.getPreferredSize();
                    dim.width = d.width > dim.width ? d.width : dim.width;
                    dim.height += d.height + this.vgap;
                }
                return dim;
            };

            this.doLayout = function(target){
                var t      = target.getTop(),
                    b      = target.height - target.getBottom(),
                    l      = target.getLeft(),
                    r      = target.width - target.getRight(),
                    center = null,
                    left   = null,
                    top    = null,
                    bottom = null,
                    right  = null;

                for(var i = 0;i < target.kids.length; i++){
                    var kid = target.kids[i];
                    if (kid.isVisible === true) {
                        switch(kid.constraints) {
                            case null:
                            case undefined:
                            case "center":
                                if (center !== null) {
                                    throw new Error("Component with center constraints is already defined");
                                }
                                center = kid;
                                break;
                            case "top" :
                                if (top !== null) {
                                    throw new Error("Component with top constraints is already defined");
                                }
                                kid.setBounds(l, t, r - l, kid.getPreferredSize().height);
                                t += kid.height + this.vgap;
                                top = kid;
                                break;
                            case "bottom":
                                if (bottom !== null) {
                                    throw new Error("Component with bottom constraints is already defined");
                                }
                                var bh = kid.getPreferredSize().height;
                                kid.setBounds(l, b - bh, r - l, bh);
                                b -= bh + this.vgap;
                                bottom = kid;
                                break;
                            case "left":
                                if (left !== null) {
                                    throw new Error("Component with left constraints is already defined");
                                }
                                left = kid;
                                break;
                            case "right":
                                if (right !== null) {
                                    throw new Error("Component with right constraints is already defined");
                                }
                                right = kid;
                                break;
                            default: throw new Error("Invalid constraints: '" + kid.constraints + "'");
                        }
                    }
                }

                if (right !== null) {
                    var rw = right.getPreferredSize().width;
                    right.setBounds(r - rw, t, rw, b - t);
                    r -= rw + this.hgap;
                }

                if (left !== null) {
                    left.setBounds(l, t, left.getPreferredSize().width, b - t);
                    l += left.width + this.hgap;
                }

                if (center !== null) {
                    center.setBounds(l, t, r - l, b - t);
                }
            };
        }
    ]);

    /**
     * Rester layout manager can be used to use absolute position of layoutable components. That means
     * all components will be laid out according coordinates and size they have. Raster layout manager
     * provides extra possibilities to control children components placing. It is possible to align
     * components by specifying layout constraints, size component to its preferred size and so on.
     * Constraints that can be set for components are the following
     *    - "top"
     *    - "topRight"
     *    - "topLeft"
     *    - "bottom"
     *    - "bottomLeft"
     *    - "bottomRight"
     *    - "right"
     *    - "center"
     *    - "left"
     * @example
     *     // instantiate component to be ordered
     *     var topLeftLab = zebkit.ui.Label("topLeft");
     *     var leftLab    = zebkit.ui.Label("left");
     *     var centerLab  = zebkit.ui.Label("center");
     *
     *     // instantiate a container with raster layoyt manager set
     *     // the manager is adjusted to size added child component to
     *     // its preferred sizes
     *     var container = new zebkit.ui.Panel(new zebkit.layout.RasterLayout(true));
     *
     *     // add child components with appropriate constraints
     *     container.add("topLeft", topLeftLab);
     *     container.add("left", leftLab);
     *     container.add("center", centerLab);
     *
     * @param {Boolean} [usePsSize] flag to add extra rule to set components size to its preferred
     * sizes.
     * @class  zebkit.layout.RasterLayout
     * @constructor
     * @uses zebkit.layout.Layout
     */
    pkg.RasterLayout = Class(pkg.Layout, [
        function(usePsSize) {
            if (arguments.length > 0) {
                this.usePsSize = usePsSize;
            }
        },

        function $prototype() {
            /**
             * Define if managed with layout manager components have to be sized according to its
             * preferred size
             * @attribute usePsSize
             * @type {Boolean}
             * @default false
             */
            this.usePsSize = false;

            this.calcPreferredSize = function(c){
                var m = { width:0, height:0 };

                for(var i = 0;i < c.kids.length; i++ ){
                    var kid = c.kids[i];
                    if (kid.isVisible === true) {
                        var ps = this.usePsSize ? kid.getPreferredSize()
                                                : { width: kid.width, height: kid.height },
                            px = kid.x + ps.width,
                            py = kid.y + ps.height;

                        if (px > m.width)  {
                            m.width  = px;
                        }

                        if (py > m.height) {
                            m.height = py;
                        }
                    }
                }
                return m;
            };

            this.doLayout = function(c) {
                var r = c.getRight(),
                    b = c.getBottom(),
                    t = c.getTop(),
                    l = c.getLeft();

                for(var i = 0;i < c.kids.length; i++){
                    var kid = c.kids[i];

                    if (kid.isVisible === true){
                        if (this.usePsSize) {
                            kid.toPreferredSize();
                        }

                        var ctr = kid.constraints === null ? null
                                                           : kid.constraints;
                        if (ctr !== null) {
                            var x = kid.x,
                                y = kid.y;

                            if (ctr === "stretch") {
                                kid.setBounds(l, t, c.width - l - r, c.height - t - b);
                            } else {
                                if (ctr === "top" || ctr === "topRight" || ctr === "topLeft") {
                                    y = t;
                                } else if (ctr === "bottom" || ctr === "bottomLeft" || ctr === "bottomRight") {
                                    y = c.height - kid.height - b;
                                } else if (ctr === "center" || ctr === "left" || ctr === "right") {
                                    y = Math.floor((c.height - kid.height) / 2);
                                }

                                if (ctr === "left" || ctr === "topLeft" || ctr === "bottomLeft") {
                                    x = l;
                                } else if (ctr === "right" || ctr === "topRight" || ctr === "bottomRight") {
                                    x = c.width - kid.width - r;
                                } else if (ctr === "center" || ctr === "top" || ctr === "bottom") {
                                    x = Math.floor((c.width  - kid.width) / 2);
                                }
                            }

                            kid.setLocation(x, y);
                        }
                    }
                }
            };
        }
    ]);

    /**
     * Flow layout manager group and places components ordered with different vertical and horizontal
     * alignments
     *
     *     // create panel and set flow layout for it
     *     // components added to the panel will be placed
     *     // horizontally aligned at the center of the panel
     *     var p = new zebkit.ui.Panel();
     *     p.setFlowLayout("center", "center");
     *
     *     // add three buttons into the panel with flow layout
     *     p.add(new zebkit.ui.Button("Button 1"));
     *     p.add(new zebkit.ui.Button("Button 2"));
     *     p.add(new zebkit.ui.Button("Button 3"));
     *
     * @param {String} [ax] ("left" by default) horizontal alignment:
     *
     *    "left"
     *    "center"
     *    "right"
     *
     * @param {String} [ay] ("top" by default) vertical alignment:
     *
     *    "top"
     *    "center"
     *    "bottom"
     *
     * @param {String} [dir] ("horizontal" by default) a direction the component has to be placed
     * in the layout
     *
     *    "vertical"
     *    "horizontal"
     *
     * @param {Integer} [gap] a space in pixels between laid out components
     * @class  zebkit.layout.FlowLayout
     * @constructor
     * @uses zebkit.layout.Layout
     */
    pkg.FlowLayout = Class(pkg.Layout, [
        function (ax, ay, dir, g){
            if (arguments.length === 1) {
                this.gap = ax;
            } else {
                if (arguments.length > 1) {
                    this.ax = ax;
                    this.ay = ay;
                }

                if (arguments.length > 2)  {
                    this.direction = zebkit.util.validateValue(dir, "horizontal", "vertical");
                }

                if (arguments.length > 3) {
                    this.gap = g;
                }
            }
        },

        function $prototype() {
            /**
             * Gap between laid out components
             * @attribute gap
             * @readOnly
             * @type {Integer}
             * @default 0
             */
            this.gap = 0;

            /**
             * Horizontal laid out components alignment
             * @attribute ax
             * @readOnly
             * @type {String}
             * @default "left"
             */
            this.ax = "left";

            /**
             * Vertical laid out components alignment
             * @attribute ay
             * @readOnly
             * @type {String}
             * @default "center"
             */
            this.ay = "center";

            /**
             * Laid out components direction
             * @attribute direction
             * @readOnly
             * @type {String}
             * @default "horizontal"
             */
            this.direction = "horizontal";

            /**
             * Define if the last added component has to be stretched to occupy
             * the rest of horizontal or vertical space of a parent component.
             * @attribute stretchLast
             * @type {Boolean}
             * @default false
             */
            this.stretchLast = false;

            this.calcPreferredSize = function (c){
                var m = { width:0, height:0 }, cc = 0;
                for(var i = 0;i < c.kids.length; i++){
                    var a = c.kids[i];
                    if (a.isVisible === true){
                        var d = a.getPreferredSize();
                        if (this.direction === "horizontal"){
                            m.width += d.width;
                            m.height = d.height > m.height ? d.height : m.height;
                        }
                        else {
                            m.width = d.width > m.width ? d.width : m.width;
                            m.height += d.height;
                        }
                        cc++;
                    }
                }

                var add = this.gap * (cc > 0 ? cc - 1 : 0);
                if (this.direction === "horizontal") {
                    m.width += add;
                } else {
                    m.height += add;
                }
                return m;
            };

            this.doLayout = function(c){
                var psSize  = this.calcPreferredSize(c),
                    t       = c.getTop(),
                    l       = c.getLeft(),
                    lastOne = null,
                    ew      = c.width  - l - c.getRight(),
                    eh      = c.height - t - c.getBottom(),
                    px      = ((this.ax === "right") ? ew - psSize.width
                                                     : ((this.ax === "center") ? Math.floor((ew - psSize.width) / 2) : 0)) + l,
                    py      = ((this.ay === "bottom") ? eh - psSize.height
                                                      : ((this.ay === "center") ? Math.floor((eh - psSize.height) / 2): 0)) + t;

                for(var i = 0;i < c.kids.length; i++){
                    var a = c.kids[i];
                    if (a.isVisible === true) {

                        var d   = a.getPreferredSize(),
                            ctr = a.constraints === null ? null : a.constraints;

                        if (this.direction === "horizontal") {
                            ctr = ctr || this.ay;

                            if (ctr === "stretch") {
                                d.height = c.height - t - c.getBottom();
                            }

                            a.setLocation(px, py + pkg.$align(ctr, psSize.height, d.height));
                            px += (d.width + this.gap);
                        } else {
                            ctr = ctr || this.ax;

                            if (ctr === "stretch") {
                                d.width = c.width - l - c.getRight();
                            }

                            a.setLocation(px + pkg.$align(ctr, psSize.width, d.width), py);
                            py += d.height + this.gap;
                        }

                        a.setSize(d.width, d.height);
                        lastOne = a;
                    }
                }

                if (lastOne !== null && this.stretchLast === true){
                    if (this.direction === "horizontal") {
                        lastOne.setSize(c.width - lastOne.x - c.getRight(), lastOne.height);
                    } else {
                        lastOne.setSize(lastOne.width, c.height - lastOne.y - c.getBottom());
                    }
                }
            };
        }
    ]);

    /**
     * List layout places components vertically one by one
     *
     *     // create panel and set list layout for it
     *     var p = new zebkit.ui.Panel();
     *     p.setListLayout();
     *
     *     // add three buttons into the panel with list layout
     *     p.add(new zebkit.ui.Button("Item 1"));
     *     p.add(new zebkit.ui.Button("Item 2"));
     *     p.add(new zebkit.ui.Button("Item 3"));
     *
     * @param {String} [ax] horizontal list item alignment:
     *
     *    "left"
     *    "right"
     *    "center"
     *    "stretch"
     *
     * @param {Integer} [gap] a space in pixels between laid out components
     * @class  zebkit.layout.ListLayout
     * @constructor
     * @uses zebkit.layout.Layout
     */
    pkg.ListLayout = Class(pkg.Layout,[
        function (ax, gap) {
            if (arguments.length === 1) {
                this.gap = ax;
            } else if (arguments.length > 1) {
                this.ax  = zebkit.util.validateValue(ax, "stretch", "left", "right", "center");
                this.gap = gap;
            }
        },

        function $prototype() {
            /**
             * Horizontal list items alignment
             * @attribute ax
             * @type {String}
             * @readOnly
             */
            this.ax = "stretch";

            /**
             * Pixel gap between list items
             * @attribute gap
             * @type {Integer}
             * @readOnly
             */
            this.gap = 0;

            this.calcPreferredSize = function (lw){
                var w = 0, h = 0, c = 0;
                for(var i = 0; i < lw.kids.length; i++){
                    var kid = lw.kids[i];
                    if (kid.isVisible === true){
                        var d = kid.getPreferredSize();
                        h += (d.height + (c > 0 ? this.gap : 0));
                        c++;
                        if (w < d.width) {
                            w = d.width;
                        }
                    }
                }
                return { width:w, height:h };
            };

            this.doLayout = function (lw){
                var x   = lw.getLeft(),
                    y   = lw.getTop(),
                    psw = lw.width - x - lw.getRight();

                for(var i = 0;i < lw.kids.length; i++){
                    var kid = lw.kids[i];

                    if (kid.isVisible === true){
                        var d      = kid.getPreferredSize(),
                            constr = kid.constraints === null ? this.ax
                                                              : kid.constraints;
                        kid.setSize((constr === "stretch") ? psw : d.width, d.height);
                        kid.setLocation(x + pkg.$align(constr, psw, kid.width), y);
                        y += (d.height + this.gap);
                    }
                }
            };
        }
    ]);

    /**
     * Percent layout places components vertically or horizontally and sizes its
     * according to its percentage constraints.
     *
     *     // create panel and set percent layout for it
     *     var p = new zebkit.ui.Panel();
     *     p.setLayout(new zebkit.layout.PercentLayout());
     *
     *     // add three buttons to the panel that are laid out horizontally with
     *     // percent layout according to its constraints: 20, 30 and 50 percents
     *     p.add(20, new zebkit.ui.Button("20%"));
     *     p.add(30, new zebkit.ui.Button("30%"));
     *     p.add(50, new zebkit.ui.Button("50%"));
     *
     *
     * Percentage constraints can be more complex. It is possible to specify a component
     * vertical and horizontal alignments. Pass the following structure to control the
     * alignments as the component constraints:
     *
     *      {
     *          ax: "center | left | right | stretch",
     *          ay: "center | top | bottom | stretch",
     *          occupy: <Integer>  // -1 means to use preferred size
     *      }
     *
     * @param {String} [dir] a direction of placing components. The
     * value can be "horizontal" or "vertical"
     * @param {Integer} [gap] a space in pixels between laid out components
     * @param {String} [ax] default horizontally component alignment. Use
     * "center", "left", "right", "stretch" as the parameter value
     * @param {String} [ay] default vertical component alignment. Use
     * "center", "top", "bottom", "stretch" as the parameter value
     * @param {Integer} [occupy] default percentage size of a component. -1 means
     * to use preferred size.
     * @class  zebkit.layout.PercentLayout
     * @constructor
     * @uses zebkit.layout.Layout
     */
    pkg.PercentLayout = Class(pkg.Layout, [
        function(dir, gap, ax, ay, occupy) {
            if (arguments.length > 0) {
                this.direction = zebkit.util.validateValue(dir, "horizontal", "vertical");
                if (arguments.length > 1) {
                    this.gap = gap;
                    if (arguments.length > 2) {
                        this.ax = zebkit.util.validateValue(ax, "center", "left", "right", "stretch");
                        if (arguments.length > 3) {
                            this.ay = zebkit.util.validateValue(ay, "center", "top", "bottom", "stretch");
                            if (arguments.length > 4) {
                                this.occupy = occupy;
                            }
                        }
                    }
                }
            }
        },

        function $prototype() {
             /**
              * Direction the components have to be placed (vertically or horizontally)
              * @attribute direction
              * @readOnly
              * @type {String}
              * @default "horizontal"
              */
            this.direction = "horizontal";

            /**
             * Pixel gap between components
             * @attribute gap
             * @readOnly
             * @type {Integer}
             * @default 2
             */
            this.gap = 2;

            /**
             * Default horizontal alignment. Use "left", "right", "center" or "stretch" as
             * the attribute value
             * @attribute ax
             * @type {String}
             * @default "stretch"
             */
            this.ax = "stretch";

            /**
             * Default vertical alignment. Use "top", "bottom", "center" or "stretch" as
             * the attribute value
             * @attribute ay
             * @type {String}
             * @default "center"
             */
            this.ay = "center";

            /**
             * Default percentage size of placed component. -1 means use preferred size
             * as the component size.
             * @attribute occupy
             * @default -1
             * @type {Integer}
             */
            this.occupy = -1;

            this.doLayout = function(target){
                var right      = target.getRight(),
                    top        = target.getTop(),
                    bottom     = target.getBottom(),
                    left       = target.getLeft(),
                    size       = target.kids.length,
                    rs         = -this.gap * (size === 0 ? 0 : size - 1),
                    loc        = 0,
                    cellWidth  = 0,
                    cellHeight = 0;

                if (this.direction === "horizontal") {
                    rs += target.width - left - right;
                    loc = left;
                    cellHeight = target.height - top - bottom;
                } else {
                    rs += target.height - top - bottom;
                    loc = top;
                    cellWidth = target.width - left - right;
                }

                for (var i = 0; i < size; i++) {
                    var l      = target.kids[i],
                        ctr    = l.constraints,
                        ps     = null,
                        ax     = this.ax,
                        ay     = this.ay,
                        occupy = this.occupy,
                        compW  = 0,
                        compH  = 0,
                        xx     = 0,
                        yy     = 0;

                    if (ctr !== null) {
                        if (ctr.constructor === Object) {
                            ax     = ctr.ax === undefined ? this.ax : ctr.ax;
                            ay     = ctr.ay === undefined ? this.ay : ctr.ay;
                            occupy = ctr.occupy === undefined ? this.occupy : ctr.occupy;
                        } else if (ctr.constructor === Number) {
                            ax     = this.ax;
                            ay     = this.ay;
                            occupy = ctr;
                        }
                    }

                    if (this.direction === "horizontal") {
                        // cell size

                        if (i === size - 1) {
                            cellWidth = target.width - loc - right;
                        } else if (occupy === -1) {
                            ps = l.getPreferredSize();
                            cellWidth = ps.width;
                        } else {
                            cellWidth = Math.floor((rs * occupy) / 100);
                        }

                        // component size
                        if (ax === "stretch") {
                            compW = cellWidth;
                            xx = loc;
                        } else {
                            if (ps === null) {
                                ps = l.getPreferredSize();
                            }

                            compW = ps.width <= cellWidth ? ps.width : cellWidth;
                            xx = loc + pkg.$align(ax, cellWidth, compW);
                        }

                        // component size
                        if (ay === "stretch") {
                            compH = cellHeight;
                            yy    = top;
                        } else {
                            if (ps === null) {
                                ps = l.getPreferredSize();
                            }

                            compH = ps.height <= cellHeight ? ps.height : cellHeight;
                            yy = top + pkg.$align(ay, cellHeight, compH);
                        }

                        loc += (cellWidth + this.gap);

                    } else {
                        // cell size
                        if (i === size - 1) {
                            cellHeight = target.height - loc - bottom;
                        } else if (occupy === -1) {
                            ps = l.getPreferredSize();
                            cellHeight = ps.height;
                        } else {
                            cellHeight = Math.floor((rs * occupy) / 100);
                        }

                        // component size
                        if (ay === "stretch") {
                            compH = cellHeight;
                            yy = loc;
                        } else {
                            if (ps === null) {
                                ps = l.getPreferredSize();
                            }

                            compH = ps.height <= cellHeight ? ps.height : cellHeight;
                            yy = loc + pkg.$align(ay, cellHeight, compH);
                        }

                        // component size
                        if (ax === "stretch") {
                            compW = cellWidth;
                            xx = left;
                        } else {
                            if (ps === null) {
                                ps = l.getPreferredSize();
                            }

                            compW = ps.width <= cellWidth ? ps.width : cellWidth;
                            xx = left + pkg.$align(ax, cellWidth, compW);
                        }

                        loc += (cellHeight + this.gap);
                    }

                    l.setBounds(xx, yy, compW, compH);
                }
            };

            this.calcPreferredSize = function (target){
                var max  = 0,
                    size = target.kids.length,
                    asz  = this.gap * (size === 0 ? 0 : size - 1);

                for(var i = 0; i < size; i++) {
                    var d = target.kids[i].getPreferredSize();
                    if (this.direction === "horizontal") {
                        if (d.height > max) {
                            max = d.height;
                        }
                        asz += d.width;
                    } else {
                        if (d.width > max) {
                            max = d.width;
                        }
                        asz += d.height;
                    }
                }
                return (this.direction === "horizontal") ? { width:asz, height:max }
                                                         : { width:max, height:asz };
            };
        }
    ]);

    /**
     * Grid layout manager constraints. Constraints says how a  component has to be placed in
     * grid layout virtual cell. The constraints specifies vertical and horizontal alignments,
     * a virtual cell paddings, etc.
     * @param {Integer} [ax] a horizontal alignment
     * @param {Integer} [ay] a vertical alignment
     * @param {Integer} [p]  a cell padding
     * @constructor
     * @class zebkit.layout.Constraints
     */
    pkg.Constraints = Class([
        function(ax, ay, p) {
            if (arguments.length > 0) {
                this.ax = ax;
                if (arguments.length > 1) {
                    this.ay = ay;
                }

                if (arguments.length > 2) {
                    this.setPadding(p);
                }

                zebkit.util.validateValue(this.ax, "stretch", "left", "center", "right");
                zebkit.util.validateValue(this.ay, "stretch", "top", "center", "bottom");
            }
        },

        function $prototype() {
            /**
             * Top cell padding
             * @attribute top
             * @type {Integer}
             * @default 0
             */
            this.top = 0;

            /**
             * Left cell padding
             * @attribute left
             * @type {Integer}
             * @default 0
             */
            this.left = 0;

            /**
             * Right cell padding
             * @attribute right
             * @type {Integer}
             * @default 0
             */
            this.right = 0;

            /**
             * Bottom cell padding
             * @attribute bottom
             * @type {Integer}
             * @default 0
             */
            this.bottom = 0;

            /**
             * Horizontal alignment
             * @attribute ax
             * @type {String}
             * @default "stretch"
             */
            this.ax = "stretch";

            /**
             * Vertical alignment
             * @attribute ay
             * @type {String}
             * @default "stretch"
             */
            this.ay = "stretch";

            this.rowSpan = this.colSpan = 1;

            /**
             * Set all four paddings (top, left, bottom, right) to the given value
             * @param  {Integer} p a padding
             * @chainable
             * @method setPadding
             */

            /**
             * Set top, left, bottom, right paddings
             * @param  {Integer} t a top padding
             * @param  {Integer} l a left padding
             * @param  {Integer} b a bottom padding
             * @param  {Integer} r a right padding
             * @chainable
             * @method setPadding
             */
            this.setPadding = function(t,l,b,r) {
                if (arguments.length === 1) {
                    this.top = this.bottom = this.left = this.right = t;
                } else {
                    this.top    = t;
                    this.bottom = b;
                    this.left   = l;
                    this.right  = r;
                }
                return this;
            };
        }
    ]);

    /**
     * Grid layout manager. can be used to split a component area to number of virtual cells where
     * children components can be placed. The way how the children components have to be laid out
     * in the cells can be customized by using "zebkit.layout.Constraints" class:
     *
     *     // create constraints
     *     var ctr = new zebkit.layout.Constraints();
     *
     *     // specify cell top, left, right, bottom paddings
     *     ctr.setPadding(8);
     *     // say the component has to be left aligned in a
     *     // virtual cell of grid layout
     *     ctr.ax = "left";
     *
     *     // create panel and set grid layout manager with two
     *     // virtual rows and columns
     *     var p = new zebkit.ui.Panel();
     *     p.setLayout(new zebkit.layout.GridLayout(2, 2));
     *
     *     // add children component
     *     p.add(ctr, new zebkit.ui.Label("Cell 1, 1"));
     *     p.add(ctr, new zebkit.ui.Label("Cell 1, 2"));
     *     p.add(ctr, new zebkit.ui.Label("Cell 2, 1"));
     *     p.add(ctr, new zebkit.ui.Label("Cell 2, 2"));
     *
     * @param {Integer} rows a number of virtual rows to layout children components
     * @param {Integer} cols a number of virtual columns to layout children components
     * @param {Boolean} [stretchRows] true if virtual cell height has to be stretched to occupy the
     * whole vertical container component space
     * @param {Boolean} [stretchCols] true if virtual cell width has to be stretched to occupy the
     * whole horizontal container component space
     * @constructor
     * @class  zebkit.layout.GridLayout
     * @uses zebkit.layout.Layout
     */
    pkg.GridLayout = Class(pkg.Layout, [
        function(r, c, stretchRows, stretchCols) {
            /**
             * Number of virtual rows to place children components
             * @attribute rows
             * @readOnly
             * @type {Integer}
             */
            this.rows = r;

            /**
             * Number of virtual columns to place children components
             * @attribute cols
             * @readOnly
             * @type {Integer}
             */
            this.cols = c;

            /**
             * Computed columns sizes.
             * @attribute colSizes
             * @type {Array}
             * @private
             */
            this.colSizes = Array(c + 1);

            /**
             * Computed rows sizes.
             * @attribute rowSizes
             * @type {Array}
             * @private
             */
            this.rowSizes = Array(r + 1);

            /**
             * Default constraints that is applied for children components
             * that doesn't define own constraints
             * @type {zebkit.layout.Constraints}
             * @attribute constraints
             */
            this.constraints = new pkg.Constraints();

            if (arguments.length > 2) {
                this.stretchRows = (stretchRows === true);
            }

            if (arguments.length > 3) {
                this.stretchCols = (stretchCols === true);
            }
        },

        function $prototype() {
            /**
             * Attributes that indicates if component has to be stretched
             * horizontally to occupy the whole space of a virtual cell.
             * @attribute stretchCols
             * @readOnly
             * @type {Boolean}
             * @default false
             */
            this.stretchCols = false;

            /**
             * Attributes that indicates if component has to be stretched
             * vertically to occupy the whole space of a virtual cell.
             * @attribute stretchRows
             * @readOnly
             * @type {Boolean}
             * @default false
             */
            this.stretchRows = false;

            /**
             * Set default grid layout cell paddings (top, left, bottom, right) to the given value
             * @param  {Integer} p a padding
             * @chainable
             * @method setPadding
             */

            /**
             * Set default grid layout cell paddings: top, left, bottom, right
             * @param  {Integer} t a top padding
             * @param  {Integer} l a left padding
             * @param  {Integer} b a bottom padding
             * @param  {Integer} r a right padding
             * @chainable
             * @method setPadding
             */
            this.setPadding = function() {
                this.constraints.setPadding.apply(this.constraints, arguments);
                return this;
            };

            /**
             * Set default constraints.
             * @method setDefaultConstraints
             * @chainable
             * @param {zebkit.layout.Constraints} c a constraints
             */
            this.setDefaultConstraints = function(c) {
                this.constraints = c;
                return this;
            };

            /**
             * Calculate columns metrics
             * @param  {zebkit.layout.Layoutable} c the target container
             * @return {Array} a columns widths
             * @method calcCols
             * @protected
             */
            this.calcCols = function(c){
                this.colSizes[this.cols] = 0;
                for(var i = 0;i < this.cols; i++) {
                    this.colSizes[i] = this.calcCol(i, c);
                    this.colSizes[this.cols] += this.colSizes[i];
                }
                return this.colSizes;
            };

            /**
             * Calculate rows metrics
             * @param  {zebkit.layout.Layoutable} c the target container
             * @return {Array} a rows heights
             * @method calcRows
             * @protected
             */
            this.calcRows = function(c){
                this.rowSizes[this.rows] = 0;
                for(var i = 0;i < this.rows; i++) {
                    this.rowSizes[i] = this.calcRow(i, c);
                    this.rowSizes[this.rows] += this.rowSizes[i];
                }
                return this.rowSizes;
            };

            /**
             * Calculate the given row height
             * @param  {Integer} row a row
             * @param  {zebkit.layout.Layoutable} c the target container
             * @return {Integer} a size of the row
             * @method calcRow
             * @protected
             */
            this.calcRow = function(row, c){
                var max = 0, s = row * this.cols;
                for (var i = s; i < c.kids.length && i < s + this.cols; i++) {
                    var a = c.kids[i];
                    if (a.isVisible === true) {
                        var arg    = a.constraints || this.constraints,
                            top    = arg.top !== undefined ? arg.top : this.constraints.top,
                            bottom = arg.bottom !== undefined ? arg.bottom : this.constraints.bottom,
                            d      = a.getPreferredSize().height;

                        d += (top + bottom);
                        if (d > max) {
                            max = d;
                        }
                    }
                }
                return max;
            };

            /**
             * Calculate the given column width
             * @param  {Integer} col a column
             * @param  {zebkit.layout.Layoutable} c the target container
             * @return {Integer} a size of the column
             * @method calcCol
             * @protected
             */
            this.calcCol = function(col, c){
                var max = 0;

                for(var i = col; i < c.kids.length; i += this.cols) {
                    var a = c.kids[i];
                    if (a.isVisible === true) {
                        var ctr   = a.constraints || this.constraints,
                            left  = ctr.left  !== undefined ? ctr.left : this.constraints.left,
                            right = ctr.right !== undefined ? ctr.right: this.constraints.right,
                            d     = a.getPreferredSize().width + left + right;

                        if (d > max) {
                            max = d;
                        }
                    }
                }
                return max;
            };

            this.calcPreferredSize = function(c){
                return { width : this.calcCols(c)[this.cols],
                         height: this.calcRows(c)[this.rows] };
            };

            this.doLayout = function(c) {
                var rows     = this.rows,
                    cols     = this.cols,
                    colSizes = this.calcCols(c),
                    rowSizes = this.calcRows(c),
                    top      = c.getTop(),
                    left     = c.getLeft(),
                    cc       = 0,
                    i        = 0;

                if (this.stretchCols) {
                    var dw = c.width - left - c.getRight() - colSizes[cols];
                    for(i = 0; i < cols; i ++ ) {
                        colSizes[i] = colSizes[i] + (colSizes[i] !== 0 ? Math.floor((dw * colSizes[i]) / colSizes[cols]) : 0);
                    }
                }

                if (this.stretchRows) {
                    var dh = c.height - top - c.getBottom() - rowSizes[rows];
                    for(i = 0; i < rows; i++) {
                        rowSizes[i] = rowSizes[i] + (rowSizes[i] !== 0 ? Math.floor((dh * rowSizes[i]) / rowSizes[rows]) : 0);
                    }
                }

                for (i = 0; i < rows && cc < c.kids.length; i++) {
                    var xx = left;
                    for(var j = 0;j < cols && cc < c.kids.length; j++, cc++) {
                        var l = c.kids[cc];
                        if (l.isVisible === true){
                            var arg   = l.constraints || this.constraints,
                                d     = l.getPreferredSize(),
                                cleft   = arg.left   !== undefined ? arg.left   : this.constraints.left,
                                cright  = arg.right  !== undefined ? arg.right  : this.constraints.right,
                                ctop    = arg.top    !== undefined ? arg.top    : this.constraints.top,
                                cbottom = arg.bottom !== undefined ? arg.bottom : this.constraints.bottom,
                                cax     = arg.ax     !== undefined ? arg.ax     : this.constraints.ax,
                                cay     = arg.ay     !== undefined ? arg.ay     : this.constraints.ay,
                                cellW   = colSizes[j],
                                cellH   = rowSizes[i];

                            cellW -= (cleft + cright);
                            cellH -= (ctop  + cbottom);

                            if ("stretch" === cax) {
                                d.width = cellW;
                            }

                            if ("stretch" === cay) {
                                d.height = cellH;
                            }

                            l.setSize(d.width, d.height);
                            l.setLocation(xx  + cleft + pkg.$align(cax, cellW, d.width),
                                          top + ctop  + pkg.$align(cay, cellH, d.height));

                            xx += colSizes[j];
                        }
                    }
                    top += rowSizes[i];
                }
            };
        }
    ]);
});
zebkit.package("draw", function(pkg, Class) {
    'use strict';
    /**
     * View package
     *
     * @class  zebkit.draw
     * @access package
     */


     /**
      * Dictionary of useful methods an HTML Canvas 2D context can be extended. The following methods are
      * included:
      *
      *   - **setFont(f)**   set font
      *   - **setColor(c)**  set background and foreground colors
      *   - **drawLine(x1, y1, x2, y2, [w])**  draw line of the given width
      *   - **ovalPath(x,y,w,h)**  build oval path
      *   - **polylinePath(xPoints, yPoints, nPoints)**  build path by the given points
      *   - **drawDottedRect(x,y,w,h)**  draw dotted rectangle
      *   - **drawDashLine(x,y,x2,y2)** draw dashed line
      *
      * @attribute Context2D
      * @type {Object}
      * @protected
      * @readOnly
      */
     pkg.Context2D = {
        setFont : function(f) {
            f = (f.s !== undefined ? f.s : f.toString());
            if (f !== this.font) {
                this.font = f;
            }
        },

        setColor : function (c) {
            c = (c.s !== undefined ? c.s : c.toString());
            if (c !== this.fillStyle) {
                this.fillStyle = c;
            }

            if (c !== this.strokeStyle) {
                this.strokeStyle = c;
            }
        },

        drawLine : function (x1, y1, x2, y2, w){
            if (arguments.length < 5) {
                w = 1;
            }

            var pw = this.lineWidth;
            this.beginPath();
            if (this.lineWidth !== w) {
                this.lineWidth = w;
            }

            if (x1 === x2) {
                x1 += w / 2;
                x2 = x1;
            } else if (y1 === y2) {
                y1 += w / 2;
                y2 = y1;
            }

            this.moveTo(x1, y1);
            this.lineTo(x2, y2);
            this.stroke();
            if (pw !== this.lineWidth) {
                this.lineWidth = pw;
            }
        },

        ovalPath: function (x,y,w,h){
            this.beginPath();
            x += this.lineWidth;
            y += this.lineWidth;
            w -= 2 * this.lineWidth;
            h -= 2 * this.lineWidth;

            var kappa = 0.5522848,
                ox = Math.floor((w / 2) * kappa),
                oy = Math.floor((h / 2) * kappa),
                xe = x + w,
                ye = y + h,
                xm = x + w / 2,
                ym = y + h / 2;
            this.moveTo(x, ym);
            this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
            this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
            this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
            this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
            this.closePath();
         },

        polylinePath : function(xPoints, yPoints, nPoints){
            this.beginPath();
            this.moveTo(xPoints[0], yPoints[0]);
            for(var i = 1; i < nPoints; i++) {
                this.lineTo(xPoints[i], yPoints[i]);
            }
        },

        drawRect : function(x,y,w,h) {
            this.beginPath();
            this.rect(x,y,w,h);
            this.stroke();
        },

        drawDottedRect : function(x,y,w,h) {
            var ctx = this, m = ["moveTo", "lineTo", "moveTo"];
            function dv(x, y, s) { for(var i=0; i < s; i++) { ctx[m[i%3]](x + 0.5, y + i); }  }
            function dh(x, y, s) { for(var i=0; i < s; i++) { ctx[m[i%3]](x + i, y + 0.5); } }
            ctx.beginPath();
            dh(x, y, w);
            dh(x, y + h - 1, w);
            ctx.stroke();
            ctx.beginPath();
            dv(x, y, h);
            dv(w + x - 1, y, h);
            ctx.stroke();
        },

        drawDashLine : function(x,y,x2,y2) {
            var pattern = [1,2],
                compute = null,
                dx      = (x2 - x), dy = (y2 - y),
                b       = (Math.abs(dx) > Math.abs(dy)),
                slope   = b ? dy / dx : dx / dy,
                sign    = b ? (dx < 0 ?-1:1) : (dy < 0?-1:1),
                dist    = Math.sqrt(dx * dx + dy * dy);

            if (b) {
                compute = function(step) {
                    x += step;
                    y += slope * step;
                };
            } else {
                compute = function(step) {
                    x += slope * step;
                    y += step;
                };
            }

            this.beginPath();
            this.moveTo(x, y);
            for (var i = 0; dist >= 0.1; i++) {
                var idx  = i % pattern.length,
                    dl   = dist < pattern[idx] ? dist : pattern[idx],
                    step = Math.sqrt(dl * dl / (1 + slope * slope)) * sign;

                compute(step);
                this[(i % 2 === 0) ? 'lineTo' : 'moveTo'](x + 0.5, y + 0.5);
                dist -= dl;
            }
            this.stroke();
        }
    };

    /**
     * Dictionary of predefined views. Every view is accessible by an id associated
     * with the view.
     * @attribute $views
     * @type {Object}
     * @protected
     * @for zebkit.draw
     */
    pkg.$views = {};

    /**
     * Build a view instance by the given object.
     * @param  {Object} v an object that can be used to build a view. The following variants
     * of object types are possible
     *
     *   - **null** null is returned
     *   - **String** if the string is color or border view id than "zebkit.draw.rgb" or border view
     *     is returned. Otherwise an instance of zebkit.draw.StringRender is returned.
     *   -  **String** if the string starts from "#" or "rgb" it is considered as encoded color.  "zebkit.draw.rgb"
     *     instance will be returned as the view
     *   - **Array** an instance of "zebkit.draw.CompositeView" is returned
     *   - **Function** in this case the passed method is considered as ans implementation of "paint(g, x, y, w, h, d)"
     *     method of "zebkit.draw.View" class. Ans instance of "zebkit.draw.View" with the method implemented is returned.
     *   - **Object** an instance of "zebkit.draw.ViewSet" is returned
     *
     * @return zebkit.draw.View a view
     * @method $view
     * @example
     *
     *      // string render
     *      var view = zebkit.draw.$view("String render");
     *
     *      // color render
     *      var view = zebkit.draw.$view("red");
     *
     *      // composite view
     *      var view = zebkit.draw.$view([
     *          zebkit.draw.rgb.yellow,
     *          "String Render"
     *      ]);
     *
     *      // custom view
     *      var view = zebkit.draw.$view(function(g,x,y,w,h,d) {
     *          g.drawLine(x, y, x + w, y + w);
     *          ...
     *       });
     *
     * @protected
     * @for zebkit.draw
     */
    pkg.$view = function(v) {
        if (v === null || v.paint !== undefined) {
            return v;
        } else if (typeof v === "string" || v.constructor === String) {
            if (pkg.rgb[v] !== undefined) { // detect color
                return pkg.rgb[v];
            } else if (pkg.$views[v] !== undefined) { // detect predefined view
                return pkg.$views[v];
            } else {
                if (v.length > 0 &&
                    (v[0] === '#'        ||
                      ( v.length > 2 &&
                        v[0] === 'r' &&
                        v[1] === 'g' &&
                        v[2] === 'b'    )  ))
                {
                    return new pkg.rgb(v);
                } else {
                    return new pkg.StringRender(v);
                }
            }
        } else if (Array.isArray(v)) {
            return new pkg.CompositeView(v);
        } else if (typeof v !== 'function') {
            return new pkg.ViewSet(v);
        } else {
            var vv = new pkg.View();
            vv.paint = v;
            return vv;
        }
    };

    /**
     * View class that is designed as a basis for various reusable decorative UI elements implementations
     * @class zebkit.draw.View
     * @constructor
     */
    pkg.View = Class([
        function $prototype() {
            this.gap = 2;

            /**
             * Get left gap. The method informs UI component that uses the view as
             * a border view how much space left side of the border occupies
             * @return {Integer} a left gap
             * @method getLeft
             */

             /**
              * Get right gap. The method informs UI component that uses the view as
              * a border view how much space right side of the border occupies
              * @return {Integer} a right gap
              * @method getRight
              */

             /**
              * Get top gap. The method informs UI component that uses the view as
              * a border view how much space top side of the border occupies
              * @return {Integer} a top gap
              * @method getTop
              */

             /**
              * Get bottom gap. The method informs UI component that uses the view as
              * a border view how much space bottom side of the border occupies
              * @return {Integer} a bottom gap
              * @method getBottom
              */
            this.getRight = this.getLeft = this.getBottom = this.getTop = function() {
                return this.gap;
            };

            /**
            * Return preferred size the view desires to have
            * @method getPreferredSize
            * @return {Object}
            */
            this.getPreferredSize = function() {
                return { width  : 0,
                         height : 0 };
            };

            /**
            * The method is called to render the decorative element on the given surface of the specified
            * UI component
            * @param {CanvasRenderingContext2D} g  graphical context
            * @param {Integer} x  x coordinate
            * @param {Integer} y  y coordinate
            * @param {Integer} w  required width
            * @param {Integer} h  required height
            * @param {zebkit.layout.Layoutable} c an UI component on which the view
            * element has to be drawn
            * @method paint
            */
            this.paint = function(g,x,y,w,h,c) {};
        }
    ]);

    /**
     * Render class extends "zebkit.draw.View" class with a notion
     * of target object. Render stores reference  to a target that
     * the render knows how to visualize. Basically Render is an
     * object visualizer. For instance, developer can implement
     * text, image and so other objects visualizers.
     * @param {Object} target a target object to be visualized
     * with the render
     * @constructor
     * @extends zebkit.draw.View
     * @class zebkit.draw.Render
     */
    pkg.Render = Class(pkg.View, [
        function(target) {
            if (arguments.length > 0) {
                this.setValue(target);
            }
        },

        function $prototype() {
            /**
             * Target object to be visualized
             * @attribute target
             * @default null
             * @readOnly
             * @type {Object}
             */
            this.target = null;

            /**
             * Set the given target object. The method triggers "valueWasChanged(oldTarget, newTarget)"
             * execution if the method is declared. Implement the method if you need to track a target
             * object updating.
             * @method setValue
             * @param  {Object} o a target object to be visualized
             * @chainable
             */
            this.setValue = function(o) {
                if (this.target !== o) {
                    var old = this.target;
                    this.target = o;
                    if (this.valueWasChanged !== undefined) {
                        this.valueWasChanged(old, o);
                    }
                }
                return this;
            };

            /**
             * Get as rendered object.
             * @return {Object} a rendered object
             * @method getValue
             */
            this.getValue = function() {
                return this.target;
            };
        }
    ]);

    /**
     * RGB color class. This class represents a rgb color as JavaScript structure:
     *
     *     // rgb color
     *     var rgb1 = new zebkit.draw.rgb(100,200,100);
     *
     *     // rgb with transparency
     *     var rgb2 = new zebkit.draw.rgb(100,200,100, 0.6);
     *
     *     // encoded as a string rgb color
     *     var rgb3 = new zebkit.draw.rgb("rgb(100,100,200)");
     *
     *     // hex rgb color
     *     var rgb3 = new zebkit.draw.rgb("#CCDDFF");
     *
     * @param  {Integer|String} r  the meaning of the argument depends on number of arguments the
     * constructor gets:
     *
     *   - If constructor gets only this argument the argument is considered as encoded rgb color:
     *      - **String**  means its hex encoded ("#CCFFDD") or rgb ("rgb(100,10,122)", "rgba(100,33,33,0.6)") encoded color
     *      - **Integer** means this is number encoded rgb color
     *   - Otherwise the argument is an integer value that depicts a red intensity of rgb color
     *
     * encoded in string rgb color
     * @param  {Integer} [g]  green color intensity
     * @param  {Integer} [b] blue color intensity
     * @param  {Float}   [a] alpha color intensity
     * @constructor
     * @class zebkit.draw.rgb
     * @extends zebkit.draw.View
     */
    pkg.rgb = Class(pkg.View, [
        function (r, g, b, a) {
            this.isOpaque = true;

            if (arguments.length === 1) {
                if (zebkit.isString(r)) {
                    this.s = r = r.trim();

                    if (r[0] === '#') {  // hex color has been detected
                        if (r.length >= 7) {  // long hex color #RRGGBB[AA]
                            var rr = parseInt(r.substring(1, 7), 16);
                            this.r =  rr >> 16;
                            this.g = (rr >> 8) & 0xFF;
                            this.b = (rr & 0xFF);

                            if (r.length > 7) {  // check if alpha is represnted with the color
                                this.a = parseInt(r.substring(7, r.length), 16);
                                this.isOpaque = (this.a === 0xFF);
                            }
                        } else {   // short hex color #RGB[A]
                            this.r = parseInt(r.substring(1, 2), 16);
                            this.g = parseInt(r.substring(2, 3), 16);
                            this.b = parseInt(r.substring(3, 4), 16);
                            if (r.length > 4) { // check if alpha is represnted with the color
                                this.a = parseInt(r.substring(4, 5), 16);
                                this.isOpaque = (this.a === 0xF);
                            }
                        }
                    } else if (r[0] === 'r' && r[1] === 'g' && r[2] === 'b') { // rgb encoded color has been detected
                        var i = r.indexOf('(', 3),
                            s = r.substring(i + 1, r.indexOf(')', i + 1)),
                            p = s.split(",");

                        this.r = parseInt(p[0].trim(), 10);
                        this.g = parseInt(p[1].trim(), 10);
                        this.b = parseInt(p[2].trim(), 10);
                        if (p.length > 3) {
                            var aa = p[3].trim();
                            if (aa[aa.length - 1] === '%') {
                                this.isOpaque = (aa == "100%");
                                this.a = parseFloat((parseInt(aa, 10) / 100).toFixed(2));
                            } else {
                                this.a = parseFloat(aa, 10);
                                this.isOpaque = (this.a == 1.0);
                            }
                        }
                    } else if (r.length > 2 && this.clazz[r] !== undefined) {
                        var col = this.clazz.colors[r];
                        this.r = col.r;
                        this.g = col.g;
                        this.b = col.b;
                        this.a = col.a;
                        this.s = col.s;
                        this.isOpaque = col.isOpaque;
                    }
                } else { // consider an number has been passed
                    this.r =  r >> 16;
                    this.g = (r >> 8) & 0xFF;
                    this.b = (r & 0xFF);
                }
            } else if (arguments.length > 1) {
                this.r = r;
                this.g = g;
                this.b = b;
                if (arguments.length > 3) {
                    this.a = a;
                    this.isOpaque = (a == 1.0);
                }
            }

            if (this.s === null) {
                this.s = (this.isOpaque === false)  ? 'rgba(' + this.r + "," + this.g +  "," +
                                                                this.b + "," + this.a + ")"
                                                    : '#' +
                                                       ((this.r < 16) ? "0" + this.r.toString(16) : this.r.toString(16)) +
                                                       ((this.g < 16) ? "0" + this.g.toString(16) : this.g.toString(16)) +
                                                       ((this.b < 16) ? "0" + this.b.toString(16) : this.b.toString(16));
            }
        },

        function $prototype() {
            this.s = null;
            this.gap = 0;

            /**
             * Indicates if the color is opaque
             * @attribute isOpaque
             * @readOnly
             * @type {Boolean}
             */
            this.isOpaque = true;

            /**
             * Red color intensity
             * @attribute r
             * @type {Integer}
             * @readOnly
             */
            this.r = 0;

            /**
             * Green color intensity
             * @attribute g
             * @type {Integer}
             * @readOnly
             */
            this.g = 0;

            /**
             * Blue color intensity
             * @attribute b
             * @type {Integer}
             * @readOnly
             */
            this.b = 0;

            /**
             * Alpha
             * @attribute a
             * @type {Float}
             * @readOnly
             */
            this.a = 1.0;

            this.paint = function(g,x,y,w,h,d) {
                if (this.s !== g.fillStyle) {
                    g.fillStyle = this.s;
                }

                // fix for IE10/11, calculate intersection of clipped area
                // and the area that has to be filled. IE11/10 have a bug
                // that triggers filling more space than it is restricted
                // with clip
                // if (g.$states !== undefined) {
                //     var t  = g.$states[g.$curState],
                //         rx = x > t.x ? x : t.x,
                //         rw = Math.min(x + w, t.x + t.width) - rx;

                //     if (rw > 0)  {
                //         var ry = y > t.y ? y : t.y,
                //         rh = Math.min(y + h, t.y + t.height) - ry;

                //         if (rh > 0) {
                //             g.fillRect(rx, ry, rw, rh);
                //         }
                //     }
                // } else {
                    g.fillRect(x, y, w, h);
//                }
            };

            this.toString = function() {
                return this.s;
            };
        },

        function $clazz() {
            /**
             * Black color constant
             * @attribute black
             * @type {zebkit.draw.rgb}
             * @static
             */

            // CSS1
            this.black       = new this(0);
            this.silver      = new this(0xC0, 0xC0, 0xC0);
            this.grey        = this.gray = new this(0x80, 0x80, 0x80);
            this.white       = new this(0xFFFFFF);
            this.maroon      = new this(0x800000);
            this.red         = new this(255,0,0);
            this.purple      = new this(0x800080);
            this.fuchsia     = new this(0xff00ff);
            this.green       = new this(0x008000);
            this.lime        = new this(0x00ff00);
            this.olive       = new this(0x808000);
            this.yellow      = new this(255,255,0);
            this.navy        = new this(0x000080);
            this.blue        = new this(0,0,255);
            this.teal        = new this(0x008080);
            this.aqua        = new this(0x00ffff);

            // CSS2
            this.orange         = new this(255,165,0);
            this.aliceblue      = new this(0xf0f8ff);
            this.antiqueWhite   = this.antiquewhite = new this(0xfaebd7);
            this.aquamarine     = new this(0x7fffd4);
            this.azure          = new this(0xf0ffff);
            this.beige          = new this(0xf5f5dc);
            this.bisque         = new this(0xffe4c4);
            this.blanchedalmond = new this(0xffebcd);
            this.blueViolet     = this.blueviolet = new this(0x8a2be2);
            this.brown          = new this(0xa52a2a);
            this.burlywood      = new this(0xdeb887);
            this.cadetblue      = new this(0x5f9ea0);
            this.chartreuse     = new this(0x7fff00);
            this.chocolate      = new this(0xd2691e);
            this.coral          = new this(0xff7f50);
            this.cornflowerblue = new this(0x6495ed);
            this.cornsilk       = new this(0xfff8dc);
            this.crimson        = new this(0xdc143c);
            this.cyan           = new this(0,255,255);
            this.darkBlue       = this.darkblue        = new this(0x00008b);
            this.darkCyan       = this.darkcyan        = new this(0x008b8b);
            this.darkGoldenrod  = this.darkgoldenrod   = new this(0xb8860b);
            this.darkGrey       = this.darkgrey        = this.darkGray  = this.darkgray = new this(0xa9a9a9);
            this.darkGreen      = this.darkgreen       = new this(0x006400);
            this.darkKhaki      = this.darkkhaki       = new this(0xbdb76b);
            this.darkMagenta    = this.darkmagenta     = new this(0x8b008b);
            this.darkOliveGreen = this.darkolivegreen  = new this(0x556b2f);
            this.darkOrange     = this.darkorange      = new this(0xff8c00);
            this.darkOrchid     = this.darkorchid      = new this(0x9932cc);
            this.darkRed        = this.darkred         = new this(0x8b0000);
            this.darkSalmon     = this.darksalmon      = new this(0xe9967a);
            this.darkSeaGreen   = this.darkseagreen    = new this(0x8fbc8f);
            this.darkSlateBlue  = this.darkslateblue   = new this(0x483d8b);
            this.darkSlateGrey  = this.darkSlateGray   = this.darkslategray  = this.darkslategrey = new this(0x2f4f4f);
            this.darkTurquoise  = this.darkturquoise   = new this(0x00ced1);
            this.darkViolet     = this.darkviolet      = new this(0x9400d3);
            this.deepPink       = this.deeppink        = new this(0xff1493);
            this.dimGrey        = this.dimGray  = this.dimgray = this.dimgrey = new this(0x696969);
            this.dodgerBlue     = this.dodgerblue  = new this(0x1e90ff);
            this.firebrick      = new this(0xb22222);
            this.floralwhite    = new this(0xfffaf0);
            this.forestgreen    = new this(0x228b22);
            this.gainsboro      = new this(0xdcdcdc);
            this.ghostwhite     = new this(0xf8f8ff);
            this.gold           = new this(0xffd700);
            this.goldenrod      = new this(0xdaa520);
            this.greenyellow    = new this(0xadff2f);
            this.honeydew       = new this(0xf0fff0);
            this.hotpink        = new this(0xff69b4);
            this.indianred      = new this(0xcd5c5c);
            this.indigo         = new this(0x4b0082);
            this.ivory          = new this(0xfffff0);
            this.khaki          = new this(0xf0e68c);
            this.lavender       = new this(0xe6e6fa);
            this.lavenderblush  = new this(0xfff0f5);
            this.lawngreen      = new this(0x7cfc00);
            this.lemonchiffon   = new this(0xfffacd);
            this.lightBlue      = this.lightblue  = new this(0xadd8e6);
            this.lightCoral     = this.lightcoral = new this(0xf08080);
            this.lightCyan      = this.lightcyan  = new this(0xe0ffff);
            this.lightGoldenRodYellow  = this.lightgoldenrodyellow  = new this(0xfafad2);

            // CSS3
            this.lightGrey       = this.lightGray      = this.lightgray = this.lightgrey = new this(0xd3d3d3);
            this.lightGreen      = this.lightgreen     = new this(0x90ee90);
            this.lightPink       = this.lightpink      = new this(0xffb6c1);
            this.lightSalmon     = this.lightsalmon    = new this(0xffa07a);
            this.lightSeaGreen   = this.lightseagreen  = new this(0x20b2aa);
            this.lightSkyBlue    = this.lightskyblue   = new this(0x87cefa);
            this.lightSlateGrey  = this.lightSlateGray = this.lightslategrey = this.lightslategray  = new this(0x778899);
            this.lightSteelBlue  = this.lightsteelblue = new this(0xb0c4de);
            this.lightYellow     = this.lightyellow    = new this(0xffffe0);
            this.linen           = new this(0xfaf0e6);
            this.magenta         = new this(0xff00ff);
            this.pink            = new this(0xffc0cb);

            this.transparent = new this(0, 0, 0, 0.0);

            this.mergeable = false;
        }
    ]);

    /**
    * Composite view. The view allows developers to combine number of
    * views and renders its together.
    * @class zebkit.draw.CompositeView
    * @param {Object} ...views number of views to be composed.
    * @constructor
    * @extends zebkit.draw.View
    */
    pkg.CompositeView = Class(pkg.View, [
        function() {
            /**
             * Composed views array.
             * @attribute views
             * @type {Array}
             * @protected
             * @readOnly
             */
            this.views = [];

            var args = arguments.length === 1 ? arguments[0] : arguments;
            for(var i = 0; i < args.length; i++) {
                this.views[i] = pkg.$view(args[i]);
                this.$recalc(this.views[i]);
            }
        },

        function $prototype() {
            /**
             * Left padding
             * @readOnly
             * @private
             * @attribute left
             * @type {Integer}
             */

            /**
             * Right padding
             * @private
             * @readOnly
             * @attribute right
             * @type {Integer}
             */

            /**
             * Top padding
             * @private
             * @readOnly
             * @attribute top
             * @type {Integer}
             */

            /**
             * Bottom padding
             * @readOnly
             * @private
             * @attribute bottom
             * @type {Integer}
             */
            this.left = this.right = this.bottom = this.top = this.height = this.width = 0;

            this.getTop = function() {
                return this.top;
            };

            this.getLeft = function() {
                return this.left;
            };

            this.getBottom = function () {
                return this.bottom;
            };

            this.getRight = function () {
                return this.right;
            };

            this.getPreferredSize = function (){
                return { width:this.width, height:this.height};
            };

            this.$recalc = function(v) {
                var b = 0, ps = v.getPreferredSize();
                if (v.getLeft !== undefined) {
                    b = v.getLeft();
                    if (b > this.left) {
                        this.left = b;
                    }
                }

                if (v.getRight !== undefined) {
                    b = v.getRight();
                    if (b > this.right) {
                        this.right = b;
                    }
                }

                if (v.getTop !== undefined) {
                    b = v.getTop();
                    if (b > this.top) {
                        this.top = b;
                    }
                }

                if (v.getBottom !== undefined) {
                    b = v.getBottom();
                    if (b > this.bottom) {
                        this.bottom = b;
                    }
                }


                if (ps.width > this.width) {
                    this.width = ps.width;
                }

                if (ps.height > this.height) {
                    this.height = ps.height;
                }

                if (this.voutline === undefined && v.outline !== undefined) {
                    this.voutline = v;
                }
            };

            /**
             * Iterate over composed views.
             * @param  {Function} f callback that is called for every iterated view. The callback
             * gets a view index and view itself as its argument.
             * @method iterate
             */
            this.iterate = function(f) {
                for(var i = 0; i < this.views.length; i++) {
                    f.call(this, i, this.views[i]);
                }
            };

            this.recalc = function() {
                this.left = this.right = this.bottom = this.top = this.height = this.width = 0;
                this.iterate(function(k, v) {
                    this.$recalc(v);
                });
            };

            this.ownerChanged = function(o) {
                this.iterate(function(k, v) {
                    if (v !== null && v.ownerChanged !== undefined) {
                        v.ownerChanged(o);
                    }
                });
            };

            this.paint = function(g,x,y,w,h,d) {
                var ctx = false;
                for(var i = 0; i < this.views.length; i++) {
                    var v = this.views[i];
                    v.paint(g, x, y, w, h, d);

                    if (i < this.views.length - 1 && typeof v.outline === 'function' && v.outline(g, x, y, w, h, d)) {
                        if (ctx === false) {
                            g.save();
                            ctx = true;
                        }
                        g.clip();
                    }
                }

                if (ctx === true) {
                    g.restore();
                }
            };

            /**
             * Return number of composed views.
             * @return {Integer} number of composed view.
             * @method  count
             */
            this.count = function() {
                return this.views.length;
            };

            this.outline = function(g,x,y,w,h,d) {
                return this.voutline !== undefined && this.voutline.outline(g,x,y,w,h,d);
            };
        }
    ]);

    /**
    * ViewSet view. The view set is a special view container that includes
    * number of views accessible by a key and allows only one view be active
    * in a particular time. Active is view that has to be rendered. The view
    * set can be used to store number of decorative elements where only one
    * can be rendered depending from an UI component state.
    * @param {Object} views object that represents views instances that have
    * to be included in the ViewSet
    * @constructor
    * @class zebkit.draw.ViewSet
    * @extends zebkit.draw.CompositeView
    */
    pkg.ViewSet = Class(pkg.CompositeView, [
        function(views) {
            if (arguments.length === 0 || views === null) {
                throw new Error("" + views);
            }

            /**
             * Views set
             * @attribute views
             * @type Object
             * @default {}
             * @readOnly
            */
            this.views = {};
            this.$size = 0;

            var activeId = "*";
            for(var k in views) {
                var id = k;
                if (k[0] === '+') {
                    id = k.substring(1);
                    activeId = id;
                }

                this.views[id] = pkg.$view(views[k]);
                this.$size++;
                if (this.views[id] !== null) {
                    this.$recalc(this.views[id]);
                }
            }

            this.activate(activeId);
        },

        function $prototype() {
            /**
             * Active in the set view
             * @attribute activeView
             * @type View
             * @default null
             * @readOnly
            */
            this.activeView = null;

            this.paint = function(g,x,y,w,h,d) {
                if (this.activeView !== null) {
                    this.activeView.paint(g, x, y, w, h, d);
                }
            };

            this.count = function() {
                return this.$size;
            };

            /**
             * Activate the given view from the given set.
             * @param  {String} id a key of a view from the set to be activated. Pass
             * null to make current view to undefined state
             * @return {Boolean} true if new view has been activated, false otherwise
             * @method activate
             */
            this.activate = function(id) {
                var old = this.activeView;

                if (id === null) {
                    return (this.activeView = null) !== old;
                } else  if (this.views.hasOwnProperty(id)) {
                    return (this.activeView = this.views[id]) !== old;
                } else if (id.length > 1 && id[0] !== '*' && id[id.length - 1] !== '*') {
                    var i = id.indexOf('.');
                    if (i > 0) {
                        var k = id.substring(0, i) + '.*';
                        if (this.views.hasOwnProperty(k)) {
                            return (this.activeView = this.views[k]) !== old;
                        } else {
                            k = "*" + id.substring(i);
                            if (this.views.hasOwnProperty(k)) {
                                return (this.activeView = this.views[k]) !== old;
                            }
                        }
                    }
                }

                // "*" is default view
                return this.views.hasOwnProperty("*") ? (this.activeView = this.views["*"]) !== old
                                                      : false;
            };

            this.iterate = function(f) {
                for(var k in this.views) {
                    f.call(this, k, this.views[k]);
                }
            };
        }
    ]);

    /**
     * Abstract shape view.
     * @param  {String}  [c]   a color of the shape
     * @param  {String}  [fc]  a fill color of the shape
     * @param  {Integer} [w]   a line size
     * @class zebkit.draw.Shape
     * @constructor
     * @extends zebkit.draw.View
     */
    pkg.Shape = Class(pkg.View, [
        function (c, fc, w) {
            if (arguments.length > 0) {
                this.color = c;
                if (arguments.length > 1) {
                    this.fillColor = fc;
                    if (arguments.length > 1) {
                        this.lineWidth = this.gap = w;
                    }
                }
            }
        },

        function $prototype() {
            this.gap = 1;

            /**
             * Shape color.
             * @attribute color
             * @type {String}
             * @default  "gray"
             */
            this.color = "gray";

            /**
             * Shape line width
             * @attribute lineWidth
             * @type {Integer}
             * @default 1
             */
            this.lineWidth = 1;

            /**
             * Fill color. null if the shape should not be filled with a color
             * @attribute fillColor
             * @type {String}
             * @default null
             */
            this.fillColor = null;

            //TODO: comment
            this.width = this.height = 8;

            //TODO: comment
            this.stretched = true;


            // TODO: comment
            this.setLineWidth = function(w) {
                if (w !== this.lineWidth) {
                    this.lineWidth = this.gap = w;
                }
                return this;
            };

            this.paint = function(g,x,y,w,h,d) {
                if (this.stretched === false) {
                    x = x + Math.floor((w - this.width) / 2);
                    y = y + Math.floor((h - this.height) / 2);
                    w = this.width;
                    h = this.height;
                }

                this.outline(g,x,y,w,h,d);

                if (this.fillColor !== null) {
                    if (this.fillColor !== g.fillStyle) {
                        g.fillStyle = this.fillColor;
                    }
                    g.fill();
                }

                if (this.color !== null) {
                    if (g.lineWidth !== this.lineWidth) {
                        g.lineWidth = this.lineWidth;
                    }

                    if (this.color !== g.strokeStyle) {
                        g.strokeStyle = this.color;
                    }
                    g.stroke();
                }
            };

            this.getPreferredSize = function() {
                return {
                    width  : this.width,
                    height : this.height
                };
            };
        }
    ]);


    /**
    * Sunken border view
    * @class zebkit.draw.Sunken
    * @constructor
    * @param {String} [brightest] a brightest border line color
    * @param {String} [moddle] a middle border line color
    * @param {String} [darkest] a darkest border line color
    * @extends zebkit.draw.View
    */
    pkg.Sunken = Class(pkg.View, [
        function (brightest,middle,darkest) {
            if (arguments.length > 0) {
                this.brightest = brightest;
                if (arguments.length > 1) {
                    this.middle = middle;
                    if (arguments.length > 2) {
                        this.darkest   = darkest;
                    }
                }
            }
        },

        function $prototype() {
            /**
             * Brightest border line color
             * @attribute brightest
             * @readOnly
             * @type {String}
             * @default "white"
             */

            /**
             * Middle border line color
             * @attribute middle
             * @readOnly
             * @type {String}
             * @default "gray"
             */

            /**
             * Darkest border line color
             * @attribute darkest
             * @readOnly
             * @type {String}
             * @default "black"
             */
            this.brightest = "white";
            this.middle    = "gray" ;
            this.darkest   = "black";

            this.paint = function(g,x1,y1,w,h,d){
                var x2 = x1 + w - 1, y2 = y1 + h - 1;
                g.setColor(this.middle);
                g.drawLine(x1, y1, x2 - 1, y1);
                g.drawLine(x1, y1, x1, y2 - 1);
                g.setColor(this.brightest);
                g.drawLine(x2, y1, x2, y2 + 1);
                g.drawLine(x1, y2, x2, y2);
                g.setColor(this.darkest);
                g.drawLine(x1 + 1, y1 + 1, x1 + 1, y2);
                g.drawLine(x1 + 1, y1 + 1, x2, y1 + 1);
            };
        }
    ]);

    /**
    * Etched border view
    * @class zebkit.draw.Etched
    * @constructor
    * @param {String} [brightest] a brightest border line color
    * @param {String} [moddle] a middle border line color
    * @extends zebkit.draw.View
    */
    pkg.Etched = Class(pkg.View, [
        function (brightest, middle) {
            if (arguments.length > 0) {
                this.brightest = brightest;
                if (arguments.length > 1) {
                    this.middle = middle;
                }
            }
        },

        function $prototype() {
            /**
             * Brightest border line color
             * @attribute brightest
             * @readOnly
             * @type {String}
             * @default "white"
             */

            /**
             * Middle border line color
             * @attribute middle
             * @readOnly
             * @type {String}
             * @default "gray"
             */
            this.brightest = "white";
            this.middle    = "gray" ;

            this.paint = function(g,x1,y1,w,h,d){
                var x2 = x1 + w - 1, y2 = y1 + h - 1;
                g.setColor(this.middle);
                g.drawLine(x1, y1, x1, y2 - 1);
                g.drawLine(x2 - 1, y1, x2 - 1, y2);
                g.drawLine(x1, y1, x2, y1);
                g.drawLine(x1, y2 - 1, x2 - 1, y2 - 1);

                g.setColor(this.brightest);
                g.drawLine(x2, y1, x2, y2);
                g.drawLine(x1 + 1, y1 + 1, x1 + 1, y2 - 1);
                g.drawLine(x1 + 1, y1 + 1, x2 - 1, y1 + 1);
                g.drawLine(x1, y2, x2 + 1, y2);
            };
        }
    ]);

    /**
    * Raised border view
    * @class zebkit.draw.Raised
    * @param {String} [brightest] a brightest border line color
    * @param {String} [middle] a middle border line color
    * @constructor
    * @extends zebkit.draw.View
    */
    pkg.Raised = Class(pkg.View, [
        function(brightest, middle) {
            /**
             * Brightest border line color
             * @attribute brightest
             * @readOnly
             * @type {String}
             * @default "white"
             */

            /**
             * Middle border line color
             * @attribute middle
             * @readOnly
             * @type {String}
             * @default "gray"
             */

            if (arguments.length > 0) {
                this.brightest = brightest;
                if (arguments.length > 1) {
                    this.middle = middle;
                }
            }
        },

        function $prototype() {
            this.brightest = "white";
            this.middle    = "gray";

            this.paint = function(g,x1,y1,w,h,d){
                var x2 = x1 + w - 1, y2 = y1 + h - 1;
                g.setColor(this.brightest);
                g.drawLine(x1, y1, x2, y1);
                g.drawLine(x1, y1, x1, y2);
                g.setColor(this.middle);
                g.drawLine(x2, y1, x2, y2 + 1);
                g.drawLine(x1, y2, x2, y2);
            };
        }
    ]);

    /**
    * Dotted border view
    * @class zebkit.draw.Dotted
    * @param {String} [c] the dotted border color
    * @constructor
    * @extends zebkit.draw.View
    */
    pkg.Dotted = Class(pkg.View, [
        function (c){
            if (arguments.length > 0) {
                this.color = c;
            }
        },

        function $prototype() {
            /**
             * @attribute color
             * @readOnly
             * @type {String}
             * @default "black"
             */
            this.color = "black";

            this.paint = function(g,x,y,w,h,d){
                g.setColor(this.color);
                g.drawDottedRect(x, y, w, h);
            };
        }
    ]);

    /**
     * Border view. Can be used to render CSS-like border. Border can be applied to any
     * zebkit UI component by calling setBorder method:

            // create label component
            var lab = new zebkit.ui.Label("Test label");

            // set red border to the label component
            lab.setBorder(new zebkit.draw.Border("red"));

     * @param  {String}  [c] border color
     * @param  {Integer} [w] border width
     * @param  {Integer} [r] border corners radius
     * @constructor
     * @class zebkit.draw.Border
     * @extends zebkit.draw.View
     */
    pkg.Border = Class(pkg.View, [
        function(c, w, r) {
            if (arguments.length > 0) {
                this.color = c;
                if (arguments.length > 1) {
                    this.width = this.gap = w;
                    if (arguments.length > 2) {
                        this.radius = r;
                        if (arguments.length > 3) {
                            for (var i = 3; i < arguments.length; i++) {
                                this.setSides(arguments[i]);
                            }
                        }
                    }
                }
            }
        },

        function $prototype() {
            /**
             * Border color
             * @attribute color
             * @readOnly
             * @type {String}
             * @default "gray"
             */

            /**
             * Border line width
             * @attribute width
             * @readOnly
             * @type {Integer}
             * @default 1
             */

            /**
             * Border radius
             * @attribute radius
             * @readOnly
             * @type {Integer}
             * @default 0
             */

            this.color  = "gray";
            this.gap    = this.width = 1;
            this.radius = 0;
            this.sides  = 15;

            /**
             * Control border sides visibility.
             * @param {String} side*  list of visible sides. You can pass number of arguments
             * to say which sides of the border are visible. The arguments can equal one of the
             * following value: "top", "bottom", "left", "right"
             * @method  setSides
             * @chainable
             */
            this.setSides = function() {
                this.sides = 0;
                for(var i = 0; i < arguments.length; i++) {
                    if (arguments[i] === "top") {
                        this.sides  |= 1;
                    } else if (arguments[i] === "left") {
                        this.sides  |= 2;
                    } else if (arguments[i] === "bottom") {
                        this.sides  |= 4;
                    } else if (arguments[i] === "right" ) {
                        this.sides  |= 8;
                    }
                }

                return this;
            };

            this.paint = function(g,x,y,w,h,d){
                if (this.color !== null && this.width > 0) {
                    var ps = g.lineWidth;

                    if (g.lineWidth !== this.width) {
                        g.lineWidth = this.width;
                    }

                    if (this.radius > 0) {
                        this.outline(g,x,y,w,h, d);
                        g.setColor(this.color);
                        g.stroke();
                    } else if (this.sides !== 15) {
                        g.setColor(this.color);
                        // top
                        if ((this.sides & 1) > 0) {
                            g.drawLine(x, y, x + w, y, this.width);
                        }

                        // right
                        if ((this.sides & 8) > 0) {
                            g.drawLine(x + w - this.width, y, x + w - this.width, y + h, this.width);
                        }

                        // bottom
                        if ((this.sides & 4) > 0) {
                            g.drawLine(x, y + h - this.width, x + w, y + h - this.width, this.width);
                        }

                        // left
                        if ((this.sides & 2) > 0) {
                            g.drawLine(x, y, x, y + h, this.width);
                        }
                    } else {
                        var dt = this.width / 2;

                        g.beginPath();
                        g.rect(x + dt, y + dt, w - this.width, h - this.width);
                        g.closePath();
                        g.setColor(this.color);
                        g.stroke();
                    }

                    if (g.lineWidth !== ps) {
                        g.lineWidth = ps;
                    }
                }
            };

            /**
             * Defines border outline for the given 2D Canvas context
             * @param  {CanvasRenderingContext2D} g
             * @param  {Integer} x x coordinate
             * @param  {Integer} y y coordinate
             * @param  {Integer} w required width
             * @param  {Integer} h required height
             * @param  {Integer} d target UI component
             * @method outline
             * @return {Boolean} true if the outline has to be applied as an
             * UI component shape
             */
            this.outline = function(g,x,y,w,h,d) {
                if (this.radius <= 0) {
                    return false;
                }

                var r  = this.radius,
                    dt = this.width / 2,
                    xx = x + w - dt,
                    yy = y + h - dt;

                x += dt;
                y += dt;

                // !!! this code can work improperly in IE 10 in Vista !
                // g.beginPath();
                // g.moveTo(x+r, y);
                // g.arcTo(xx, y, xx, yy, r);
                // g.arcTo(xx, yy, x, yy, r);
                // g.arcTo(x, yy, x, y, r);
                // g.arcTo(x, y, xx, y, r);
                // g.closePath();
                // return true;

                g.beginPath();
                g.moveTo(x + r, y);
                g.lineTo(xx - r, y);
                g.quadraticCurveTo(xx, y, xx, y + r);
                g.lineTo(xx, yy  - r);
                g.quadraticCurveTo(xx, yy, xx - r, yy);
                g.lineTo(x + r, yy);
                g.quadraticCurveTo(x, yy, x, yy - r);
                g.lineTo(x, y + r);
                g.quadraticCurveTo(x, y, x + r, y);
                g.closePath();
                return true;
            };
        }
    ]);

    /**
     * Round border view.
     * @param  {String}  [col] border color. Use null as the
     * border color value to prevent painting of the border
     * @param  {Integer} [width] border width
     * @constructor
     * @class zebkit.draw.RoundBorder
     * @extends zebkit.draw.View
     */
    pkg.RoundBorder = Class(pkg.View, [
        function(col, width) {
            if (arguments.length > 0) {
                if (zebkit.isNumber(col)) {
                    this.width = col;
                } else {
                    this.color = col;
                    if (zebkit.isNumber(width)) {
                        this.width = width;
                    }
                }
            }
            this.gap = this.width;
        },

        function $prototype() {
            /**
             * Border width
             * @attribute width
             * @readOnly
             * @type {Integer}
             * @default 1
             */
            this.width = 1;

            /**
             * Border color
             * @attribute color
             * @readOnly
             * @type {String}
             * @default null
             */
            this.color = null;

            /**
             * Color to fill the inner area surrounded with the round border.
             * @attribute fillColor
             * @type {String}
             * @default null
             */
            this.fillColor = null;

            this.paint = function(g,x,y,w,h,d) {
                if (this.color !== null && this.width > 0) {
                    this.outline(g,x,y,w,h,d);
                    g.setColor(this.color);
                    g.stroke();
                    if (this.fillColor !== null) {
                       g.setColor(this.fillColor);
                       g.fill();
                    }
                }
            };

            this.outline = function(g,x,y,w,h,d) {
                g.lineWidth = this.width;
                if (w === h) {
                    g.beginPath();
                    g.arc(Math.floor(x + w / 2) + (w % 2 === 0 ? 0 : 0.5),
                          Math.floor(y + h / 2) + (h % 2 === 0 ? 0 : 0.5),
                          Math.floor((w - g.lineWidth) / 2), 0, 2 * Math.PI, false);
                    g.closePath();
                } else {
                    g.ovalPath(x,y,w,h);
                }
                return true;
            };

            this.getPreferredSize = function() {
                var s = this.width * 8;
                return  {
                    width : s, height : s
                };
            };
        }
    ]);

    /**
     * Render class that allows developers to render a border with a title area.
     * The title area has to be specified by an UI component that uses the border
     * by defining "getTitleInfo()"" method. The method has to return object that
     * describes title size, location and alignment:
     *
     *
     *      {
     *        x: {Integer}, y: {Integer},
     *        width: {Integer}, height: {Integer},
     *        orient: {String}
     *      }
     *
     *
     * @class zebkit.draw.TitledBorder
     * @extends zebkit.draw.Render
     * @constructor
     * @param zebkit.draw.View border  a border to be rendered with a title area
     * @param {String} [lineAlignment] a line alignment. Specifies how
     * a title area has to be aligned relatively border line:
     *
     *       "bottom"  - title area will be placed on top of border line:
     *                    ___| Title area |___
     *
     *
     *      "center"   - title area will be centered relatively to border line:
     *                    ---| Title area |-----
     *
     *
     *      "top"      - title area will be placed underneath of border line:
     *                     ____              ________
     *                         |  Title area |
     *
     */
    pkg.TitledBorder = Class(pkg.Render, [
        function (b, a){
            if (arguments.length > 1) {
                this.lineAlignment = zebkit.util.validateValue(a, "bottom", "top", "center");
            }

            this.setValue(pkg.$view(b));
        },

        function $prototype() {
            this.lineAlignment = "bottom";

            this.getTop  = function (){
                return this.target.getTop();
            };

            this.getLeft = function (){
                return this.target.getLeft();
            };

            this.getRight = function (){
                return this.target.getRight();
            };

            this.getBottom = function (){
                return this.target.getBottom();
            };

            this.outline = function (g,x,y,w,h,d) {
                var xx = x + w, yy = y + h;
                if (d.getTitleInfo !== undefined) {
                    var r = d.getTitleInfo();
                    if (r !== null) {
                        switch(r.orient) {
                            case "bottom":
                                var bottom = this.target.getBottom();
                                switch (this.lineAlignment) {
                                    case "center" : yy = r.y + Math.floor((r.height - bottom)/ 2) + bottom; break;
                                    case "top"    : yy = r.y + r.height + bottom; break;
                                    case "bottom" : yy = r.y; break;
                                }
                                break;
                            case "top":
                                var top = this.target.getTop();
                                switch (this.lineAlignment) {
                                    case "center" : y = r.y + Math.floor((r.height - top)/2);   break; // y = r.y + Math.floor(r.height/ 2) ; break;
                                    case "top"    : y = r.y - top; break;
                                    case "bottom" : y = r.y + r.height; break;
                                }
                                break;
                            case "left":
                                var left = this.target.getLeft();
                                switch (this.lineAlignment) {
                                    case "center" : x = r.x + Math.floor((r.width - left) / 2); break;
                                    case "top"    : x = r.x - left; break;
                                    case "bottom" : x = r.x + r.width; break;
                                }
                                break;
                            case "right":
                                var right = this.target.getRight();
                                switch (this.lineAlignment) {
                                    case "center" : xx = r.x + Math.floor((r.width - right) / 2) + right; break;
                                    case "top"    : xx = r.x + r.width + right; break;
                                    case "bottom" : xx = r.x; break;
                                }
                                break;
                        }
                    }
                }

                if (this.target !== null &&
                    this.target.outline !== undefined &&
                    this.target.outline(g, x, y, xx - x, yy - y, d) === true)
                {
                    return true;
                }

                g.beginPath();
                g.rect(x, y, xx - x, yy - y);
                g.closePath();
                return true;
            };

            this.$isIn = function(clip, x, y, w, h) {
                var rx = clip.x > x ? clip.x : x,
                    ry = clip.y > y ? clip.y : y,
                    rw = Math.min(clip.x + clip.width, x + w) - rx,
                    rh = Math.min(clip.y + clip.height, y + h) - ry;
                return (clip.x === rx && clip.y === ry && clip.width === rw && clip.height === rh);
            };

            this.paint = function(g,x,y,w,h,d){
                if (d.getTitleInfo !== undefined) {
                    var r = d.getTitleInfo();
                    if (r !== null) {
                        var xx = x + w, yy = y + h, t = g.$states[g.$curState];
                        switch (r.orient) {
                            case "top":
                                var top = this.target.getTop();
                                // compute border y
                                switch (this.lineAlignment) {
                                    case "center" : y = r.y + Math.floor((r.height - top) / 2) ; break;
                                    case "top"    : y = r.y - top; break;
                                    case "bottom" : y = r.y + r.height; break;
                                }

                                // skip rendering border if the border is not in clip rectangle
                                // This is workaround because of IE10/IE11 have bug what causes
                                // handling rectangular clip + none-rectangular clip side effect
                                // to "fill()" subsequent in proper working (fill without respect of
                                // clipping  area)
                                if (this.$isIn(t, x + this.target.getLeft(), y,
                                               w - this.target.getRight() - this.target.getLeft(),
                                               yy - y - this.target.getBottom()))
                                {
                                    return;
                                }

                                g.save();
                                g.beginPath();

                                g.moveTo(x, y);
                                g.lineTo(r.x, y);
                                g.lineTo(r.x, y + top);
                                g.lineTo(r.x + r.width, y + top);
                                g.lineTo(r.x + r.width, y);
                                g.lineTo(xx, y);
                                g.lineTo(xx, yy);
                                g.lineTo(x, yy);
                                g.lineTo(x, y);

                                break;
                            case "bottom":
                                var bottom = this.target.getBottom();
                                switch (this.lineAlignment) {
                                    case "center" : yy = r.y + Math.floor((r.height - bottom) / 2) + bottom; break;
                                    case "top"    : yy = r.y + r.height + bottom; break;
                                    case "bottom" : yy = r.y ; break;
                                }

                                if (this.$isIn(t, x + this.target.getLeft(), y + this.target.getTop(),
                                                  w - this.target.getRight() - this.target.getLeft(),
                                                  yy - y - this.target.getTop()))
                                {
                                    return;
                                }

                                g.save();
                                g.beginPath();

                                g.moveTo(x, y);
                                g.lineTo(xx, y);
                                g.lineTo(xx, yy);
                                g.lineTo(r.x + r.width, yy);
                                g.lineTo(r.x + r.width, yy - bottom);
                                g.lineTo(r.x, yy - bottom);
                                g.lineTo(r.x, yy);
                                g.lineTo(x, yy);
                                g.lineTo(x, y);

                                break;
                            case "left":
                                var left = this.target.getLeft();
                                switch (this.lineAlignment) {
                                    case "center" : x = r.x + Math.floor((r.width - left) / 2); break;
                                    case "top"    : x = r.x  - left; break;
                                    case "bottom" : x = r.x + r.width; break;
                                }

                                if (this.$isIn(t, x, y + this.target.getTop(),
                                               xx - x - this.target.getRight(),
                                               h - this.target.getTop() - this.target.getBottom()))
                                {
                                    return;
                                }

                                g.save();
                                g.beginPath();

                                g.moveTo(x, y);
                                g.lineTo(xx, y);
                                g.lineTo(xx, yy);
                                g.lineTo(x, yy);
                                g.lineTo(x, r.y + r.height);
                                g.lineTo(x + left, r.y + r.height);
                                g.lineTo(x + left, r.y);
                                g.lineTo(x, r.y);
                                g.lineTo(x, y);

                                break;
                            case "right":
                                var right = this.target.getRight();
                                switch (this.lineAlignment) {
                                    case "center" : xx = r.x + Math.floor((r.width - right) / 2) + right; break;
                                    case "top"    : xx = r.x  + r.width + right; break;
                                    case "bottom" : xx = r.x; break;
                                }

                                if (this.$isIn(t, x + this.target.getLeft(),
                                                  y + this.target.getTop(),
                                                  xx - x - this.target.getLeft(),
                                                  h - this.target.getTop() - this.target.getBottom()))
                                {
                                    return;
                                }

                                g.save();
                                g.beginPath();

                                g.moveTo(x, y);
                                g.lineTo(xx, y);
                                g.lineTo(xx, r.y);
                                g.lineTo(xx - right, r.y);
                                g.lineTo(xx - right, r.y + r.height);
                                g.lineTo(xx, r.y + r.height);
                                g.lineTo(xx, yy);
                                g.lineTo(x, yy);
                                g.lineTo(x, y);
                                break;
                            // throw error to avoid wrongly called restore method below
                            default: throw new Error("Invalid title orientation " + r.orient);
                        }

                        g.closePath();
                        g.clip();
                        this.target.paint(g, x, y, xx - x, yy - y, d);
                        g.restore();
                    }
                } else {
                    this.target.paint(g, x, y, w, h, d);
                }
            };
        }
    ]);



    /**
     * Break the given line to parts that can be placed in the area with
     * the specified width.
     * @param  {zebkit.Font} font a font to compute text metrics
     * @param  {Integer} maxWidth a maximal area with
     * @param  {String} line   a line
     * @param  {Array} result a result array to accumulate wrapped lines
     * @method  wrapToLines
     * @for zebkit.draw
     */
    pkg.wrapToLines = function(font, maxWidth, line, result) {
        // The method goes through number of tokens the line is split. Token
        // is a word or delimiter. Delimiter is specified with the regexp
        // (in this implementation delimiter is one or more space). Delimiters
        // are also considered as tokens. On every iteration accumulated tokens
        // width is compared with maximal possible and if the width is greater
        // then maximal we sift to previous tokens set and put it as line to
        // result. Then start iterating again  from the last token we could
        // not accommodate.
        if (line === "") {
            result.push(line);
        } else {
            var len = font.stringWidth(line);
            if (len <= maxWidth) {
                result.push(line);
            } else {
                var m   = "not null",
                    b   = true,
                    i   = 0,
                    al  = 0,
                    pos = 0,
                    skip = false,
                    tokenEnd = 0,
                    searchRE = /\s+/g,
                    tokenStart = -1;

                for(; pos !== line.length; ) {
                    if (skip !== true && m !== null) {
                        if (b) {
                            m = searchRE.exec(line);
                            if (m === null) {
                                tokenStart = tokenEnd;
                                tokenEnd   = line.length;
                            }
                        }

                        if (m !== null) {
                            if (m.index > tokenEnd) {
                                // word token detected
                                tokenStart = tokenEnd;
                                tokenEnd   = m.index;
                                b = false;
                            } else {
                                // space token detected
                                tokenStart = m.index;
                                tokenEnd   = m.index + m[0].length;
                                b = true;
                            }
                        }
                    }
                    skip = false;
                    al = font.stringWidth(line.substring(pos, tokenEnd));
                    if (al > maxWidth) {
                        if (i === 0) {
                            result.push(line.substring(pos, tokenEnd));
                            pos = tokenEnd;
                        } else {
                            result.push(line.substring(pos, tokenStart));
                            pos = tokenStart;
                            skip = true;
                            i = 0;
                        }
                    } else {
                        if (tokenEnd === line.length) {
                            result.push(line.substring(pos, tokenEnd));
                            break;
                        } else {
                            i++;
                        }
                    }
                }
            }
        }
    };


    /**
     * Default normal font
     * @attribute font
     * @type {zebkit.Font}
     * @for  zebkit.draw
     */
    pkg.font = new zebkit.Font("Arial", 14);

    /**
     * Default small font
     * @attribute smallFont
     * @type {zebkit.Font}
     * @for  zebkit.draw
     */
    pkg.smallFont = new zebkit.Font("Arial", 10);

    /**
     * Default bold font
     * @attribute boldFont
     * @type {zebkit.Font}
     * @for  zebkit.draw
     */
    pkg.boldFont = new zebkit.Font("Arial", "bold", 12);

    /**
     * Base class to build text render implementations.
     * @class  zebkit.draw.BaseTextRender
     * @constructor
     * @param  {Object} [target]  target component to be rendered
     * @extends zebkit.draw.Render
     */
    pkg.BaseTextRender = Class(pkg.Render, [
        function $clazz() {
            this.font          =  pkg.font;
            this.color         = "gray";
            this.disabledColor = "white";
        },

        function $prototype(clazz) {
            /**
             * UI component that holds the text render
             * @attribute owner
             * @default null
             * @readOnly
             * @protected
             * @type {zebkit.layout.Layoutable}
             */
            this.owner = null;

            /**
             * Line indention
             * @attribute lineIndent
             * @type {Integer}
             * @default 1
             */
            this.lineIndent = 1;

            // implement position metric methods
            this.getMaxOffset = this.getLineSize = this.getLines = function() {
                return 0;
            };

            /**
             * Set the rendered text font.
             * @param  {String|zebkit.Font} f a font as CSS string or
             * zebkit.Font class instance
            *  @chainable
             * @method setFont
             */
            this.setFont = function(f) {
                if ((f instanceof zebkit.Font) === false && f !== null) {
                    f = zebkit.newInstance(zebkit.Font, arguments);
                }

                if (f != this.font) {
                    this.font = f;

                    if (this.owner !== null && this.owner.isValid === true) {
                        this.owner.invalidate();
                    }

                    if (this.invalidate !== undefined) {
                        this.invalidate();
                    }
                }
                return this;
            };

            /**
             * Resize font
             * @param  {String|Integer} size a new size of the font
             * @chainable
             * @method resizeFont
             */
            this.resizeFont = function(size) {
                return this.setFont(this.font.resize(size));
            };

            /**
             * Re-style font.
             * @param {String} style a new font style
             * @method restyleFont
             * @chainable
             */
            this.restyleFont = function(style) {
                return this.setFont(this.font.restyle(style));
            };

            /**
             * Get line height
             * @method getLineHeight
             * @return {Integer} a line height
             */
            this.getLineHeight = function() {
                return this.font.height;
            };

            /**
             * Set rendered text color
             * @param  {String} c a text color
             * @method setColor
             * @chainable
             */
            this.setColor = function(c) {
                if (c != this.color) {
                    this.color = c.toString();
                }
                return this;
            };

            /**
             * Called whenever an owner UI component has been changed
             * @param  {zebkit.layout.Layoutable} v a new owner UI component
             * @method ownerChanged
             */
            this.ownerChanged = function(v) {
                this.owner = v;
            };

            this.getLine = function(i) {
                throw new Error("Not implemented");
            };

            /**
             * Overridden method to catch target value changing events.
             * @param  {Object} o an old target value
             * @param  {Object} n a new target value
             * @method valueWasChanged
             */
            this.valueWasChanged = function(o, n) {
                if (this.owner !== null && this.owner.isValid) {
                    this.owner.invalidate();
                }

                if (this.invalidate !== undefined) {
                    this.invalidate();
                }
            };

            this.toString = function() {
                return this.target === null ? null
                                            : this.target;
            };
        }
    ]);

    /**
     * Lightweight implementation of single line string render. The render requires
     * a simple string as a target object.
     * @param {String} str a string to be rendered
     * @param {zebkit.Font} [font] a text font
     * @param {String} [color] a text color
     * @constructor
     * @extends zebkit.draw.BaseTextRender
     * @use zebkit.util.Position.Metric
     * @class zebkit.draw.StringRender
     */
    pkg.StringRender = Class(pkg.BaseTextRender, zebkit.util.Position.Metric, [
        function $prototype() {
            /**
             * Calculated string width (in pixels). If string width has not been calculated
             * the value is set to -1.
             * @attribute stringWidth
             * @protected
             * @default -1
             * @type {Integer}
             */
            this.stringWidth = -1;

            // for the sake of speed up construction of the widely used render
            // declare it none standard way.
            this[''] = function(txt, font, color) {
                this.setValue(txt);

                /**
                 * Font to be used to render the target string
                 * @attribute font
                 * @readOnly
                 * @type {zebkit.Font}
                 */
                this.font = arguments.length > 1 ? font : this.clazz.font;
                if ((this.font instanceof zebkit.Font) === false) {
                    this.font = zebkit.$font(this.font);
                }

                /**
                 * Color to be used to render the target string
                 * @readOnly
                 * @attribute color
                 * @type {String}
                 */
                this.color = arguments.length > 2 ? color : this.clazz.color;
            };

            // TODO: the methods below simulate text model methods. it is done for the future
            // usage of the string render for text field. The render can be convenient for masked
            // input implementation.
            this.write = function(s, off) {
                if (off === 0) {
                    this.target = s + this.target;
                } else if (off === s.length) {
                    this.target = this.target + s;
                } else {
                    this.target = this.target.substring(0, off) + s + this.target.substring(off);
                }

                return true;
            };

            this.remove = function(off, len) {
                this.target = this.target.substring(0, off) +
                              this.target.substring(off + len);
                return true;
            };

            this.replace = function(s, off, size) {
                if (s.length === 0) {
                    return this.remove(off, size);
                } else if (size === 0) {
                    return this.write(s, off);
                } else {
                    var b = this.remove(off, size, false);
                    return this.write(s, off) && b;
                }
            };

            /**
             * Implementation of position metric interface. Returns maximal
             * possible offset within the given string.
             * @method getMaxOffset
             * @return {Integer} a maximal possible offset.
             */
            this.getMaxOffset = function() {
                return this.target.length;
            };

            /**
             * Implementation of position metric interface. Returns the given
             * line size (in characters).
             * @param {Integer}  line a line number. This render supports only
             * single line.
             * @method getLineSize
             * @return {Integer} a line size
             */
            this.getLineSize = function(line) {
                if (line > 0) {
                    throw new RangeError("Line number " + line + " is out of the range");
                }
                return this.target.length + 1;
            };

            /**
             * Implementation of position metric interface. Returns number
             * of lines.
             * @method getLines
             * @return {Integer} a number of lines.
             */
            this.getLines = function() {
                return 1;
            };

            /**
             * Calculates string width if it has not been done yet.
             * @method calcLineWidth
             * @protected
             * @return {Integer} a string width
             */
            this.calcLineWidth = function() {
                if (this.stringWidth < 0) {
                    this.stringWidth = this.font.stringWidth(this.target);
                }
                return this.stringWidth;
            };

            /**
             * Invalidate the render state. Invalidation flushes string metrics
             * to be re-calculated again.
             * @protected
             * @method invalidate
             */
            this.invalidate = function() {
                this.stringWidth = -1;
            };

            this.paint = function(g,x,y,w,h,d) {
                // save a few milliseconds
                if (this.font.s !== g.font) {
                    g.setFont(this.font);
                }

                if (d !== null && d.getStartSelection !== undefined) {
                    var startSel = d.getStartSelection(),
                        endSel   = d.getEndSelection();

                    if (startSel     !== null       &&
                        endSel       !== null       &&
                        startSel.col !== endSel.col &&
                        d.selectView !== null          )
                    {
                        d.selectView.paint(g, x + this.font.charsWidth(this.target, 0, startSel.col),
                                              y,
                                              this.font.charsWidth(this.target,
                                                                   startSel.col,
                                                                   endSel.col - startSel.col),
                                              this.getLineHeight(), d);
                    }
                }

                // save a few milliseconds
                if (this.color !== g.fillStyle) {
                    g.fillStyle = this.color;
                }

                if (d !== null && d.isEnabled === false) {
                    g.fillStyle = d !== null &&
                                  d.disabledColor !== null &&
                                  d.disabledColor !== undefined ? d.disabledColor
                                                                : this.clazz.disabledColor;
                }

                g.fillText(this.target, x, y);
            };

            /**
             * Get the given line.
             * @param  {Integer} l a line number
             * @return {String} a line
             * @method getLine
             */
            this.getLine = function(l) {
                if (l < 0 || l > 1) {
                    throw new RangeError();
                }
                return this.target;
            };

            this.getPreferredSize = function() {
                if (this.stringWidth < 0) {
                    this.stringWidth = this.font.stringWidth(this.target);
                }

                return {
                    width: this.stringWidth,
                    height: this.font.height
                };
            };
        }
    ]);

    /**
     * Text render that expects and draws a text model or a string as its target
     * @class zebkit.draw.TextRender
     * @constructor
     * @extends zebkit.draw.BaseTextRender
     * @uses zebkit.util.Position.Metric
     * @param  {String|zebkit.data.TextModel} text a text as string or text model object
     */
    pkg.TextRender = Class(pkg.BaseTextRender, zebkit.util.Position.Metric, [
        function $prototype() {
            this.textWidth = this.textHeight = this.startInvLine = this.invLines = 0;

            // speed up constructor by avoiding super execution since
            // text render is one of the most used class
            this[''] = function(text) {
                /**
                 * Text color
                 * @attribute color
                 * @type {String}
                 * @default zebkit.draw.TextRender.color
                 * @readOnly
                 */
                this.color = this.clazz.color;

                /**
                 * Text font
                 * @attribute font
                 * @type {String|zebkit.Font}
                 * @default zebkit.draw.TextRender.font
                 * @readOnly
                 */
                this.font = this.clazz.font;

                this.setValue(text);
            };

            this.write = function() {
                return this.target.write.apply(this.target, arguments);
            };

            this.remove = function() {
                return this.target.remove.apply(this.target, arguments);
            };

            this.replace = function() {
                return this.target.replace.apply(this.target, arguments);
            };

            this.on = function() {
                return this.target.on.apply(this.target, arguments);
            };

            this.off = function() {
                return this.target.off.apply(this.target, arguments);
            };

            /**
             * Get number of lines of target text
             * @return   {Integer} a number of line in the target text
             * @method getLines
             */
            this.getLines = function() {
                return this.target.getLines();
            };

            this.getLineSize = function(l) {
                return this.target.getLine(l).length + 1;
            };

            this.getMaxOffset = function() {
                return this.target.getTextLength();
            };

            /**
             * Paint the specified text line
             * @param  {CanvasRenderingContext2D} g graphical 2D context
             * @param  {Integer} x x coordinate
             * @param  {Integer} y y coordinate
             * @param  {Integer} line a line number
             * @param  {zebkit.layout.Layoutable} d an UI component on that the line has to be rendered
             * @method paintLine
             */
            this.paintLine = function(g,x,y,line,d) {
                g.fillText(this.getLine(line), x, y);
            };

            /**
             * Get text line by the given line number
             * @param  {Integer} r a line number
             * @return {String} a text line
             * @method getLine
             */
            this.getLine = function(r) {
                return this.target.getLine(r);
            };

            /**
             * Set the text model content
             * @param  {String|zebkit.data.TextModel} s a text as string object
             * @method setValue
             * @chainable
             */
            this.setValue = function(s) {
                if (s !== null && (typeof s === "string" || s.constructor === String)) {
                    if (this.target !== null) {
                        this.target.setValue(s);
                        return this;
                    } else {
                        s = new zebkit.data.Text(s);
                    }
                }

                //TODO: copy paste from Render to speed up
                if (this.target !== s) {
                    var old = this.target;
                    this.target = s;
                    if (this.valueWasChanged !== undefined) {
                        this.valueWasChanged(old, s);
                    }
                }

                return this;
            };

            /**
             * Get the given text line width in pixels
             * @param  {Integer} line a text line number
             * @return {Integer} a text line width in pixels
             * @method lineWidth
             */
            this.calcLineWidth = function(line){
                if (this.invLines > 0) {
                    this.recalc();
                }

                return this.target.$lineTags(line).$lineWidth;
            };

            /**
             * Called every time the target text metrics has to be recalculated
             * @method recalc
             */
            this.recalc = function() {
                if (this.invLines > 0 && this.target !== null){
                    var model = this.target, i = 0;
                    if (this.invLines > 0) {
                        for(i = this.startInvLine + this.invLines - 1; i >= this.startInvLine; i--) {
                            model.$lineTags(i).$lineWidth = this.font.stringWidth(this.getLine(i));
                        }
                        this.startInvLine = this.invLines = 0;
                    }

                    this.textWidth = 0;
                    var size = model.getLines();
                    for(i = 0; i < size; i++){
                        var len = model.$lineTags(i).$lineWidth;
                        if (len > this.textWidth) {
                            this.textWidth = len;
                        }
                    }
                    this.textHeight = this.getLineHeight() * size + (size - 1) * this.lineIndent;
                }
            };

            /**
             * Text model update listener handler
             * @param  {zebkit.data.TextEvent} e text event
             * @method textUpdated
             */
            this.textUpdated = function(e) {
                if (e.id === "remove") {
                    if (this.invLines > 0) {
                        var p1 = e.line - this.startInvLine,
                            p2 = this.startInvLine + this.invLines - e.line - e.lines;
                        this.invLines = ((p1 > 0) ? p1 : 0) + ((p2 > 0) ? p2 : 0) + 1;
                        this.startInvLine = this.startInvLine < e.line ? this.startInvLine : e.line;
                    } else {
                        this.startInvLine = e.line;
                        this.invLines = 1;
                    }

                    if (this.owner !== null && this.owner.isValid === true) {
                        this.owner.invalidate();
                    }
                } else {  // insert
                    // TODO:  check the code
                    if (this.invLines > 0) {
                        if (e.line <= this.startInvLine) {
                            this.startInvLine += (e.lines - 1);
                        } else if (e.line < (this.startInvLine + this.invLines)) {
                            this.invLines += (e.lines - 1);
                        }
                    }
                    this.invalidate(e.line, e.lines);
                }
            };

            /**
             * Invalidate metrics for the specified range of lines.
             * @param  {Integer} start first line to be invalidated
             * @param  {Integer} size  number of lines to be invalidated
             * @method invalidate
             * @private
             */
            this.invalidate = function(start,size) {

                if (arguments.length === 0) {
                    start = 0;
                    size  = this.getLines();
                    if (size === 0) {
                        this.invLines = 0;
                        return;
                    }
                }

                if (size > 0 && (this.startInvLine !== start || size !== this.invLines)) {
                    if (this.invLines === 0){
                        this.startInvLine = start;
                        this.invLines = size;
                    } else {
                        var e = this.startInvLine + this.invLines;
                        this.startInvLine = start < this.startInvLine ? start : this.startInvLine;
                        this.invLines     = Math.max(start + size, e) - this.startInvLine;
                    }

                    if (this.owner !== null) {
                        this.owner.invalidate();
                    }
                }
            };

            this.getPreferredSize = function(){
                if (this.invLines > 0 && this.target !== null) {
                    this.recalc();
                }
                return { width:this.textWidth, height:this.textHeight };
            };

            this.paint = function(g,x,y,w,h,d) {
                var ts = g.$states[g.$curState];
                if (ts.width > 0 && ts.height > 0) {
                    var lineIndent   = this.lineIndent,
                        lineHeight   = this.getLineHeight(),
                        lilh         = lineHeight + lineIndent,
                        startInvLine = 0;

                    w = ts.width  < w ? ts.width  : w;
                    h = ts.height < h ? ts.height : h;

                    if (y < ts.y) {
                        startInvLine = Math.floor((lineIndent + ts.y - y) / lilh);
                        h += (ts.y - startInvLine * lineHeight - startInvLine * lineIndent);
                    } else if (y > (ts.y + ts.height)) {
                        return;
                    }

                    var size = this.getLines();
                    if (startInvLine < size){
                        var lines = Math.floor((h + lineIndent) / lilh) + (((h + lineIndent) % lilh > lineIndent) ? 1 : 0), i = 0;
                        if (startInvLine + lines > size) {
                            lines = size - startInvLine;
                        }
                        y += startInvLine * lilh;

                        // save few milliseconds
                        if (this.font.s !== g.font) {
                            g.setFont(this.font);
                        }

                        if (d === null || d.isEnabled === true){
                            // save few milliseconds
                            if (this.color != g.fillStyle) {
                                g.fillStyle = this.color;
                            }

                            var p1 = null, p2 = null, bsel = false;
                            if (lines > 0 && d !== null && d.getStartSelection !== undefined) {
                                p1   = d.getStartSelection();
                                p2   = d.getEndSelection();
                                bsel = p1 !== null && (p1.row !== p2.row || p1.col !== p2.col);
                            }

                            for(i = 0; i < lines; i++){
                                if (bsel === true) {
                                    var line = i + startInvLine;
                                    if (line >= p1.row && line <= p2.row){
                                        var s  = this.getLine(line),
                                            lw = this.calcLineWidth(line),
                                            xx = x;

                                        if (line === p1.row) {
                                            var ww = this.font.charsWidth(s, 0, p1.col);
                                            xx += ww;
                                            lw -= ww;
                                            if (p1.row === p2.row) {
                                                lw -= this.font.charsWidth(s, p2.col, s.length - p2.col);
                                            }
                                        } else if (line === p2.row) {
                                            lw = this.font.charsWidth(s, 0, p2.col);
                                        }
                                        this.paintSelection(g, xx, y, lw === 0 ? 1 : lw, lilh, line, d);

                                        // restore color to paint text since it can be
                                        // res-set with paintSelection method
                                        if (this.color !== g.fillStyle) {
                                            g.fillStyle = this.color;
                                        }
                                    }
                                }

                                this.paintLine(g, x, y, i + startInvLine, d);
                                y += lilh;
                            }
                        } else {
                            var dcol = d !== null &&
                                       d.disabledColor !== null &&
                                       d.disabledColor !== undefined ? d.disabledColor
                                                                     : pkg.TextRender.disabledColor;

                            for(i = 0; i < lines; i++) {
                                g.setColor(dcol);
                                this.paintLine(g, x, y, i + startInvLine, d);
                                y += lilh;
                            }
                        }
                    }
                }
            };

            /**
             * Paint the specified text selection of the given line. The area
             * where selection has to be rendered is denoted with the given
             * rectangular area.
             * @param  {CanvasRenderingContext2D} g a canvas graphical context
             * @param  {Integer} x a x coordinate of selection rectangular area
             * @param  {Integer} y a y coordinate of selection rectangular area
             * @param  {Integer} w a width of of selection rectangular area
             * @param  {Integer} h a height of of selection rectangular area
             * @param  {Integer} line [description]
             * @param  {zebkit.layout.Layoutable} d a target UI component where the text
             * has to be rendered
             * @protected
             * @method paintSelection
             */
            this.paintSelection = function(g, x, y, w, h, line, d) {
                if (d.selectView !== null) {
                    d.selectView.paint(g, x, y, w, h, d);
                }
            };

            this.toString = function() {
                return this.target === null ? null
                                            : this.target.getValue();
            };
        },

        function valueWasChanged(o, n) {
            if (o !== null) {
                o.off(this);
            }

            if (n !== null) {
                n.on(this);
            }

            this.$super(o, n);
        }
    ]);

    /**
     * Render to visualize string whose width is greater than available or specified width.
     * @class zebkit.draw.CutStringRender
     * @extends zebkit.draw.StringRender
     * @constructor
     * @param  {String} [txt] a string to be rendered
     */
    pkg.CutStringRender = Class(pkg.StringRender, [
        function $prototype() {
            /**
             * Maximal width of the string in pixels. By default the attribute
             * is set to -1.
             * @attribute maxWidth
             * @type {Integer}
             * @default -1
             */
            this.maxWidth = -1;

            /**
             * String to be rendered at the end of cut string
             * @attribute dots
             * @type {String}
             * @default "..."
             */
            this.dots  = "...";
        },

        function paint(g,x,y,w,h,d) {
            var maxw = -1;

            if (this.maxWidth > 0 && this.stringWidth > this.maxWidth) {
                maxw = this.maxWidth;
            } else if (this.stringWidth > w) {
                maxw = w;
            } else if (g.$states !== undefined) {
                var ts = g.$states[g.$curState];
                if (ts.x > x || (ts.x + ts.width) < (x + w)) {
                    maxw = ts.width;
                }
            }

            if (maxw <= 0) {
                this.$super(g,x,y,w,h,d);
            } else {
                var dotsLen = this.font.stringWidth(this.dots);
                try {
                    g.save();
                    g.clipRect(x, y, maxw - dotsLen, h);
                    this.$super(g,x,y,w,h,d);
                } catch(e) {
                    g.restore();
                    throw e;
                }
                g.restore();
                g.setColor(this.color);
                g.setFont(this.font);
                g.fillText(this.dots, x + maxw - dotsLen, y);
            }
         }
    ]);

    /**
     * Wrapped text render.
     * @constructor
     * @param  {String|zebkit.data.TextModel} text a text as string or text model object
     * @class zebkit.draw.WrappedTextRender
     * @extends zebkit.draw.TextRender
     */
    pkg.WrappedTextRender = Class(pkg.TextRender, [
        function $prototype() {
            this.$brokenLines = [];
            this.$lastWidth    = -1;

            /**
             * Break text model to number of lines taking in account the maximal width.
             * @param  {Integer} w a maximal width
             * @return {Array}  an array of lines
             * @method $breakToLines
             * @private
             */
            this.$breakToLines = function(w) {
                var res = [];
                for (var i = 0; i < this.target.getLines(); i++) {
                    pkg.wrapToLines(this.font, w, this.target.getLine(i), res);
                }
                return res;
            };
        },

        function getLines() {
            return this.$lastWidth < 0 ? this.$super()
                                       : this.$brokenLines.length;
        },

        function getLine(i) {
            return this.$lastWidth < 0 ? this.$super(i)
                                       : this.$brokenLines[i];
        },

        function invalidate(sl, len){
            this.$super(sl, len);
            this.$brokenLines.length = 0;
            this.$lastWidth = -1;
        },

        /**
         * Get preferred size of the text render
         * @param  {Integer} [pw] a width the wrapped text has to be computed
         * @return {Object}  a preferred size
         *
         *     { width: {Integer}, height:{Integer} }
         *
         * @method calcPreferredSize
         */
        function calcPreferredSize(pw) {
            if (arguments.length > 0) {
                var bl = [];
                if (this.$lastWidth < 0 || this.$lastWidth !== pw) {
                    bl = this.$breakToLines(pw);
                } else {
                    bl = this.$brokenLines;
                }

                return {
                    width  : pw,
                    height : bl.length * this.getLineHeight() +
                            (bl.length - 1) * this.lineIndent
                };
            } else {
                return this.$super();
            }
        },

        function paint(g,x,y,w,h,d) {
            if (this.$lastWidth < 0 || this.$lastWidth !== w) {
                this.$lastWidth = w;
                this.$brokenLines = this.$breakToLines(this.$lastWidth);
            }
            this.$super(g,x,y,w,h,d);
        }
    ]);

    /**
     * Decorated text render. This decorator allows developer to draw under, over or strike
     * lines over the rendered text.
     * @class  zebkit.draw.DecoratedTextRender
     * @extends zebkit.draw.TextRender
     * @constructor
     * @param  {String|zebkit.data.TextModel} text a text as string or text model object
     */
    pkg.DecoratedTextRender = zebkit.Class(pkg.TextRender, [
        function(text) {
            this.decorations = {
                underline : false,
                strike    : false,
                overline  : false
            };
            this.$super(text);
        },

        function $prototype() {
            /**
             * Line width
             * @attribute lineWidth
             * @type {Integer}
             * @default 1
             */
            this.lineWidth = 1;

            /**
             * Decoration line color
             * @attribute lineColor
             * @type {String}
             * @default  null
             */
            this.lineColor = null;

            /**
             * Set set of decorations.
             * @param {String} [decoration]* set of decorations.
             * @method setDecorations
             * @chainable
             */
            this.setDecorations = function() {
                for(var k in this.decorations) {
                    this.decorations[k] = false;
                }
                this.addDecorations.apply(this, arguments);
                return this;
            };

            /**
             * Clear the given decorations.
             * @param {String} [decorations]* decorations IDs.
             * @chainable
             * @method clearDecorations
             */
            this.clearDecorations = function() {
                for (var i = 0; i < arguments.length; i++) {
                    zebkit.util.validateValue(arguments[i], "underline", "overline", "strike");
                    this.decorations[arguments[i]] = false;
                }
                return this;
            };

            /**
             * Add the given decorations.
             * @param {String} [decorations]* decorations IDs.
             * @chainable
             * @method addDecorations
             */
            this.addDecorations = function() {
                for (var i = 0; i < arguments.length; i++) {
                    zebkit.util.validateValue(arguments[i], "underline", "overline", "strike");
                    this.decorations[arguments[i]] = true;
                }
                return this;
            };

            this.hasDecoration = function(dec) {
                return this.decorations[dec] === true;
            };
        },

        function paintLine(g,x,y,line,d) {
            this.$super(g,x,y,line,d);

            var lw = this.calcLineWidth(line),
                lh = this.getLineHeight(line);

            if (this.lineColor !== null) {
                g.setColor(this.lineColor);
            } else {
                g.setColor(this.color);
            }

            if (this.decorations.overline) {
                g.lineWidth = this.lineWidth;
                g.drawLine(x, y + this.lineWidth, x + lw, y + this.lineWidth);
            }

            if (this.decorations.underline) {
                g.lineWidth = this.lineWidth;
                g.drawLine(x, y + lh - 1, x + lw, y  + lh - 1);
            }

            if (this.decorations.strike) {
                var yy = y + Math.round(lh / 2) - 1;
                g.lineWidth = this.lineWidth;
                g.drawLine(x, yy, x + lw, yy);
            }

            // restore text color
            if (this.lineColor !== null) {
                g.setColor(this.color);
            }
        }
    ]);

    pkg.BoldTextRender = Class(pkg.TextRender, [
        function $clazz() {
            this.font = pkg.boldFont;
        }
    ]);

    /**
     * Password text render class. This class renders a secret text with hiding
     * it with the given character.
     * @param {String|zebkit.data.TextModel} [text] a text as string or text
     * model instance
     * @class zebkit.draw.PasswordText
     * @constructor
     * @extends zebkit.draw.TextRender
     */
    pkg.PasswordText = Class(pkg.TextRender, [
        function(text){
            if (arguments.length === 0) {
                text = new zebkit.data.SingleLineTxt("");
            }

            this.$super(text);
        },

        function $prototype() {
            /**
             * Echo character that will replace characters of hidden text
             * @attribute echo
             * @type {String}
             * @readOnly
             * @default "*"
             */
            this.echo = "*";

            /**
             * Indicates if the last entered character doesn't have to be replaced
             * with echo character
             * @type {Boolean}
             * @attribute showLast
             * @default true
             * @readOnly
             */
            this.showLast = true;

            /**
             * Set the specified echo character. The echo character is used to
             * hide secret text.
             * @param {String} ch an echo character
             * @method setEchoChar
             * @chainable
             */
            this.setEchoChar = function(ch){
                if (this.echo !== ch){
                    this.echo = ch;
                    if (this.target !== null) {
                        this.invalidate(0, this.target.getLines());
                    }
                }
                return this;
            };
        },

        function getLine(r) {
            var buf = [],
                ln  = this.$super(r);

            for(var i = 0;i < ln.length; i++) {
                buf[i] = this.echo;
            }

            if (this.showLast && ln.length > 0) {
                buf[ln.length - 1] = ln[ln.length - 1];
            }

            return buf.join('');
        }
    ]);


    /**
     * Triangle shape view.
     * @param  {String}  [c]  a color of the shape
     * @param  {Integer} [w]  a line size
     * @class zebkit.draw.TriangleShape
     * @constructor
     * @extends zebkit.draw.Shape
     */
    pkg.TriangleShape = Class(pkg.Shape, [
        function $prototype() {
            this.outline = function(g,x,y,w,h,d) {
                g.beginPath();
                w -= 2 * this.lineWidth;
                h -= 2 * this.lineWidth;
                g.moveTo(x + w - 1, y);
                g.lineTo(x + w - 1, y + h - 1);
                g.lineTo(x, y + h - 1);
                g.closePath();
                return true;
            };
        }
    ]);

    /**
    * Vertical or horizontal linear gradient view
    * @param {String} startColor start color
    * @param {String} endColor end color
    * @param {String} [type] type of gradient
    *  "vertical" or "horizontal"
    * @constructor
    * @class zebkit.draw.Gradient
    * @extends zebkit.draw.View
    */
    pkg.Gradient = Class(pkg.View, [
        function() {
            /**
             * Gradient start and stop colors
             * @attribute colors
             * @readOnly
             * @type {Array}
             */

            this.colors = Array.prototype.slice.call(arguments, 0);
            if (arguments.length > 2) {
                this.orient = arguments[arguments.length - 1];
                this.colors.pop();
            }
        },

        function $prototype() {
            /**
             * Gradient orientation: vertical or horizontal
             * @attribute orient
             * @readOnly
             * @default "vertical"
             * @type {String}
             */
            this.orient = "vertical";

            this.$gradient = null;
            this.$gy2 = this.$gy1 = this.$gx2 = this.$gx1 = 0;

            this.paint = function(g,x,y,w,h,dd){
                var d  = (this.orient === "horizontal" ? [0,1]: [1,0]),
                    x1 = x * d[1],
                    y1 = y * d[0],
                    x2 = (x + w - 1) * d[1],
                    y2 = (y + h - 1) * d[0];

                if (this.$gradient === null  || this.$gx1 !== x1 ||
                    this.$gx2 !== x2         || this.$gy1 !== y1 ||
                    this.$gy2 !== y2                              )
                {
                    this.$gx1 = x1;
                    this.$gx2 = x2;
                    this.$gy1 = y1;
                    this.$gy2 = y2;

                    this.$gradient = g.createLinearGradient(x1, y1, x2, y2);
                    for(var i = 0; i < this.colors.length; i++) {
                        this.$gradient.addColorStop(i, this.colors[i].toString());
                    }
                }

                g.fillStyle = this.$gradient;
                g.fillRect(x, y, w, h);
            };
        }
    ]);

    /**
    * Radial gradient view
    * @param {String} startColor a start color
    * @param {String} stopColor a stop color
    * @constructor
    * @class zebkit.draw.Radial
    * @extends zebkit.draw.View
    */
    pkg.Radial = Class(pkg.View, [
        function() {
            this.colors = [];
            for(var i = 0; i < arguments.length; i++) {
                this.colors[i] = arguments[i] !== null ? arguments[i].toString() : null;
            }

            this.colors = Array.prototype.slice.call(arguments, 0);
        },

        function $prototype() {
            this.$gradient = null;
            this.$cx1 = this.$cy1 = this.$rad1 = this.$rad2 = 0;
            this.$colors = [];

            this.radius = 10;

            this.paint = function(g,x,y,w,h,d){
                var cx1  = Math.floor(w / 2),
                    cy1  = Math.floor(h / 2),
                    rad2 = w > h ? w : h;

                if (this.$gradient === null     ||
                    this.$cx1  !== cx1         ||
                    this.$cy1  !== cy1         ||
                    this.$rad1 !== this.radius ||
                    this.$rad2 !== this.rad2      )
                {
                    this.$gradient = g.createRadialGradient(cx1, cy1, this.radius, cx1, cy1, rad2);
                }

                var b = false,
                    i = 0;

                if (this.$colors.length !== this.colors.length) {
                    b = true;
                } else {
                    for (i = 0; i < this.$colors.length; i++) {
                        if (this.$colors[i] !== this.colors[i]) {
                            b = true;
                            break;
                        }
                    }
                }

                if (b) {
                    for (i = 0; i < this.colors.length; i++) {
                        this.$gradient.addColorStop(i, this.colors[i]);
                    }
                }

                g.fillStyle = this.$gradient;
                g.fillRect(x, y, w, h);
            };
        }
    ]);

    /**
    * Image render. Render an image target object or specified area of
    * the given target image object.
    * @param {Image} img the image to be rendered
    * @param {Integer} [x] a x coordinate of the rendered image part
    * @param {Integer} [y] a y coordinate of the rendered image part
    * @param {Integer} [w] a width of the rendered image part
    * @param {Integer} [h] a height of the rendered image part
    * @constructor
    * @class zebkit.draw.Picture
    * @extends zebkit.draw.Render
    */
    pkg.Picture = Class(pkg.Render, [
        function(img, x, y, w, h) {
            this.setValue(img);
            if (arguments.length === 2) {
                this.height = this.width = x;
            } else if (arguments.length === 3) {
                this.width  = x;
                this.height = y;
            } else if (arguments.length > 3) {
                this.x      = x;
                this.y      = y;
                this.width  = w;
                this.height = h;
            }
        },

        function $prototype() {
            /**
             * A x coordinate of the image part that has to be rendered
             * @attribute x
             * @readOnly
             * @type {Integer}
             * @default -1
             */
            this.x = -1;

            /**
             * A y coordinate of the image part that has to be rendered
             * @attribute y
             * @readOnly
             * @type {Integer}
             * @default -1
             */
            this.y = -1;

            /**
             * A width  of the image part that has to be rendered
             * @attribute width
             * @readOnly
             * @type {Integer}
             * @default -1
             */
            this.width = -1;

            /**
             * A height of the image part that has to be rendered
             * @attribute height
             * @readOnly
             * @type {Integer}
             * @default -1
             */
            this.height = -1;

            this.paint = function(g,x,y,w,h,d) {
                if (this.target !== null &&
                    this.target.complete === true &&
                    this.target.naturalWidth > 0 &&
                    w > 0 && h > 0)
                {
                    if (this.x >= 0) {
                        g.drawImage(this.target, this.x, this.y,
                                    this.width, this.height, x, y, w, h);
                    } else {
                        g.drawImage(this.target, x, y, w, h);
                    }
                }
            };

            this.getPreferredSize = function() {
                var img = this.target;
                return (img === null ||
                        img.naturalWidth <= 0 ||
                        img.complete !== true) ? { width:0, height:0 }
                                               : (this.width > 0) ? { width:this.width, height:this.height }
                                                                  : { width:img.width, height:img.height };
            };
        }
    ]);

    /**
    * Pattern render.
    * @class zebkit.draw.Pattern
    * @param {Image} [img] an image to be used as the pattern
    * @constructor
    * @extends zebkit.draw.Render
    */
    pkg.Pattern = Class(pkg.Render, [
        function $prototype() {
            /**
             * Buffered pattern
             * @type {Pattern}
             * @protected
             * @attribute $pattern
             * @readOnly
             */
            this.$pattern = null;

            this.paint = function(g,x,y,w,h,d) {
                if (this.$pattern === null && this.target !== null) {
                    this.$pattern = g.createPattern(this.target, 'repeat');
                }
                g.beginPath();
                g.rect(x, y, w, h);
                g.closePath();
                g.fillStyle = this.$pattern;
                g.fill();
            };

            this.valueWasChanged = function(o, n) {
                this.$pattern = null;
            };
        }
    ]);

    /**
     * Line view.
     * @class  zebkit.draw.Line
     * @extends zebkit.draw.View
     * @constructor
     * @param  {String} [side] a side of rectangular area where the line has to be rendered. Use
     * "left", "top", "right" or "bottom" as the parameter value
     * @param  {String} [color] a line color
     * @param  {Integer} [width] a line width
     */
    pkg.LineView = Class(pkg.View, [
        function(side, color, lineWidth) {
            if (arguments.length > 0) {
                this.side = zebkit.util.validateValue(side, "top", "right", "bottom", "left");
                if (arguments.length > 1) {
                    this.color = color;
                    if (arguments.length > 2) {
                        this.lineWidth = lineWidth;
                    }
                }
            }
        },

        function $prototype() {
            /**
             * Side the line has to be rendered
             * @attribute side
             * @type {String}
             * @default "top"
             * @readOnly
             */
            this.side = "top";

            /**
             * Line color
             * @attribute color
             * @type {String}
             * @default "black"
             * @readOnly
             */
            this.color = "black";

            /**
             * Line width
             * @attribute lineWidth
             * @type {Integer}
             * @default 1
             * @readOnly
             */
            this.lineWidth = 1;

            this.paint = function(g,x,y,w,h,t) {
                g.setColor(this.color);
                g.beginPath();
                g.lineWidth = this.lineWidth;

                var d = this.lineWidth / 2;
                if (this.side === "top") {
                    g.moveTo(x, y + d);
                    g.lineTo(x + w - 1, y + d);
                } else if (this.side === "bottom") {
                    g.moveTo(x, y + h - d);
                    g.lineTo(x + w - 1, y + h - d);
                } else if (this.side === "left") {
                    g.moveTo(x + d, y);
                    g.lineTo(x + d, y + h - 1);
                } else if (this.side === "right") {
                    g.moveTo(x + w - d, y);
                    g.lineTo(x + w - d, y + h - 1);
                } else if (this.side === "verCenter") {
                    // TODO: not implemented
                } else if (this.side === "horCenter") {
                    // TODO: not implemented
                }

                g.stroke();
            };

            this.getPreferredSize = function() {
                return {
                    width  : this.lineWidth,
                    height : this.lineWidth
                };
            };
        }
    ]);

    /**
     * Arrow view. Tye view can be use to render triangle arrow element to one of the
     * following direction: "top", "left", "bottom", "right".
     * @param  {String} direction an arrow view direction.
     * @param  {String} color an arrow view line color.
     * @param  {String} fillColor an arrow view filling.
     * @constructor
     * @class zebkit.draw.ArrowView
     * @extends zebkit.draw.Shape
     */
    pkg.ArrowView = Class(pkg.Shape, [
        function (direction, color, fillColor) {
            if (arguments.length > 0) {
                this.direction = zebkit.util.validateValue(direction, "left", "right", "bottom", "top");
                if (arguments.length > 1) {
                    this.color = color;
                    if (arguments.length > 2) {
                        this.fillColor = fillColor;
                    }
                }
            }
        },

        function $prototype() {
            this.gap   = 0;
            this.color = null;
            this.fillColor = "black";

            /**
             * Arrow direction.
             * @attribute direction
             * @type {String}
             * @default "bottom"
             */
            this.direction = "bottom";

            this.outline = function(g, x, y, w, h, d) {
                x += this.gap;
                y += this.gap;
                w -= this.gap * 2;
                h -= this.gap * 2;

                var dt = this.lineWidth / 2,
                    w2 = Math.round(w / 2) - (w % 2 === 0 ? 0 : dt),
                    h2 = Math.round(h / 2) - (h % 2 === 0 ? 0 : dt);

                g.beginPath();
                if ("bottom" === this.direction) {
                    g.moveTo(x, y + dt);
                    g.lineTo(x + w - 1, y + dt);
                    g.lineTo(x + w2, y + h - dt);
                    g.lineTo(x + dt, y + dt);
                } else if ("top" === this.direction) {
                    g.moveTo(x, y + h - dt);
                    g.lineTo(x + w - 1, y + h - dt);
                    g.lineTo(x + w2, y);
                    g.lineTo(x + dt, y + h - dt);
                } else if ("left" === this.direction) {
                    g.moveTo(x + w - dt, y);
                    g.lineTo(x + w - dt, y + h - 1);
                    g.lineTo(x, y + h2);
                    g.lineTo(x + w + dt, y);
                } else if ("right" === this.direction) {
                    g.moveTo(x + dt, y);
                    g.lineTo(x + dt, y + h - 1);
                    g.lineTo(x + w, y + h2);
                    g.lineTo(x - dt, y);
                }

                return true;
            };

            /**
             * Set gap.
             * @param {Integer} gap a gap
             * @chainable
             * @method setGap
             */
            this.setGap = function(gap) {
                this.gap = gap;
                return this;
            };
        }
    ]);

    pkg.TabBorder = Class(pkg.View, [
        function(t, w) {
            if (arguments.length > 1) {
                this.width  = w;
            }

            if (arguments.length > 0) {
                this.state = t;
            }

            this.left = this.top = this.bottom = this.right = 6 + this.width;
        },

        function $prototype() {
            this.state  = "out";
            this.width  = 1;

            this.fillColor1 = "#DCF0F7";
            this.fillColor2 = "white";
            this.fillColor3 = "#F3F3F3";

            this.onColor1 = "black";
            this.onColor2 = "#D9D9D9";
            this.offColor = "#A1A1A1";


            this.paint = function(g,x,y,w,h,d){
                var xx = x + w - 1,
                    yy = y + h - 1,
                    o  = d.parent.orient,
                    t  = this.state,
                    s  = this.width,
                    ww = 0,
                    hh = 0,
                    dt = s / 2;

                g.beginPath();
                g.lineWidth = s;
                switch(o) {
                    case "left":
                        g.moveTo(xx + 1, y + dt);
                        g.lineTo(x + s * 2, y + dt);
                        g.lineTo(x + dt , y + s * 2);
                        g.lineTo(x + dt, yy - s * 2 + dt);
                        g.lineTo(x + s * 2, yy + dt);
                        g.lineTo(xx + 1, yy + dt);

                        if (d.isEnabled === true){
                            g.setColor(t === "over" ? this.fillColor1 : this.fillColor2);
                            g.fill();
                        }

                        g.setColor((t === "selected" || t === "over") ? this.onColor1 : this.offColor);
                        g.stroke();

                        if (d.isEnabled === true) {
                            ww = Math.floor((w - 6) / 2);
                            g.setColor(this.fillColor3);
                            g.fillRect(xx - ww + 1, y + s, ww, h - s - 1);
                        }

                        if (t === "out") {
                            g.setColor(this.onColor2);
                            g.drawLine(x + 2*s + 1, yy - s, xx + 1, yy - s, s);
                        }
                        break;
                    case "right":
                        xx -= dt; // thick line grows left side and right side proportionally
                                  // correct it

                        g.moveTo(x, y + dt);
                        g.lineTo(xx - 2 * s, y + dt);

                        g.lineTo(xx, y + 2 * s);
                        g.lineTo(xx, yy - 2 * s);
                        g.lineTo(xx - 2 * s, yy + dt);
                        g.lineTo(x, yy + dt);

                        if (d.isEnabled === true){
                            g.setColor(t === "over" ? this.fillColor1 : this.fillColor2);
                            g.fill();
                        }

                        g.setColor((t === "selected" || t === "over") ? this.onColor1 : this.offColor);
                        g.stroke();

                        if (d.isEnabled === true) {
                            ww = Math.floor((w - 6) / 2);
                            g.setColor(this.fillColor3);
                            g.fillRect(x, y + s, ww, h - s - 1);
                        }

                        if (t === "out") {
                            g.setColor(this.onColor2);
                            g.drawLine(x, yy - s, xx - s - 1, yy - s, s);
                        }
                        break;
                    case "top":
                        g.moveTo(x + dt, yy + 1 );
                        g.lineTo(x + dt, y + s*2);
                        g.lineTo(x + s * 2, y + dt);
                        g.lineTo(xx - s * 2 + s, y + dt);
                        g.lineTo(xx + dt, y + s * 2);
                        g.lineTo(xx + dt, yy + 1);

                        if (d.isEnabled === true){
                            g.setColor(t === "over" ? this.fillColor1 : this.fillColor2);
                            g.fill();
                        }

                        g.setColor((t === "selected" || t === "over") ? this.onColor1 : this.offColor);
                        g.stroke();

                        if (d.isEnabled === true){
                            g.setColor(this.fillColor3);
                            hh = Math.floor((h - 6) / 2);
                            g.fillRect(x + s, yy - hh + 1 , w - s - 1, hh);
                        }

                        if (t === "selected") {
                            g.setColor(this.onColor2);
                            g.beginPath();
                            g.moveTo(xx + dt - s, yy + 1);
                            g.lineTo(xx + dt - s, y + s * 2);
                            g.stroke();
                        }

                        break;
                    case "bottom":
                        yy -= dt;

                        g.moveTo(x + dt, y);
                        g.lineTo(x + dt, yy - 2 * s);
                        g.lineTo(x + 2 * s + dt, yy);
                        g.lineTo(xx - 2 * s, yy);
                        g.lineTo(xx + dt, yy - 2 * s);
                        g.lineTo(xx + dt, y);

                        if (d.isEnabled === true){
                            g.setColor(t === "over" ? this.fillColor1 : this.fillColor2);
                            g.fill();
                        }

                        g.setColor((t === "selected" || t === "over") ? this.onColor1 : this.offColor);
                        g.stroke();

                        if (d.isEnabled === true){
                            g.setColor(this.fillColor3);
                            hh = Math.floor((h - 6) / 2);
                            g.fillRect(x + s, y, w - s - 1, hh);
                        }

                        if (t === "selected") {
                            g.setColor(this.onColor2);
                            g.beginPath();
                            g.moveTo(xx + dt - s, y);
                            g.lineTo(xx + dt - s, yy - s - 1);
                            g.stroke();
                        }
                        break;
                    default: throw new Error("Invalid tab alignment");
                }
            };

            this.getTop    = function () { return this.top;   };
            this.getBottom = function () { return this.bottom;};
            this.getLeft   = function () { return this.left;  };
            this.getRight  = function () { return this.right; };
        }
    ]);

    /**
     * The check box ticker view.
     * @class  zebkit.draw.CheckboxView
     * @extends zebkit.draw.View
     * @constructor
     * @param {String} [color] color of the ticker
     */
    pkg.CheckboxView = Class(pkg.View, [
        function(color) {
            if (arguments.length > 0) {
                this.color = color;
            }
        },

        function $prototype() {
            /**
             * Ticker color.
             * @attribute color
             * @type {String}
             * @readOnly
             * @default "rgb(65, 131, 255)"
             */
            this.color = "rgb(65, 131, 255)";

            this.paint = function(g,x,y,w,h,d){
                g.beginPath();
                g.strokeStyle = this.color;
                g.lineWidth = 2;
                g.moveTo(x + 1, y + 2);
                g.lineTo(x + w - 3, y + h - 3);
                g.stroke();
                g.beginPath();
                g.moveTo(x + w - 2, y + 2);
                g.lineTo(x + 2, y + h - 2);
                g.stroke();
                g.lineWidth = 1;
            };
        }
    ]);

    /**
     * Thumb element view.
     * @class  zebkit.draw.ThumbView
     * @extends zebkit.draw.Shape
     * @constructor
     * @param  {String} [dir]  a direction
     * @param  {String} [color] a shape line color
     * @param  {String} [fillColor] a fill color
     * @param  {Integer} [lineWidth] a shape line width
     */
    pkg.ThumbView = Class(pkg.Shape, [
        function(dir, color, fillColor, lineWidth) {
            if (arguments.length > 0) {
                this.direction = zebkit.util.validateValue(dir, "vertical", "horizontal");
                if (arguments.length > 1) {
                    this.color = color;
                    if (arguments.length > 2) {
                        this.fillColor = fillColor;
                        if (arguments.length > 3) {
                            this.lineWidth = lineWidth;
                        }
                    }
                }
            }
        },

        function $prototype() {
            this.fillColor = "#AAAAAA";

            this.color = null;

            /**
             * Direction.
             * @attribute direction
             * @type {String}
             * @default "vertical"
             */
            this.direction = "vertical";

            this.outline = function(g, x, y, w, h, d) {
                g.beginPath();

                var r = 0;
                if (this.direction === "vertical") {
                    r = w / 2;
                    g.arc(x + r, y + r, r, Math.PI, 0, false);
                    g.lineTo(x + w, y + h - r);
                    g.arc(x + r, y + h - r, r, 0, Math.PI, false);
                    g.lineTo(x, y + r);
                } else {
                    r = h / 2;
                    g.arc(x + r, y + r, r, 0.5 * Math.PI, 1.5 * Math.PI, false);
                    g.lineTo(x + w - r, y);
                    g.arc(x + w - r, y + h - r, r, 1.5 * Math.PI, 0.5 * Math.PI, false);
                    g.lineTo(x + r, y + h);
                }
            };
        }
    ]);

    /**
     * The radio button ticker view.
     * @class  zebkit.draw.RadioView
     * @extends zebkit.draw.View
     * @constructor
     * @param {String} [outerColor] color one to fill the outer circle
     * @param {String} [innerColor] color tow to fill the inner circle
     */
    pkg.RadioView = Class(pkg.View, [
        function(outerColor, innerColor) {
            if (arguments.length > 0) {
                this.outerColor = outerColor;
                if (arguments.length > 1) {
                    this.innerColor = innerColor;
                }
            }
        },

        function $prototype() {
            /**
             * Outer circle filling color.
             * @attribute outerColor
             * @readOnly
             * @default "rgb(15, 81, 205)"
             * @type {String}
             */
            this.outerColor = "rgb(15, 81, 205)";

            /**
             * Inner circle filling color.
             * @attribute innerColor
             * @readOnly
             * @default "rgb(65, 131, 255)"
             * @type {String}
             */
            this.innerColor = "rgb(65, 131, 255)";

            this.paint = function(g,x,y,w,h,d){
                g.beginPath();
                if (g.fillStyle !== this.outerColor) {
                    g.fillStyle = this.outerColor;
                }
                g.arc(Math.floor(x + w/2), Math.floor(y + h/2) , Math.floor(w/3 - 0.5), 0, 2 * Math.PI, 1, false);
                g.fill();

                g.beginPath();
                if (g.fillStyle !== this.innerColor) {
                    g.fillStyle = this.innerColor;
                }
                g.arc(Math.floor(x + w/2), Math.floor(y + h/2) , Math.floor(w/4 - 0.5), 0, 2 * Math.PI, 1, false);
                g.fill();
            };
        }
    ]);

    /**
     * Toggle view element class
     * @class  zebkit.draw.ToggleView
     * @extends zebkit.draw.View
     * @constructor
     * @param  {Boolean} [plus] indicates the sign type plus (true) or minus (false)
     * @param  {String}  [color] a color
     * @param  {String}  [bg] a background
     * @param  {Integer} [w] a width
     * @param  {Integer} [h] a height
     * @param  {zebkit.draw.View | String}  [br] a border view
     */
    pkg.ToggleView = Class(pkg.View, [
        function(plus, color, bg, w, h, br) {
            if (arguments.length > 0) {
                this.plus = plus;
                if (arguments.length > 1) {
                    this.color = color;
                    if (arguments.length > 2) {
                        this.bg = bg;
                        if (arguments.length > 3) {
                            this.width = this.height = w;
                            if (arguments.length > 4) {
                                this.height = h;
                                if (arguments.length > 5) {
                                    this.br = pkg.$view(br);
                                }
                            }
                        }
                    }
                }
            }
        },

        function $prototype() {
            this.color = "white";
            this.bg    = "lightGray";
            this.plus  = false;
            this.br    = new pkg.Border("rgb(65, 131, 215)", 1, 3);
            this.width = this.height = 12;

            this.paint = function(g, x, y, w, h, d) {
                if (this.bg !== null && (this.br === null || this.br.outline(g, x, y, w, h, d) === false)) {
                    g.beginPath();
                    g.rect(x, y, w, h);
                }

                if (this.bg !== null) {
                    g.setColor(this.bg);
                    g.fill();
                }

                if (this.br !== null) {
                    this.br.paint(g, x, y, w, h, d);
                }

                g.setColor(this.color);
                g.lineWidth = 2;
                x += 2;
                w -= 4;
                h -= 4;
                y += 2;

                g.beginPath();
                g.moveTo(x, y + h / 2);
                g.lineTo(x + w, y + h / 2);
                if (this.plus) {
                    g.moveTo(x + w / 2, y);
                    g.lineTo(x + w / 2, y + h);
                }

                g.stroke();
                g.lineWidth = 1;
            };

            this.getPreferredSize = function() {
                return { width:this.width, height:this.height };
            };
        }
    ]);

    pkg.CaptionBgView = Class(pkg.View, [
        function(bg, gap, radius) {
            if (arguments.length > 0) {
                this.bg = bg;
                if (arguments.length > 1) {
                    this.gap = gap;

                    if (arguments.length > 2) {
                        this.radius = radius;
                    }
                }
            }
        },

        function $prototype() {
            this.gap    = 6;
            this.radius = 6;
            this.bg      = "#66CCFF";

            this.paint = function(g,x,y,w,h,d) {
                this.outline(g,x,y,w,h,d);
                g.setColor(this.bg);
                g.fill();
            };

            this.outline = function(g,x,y,w,h,d) {
                g.beginPath();
                g.moveTo(x + this.radius, y);
                g.lineTo(x + w - this.radius*2, y);
                g.quadraticCurveTo(x + w, y, x + w, y + this.radius);
                g.lineTo(x + w, y + h);
                g.lineTo(x, y + h);
                g.lineTo(x, y + this.radius);
                g.quadraticCurveTo(x, y, x + this.radius, y);
                return true;
            };
        }
    ]);

    // TODO: not sure it makes sense to put here
    // a bit dirty implementation
    pkg.CloudView = Class(pkg.Shape, [
        function outline(g,x,y,w,h,d) {
            g.beginPath();
            g.moveTo(x + w * 0.2, y  +  h * 0.25);
            g.bezierCurveTo(x, y + h*0.25, x, y + h*0.75, x + w * 0.2, y + h*0.75);
            g.bezierCurveTo(x + 0.1 * w, y + h - 1 , x + 0.8*w, y + h - 1, x + w * 0.7, y + h*0.75);
            g.bezierCurveTo(x + w - 1, y + h * 0.75 , x + w - 1, y, x + w * 0.65, y + h*0.25) ;
            g.bezierCurveTo(x + w - 1, y, x + w * 0.1, y, x + w * 0.2, y + h * 0.25) ;
            g.closePath();
            return true;
        }
    ]);

    /**
     * Function render. The render draws a chart for the specified function
     * within the given interval.
     * @constructor
     * @class zebkit.draw.FunctionRender
     * @extends zebkit.draw.Render
     * @param  {Function} fn  a function to be rendered
     * @param  {Number}   x1  a minimal value of rendered function interval
     * @param  {Number}   x2  a maximal value of rendered function interval
     * @param  {Integer} [granularity]  a granularity
     * @param  {String}  [color]  a chart color
     * @example
     *
     *      zebkit.require("ui", "draw", function(ui, draw) {
     *          var root = new ui.zCanvas(400, 300).root;
     *          // paint sin(x) function as a background of root component
     *          root.setBackground(new draw.FunctionRender(function(x) {
     *              return Math.sin(x);
     *          }, -3, 3));
     *      });
     *
     */
    pkg.FunctionRender = Class(pkg.Render, [
        function(fn, x1, x2, granularity) {
            this.$super(fn);
            this.setRange(x1, x2);
            if (arguments.length > 3) {
                this.granularity = arguments[3];
                if (arguments.length > 4) {
                    this.color = arguments[4];
                }
            }
        },

        function $prototype(g,x,y,w,h,c) {
            /**
             * Granularity defines how many points the given interval have to be split.
             * @type {Integer}
             * @attribute granularity
             * @readOnly
             * @default 200
             */
            this.granularity = 200;

            /**
             * Function chart color.
             * @type {String}
             * @attribute color
             * @default "orange"
             */
            this.color = "orange";

            /**
             * Chart line width.
             * @type {Integer}
             * @attribute lineWidth
             * @default 1
             */
            this.lineWidth   = 1;

            /**
             * Indicates if the function chart has to be stretched vertically
             * to occupy the whole vertical space
             * @type {Boolean}
             * @attribute stretch
             * @default true
             */
            this.stretch = true;

            this.$fy = null;


            this.valueWasChanged = function(o, n) {
                if (n !== null && typeof n !== 'function') {
                    throw new Error("Function is expected");
                }
                this.$fy = null;
            };

            /**
             * Set the given granularity. Granularity defines how smooth
             * the rendered chart should be.
             * @param {Integer} g a granularity
             * @method setGranularity
             */
            this.setGranularity = function(g) {
                if (g !== this.granularity) {
                    this.granularity = g;
                    this.$fy = null;
                }
            };

            /**
             * Set the specified interval - minimal and maximal function
             * argument values.
             * @param {Number} x1 a minimal value
             * @param {Number} x2 a maximal value
             * @method setRange
             */
            this.setRange = function(x1, x2) {
                if (x1 > x2) {
                    throw new RangeError("Incorrect range interval");
                }

                if (this.x1 !== x1 || this.x2 !== x2) {
                    this.x1 = x1;
                    this.x2 = x2;
                    this.$fy = null;
                }
            };

            this.$recalc = function() {
                if (this.$fy === null) {
                    this.$fy   = [];
                    this.$maxy = -100000000;
                    this.$miny =  100000000;
                    this.$dx   = (this.x2 - this.x1) / this.granularity;

                    for(var x = this.x1, i = 0; x <= this.x2; i++) {
                        this.$fy[i] = this.target(x);
                        if (this.$fy[i] > this.$maxy) {
                            this.$maxy = this.$fy[i];
                        }

                        if (this.$fy[i] < this.$miny) {
                            this.$miny = this.$fy[i];
                        }

                        x += this.$dx;
                    }
                }
            };

            this.paint = function(g,x,y,w,h,c) {
                if (this.target !== null) {
                    this.$recalc();

                    var cx = (w - this.lineWidth * 2) / (this.x2 - this.x1),  // value to pixel scale
                        cy = cx,
                        vx = this.$dx,
                        sy = this.lineWidth,
                        dy = h - this.lineWidth * 2;

                    if (this.stretch) {
                        cy = (h - this.lineWidth * 2) / (this.$maxy - this.$miny);
                    } else {
                        dy = (this.$maxy - this.$miny) * cx;
                        sy = (h - dy) / 2;
                    }

                    g.beginPath();
                    g.setColor(this.color);
                    g.lineWidth = this.lineWidth;

                    g.moveTo(this.lineWidth,
                             sy + dy - (this.$fy[0] - this.$miny) * cy);
                    for(var i = 1; i < this.$fy.length; i++) {
                        g.lineTo(this.lineWidth + vx * cx,
                                 sy + dy - (this.$fy[i] - this.$miny) * cy);
                        vx += this.$dx;
                    }
                    g.stroke();
                }
            };
        }
    ]);

    /**
     * Pentahedron shape view.
     * @param  {String}  [c]  a color of the shape
     * @param  {Integer} [w]  a line size
     * @class zebkit.draw.PentahedronShape
     * @constructor
     * @extends zebkit.draw.Shape
     */
    pkg.PentahedronShape =  Class(pkg.Shape, [
        function outline(g,x,y,w,h,d) {
            g.beginPath();
            x += this.lineWidth;
            y += this.lineWidth;
            w -= 2*this.lineWidth;
            h -= 2*this.lineWidth;
            g.moveTo(x + w/2, y);
            g.lineTo(x + w - 1, y + h/3);
            g.lineTo(x + w - 1 - w/3, y + h - 1);
            g.lineTo(x + w/3, y + h - 1);
            g.lineTo(x, y + h/3);
            g.lineTo(x + w/2, y);
            return true;
        }
    ]);

    /**
     * Base class to implement model values renders.
     * @param  {zebkit.draw.Render} [render] a render to visualize values.
     * By default string render is used.
     * @class zebkit.draw.BaseViewProvider
     * @constructor
     */
    pkg.BaseViewProvider = Class([
        function(render) {
            /**
             * Default render that is used to paint grid content.
             * @type {zebkit.draw.Render}
             * @attribute render
             * @readOnly
             * @protected
             */
            this.render = (arguments.length === 0 || render === undefined ? new zebkit.draw.StringRender("")
                                                                          : render);
            zebkit.properties(this, this.clazz);
        },

        function $prototype() {
            /**
             * Set the default view provider font if defined render supports it
             * @param {zebkit.Font} f a font
             * @method setFont
             * @chainable
             */
            this.setFont = function(f) {
                if (this.render.setFont !== undefined) {
                    this.render.setFont(f);
                }
                return this;
            };

            /**
             * Set the default view provider color if defined render supports it
             * @param {String} c a color
             * @method setColor
             * @chainable
             */
            this.setColor = function(c) {
                if (this.render.setColor !== undefined) {
                    this.render.setColor(c);
                }
                return this;
            };

            /**
             * Get a view to render the specified value of the target component.
             * @param  {Object} target a target  component
             * @param  {Object} [arg]* arguments list
             * @param  {Object} obj a value to be rendered
             * @return {zebkit.draw.View}  an instance of view to be used to
             * render the given value
             * @method  getView
             */
            this.getView = function(target) {
                var obj = arguments[arguments.length - 1];
                if (obj !== null && obj !== undefined) {
                    if (obj.toView !== undefined) {
                        return obj.toView();
                    } else if (obj.paint !== undefined) {
                        return obj;
                    } else {
                        this.render.setValue(obj.toString());
                        return this.render;
                    }
                } else {
                    return null;
                }
            };
        }
    ]);
},false);
zebkit.package("ui.event", function(pkg, Class) {
    'use strict';

   /**
    *  UI  event and event manager package.
    *  @class zebkit.ui.event
    *  @access package
    */

    /**
     *  UI manager class. The class is widely used as a basement for building various UI managers
     *  like focus, event managers etc. Manager is automatically registered as global events
     *  listener for events it implements to handle.
     *  @class zebkit.ui.event.Manager
     *  @constructor
     */
    pkg.Manager = Class([
        function() {
            //TODO: correct to event package
            if (zebkit.ui.events !== null && zebkit.ui.events !== undefined) {
                zebkit.ui.events.on(this);
            }
        }
    ]);

    /**
     * Component event class. Component events are fired when:
     *
     *   - a component is re-located ("compMoved" event)
     *   - a component is re-sized ("compResized" event)
     *   - a component visibility is updated ("compShown" event)
     *   - a component is enabled ("compEnabled" event)
     *   - a component has been inserted into another component ("compAdded" event)
     *   - a component has been removed from another component ("compRemoved" event)
     *
     * Appropriate event type is set in the event id property.
     * @constructor
     * @class   zebkit.ui.event.CompEvent
     * @extends zebkit.Event
     */
    pkg.CompEvent = Class(zebkit.Event, [
        function $prototype() {
            /**
             * A kid component that has been added or removed (depending on event type).
             * @attribute kid
             * @readOnly
             * @default null
             * @type {zebkit.ui.Panel}
             */
            this.kid = this.constraints = null;

            /**
             * A constraints with that a kid component has been added or removed (depending on event type).
             * @attribute constraints
             * @readOnly
             * @default null
             * @type {Object}
             */

            /**
             * A previous x location the component has had.
             * @readOnly
             * @attribute prevX
             * @type {Integer}
             * @default -1
             */

            /**
             * A previous y location the component has had.
             * @readOnly
             * @attribute prevY
             * @type {Integer}
             * @default -1
             */

            /**
             * An index at which a component has been added or removed.
             * @readOnly
             * @attribute index
             * @type {Integer}
             * @default -1
             */

            /**
             * A previous width the component has had.
             * @readOnly
             * @attribute prevWidth
             * @type {Integer}
             * @default -1
             */

            /**
             * A previous height the component has had.
             * @readOnly
             * @attribute height
             * @type {Integer}
             * @default -1
             */
            this.prevX = this.prevY = this.index = -1;
            this.prevWidth = this.prevHeight = -1;
        }
    ]);

    /**
     * Input key event class.
     * @class  zebkit.ui.event.KeyEvent
     * @extends zebkit.Event
     * @constructor
     */
    pkg.KeyEvent = Class(zebkit.Event, [
        function $prototype() {
            /**
             * A code of a pressed key
             * @attribute code
             * @readOnly
             * @type {Strung}
             */
            this.code = null;

            /**
             * A pressed key
             * @attribute key
             * @readOnly
             * @type {String}
             */
            this.key = null;

            /**
             * Input device type. Can be for instance "keyboard", vkeyboard" (virtual keyboard)
             * @attribute device
             * @default "keyboard"
             * @type {String}
             */
            this.device = "keyboard";

            /**
             * Boolean that shows state of ALT key.
             * @attribute altKey
             * @type {Boolean}
             * @readOnly
             */
            this.altKey = false;

            /**
             * Boolean that shows state of SHIFT key.
             * @attribute shiftKey
             * @type {Boolean}
             * @readOnly
             */
            this.shiftKey = false;

            /**
             * Boolean that shows state of CTRL key.
             * @attribute ctrlKey
             * @type {Boolean}
             * @readOnly
             */
            this.ctrlKey = false;

            /**
             * Boolean that shows state of META key.
             * @attribute metaKey
             * @type {Boolean}
             * @readOnly
             */
            this.metaKey = false;

            /**
             * Repeat counter
             * @attribute repeat
             * @type {Number}
             */
            this.repeat = 0;

            /**
             * Time stamp
             * @attribute  timeStamp
             * @type {Number}
             */
            this.timeStamp = 0;

            /**
             * Get the given modifier key state. The following modifier key codes are supported:
             * "Meta", "Control", "Shift", "Alt".
             * @param  {String} m a modifier key code
             * @return {Boolean} true if the modifier key state is pressed.
             * @method getModifierState
             */
            this.getModifierState = function(m) {
                if (m === "Meta") {
                    return this.metaKey;
                }

                if (m === "Control") {
                    return this.ctrlKey;
                }

                if (m === "Shift") {
                    return this.shiftKey;
                }

                if (m === "Alt") {
                    return this.altKey;
                }

                throw new Error("Unknown modifier key '" + m + "'");
            };
        }
    ]);

    /**
     * Mouse and touch screen input event class. The input event is triggered by a mouse or
     * touch screen.
     * @class  zebkit.ui.event.PointerEvent
     * @extends zebkit.Event
     * @constructor
     */
    pkg.PointerEvent = Class(zebkit.Event, [
        function $prototype() {
            /**
             * Pointer type. Can be "mouse", "touch", "pen"
             * @attribute  pointerType
             * @type {String}
             */
            this.pointerType = "mouse";

            /**
             * Touch counter
             * @attribute touchCounter
             * @type {Integer}
             * @default 0
             */
            this.touchCounter = 0;

            /**
             * Page x
             * @attribute pageX
             * @type {Integer}
             * @default -1
             */
            this.pageX = -1;

            /**
             * Page y
             * @attribute pageY
             * @type {Integer}
             * @default -1
             */
            this.pageY = -1;

            /**
             * Target DOM element
             * @attribute target
             * @type {DOMElement}
             * @default null
             */
            this.target = null;

            /**
             * Pointer identifier.
             * @attribute identifier
             * @type {Object}
             * @default null
             */
            this.identifier = null;

            this.shiftKey = this.altKey = this.metaKey = this.ctrlKey = false;

            this.pressure = 0.5;

            /**
             * Absolute mouse pointer x coordinate
             * @attribute absX
             * @readOnly
             * @type {Integer}
             */
            this.absX = 0;

            /**
             * Absolute mouse pointer y coordinate
             * @attribute absY
             * @readOnly
             * @type {Integer}
             */
             this.absY = 0;

            /**
             * Mouse pointer x coordinate (relatively to source UI component)
             * @attribute x
             * @readOnly
             * @type {Integer}
             */
            this.x = 0;

            /**
             * Mouse pointer y coordinate (relatively to source UI component)
             * @attribute y
             * @readOnly
             * @type {Integer}
             */
            this.y = 0;

            /**
             * Recompute the event relative location for the new source component and it
             * absolute location
             * @private
             * @param  {zebkit.ui.Panel} source  a source component that triggers the event
             * @param  {Integer} ax an absolute (relatively to a canvas where the source
             * component is hosted) x mouse cursor coordinate
             * @param  {Integer} ay an absolute (relatively to a canvas where the source
             * component is hosted) y mouse cursor coordinate
             * @method  updateCoordinates
             */
            this.update = function(source, ax, ay){
                // this can speed up calculation significantly check if source zebkit component
                // has not been changed, his location and parent component also has not been
                // changed than we can skip calculation of absolute location by traversing
                // parent hierarchy
                if (this.source        === source        &&
                    this.source.parent === source.parent &&
                    source.x           === this.$px      &&
                    source.y           === this.$py         )
                {
                    this.x += (ax - this.absX);
                    this.y += (ay - this.absY);
                    this.absX = ax;
                    this.absY = ay;
                    this.source = source;
                } else {
                    this.source = source;
                    this.absX = ax;
                    this.absY = ay;

                    // convert absolute location to relative location
                    while (source.parent !== null) {
                        ax -= source.x;
                        ay -= source.y;
                        source = source.parent;
                    }
                    this.x = ax;
                    this.y = ay;
                }

                this.$px = source.x;
                this.$py = source.y;
                return this;
            };

            this.setLocation = function(x, y) {
                if (this.source === null) {
                    throw new Error("Unknown source component");
                }

                if (this.x !== x || this.y !== y) {
                    this.absX = this.x = x;
                    this.absY = this.y = y;

                    // convert relative coordinates to absolute
                    var source = this.source;
                    while (source.parent !== null) {
                        this.absX += source.x;
                        this.absY += source.y;
                        source = source.parent;
                    }
                }
            };

            this.isAction = function() {
                // TODO: actually this is abstract method
                throw new Error("Not implemented");
            };

            this.getTouches = function() {
                // TODO: actually this is abstract method
                throw new Error("Not implemented");
            };
        }
    ]);

    /**
     * Event manager class. One of the key zebkit manager that is responsible for distributing various
     * events in zebkit UI. The manager provides possibility to catch and handle UI events globally. Below
     * is list event types that can be caught with the event manager:
     *
     *   - Key events:
     *     - "keyTyped"
     *     - "keyReleased"
     *     - "keyPressed"
     *
     *   - Pointer events:
     *     - "pointerDragged"
     *     - "pointerDragStarted"
     *     - "pointerDragEnded"
     *     - "pointerMoved"
     *     - "pointerClicked"
     *     - "pointerDoubleClicked"
     *     - "pointerPressed"
     *     - "pointerReleased"
     *     - "pointerEntered"
     *     - "pointerExited"
     *
     *   - Focus event:
     *     - "focusLost"
     *     - "focusGained"
     *
     *   - Component events:
     *     - "compSized"
     *     - "compMoved"
     *     - "compEnabled"
     *     - "compShown"
     *     - "compAdded"
     *     - "compRemoved"
     *
     *   - Window events:
     *     - "winOpened"
     *     - "winActivated"
     *
     *   - Menu events:
     *     - "menuItemSelected'
     *
     *   - Shortcut events:
     *     - "shortcutFired"
     *
     * Current events manager is available with "zebkit.ui.events"
     *
     * @class zebkit.ui.event.EventManager
     * @constructor
     * @extends zebkit.ui.event.Manager
     * @example
     *
     *     // catch all pointer pressed events that are triggered by zebkit UI
     *     zebkit.ui.events.on("pointerPressed", function(e) {
     *         // handle event
     *         ...
     *     });
     */
    pkg.EventManager = Class(pkg.Manager, zebkit.EventProducer, [
        function() {
            this._ = new this.clazz.Listerners();
            this.$super();
        },

        function $clazz() {
            var eventNames = [
                'keyTyped',
                'keyReleased',
                'keyPressed',
                'pointerDragged',
                'pointerDragStarted',
                'pointerDragEnded',
                'pointerMoved',
                'pointerClicked',
                'pointerDoubleClicked',
                'pointerPressed',
                'pointerReleased',
                'pointerEntered',
                'pointerExited',

                'focusLost',
                'focusGained',

                'compSized',
                'compMoved',
                'compEnabled',
                'compShown',
                'compAdded',
                'compRemoved'
            ];

            this.$CHILD_EVENTS_MAP = {};

            // add child<eventName> events names mapping
            for(var i = 0; i < eventNames.length; i++) {
                var eventName = eventNames[i];
                this.$CHILD_EVENTS_MAP[eventName] = "child" + eventName[0].toUpperCase() + eventName.substring(1);
            }

            this.Listerners = zebkit.ListenersClass.apply(this, eventNames);
        },

        function $prototype(clazz) {
            var $CEM = clazz.$CHILD_EVENTS_MAP;

            this.regEvents = function() {
                this._.addEvents.apply(this._, arguments);

                // add child<eventName> events names mapping
                for(var i = 0; i < arguments.length; i++) {
                    var eventName = arguments[i];
                    $CEM[eventName] = "child" + eventName[0].toUpperCase() + eventName.substring(1);
                }
            };

            /**
             * Fire event with the given id
             * @param  {String} id an event id type
             * @param  {zebkit.Event} e different sort of event
             * @return {Boolean} boolean flag that indicates if a event handling has been interrupted on one of a stage:
             *
             *    - Suppressed by a target component
             *    - By a global listener
             *    - By a target component event listener
             *
             * @method  fire
             * @protected
             */
            this.fire = function(id, e) {
                var childEvent = $CEM[id];

                // assign id that matches method to be called
                e.id = id;

                // TODO: not stable concept. the idea to suppress event
                // distribution to global listeners (managers) and child
                // components
                if (e.source.$suppressEvent !== undefined &&
                    e.source.$suppressEvent(e) === true)
                {
                    return true;
                }

                // call global listeners
                if (this._[id](e) === false) {
                    // call target component listener
                    if (e.source[id] !== undefined && e.source[id].call(e.source, e) === true) {
                        return true;
                    }

                    // call parents listeners
                    for(var t = e.source.parent; t !== null && t !== undefined; t = t.parent) {
                        if (t[childEvent] !== undefined) {
                            t[childEvent].call(t, e);
                        }
                    }

                    return false;
                } else {
                    return true;
                }
            };
        }
    ]);

    /**
     * Event manager reference. The reference can be used to register listeners that can
     * get all events of the given type that are fired by zebkit UI. For instance you can
     * catch all pointer pressed events as follow:
     * @example
     *
     *     zebkit.ui.events.on("pointerPressed", function(e) {
     *         // handle pointer pressed event here
     *         ...
     *     });
     *
     * @attribute events
     * @type {zebkit.ui.event.EventManager}
     * @readOnly
     */

     // TODO: correct to event package
     //this.events = new pkg.EventManager();
    zebkit.ui.events = new pkg.EventManager();

     /**
      * Base class to implement clipboard manager.
      * @class zebkit.ui.event.Clipboard
      * @constructor
      * @extends zebkit.ui.event.Manager
      */
    pkg.Clipboard = Class(pkg.Manager, [
        function $prototype() {
           /**
            * Get destination component. Destination component is a component that
            * is currently should participate in clipboard data exchange.
            * @return {zebkit.ui.Panel} a destination component.
            * @method getDestination
            */
            this.getDestination = function() {
                //TODO: may be focusManager has to be moved to "ui.event" package
                return zebkit.ui.focusManager.focusOwner;
            };
        }
    ]);

     /**
      * Base class to implement cursor manager.
      * @class zebkit.ui.event.CursorManager
      * @constructor
      * @extends zebkit.ui.event.Manager
      */
    pkg.CursorManager = Class(pkg.Manager, [
        function $prototype() {
            /**
             * Current cursor type
             * @attribute cursorType
             * @type {String}
             * @readOnly
             * @default "default"
             */
            this.cursorType = "default";
        }
    ]);

    /**
     * Input events state handler interface. The interface implements pointer and key
     * events handler to track the current state where State can have one of the following
     * value:
     *
     *   - **over** the pointer cursor is inside the component
     *   - **out** the pointer cursor is outside the component
     *   - **pressed.over** the pointer cursor is inside the component and an action pointer
     *     button or key is pressed
     *   - **pressed.out** the pointer cursor is outside the component and an action pointer
     *     button or key is pressed
     *
     * Every time a state has been updated "stateUpdated" method is called (if it implemented).
     * The interface can be handy way to track typical states. For instance to implement a
     * component that changes its view depending its state the following code can be used:
     *
     *     // create  panel
     *     var pan = new zebkit.ui.Panel();
     *
     *     // let's track the panel input events state and update
     *     // the component background view depending the state
     *     pan.extend(zebkit.ui.event.TrackInputEventState, [
     *         function stateUpdate(o, n) {
     *             if (n === "over") {
     *                 this.setBackround("orange");
     *             } else if (n === "out") {
     *                 this.setBackround("red");
     *             } else {
     *                 this.setBackround(null);
     *             }
     *         }
     *     ]);
     *
     *
     * @class zebkit.ui.event.TrackInputEventState
     * @interface zebkit.ui.event.TrackInputEventState
     */

    var OVER         = "over",
        PRESSED_OVER = "pressed.over",
        OUT          = "out",
        PRESSED_OUT  = "pressed.out";

    pkg.TrackInputEventState = zebkit.Interface([
        function $prototype() {
            this.state = OUT;
            this.$isIn = false;

            this._keyPressed = function(e) {
                if (this.state !== PRESSED_OVER &&
                    this.state !== PRESSED_OUT  &&
                    (e.code === "Enter" || e.code === "Space"))
                {
                     this.setState(PRESSED_OVER);
                }
            };

            this._keyReleased = function(e) {
                if (this.state === PRESSED_OVER || this.state === PRESSED_OUT){
                    this.setState(OVER);
                    if (this.$isIn === false) {
                        this.setState(OUT);
                    }
                }
            };

            this._pointerEntered = function(e) {
                if (this.isEnabled === true) {
                    this.setState(this.state === PRESSED_OUT ? PRESSED_OVER : OVER);
                    this.$isIn = true;
                }
            };

            this._pointerPressed = function(e) {
                if (this.state !== PRESSED_OVER && this.state !== PRESSED_OUT && e.isAction()){
                     this.setState(PRESSED_OVER);
                }
            };

            this._pointerReleased = function(e) {
                if ((this.state === PRESSED_OVER || this.state === PRESSED_OUT) && e.isAction()){
                    if (e.source === this) {
                        this.setState(e.x >= 0 && e.y >= 0 && e.x < this.width && e.y < this.height ? OVER
                                                                                                    : OUT);
                    } else {
                        var p = zebkit.layout.toParentOrigin(e.x, e.y, e.source, this);
                        this.$isIn = p.x >= 0 && p.y >= 0 && p.x < this.width && p.y < this.height;
                        this.setState(this.$isIn ? OVER : OUT);
                    }
                }
            };

            this.childKeyPressed = function(e) {
                this._keyPressed(e);
            };

            this.childKeyReleased = function(e) {
                this._keyReleased(e);
            };

            this.childPointerEntered = function(e) {
                this._pointerEntered(e);
            };

            this.childPointerPressed = function(e) {
                this._pointerPressed(e);
            };

            this.childPointerReleased = function(e) {
                this._pointerReleased(e);
            };

            this.childPointerExited = function(e) {
                // check if the pointer cursor is in of the source component
                // that means another layer has grabbed control
                if (e.x >= 0 && e.y >= 0 && e.x < e.source.width && e.y < e.source.height) {
                    this.$isIn = false;
                } else {
                    var p = zebkit.layout.toParentOrigin(e.x, e.y, e.source, this);
                    this.$isIn = p.x >= 0 && p.y >= 0 && p.x < this.width && p.y < this.height;
                }

                if (this.$isIn === false) {
                    this.setState(this.state === PRESSED_OVER ? PRESSED_OUT : OUT);
                }
            };

            /**
              * Define key pressed events handler
              * @param  {zebkit.ui.event.KeyEvent} e a key event
              * @method keyPressed
              */
            this.keyPressed = function(e){
                this._keyPressed(e);
            };

             /**
              * Define key released events handler
              * @param  {zebkit.ui.event.KeyEvent} e a key event
              * @method keyReleased
              */
             this.keyReleased = function(e){
                 this._keyReleased(e);
             };

             /**
              * Define pointer entered events handler
              * @param  {zebkit.ui.event.PointerEvent} e a key event
              * @method pointerEntered
              */
            this.pointerEntered = function (e){
                this._pointerEntered();
            };

             /**
              * Define pointer exited events handler
              * @param  {zebkit.ui.event.PointerEvent} e a key event
              * @method pointerExited
              */
            this.pointerExited = function(e){
                if (this.isEnabled === true) {
                    this.setState(this.state === PRESSED_OVER ? PRESSED_OUT : OUT);
                    this.$isIn = false;
                }
            };

             /**
              * Define pointer pressed events handler
              * @param  {zebkit.ui.event.PointerEvent} e a key event
              * @method pointerPressed
              */
            this.pointerPressed = function(e){
                this._pointerPressed(e);
            };

             /**
              * Define pointer released events handler
              * @param  {zebkit.ui.event.PointerEvent} e a key event
              * @method pointerReleased
              */
            this.pointerReleased = function(e){
                this._pointerReleased(e);
            };

            /**
             * Define pointer dragged events handler
             * @param  {zebkit.ui.event.PointerEvent} e a key event
             * @method pointerDragged
             */
            this.pointerDragged = function(e){
                if (e.isAction()) {
                    var pressed = (this.state === PRESSED_OUT || this.state === PRESSED_OVER);
                    if (e.x > 0 && e.y > 0 && e.x < this.width && e.y < this.height) {
                        this.setState(pressed ? PRESSED_OVER : OVER);
                    } else {
                        this.setState(pressed ? PRESSED_OUT : OUT);
                    }
                }
            };

             /**
              * Set the component state
              * @param {Object} s a state
              * @method  setState
              * @chainable
              */
            this.setState = function(s) {
                if (s !== this.state){
                    var prev = this.state;
                    this.state = s;
                    if (this.stateUpdated !== undefined) {
                        this.stateUpdated(prev, s);
                    }
                }
                return this;
            };
         }
     ]);


    /**
     * Focus event class.
     * @class zebkit.ui.event.FocusEvent
     * @constructor
     * @extends zebkit.Event
     */
    pkg.FocusEvent = Class(zebkit.Event, [
        function $prototype() {
            /**
             * Related to the event component. For focus gained event it should be a component
             * that lost focus. For focus lost event it should be a component that is going to
             * get a focus.
             * @attribute related
             * @readOnly
             * @default null
             * @type {zebkit.ui.Panel}
             */
            this.related = null;
        }
    ]);

    var FOCUS_EVENT = new pkg.FocusEvent();

    /**
     * Focus manager class defines the strategy of focus traversing among hierarchy of UI components.
     * It keeps current focus owner component and provides API to change current focus component
     * @class zebkit.ui.event.FocusManager
     * @constructor
     * @extends zebkit.ui.event.Manager
     */
    pkg.FocusManager = Class(pkg.Manager, [
        function $prototype() {
            /**
             * Reference to the current focus owner component.
             * @attribute focusOwner
             * @readOnly
             * @type {zebkit.ui.Panel}
             */
            this.focusOwner = null;

            this.$freeFocus = function(comp) {
                if ( this.focusOwner !== null &&
                    (this.focusOwner === comp || zebkit.layout.isAncestorOf(comp, this.focusOwner)))
                {
                    this.requestFocus(null);
                }
            };

            /**
             * Component enabled event handler
             * @param  {zebkit.ui.Panel} c a component
             * @method compEnabled
             */
            this.compEnabled = function(e) {
                var c = e.source;
                if (c.isVisible === true && c.isEnabled === false && this.focusOwner !== null) {
                    this.$freeFocus(c);
                }
            };

            /**
             * Component shown event handler
             * @param  {zebkit.ui.Panel} c a component
             * @method compShown
             */
            this.compShown = function(e) {
                var c = e.source;
                if (c.isEnabled === true && c.isVisible === false && this.focusOwner !== null) {
                    this.$freeFocus(c);
                }
            };

            /**
             * Component removed event handler
             * @param  {zebkit.ui.Panel} p a parent
             * @param  {Integer} i      a removed component index
             * @param  {zebkit.ui.Panel} c a removed component
             * @method compRemoved
             */
            this.compRemoved = function(e) {
                var c = e.kid;
                if (c.isEnabled === true && c.isVisible === true && this.focusOwner !== null) {
                    this.$freeFocus(c);
                }
            };

            /**
             * Test if the given component is a focus owner
             * @param  {zebkit.ui.Panel} c an UI component to be tested
             * @method hasFocus
             * @return {Boolean} true if the given component holds focus
             */
            this.hasFocus = function(c) {
                return this.focusOwner === c;
            };

            /**
             * Key pressed event handler.
             * @param  {zebkit.ui.event.KeyEvent} e a key event
             * @method keyPressed
             */
            this.keyPressed = function(e){
                if ("Tab" === e.code) {
                    var cc = this.ff(e.source, e.shiftKey ?  -1 : 1);
                    if (cc !== null) {
                        this.requestFocus(cc);
                    }
                    return true;
                }
            };

            /**
             * Find next candidate to grab focus starting from the given component.
             * @param  {zebkit.ui.Panel} c a component to start looking for next focusable.
             * @return {zebkit.ui.Panel} a next component to gain focus.
             * @method findFocusable
             */
            this.findFocusable = function(c) {
                return (this.isFocusable(c) ? c : this.fd(c, 0, 1));
            };

            /**
             * Test if the given component can catch focus
             * @param  {zebkit.ui.Panel} c an UI component to be tested
             * @method isFocusable
             * @return {Boolean} true if the given component can catch a focus
             */
            this.isFocusable = function(c) {
                var d = c.getCanvas();
                if (d !== null &&
                       (c.canHaveFocus === true ||
                         (typeof c.canHaveFocus === "function" && c.canHaveFocus() === true)))
                {
                    for(;c !== d && c !== null; c = c.parent) {
                        if (c.isVisible === false || c.isEnabled === false) {
                            return false;
                        }
                    }
                    return c === d;
                }

                return false;
            };

            // looking recursively a focusable component among children components of
            // the given target  starting from the specified by index kid with the
            // given direction (forward or backward lookup)
            this.fd = function(t, index, d) {
                if (t.kids.length > 0){
                    var isNComposite = t.catchInput === undefined || t.catchInput === false;
                    for(var i = index; i >= 0 && i < t.kids.length; i += d) {
                        var cc = t.kids[i];

                        // check if the current children component satisfies
                        // conditions it can grab focus or any deeper in hierarchy
                        // component that can grab the focus exist
                        if (cc.isEnabled === true                                           &&
                            cc.isVisible === true                                           &&
                            cc.width      >  0                                              &&
                            cc.height     >  0                                              &&
                            (isNComposite || (t.catchInput !== true      &&
                                              t.catchInput(cc) === false)  )                &&
                            ( (cc.canHaveFocus === true || (cc.canHaveFocus !== undefined   &&
                                                            cc.canHaveFocus !== false &&
                                                            cc.canHaveFocus())            ) ||
                              (cc = this.fd(cc, d > 0 ? 0 : cc.kids.length - 1, d)) !== null)  )
                        {
                            return cc;
                        }
                    }
                }

                return null;
            };

            // find next focusable component
            // c - component starting from that a next focusable component has to be found
            // d - a direction of next focusable component lookup: 1 (forward) or -1 (backward)
            this.ff = function(c, d) {
                var top = c;
                while (top !== null && top.getFocusRoot === undefined) {
                    top = top.parent;
                }

                if (top === null) {
                    return null;
                }

                top = top.getFocusRoot();
                if (top === null) {
                    return null;
                }

                if (top.traverseFocus !== undefined) {
                    return top.traverseFocus(c, d);
                }

                for(var index = (d > 0) ? 0 : c.kids.length - 1; c !== top.parent; ){
                    var cc = this.fd(c, index, d);
                    if (cc !== null) {
                        return cc;
                    }
                    cc = c;
                    c = c.parent;
                    if (c !== null) {
                        index = d + c.indexOf(cc);
                    }
                }

                return this.fd(top, d > 0 ? 0 : top.kids.length - 1, d);
            };

            /**
             * Force to pass a focus to the given UI component
             * @param  {zebkit.ui.Panel} c an UI component to pass a focus
             * @method requestFocus
             */
            this.requestFocus = function(c) {
                if (c !== this.focusOwner && (c === null || this.isFocusable(c))) {
                    var oldFocusOwner = this.focusOwner;
                    if (c !== null) {
                        var nf = c.getEventDestination();
                        if (nf === null || oldFocusOwner === nf) {
                            return;
                        }
                        this.focusOwner = nf;
                    } else {
                        this.focusOwner = c;
                    }

                    if (oldFocusOwner !== null) {
                        FOCUS_EVENT.source  = oldFocusOwner;
                        FOCUS_EVENT.related = this.focusOwner;
                        oldFocusOwner.focused();
                        zebkit.ui.events.fire("focusLost", FOCUS_EVENT);
                    }

                    if (this.focusOwner !== null) {
                        FOCUS_EVENT.source  = this.focusOwner;
                        FOCUS_EVENT.related = oldFocusOwner;
                        this.focusOwner.focused();
                        zebkit.ui.events.fire("focusGained", FOCUS_EVENT);
                    }
                }
            };

            /**
             * Pointer pressed event handler.
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerPressed
             */
            this.pointerPressed = function(e){
                if (e.isAction()) {
                    this.requestFocus(e.source);
                }
            };
        }
    ]);


    // add shortcut event type
    zebkit.ui.events.regEvents("shortcutFired");

    /**
     * Build all possible combination of the characters set:
     *   "abc" ->   abc, acb, bac, bca, cab, cba
     * @param  {String}   sequence character set
     * @param  {Function} cb  called for every new unique characters
     * sequence
     */
    function variants(sequence, cb) {
        if (sequence.length === 2) {
            cb(sequence);
            cb(sequence[1] + sequence[0]);
        } else if (sequence.length > 2) {
            for(var i = 0; i < sequence.length; i++) {
                (function(sequence, ch) {
                    variants(sequence, function(v) {
                        cb(ch + v);
                    });
                })(sequence.substring(0, i) + sequence.substring(i+1), sequence[i]);
            }
        } else {
            cb(sequence);
        }
    }

    /**
     * Shortcut event class
     * @constructor
     * @param  {zebkit.ui.Panel} src a source of the event
     * @param  {String} shortcut a shortcut name
     * @param  {String} keys a keys combination ("Control + KeyV")
     * @class zebkit.ui.event.ShortcutEvent
     * @extends zebkit.Event
     */
    pkg.ShortcutEvent = Class(zebkit.Event, [
        function(src, shortcut, keys) {
            this.source = src;

            /**
             * Shortcut name
             * @attribute shortcut
             * @readOnly
             * @type {String}
             */
            this.shortcut = shortcut;

            /**
             * Shortcut keys combination
             * @attribute keys
             * @readOnly
             * @type {String}
             */
            this.keys = keys;
        }
    ]);

    var SHORTCUT_EVENT = new pkg.ShortcutEvent();

    /**
     *  Shortcut manager supports short cut (keys) definition and listening. The shortcuts have to be defined in
     *  zebkit JSON configuration files. There are two sections:
     *
     *   - **osx** to keep shortcuts for Mac OS X platform
     *   - **common** to keep shortcuts for all other platforms
     *
     *  The JSON configuration entity has simple structure:
     *
     *
     *     {
     *       "common": {
     *           "UNDO": "Control + KeyZ",
     *           "REDO": "Control + Shift + KeyZ",
     *            ...
     *       },
     *       "osx" : {
     *           "UNDO":  "MetaLeft + KeyZ",
     *            ...
     *       }
     *     }
     *
     *  The configuration contains list of shortcuts. Every shortcut is bound to a key combination that triggers it.
     *  Shortcut has a name and an optional list of arguments that have to be passed to a shortcut listener method.
     *  The optional arguments can be used to differentiate two shortcuts that are bound to the same command.
     *
     *  On the component level shortcut can be listened by implementing "shortcutFired(e)" listener handler.
     *  Pay attention to catch shortcut your component has to be focusable - be able to hold focus.
     *  For instance, to catch "UNDO" shortcut do the following:
     *
     *      var pan = new zebkit.ui.Panel([
     *          function shortcutFired(e) {
     *              // handle shortcut here
     *              if (e.shortcut === "UNDO") {
     *
     *              }
     *          },
     *
     *          // visualize the component gets focus
     *          function focused() {
     *              this.$super();
     *              this.setBackground(this.hasFocus()?"red":null);
     *          }
     *      ]);
     *
     *      // let our panel to hold focus by setting appropriate property
     *      pan.canHaveFocus = true;
     *
     *
     *  @constructor
     *  @class zebkit.ui.event.ShortcutManager
     *  @extends zebkit.ui.event.Manager
     */
    pkg.ShortcutManager = Class(pkg.Manager, [
        function(shortcuts) {
            this.$super();

            // special structure that is a path from the first key of a sjortcut to the ID
            // for instance SELECTALL : [ "Control + KeyA", "Control + KeyW"], ... } will
            // be stored as:
            //  {
            //     "Control" : {
            //         "KeyA" : SELECTALL,
            //         "KeyW" : SELECTALL
            //     }
            //  }

            this.keyShortcuts = {};

            if (arguments.length > 0) {
                this.setShortcuts(shortcuts.common);
                if (zebkit.isMacOS === true && shortcuts.osx !== undefined) {
                    this.setShortcuts(shortcuts.osx);
                }
            }
        },

        function $prototype() {
            this.$keyPath = [];

            /**
             * Key pressed event handler.
             * @param  {zebkit.ui.event.KeyEvent} e a key event
             * @method keyPressed
             */
            this.keyPressed = function(e) {
                if (e.code === null || this.$keyPath.length > 5) {
                    this.$keyPath = [];
                } else if (e.repeat === 1) {
                    this.$keyPath[this.$keyPath.length] = e.code;
                }

                // TODO: may be focus manager has to be moved to "ui.event" package
                var fo = zebkit.ui.focusManager.focusOwner;
                if (this.$keyPath.length > 1) {
                    var sh = this.keyShortcuts;
                    for(var i = 0; i < this.$keyPath.length; i++) {
                        var code = this.$keyPath[i];
                        if (sh.hasOwnProperty(code)) {
                            sh = sh[code];
                        } else {
                            sh = null;
                            break;
                        }
                    }

                    if (sh !== null) {
                        SHORTCUT_EVENT.source   = fo;
                        SHORTCUT_EVENT.shortcut = sh;
                        SHORTCUT_EVENT.keys     = this.$keyPath.join('+');
                        zebkit.ui.events.fire("shortcutFired", SHORTCUT_EVENT);
                    }
                }
            };

            this.keyReleased = function(e) {
                if (e.key === "Meta") {
                    this.$keyPath = [];
                } else {
                    for(var i = 0; i < this.$keyPath.length; i++) {
                        if (this.$keyPath[i] === e.code) {
                            this.$keyPath.splice(i, 1);
                            break;
                        }
                    }
                }
            };

            /**
             * Set shortcuts. Expected shortcuts format is:
             *
             *      { "<ID>"  : "Control + KeyZ", ... }
             *
             * or
             *
             *       { "<ID>"  :  ["Control + KeyZ", "Control + KeyV" ], ... }
             *
             * @param {shortcuts} shortcuts
             * @method setShortcuts
             */
            this.setShortcuts = function(shortcuts) {
                for (var id in shortcuts) {
                    var shortcut = shortcuts[id],
                        j        = 0;
                    id = id.trim();

                    if (Array.isArray(shortcut) === false) {
                        shortcut = [ shortcut ];
                    }

                    var re = /\(([^()]+)\)/;
                    for(j = 0; j < shortcut.length; j++) {
                        var m = re.exec(shortcut[j]);
                        if (m !== null) {
                            var variants = m[1].replace(/\s+/g, '').split('+'),
                                prefix   = shortcut[j].substring(0, m.lastIndex),
                                suffix   = shortcut[j].substring(m[1].length + 1);
                        }
                    }

                    for(j = 0; j < shortcut.length; j++) {
                        var keys  = shortcut[j].replace(/\s+/g, '').split('+'),
                            st    = this.keyShortcuts,
                            len   = keys.length;

                        for(var i = 0; i < len; i++) {
                            var key = keys[i];

                            if (i === (len - 1)) {
                                st[key] = id;
                            } else if (st.hasOwnProperty(key) === false || zebkit.isString(st[key])) {
                                st[key] = {};
                            }

                            st = st[key];
                        }
                    }
                }
            };
        }
    ]);
},false);
zebkit.package("ui", function(pkg, Class) {
    'use strict';
    var   basedir      = zebkit.config("ui.basedir"),
          theme        = zebkit.config("ui.theme");

    this.config( { "basedir" : basedir ? basedir
                                       : zebkit.URI.join(this.$url, "rs/themes/%{theme}"),
                   "theme"   : theme   ? theme
                                       : "dark" },
                   false);

    // Panel WEB specific dependencies:
    //   -  getCanvas() -> zCanvas
    //      -  $da (dirty area)
    //      -  $isRootCanvas
    //      -  $waitingForPaint (created and controlled by Panel painting !)
    //      -  $context
    //          - restore(...)
    //          - restoreAll(...)
    //          - save()
    //          - clipRect(...)
    //          - clip()
    //          - clearRect(...)
    //          - translate(...)
    //          - $states[g.$curState] ?
    //
    // Panel zebkit classes dependencies
    //   - ui.CompEvent
    //   - ui.events EventManager
    //   - util.*


    /**
     *  Zebkit UI package contains a lot of various components. Zebkit UI idea is rendering
     *  hierarchy of UI components on a canvas (HTML5 Canvas). Typical zebkit application
     *  looks as following:
     *
     *       zebkit.require("ui", "layout", function(ui) {
     *           // create canvas and save reference to root layer
     *           // where zebkit UI components should live.
     *           var root = new ui.zCanvas(400, 400).root;
     *
     *           // build UI layout
     *           root.properties({
     *               layout : new layout.BorderLayout(4),
     *               padding: 8,
     *               kids   : {
     *                   "center" : new ui.TextArea("A text"),
     *                   "top"    : new ui.ToolbarPan().properties({
     *                       kids : [
     *                           new ui.ImagePan("icon1.png"),
     *                           new ui.ImagePan("icon2.png"),
     *                           new ui.ImagePan("icon3.png")
     *                      ]
     *                   }),
     *                   "bottom" : new ui.Button("Apply")
     *               }
     *           });
     *       });
     *
     *  UI components are ordered with help of layout managers. You should not use absolute
     *  location or size your component. It is up to layout manager to decide which size and
     *  location the given  component has to have. In the example above we add number of UI
     *  components to "root" (UI Panel). The root panel uses "BorderLayout" [to order the
     *  added components. The layout manager split root area to number of areas: "center",
     *  "top", "left", "right", "bottom" where children components can be placed.
     *
     *  @class zebkit.ui
     *  @access package
     */

    // extend Zson with special method to fill predefined views set
    zebkit.Zson.prototype.views = function(v) {
        for (var k in v) {
            if (v.hasOwnProperty(k)) {
                zebkit.draw.$views[k] = v[k];
            }
        }
    };

    function testCondition(target, cond) {
        for(var k in cond) {
            var cv = cond[k],
                tv = zebkit.getPropertyValue(target, k, true);

            if (cv !== tv) {
                return false;
            }
        }
        return true;
    }

    zebkit.Zson.prototype.completed = function() {
        if (this.$actions !== undefined && this.$actions.length > 0) {
            try {
                var root = this.root;
                for (var i = 0; i < this.$actions.length; i++) {
                    var a = this.$actions[i];

                    (function(source, eventName, cond, targets) {
                        var args    = [],
                            srcComp = root.byPath(source);

                        if (eventName !== undefined && eventName !== null) {
                            args.push(eventName);
                        }
                        args.push(source);
                        args.push(function() {
                            if (cond === null || cond === undefined || testCondition(srcComp, cond)) {
                                // targets
                                for(var j = 0; j < targets.length; j++) {
                                    var target     = targets[j],
                                        targetPath = (target.path === undefined) ? source : target.path;

                                    // find target
                                    root.byPath(targetPath, function(c) {
                                        if (target.condition === undefined || testCondition(c, target.condition)) {
                                            if (target.update !== undefined) {
                                                c.properties(target.update);
                                            }

                                            if (target.do !== undefined) {
                                                for(var cmd in target['do']) {
                                                    c[cmd].apply(c, target['do'][cmd]);
                                                }
                                            }
                                        }
                                    });
                                }
                            }
                        });
                        root.on.apply(root, args);
                    } (a.source, a.event, a.condition, a.targets !== undefined ? a.targets : [ a.target ]));
                }
            } finally {
                this.$actions.length = 0;
            }
        }
    };

    zebkit.Zson.prototype.actions = function(v) {
        this.$actions = [];
        for (var i = 0; i < arguments.length; i++) {
            this.$actions.push(arguments[i]);
        }
    };

    zebkit.Zson.prototype.font = function() {
        return zebkit.$font.apply(this, arguments);
    };

    zebkit.Zson.prototype.gradient = function() {
        if (arguments.length === 1) {
            if (zebkit.instanceOf(arguments[0], zebkit.draw.Gradient)) {
                return arguments[0];
            } else {
                return new zebkit.draw.Gradient(arguments[0]);
            }
        } else {
            return zebkit.draw.Gradient.newInstancea(arguments);
        }
    };

    zebkit.Zson.prototype.border = function() {
        if (arguments.length === 1 && zebkit.instanceOf(arguments[0], zebkit.draw.Border)) {
            return arguments[0];
        } else {
            return zebkit.draw.Border.newInstancea(arguments);
        }
    };

    zebkit.Zson.prototype.pic = function() {
        if (arguments.length === 1 && zebkit.instanceOf(arguments[0], zebkit.draw.Picture)) {
            return arguments[0];
        } else {
            return zebkit.draw.Picture.newInstancea(arguments);
        }
    };

    // TODO: prototype of zClass, too simple to say something
    pkg.zCanvas = Class([]);

    /**
     * Get preferred size shortcut. Null can be passed as the method argument
     * @private
     * @param  {zebkit.ui.Layoutable} l a layoutable component
     * @return {Object}  a preferred size:
     *      { width : {Integer}, height: {Integer} }
     * @method $getPS
     * @for zebkit.ui
     */
    pkg.$getPS = function(l) {
        return l !== null && l.isVisible === true ? l.getPreferredSize()
                                                  : { width:0, height:0 };
    };

    /**
     * Calculate visible area of the given components taking in account
     * intersections with parent hierarchy.
     * @private
     * @param  {zebkit.ui.Panel} c  a component
     * @param  {Object} r a variable to store visible area
     *
     *       { x: {Integer}, y: {Integer}, width: {Integer}, height: {Integer} }
     *
     * @method $cvp
     * @for zebkit.ui
     */
    pkg.$cvp = function(c, r) {
        if (c.width > 0 && c.height > 0 && c.isVisible === true){
            var p  = c.parent,
                px = -c.x,  // transform parent coordinates to
                py = -c.y;  // children component coordinate system
                            // since the result has to be in

            if (arguments.length < 2) {
                r = { x:0, y:0, width : c.width, height : c.height };
            } else {
                r.x = r.y = 0;
                r.width  = c.width;
                r.height = c.height;
            }

            while (p !== null && r.width > 0 && r.height > 0) {
                var xx = r.x > px ? r.x : px,
                    yy = r.y > py ? r.y : py,
                    w1 = r.x + r.width,
                    w2 = px  + p.width,
                    h1 = r.y + r.height,
                    h2 = py  + p.height;

                r.width  = (w1 < w2 ? w1 : w2) - xx;
                r.height = (h1 < h2 ? h1 : h2) - yy;
                r.x = xx;
                r.y = yy;

                px -= p.x;  // transform next parent coordinates to
                py -= p.y;  // children component coordinate system
                p = p.parent;
            }

            return r.width > 0 && r.height > 0 ? r : null;
        } else {
            return null;
        }
    };

    /**
     * Relocate the given component to make them fully visible.
     * @param  {zebkit.ui.Panel} [d] a parent component where the given component has to be re-located
     * @param  {zebkit.ui.Panel} c  a component to re-locate to make it fully visible in the parent
     * component
     * @method makeFullyVisible
     * @for  zebkit.ui
     */
    pkg.makeFullyVisible = function(d, c){
        if (arguments.length === 1) {
            c = d;
            d = c.parent;
        }

        var right  = d.getRight(),
            top    = d.getTop(),
            bottom = d.getBottom(),
            left   = d.getLeft(),
            xx     = c.x,
            yy     = c.y;

        if (xx < left) {
            xx = left;
        }

        if (yy < top)  {
            yy = top;
        }

        if (xx + c.width > d.width - right) {
            xx = d.width + right - c.width;
        }

        if (yy + c.height > d.height - bottom) {
            yy = d.height + bottom - c.height;
        }
        c.setLocation(xx, yy);
    };

    pkg.calcOrigin = function(x,y,w,h,px,py,t,tt,ll,bb,rr){
        if (arguments.length < 8) {
        tt = t.getTop();
            ll = t.getLeft();
            bb = t.getBottom();
            rr = t.getRight();
        }

        var dw = t.width, dh = t.height;
        if (dw > 0 && dh > 0){
            if (dw - ll - rr > w){
                var xx = x + px;
                if (xx < ll) {
                    px += (ll - xx);
                } else {
                    xx += w;
                    if (xx > dw - rr) {
                        px -= (xx - dw + rr);
                    }
                }
            }
            if (dh - tt - bb > h){
                var yy = y + py;
                if (yy < tt) {
                    py += (tt - yy);
                } else {
                    yy += h;
                    if (yy > dh - bb) {
                        py -= (yy - dh + bb);
                    }
                }
            }
            return [px, py];
        }
        return [0, 0];
    };


    var $paintTask = null,
        $paintTasks = [],
        temporary = { x:0, y:0, width:0, height:0 },
        COMP_EVENT = new zebkit.ui.event.CompEvent();

    /**
     * Trigger painting for all collected paint tasks
     * @protected
     * @method $doPaint
     * @for zebkit.ui
     */
    pkg.$doPaint = function() {
        for (var i = $paintTasks.length - 1; i >= 0; i--) {
            var canvas = $paintTasks.shift();
            try {
                // do validation before timer will be set to null to avoid
                // unnecessary timer initiating what can be caused by validation
                // procedure by calling repaint method
                if (canvas.isValid === false || canvas.isLayoutValid === false) {
                    canvas.validate();
                }

                if (canvas.$da.width > 0) {
                    canvas.$context.save();

                    // check if the given canvas has transparent background
                    // if it is true call clearRect method to clear dirty area
                    // with transparent background, otherwise it will be cleaned
                    // by filling the canvas with background later
                    if (canvas.bg === null || canvas.bg.isOpaque !== true) {

                        // Clear method can be applied to scaled (retina screens) canvas
                        // The real cleaning location is calculated as x' = scaleX * x.
                        // The problem appears when scale factor is not an integer value
                        // what brings to situation x' can be float like 1.2 or 1.8.
                        // Most likely canvas applies round operation to x' so 1.8 becomes 2.
                        // That means clear method will clear less then expected, what results
                        // in visual artifacts are left on the screen. The code below tries
                        // to correct cleaning area to take in account the round effect.
                        if (canvas.$context.$scaleRatioIsInt === false) {

                            // Clear canvas scaling and calculate dirty area bounds.
                            // Bounds are calculated taking in account the fact that float
                            // bounds can leave visual traces
                            var xx = Math.floor(canvas.$da.x * canvas.$context.$scaleRatio),
                                yy = Math.floor(canvas.$da.y * canvas.$context.$scaleRatio),
                                ww = Math.ceil((canvas.$da.x + canvas.$da.width) * canvas.$context.$scaleRatio) - xx,
                                hh = Math.ceil((canvas.$da.y + canvas.$da.height) * canvas.$context.$scaleRatio) - yy;

                                canvas.$context.save();
                                canvas.$context.setTransform(1, 0, 0, 1, 0, 0);
                                canvas.$context.clearRect(xx, yy, ww, hh);

                                // !!!! clipping has to be done over not scaled
                                // canvas, otherwise if we have two overlapped panels
                                // with its own background moving third panel over overlapped
                                // part will leave traces that comes from lowest overlapped panel
                                // !!! Have no idea why !
                                // canvas.$context.beginPath();
                                // canvas.$context.rect(xx, yy, ww, hh);
                                // canvas.$context.closePath();
                                // canvas.$context.clip();

                                canvas.$context.restore();

                                canvas.$context.clipRect(canvas.$da.x - 1,
                                                         canvas.$da.y - 1,
                                                         canvas.$da.width + 2,
                                                         canvas.$da.height + 2);
                        } else {
                            canvas.$context.clearRect(canvas.$da.x, canvas.$da.y,
                                                      canvas.$da.width, canvas.$da.height);

                            // !!!
                            // call clipping area later than possible
                            // clearRect since it can bring to error in IE
                            canvas.$context.clipRect(canvas.$da.x,
                                                     canvas.$da.y,
                                                     canvas.$da.width,
                                                     canvas.$da.height);

                        }
                    }


                    // no dirty area anymore. put it hear to prevent calling
                    // animation  task from repaint() method that can be called
                    // inside paintComponent method.
                    canvas.$da.width = -1;

                    // clear flag that says the canvas is waiting for repaint, that allows to call
                    // repaint from paint method
                    canvas.$waitingForPaint = false;

                    canvas.paintComponent(canvas.$context);
                    canvas.$context.restore();
                } else {
                    canvas.$waitingForPaint = false;
                }
            } catch(ex) {
                // catch error and clean task list if any to avoid memory leaks
                try {
                    if (canvas !== null) {
                        canvas.$waitingForPaint = false;
                        canvas.$da.width = -1;
                        if (canvas.$context !== null) {
                            canvas.$context.restoreAll();
                        }
                    }
                } catch(exx) {
                    $paintTask = null;
                    $paintTasks.length = 0;
                    throw exx;
                }

                zebkit.dumpError(ex);
            }
        }

        // paint task is done
        $paintTask = null;

        // test if new dirty canvases have appeared and start
        // animation again
        if ($paintTasks.length !== 0) {
            $paintTask = zebkit.environment.animate(pkg.$doPaint);
        }
    };

    /**
     *  This the core UI component class. All other UI components has to be successor of panel class.
     *
     *      // instantiate panel with no arguments
     *      var p = new zebkit.ui.Panel();
     *
     *      // instantiate panel with border layout set as its layout manager
     *      var p = new zebkit.ui.Panel(new zebkit.layout.BorderLayout());
     *
     *      // instantiate panel with the given properties (border
     *      // layout manager, blue background and plain border)
     *      var p = new zebkit.ui.Panel({
     *         layout: new zebkit.ui.BorderLayout(),
     *         background : "blue",
     *         border     : "plain"
     *      });
     *
     *  **Container**
     * Panel can contains number of other UI components as its children where the children components
     * are placed with a defined by the panel layout manager:
     *
     *      // add few children component to panel top, center and bottom parts
     *      // with help of border layout manager
     *      var p = new zebkit.ui.Panel();
     *      p.setBorderLayout(4); // set layout manager to
     *                            // order children components
     *
     *      p.add("top", new zebkit.ui.Label("Top label"));
     *      p.add("center", new zebkit.ui.TextArea("Text area"));
     *      p.add("bottom", new zebkit.ui.Button("Button"));
     *
     * **Input and component events**
     * The class provides possibility to catch various component and input events by declaring an
     * appropriate event method handler. The most simple case you just define a method:
     *
     *      var p = new zebkit.ui.Panel();
     *      p.pointerPressed = function(e) {
     *          // handle event here
     *      };
     *
     * If you prefer to create an anonymous class instance you can do it as follow:
     *
     *      var p = new zebkit.ui.Panel([
     *          function pointerPressed(e) {
     *              // handle event here
     *          }
     *      ]);
     *
     * One more way to add the event handler is dynamic extending of an instance class demonstrated
     * below:
     *
     *      var p = new zebkit.ui.Panel("Test");
     *      p.extend([
     *          function pointerPressed(e) {
     *              // handle event here
     *          }
     *      ]);
     *
     * Pay attention Zebkit UI components often declare own event handlers and in this case you can
     * overwrite the default event handler with a new one. Preventing the basic event handler execution
     * can cause the component will work improperly. You should care about the base event handler
     * execution as follow:
     *
     *      // button component declares own pointer pressed event handler
     *      // we have to call the original handler to keep the button component
     *      // properly working
     *      var p = new zebkit.ui.Button("Test");
     *      p.extend([
     *          function pointerPressed(e) {
     *              this.$super(e); // call parent class event handler implementation
     *              // handle event here
     *          }
     *      ]);
     *
     *  @class zebkit.ui.Panel
     *  @param {Object|zebkit.layout.Layout} [l] pass a layout manager or number of properties that have
     *  to be applied to the instance of the panel class.
     *  @constructor
     *  @extends zebkit.layout.Layoutable
     */

    /**
     * Implement the event handler method to catch pointer pressed event. The event is triggered every time
     * a pointer button has been pressed or a finger has touched a touch screen.
     *
     *     var p = new zebkit.ui.Panel();
     *     p.pointerPressed = function(e) { ... }; // add event handler
     *
     * @event pointerPressed
     * @param {zebkit.ui.event.PointerEvent} e a pointer event
    */

    /**
     * Implement the event handler method to catch pointer released event. The event is triggered every time
     * a pointer button has been released or a finger has untouched a touch screen.
     *
     *     var p = new zebkit.ui.Panel();
     *     p.pointerReleased = function(e) { ... }; // add event handler
     *
     * @event pointerReleased
     * @param {zebkit.ui.event.PointerEvent} e a pointer event
     */

    /**
     * Implement the event handler method  to catch pointer moved event. The event is triggered every time
     * a pointer cursor has been moved with no a pointer button pressed.
     *
     *     var p = new zebkit.ui.Panel();
     *     p.pointerMoved = function(e) { ... }; // add event handler
     *
     * @param {zebkit.ui.event.PointerEvent} e a pointer event
     * @event  pointerMoved
     */

    /**
     * Implement the event handler method to catch pointer entered event. The event is triggered every
     * time a pointer cursor entered the given component.
     *
     *     var p = new zebkit.ui.Panel();
     *     p.pointerEntered = function(e) { ... }; // add event handler
     *
     * @param {zebkit.ui.event.PointerEvent} e a pointer event
     * @event  pointerEntered
     */

    /**
     * Implement the event handler method to catch pointer exited event. The event is triggered every
     * time a pointer cursor exited the given component.
     *
     *     var p = new zebkit.ui.Panel();
     *     p.pointerExited = function(e) { ... }; // add event handler
     *
     * @param {zebkit.ui.event.PointerEvent} e a pointer event
     * @event  pointerExited
     */

    /**
     * Implement the event handler method to catch pointer clicked event. The event is triggered every
     * time a pointer button has been clicked. Click events are generated only if no one pointer moved
     * or drag events has been generated in between pointer pressed -> pointer released events sequence.
     *
     *     var p = new zebkit.ui.Panel();
     *     p.pointerClicked = function(e) { ... }; // add event handler
     *
     * @param {zebkit.ui.event.PointerEvent} e a pointer event
     * @event  pointerClicked
     */

    /**
     * Implement the event handler method to catch pointer dragged event. The event is triggered every
     * time a pointer cursor has been moved when a pointer button has been pressed. Or when a finger
     * has been moved over a touch screen.
     *
     *     var p = new zebkit.ui.Panel();
     *     p.pointerDragged = function(e) { ... }; // add event handler
     *
     * @param {zebkit.ui.event.PointerEvent} e a pointer event
     * @event  pointerDragged
     */

    /**
     * Implement the event handler method to catch pointer drag started event. The event is triggered
     * every time a pointer cursor has been moved first time when a pointer button has been pressed.
     * Or when a finger has been moved first time over a touch screen.
     *
     *     var p = new zebkit.ui.Panel();
     *     p.pointerDragStarted = function(e) { ... }; // add event handler
     *
     * @param {zebkit.ui.event.PointerEvent} e a pointer event
     * @event  pointerDragStarted
    */

    /**
     * Implement the event handler method to catch pointer drag ended event. The event is triggered
     * every time a pointer cursor has been moved last time when a pointer button has been pressed.
     * Or when a finger has been moved last time over a touch screen.
     *
     *     var p = new zebkit.ui.Panel();
     *     p.pointerDragEnded = function(e) { ... }; // add event handler
     *
     * @param {zebkit.ui.event.PointerEvent} e a pointer event
     * @event  pointerDragEnded
    */

    /**
     * Implement the event handler method to catch key pressed event The event is triggered every
     * time a key has been pressed.
     *
     *    var p = new zebkit.ui.Panel();
     *    p.keyPressed = function(e) { ... }; // add event handler
     *
     * @param {zebkit.ui.event.KeyEvent} e a key event
     * @event  keyPressed
     */

    /**
     * Implement the event handler method to catch key types event The event is triggered every
     * time a key has been typed.
     *
     *     var p = new zebkit.ui.Panel();
     *     p.keyTyped = function(e) { ... }; // add event handler
     *
     * @param {zebkit.ui.event.KeyEvent} e a key event
     * @event  keyTyped
     */

    /**
     * Implement the event handler method to catch key released event
     * The event is triggered every time a key has been released.
     *
     *     var p = new zebkit.ui.Panel();
     *     p.keyReleased = function(e) { ... }; // add event handler
     *
     * @param {zebkit.ui.event.KeyEvent} e a key event
     * @event  keyReleased
     */

    /**
     * Implement the event handler method to catch the component sized event
     * The event is triggered every time the component has been re-sized.
     *
     *     var p = new zebkit.ui.Panel();
     *     p.compSized = function(e) { ... }; // add event handler
     *
     * @param {zebkit.ui.event.CompEvent} e a component event. Source of the event
     * is a component that has been sized, "prevWidth" and "prevHeight" fields
     * keep a previous size the component had.
     * @event compSized
     */

    /**
     * Implement the event handler method to catch component moved event
     * The event is triggered every time the component location has been
     * updated.
     *
     *      var p = new zebkit.ui.Panel();
     *      p.compMoved = function(e) { ... }; // add event handler
     *
     * @param {zebkit.ui.event.CompEvent} e a component event. Source of the event
     * is a component that has been moved.
     * @event compMoved
     */

    /**
     * Implement the event handler method to catch component enabled event
     * The event is triggered every time a component enabled state has been
     * updated.
     *
     *     var p = new zebkit.ui.Panel();
     *     p.compEnabled = function(e) { ... }; // add event handler
     *
     * @param {zebkit.ui.event.CompEvent} e a component event.
     * @event compEnabled
     */

    /**
     * Implement the event handler method to catch component shown event
     * The event is triggered every time a component visibility state has
     * been updated.
     *
     *     var p = new zebkit.ui.Panel();
     *     p.compShown = function(e) { ... }; // add event handler
     *
     * @param {zebkit.ui.event.CompEvent} e a component event.
     * @event compShown
     */

    /**
     * Implement the event handler method to catch component added event
     * The event is triggered every time the component has been inserted into
     * another one.
     *
     *     var p = new zebkit.ui.Panel();
     *     p.compAdded = function(e) { ... }; // add event handler
     *
     * @param {zebkit.ui.event.CompEvent} e a component event. The source of the passed event
     * is set to a container component.
     * @event compAdded
     */

    /**
     * Implement the event handler method to catch component removed event
     * The event is triggered every time the component has been removed from
     * its parent UI component.
     *
     *     var p = new zebkit.ui.Panel();
     *     p.compRemoved = function(e) { ... }; // add event handler
     *
     * @param {zebkit.ui.event.CompEvent} e a component event. The source of the passed event
     * is set to the container component.
     * @event compRemoved
     */

    /**
     * Implement the event handler method to catch component focus gained event
     * The event is triggered every time a component has gained focus.
     *
     *     var p = new zebkit.ui.Panel();
     *     p.focusGained = function(e) { ... }; // add event handler
     *
     * @param {zebkit.ui.event.FocusEvent} e an input event
     * @event  focusGained
     */

    /**
     * Implement the event handler method to catch component focus lost event
     * The event is triggered every time a component has lost focus
     *
     *     var p = new zebkit.ui.Panel();
     *     p.focusLost = function(e) { ... }; // add event handler
     *
     * @param {zebkit.ui.event.FocusEvent} e an input event
     * @event  focusLost
     */

    /**
     * It is also possible to listen all the listed above event for children component. To handle
     * the event register listener method following the pattern below:
     *
     *
     *     var p = new zebkit.ui.Panel();
     *     p.childPointerPressed = function(e) { ... }; // add event handler
     *
     *
     * @param {zebkit.ui.event.KeyEvent | zebkit.ui.event.PointerEvent | zebkit.ui.event.CompEvent | zebkit.ui.event.FocusEvent}
     * e an UI event fired by a child component.
     * @event  childEventName
     */

     /**
      * The method is called for focusable UI components (components that can hold input focus) to ask
      * a string to be saved in native clipboard
      *
      * @return {String} a string to be copied in native clipboard
      *
      * @event clipCopy
      */

     /**
      * The method is called to pass string from clipboard to a focusable (a component that can hold
      * input focus) UI component
      *
      * @param {String} s a string from native clipboard
      *
      * @event clipPaste
      */
    pkg.Panel = Class(zebkit.layout.Layoutable, [
        function $prototype() {
            /**
             * Request the whole UI component or part of the UI component to be repainted
             * @param  {Integer} [x] x coordinate of the component area to be repainted
             * @param  {Integer} [y] y coordinate of the component area to be repainted
             * @param  {Integer} [w] width of the component area to be repainted
             * @param  {Integer} [h] height of the component area to be repainted
             * @method repaint
             */
            this.repaint = function(x, y, w ,h) {
                // step I: skip invisible components and components that are not in hierarchy
                //         don't initiate repainting thread for such sort of the components,
                //         but don't forget for zCanvas whose parent field is null, but it has $context
                if (this.isVisible === true && (this.parent !== null || this.$context !== undefined)) {
                    //!!! find context buffer that holds the given component

                    var canvas = this;
                    for(; canvas.$context === undefined; canvas = canvas.parent) {
                        // component either is not in visible state or is not in hierarchy
                        // than stop repaint procedure
                        if (canvas.isVisible === false || canvas.parent === null) {
                            return;
                        }
                    }

                    // no arguments means the whole component has top be repainted
                    if (arguments.length === 0) {
                        x = y = 0;
                        w = this.width;
                        h = this.height;
                    }

                    // step II: calculate new actual dirty area
                    if (w > 0 && h > 0) {
                        var r = pkg.$cvp(this, temporary);

                        if (r !== null) {
                            zebkit.util.intersection(r.x, r.y, r.width, r.height, x, y, w, h, r);

                            if (r.width > 0 && r.height > 0) {
                                x = r.x;
                                y = r.y;
                                w = r.width;
                                h = r.height;

                                // calculate repainted component absolute location
                                var cc = this;
                                while (cc !== canvas) {
                                    x += cc.x;
                                    y += cc.y;
                                    cc = cc.parent;
                                }

                                // normalize repaint area coordinates
                                if (x < 0) {
                                    w += x;
                                    x = 0;
                                }

                                if (y < 0) {
                                    h += y;
                                    y = 0;
                                }

                                if (w + x > canvas.width ) {
                                    w = canvas.width - x;
                                }

                                if (h + y > canvas.height) {
                                    h = canvas.height - y;
                                }

                                // still have what to repaint than calculate new
                                // dirty area of target canvas element
                                if (w > 0 && h > 0) {
                                    var da = canvas.$da;
                                    // if the target canvas already has a dirty area set than
                                    // unite it with requested
                                    if (da.width > 0) {
                                        // check if the requested repainted area is not in
                                        // exiting dirty area
                                        if (x < da.x                ||
                                            y < da.y                ||
                                            x + w > da.x + da.width ||
                                            y + h > da.y + da.height  )
                                        {
                                            // !!!
                                            // speed up to comment method call
                                            //MB.unite(da.x, da.y, da.width, da.height, x, y, w, h, da);
                                            var dax = da.x, day = da.y;
                                            if (da.x > x) {
                                                da.x = x;
                                            }
                                            if (da.y > y) {
                                                da.y = y;
                                            }
                                            da.width  = Math.max(dax + da.width,  x + w) - da.x;
                                            da.height = Math.max(day + da.height, y + h) - da.y;
                                        }
                                    } else {
                                        // if the target canvas doesn't have a dirty area set than
                                        // cut (if necessary) the requested repainting area by the
                                        // canvas size

                                        // !!!
                                        // not necessary to call the method since we have already normalized
                                        // repaint coordinates and sizes
                                        //!!! MB.intersection(0, 0, canvas.width, canvas.height, x, y, w, h, da);

                                        da.x      = x;
                                        da.width  = w;
                                        da.y      = y;
                                        da.height = h;
                                    }
                                }
                            }
                        }
                    }

                    if (canvas.$waitingForPaint !== true && (canvas.isValid === false ||
                                                             canvas.$da.width > 0     ||
                                                             canvas.isLayoutValid === false))
                    {
                        $paintTasks[$paintTasks.length] = canvas;
                        canvas.$waitingForPaint = true;
                        if ($paintTask === null) {
                            $paintTask = zebkit.environment.animate(pkg.$doPaint);
                        }
                    }
                }
            };

            // destination is component itself or one of his composite parent.
            // composite component is a component that grab control from his
            // children component. to make a component composite
            // it has to implement catchInput field or method. If composite component
            // has catchInput method it will be called
            // to detect if the composite component takes control for the given kid.
            // composite components can be embedded (parent composite can take
            // control on its child composite component)
            this.getEventDestination = function() {
                var c = this, p = this;
                while ((p = p.parent) !== null) {
                    if (p.catchInput !== undefined &&
                        (p.catchInput === true || (p.catchInput    !== false &&
                                                   p.catchInput(c) === true     )))
                    {
                        c = p;
                    }
                }
                return c;
            };

            /**
             * Paint the component and all its child components using the
             * given 2D HTML Canvas context
             * @param  {CanvasRenderingContext2D} g a canvas 2D context
             * @method paintComponent
             */
            this.paintComponent = function(g) {
                var ts  = g.$states[g.$curState]; // current state including clip area

                if (ts.width  > 0  &&
                    ts.height > 0  &&
                    this.isVisible === true)
                {
                    // !!! TODO: WTF
                    // calling setSize in the case of raster layout doesn't
                    // cause hierarchy layout invalidation
                    if (this.isLayoutValid === false) {
                        this.validate();
                    }

                    var b = this.bg !== null && (this.parent === null || this.bg !== this.parent.bg);

                    // if component defines shape and has update, [paint?] or background that
                    // differs from parent background try to apply the shape and than build
                    // clip from the applied shape
                    if ( (this.border !== null && this.border.outline !== undefined) &&
                         (b === true || this.update !== undefined)                   &&
                         this.border.outline(g, 0, 0, this.width, this.height, this) === true)
                    {
                        g.save();
                        g.clip();

                        if (b) {
                            this.bg.paint(g, 0, 0, this.width, this.height, this);
                        }

                        if (this.update !== undefined) {
                            this.update(g);
                        }

                        g.restore();

                        this.border.paint(g, 0, 0, this.width, this.height, this);
                    } else {
                        if (b === true) {
                            this.bg.paint(g, 0, 0, this.width, this.height, this);
                        }

                        if (this.update !== undefined) {
                            this.update(g);
                        }

                        if (this.border !== null) {
                            this.border.paint(g, 0, 0, this.width, this.height, this);
                        }
                    }

                    if (this.paint !== undefined) {
                        var left   = this.getLeft(),
                            top    = this.getTop(),
                            bottom = this.getBottom(),
                            right  = this.getRight();

                        if (left > 0 || right > 0 || top > 0 || bottom > 0) {
                            var tsxx = ts.x + ts.width,
                                tsyy = ts.y + ts.height,
                                cright     = this.width - right,
                                cbottom    = this.height - bottom,
                                x1         = (ts.x > left ? ts.x : left), // max
                                y1         = (ts.y > top  ? ts.y : top),  // max
                                w1         = (tsxx < cright  ? tsxx : cright)  - x1, // min
                                h1         = (tsyy < cbottom ? tsyy : cbottom) - y1; // min

                            if (x1 !== ts.x || y1 !== ts.y || w1 !== ts.width || h1 !== ts.height) {
                                g.save();
                                g.clipRect(x1, y1, w1, h1);

                                this.paint(g);
                                if (this.$doNotClipChildComponents === true) {
                                    g.restore();
                                    this.paintChildComponents(g, false);
                                } else {
                                    this.paintChildComponents(g, false);
                                    g.restore();
                                }
                            } else {
                                // It has been checked that the optimization works for some components
                                this.paint(g);
                                this.paintChildComponents(g, this.$doNotClipChildComponents !== true);
                            }
                        } else {
                            this.paint(g);
                            this.paintChildComponents(g, this.$doNotClipChildComponents !== true);
                        }
                    } else {
                        this.paintChildComponents(g, this.$doNotClipChildComponents !== true);
                    }

                    if (this.paintOnTop !== undefined) {
                        this.paintOnTop(g);
                    }
                }
            };

            /**
             * Paint child components.
             * @param  {CanvasRenderingContext2D} g a canvas 2D context
             * @param {Boolean}  clipChild true if child components have to be clipped with
             * the parent component paddings.
             * @method paintChildComponents
             */
            this.paintChildComponents = function(g, clipChild) {
                var ts = g.$states[g.$curState]; // current state including clip area

                if (ts.width > 0 && ts.height > 0 && this.kids.length > 0) {
                    var shouldClip = false,
                        tsxx       = ts.x + ts.width,
                        tsyy       = ts.y + ts.height;;

                    if (clipChild === true) {
                        var left   = this.getLeft(),
                            top    = this.getTop(),
                            bottom = this.getBottom(),
                            right  = this.getRight();

                        if (left > 0 || right > 0 || top > 0 || bottom > 0) {
                            var x1         = (ts.x > left ? ts.x : left), // max
                                y1         = (ts.y > top  ? ts.y : top),  // max
                                cright     = this.width - right,
                                cbottom    = this.height - bottom,
                                w1         = (tsxx < cright  ? tsxx : cright)  - x1, // min
                                h1         = (tsyy < cbottom ? tsyy : cbottom) - y1; // min

                            shouldClip = (x1 !== ts.x || y1 !== ts.y || w1 !== ts.width || h1 !== ts.height);

                            if (shouldClip === true) {
                                g.save();
                                g.clipRect(x1, y1, w1, h1);
                            }
                        }
                    }

                    for(var i = 0; i < this.kids.length; i++) {
                        var kid = this.kids[i];
                        // ignore invisible components and components that declare own 2D context
                        if (kid.isVisible === true && kid.$context === undefined) {
                            // calculate if the given component area has intersection
                            // with current clipping area
                            var kidxx = kid.x + kid.width,
                                kidyy = kid.y + kid.height,
                                iw = (kidxx < tsxx ? kidxx : tsxx) - (kid.x > ts.x ? kid.x : ts.x),
                                ih = (kidyy < tsyy ? kidyy : tsyy) - (kid.y > ts.y ? kid.y : ts.y);

                            if (iw > 0 && ih > 0) {
                                g.save();
                                g.translate(kid.x, kid.y);
                                g.clipRect(0, 0, kid.width, kid.height);
                                kid.paintComponent(g);
                                g.restore();
                            }
                        }
                    }

                    if (shouldClip === true) {
                        g.restore();
                    }
                }
            };

            /**
             * UI component border view
             * @attribute border
             * @default null
             * @readOnly
             * @type {zebkit.draw.View}
             */
            this.border = null;

            /**
             * UI component background view
             * @attribute bg
             * @default null
             * @readOnly
             * @type {zebkit.draw.View}
            */
            this.bg = null;

            /**
             * Define and set the property to true if the component has to catch focus
             * @attribute canHaveFocus
             * @type {Boolean}
             * @default undefined
             */

            this.top = this.left = this.right = this.bottom = 0;

            /**
             * UI component enabled state
             * @attribute isEnabled
             * @default true
             * @readOnly
             * @type {Boolean}
             */
            this.isEnabled = true;

            /**
             * Find a zebkit.ui.zCanvas where the given UI component is hosted
             * @return {zebkit.ui.zCanvas} a zebkit canvas
             * @method getCanvas
             */
            this.getCanvas = function() {
                var c = this;
                for(; c !== null && c.$isRootCanvas !== true; c = c.parent) {}
                return c;
            };

            this.notifyRender = function(o, n){
                if (o !== null && o.ownerChanged !== undefined) {
                    o.ownerChanged(null);
                }
                if (n !== null && n.ownerChanged !== undefined) {
                    n.ownerChanged(this);
                }
            };

            /**
             * Set border layout shortcut method
             * @param {Integer} [gap] a gap
             * @method setBorderLayout
             * @chainable
             */
            this.setBorderLayout = function() {
                this.setLayout(zebkit.layout.BorderLayout.newInstancea(arguments));
                return this;
            };

            /**
             * Set flow layout shortcut method.
             * @param {String} [ax] ("left" by default) horizontal alignment:
             *
             *    "left"
             *    "center"
             *    "right"
             *
             * @param {String} [ay] ("top" by default) vertical alignment:
             *
             *    "top"
             *    "center"
             *    "bottom"
             *
             * @param {String} [dir] ("horizontal" by default) a direction the component has to be placed
             * in the layout
             *
             *    "vertical"
             *    "horizontal"
             *
             * @param {Integer} [gap] a space in pixels between laid out components
             * @method setFlowLayout
             * @chainable
             */
            this.setFlowLayout = function() {
                this.setLayout(zebkit.layout.FlowLayout.newInstancea(arguments));
                return this;
            };

            /**
             * Set stack layout shortcut method
             * @param {Integer} [gap] a gap
             * @method setStackLayout
             * @chainable
             */
            this.setStackLayout = function() {
                this.setLayout(zebkit.layout.StackLayout.newInstancea(arguments));
                return this;
            };

            /**
             * Set list layout shortcut method
             * @param {String} [ax] horizontal list item alignment:
             *
             *    "left"
             *    "right"
             *    "center"
             *    "stretch"
             *
             * @param {Integer} [gap] a space in pixels between laid out components
             * @method setListLayout
             * @chainable
             */
            this.setListLayout = function() {
                this.setLayout(zebkit.layout.ListLayout.newInstancea(arguments));
                return this;
            };

            /**
             * Set raster layout shortcut method
             * @param {Boolean} [usePsSize] flag to add extra rule to set components size to its preferred
             * sizes.
             * @method setRasterLayout
             * @chainable
             */
            this.setRasterLayout = function() {
                this.setLayout(zebkit.layout.RasterLayout.newInstancea(arguments));
                return this;
            };

            /**
             * Set raster layout shortcut method
             * sizes.
             * @method setGrisLayout
             * @chainable
             */
            this.setGridLayout = function() {
                this.setLayout(zebkit.layout.GridLayout.newInstancea(arguments));
                return this;
            };

            /**
             * Load content of the panel UI components from the specified JSON file.
             * @param  {String|Object} JSON URL, JSON string or JS object that describes UI
             * to be loaded into the panel
             * @return {zebkit.DoIt} a runner to track JSON loading
             * @method load
             */
            this.load = function(jsonPath) {
                return new zebkit.Zson(this).then(jsonPath);
            };

            /**
             * Get a children UI component that embeds the given point. The method
             * calculates the component visible area first and than looks for a
             * children component only in this calculated visible area. If no one
             * children component has been found than component return itself as
             * a holder of the given point if one of the following condition is true:
             *
             *   - The component doesn't implement custom "contains(x, y)" method
             *   - The component implements "contains(x, y)" method and for the given point the method return true
             *
             * @param  {Integer} x x coordinate
             * @param  {Integer} y y coordinate
             * @return {zebkit.ui.Panel} a children UI component
             * @method getComponentAt
             */
            this.getComponentAt = function(x, y){
                var r = pkg.$cvp(this, temporary);

                if (r === null ||
                    (x < r.x || y < r.y || x >= r.x + r.width || y >= r.y + r.height))
                {
                    return null;
                }

                if (this.kids.length > 0) {
                    for(var i = this.kids.length; --i >= 0; ){
                        var kid = this.kids[i];
                        kid = kid.getComponentAt(x - kid.x,
                                                 y - kid.y);
                        if (kid !== null) {
                            return kid;
                        }
                    }
                }
                return this.contains === undefined || this.contains(x, y) === true ? this : null;
            };

            /**
             * Shortcut method to invalidating the component and then initiating the component
             * repainting.
             * @method vrp
             */
            this.vrp = function(){
                this.invalidate();

                // extra condition to save few millisecond on repaint() call
                if (this.isVisible === true && this.parent !== null) {
                    this.repaint();
                }
            };

            this.getTop = function() {
                return this.border !== null ? this.top + this.border.getTop()
                                            : this.top;
            };

            this.getLeft = function() {
                return this.border !== null ? this.left + this.border.getLeft()
                                            : this.left;
            };

            this.getBottom = function() {
                return this.border !== null ? this.bottom + this.border.getBottom()
                                            : this.bottom;
            };

            this.getRight  = function() {
                return this.border !== null ? this.right  + this.border.getRight()
                                            : this.right;
            };

            //TODO: the method is not used yet
            this.isInvalidatedByChild = function(c) {
                return true;
            };

            /**
             * The method is implemented to be aware about a children component insertion.
             * @param  {Integer} index an index at that a new children component
             * has been added
             * @param  {Object} constr a layout constraints of an inserted component
             * @param  {zebkit.ui.Panel} l a children component that has been inserted
             * @method kidAdded
             */
            this.kidAdded = function(index, constr, l) {
                COMP_EVENT.source = this;
                COMP_EVENT.constraints = constr;
                COMP_EVENT.kid = l;

                pkg.events.fire("compAdded", COMP_EVENT);

                if (l.width > 0 && l.height > 0) {
                    l.repaint();
                } else {
                    this.repaint(l.x, l.y, 1, 1);
                }
            };

            /**
             * Set the component layout constraints.
             * @param {Object} ctr a constraints whose value depends on layout manager that has been set
             * @method setConstraints
             * @chainable
             */
            this.setConstraints = function(ctr) {
                if (this.constraints !== ctr) {
                    this.constraints = ctr;
                    if (this.parent !== null) {
                        this.vrp();
                    }
                }
                return this;
            };

            /**
             * The method is implemented to be aware about a children component removal.
             * @param  {Integer} i an index of a removed component
             * @param  {zebkit.ui.Panel} l a removed children component
             * @param  {Object} ctr a constraints the kid component had
             * @method kidRemoved
             */
            this.kidRemoved = function(i, l, ctr){
                COMP_EVENT.source = this;
                COMP_EVENT.index  = i;
                COMP_EVENT.kid    = l;
                pkg.events.fire("compRemoved", COMP_EVENT);
                if (l.isVisible === true) {
                    this.repaint(l.x, l.y, l.width, l.height);
                }
            };

            /**
             * The method is implemented to be aware the component location updating
             * @param  {Integer} px a previous x coordinate of the component
             * @param  {Integer} py a previous y coordinate of the component
             * @method relocated
             */
            this.relocated = function(px, py) {
                COMP_EVENT.source = this;
                COMP_EVENT.prevX  = px;
                COMP_EVENT.prevY  = py;
                pkg.events.fire("compMoved", COMP_EVENT);

                var p = this.parent,
                    w = this.width,
                    h = this.height;

                if (p !== null && w > 0 && h > 0) {
                    var x = this.x,
                        y = this.y,
                        nx = x < px ? x : px,
                        ny = y < py ? y : py;

                    if (nx < 0) {
                        nx = 0;
                    }

                    if (ny < 0) {
                        ny = 0;
                    }

                    var w1 = p.width - nx,
                        w2 = w + (x > px ? x - px : px - x),
                        h1 = p.height - ny,
                        h2 = h + (y > py ? y - py : py - y);

                    p.repaint(nx, ny, (w1 < w2 ? w1 : w2),
                                      (h1 < h2 ? h1 : h2));
                }
            };

            /**
             * The method is implemented to be aware the component size updating
             * @param  {Integer} pw a previous width of the component
             * @param  {Integer} ph a previous height of the component
             * @method resized
             */
            this.resized = function(pw,ph) {
                COMP_EVENT.source = this;
                COMP_EVENT.prevWidth  = pw;
                COMP_EVENT.prevHeight = ph;
                pkg.events.fire("compSized", COMP_EVENT);

                if (this.parent !== null) {
                    this.parent.repaint(this.x, this.y,
                                        ((this.width  > pw) ? this.width  : pw),
                                        ((this.height > ph) ? this.height : ph));
                }
            };

            /**
             * Checks if the component has a focus
             * @return {Boolean} true if the component has focus
             * @method hasFocus
             */
            this.hasFocus = function(){
                return pkg.focusManager.hasFocus(this);
            };

            /**
             * Force the given component to catch focus if the component is focusable.
             * @method requestFocus
             */
            this.requestFocus = function(){
                pkg.focusManager.requestFocus(this);
            };

            /**
             * Force the given component to catch focus in the given timeout.
             * @param {Integer} [timeout] a timeout in milliseconds. The default value is 50
             * milliseconds
             * @method requestFocusIn
             */
            this.requestFocusIn = function(timeout) {
                if (arguments.length === 0) {
                    timeout = 50;
                }

                var $this = this;
                zebkit.util.tasksSet.runOnce(function () {
                    $this.requestFocus();
                }, timeout);
            };

            /**
             * Set the UI component visibility
             * @param  {Boolean} b a visibility state
             * @method setVisible
             * @chainable
             */
            this.setVisible = function (b) {
                if (this.isVisible !== b) {
                    this.isVisible = b;
                    this.invalidate();

                    COMP_EVENT.source = this;
                    pkg.events.fire("compShown", COMP_EVENT);

                    if (this.parent !== null) {
                        if (b) {
                            this.repaint();
                        } else {
                            this.parent.repaint(this.x, this.y, this.width, this.height);
                        }
                    }
                }
                return this;
            };

            /**
             *  Set the UI component enabled state. Using this property
             *  an UI component can be excluded from getting input events
             *  @param  {Boolean} b a enabled state
             *  @method setEnabled
             *  @chainable
             */
            this.setEnabled = function (b){
                if (this.isEnabled !== b){
                    this.isEnabled = b;

                    COMP_EVENT.source = this;
                    pkg.events.fire("compEnabled", COMP_EVENT);
                    if (this.kids.length > 0) {
                        for(var i = 0;i < this.kids.length; i++) {
                            this.kids[i].setEnabled(b);
                        }
                    }
                    this.repaint();
                }
                return this;
            };

            /**
             * Set the UI component top, right, left, bottom paddings to the same given value
             * @param  {Integer} v the value that will be set as top, right, left, bottom UI
             * component paddings
             * @method setPadding
             * @chainable
             */

            /**
             * Set UI component top, left, bottom, right paddings. The paddings are
             * gaps between component border and painted area.
             * @param  {Integer} top a top padding
             * @param  {Integer} left a left padding
             * @param  {Integer} bottom a bottom padding
             * @param  {Integer} right a right padding
             * @method setPadding
             * @chainable
             */
            this.setPadding = function (top,left,bottom,right){
                if (arguments.length === 1) {
                    left = bottom = right = top;
                }

                if (this.top    !== top    || this.left  !== left  ||
                    this.bottom !== bottom || this.right !== right   )
                {
                    this.top = top;
                    this.left = left;
                    this.bottom = bottom;
                    this.right = right;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set top padding
             * @param {Integer} top a top padding
             * @method  setTopPadding
             * @chainable
             */
            this.setTopPadding = function(top) {
                if (this.top !== top) {
                    this.top = top;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set left padding
             * @param {Integer} left a left padding
             * @method  setLeftPadding
             * @chainable
             */
            this.setLeftPadding = function(left) {
                if (this.left !== left) {
                    this.left = left;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set bottom padding
             * @param {Integer} bottom a bottom padding
             * @method  setBottomPadding
             * @chainable
             */
            this.setBottomPadding = function(bottom) {
                if (this.bottom !== bottom) {
                    this.bottom = bottom;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set right padding
             * @param {Integer} right a right padding
             * @method  setRightPadding
             * @chainable
             */
            this.setRightPadding = function(right) {
                if (this.right !== right) {
                    this.right = right;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set the border view
             * @param  {zebkit.draw.View|Function|String} [v] a border view or border "paint(g,x,y,w,h,c)"
             * rendering function or one of predefined border name: "plain", "sunken", "raised", "etched".
             * If no argument has been passed the method tries to set "plain" as the component border.
             * @method setBorder
             * @example
             *
             *      var pan = new zebkit.ui.Panel();
             *
             *      // set round border
             *      pan.setBorder(zebkit.draw.RoundBorder("red"));
             *
             *      ...
             *      // set one of predefined border
             *      pan.setBorder("plain");
             *
             * @chainable
             */
            this.setBorder = function (v) {
                if (arguments.length === 0) {
                    v = "plain";
                }

                var old = this.border;
                v = zebkit.draw.$view(v);
                if (v != old){
                    this.border = v;
                    this.notifyRender(old, v);

                    if ( old === null || v === null       ||
                         old.getTop()    !== v.getTop()    ||
                         old.getLeft()   !== v.getLeft()   ||
                         old.getBottom() !== v.getBottom() ||
                         old.getRight()  !== v.getRight()     )
                    {
                        this.invalidate();
                    }

                    if (v !== null && v.activate !== undefined) {
                        v.activate(this.hasFocus() ?  "focuson": "focusoff", this);
                    }

                    this.repaint();
                }
                return this;
            };

            /**
             * Set the background. Background can be a color string or a zebkit.draw.View class
             * instance, or a function(g,x,y,w,h,c) that paints the background:
             *
             *     // set background color
             *     comp.setBackground("red");
             *
             *     // set a picture as a component background
             *     comp.setBackground(new zebkit.draw.Picture(...));
             *
             *     // set a custom rendered background
             *     comp.setBackground(function(g,x,y,w,h,target) {
             *         // paint a component background here
             *         g.setColor("blue");
             *         g.fillRect(x,y,w,h);
             *         g.drawLine(...);
             *         ...
             *     });
             *
             *
             * @param  {String|zebkit.draw.View|Function} v a background view, color or
             * background "paint(g,x,y,w,h,c)" rendering function.
             * @method setBackground
             * @chainable
             */
            this.setBackground = function(v) {
                var old = this.bg;
                v = zebkit.draw.$view(v);
                if (v !== old) {
                    this.bg = v;
                    this.notifyRender(old, v);
                    this.repaint();
                }
                return this;
            };

            /**
             * Add the given children component or number of components to the given panel.
             * @protected
             * @param {zebkit.ui.Panel|Array|Object} a children component of number of
             * components to be added. The parameter can be:
             *
             *   - Component
             *   - Array of components
             *   - Dictionary object where every element is a component to be added and the key of
             *     the component is stored in the dictionary is considered as the component constraints
             *
             * @method setKids
             * @chainable
             */
            this.setKids = function(a) {
                if (arguments.length === 1 && zebkit.instanceOf(a, pkg.Panel)) {
                   this.add(a);
                } else {
                    var i = 0;

                    // if components list passed as number of arguments
                    if (arguments.length > 1) {
                        for(i = 0; i < arguments.length; i++) {
                            var kid = arguments[i];
                            if (kid !== null) {
                                this.add(kid.$new !== undefined ? kid.$new() : kid);
                            }
                        }
                    } else {
                        if (Array.isArray(a)) {
                            for(i = 0; i < a.length; i++) {
                                if (a[i] !== null) {
                                    this.add(a[i]);
                                }
                            }
                        } else {
                            var kids = a;
                            for(var k in kids) {
                                if (kids.hasOwnProperty(k)) {
                                    this.add(k, kids[k]);
                                }
                            }
                        }
                    }
                }
                return this;
            };

            /**
             * The method is called whenever the UI component gets or looses focus
             * @method focused
             * @protected
             */
            this.focused = function() {
                // extents of activate method indicates it is
                if (this.border !== null && this.border.activate !== undefined) {
                    var id = this.hasFocus() ? "focuson" : "focusoff" ;
                    if (this.border.views[id] !== undefined) {
                        this.border.activate(id, this);
                        this.repaint();
                    }
                }

                // TODO: think if the background has to be focus dependent
                // if (this.bg !== null && this.bg.activate !== undefined) {
                //     var id = this.hasFocus() ? "focuson" : "focusoff" ;
                //     if (this.bg.views[id]) {
                //         this.bg.activate(id);
                //         this.repaint();
                //     }
                // }
            };

            /**
             * Remove all children components
             * @method removeAll
             * @chainable
             */
            this.removeAll = function (){
                if (this.kids.length > 0){
                    var size = this.kids.length, mx1 = Number.MAX_VALUE, my1 = mx1, mx2 = 0, my2 = 0;
                    for(; size > 0; size--){
                        var child = this.kids[size - 1];
                        if (child.isVisible === true){
                            var xx = child.x, yy = child.y;
                            mx1 = mx1 < xx ? mx1 : xx;
                            my1 = my1 < yy ? my1 : yy;
                            mx2 = Math.max(mx2, xx + child.width);
                            my2 = Math.max(my2, yy + child.height);
                        }
                        this.removeAt(size - 1);
                    }
                    this.repaint(mx1, my1, mx2 - mx1, my2 - my1);
                }
                return this;
            };

            /**
             * Bring the UI component to front
             * @method toFront
             * @chainable
             */
            this.toFront = function(){
                if (this.parent !== null && this.parent.kids[this.parent.kids.length-1] !== this){
                    var p = this.parent;
                    p.kids.splice(p.indexOf(this), 1);
                    p.kids[p.kids.length] = this;
                    p.vrp();
                }
                return this;
            };

            /**
             * Send the UI component to back
             * @method toBack
             * @chainable
             */
            this.toBack = function(){
                if (this.parent !== null && this.parent.kids[0] !== this){
                    var p = this.parent;
                    p.kids.splice(p.indexOf(this), 1);
                    p.kids.unshift(this);
                    p.vrp();
                }
                return this;
            };

            /**
             * Set the UI component size to its preferred size
             * @chainable
             * @method toPreferredSize
             */
            this.toPreferredSize = function() {
                var ps = this.getPreferredSize();
                this.setSize(ps.width, ps.height);
                return this;
            };

            /**
             * Set the UI component height to its preferred height
             * @method toPreferredHeight
             * @chainable
             */
            this.toPreferredHeight = function() {
                var ps = this.getPreferredSize();
                this.setSize(this.width, ps.height);
                return this;
            };

            /**
             * Set the UI component width to its preferred width
             * @method toPreferredWidth
             * @chainable
             */
            this.toPreferredWidth = function() {
                var ps = this.getPreferredSize();
                this.setSize(ps.width, this.height);
                return this;
            };

            /**
             * Build zebkit.draw.View that represents the UI component
             * @return {zebkit.draw.View} a view of the component
             * @param {zebkit.ui.Panel} target a target component
             * @method toView
             */
            this.toView = function(target) {
                return new pkg.CompRender(this);
            };

            /**
             * Paint the given view with he specified horizontal and vertical
             * alignments.
             * @param  {CanvasRenderingContext2D} g a 2D context
             * @param  {String} ax a horizontal alignment ("left", "right", "center")
             * @param  {String} ay a vertical alignment ("top", "center", "bottom")
             * @param  {zebkit.draw.View} v a view
             * @chainable
             * @method paintViewAt
             */
            this.paintViewAt = function(g, ax, ay, v) {
                var x  = this.getLeft(),
                    y  = this.getTop(),
                    ps = v.getPreferredSize();

                if (ax === "center") {
                    x = Math.floor((this.width - ps.width)/2);
                } else if (ax === "right") {
                    x = this.width - this.getRight() - ps.width;
                }

                if (ay === "center") {
                    y = Math.floor((this.height - ps.height)/2);
                } else if (ay === "bottom") {
                    y = this.height - this.getBottom() - ps.height;
                }

                v.paint(g, x, y, ps.width, ps.height, this);
                return this;
            };

            this[''] = function(l) {
                // TODO:
                // !!! dirty trick to call super, for the sake of few milliseconds back
                //this.$super();
                if (this.kids === undefined) {
                    this.kids = [];
                }

                if (this.layout === null) {
                    this.layout = this;
                }

                if (this.clazz.inheritProperties === true) {
                    // instead of recursion collect stack in array than go through it
                    var hierarchy = [],
                        props     = {},
                        pp        = this.clazz;

                    // collect clazz hierarchy
                    while (pp.$parent !== null && pp.inheritProperties === true) {
                        pp = pp.$parent;
                        hierarchy[hierarchy.length] = pp;
                    }

                    // collect properties taking in account possible  overwriting
                    var b = false;
                    for (var i = hierarchy.length; i >= 0; i--) {
                        pp = hierarchy[i];
                        for (var k in pp) {
                            if (this.clazz[k] === undefined && props[k] === undefined) {
                                props[k] = pp[k];
                                if (b === false) {
                                    b = true;
                                }
                            }
                        }
                    }

                    if (b)  {
                        this.properties(props);
                    }
                }

                this.properties(this.clazz);

                if (arguments.length > 0) {
                    if (l === undefined || l === null) {
                        throw new Error("Undefined arguments. Properties set (Object) or layout manager instance is expected as Panel constructor input");
                    }

                    if (l.constructor === Object) {  // TODO: not 100% method to detect "{}" dictionary
                        this.properties(l);
                    } else {
                        this.setLayout(l);
                    }
                }
            };
        }
    ]);

    /**
     * Root layer interface.
     * @class zebkit.ui.RootLayerMix
     * @constructor
     * @interface zebkit.ui.RootLayerMix
     */
    pkg.RootLayerMix = zebkit.Interface([
        function $clazz() {
            /**
             * Root layer id.
             * @attribute id
             * @type {String}
             * @readOnly
             * @default "root"
             */
            this.id = "root";
        },

        function $prototype() {
            this.getFocusRoot = function() {
                return this;
            };
        }
    ]);

    /**
     * Root layer panel implementation basing on zebkit.ui.Panel component.
     * @class zebkit.ui.RootLayer
     * @extends zebkit.ui.Panel
     * @uses zebkit.ui.RootLayerMix
     */
    pkg.RootLayer = Class(pkg.Panel, pkg.RootLayerMix, []);


    /**
     * Class that holds mouse cursor constant.
     * @constructor
     * @class zebkit.ui.Cursor
     */
    pkg.Cursor = {
        /**
         * "default"
         * @const DEFAULT
         * @type {String}
         */
        DEFAULT: "default",

        /**
         * "move"
         * @const MOVE
         * @type {String}
         */
        MOVE: "move",

        /**
         * "wait"
         * @const WAIT
         * @type {String}
         */
        WAIT: "wait",

        /**
         * "text"
         * @const TEXT
         * @type {String}
         */
        TEXT: "text",

        /**
         * "pointer"
         * @const HAND
         * @type {String}
         */
        HAND: "pointer",

        /**
         * "ne-resize"
         * @const NE_RESIZE
         * @type {String}
         */
        NE_RESIZE: "ne-resize",

        /**
         * "sw-resize"
         * @const SW_RESIZE
         * @type {String}
         */
        SW_RESIZE: "sw-resize",

        /**
         * "se-resize"
         * @const SE_RESIZE
         * @type {String}
         */
        SE_RESIZE: "se-resize",

        /**
         * "nw-resize"
         * @const NW_RESIZE
         * @type {String}
         */
        NW_RESIZE: "nw-resize",

        /**
         * "s-resize"
         * @const S_RESIZE
         * @type {String}
         */
        S_RESIZE: "s-resize",

        /**
         * "w-resize"
         * @const W_RESIZE
         * @type {String}
         */
        W_RESIZE: "w-resize",

        /**
         * "n-resize"
         * @const N_RESIZE
         * @type {String}
         */
        N_RESIZE: "n-resize",

        /**
         * "e-resize"
         * @const E_RESIZE
         * @type {String}
         */
        E_RESIZE: "e-resize",

        /**
         * "col-resize"
         * @const COL_RESIZE
         * @type {String}
         */
        COL_RESIZE: "col-resize",

        /**
         * "help"
         * @const HELP
         * @type {String}
         */
        HELP: "help"
    };

    /**
     *  UI component render class. Renders the given target UI component
     *  on the given surface using the specified 2D context
     *  @param {zebkit.layout.Layoutable} [target] an UI component to be rendered
     *  @class zebkit.ui.CompRender
     *  @constructor
     *  @extends zebkit.draw.Render
     */
    pkg.CompRender = Class(zebkit.draw.Render, [
        function $prototype() {
            /**
             * Get preferred size of the render. The method doesn't calculates
             * preferred size it simply calls the target component "getPreferredSize"
             * method.
             * @method getPreferredSize
             * @return {Object} a preferred size
             *
             *      {width:<Integer>, height: <Integer>}
             */
            this.getPreferredSize = function(){
                return this.target === null || this.target.isVisible === false ? { width:0, height:0 }
                                                                               : this.target.getPreferredSize();
            };

            this.paint = function(g,x,y,w,h,d){
                var c = this.target;
                if (c !== null && c.isVisible) {
                    var prevW  = -1,
                        prevH  = 0,
                        parent = null;

                    if (w !== c.width || h !== c.height) {
                        if (c.getCanvas() !== null) {
                            parent = c.parent;
                            c.parent = null;
                        }

                        prevW = c.width;
                        prevH = c.height;
                        c.setSize(w, h);
                    }

                    // validate should be done here since setSize can be called
                    // above
                    c.validate();
                    g.translate(x, y);

                    try {
                        c.paintComponent(g);
                    } catch(e) {
                        if (parent !== null) {
                            c.parent = parent;
                        }
                        g.translate(-x, -y);
                        throw e;
                    }
                    g.translate(-x, -y);

                    if (prevW >= 0){
                        c.setSize(prevW, prevH);
                        if (parent !== null) {
                            c.parent = parent;
                        }
                        c.validate();
                    }
                }
            };
        }
    ]);

    /**
     * Shortcut to create a UI component by the given description. Depending on the description type
     * the following components are created:
     *
     *    - **String**
     *       - String encoded as "[x] Text" or "[] Text" will considered as checkbox component
     *       - String encoded as "@(image_path:WxH) Text" will considered as image or image label component
     *       - All other strings will be considered as label component
     *    - **Array** zebkit.ui.Combobox
     *    - **2D Array** zebkit.ui.grid.Grid
     *    - **Image** will be embedded with zebkit.ui.ImagePan component
     *    - **zebkit.ui.View instance** will be embedded with zebkit.ui.ViewPan component
     *    - **zebkit.ui.Panel instance** will be returned as is
     *
     * @method $component
     * @protected
     * @for  zebkit.ui
     * @param  {Object} desc a description
     * @return {zebkit.ui.Panel}  a created UI component
     */
    pkg.$component = function(desc, instance) {
        var hasInstance = arguments.length > 1;

        if (zebkit.isString(desc)) {
            //  [x] Text
            //  @(image-path:WxH) label
            //  %{<json> json-path}  !!! not supported
            //  { "zebkit.ui.Panel" }

            var m   = desc.match(/^(\[[x ]?\])/),
                txt = null;

            if (m !== null) {
                txt = desc.substring(m[1].length);
                var ch  = hasInstance && instance.clazz.Checkbox !== undefined ? new instance.clazz.Checkbox(txt)
                                                                               : new pkg.Checkbox(txt);
                ch.setValue(m[1].indexOf('x') > 0);
                return ch;
            } else {
                m = desc.match(/^@\((.*)\)(\:[0-9]+x[0-9]+)?/);
                if (m !== null) {
                    var path = m[1];

                    txt  = desc.substring(path.length + 3 + (m[2] !== undefined ? m[2].length : 0)).trim();

                    var img = hasInstance && instance.clazz.ImagePan !== undefined ? new instance.clazz.ImagePan(path)
                                                                                   : new pkg.ImagePan(path);

                    if (m[2] !== undefined) {
                        var s = m[2].substring(1).split('x'),
                            w = parseInt(s[0], 10),
                            h = parseInt(s[1], 10);

                        img.setPreferredSize(w, h);
                    }

                    if (txt.length === 0) {
                        return img;
                    } else {
                        return hasInstance && instance.clazz.ImageLabel !== undefined ? new instance.clazz.ImageLabel(txt, img)
                                                                                      : new pkg.ImageLabel(txt, img);
                    }
                } else {
                    return hasInstance && instance.clazz.Label !== undefined ? new instance.clazz.Label(desc)
                                                                             : new pkg.Label(desc);
                }
            }
        } else if (Array.isArray(desc)) {
            if (desc.length > 0 && Array.isArray(desc[0])) {
                var model = new zebkit.data.Matrix(desc.length, desc[0].length);
                for(var row = 0; row < model.rows; row++) {
                    for(var col = 0; col < model.cols; col++) {
                        model.put(row, col, desc[row][col]);
                    }
                }
                return new pkg.grid.Grid(model);
            } else {
                var clz = hasInstance && instance.clazz.Combo !== undefined ? instance.clazz.Combo
                                                                            : pkg.Combo,
                    combo = new clz(new clz.CompList(true)),
                    selectedIndex = -1;

                for(var i = 0; i < desc.length; i++) {
                    var ss = desc[i];
                    if (zebkit.isString(ss)) {
                        if (selectedIndex === -1 && ss.length > 1 && ss[0] === '*') {
                            selectedIndex = i;
                            desc[i] = ss.substring(1);
                        }
                    }
                    combo.list.add(pkg.$component(desc[i], combo.list));
                }

                combo.select(selectedIndex);
                return combo;
            }
        } else if (desc instanceof Image) {
            return hasInstance && instance.clazz.ImagePan !== undefined ? new instance.clazz.ImagePan(desc)
                                                                        : new pkg.ImagePan(desc);
        } else if (zebkit.instanceOf(desc, zebkit.draw.View)) {
            var v = hasInstance && instance.clazz.ViewPan !== undefined ? new instance.clazz.ViewPan()
                                                                        : new pkg.ViewPan();
            v.setView(desc);
            return v;
        } else if (zebkit.instanceOf(desc, pkg.Panel)) {
            return desc;
        } else {
            throw new Error("Invalid component description '" +  desc + "'");
        }
    };

    /**
     * Named views holder interface.
     * @class  zebkit.ui.HostDecorativeViews
     * @interface  zebkit.ui.HostDecorativeViews
     */
    pkg.HostDecorativeViews = zebkit.Interface([
        function $prototype() {
            /**
             * Set views set.
             * @param {Object} v named views set.
             * @method setViews
             * @chainable
             */
            this.setViews = function(v){
                if (this.views === undefined) {
                    this.views = {};
                }

                var b = false;
                for(var k in v) {
                    if (v.hasOwnProperty(k)) {
                        var nv = zebkit.draw.$view(v[k]);
                        if (this.views[k] !== nv) {
                            this.views[k] = nv;
                            b = true;
                        }
                    }
                }

                if (b === true) {
                    this.vrp();
                }

                return this;
            };
        }
    ]);

    /**
     *  UI component to keep and render the given "zebkit.draw.View" class
     *  instance. The target view defines the component preferred size
     *  and the component view.
     *  @class zebkit.ui.ViewPan
     *  @constructor
     *  @extends zebkit.ui.Panel
     */
    pkg.ViewPan = Class(pkg.Panel, [
        function $prototype() {
            /**
             * Reference to a view that the component visualize
             * @attribute view
             * @type {zebkit.draw.View}
             * @default null
             * @readOnly
             */
            this.view = null;

            this.paint = function (g){
                if (this.view !== null){
                    var l = this.getLeft(),
                        t = this.getTop();

                    this.view.paint(g, l, t, this.width  - l - this.getRight(),
                                             this.height - t - this.getBottom(), this);
                }
            };

            /**
             * Set the target view to be wrapped with the UI component
             * @param  {zebkit.draw.View|Function} v a view or a rendering
             * view "paint(g,x,y,w,h,c)" function
             * @method setView
             * @chainable
             */
            this.setView = function(v) {
                var old = this.view;
                v = zebkit.draw.$view(v);

                if (v !== old) {
                    this.view = v;
                    this.notifyRender(old, v);
                    this.vrp();
                }

                return this;
            };

            /**
             * Override the parent method to calculate preferred size basing on a target view.
             * @param  {zebkit.ui.Panel} t a target container
             * @return {Object} return a target view preferred size if it is defined.
             * The returned structure is the following:
             *
             *     { width: {Integer}, height:{Integer} }
             *
             *  @method  calcPreferredSize
             */
            this.calcPreferredSize = function(t) {
                return this.view !== null ? this.view.getPreferredSize() : { width:0, height:0 };
            };
        }
    ]);

    /**
     *  Image panel UI component class. The component renders an image.
     *  @param {String|Image} [img] a path or direct reference to an image object.
     *  If the passed parameter is string it considered as path to an image.
     *  In this case the image will be loaded using the passed path.
     *  @param {Integer} [w] a preferred with of the image
     *  @param {Integer} [h] a preferred height of the image
     *  @class zebkit.ui.ImagePan
     *  @constructor
     *  @extends zebkit.ui.ViewPan
     */
    pkg.ImagePan = Class(pkg.ViewPan, [
        function(img, w, h) {
            this.setImage(arguments.length > 0 ? img : null);
            this.$super();
            if (arguments.length > 1) {
                this.setPreferredSize(w, arguments < 3 ? w : h);
            }
        },

        function $prototype() {
            this.$runner = null;

            /**
             * Set image to be rendered in the UI component
             * @method setImage
             * @param {String|Image|zebkit.draw.Picture} img a path or direct reference to an
             * image or zebkit.draw.Picture render.
             * If the passed parameter is string it considered as path to an image.
             * In this case the image will be loaded using the passed path
             * @chainable
             */
            this.setImage = function(img) {
                var $this = this;

                if (img !== null) {
                    var isPic = zebkit.instanceOf(img, zebkit.draw.Picture);
                    this.setView(isPic ? img : null);

                    this.$runner = zebkit.image(isPic ? img.target : img);
                    this.$runner.then(function(img) {
                        $this.$runner = null;

                        if (isPic === false) {
                            $this.setView(new zebkit.draw.Picture(img));
                        }

                        $this.vrp();

                        if ($this.imageLoaded !== undefined) {
                            $this.imageLoaded(img);
                        }

                        // fire imageLoaded event to children
                        for(var t = $this.parent; t !== null; t = t.parent){
                            if (t.childImageLoaded !== undefined) {
                                t.childImageLoaded(img);
                            }
                        }
                    }).catch(function(e) {
                        console.log(img);
                        zebkit.dumpError(e);
                        $this.$runner = null;
                        $this.setView(null);
                    });
                } else {
                    if (this.$runner === null) {
                        this.setView(null);
                    } else {
                        this.$runner.then(function() {
                            $this.setView(null);
                        });
                    }
                }
                return this;
            };
        }
    ]);

    /**
     * Line UI component class. Draw series of vertical or horizontal lines of using
     * the given line width and color. Vertical or horizontal line rendering s selected
     * depending on the line component size: if height is greater than width than vertical
     * line will be rendered.
     * @constructor
     * @param {String} [colors]* line colors
     * @class zebkit.ui.Line
     * @extends zebkit.ui.Panel
     */
    pkg.Line = Class(pkg.Panel, [
        function() {
            /**
             * Line colors
             * @attribute colors
             * @type {Array}
             * @readOnly
             * @default [ "gray" ]
             */
            this.$super();

            if (arguments.length > 0) {
                this.setColors.apply(this, arguments);
            }
        },

        function $prototype() {
            /**
             * Line colors set.
             * @attribute colors
             * @type {Array}
             * @readOnly
             * @default [ "gray" ]
             */
            this.colors = [ "gray" ];

            /**
             * Line width
             * @attribute lineWidth
             * @type {Integer}
             * @default 1
             */
            this.lineWidth = 1;

            /**
             * Line direction attribute. Can be "vertical" or "horizontal" or null value.
             * @attribute direction
             * @type {String}
             * @default null
             */
            this.direction = null;

            /**
             * Set line color.
             * @param {String} c a color
             * @method  setColor
             * @chainable
             * @readOnly
             */
            this.setColor = function(c) {
                this.setColors(c);
                return this;
            };

            /**
             * Set set of colors to be used to paint the line. Number of colors defines the number of
             * lines to be painted.
             * @param {String} colors* colors
             * @method setLineColors
             * @chainable
             */
            this.setColors = function() {
                this.colors = (arguments.length === 1) ? (Array.isArray(arguments[0]) ? arguments[0].slice(0)
                                                                                      : [ arguments[0] ] )
                                                       : Array.prototype.slice.call(arguments);
                this.repaint();
                return this;
            };

            /**
             * Set the given line direction.
             * @param {String} d a line direction. Can be "vertical" or "horizontal" or null value.
             * null means auto detected direction.
             * @method setDirection
             */
            this.setDirection = function(d) {
                if (d !== this.direction) {
                    this.direction = d;
                    this.vrp();
                }
                return this;
            };

            this.paint = function(g) {
                var isHor  = this.direction === null ? this.width > this.height
                                                     : this.direction === "horizontal",
                    left   = this.getLeft(),
                    right  = this.getRight(),
                    top    = this.getTop(),
                    bottom = this.getBottom(),
                    xy     = isHor ? top : left;

                for(var i = 0; i < this.colors.length; i++) {
                    if (this.colors[i] !== null) {
                        g.setColor(this.colors[i]);
                        if (isHor === true) {
                            g.drawLine(this.left, xy, this.width - right - left, xy, this.lineWidth);
                        } else {
                            g.drawLine(xy, top, xy, this.height - top - bottom, this.lineWidth);
                        }
                    }
                    xy += this.lineWidth;
                }
            };

            this.calcPreferredSize = function(target) {
                var s = this.colors.length * this.lineWidth;
                return { width: s, height:s};
            };
        }
    ]);

    /**
     * Label UI component class. The label can be used to visualize simple string or multi lines text or
     * the given text render implementation:
     *
     *       // render simple string
     *       var l = new zebkit.ui.Label("Simple string");
     *
     *       // render multi lines text
     *       var l = new zebkit.ui.Label(new zebkit.data.Text("Multiline\ntext"));
     *
     *       // render password text
     *       var l = new zebkit.ui.Label(new zebkit.draw.PasswordText("password"));
     *
     * @param  {String|zebkit.data.TextModel|zebkit.draw.TextRender} [r] a text to be shown with the label.
     * You can pass a simple string or an instance of a text model or an instance of text render as the
     * text value.
     * @class zebkit.ui.Label
     * @constructor
     * @extends zebkit.ui.ViewPan
     */
    pkg.Label = Class(pkg.ViewPan, [
        function (r) {
            if (arguments.length === 0) {
                this.setView(new zebkit.draw.StringRender(""));
            } else {
                // test if input string is string
                if (typeof r === "string" || r.constructor === String) {
                    this.setView(r.length === 0 || r.indexOf('\n') >= 0 ? new zebkit.draw.TextRender(new zebkit.data.Text(r))
                                                                        : new zebkit.draw.StringRender(r));
                } else if (r.clazz         !== undefined &&
                           r.getTextLength !== undefined &&   // a bit faster than instanceOf checking if
                           r.getLines      !== undefined   )  // test if this is an instance of zebkit.data.TextModel
                {
                    this.setView(new zebkit.draw.TextRender(r));
                } else {
                    this.setView(r);
                }
            }
            this.$super();
        },

        function $prototype() {
            /**
             * Get the label text
             * @return {String} a zebkit label text
             * @method getValue
             */
            this.getValue = function() {
                return this.view.toString();
            };

            /**
             * Set the text field text model
             * @param  {zebkit.data.TextModel|String} m a text model to be set
             * @method setModel
             * @chainable
             */
            this.setModel = function(m) {
                this.setView(zebkit.isString(m) ? new zebkit.draw.StringRender(m)
                                                : new zebkit.draw.TextRender(m));
                return this;
            };

            /**
             * Get a text model
             * @return {zebkit.data.TextModel} a text model
             * @method getModel
             */
            this.getModel = function() {
                return this.view !== null ? this.view.target : null;
            };

            /**
             * Get the label text color
             * @return {String} a zebkit label color
             * @method getColor
             */
            this.getColor = function (){
                return this.view.color;
            };

            /**
             * Get the label text font
             * @return {zebkit.Font} a zebkit label font
             * @method getFont
             */
            this.getFont = function (){
                return this.view.font;
            };

            /**
             * Set the label text value
             * @param  {String} s a new label text
             * @method setValue
             * @chainable
             */
            this.setValue = function(s){
                if (s === null) {
                    s = "";
                }

                var old = this.view.toString();
                if (old !== s) {
                    this.view.setValue(s);
                    this.repaint();
                }

                return this;
            };

            /**
             * Set the label text color
             * @param  {String} c a text color
             * @method setColor
             * @chainable
             */
            this.setColor = function(c) {
                var old = this.view.color;
                if (old !== c) {
                    this.view.setColor(c);
                    this.repaint();
                }
                return this;
            };

            /**
             * Set the label text font
             * @param  {zebkit.Font} f a text font
             * @method setFont
             * @chainable
             */
            this.setFont = function(f) {
                var old = this.view.font;
                this.view.setFont.apply(this.view, arguments);
                if (old != this.view.font) {
                    this.repaint();
                }
                return this;
            };
        }
    ]);


    /**
     * Shortcut class to render bold text in Label
     * @param {String|zebkit.draw.TextRender|zebkit.data.TextModel} [t] a text string,
     * text model or text render instance
     * @constructor
     * @class zebkit.ui.BoldLabel
     * @extends zebkit.ui.Label
     */
    pkg.BoldLabel = Class(pkg.Label, []);


    /**
     * Image label UI component. This is UI container that consists from an image
     * component and an label component.Image is located at the left size of text.
     * @param {Image|String} [img] an image or path to the image
     * @param {String|zebkit.draw.TextRender|zebkit.data.TextModel} [txt] a text string,
     * text model or text render instance
     * @param {Integer} [w] an image preferred width
     * @param {Integer} [h] an image preferred height
     * @constructor
     * @class zebkit.ui.ImageLabel
     * @extends zebkit.ui.Panel
     */
    pkg.ImageLabel = Class(pkg.Panel, [
        function(txt, path, w, h) {
            var img = null,
                lab = null;

            if (arguments.length > 0) {
                lab = zebkit.instanceOf(txt, pkg.Panel) ? txt
                                                        : new this.clazz.Label(txt);
                if (arguments.length > 1) {
                    img = zebkit.instanceOf(path, pkg.ImagePan) ? path
                                                                : new this.clazz.ImagePan(path);
                    if (arguments.length > 2) {
                        img.setPreferredSize(w, (arguments.length > 3 ? h : w));
                    }
                }
            }

            // TODO: this is copy paste of Panel constructor to initialize fields that has to
            // be used for adding child components. these components have to be added before
            // properties() call. a bit dirty trick
            if (this.kids === undefined) {
                this.kids = [];
            }

            this.layout = new zebkit.layout.FlowLayout("left", "center", "horizontal", 6);

            // add before panel constructor thanks to copy pasted code above
            if (img !== null) {
                this.add(img);
            }

            if (lab !== null) {
                this.add(lab);
            }

            this.$super();

            lab.setVisible(txt !== null);
        },

        function $clazz() {
            this.ImagePan = Class(pkg.ImagePan, []);
            this.Label    = Class(pkg.Label, []);
        },

        function $prototype() {
            /**
             * Set the specified caption
             * @param {String|zebkit.ui.Label} c a label text or component
             * @method setValue
             * @chainable
             */
            this.setValue = function(c) {
                var lab = this.getLabel();

                if (zebkit.instanceOf(c, pkg.Label)) {
                    var i = -1;
                    if (lab !== null) {
                        i = this.indexOf(lab);
                    }

                    if (i >= 0) {
                        this.setAt(i, c);
                    }
                } else {
                    lab.setValue(c);
                    lab.setVisible(c !== null);
                }

                return this;
            };

            /**
             * Set the specified label image
             * @param {String|Image} p a path to an image of image object
             * @method setImage
             * @chainable
             */
            this.setImage = function(p) {
                var image = this.getImagePan();
                image.setImage(p);
                image.setVisible(p !== null);
                return this;
            };

            /**
             * Get image panel.
             * @return  {zebkit.ui.ImagePan} an image panel.
             * @method getImagePan
             */
            this.getImagePan = function() {
                return this.byPath("/~zebkit.ui.ImagePan");
            };

            /**
             * Get label component.
             * @return  {zebkit.ui.ImagePan} a label component.
             * @method getLabel
             */
            this.getLabel = function(p) {
                return this.byPath("/~zebkit.ui.Label");
            };

            /**
             * Set the caption font
             * @param {zebkit.Font} a font
             * @method setFont
             * @chainable
             */
            this.setFont = function() {
                var lab = this.getLabel();
                if (lab !== null) {
                    lab.setFont.apply(lab, arguments);
                }
                return this;
            };

            /**
             * Set the caption color
             * @param {String} a color
             * @method setColor
             * @chainable
             */
            this.setColor = function (c) {
                var lab = this.getLabel();
                if (lab !== null) {
                    lab.setColor(c);
                }
                return this;
            };

            /**
             * Get caption value
             * @return {zebkit.ui.Panel} a caption value
             * @method getValue
             */
            this.getValue = function () {
                var lab = this.getLabel();
                return lab === null ? null : lab.getValue();
            };

            /**
             * Set the image alignment.
             * @param {String} an alignment. Following values are possible:
             *
             *    - "left"
             *    - "right"
             *    - "top"
             *    - "bottom"
             *
             * @method  setImgAlignment
             * @chainable
             */
            this.setImgAlignment = function(a) {
                var b   = false,
                    img = this.getImagePan(),
                    i   = this.indexOf(img);

                if (a === "top" || a === "bottom") {
                    if (this.layout.direction !== "vertical") {
                        this.layout.direction = "vertical";
                        b = true;
                    }
                } else if (a === "left" || a === "right") {
                    if (this.layout.direction !== "horizontal") {
                        this.layout.direction = "horizontal";
                        b = true;
                    }
                }

                if (this.layout.ax !== "center") {
                    this.layout.ax = "center";
                    b = true;
                }

                if (this.layout.ay !== "center") {
                    this.layout.ay = "center";
                    b = true;
                }

                if ((a === "top" || a === "left") && i !== 0 ) {
                    this.insert(null, 0, this.removeAt(i));
                    b = false;
                } else if ((a === "bottom"  || a === "right") && i !== 1) {
                    this.add(null, this.removeAt(i));
                    b = false;
                }

                if (b) {
                    this.vrp();
                }

                return this;
            };

            /**
             * Set image preferred size.
             * @param {Integer} w a width and height if the second argument has not been specified
             * @param {Integer} [h] a height
             * @method setImgPreferredSize
             * @chainable
             */
            this.setImgPreferredSize = function (w, h) {
                if (arguments.length === 1) {
                    h = w;
                }
                this.getImagePan().setPreferredSize(w, h);
                return this;
            };
        }
    ]);

    /**
     * Progress bar UI component class.
     * @class zebkit.ui.Progress
     * @constructor
     * @param {String} [orient] an orientation of the progress bar. Use
     * "vertical" or "horizontal" as the parameter value
     * @extends zebkit.ui.Panel
     */

    /**
     * Fired when a progress bar value has been updated
     *
     *     progress.on(function(src, oldValue) {
     *         ...
     *     });
     *
     *  @event fired
     *  @param {zebkit.ui.Progress} src a progress bar that triggers
     *  the event
     *  @param {Integer} oldValue a progress bar previous value
     */
    pkg.Progress = Class(pkg.Panel, [
        function(orient) {
            this.$super();
            if (arguments.length > 0) {
                this.setOrientation(orient);
            }
        },

        function $prototype() {
            /**
             * Progress bar value
             * @attribute value
             * @type {Integer}
             * @readOnly
             */
            this.value = 0;

            /**
             * Progress bar element width
             * @attribute barWidth
             * @type {Integer}
             * @readOnly
             * @default 6
             */
             this.barWidth = 6;

            /**
             * Progress bar element height
             * @attribute barHeight
             * @type {Integer}
             * @readOnly
             * @default 6
             */
            this.barHeight = 6;

            /**
             * Gap between bar elements
             * @default 2
             * @attribute gap
             * @type {Integer}
             * @readOnly
             */
            this.gap = 2;

            /**
             * Progress bar maximal value
             * @attribute maxValue
             * @type {Integer}
             * @readOnly
             * @default 20
             */
            this.maxValue = 20;

            /**
             * Bar element view
             * @attribute barView
             * @readOnly
             * @type {String|zebkit.draw.View}
             * @default "blue"
             */
            this.barView = "blue";


            this.titleView = null;

            /**
             * Progress bar orientation
             * @default "horizontal"
             * @attribute orient
             * @type {String}
             * @readOnly
             */
            this.orient = "horizontal";

            this.paint = function(g){
                var left    = this.getLeft(),
                    right   = this.getRight(),
                    top     = this.getTop(),
                    bottom  = this.getBottom(),
                    rs      = (this.orient === "horizontal") ? this.width - left - right
                                                             : this.height - top - bottom,
                    barSize = (this.orient === "horizontal") ? this.barWidth
                                                             : this.barHeight;

                if (rs >= barSize){
                    var vLoc   = Math.floor((rs * this.value) / this.maxValue),
                        x      = left,
                        y      = this.height - bottom,
                        bar    = this.barView,
                        wh     = this.orient === "horizontal" ? this.height - top - bottom
                                                              : this.width - left - right;

                    while (x < (vLoc + left) && this.height - vLoc - bottom < y){
                        if (this.orient === "horizontal"){
                            bar.paint(g, x, top, barSize, wh, this);
                            x += (barSize + this.gap);
                        } else {
                            bar.paint(g, left, y - barSize, wh, barSize, this);
                            y -= (barSize + this.gap);
                        }
                    }

                    if (this.titleView !== null) {
                        var ps = this.barView.getPreferredSize();
                        this.titleView.paint(g, Math.floor((this.width  - ps.width ) / 2),
                                                Math.floor((this.height - ps.height) / 2),
                                                ps.width, ps.height, this);
                    }
                }
            };

            this.calcPreferredSize = function(l) {
                var barSize = (this.orient === "horizontal") ? this.barWidth
                                                             : this.barHeight,
                    v1 = (this.maxValue * barSize) + (this.maxValue - 1) * this.gap,
                    ps = this.barView.getPreferredSize();

                ps = (this.orient === "horizontal") ? {
                                                         width :v1,
                                                         height:(this.barHeight >= 0 ? this.barHeight
                                                                                        : ps.height)
                                                      }
                                                    : {
                                                        width:(this.barWidth >= 0 ? this.barWidth
                                                                                     : ps.width),
                                                        height: v1
                                                      };
                if (this.titleView !== null) {
                    var tp = this.titleView.getPreferredSize();
                    ps.width  = Math.max(ps.width, tp.width);
                    ps.height = Math.max(ps.height, tp.height);
                }
                return ps;
            };

            /**
             * Set the progress bar orientation
             * @param {String} o an orientation: "vertical" or "horizontal"
             * @method setOrientation
             * @chainable
             */
            this.setOrientation = function(o) {
                if (o !== this.orient) {
                    this.orient = zebkit.util.validateValue(o, "horizontal", "vertical");
                    this.vrp();
                }
                return this;
            };

            /**
             * Set maximal integer value the progress bar value can rich
             * @param {Integer} m a maximal value the progress bar value can rich
             * @method setMaxValue
             * @chainable
             */
            this.setMaxValue = function(m) {
                if (m !== this.maxValue) {
                    this.maxValue = m;
                    this.setValue(this.value);
                    this.vrp();
                }
                return this;
            };

            /**
             * Set the current progress bar value
             * @param {Integer} p a progress bar
             * @method setValue
             * @chainable
             */
            this.setValue = function(p) {
                p = p % (this.maxValue + 1);
                if (this.value !== p){
                    var old = this.value;
                    this.value = p;
                    this.fire("fired", [this, old]);
                    this.repaint();
                }
                return this;
            };

            /**
             * Set the given gap between progress bar element elements
             * @param {Integer} g a gap
             * @method setGap
             * @chainable
             */
            this.setGap = function(g) {
                if (this.gap !== g){
                    this.gap = g;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set the progress bar element element view
             * @param {zebkit.draw.View} v a progress bar element view
             * @method setBarView
             * @chainable
             */
            this.setBarView = function(v) {
                if (this.barView != v){
                    this.barView = zebkit.draw.$view(v);
                    this.vrp();
                }
                return this;
            };

            /**
             * Set the progress bar element size
             * @param {Integer} w a element width
             * @param {Integer} h a element height
             * @method setBarSize
             * @chainable
             */
            this.setBarSize = function(w, h) {
                if (w !== this.barWidth && h !== this.barHeight){
                    this.barWidth  = w;
                    this.barHeight = h;
                    this.vrp();
                }
                return this;
            };
        }
    ]).events("fired");


    /**
     * State panel class. The class is UI component that allows to customize
     * the component  face, background and border depending on the component
     * state. Number and names of states the component can have is defined
     * by developers. To bind a view to the specified state use zebkit.draw.ViewSet
     * class. For instance if a component has to support two states : "state1" and
     * "state2" you can do it as following:
     *
     *     // create state component
     *     var p = new zebkit.ui.StatePan();
     *
     *     // define border view that contains views for "state1" and "state2"
     *     p.setBorder({
     *         "state1": new zebkit.draw.Border("red", 1),
     *         "state2": new zebkit.draw.Border("blue", 2)
     *     });
     *
     *     // define background view that contains views for "state1" and "state2"
     *     p.setBackground({
     *         "state1": "yellow",
     *         "state2": "green"
     *     });
     *
     *     // set component state
     *     p.setState("state1");
     *
     * State component children components can listening when the state of the component
     * has been updated by implementing "parentStateUpdated(o,n,id)" method. It gets old
     * state, new state and a view id that is mapped to the new state.  The feature is
     * useful if we are developing a composite components whose children component also
     * should react to a state changing.
     * @class  zebkit.ui.StatePan
     * @constructor
     * @extends zebkit.ui.ViewPan
     */
    pkg.StatePan = Class(pkg.ViewPan, [
        function $prototype() {
            /**
             * Current component state
             * @attribute state
             * @readOnly
             * @default null
             * @type {Object}
             */
            this.state = null;

            /**
             * Set the component state
             * @param {Object} s a state
             * @method  setState
             * @chainable
             */
            this.setState = function(s) {
                if (s !== this.state){
                    var prev = this.state;
                    this.state = s;
                    this.stateUpdated(prev, s);
                }
                return this;
            };

            /**
             * Define the method if the state value has to be
             * somehow converted to a view id. By default the state value
             * itself is used as a view id.
             * @param {Object} s a state to be converted
             * @return {String} a view ID
             * @method toViewId
             */
            this.toViewId = function(st) {
                return st;
            };

            /**
             * Called every time the component state has been updated
             * @param  {Integer} o a previous component state
             * @param  {Integer} n a new component state
             * @method stateUpdated
             */
            this.stateUpdated = function(o, n) {
                var b  = false,
                    id = this.toViewId(n);

                if (id !== null) {
                    for(var i = 0; i < this.kids.length; i++) {
                        var kid = this.kids[i];
                        if (kid.setState !== undefined) {
                            kid.setState(id);
                        }
                    }

                    if (this.border !== null && this.border.activate !== undefined) {
                        b = this.border.activate(id, this) === true || b;
                    }

                    if (this.view !== null && this.view.activate !== undefined) {
                        b = this.view.activate(id, this) === true || b;
                    }

                    if (this.bg !== null && this.bg.activate !== undefined) {
                        b = this.bg.activate(id, this) === true || b;
                    }

                    if (b) {
                        this.repaint();
                    }
                }

                // TODO: code to support potential future state update listener support
                if (this._ !== undefined && this._.stateUpdated !== undefined) {
                    this._.stateUpdated(this, o, n, id);
                }
            };

            /**
             * Refresh state
             * @protected
             * @method syncState
             */
            this.syncState = function() {
                this.stateUpdated(this.state, this.state);
            };
        },

        function setView(v) {
            if (v !== this.view) {
                this.$super(v);

                // check if the method called after constructor execution
                // otherwise sync is not possible
                if (this.kids !== undefined) {
                    this.syncState(this.state, this.state);
                }
            }
            return this;
        },

        function setBorder(v) {
            if (v !== this.border) {
                this.$super(v);
                this.syncState(this.state, this.state);
            }
            return this;
        },

        function setBackground(v) {
            if (v !== this.bg) {
                this.$super(v);
                this.syncState(this.state, this.state);
            }
            return this;
        },

        function setEnabled(b) {
            this.$super(b);
            this.setState(b ? "out" : "disabled");
            return this;
        }
    ]);

    // TODO: probably should be removed
    /**
     * Input events state panel.
     * @class zebkit.ui.EvStatePan
     * @extends zebkit.ui.StatePan
     * @uses zebkit.ui.event.TrackInputEventState
     * @uses zebkit.ui.StatePan
     * @constructor
     */
    pkg.EvStatePan = Class(pkg.StatePan, pkg.event.TrackInputEventState, []);

    /**
     * Interface to add focus marker rendering. Focus marker is drawn either over
     * the component space or around the specified anchor child component.
     * @class  zebkit.ui.DrawFocusMarker
     * @interface  zebkit.ui.DrawFocusMarker
     */
    pkg.DrawFocusMarker = zebkit.Interface([
        function $prototype() {
            /**
             * Component that has to be used as focus indicator anchor
             * @attribute focusComponent
             * @type {zebkit.ui.Panel}
             * @default null
             * @readOnly
             */
            this.focusComponent = null;

            /**
             * Reference to an anchor focus marker component
             * @attribute focusMarkerView
             * @readOnly
             * @type {zebkit.draw.View}
             */
            this.focusMarkerView = null;

            /**
             * Focus marker vertical and horizontal gaps.
             * @attribute focusMarkerGaps
             * @type {Integer}
             * @default 2
             */
            this.focusMarkerGaps = 2;

            this.paintOnTop = function(g) {
                var fc = this.focusComponent;
                if (this.focusMarkerView !== null && fc !== null && this.hasFocus()) {
                    if (fc === this) {
                        this.focusMarkerView.paint(g, this.focusMarkerGaps,
                                                      this.focusMarkerGaps,
                                                      fc.width  - this.focusMarkerGaps * 2,
                                                      fc.height - this.focusMarkerGaps * 2,
                                                      this);
                    } else {
                        this.focusMarkerView.paint(g, fc.x - this.focusMarkerGaps,
                                                      fc.y - this.focusMarkerGaps,
                                                      this.focusMarkerGaps * 2 + fc.width,
                                                      this.focusMarkerGaps * 2 + fc.height,
                                                      this);
                    }
                }
            };

            /**
             * Set the view that has to be rendered as focus marker when the component gains focus.
             * @param  {String|zebkit.draw.View|Function} c a view.
             * The view can be a color or border string code or view
             * or an implementation of zebkit.draw.View "paint(g,x,y,w,h,t)" method.
             * @method setFocusMarkerView
             * @chainable
             */
            this.setFocusMarkerView = function(c) {
                if (c != this.focusMarkerView){
                    this.focusMarkerView = zebkit.draw.$view(c);
                    this.repaint();
                }
                return this;
            };

            /**
             * Says if the component can hold focus or not
             * @param  {Boolean} b true if the component can gain focus
             * @method setCanHaveFocus
             */
            this.setCanHaveFocus = function(b){
                if (this.canHaveFocus !== b) {
                    var fm = pkg.focusManager;
                    if (b === false && fm.focusOwner === this) {
                        fm.requestFocus(null);
                    }
                    this.canHaveFocus = b;
                }
                return this;
            };

            /**
             * Set the specified children component to be used as focus marker view anchor
             * component. Anchor component is a component over that the focus marker view
             * is painted.
             * @param  {zebkit.ui.Panel} c an anchor component
             * @method setFocusAnchorComponent
             * @chainable
             */
            this.setFocusAnchorComponent = function(c) {
                if (this.focusComponent !== c) {
                    if (c !== this && c !== null && this.kids.indexOf(c) < 0) {
                        throw new Error("Focus component doesn't exist");
                    }
                    this.focusComponent = c;
                    this.repaint();
                }
                return this;
            };
        },

        function focused() {
            this.$super();
            this.repaint();
        },

        function kidRemoved(i, l, ctr){
            if (l === this.focusComponent) {
                this.focusComponent = null;
            }
            this.$super(i, l, ctr);
        }
    ]);


    /**
     * Special interface that provides set of method for state components to implement repeatable
     * state.
     * @class zebkit.ui.FireEventRepeatedly
     * @interface zebkit.ui.FireEventRepeatedly
     */
    pkg.FireEventRepeatedly = zebkit.Interface([
        function $prototype() {
            /**
             * Indicate if the button should
             * fire event by pressed event
             * @attribute isFireByPress
             * @type {Boolean}
             * @default false
             * @readOnly
             */
            this.isFireByPress = false;

            /**
             * Fire button event repeating period. -1 means
             * the button event repeating is disabled.
             * @attribute firePeriod
             * @type {Integer}
             * @default -1
             * @readOnly
             */
            this.firePeriod = -1;

            /**
             * Indicates a time the repeat state events have to start in
             * @attribute startIn
             * @type {Integer}
             * @readOnly
             * @default 400
             */
            this.startIn = 400;

            /**
             * Task that has been run to support repeatable "fired" event.
             * @attribute $repeatTask
             * @type {zebkit.util.Task}
             * @private
             */
            this.$repeatTask = null;

            /**
             * Set the mode the button has to fire events. Button can fire
             * event after it has been unpressed or immediately when it has
             * been pressed. Also button can start firing events periodically
             * when it has been pressed and held in the pressed state.
             * @param  {Boolean} b  true if the button has to fire event by
             * pressed event
             * @param  {Integer} firePeriod the period of time the button
             * has to repeat firing events if it has been pressed and
             * held in pressed state. -1 means event doesn't have
             * repeated
             * @param  {Integer} [startIn] the timeout when repeat events
             * has to be initiated
             * @method setFireParams
             */
            this.setFireParams = function (b, firePeriod, startIn){
                if (this.$repeatTask !== null) {
                    this.$repeatTask.shutdown();
                }

                this.isFireByPress = b;
                this.firePeriod = firePeriod;
                if (arguments.length > 2) {
                    this.startIn = startIn;
                }
                return this;
            };

            this.$fire = function() {
                this.fire("fired", [ this ]);
                if (this.fired !== undefined) {
                    this.fired();
                }
            };
        },

        function stateUpdated(o,n){
            this.$super(o, n);
            if (n === "pressed.over") {
                if (this.isFireByPress === true){
                    this.$fire();

                    if (this.firePeriod > 0) {
                        var $this = this;
                        this.$repeatTask = zebkit.util.tasksSet.run(function() {
                                if ($this.state === "pressed.over") {
                                    $this.$fire();
                                }
                            },
                            this.startIn,
                            this.firePeriod
                        );
                    }
                }
            } else {
                if (this.firePeriod > 0 && this.$repeatTask !== null) {
                    this.$repeatTask.shutdown();
                }

                if (n === "over" && (o === "pressed.over" && this.isFireByPress === false)) {
                    this.$fire();
                }
            }
        }
    ]);

    /**
     *  Button UI component. Button is composite component whose look and feel can
     *  be easily customized:
     *
     *       // create image button
     *       var button = new zebkit.ui.Button(new zebkit.ui.ImagePan("icon1.gif"));
     *
     *       // create image + caption button
     *       var button = new zebkit.ui.Button(new zebkit.ui.ImageLabel("Caption", "icon1.gif"));
     *
     *       // create multilines caption button
     *       var button = new zebkit.ui.Button("Line1\nLine2");
     *
     *
     *  @class  zebkit.ui.Button
     *  @constructor
     *  @param {String|zebkit.ui.Panel|zebkit.draw.View} [t] a button label.
     *  The label can be a simple text or an UI component.
     *  @extends zebkit.ui.EvStatePan
     *  @uses  zebkit.ui.FireEventRepeatedly
     *  @uses  zebkit.ui.DrawFocusMarker
     */

    /**
     * Fired when a button has been pressed
     *
     *     var b = new zebkit.ui.Button("Test");
     *     b.on(function (src) {
     *         ...
     *     });
     *
     * Button can be adjusted in respect how it generates the pressed event. Event can be
     * triggered by pressed or clicked even. Also event can be generated periodically if
     * the button is kept in pressed state.
     * @event fired
     * @param {zebkit.ui.Button} src a button that has been pressed
     *
     */
    pkg.Button = Class(pkg.EvStatePan, pkg.DrawFocusMarker, pkg.FireEventRepeatedly, [
        function(t) {
            this.$super();

            if (arguments.length > 0 && t !== null) {
                t = pkg.$component(t, this);
                this.add(t);
                this.setFocusAnchorComponent(t);
            }
        },

        function $clazz() {
            this.Label = Class(pkg.Label, []);

            this.ViewPan = Class(pkg.ViewPan, [
                function(v) {
                    this.$super();
                    this.setView(v);
                },

                function $prototype() {
                    this.setState = function(id) {
                        if (this.view !== null && this.view.activate !== undefined) {
                            this.activate(id);
                        }
                    };
                }
            ]);

            this.ImageLabel = Class(pkg.ImageLabel, []);
        },

        function $prototype() {
            /**
             * Indicates the component can have focus
             * @attribute canHaveFocus
             * @type {Boolean}
             * @default true
             */
            this.canHaveFocus = true;


            this.catchInput = true;
        }
    ]).events("fired");

    /**
     * Group class to help managing group of element where only one can be on.
     *
     *     // create group of check boxes that will work as a radio group
     *     var gr  = new zebkit.ui.Group();
     *     var ch1 = new zebkit.ui.Checkbox("Test 1", gr);
     *     var ch2 = new zebkit.ui.Checkbox("Test 2", gr);
     *     var ch3 = new zebkit.ui.Checkbox("Test 3", gr);
     *
     * @class  zebkit.ui.Group
     * @uses  zebkit.EventProducer
     * @param {Boolean} [un] indicates if group can have no one item selected.
     * @constructor
     */
    pkg.Group = Class([
        function(un) {
            this.selected = null;

            if (arguments.length > 0) {
                this.allowNoneSelected = un;
            }
        },

        function $prototype() {
            /**
             * indicates if group can have no one item selected.
             * @attribute allowNoneSelected
             * @readOnly
             * @type {Boolean}
             * @default false
             */
            this.allowNoneSelected = false;

            this.$group  = null;
            this.$locked = false;

            this.$allowValueUpdate = function(src) {
                if (this.$group === null || this.$group.indexOf(src) < 0) {
                    throw new Error("Component is not the group member");
                }

                return (this.selected !== src ||
                        src.getValue() === false ||
                        this.allowNoneSelected === true);
            };

            this.attach = function(c) {
                if (this.$group === null) {
                    this.$group = [];
                }

                if (this.$group.indexOf(c) >= 0) {
                    throw new Error("Duplicated group element");
                }

                if (this.selected !== null && c.getValue() === true) {
                    c.setValue(false);
                }

                c.on(this);
                this.$group.push(c);
            };

            this.detach = function(c) {
                if (this.$group === null || this.$group.indexOf(c) < 0) {
                    throw new Error("Component is not the group member");
                }

                if (this.selected !== null && c.getValue() === true) {
                    c.setValue(false);
                }

                c.off(this);
                var i = this.$group.indexOf(c);
                this.$group.splice(i, 1);

                if (this.selected === c) {
                    if (this.allowNoneSelected !== true && this.$group.length > 0) {
                        this.$group[i % this.$group.length].setValue(true);
                    }
                    this.selected = null;
                }
            };

            this.fired = function(c) {
                if (this.$locked !== true) {
                    try {
                        this.$locked = true;
                        var b   = c.getValue(),
                            old = this.selected;

                        if (this.allowNoneSelected && b === false && this.selected !== null) {
                            this.selected = null;
                            this.updated(old);
                        } else if (b && this.selected !== c) {
                            this.selected = c;
                            if (old !== null) {
                                old.setValue(false);
                            }
                            this.updated(old);
                        }
                    } finally {
                        this.$locked = false;
                    }
                }
            };

            this.updated = function(old) {
                this.fire("selected", [this, this.selected, old]);
            };
        }
    ]).events("selected");

    /**
     * Check-box UI component. The component is a container that consists from two other UI components:
     *
     *   - Box component to keep checker indicator
     *   - Label component to paint label
     *
     * Developers are free to customize the component as they want. There is no limitation regarding
     * how the box and label components have to be laid out, which UI components have to be used as
     * the box or label components, etc. The check box extends state panel component and re-map states
     * to own views IDs:
     *
     *   - **"pressed.out"** - checked and pointer cursor is out
     *   - **"out"** - un-checked and pointer cursor is out
     *   - **"pressed.disabled"** - disabled and checked,
     *   - **"disabled"** - disabled and un-checked ,
     *   - **"pressed.over"** - checked and pointer cursor is over
     *   - **"over"** - un-checked and pointer cursor is out
     *
     *
     * Customize is quite similar to what explained for zebkit.ui.EvStatePan:
     *
     *
     *       // create checkbox component
     *       var ch = new zebkit.ui.Checkbox("Checkbox");
     *
     *       // change border when the component checked to green
     *       // otherwise set it to red
     *       ch.setBorder(new zebkit.draw.ViewSet({
     *           "*": new zebkit.draw.Border("red"),
     *           "pressed.*": new zebkit.draw.Border("green")
     *       }));
     *
     *       // customize checker box children UI component to show
     *       // green for checked and red for un-cheked states
     *       ch.kids[0].setView(new zebkit.draw.ViewSet({
     *           "*": "red",
     *           "pressed.*": "green"
     *       }));
     *       // sync current state with new look and feel
     *       ch.syncState();
     *
     * Listening checked event should be done by registering a listener in the check box switch manager
     * as follow:
     *
     *       // create check box component
     *       var ch = new zebkit.ui.Checkbox("Checkbox");
     *
     *       // register a check box listener
     *       ch.on(function(src) {
     *           var s = src.getValue();
     *           ...
     *       });
     *
     * @class  zebkit.ui.Checkbox
     * @extends zebkit.ui.EvStatePan
     * @uses zebkit.ui.DrawFocusMarker
     * @constructor
     * @param {String|zebkit.ui.Panel} [label] a label
     */
    pkg.Checkbox = Class(pkg.EvStatePan, pkg.DrawFocusMarker, [
        function (c) {
            if (arguments.length > 0 && c !== null && zebkit.isString(c)) {
                c = new this.clazz.Label(c);
            }

            this.$super();

            /**
             * Reference to box component
             * @attribute box
             * @type {zebkit.ui.Panel}
             * @readOnly
             */
            this.box = new this.clazz.Box();
            this.add(this.box);

            if (arguments.length > 0 && c !== null) {
                this.add(c);
                this.setFocusAnchorComponent(c);
            }
        },

        function $clazz() {
            /**
             * The box UI component class that is used by default with the check box component.
             * @constructor
             * @class zebkit.ui.Checkbox.Box
             * @extends zebkit.ui.ViewPan
             */
            this.Box = Class(pkg.StatePan, []);

            /**
             * @for zebkit.ui.Checkbox
             */
            this.Label = Class(pkg.Label, []);
        },

        function $prototype() {
            /**
             * Check box state
             * @attribute value
             * @type {Boolean}
             * @readOnly
             * @protected
             */
            this.value = false;

            this.$group = null;

            this.catchInput = true;

            /**
             *  Called every time the check box state has been updated
             *  @param {zebkit.ui.Checkbox} ch a check box that has triggered the swicth
             *  @method  switched
             */

            /**
             * Set the check box state.
             * @param {Boolean} v a state of the check box
             * @method setValue
             * @chainable
             */
            this.setValue = function(v) {
                if (this.value !== v && (this.$group === null || this.$group.$allowValueUpdate(this))) {
                    this.value = v;
                    this.stateUpdated(this.state, this.state);
                    if (this.switched !== undefined) {
                        this.switched(this);
                    }

                    this.fire("fired", this);
                }
                return this;
            };

            /**
             * Get the check box state
             * @return {Boolean} a state
             * @method getValue
             */
            this.getValue = function() {
                return this.value;
            };

            /**
             * Toggle check box state.
             * @method toggle
             * @chainable
             */
            this.toggle = function() {
                this.setValue(this.value !== true);
                return this;
            };

            /**
             * Map the specified state into its symbolic name.
             * @protected
             * @param  {String} state a state
             * @return {String} a symbolic name of the state
             * @method toViewId
             */
            this.toViewId = function(state){
                if (this.isEnabled === true) {
                    return this.getValue() ? (state === "over" ? "pressed.over" : "pressed.out")
                                           : (state === "over" ? "over" : "out");
                } else {
                    return this.getValue() ? "pressed.disabled" : "disabled";
                }
            };

            /**
             * Attach the given check box tho the specified group
             * @param {zebkit.ui.Group} g a group
             * @method setGroup
             * @chainable
             */
            this.setGroup = function(g) {
                if (this.$group !== null) {
                    this.$group.detach(this);
                    this.$group = null;
                }

                if (this.$group !== g) {
                    this.$group = g;
                    if (this.$group !== null) {
                        this.$group.attach(this);
                    }
                }

                return this;
            };
        },

        function stateUpdated(o, n) {
            if (o === "pressed.over" && n === "over") {
                this.toggle();
            }
            this.$super(o, n);
        },

        function kidRemoved(index, c, ctr) {
            if (this.box === c) {
                this.box = null;
            }
            this.$super(index,c);
        },

        function keyPressed(e){
            if (this.$group !== null && this.getValue()){
                var d = 0;
                if (e.code === "ArrowLeft" || e.code === "ArrowUp") {
                    d = -1;
                } else if (e.code === "ArrowRight" || e.code === "ArrowDown") {
                    d = 1;
                }

                if (d !== 0) {
                    var p = this.parent;
                    for(var i = p.indexOf(this) + d; i < p.kids.length && i >= 0; i += d) {
                        var l = p.kids[i];
                        if (l.isVisible === true &&
                            l.isEnabled === true &&
                            l.$group    === this.$group )
                        {
                            l.requestFocus();
                            l.setValue(true);
                            break;
                        }
                    }
                    return ;
                }
            }
            this.$super(e);
        }
    ]).events("fired");

    /**
     * Radio-box UI component class. This class is extension of "zebkit.ui.Checkbox" class that sets group
     * as a default switch manager. The other functionality id identical to check box component. Generally
     * speaking this class is a shortcut for radio box creation.
     * @class  zebkit.ui.Radiobox
     * @constructor
     * @param {String|zebkit.ui.Panel} [label] a label
     * @param {zebkit.ui.Group} [m] a group
     * @extends zebkit.ui.Checkbox
     */
    pkg.Radiobox = Class(pkg.Checkbox, [
        function(lab, group) {
            if (arguments.length > 0) {
                if (zebkit.instanceOf(lab, pkg.Group)) {
                    group = lab;
                    lab   = null;
                }

                this.$super(lab);
            } else {
                this.$super();
            }

            if (group !== undefined) {
                this.setGroup(group);
            }
        }
    ]);

    /**
     * UI link component class.
     * @class zebkit.ui.Link
     * @param {String} s a link text
     * @constructor
     * @extends zebkit.ui.Button
     */
    pkg.Link = Class(pkg.Button, [
        function(s) {
            // do it before super
            this.view = new zebkit.draw.DecoratedTextRender(s);
            this.overDecoration = "underline";

            this.$super(null);

            // if colors have not been set with default property set it here
            if (this.colors === null) {
                this.colors  = {
                    "pressed.over" : "blue",
                    "out"          : "white",
                    "over"         : "white",
                    "pressed.out"  : "black",
                    "disabled"     : "gray"
                };
            }

            this.stateUpdated(this.state, this.state);
        },

        function $prototype() {
            this.colors = null;

            /**
             * Mouse cursor type.
             * @attribute cursorType
             * @default zebkit.ui.Cursor.HAND;
             * @type {String}
             * @readOnly
             */
            this.cursorType = pkg.Cursor.HAND;

            /**
             * Set link font
             * @param {zebkit.Font} f a font
             * @method setFont
             * @chainable
             */
            this.setFont = function(f) {
                var old = this.view !== null ? this.view.font
                                             : null;

                this.view.setFont.apply(this.view, arguments);
                if (old !== this.view.font) {
                    this.vrp();
                }
                return this;
            };

            /**
             * Set the link text color for the specified link state
             * @param {String} state a link state
             * @param {String} c a link text color
             * @method  setColor
             * @chainable
             */
            this.setColor = function(state,c){
                if (this.colors[state] !== c){
                    this.colors[state] = c;
                    this.syncState();
                }
                return this;
            };

            this.setColors = function(colors) {
                this.colors = zebkit.clone(colors);
                this.syncState();
                return this;
            };

            this.setValue = function(s) {
                this.view.setValue(s.toString());
                this.repaint();
                return this;
            };
        },

        function stateUpdated(o, n){
            this.$super(o, n);

            var k = this.toViewId(n),
                b = false;

            if (this.view !== null &&
                this.view.color !== this.colors[k] &&
                this.colors[k]  !== null &&
                this.colors[k]  !== undefined)
            {
                this.view.setColor(this.colors[k]);
                b = true;
            }

            if (zebkit.instanceOf(this.view, zebkit.draw.DecoratedTextRender) && this.isEnabled === true) {
                if (n === "over") {
                    this.view.addDecorations(this.overDecoration);
                    b = true;
                } else if (this.view.hasDecoration(this.overDecoration)) {
                    this.view.clearDecorations(this.overDecoration);
                    b = true;
                }
            }

            if (b) {
                this.repaint();
            }
        }
    ]);

    // cannot be declared in Button.$clazz since Link appears later and link inherits Button class
    pkg.Button.Link = Class(pkg.Link, []);


    /**
     * Toolbar UI component. Handy way to place number of click able elements
     * @class zebkit.ui.Toolbar
     * @constructor
     * @extends zebkit.ui.Panel
     */

    /**
     * Fired when a toolbar element has been pressed
     *
     *       var t = new zebkit.ui.Toolbar();
     *
     *       // add three pressable icons
     *       t.addImage("icon1.jpg");
     *       t.addImage("icon2.jpg");
     *       t.addLine();
     *       t.addImage("ico3.jpg");
     *
     *       // catch a toolbar icon has been pressed
     *       t.on(function (src) {
     *           ...
     *       });
     *
     * @event pressed
     * @constructor
     * @param {zebkit.ui.Panel} src a toolbar element that has been pressed
     */
    pkg.Toolbar = Class(pkg.Panel, [
        function $clazz() {
            this.ToolPan = Class(pkg.EvStatePan, [
                function(c) {
                    this.$super(new zebkit.layout.BorderLayout());
                    this.add("center", c);
                },

                function getContentComponent() {
                    return this.kids[0];
                },

                function stateUpdated(o, n) {
                    this.$super(o, n);
                    if (o === "pressed.over" && n === "over") {
                        this.parent.fire("fired", [ this.parent, this.getContentComponent() ]);
                    }
                }
            ]);

            this.ImagePan = Class(pkg.ImagePan, []);
            this.Line     = Class(pkg.Line, []);
            this.Checkbox = Class(pkg.Checkbox, []);
            this.Radiobox = Class(pkg.Radiobox, []);

            // TODO: combo is not available in  this module yet
            // ui + ui.list has to be combined as one package
            //this.Combo    = Class(pkg.Combo, []);
        },

        function $prototype() {
            /**
             * Test if the given component is a decorative element
             * in the toolbar
             * @param  {zebkit.ui.Panel}  c a component
             * @return {Boolean} return true if the component is
             * decorative element of the toolbar
             * @method isDecorative
             * @protected
             */
            this.isDecorative = function(c){
                return zebkit.instanceOf(c, pkg.StatePan) === false;
            };

            /**
             * Add a radio box as the toolbar element that belongs to the
             * given group and has the specified content component
             * @param {zebkit.ui.Group} g a radio group the radio box belongs
             * @param {zebkit.ui.Panel} c a content
             * @return {zebkit.ui.Panel} a component that has been added
             * @method addRadio
             */
            this.addRadio = function(g,c) {
                var cbox = new this.clazz.Radiobox(c, g);
                cbox.setCanHaveFocus(false);
                return this.add(cbox);
            };

            /**
             * Add a check box as the toolbar element with the specified content
             * component
             * @param {zebkit.ui.Panel} c a content
             * @return {zebkit.ui.Panel} a component that has been added
             * @method addSwitcher
             */
            this.addSwitcher = function(c){
                var cbox = new this.clazz.Checkbox(c);
                cbox.setCanHaveFocus(false);
                return this.add(cbox);
            };

            /**
             * Add an image as the toolbar element
             * @param {String|Image} img an image or a path to the image
             * @return {zebkit.ui.Panel} a component that has been added
             * @method addImage
             */
            this.addImage = function(img) {
                this.validateMetric();
                return this.add(new this.clazz.ImagePan(img));
            };

            /**
             * Add line to the toolbar component. Line is a decorative ]
             * element that logically splits toolbar elements. Line as any
             * other decorative element doesn't fire event
             * @return {zebkit.ui.Panel} a component that has been added
             * @method addLine
             */
            this.addLine = function(){
                var line = new this.clazz.Line();
                line.constraints = "stretch";
                return this.addDecorative(line);
            };
        },

        /**
         * Add the given component as decorative element of the toolbar.
         * Decorative elements don't fire event and cannot be pressed
         * @param {zebkit.ui.Panel} c a component
         * @return {zebkit.ui.Panel} a component that has been added
         * @method addDecorative
         */
        function addDecorative(c) {
            return this.$getSuper("insert").call(this, this.kids.length, null, c);
        },

        function insert(i,id,d){
            if (d === "-") {
                var line = new this.clazz.Line();
                line.constraints = "stretch";
                return this.$super(i, null, line);
            } else if (Array.isArray(d)) {
                d = new this.clazz.Combo(d);
            }
            return this.$super(i, id, new this.clazz.ToolPan(d));
        }
    ]).events("fired");


    pkg.ApplyStateProperties = zebkit.Interface([
        function $prototype() {
            this.stateProperties = null;

            /**
             * Set properties set for the states
             * @param {Object} s a states
             * @method setStateProperties
             * @chainable
             */
            this.setStateProperties = function(s) {
                this.stateProperties = zebkit.clone(s);
                this.syncState();
                this.vrp();
                return this;
            };
        },

        function stateUpdated(o, n) {
            this.$super(o, n);
            if (this.stateProperties !== undefined &&
                this.stateProperties !== null        )
            {
                if (this.stateProperties[n] !== undefined &&
                    this.stateProperties[n] !== null        )
                {
                    zebkit.properties(this,
                                      this.stateProperties[n]);
                } else if (this.stateProperties["*"] !== undefined &&
                           this.stateProperties["*"] !== null        )
                {
                    zebkit.properties(this,
                                      this.stateProperties["*"]);
                }
            }
        }
    ]);

    /**
     * Arrow button component.
     * @class zebkit.ui.ArrowButton
     * @constructor
     * @param  {String} direction an arrow icon direction. Use "left", "right", "top", "bottom" as
     * the parameter value.
     * @extends zebkit.ui.Button
     */
    pkg.ArrowButton = Class(pkg.Button, pkg.ApplyStateProperties, [
        function(direction) {
            this.view = new zebkit.draw.ArrowView();
            this.$super();
            this.syncState();
        },

        function $prototype() {
            this.cursorType = pkg.Cursor.HAND;

            this.canHaveFocus = false;

            /**
             * Set arrow direction. Use one of the following values: "top",
             * "left", "right" or "bottom" as the argument value.
             * @param {String} d an arrow direction
             * @method setDirection
             * @chainable
             */
            this.setDirection = function(d) {
                if (this.view.direction !== d) {
                    this.view.direction = d;
                    this.repaint();
                }
                return this;
            };

            this.setStretchArrow = function(b) {
                if (this.view.stretched !== b) {
                    this.view.stretched = b;
                    this.repaint();
                }
                return this;
            };

            this.setArrowSize = function(w, h) {
                if (arguments.length === 1) {
                    h = w;
                }

                if (this.view.width !== w || this.view.height !== h) {
                    this.view.width  = w;
                    this.view.height = h;
                    this.vrp();
                }

                return this;
            };

            this.setFillColor = function(c) {
                if (this.view.fillColor !== c) {
                    this.view.fillColor = c;
                    this.repaint();
                }
                return this;
            };

            this.setColor = function(c) {
                if (this.view.color !== c) {
                    this.view.color = c;
                    this.repaint();
                }
                return this;
            };

            this.setLineSize = function(s) {
                if (this.view.lineSize !== s) {
                    this.view.lineSize = s;
                    this.repaint();
                }
                return this;
            };
        }
    ]);


    /**
     * Scroll manager class.
     * @param {zebkit.ui.Panel} t a target component to be scrolled
     * @constructor
     * @class zebkit.ui.ScrollManager
     * @uses  zebkit.EventProducer
     */

     /**
      * Fired when a target component has been scrolled
      *
      *      scrollManager.on(function(px, py) {
      *          ...
      *      });
      *
      * @event scrolled
      * @param  {Integer} px a previous x location target component scroll location
      * @param  {Integer} py a previous y location target component scroll location
      */

     /**
      * Fired when a scroll state has been updated
      *
      *      scrollManager.scrollStateUpdated = function(x, y, px, py) {
      *          ...
      *      };
      *
      * @event scrollStateUpdated
      * @param  {Integer} x a new x location target component scroll location
      * @param  {Integer} y a new y location target component scroll location
      * @param  {Integer} px a previous x location target component scroll location
      * @param  {Integer} py a previous y location target component scroll location
      */
    pkg.ScrollManager = Class([
        function(c) {
            /**
             * Target UI component for that the scroll manager has been instantiated
             * @attribute target
             * @type {zebkit.ui.Panel}
             * @readOnly
             */
            this.target = c;
        },

        function $prototype() {
            this.sx = this.sy = 0;

            /**
             * Get current target component x scroll location
             * @return {Integer} a x scroll location
             * @method getSX
             */
            this.getSX = function() {
                return this.sx;
            };

            /**
             * Get current target component y scroll location
             * @return {Integer} a y scroll location
             * @method getSY
             */
            this.getSY = function() {
                return this.sy;
            };

            /**
             * Set a target component scroll x location to the
             * specified value
             * @param  {Integer} v a x scroll location
             * @method scrollXTo
             */
            this.scrollXTo = function(v){
                this.scrollTo(v, this.getSY());
            };

            /**
             * Set a target component scroll y location to the
             * specified value
             * @param  {Integer} v a y scroll location
             * @method scrollYTo
             */
            this.scrollYTo = function(v){
                this.scrollTo(this.getSX(), v);
            };

            /**
             * Scroll the target component into the specified location
             * @param  {Integer} x a x location
             * @param  {Integer} y a y location
             * @method scrollTo
             */
            this.scrollTo = function(x, y){
                var psx = this.getSX(),
                    psy = this.getSY();

                if (psx !== x || psy !== y){
                    this.sx = x;
                    this.sy = y;
                    if (this.scrollStateUpdated !== undefined) {
                        this.scrollStateUpdated(x, y, psx, psy);
                    }
                    if (this.target.catchScrolled !== undefined) {
                        this.target.catchScrolled(psx, psy);
                    }

                    // TODO: a bit faster then .fire("scrolled", [this, psx, psy])
                    if (this._ !== undefined) {
                        this._.scrolled(this, psx, psy);
                    }
                }
                return this;
            };

            /**
             * Make visible the given rectangular area of the
             * scrolled target component
             * @param  {Integer} x a x coordinate of top left corner
             * of the rectangular area
             * @param  {Integer} y a y coordinate of top left corner
             * of the rectangular area
             * @param  {Integer} w a width of the rectangular area
             * @param  {Integer} h a height of the rectangular area
             * @method makeVisible
             * @chainable
             */
            this.makeVisible = function(x,y,w,h){
                var p = pkg.calcOrigin(x, y, w, h, this.getSX(), this.getSY(), this.target);
                this.scrollTo(p[0], p[1]);
                return this;
            };
        }
    ]).events("scrolled");

    /**
     * Scroll bar UI component
     * @param {String} [t] orientation of the scroll bar components:

            "vertical" - vertical scroll bar
            "horizontal"- horizontal scroll bar

     * @class zebkit.ui.Scroll
     * @constructor
     * @extends zebkit.ui.Panel
     * @uses zebkit.util.Position.Metric
     */
    pkg.Scroll = Class(pkg.Panel, zebkit.util.Position.Metric, [
        function(t) {
            if (arguments.length > 0) {
                this.orient = zebkit.util.validateValue(t, "vertical", "horizontal");
            }

            /**
             * Increment button
             * @attribute incBt
             * @type {zebkit.ui.Button}
             * @readOnly
             */

            /**
             * Decrement button
             * @attribute decBt
             * @type {zebkit.ui.Button}
             * @readOnly
             */

            /**
             * Scroll bar thumb component
             * @attribute thumb
             * @type {zebkit.ui.Panel}
             * @readOnly
             */

            this.thumbLoc = 0;
            this.startDragLoc = Number.MAX_VALUE;
            this.$super(this);

            var b = (this.orient === "vertical");
            this.add("center", b ? new pkg.Scroll.VerticalThumb()     : new pkg.Scroll.HorizontalThumb());
            this.add("top"   , b ? new pkg.Scroll.BottomArrowButton() : new pkg.Scroll.RightArrowButton());
            this.add("bottom", b ? new pkg.Scroll.TopArrowButton()    : new pkg.Scroll.LeftArrowButton());

            this.setPosition(new zebkit.util.SingleColPosition(this));
        },

        function $clazz() {
            this.isDragable = true;

            this.ArrowButton = Class(pkg.ArrowButton, [
                function $prototype() {
                    this.isFireByPress  = true;
                    this.firePeriod     = 20;
                }
            ]);

            this.ArrowButton.inheritProperties = true;

            this.TopArrowButton    = Class(this.ArrowButton, []);
            this.BottomArrowButton = Class(this.ArrowButton, []);
            this.LeftArrowButton   = Class(this.ArrowButton, []);
            this.RightArrowButton  = Class(this.ArrowButton, []);

            this.VerticalThumb = Class(pkg.ViewPan, []);
            this.HorizontalThumb = Class(pkg.ViewPan, []);
        },

        function $prototype() {
            this.MIN_BUNDLE_SIZE = 16;

            this.incBt = this.decBt = this.thumb = this.position = null;

            /**
             * Maximal possible value
             * @attribute max
             * @type {Integer}
             * @readOnly
             * @default 100
             */
            this.extra = this.max  = 100;

            /**
             * Page increment value
             * @attribute pageIncrement
             * @type {Integer}
             * @readOnly
             * @default 20
             */
            this.pageIncrement = 20;

            /**
             * Unit increment value
             * @attribute unitIncrement
             * @type {Integer}
             * @readOnly
             * @default 5
             */
            this.unitIncrement = 5;

            /**
             * Scroll orientation.
             * @attribute orient
             * @type {String}
             * @readOnly
             * @default "vertical"
             */
            this.orient = "vertical";

            /**
             * Evaluate if the given point is in scroll bar thumb element
             * @param  {Integer}  x a x location
             * @param  {Integer}  y a y location
             * @return {Boolean}   true if the point is located inside the
             * scroll bar thumb element
             * @method isInThumb
             */
            this.isInThumb = function(x,y){
                var bn = this.thumb;
                return (bn !== null &&
                        bn.isVisible === true &&
                        bn.x <= x && bn.y <= y &&
                        bn.x + bn.width > x &&
                        bn.y + bn.height > y);
            };

            this.amount = function(){
                var db = this.decBt;
                return (this.orient === "vertical") ? this.incBt.y - db.y - db.height
                                                    : this.incBt.x - db.x - db.width;
            };

            this.pixel2value = function(p) {
                var db = this.decBt;
                return (this.orient === "vertical") ? Math.floor((this.max * (p - db.y - db.height)) / (this.amount() - this.thumb.height))
                                                    : Math.floor((this.max * (p - db.x - db.width )) / (this.amount() - this.thumb.width));
            };

            this.value2pixel = function(){
                var db = this.decBt, bn = this.thumb, off = this.position.offset;
                return (this.orient === "vertical") ? db.y + db.height +  Math.floor(((this.amount() - bn.height) * off) / this.max)
                                                    : db.x + db.width  +  Math.floor(((this.amount() - bn.width) * off) / this.max);
            };


            /**
             * Define composite component catch input method
             * @param  {zebkit.ui.Panel} child a children component
             * @return {Boolean} true if the given children component has to be input events transparent
             * @method catchInput
             */
            this.catchInput = function (child){
                return child === this.thumb || (this.thumb.kids.length > 0 &&
                                                 zebkit.layout.isAncestorOf(this.thumb, child));
            };

            this.posChanged = function(target, po, pl, pc) {
                if (this.thumb !== null) {
                    if (this.orient === "horizontal") {
                        this.thumb.setLocation(this.value2pixel(), this.getTop());
                    } else {
                        this.thumb.setLocation(this.getLeft(), this.value2pixel());
                    }
                }
            };

            this.getLines     = function ()     { return this.max; };
            this.getLineSize  = function (line) { return 1; };
            this.getMaxOffset = function ()     { return this.max; };

            this.fired = function(src){
                this.position.setOffset(this.position.offset + ((src === this.incBt) ? this.unitIncrement
                                                                                     : -this.unitIncrement));
            };

            /**
             * Define pointer dragged events handler
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerDragged
             */
            this.pointerDragged = function(e){
                if (Number.MAX_VALUE !== this.startDragLoc) {
                    this.position.setOffset(this.pixel2value(this.thumbLoc -
                                                             this.startDragLoc +
                                                             ((this.orient === "horizontal") ? e.x : e.y)));
                }
            };

            /**
             * Define pointer drag started  events handler
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerDragStarted
             */
            this.pointerDragStarted = function (e){
                if (this.isDragable === true && this.isInThumb(e.x, e.y)) {
                    this.startDragLoc = this.orient === "horizontal" ? e.x : e.y;
                    this.thumbLoc    = this.orient === "horizontal" ? this.thumb.x : this.thumb.y;
                }
            };

            /**
             * Define pointer drag ended events handler
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerDragEnded
             */
            this.pointerDragEnded = function(e) {
                this.startDragLoc = Number.MAX_VALUE;
            };

            /**
             * Define pointer clicked events handler
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerClicked
             */
            this.pointerClicked = function (e){
                if (this.isInThumb(e.x, e.y) === false && e.isAction()){
                    var d = this.pageIncrement;
                    if (this.orient === "vertical"){
                        if (e.y < (this.thumb !== null ? this.thumb.y : Math.floor(this.height / 2))) {
                            d =  -d;
                        }
                    } else {
                        if (e.x < (this.thumb !== null ? this.thumb.x : Math.floor(this.width / 2))) {
                            d =  -d;
                        }
                    }
                    this.position.setOffset(this.position.offset + d);
                }
            };

            this.calcPreferredSize = function (target){
                var ps1 = pkg.$getPS(this.incBt),
                    ps2 = pkg.$getPS(this.decBt),
                    ps3 = pkg.$getPS(this.thumb);

                if (this.orient === "horizontal"){
                    ps1.width += (ps2.width + ps3.width);
                    ps1.height = Math.max((ps1.height > ps2.height ? ps1.height : ps2.height), ps3.height);
                } else {
                    ps1.height += (ps2.height + ps3.height);
                    ps1.width = Math.max((ps1.width > ps2.width ? ps1.width : ps2.width), ps3.width);
                }
                return ps1;
            };

            this.doLayout = function(target){
                var right  = this.getRight(),
                    top    = this.getTop(),
                    bottom = this.getBottom(),
                    left   = this.getLeft(),
                    ew     = this.width - left - right,
                    eh     = this.height - top - bottom,
                    b      = (this.orient === "horizontal"),
                    ps1    = pkg.$getPS(this.decBt),
                    ps2    = pkg.$getPS(this.incBt),
                    minbs  = this.MIN_BUNDLE_SIZE;

                this.decBt.setBounds(left, top, b ? ps1.width
                                                  : ew,
                                                b ? eh
                                                  : ps1.height);


                this.incBt.setBounds(b ? this.width - right - ps2.width : left,
                                     b ? top : this.height - bottom - ps2.height,
                                     b ? ps2.width : ew,
                                     b ? eh : ps2.height);

                if (this.thumb !== null && this.thumb.isVisible === true){
                    var am = this.amount();
                    if (am > minbs) {
                        var bsize = Math.max(Math.min(Math.floor((this.extra * am) / this.max), am - minbs), minbs);
                        this.thumb.setBounds(b ? this.value2pixel() : left,
                                              b ? top   : this.value2pixel(),
                                              b ? bsize : ew,
                                              b ? eh    : bsize);
                    } else {
                        this.thumb.setSize(0, 0);
                    }
                }
            };

            /**
             * Set the specified maximum value of the scroll bar component
             * @param {Integer} m a maximum value
             * @method setMaximum
             * @chainable
             */
            this.setMaximum = function (m){
                if (m !== this.max) {
                    this.max = m;
                    if (this.position.offset > this.max) {
                        this.position.setOffset(this.max);
                    }
                    this.vrp();
                }
                return this;
            };

            /**
             * Set the scroll bar value.
             * @param {Integer} v a scroll bar value.
             * @method setValue
             * @chainable
             */
            this.setValue = function(v){
                this.position.setOffset(v);
                return this;
            };

            this.setPosition = function(p){
                if (p !== this.position){
                    if (this.position !== null) {
                        this.position.off(this);
                    }
                    this.position = p;

                    if (this.position !== null){
                        this.position.on(this);
                        this.position.setMetric(this);
                        this.position.setOffset(0);
                    }
                }
                return this;
            };

            this.setExtraSize = function(e){
                if (e !== this.extra){
                    this.extra = e;
                    this.vrp();
                }
                return this;
            };
        },

        function kidAdded(index,ctr,lw) {
            this.$super(index, ctr, lw);

            if ("center" === ctr) {
                this.thumb = lw;
            } else if ("bottom" === ctr) {
                this.incBt = lw;
                this.incBt.on(this);
            } else if ("top" === ctr) {
                this.decBt = lw;
                this.decBt.on(this);
            } else {
                throw new Error("Invalid constraints : " + ctr);
            }
        },

        function kidRemoved(index, lw, ctr) {
            this.$super(index, lw, ctr);
            if (lw === this.thumb) {
                this.thumb = null;
            } else if (lw === this.incBt) {
                this.incBt.off(this);
                this.incBt = null;
            } else if (lw === this.decBt) {
                this.decBt.off(this);
                this.decBt = null;
            }
        }
    ]);

    /**
     * Scroll UI panel. The component is used to manage scrolling for a children UI component
     * that occupies more space than it is available. The usage is very simple, just put an
     * component you want to scroll horizontally or/and vertically in the scroll panel:

            // scroll vertically and horizontally a large picture
            var scrollPan = new zebkit.ui.ScrollPan(new zebkit.ui.ImagePan("largePicture.jpg"));

            // scroll vertically  a large picture
            var scrollPan = new zebkit.ui.ScrollPan(new zebkit.ui.ImagePan("largePicture.jpg"),
                                                   "vertical");

            // scroll horizontally a large picture
            var scrollPan = new zebkit.ui.ScrollPan(new zebkit.ui.ImagePan("largePicture.jpg"),
                                                   "horizontal");



     * @param {zebkit.ui.Panel} [c] an UI component that has to be placed into scroll panel
     * @param {String} [scrolls] a scroll bars that have to be shown. Use "vertical", "horizontal"
     * or "both" string value to control scroll bars visibility. By default the value is "both"
     * @constructor
     * @param {Boolean} [autoHide] a boolean value that says if the scrollbars have to work in
     * auto hide mode. Pass true to switch scrollbars in auto hide mode. By default the value is
     * false
     * @class zebkit.ui.ScrollPan
     * @extends zebkit.ui.Panel
     */
    pkg.ScrollPan = Class(pkg.Panel, [
        function (c, scrolls, autoHide) {
            if (arguments.length < 2)  {
                scrolls = "both";
            }

            this.$isPosChangedLocked = false;
            this.$super();

            if (arguments.length > 0 && c !== null) {
                this.add("center", c);
            }

            if (arguments.length < 2 || scrolls === "both" || scrolls === "horizontal") {
                this.add("bottom", new pkg.Scroll("horizontal"));
            }

            if (arguments.length < 2 || scrolls === "both" || scrolls === "vertical") {
                this.add("right", new pkg.Scroll("vertical"));
            }

            if (arguments.length > 2) {
                this.setAutoHide(autoHide);
            }
        },

        function $clazz() {
            this.ContentPanLayout = Class(zebkit.layout.Layout, [
                function $prototype() {
                    this.calcPreferredSize = function(t) {
                        return t.kids[0].getPreferredSize();
                    };

                    this.doLayout = function(t) {
                        var kid = t.kids[0];
                        if (kid.constraints === "stretch") {
                            var ps = kid.getPreferredSize(),
                                w  = t.parent.hBar !== null ? ps.width : t.width,
                                h  = t.parent.vBar !== null ? ps.height : t.height;
                            kid.setSize(w, h);
                        } else {
                            kid.toPreferredSize();
                        }
                    };
                }
            ]);

            var SM = this.ContentPanScrollManager = Class(pkg.ScrollManager, [
                function $prototype() {
                    this.getSX = function() {
                        return this.target.x;
                    };

                    this.getSY = function() {
                        return this.target.y;
                    };

                    this.scrollStateUpdated = function(sx,sy,psx,psy) {
                        this.target.setLocation(sx, sy);
                    };
                }
            ]);

            var contentPanLayout = new this.ContentPanLayout();
            this.ContentPan = Class(pkg.Panel, [
                function(c) {
                    this.$super(contentPanLayout);
                    this.scrollManager = new SM(c);
                    this.add(c);
                }
            ]);
        },

        function $prototype() {
            /**
             * Horizontal scroll bar component
             * @attribute hBar
             * @type {zebkit.ui.Scroll}
             * @readOnly
             */
            this.hBar = null;

            /**
             * Vertical scroll bar component
             * @attribute vBar
             * @type {zebkit.ui.Scroll}
             * @readOnly
             */
            this.vBar = null;

            /**
             * Scrollable target component
             * @attribute scrollObj
             * @type {zebkit.ui.Panel}
             * @readOnly
             */
            this.scrollObj = null;

            /**
             * Indicate if the scroll bars should be hidden
             * when they are not active
             * @attribute autoHide
             * @type {Boolean}
             * @readOnly
             */
            this.autoHide  = false;

            this.$interval = 0;

            /**
             * Set the given auto hide state.
             * @param  {Boolean} b an auto hide state.
             * @method setAutoHide
             * @chainable
             */
            this.setAutoHide = function(b) {
                if (this.autoHide !== b) {
                    this.autoHide = b;
                    if (this.hBar !== null) {
                        if (this.hBar.incBt !== null) {
                            this.hBar.incBt.setVisible(b === false);
                        }

                        if (this.hBar.decBt !== null) {
                            this.hBar.decBt.setVisible(b === false);
                        }

                        if (b === true) {
                            this.hBar.toBack();
                        } else {
                            this.hBar.toFront();
                        }
                    }

                    if (this.vBar !== null) {
                        if (this.vBar.incBt !== null) {
                            this.vBar.incBt.setVisible(b === false);
                        }

                        if (this.vBar.decBt !== null) {
                            this.vBar.decBt.setVisible(b === false);
                        }

                        if (b === true) {
                            this.vBar.toBack();
                        } else {
                            this.vBar.toFront();
                        }
                    }

                    if (this.$interval !== 0) {
                        zebkit.environment.clearInterval(this.$interval);
                        this.$interval = 0;
                    }

                    this.vrp();
                }
                return this;
            };

            /**
             * Scroll horizontally and vertically to the given positions
             * @param  {Integer} sx a horizontal position
             * @param  {Integer} sy a vertical position
             * @method scrollTo
             */
            this.scrollTo = function(sx, sy) {
                this.scrollObj.scrollManager.scrollTo(sx, sy);
            };

            /**
             * Scroll horizontally
             * @param  {Integer} sx a position
             * @method scrollXTo
             */
            this.scrollXTo = function(sx) {
                this.scrollObj.scrollManager.scrollXTo(sx);
            };

            /**
             * Scroll vertically
             * @param  {Integer} sy a position
             * @method scrollYTo
             */
            this.scrollYTo = function(sx, sy) {
                this.scrollObj.scrollManager.scrollYTo(sy);
            };

            this.doScroll = function(dx, dy, source) {
                var b = false, v = 0;

                if (dy !== 0 && this.vBar !== null && this.vBar.isVisible === true) {
                    v =  this.vBar.position.offset + dy;
                    if (v >= 0) {
                        this.vBar.position.setOffset(v);
                    } else {
                        this.vBar.position.setOffset(0);
                    }
                    b = true;
                }

                if (dx !== 0 && this.hBar !== null && this.hBar.isVisible === true) {
                    v =  this.hBar.position.offset + dx;
                    if (v >= 0) {
                        this.hBar.position.setOffset(v);
                    } else  {
                        this.hBar.position.setOffset(0);
                    }
                    b = true;
                }

                return b;
            };

            /**
             * Scroll manager listener method that is called every time
             * a target component has been scrolled
             * @param  {Integer} psx previous scroll x location
             * @param  {Integer} psy previous scroll y location
             * @method  scrolled
             */
            this.scrolled = function (psx,psy){
                this.validate();
                try {
                    this.$isPosChangedLocked = true;

                    if (this.hBar !== null) {
                        this.hBar.position.setOffset( -this.scrollObj.scrollManager.getSX());
                    }

                    if (this.vBar !== null) {
                        this.vBar.position.setOffset( -this.scrollObj.scrollManager.getSY());
                    }

                    if (this.scrollObj.scrollManager === null || this.scrollObj.scrollManager === undefined) {
                        this.invalidate();
                    }

                    this.$isPosChangedLocked = false;
                } catch(e) {
                    this.$isPosChangedLocked = false;
                    throw e;
                }
            };

            this.calcPreferredSize = function (target){
                return pkg.$getPS(this.scrollObj);
            };

            this.doLayout = function (target){
                var sman   = (this.scrollObj === null) ? null : this.scrollObj.scrollManager,
                    right  = this.getRight(),
                    top    = this.getTop(),
                    bottom = this.getBottom(),
                    left   = this.getLeft(),
                    ww     = this.width  - left - right,  maxH = ww,
                    hh     = this.height - top  - bottom, maxV = hh,
                    so     = this.scrollObj.getPreferredSize(),
                    vps    = this.vBar === null ? { width:0, height:0 } : this.vBar.getPreferredSize(),
                    hps    = this.hBar === null ? { width:0, height:0 } : this.hBar.getPreferredSize();

                // compensate scrolled vertical size by reduction of horizontal bar height if necessary
                // autoHidded scrollbars don't have an influence to layout
                if (this.hBar !== null && this.autoHide === false &&
                      (so.width  > ww ||
                      (so.height > hh && so.width > (ww - vps.width))))
                {
                    maxV -= hps.height;
                }
                maxV = so.height > maxV ? (so.height - maxV) :  -1;

                // compensate scrolled horizontal size by reduction of vertical bar width if necessary
                // autoHidded scrollbars don't have an influence to layout
                if (this.vBar !== null && this.autoHide === false &&
                      (so.height > hh ||
                      (so.width > ww && so.height > (hh - hps.height))))
                {
                    maxH -= vps.width;
                }
                maxH = so.width > maxH ? (so.width - maxH) :  -1;

                var sy = sman.getSY(), sx = sman.getSX();
                if (this.vBar !== null) {
                    if (maxV < 0) {
                        if (this.vBar.isVisible === true){
                            this.vBar.setVisible(false);
                            sman.scrollTo(sx, 0);
                            this.vBar.position.setOffset(0);
                        }
                        sy = 0;
                    } else {
                        this.vBar.setVisible(true);
                    }
                }

                if (this.hBar !== null){
                    if (maxH < 0){
                        if (this.hBar.isVisible === true){
                            this.hBar.setVisible(false);
                            sman.scrollTo(0, sy);
                            this.hBar.position.setOffset(0);
                        }
                    } else {
                        this.hBar.setVisible(true);
                    }
                }

                if (this.scrollObj.isVisible === true) {
                    this.scrollObj.setBounds(left, top,
                                             ww - (this.autoHide === false && this.vBar !== null && this.vBar.isVisible === true ? vps.width  : 0),
                                             hh - (this.autoHide === false && this.hBar !== null && this.hBar.isVisible === true ? hps.height : 0));
                }

                if (this.$interval === 0 && this.autoHide === true) {
                    hps.height = vps.width = 0;
                }

                if (this.hBar !== null && this.hBar.isVisible === true){
                    this.hBar.setBounds(left, this.height - bottom - hps.height,
                                        ww - (this.vBar !== null && this.vBar.isVisible === true ? vps.width : 0),
                                        hps.height);
                    this.hBar.setMaximum(maxH);
                }

                if (this.vBar !== null && this.vBar.isVisible === true){
                    this.vBar.setBounds(this.width - right - vps.width, top,
                                        vps.width,
                                        hh -  (this.hBar !== null && this.hBar.isVisible === true ? hps.height : 0));
                    this.vBar.setMaximum(maxV);
                }
            };

            this.posChanged = function (target,prevOffset,prevLine,prevCol){
                if (this.$isPosChangedLocked === false) {
                    //  show if necessary hidden scroll bar(s)
                    if (this.autoHide === true) {
                        // make sure autohide thread has not been initiated and make sure it makes sense
                        // to initiate the thread (one of the scroll bar has to be visible)
                        if (this.$interval === 0 && ((this.vBar !== null && this.vBar.isVisible === true) ||
                                                     (this.hBar !== null && this.hBar.isVisible === true)    ))
                        {
                            var $this = this;

                            // show scroll bar(s)
                            if (this.vBar !== null) {
                                this.vBar.toFront();
                            }

                            if (this.hBar !== null) {
                                this.hBar.toFront();
                            }

                            this.vrp();

                            // pointer move should keep scroll bars visible and pointer entered exited
                            // events have to be caught to track if pointer cursor is on a scroll
                            // bar. add temporary listeners
                            $this.$hiddingCounter = 2;
                            $this.$targetBar      = null;
                            var listener = pkg.events.on({
                                pointerMoved: function(e) {
                                    $this.$hiddingCounter = 1;
                                },

                                pointerExited: function(e) {
                                    $this.$targetBar = null;
                                },

                                pointerEntered: function(e) {
                                    if (e.source === $this.vBar) {
                                        $this.$targetBar = $this.vBar;
                                    } else {
                                        if (e.source === $this.hBar) {
                                            $this.$targetBar = $this.hBar;
                                            return;
                                        }

                                        $this.$targetBar = null;
                                    }
                                }
                            });

                            // start thread to autohide shown scroll bar(s)
                            this.$interval = zebkit.environment.setInterval(function() {
                                if ($this.$hiddingCounter-- === 0 && $this.$targetBar === null) {
                                    zebkit.environment.clearInterval($this.$interval);
                                    $this.$interval = 0;
                                    pkg.events.off(listener);
                                    $this.doLayout();
                                }
                            }, 500);
                        }
                    }

                    if (this.vBar !== null && this.vBar.position === target) {
                        this.scrollObj.scrollManager.scrollYTo(-this.vBar.position.offset);
                    } else if (this.hBar !== null && this.hBar.position === target) {
                        this.scrollObj.scrollManager.scrollXTo(-this.hBar.position.offset);
                    }
                }
            };

            this.setIncrements = function (hUnit,hPage,vUnit,vPage) {
                if (this.hBar !== null){
                    if (hUnit !==  -1) {
                        this.hBar.unitIncrement = hUnit;
                    }

                    if (hPage !==  -1) {
                        this.hBar.pageIncrement = hPage;
                    }
                }

                if (this.vBar !== null){
                    if (vUnit !==  -1) {
                        this.vBar.unitIncrement = vUnit;
                    }
                    if (vPage !==  -1) {
                        this.vBar.pageIncrement = vPage;
                    }
                }
                return this;
            };
        },

        function insert(i,ctr,c) {
            if ("center" === ctr) {
                if (c.scrollManager === null || c.scrollManager === undefined) {
                    c = new this.clazz.ContentPan(c);
                }

                this.scrollObj = c;
                c.scrollManager.on(this);
            } else {
                if ("bottom" === ctr || "top" === ctr){
                    this.hBar = c;
                } else if ("left" === ctr || "right" === ctr) {
                    this.vBar = c;
                } else {
                    throw new Error("Invalid constraints");
                }

                // valid for scroll bar only
                if (c.incBt !== null) {
                    c.incBt.setVisible(!this.autoHide);
                }
                if (c.decBt !== null) {
                    c.decBt.setVisible(!this.autoHide);
                }
                c.position.on(this);
            }

            return this.$super(i, ctr, c);
        },

        function kidRemoved(index, comp, ctr){
            this.$super(index, comp, ctr);
            if (comp === this.scrollObj){
                this.scrollObj.scrollManager.off(this);
                this.scrollObj = null;
            } else if (comp === this.hBar) {
                this.hBar.position.off(this);
                this.hBar = null;
            } else if (comp === this.vBar) {
                this.vBar.position.off(this);
                this.vBar = null;
            }
        }
    ]);

    /**
     * Mobile scroll manager class. Implements inertial scrolling in zebkit mobile application.
     * @class zebkit.ui.MobileScrollMan
     * @extends zebkit.ui.event.Manager
     * @constructor
     */
    pkg.MobileScrollMan = Class(zebkit.ui.event.Manager, [
        function $prototype() {
            this.$timer = this.identifier = this.target = null;

            /**
             * Define pointer drag started events handler.
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerDragStarted
             */
            this.pointerDragStarted = function(e) {
                if (e.touchCounter === 1 && e.pointerType === "touch") {
                    this.$identifier = e.identifier;
                    this.$target     = e.source;

                    // detect scrollable component
                    while (this.$target !== null && this.$target.doScroll === undefined) {
                        this.$target = this.$target.parent;
                    }

                    if (this.$target !== null && this.$target.pointerDragged !== undefined) {
                         this.$target = null;
                    }
                }
            };

            /**
             * Define pointer dragged events handler.
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerDragged
             */
            this.pointerDragged = function(e) {
                if (e.touchCounter   === 1            &&
                    this.$target    !==  null         &&
                    this.$identifier === e.identifier &&
                    e.direction     !==  null            )
                {
                    this.$target.doScroll(-e.dx, -e.dy, "touch");
                }
            };

            this.$taskMethod = function() {
                var bar = this.$target.vBar,
                    o   = bar.position.offset;

                // this is linear function with angel 42. every next value will
                // be slightly lower prev. one. theoretically angel 45 should
                // bring to infinite scrolling :)
                this.$dt = Math.tan(42 * Math.PI / 180) * this.$dt;
                bar.position.setOffset(o - Math.round(this.$dt));
                this.$counter++;

                if (o === bar.position.offset) {
                    this.$target = null;
                    zebkit.environment.clearInterval(this.$timer);
                    this.$timer = null;
                }
            };

            /**
             * Define pointer drag ended events handler.
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerDragEnded
             */
            this.pointerDragEnded = function(e) {
                if (this.$target !== null &&
                    this.$timer  === null  &&
                    this.$identifier === e.identifier &&
                    (e.direction === "bottom" || e.direction === "top") &&
                    this.$target.vBar !== null &&
                    this.$target.vBar.isVisible &&
                    e.dy !== 0)
                {
                    this.$dt = 2 * e.dy;
                    this.$counter = 0;
                    var $this = this;
                    this.$timer = zebkit.environment.setInterval(function() {
                        $this.$taskMethod($this);
                    }, 50);
                }
            };

            /**
             * Define pointer pressed events handler.
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerPressed
             */
            this.pointerPressed = function(e) {
                if (this.$timer !== null) {
                    zebkit.environment.clearInterval(this.$timer);
                    this.$timer = null;
                }
                this.$target = null;
            };
        }
    ]);


    /**
     * Text field UI component. The component is designed to enter single line, multi lines or password text.
     * The component implement text field functionality from the scratch. It supports the following features
     *
     *   - Text selection
     *   - Redu/Undo actions
     *   - Native WEB clipboard
     *   - Basic text navigation
     *   - Read-only mode
     *   - Left or right text alignment
     *
     * @constructor
     * @param {String|zebkit.data.TextModel|zebkit.draw.TextRender} [txt] a text the text field component
     * has to be filled. The parameter can be a simple string, text model or text render class instance.
     * @param {Integer} [maxCol] a maximal size of entered text. -1 means the size of the edited text
     * has no length limit.
     * @class zebkit.ui.TextField
     * @extends zebkit.ui.Label
     */

    /**
     * Fire when a text field content has been updated.
     *
     *       textField.on("updated", function(src) {
     *           ...
     *       });
     *
     * @event updated
     * @param  {zebkit.ui.TextField} src a source of the event
     */

    /**
     * Fire when a text field content has been selected.
     *
     *       textField.on("selected", function(src) {
     *           ...
     *       });
     *
     * @event selected
     * @param  {zebkit.ui.TextField} src a source of the event
     */

    /**
     * Fire when a cursor position has been changed.
     *
     *       textField.on("posChanged", function(src) {
     *           ...
     *       });
     *
     * @event posChanged
     * @param  {zebkit.ui.TextField} src a source of the event
     */
    pkg.TextField = Class(pkg.Label, [
        function (render, maxCol){
            this.$history = Array(100);

            this.scrollManager = new pkg.ScrollManager(this);

            var renderDefined = false;
            if (arguments.length === 0) {
                maxCol = -1;
                render = new zebkit.draw.TextRender(new zebkit.data.SingleLineTxt());
                renderDefined = true;
            } else if (arguments.length === 1){
                if (zebkit.isNumber(render)) {
                    maxCol = render;
                    render = new zebkit.draw.TextRender(new zebkit.data.SingleLineTxt());
                    renderDefined = true;
                } else {
                    maxCol = -1;
                }
            }

            if (renderDefined === false) {
                if (zebkit.isString(render)) {
                    render = new zebkit.draw.TextRender(new zebkit.data.SingleLineTxt(render));
                } else if (zebkit.instanceOf(render, zebkit.data.TextModel)) {
                    render = new zebkit.draw.TextRender(render);
                }
            }

            this.$super(render);
            if (maxCol > 0) {
                this.setPSByRowsCols(-1, maxCol);
            }
        },

        function $clazz() {
            /**
             * Text field hint text render
             * @constructor
             * @class zebkit.ui.TextField.HintRender
             * @extends zebkit.draw.StringRender
             */
            this.HintRender = Class(zebkit.draw.StringRender, []);
        },

        /**
         * @for zebkit.ui.TextField
         */
        function $prototype() {
            this.$historyPos  = -1;
            this.$lineHeight  = 0;
            this.$redoCounter = 0;
            this.$undoCounter = 0;
            this.$blinkTask   = null;

            /**
             * Cursor x loacation
             * @attribute cursorX
             * @type {Integer}
             * @readOnly
             */
            this.cursorX = 0;

            /**
             * Cursor y loacation
             * @attribute cursorY
             * @type {Integer}
             * @readOnly
             */
            this.cursorY = 0;

            /**
             * Cursor width
             * @attribute cursorWidth
             * @type {Integer}
             * @readOnly
             */
            this.cursorWidth = 0;

            /**
             * Cursor height
             * @attribute cursorHeight
             * @type {Integer}
             * @readOnly
             */
            this.cursorHeight = 0;

            /**
             * Selection view.
             * @attribute selectView
             * @type {zebkit.draw.View|String}
             * @readOnly
             */
            this.selectView = null;

            /**
             * Hint view
             * @attribute hint
             * @type {zebkit.draw.View}
             * @readOnly
             */
            this.hint = null;

            // TODO: check the place the property is required
            this.vkMode = "indirect";

            this.startLine = this.startCol = this.endLine = this.endCol = 0;
            this.startOff  = this.endOff = -1;

            /**
             * Cursor position manager
             * @attribute  position
             * @type {zebkit.util.Position}
             * @readOnly
             */
            this.position = null;

            /**
             * Specify the text field cursor blinking period in milliseconds.
             * -1 means no blinkable cursor
             * @type {Number}
             * @default -1
             * @readOnly
             * @attribute blinkigPeriod
             */
            this.blinkingPeriod = -1;
            this.$blinkMe        = true;
            this.$blinkMeCounter = 0;

            /**
             * Cursor type
             * @attribute cursorType
             * @type {String}
             * @default zebkit.ui.Cursor.TEXT;
             */
            this.cursorType = pkg.Cursor.TEXT;

            /**
             * Text alignment
             * @attribute textAlign
             * @type {String}
             * @default "left"
             * @readOnly
             */
            this.textAlign = "left";

            /**
             * Cursor view
             * @attribute cursorView
             * @type {zebkit.draw.View}
             * @readOnly
             */
            this.cursorView = null;

            /**
             * Indicate if the text field is editable
             * @attribute  isEditable
             * @type {Boolean}
             * @default true
             * @readOnly
             */
            this.canHaveFocus = this.isEditable = true;

            /**
             * Set the specified blinking period of the text field cursor
             * @param {Integer} [period] a text field cursor blinking period (in milliseconds),
             * use -1 to disable cursor blinking. If the argument is not passed the default (500ms)
             * blinking period will be applied.
             * @method setBlinking
             * @chainable
             */
            this.setBlinking = function(period) {
                if (arguments.length === 0) {
                    period = 500;
                }

                if (period !== this.blinkingPeriod) {
                    this.blinkingPeriod = period;
                    this.repaintCursor();
                }
                return this;
            };

            /**
             * Set the text algnment.
             * @method setTextAlignment
             * @param {String} a a text alignment. Use "left" or "right" as the parameter value
             * @chainable
             */
            this.setTextAlignment = function(a) {
                if (this.textAlign !== a) {
                    this.textAlign = a;
                    this.vrp();
                }
                return this;
            };

            this.textUpdated = function(e) {
                if (this.position !== null) {
                    if (this.endOff !== this.startOff) {
                        this.endOff = this.startOff = -1; // clear selection
                        this.fire("selected", this);
                    }

                    if (e.id === "insert") {
                        this.position.inserted(e.offset, e.size);
                    } else {
                        this.position.removed(e.offset, e.size);
                    }
                }

                if (e.isLastStep) {
                    if (this.updated !== undefined) {
                        this.updated();
                    }
                    this.fire("updated", this);
                }
            };

            /**
             * Compute a text column and row by the given location.
             * @param  {Integer} x  a x coordinate
             * @param  {Integer} y  a y coordinate
             * @return {Object} a text row and column as an object { row:, col }.
             * @method  getTextRowColAt
             */
            this.getTextRowColAt = function(x, y) {
                var lines = this.getLines();

                // normalize text location to virtual (zero, zero)
                y -= (this.scrollManager.getSY() + this.getTop());
                x -= this.scrollManager.getSX();
                if (this.textAlign === "left") {
                    x -= this.getLeft();
                } else {
                    x -= (this.width - this.view.getPreferredSize().width - this.getRight());
                }

                if (x >= 0 && y >= 0 && lines > 0) {
                    var lh = this.view.getLineHeight(),
                        li = this.view.lineIndent,
                        row = (y < 0) ? 0 : Math.floor((y + li) / (lh + li)) + ((y + li) % (lh + li) > li ? 1 : 0) -1;

                    if (row < lines && row >= 0) {
                        var s    = this.view.getLine(row),
                            pdt  = 1000000,
                            pcol = -1;

                        for(var col = Math.floor((x / this.view.calcLineWidth(row)) * s.length); col >= 0 && col <= s.length;) {
                            var l  = this.view.font.charsWidth(s, 0, col),
                                dt = Math.abs(l - x);

                            if (dt >= pdt) {
                                return { row : row, col : pcol };
                            }

                            pdt  = dt;
                            pcol = col;
                            col += (l > x ? -1: 1);
                        }

                        return { row : row, col : s.length };
                    }
                }
                return null;
            };

            /**
             * Find the next or previous word in the given text model starting from the given
             * line and column.
             * @param  {zebkit.data.TextModel | zebkit.draw.BaseTextRender} t a text model
             * @param  {Integer} line a starting line
             * @param  {Integer} col a starting column
             * @param  {Integer} d   a direction. 1 means looking for a next word and -1 means
             * search for a previous word.
             * @return {Object} a structure with the next or previous word location:
             *
             *        { row: {Integer}, col: {Integer} }
             *
             *
             * The method returns null if the next or previous word cannot be found.
             * @method  findNextWord
             * @protected
             */
            this.findNextWord = function(t, line, col, d){
                if (line < 0 || line >= t.getLines()) {
                    return null;
                }

                var ln = t.getLine(line);
                col += d;
                if (col < 0 && line > 0) {
                    return { row: line - 1, col : t.getLine(line - 1).length };
                } else if (col > ln.length && line < t.getLines() - 1) {
                    return { row : line + 1, col : 0 };
                }

                var b = false;
                for(; col >= 0 && col < ln.length; col += d){
                    if (b) {
                        if (d > 0) {
                            if (zebkit.util.isLetter(ln[col])) {
                                return { row:line, col:col };
                            }
                        } else {
                            if (!zebkit.util.isLetter(ln[col])) {
                                return { row : line, col: col + 1 };
                            }
                        }
                    } else  {
                        b = d > 0 ? !zebkit.util.isLetter(ln[col]) : zebkit.util.isLetter(ln[col]);
                    }
                }
                return (d > 0 ? { row: line, col : ln.length }: { row : line, col : 0 } );
            };

            // collect text model lines into string by the given start and end offsets
            // r     - text view
            // start - start offset
            // end   - end offset
            this.getSubString = function(r, start, end){
                var res = [],
                    sr = start.row,
                    er = end.row;

                for(var i = sr; i < er + 1; i++){
                    var ln = r.getLine(i);
                    if (i !== sr) {
                        res.push('\n');
                    } else {
                        ln = ln.substring(start.col);
                    }

                    if (i === er) {
                        ln = ln.substring(0, end.col - ((sr === er) ? start.col : 0));
                    }
                    res.push(ln);
                }
                return res.join('');
            };

            /**
             * Remove selected text
             * @method removeSelected
             * @chainable
             */
            this.removeSelected = function(){
                if (this.hasSelection()){
                    var start = this.startOff < this.endOff ? this.startOff : this.endOff;
                    this.remove(start, (this.startOff > this.endOff ? this.startOff : this.endOff) - start);
                    this.clearSelection();
                }
                return this;
            };

            /**
             * Start selection.
             * @protected
             * @method  startSelection
             * @chainable
             */
            this.startSelection = function() {
                if (this.startOff < 0 && this.position !== null){
                    var pos = this.position;
                    this.endLine = this.startLine = pos.currentLine;
                    this.endCol = this.startCol = pos.currentCol;
                    this.endOff = this.startOff = pos.offset;
                }
                return this;
            };

            this.keyTyped = function(e) {
                // Test if selection has been initiated (but nothing has been selected yet)
                // Typing a character changes position so if selection is active then
                // typed character will be unexpectedly selected.
                if (e.shiftKey && this.startOff >= 0 && this.endOff === this.startOff) {
                    this.clearSelection();
                }

                if (this.isEditable === true &&
                    e.ctrlKey === false &&
                    e.metaKey === false &&
                    e.key !== '\t')
                {
                    this.write(this.position.offset, e.key);
                }
            };

            /**
             * Select all text.
             * @method  selectAll
             * @chainable
             */
            this.selectAll = function() {
                this.select(0, this.getMaxOffset());
                return this;
            };

            /**
             * Shortcut event handler
             * @param  {java.ui.event.ShortcutEvent} e a shortcut event
             * @method shortcutFired
             */
            this.shortcutFired = function(e) {
                if (e.shortcut === "SELECTALL") {
                    this.selectAll();
                } else {
                    var d  = (e.shortcut === "PREVWORDSELECT" || e.shortcut === "PREVWORD") ? -1 : 1;

                    if (e.shortcut === "PREVWORDSELECT" ||
                        e.shortcut === "NEXTWORDSELECT" ||
                        e.shortcut === "NEXTPAGESELECT" ||
                        e.shortcut === "PREVPAGESELECT"   )
                    {
                        this.startSelection();
                    }

                    switch (e.shortcut) {
                        case "UNDO"          : this.undo(); break;
                        case "REDO"          : this.redo(); break;
                        case "NEXTPAGESELECT":
                        case "NEXTPAGE"      :  this.position.seekLineTo("down", this.pageSize()); break;
                        case "PREVPAGESELECT":
                        case "PREVPAGE"      :  this.position.seekLineTo("up", this.pageSize()); break;
                        case "NEXTWORDSELECT":
                        case "PREVWORDSELECT":
                        case "PREVWORD":
                        case "NEXTWORD" : {
                            var p = this.findNextWord(this.view, this.position.currentLine,
                                                                 this.position.currentCol, d);
                            if (p !== null) {
                                this.position.setRowCol(p.row, p.col);
                            }
                        } break;
                    }
                }
            };

            this.keyPressed = function(e) {
                if (this.isFiltered(e) === false)  {
                    var position = this.position;
                    if (e.shiftKey) {
                        this.startSelection();
                    }

                    switch(e.code) {
                        case "ArrowDown" : position.seekLineTo("down"); break;
                        case "ArrowUp"   : position.seekLineTo("up"); break;
                        case "ArrowLeft" :
                            if (e.ctrlKey === false && e.metaKey === false) {
                                position.seek(-1);
                            }
                            break;
                        case "ArrowRight":
                            if (e.ctrlKey === false && e.metaKey === false) {
                                position.seek(1);
                            }
                            break;
                        case "End":
                            if (e.ctrlKey) {
                                position.seekLineTo("down", this.getLines() - position.currentLine - 1);
                            } else {
                                position.seekLineTo("end");
                            }
                            break;
                        case "Home":
                            if (e.ctrlKey) {
                                position.seekLineTo("up", position.currentLine);
                            } else {
                                position.seekLineTo("begin");
                            }
                            break;
                        case "PageDown" :
                            position.seekLineTo("down", this.pageSize());
                            break;
                        case "PageUp" :
                            position.seekLineTo("up", this.pageSize());
                            break;
                        case "Delete":
                            if (this.hasSelection() && this.isEditable === true) {
                                this.removeSelected();
                            } else if (this.isEditable === true) {
                                this.remove(position.offset, 1);
                            } break;
                        case "Backspace":
                            if (this.isEditable === true) {
                                if (this.hasSelection()) {
                                    this.removeSelected();
                                } else if (this.isEditable === true && position.offset > 0) {
                                    position.seek(-1);
                                    this.remove(position.offset, 1);
                                }
                            } break;
                        default: return ;
                    }

                    if (e.shiftKey === false) {
                        this.clearSelection();
                    }
                }
            };

            /**
             * Test if the given key pressed event has to be processed
             * @protected
             * @param  {zebkit.ui.event.KeyEvent} e a key event
             * @return {Boolean} true if the given key pressed event doesn't
             * have be processed
             * @method isFiltered
             */
            this.isFiltered = function(e){
                var code = e.code;
                return code === "Shift" || code === "Control" ||
                       code === "Tab"   || code === "Alt"     ||
                       e.altKey;
            };

            /**
             * Remove the specified part of edited text
             * @param  {Integer} pos a start position of a removed text
             * @param  {Integer} size a size of removed text
             * @method remove
             */
            this.remove = function(pos, size){
                if (this.isEditable === true) {
                    if (pos >= 0 && (pos + size) <= this.getMaxOffset()) {
                        if (size < 10000) {
                            this.$historyPos = (this.$historyPos + 1) % this.$history.length;
                            this.$history[this.$historyPos] = [-1, pos, this.getValue().substring(pos, pos+size)];
                            if (this.$undoCounter < this.$history.length) {
                                this.$undoCounter++;
                            }
                        }

                        if (this.view.remove(pos, size)) {
                            this.repaint();
                            return true;
                        }
                    }
                }
                return false;
            };

            /**
             * Insert the specified text into the edited text at the given position
             * @param  {Integer} pos a start position of a removed text
             * @param  {String} s a text to be inserted
             * @return {Boolean} true if repaint has been requested
             * @method write
             */
            this.write = function (pos,s) {
                if (this.isEditable === true) {
                    // TODO: remove hard coded undo/redo deepness value
                    if (s.length < 10000) {
                        this.$historyPos = (this.$historyPos + 1) % this.$history.length;
                        this.$history[this.$historyPos] = [1, pos, s.length];
                        if (this.$undoCounter < this.$history.length) {
                            this.$undoCounter++;
                        }
                    }

                    // has selection then replace the selection with the given text nevertheless
                    // the requested pos
                    if (this.startOff !== this.endOff) {
                        var start = this.startOff < this.endOff ? this.startOff : this.endOff,
                            end   = this.startOff > this.endOff ? this.startOff : this.endOff;

                        if (this.view.replace(s, start, end - start)) {
                            this.repaint();
                            return true;
                        }
                    } else {
                        if (this.view.write(s, pos)) {
                            this.repaint();
                            return true;
                        }
                    }
                }
                return false;
            };

            this.recalc = function() {
                var r = this.view;
                if (this.position.offset >= 0) {
                    var l = r.getLine(this.position.currentLine);
                    if (this.textAlign === "left") {
                        this.curX = r.font.charsWidth(l, 0, this.position.currentCol) + this.getLeft();
                    } else {
                        this.curX = this.width - this.getRight() - this.view.getPreferredSize().width +
                                    r.font.charsWidth(l, 0, this.position.currentCol);
                    }

                    this.curY = this.position.currentLine * (r.getLineHeight() + r.lineIndent) +
                                this.getTop();
                }

                this.$lineHeight = r.getLineHeight() - 1;
            };

            this.catchScrolled = function(psx, psy) {
                this.repaint();
            };

            /**
             * Draw the text field cursor.
             * @protected
             * @param  {CanvasRenderingContext2D} g a 2D context
             * @method drawCursor
             */
            this.drawCursor = function (g) {
                if (this.position.offset >= 0 &&
                    this.cursorView !== null  &&
                    this.$blinkMe             &&
                    this.hasFocus()              )
                {
                    if (this.textAlign === "left") {
                        this.cursorView.paint(g, this.curX, this.curY,
                                              this.cursorWidth,
                                              (this.cursorHeight === 0 ? this.$lineHeight : this.cursorHeight),
                                              this);
                    } else {
                        this.cursorView.paint(g, this.curX - this.cursorWidth, this.curY,
                                              this.cursorWidth,
                                              (this.cursorHeight === 0 ? this.$lineHeight : this.cursorHeight),
                                              this);
                    }
                }
            };

            this.pointerDragStarted = function (e){
                if (e.isAction() && this.getMaxOffset() > 0) {
                    this.startSelection();
                }
            };

            this.pointerDragEnded =function (e){
                if (e.isAction() && this.hasSelection() === false) {
                    this.clearSelection();
                }
            };

            this.pointerDragged = function (e){
                if (e.isAction()){
                    var p = this.getTextRowColAt(e.x, e.y);
                    if (p !== null) {
                        this.position.setRowCol(p.row, p.col);
                    }
                }
            };

            /**
             * Select the specified part of the edited text
             * @param  {Integer} startOffset a start position of a selected text
             * @param  {Integer} endOffset  an end position of a selected text
             * @method select
             * @chainable
             */
            this.select = function (startOffset, endOffset){
                if (endOffset < startOffset ||
                    startOffset < 0 ||
                    endOffset > this.getMaxOffset())
                {
                    throw new Error("Invalid selection offsets");
                }

                if (this.startOff !== startOffset || endOffset !== this.endOff) {
                    if (startOffset === endOffset) {
                        this.clearSelection();
                    } else {
                        this.startOff = startOffset;
                        var p = this.position.getPointByOffset(startOffset);
                        this.startLine = p[0];
                        this.startCol  = p[1];
                        this.endOff    = endOffset;
                        p = this.position.getPointByOffset(endOffset);
                        this.endLine = p[0];
                        this.endCol  = p[1];

                        this.fire("selected", this);
                        this.repaint();
                    }
                }

                return this;
            };

            /**
             * Tests if the text field has a selected text
             * @return {Boolean} true if the text field has a selected text
             * @method hasSelection
             */
            this.hasSelection = function () {
                return this.startOff !== this.endOff;
            };

            this.posChanged = function (target, po, pl, pc){
                this.recalc();

                var position = this.position;
                if (position.offset >= 0) {

                    this.$blinkMeCounter = 0;
                    this.$blinkMe = true;

                    var lineHeight = this.view.getLineHeight(),
                        top        = this.getTop();

                    this.scrollManager.makeVisible(this.textAlign === "left" ? this.curX
                                                                             : this.curX - this.cursorWidth,
                                                    this.curY, this.cursorWidth, lineHeight);

                    if (pl >= 0) {
                        // means selected text exists, than we have to correct selection
                        // according to the new position
                        if (this.startOff >= 0) {
                            this.endLine = position.currentLine;
                            this.endCol  = position.currentCol;
                            this.endOff  = position.offset;

                            this.fire("selected", this);
                        }

                        var minUpdatedLine = pl < position.currentLine ? pl : position.currentLine,
                            li             = this.view.lineIndent,
                            bottom         = this.getBottom(),
                            left           = this.getLeft(),
                            y1             = lineHeight * minUpdatedLine + minUpdatedLine * li +
                                             top + this.scrollManager.getSY();

                        if (y1 < top) {
                            y1 = top;
                        }

                        if (y1 < this.height - bottom){
                            var h = ((pl > position.currentLine ? pl
                                                                : position.currentLine) - minUpdatedLine + 1) * (lineHeight + li);
                            if (y1 + h > this.height - bottom) {
                                h = this.height - bottom - y1;
                            }
                            this.repaint(left, y1, this.width - left - this.getRight(), h);
                        }
                    } else {
                        this.repaint();
                    }
                }

                this.fire("posChanged", this);
            };

            this.paintOnTop = function(g) {
                if (this.hint !== null && this.getMaxOffset() === 0) {
                    var ps = this.hint.getPreferredSize(),
                        yy = Math.floor((this.height - ps.height)/2),
                        xx = ("left" === this.textAlign) ? this.getLeft() + this.cursorWidth
                                                         : this.width - ps.width - this.getRight() - this.cursorWidth;

                    this.hint.paint(g, xx, yy, this.width, this.height, this);
                }
            };

            /**
             * Set the specified hint text to be drawn with the given font and color.
             * The hint is not-editable text that is shown in empty text field to help
             * a user to understand which input the text field expects.
             * @param {String|zebkit.draw.View|Function} hint a hint text, view or view render method
             * @method setHint
             * @chainable
             */
            this.setHint = function(hint) {
                if (this.hint !== hint) {
                    this.hint = zebkit.isString(hint) ? new this.clazz.HintRender(hint)
                                                      : zebkit.draw.$view(hint);
                    this.repaint();
                }
                return this;
            };

            /**
             * Performs undo operation
             * @method undo
             * @chainable
             */
            this.undo = function() {
                if (this.$undoCounter > 0) {
                    var h = this.$history[this.$historyPos];

                    this.$historyPos--;
                    if (h[0] === 1) {
                        this.remove(h[1], h[2]);
                    }
                    else {
                        this.write (h[1], h[2]);
                    }

                    this.$undoCounter -= 2;
                    this.$redoCounter++;

                    this.$historyPos--;
                    if (this.$historyPos < 0) {
                        this.$historyPos = this.$history.length - 1;
                    }

                    this.repaint();
                }
                return this;
            };

            /**
             * Performs redo operation
             * @method redo
             * @chainable
             */
            this.redo = function() {
                if (this.$redoCounter > 0) {
                    var h = this.$history[(this.$historyPos + 1) % this.$history.length];
                    if (h[0] === 1) {
                        this.remove(h[1], h[2]);
                    } else {
                        this.write (h[1], h[2]);
                    }
                    this.$redoCounter--;
                    this.repaint();
                }
                return this;
            };

            /**
             * Get a starting position (row and column) of a selected text
             * @return {Array} a position of a selected text. First element
             * of is a row and second column of selected text. null if
             * there is no any selected text
             * @method getStartSelection
             */
            this.getStartSelection = function(){
                return this.startOff !== this.endOff ? ((this.startOff < this.endOff) ? { row: this.startLine, col: this.startCol }
                                                                                      : { row: this.endLine, col: this.endCol } )
                                                     : null;
            };

            /**
             * Get an ending position (row and column) of a selected text
             * @return {Array} a position of a selected text. First element
             * of is a row and second column of selected text. null if
             * there is no any selected text
             * @method getEndSelection
             */
            this.getEndSelection = function(){
                return this.startOff !== this.endOff ? ((this.startOff < this.endOff) ? { row : this.endLine,   col : this.endCol   }
                                                                                      : { row : this.startLine, col : this.startCol })
                                                     : null;
            };

            /**
             * Get a selected text
             * @return {String} a selected text
             * @method getSelectedText
             */
            this.getSelectedText = function(){
                return this.startOff !== this.endOff ? this.getSubString(this.view,
                                                                         this.getStartSelection(),
                                                                         this.getEndSelection())
                                                     : null;
            };

            this.getLines = function() {
                return this.position === null ? -1 : this.position.metrics.getLines();
            };

            this.getMaxOffset = function() {
                return this.position === null ? -1 : this.position.metrics.getMaxOffset();
            };

            this.focusGained = function (e){
                if (this.position.offset < 0) {
                    this.position.setOffset(this.textAlign === "left" || this.getLines() > 1 ? 0
                                                                                             : this.getMaxOffset());
                } else if (this.hint !== null) {
                    this.repaint();
                } else {
                    this.repaintCursor();
                }

                if (this.isEditable === true && this.blinkingPeriod > 0) {
                    this.$blinkMeCounter = 0;
                    this.$blinkMe = true;

                    var $this = this;
                    this.$blinkTask = zebkit.util.tasksSet.run(function() {
                            $this.$blinkMeCounter = ($this.$blinkMeCounter + 1) % 3;
                            if ($this.$blinkMeCounter === 0) {
                                $this.$blinkMe = !$this.$blinkMe;
                                $this.repaintCursor();
                            }
                        },
                        Math.floor(this.blinkingPeriod / 3),
                        Math.floor(this.blinkingPeriod / 3)
                    );
                }
            };

            this.focusLost = function(e) {
                this.repaintCursor();
                if (this.isEditable === true) {
                    if (this.hint !== null) {
                        this.repaint();
                    }

                    if (this.blinkingPeriod > 0) {
                        if (this.$blinkTask !== null) {
                            this.$blinkTask.shutdown();
                            this.$blinkTask = null;
                        }
                        this.$blinkMe = true;
                    }
                }
            };

            /**
             * Force text field cursor repainting.
             * @method repaintCursor
             * @protected
             */
            this.repaintCursor = function() {
                if (this.curX > 0 && this.cursorWidth > 0 && (this.cursorHeight > 0 || this.$lineHeight > 0)) {
                    this.repaint(this.curX + this.scrollManager.getSX(),
                                 this.curY + this.scrollManager.getSY(),
                                 this.cursorWidth,
                                 (this.cursorHeight === 0 ? this.$lineHeight : this.cursorHeight));
                }
            };

            /**
             * Clear a text selection.
             * @method clearSelection
             * @chainable
             */
            this.clearSelection = function() {
                if (this.startOff >= 0){
                    var b = this.hasSelection();
                    this.endOff = this.startOff = -1;
                    this.startLine = this.startCol = -1;
                    this.endLine = this.endCol = -1;

                    if (b) {
                        this.repaint();
                        this.fire("selected", this);
                    }
                }
                return this;
            };

            this.pageSize = function (){
                var height = this.height - this.getTop() - this.getBottom(),
                    indent = this.view.lineIndent,
                    textHeight = this.view.getLineHeight();

                return Math.round((height + indent) / (textHeight + indent)) +
                       (((height + indent) % (textHeight + indent) > indent) ? 1 : 0);
            };

            this.clipPaste = function(txt){
                if (txt !== null) {
                    this.removeSelected();
                    this.write(this.position.offset, txt);
                }
            };

            this.clipCopy = function() {
                return this.getSelectedText();
            };

            /**
             * Cut selected text
             * @return {String} a text that has been selected and cut
             * @method  cut
             */
            this.cut = function() {
                var t = this.getSelectedText();
                if (this.isEditable === true) {
                    this.removeSelected();
                }
                return t;
            };

            /**
             * Set the specified cursor position controller
             * @param {zebkit.util.Position} p a position controller
             * @method setPosition
             * @chainable
             */
            this.setPosition = function (p){
                if (this.position !== p) {
                    if (this.position !== null) {
                        this.position.off(this);
                    }
                    this.position = p;
                    if (this.position !== null) {
                        this.position.on(this);
                    }
                    this.invalidate();
                }

                return this;
            };

            /**
             * Set the cursor view. The view defines rendering of the text field
             * cursor.
             * @param {zebkit.draw.View} v a cursor view
             * @method setCursorView
             * @chainable
             */
            this.setCursorView = function (v){
                if (v !== this.cursorView) {
                    this.cursorWidth = 1;
                    this.cursorView = zebkit.draw.$view(v);
                    if (this.cursorView !== null && this.cursorWidth === 0) {
                        this.cursorWidth = this.cursorView.getPreferredSize().width;
                    }
                    this.vrp();
                }

                return this;
            };

            /**
             * Set cursor width.
             * @param {Integer} w a cursor width
             * @method setCursorWidth
             * @chainable
             */
            this.setCursorWidth = function(w) {
                if (w !== this.cursorWidth) {
                    this.cursorWidth = w;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set cursor size.
             * @param {Integer} w a cursor width
             * @param {Integer} h a cursor height
             * @method setCursorSize
             * @chainable
             */
            this.setCursorSize = function(w, h) {
                if (w !== this.cursorWidth || h !== this.cursorHeight) {
                    this.cursorWidth  = w;
                    this.cursorHeight = h;
                    this.vrp();
                }
                return this;
            };

            /**
             * Adjust the size of the text field component to be enough to place the given
             * number of rows and columns.
             * @param {Integer} r a row of the text the height of the text field has to be adjusted
             * @param {Integer} c a column of the text the width of the text field has to be adjusted
             * @method setPSByRowsCols
             * @chainable
             */
            this.setPSByRowsCols = function (r,c){
                var tr = this.view,
                    w  = (c > 0) ? (tr.font.stringWidth("W") * c)
                                 : this.psWidth,
                    h  = (r > 0) ? (r * tr.getLineHeight() + (r - 1) * tr.lineIndent)
                                 : this.psHeight;
                this.setPreferredSize(w, h);
                return this;
            };

            /**
             * Control the text field editable state
             * @param {Boolean} b true to make the text field editable
             * @method setEditable
             * @chainable
             */
            this.setEditable = function (b){
                if (b !== this.isEditable){
                    this.isEditable = b;
                    if (b && this.blinkingPeriod > 0 && this.hasFocus()) {
                        if (this.$blinkTask !== null) {
                            this.$blinkTask.shutdown();
                        }
                        this.$blinkMe = true;
                    }
                    this.vrp();
                }
                return this;
            };

            this.pointerDoubleClicked = function(e){
                if (e.isAction()) {
                    this.select(0, this.getMaxOffset());
                }
            };

            this.pointerPressed = function(e){
                if (e.isAction()) {
                    if (e.shiftKey) {
                        this.startSelection();
                    } else {
                        this.clearSelection();
                    }

                    var p = this.getTextRowColAt(e.x, e.y);
                    if (p !== null) {
                        this.position.setRowCol(p.row, p.col);
                    }
                }
            };

            /**
             * Set selection color or view
             * @param {String|zebkit.draw.View} c a selection color or view
             * @method setSelectView
             * @chainable
             */
            this.setSelectView = function(c) {
                if (c != this.selectView) {
                    this.selectView = zebkit.draw.$view(c);

                    if (this.hasSelection()) {
                        this.repaint();
                    }
                }
                return this;
            };

            this.calcPreferredSize = function (t) {
                var ps = this.view.getPreferredSize();
                ps.width += this.cursorWidth;
                return ps;
            };

            //!!! to maximize optimize performance the method duplicates part of ViewPan.paint() code
            this.paint = function(g){
                var sx = this.scrollManager.getSX(),
                    sy = this.scrollManager.getSY(),
                    l  = this.getLeft(),
                    t  = this.getTop(),
                    r  = this.getRight();

                try {
                    g.translate(sx, sy);

                    if (this.textAlign === "left") {
                        this.view.paint(g, l, t,
                                        this.width  - l - r,
                                        this.height - t - this.getBottom(), this);
                    } else {
                        this.view.paint(g, this.width - r - this.view.getPreferredSize().width, t,
                                           this.width  - l - r,
                                           this.height - t - this.getBottom(), this);
                    }

                    this.drawCursor(g);
                } catch(e) {
                    g.translate(-sx, -sy);
                    throw e;
                }
                g.translate(-sx, -sy);
            };
        },

        function setView(v){
            if (v != this.view) {
                if (this.view !== null && this.view.off !== undefined) {
                    this.view.off(this);
                }

                this.$super(v);
                if (this.position === null) {
                    this.setPosition(new zebkit.util.Position(this.view));
                } else {
                    this.position.setMetric(this.view);
                }

                if (this.view !== null && this.view.on !== undefined) {
                    this.view.on(this);
                }
            }
            return this;
        },

        /**
         * Set the text content of the text field component
         * @param {String} s a text the text field component has to be filled
         * @method setValue
         * @chainable
         */
        function setValue(s) {
            var txt = this.getValue();
            if (txt !== s){
                if (this.position !== null) {
                    this.position.setOffset(0);
                }
                this.scrollManager.scrollTo(0, 0);
                this.$super(s);
            }
            return this;
        },

        function setEnabled(b){
            this.clearSelection();
            this.$super(b);
            return this;
        }
    ]).events("updated", "selected", "posChanged");

    /**
     * Text area UI component. The UI component to render multi-lines text.
     * @class zebkit.ui.TextArea
     * @constructor
     * @param {String} [txt] a text
     * @extends zebkit.ui.TextField
     */
    pkg.TextArea = Class(pkg.TextField, [
        function(txt) {
            if (arguments.length === 0) {
                txt = "";
            }
            this.$super(new zebkit.data.Text(txt));
        }
    ]);

    /**
     * Password text field.
     * @class zebkit.ui.PassTextField
     * @constructor
     * @param {String} txt password text
     * @param {Integer} [maxSize] maximal size
     * @param {Boolean} [showLast] indicates if last typed character should
     * not be disguised with a star character
     * @extends zebkit.ui.TextField
     */
    pkg.PassTextField = Class(pkg.TextField, [
        function(txt, size, showLast) {
            if (arguments.length === 1) {
                showLast = false;
                size     = -1;

                if (zebkit.isBoolean(txt)) {
                    showLast = txt;
                    txt      = "";
                } else if (zebkit.isNumber(txt)) {
                    size = txt;
                    txt = "";
                }
            } else if (arguments.length === 0) {
                showLast = false;
                size     = -1;
                txt      = "";
            } else if (arguments.length === 2) {
                showLast = false;
            }

            var pt = new zebkit.draw.PasswordText(new zebkit.data.SingleLineTxt(txt, size));
            pt.showLast = showLast;
            this.$super(pt);
            if (size > 0) {
                this.setPSByRowsCols(-1, size);
            }
        },

        function $prototype() {
            /**
             * Set flag that indicates if the last password character has to be visible.
             * @param {Boolean} b a boolean flag that says if last password character has
             * to be visible.
             * @method setShowLast
             * @chainable
             */
            this.setShowLast = function(b) {
                if (this.showLast !== b) {
                    this.view.showLast = b;
                    this.repaint();
                }
                return this;
            };
        }
    ]);


    /**
     * Base UI list component class that has to be extended with a
     * concrete list component implementation. The list component
     * visualizes list data model (zebkit.data.ListModel).
     * @class  zebkit.ui.BaseList
     * @constructor
     * @param {zebkit.data.ListModel|Array} [m] a list model that should be passed as an instance
     * of zebkit.data.ListModel or as an array.
     * @param {Boolean} [b] true if the list navigation has to be triggered by
     * pointer cursor moving
     * @extends zebkit.ui.Panel
     * @uses zebkit.util.Position.Metric
     * @uses zebkit.ui.HostDecorativeViews
     */

    /**
     * Fire when a list item has been selected:
     *
     *     list.on("selected", function(src, prev) {
     *         ...
     *     });
     *
     * @event selected
     * @param {zebkit.ui.BaseList} src a list that triggers the event
     * @param {Integer|Object} prev a previous selected index, return null if the selected item has been re-selected
     */
    pkg.BaseList = Class(pkg.Panel, zebkit.util.Position.Metric, pkg.HostDecorativeViews, [
        function (m, b) {
            if (arguments.length === 0) {
                m = [];
                b = false;
            } else if (arguments.length === 1) {
                if (zebkit.isBoolean(m))  {
                    b = m;
                    m = [];
                } else {
                    b = false;
                }
            } else if (m === null) {
                m = [];
            }

            /**
             * Currently selected list item index
             * @type {Integer}
             * @attribute selectedIndex
             * @default -1
             * @readOnly
             */
            this.selectedIndex = -1;

            /**
             * Indicate the current mode the list items selection has to work
             * @readOnly
             * @default false
             * @attribute isComboMode
             * @type {Boolean}
             */
            this.isComboMode = b;

            /**
             * Scroll manager
             * @attribute scrollManager
             * @readOnly
             * @protected
             * @type {zebkit.ui.ScrollManager}
             */
            this.scrollManager = new pkg.ScrollManager(this);

            this.$super();

            // position manager should be set before model initialization
            this.setPosition(new zebkit.util.Position(this));

            /**
             * List model
             * @readOnly
             * @attribute model
             */
            this.setModel(m);
        },

        function $prototype() {
            this.scrollManager = null;

            /**
             * Makes the component focusable
             * @attribute canHaveFocus
             * @type {Boolean}
             * @default true
             */
            this.canHaveFocus = true;

            /**
             * List model the component visualizes
             * @attribute model
             * @type {zebkit.data.ListModel}
             * @readOnly
             */
            this.model = null;

            /**
             * Position manager.
             * @attribute position
             * @type {zebkit.util.Position}
             * @readOnly
             */
            this.position = null;

            /**
             * Select the specified list item.
             * @param {Object} v a list item to be selected. Use null as
             * the parameter value to clean an item selection
             * @return {Integer} an index of a selected item
             * @method setValue
             */
            this.setValue = function(v) {
                if (v === null) {
                    this.select(-1);
                } else if (this.model !== null) {
                    for(var i = 0; i < this.model.count(); i++) {
                        if (this.model.get(i) === v && this.isItemSelectable(i)) {
                            this.select(i);
                            return i;
                        }
                    }
                }
                return -1;
            };

            /**
             * Get the list component selected item
             * @return {Object} a selected item
             * @method getValue
             */
            this.getValue = function() {
                return this.getSelected();
            };

            /**
             * Test if the given item is selectable.
             * @param  {Integer}  i an item index
             * @return {Boolean}  true if the given item is selectable
             * @method isItemSelectable
             */
            this.isItemSelectable = function(i) {
                return true;
            };

            /**
             * Get selected list item
             * @return {Object} an item
             * @method getSelected
             */
            this.getSelected = function() {
                return this.selectedIndex < 0 ? null
                                              : this.model.get(this.selectedIndex);
            };

            /**
             * Lookup a list item buy the given first character
             * @param  {String} ch a first character to lookup
             * @return {Integer} a position of found list item in the list or -1 if no item is found.
             * @method lookupItem
             * @protected
             */
            this.lookupItem = function(ch){
                var count = this.model === null ? 0 : this.model.count();
                if (zebkit.util.isLetter(ch) && count > 0){
                    var index = this.selectedIndex < 0 ? 0 : this.selectedIndex + 1;
                    ch = ch.toLowerCase();
                    for(var i = 0;i < count - 1; i++){
                        var idx  = (index + i) % count,
                            item = this.model.get(idx).toString();

                        if (this.isItemSelectable(idx) && item.length > 0 && item[0].toLowerCase() === ch) {
                            return idx;
                        }
                    }
                }
                return -1;
            };

            /**
             * Test if the given list item is selected
             * @param  {Integer}  i an item index
             * @return {Boolean}  true if the item with the given index is selected
             * @method isSelected
             */
            this.isSelected = function(i) {
                return i === this.selectedIndex;
            };

            /**
             * Called when a pointer (pointer or finger on touch screen) is moved
             * to a new location
             * @param  {Integer} x a pointer x coordinate
             * @param  {Integer} y a pointer y coordinate
             * @method $pointerMoved
             * @protected
             */
            this.$pointerMoved = function(x, y){
                if (this.isComboMode === true && this.model !== null) {
                    var index = this.getItemIdxAt(x, y);
                    if (index !== this.position.offset && (index < 0 || this.isItemSelectable(index) === true)) {
                        this.$triggeredByPointer = true;

                        if (index < 0) {
                            this.position.setOffset(null);
                        } else {
                            this.position.setOffset(index);
                        }
                        this.makeItemVisible(index);
                        this.$triggeredByPointer = false;
                    }
                }
            };

            /**
             * Return the given list item location.
             * @param  {Integer} i a list item index
             * @return {Object}  a location of the list item. The result is object that
             * has the following structure:
                    { x:{Integer}, y:{Integer} }
             * @method getItemLocation
             */
            this.getItemLocation = function(index) {
                this.validate();

                var y = this.getTop() + this.scrollManager.getSY();
                for(var i = 0; i < index; i++) {
                    y += this.getItemSize(i).height;
                }

                return { x:this.getLeft(), y:y };
            };

            /**
             * Return the given list item size.
             * @param  {Integer} i a list item index
             * @return {Object}  a size of the list item. The result is object that
             * has the following structure:
                    { width:{Integer}, height:{Integer} }
             * @method getItemSize
             */
            this.getItemSize = function(i) {
                throw new Error("Not implemented");
            };

            this.getLines = function() {
                return this.model === null ? 0 : this.model.count();
            };

            this.getLineSize = function(l) {
                return 1;
            };

            this.getMaxOffset = function() {
                return this.getLines() - 1;
            };

            this.catchScrolled = function(psx, psy) {
                this.repaint();
            };

            /**
             * Detect an item by the specified location
             * @param  {Integer} x a x coordinate
             * @param  {Integer} y a y coordinate
             * @return {Integer} a list item that is located at the given position.
             * -1 if no any list item can be found.
             * @method getItemIdxAt
             */
            this.getItemIdxAt = function(x,y) {
                return -1;
            };

            /**
             * Calculate maximal width and maximal height the items in the list have
             * @protected
             * @return {Integer} a max items size
             * @method calcMaxItemSize
             */
            this.calcMaxItemSize = function (){
                var maxH = 0,
                    maxW = 0;

                this.validate();
                if (this.model !== null) {
                    for(var i = 0;i < this.model.count(); i++){
                        var is = this.getItemSize(i);
                        if (is.height > maxH) {
                            maxH = is.height;
                        }

                        if (is.width  > maxW) {
                            maxW = is.width;
                        }
                    }
                }
                return { width:maxW, height:maxH };
            };

            /**
             * Force repainting of the given list items
             * @protected
             * @param  {Integer} p an index of the first list item to be repainted
             * @param  {Integer} n an index of the second list item to be repainted
             * @method repaintByOffsets
             */
            this.repaintByOffsets = function(p, n) {
                this.validate();
                var xx    = this.width - this.getRight(),
                    l     = 0,
                    count = this.model === null ? 0
                                                : this.model.count();

                if (p >= 0 && p < count){
                    l = this.getItemLocation(p);
                    this.repaint(l.x, l.y, xx - l.x, this.getItemSize(p).height);
                }

                if (n >= 0 && n < count){
                    l = this.getItemLocation(n);
                    this.repaint(l.x, l.y, xx - l.x, this.getItemSize(n).height);
                }
            };

            /**
             * Draw the given list view element identified by the given id
             * on the given list item.
             * @param  {CanvasRenderingContext2D} g     a graphical context
             * @param  {String}     id    a view id
             * @param  {Integer}    index a list item index
             * @protected
             * @method drawViewAt
             */
            this.drawViewAt = function(g, id, index) {
                if (index >= 0 && this.views.hasOwnProperty(id) && this.views[id] !== null && this.isItemSelectable(index)) {
                    var is  = this.getItemSize(index),
                        l   = this.getItemLocation(index);

                    this.drawView(g, id, this.views[id],
                                  l.x, l.y,
                                  is.width ,
                                  is.height);
                }
            };

            /**
             * Draw the given list view element identified by the given id
             * at the specified location.
             * @param  {CanvasRenderingContext2D} g     a graphical context
             * @param  {String}     id    a view id
             * @param  {Integer}    x a x coordinate the view has to be drawn
             * @param  {Integer}    y a y coordinate the view has to be drawn
             * @param  {Integer}    w a view width
             * @param  {Integer}    h a view height
             * @protected
             * @method drawView
             */
            this.drawView = function(g, id, v, x, y, w ,h) {
                this.views[id].paint(g, x, y, w, h, this);
            };

            this.update = function(g) {
                if (this.isComboMode === true || this.hasFocus() === true)  {
                    this.drawViewAt(g, "marker", this.position.offset);
                }
                this.drawViewAt(g, "select", this.selectedIndex);
            };

            this.paintOnTop = function(g) {
                if (this.isComboMode === true || this.hasFocus())  {
                    this.drawViewAt(g, "topMarker", this.position.offset);
                }
            };

            /**
             * Select the given list item
             * @param  {Integer} index an item index to be selected
             * @method select
             */
            this.select = function(index){
                if (index === null || index === undefined) {
                    throw new Error("Null index");
                }

                if (this.model !== null && index >= this.model.count()){
                    throw new RangeError(index);
                }

                if (this.selectedIndex !== index) {
                    if (index < 0 || this.isItemSelectable(index)) {
                        var prev = this.selectedIndex;
                        this.selectedIndex = index;
                        this.makeItemVisible(index);
                        this.repaintByOffsets(prev, this.selectedIndex);
                        this.fireSelected(prev);
                    }
                } else {
                    this.fireSelected(null);
                }
            };

            /**
             * Fire selected event
             * @param  {Integer|null} prev a previous selected item index. null if the
             * same item has been re-selected
             * @method fireSelected
             * @protected
             */
            this.fireSelected = function(prev) {
                this.fire("selected", [this, prev]);
            };

            this.pointerClicked = function(e) {
                if (this.model !== null && e.isAction() && this.model.count() > 0) {
                    this.$select(this.position.offset < 0 ? 0 : this.position.offset);
                }
            };

            this.pointerReleased = function(e){
                if (this.model !== null    &&
                    this.model.count() > 0 &&
                    e.isAction()           &&
                    this.position.offset !== this.selectedIndex)
                {
                    this.position.setOffset(this.selectedIndex);
                }
            };

            this.pointerPressed = function(e){
                if (e.isAction() && this.model !== null && this.model.count() > 0) {
                    var index = this.getItemIdxAt(e.x, e.y);
                    if (index >= 0 && this.position.offset !== index && this.isItemSelectable(index)) {
                        this.position.setOffset(index);
                    }
                }
            };

            this.pointerDragged = this.pointerMoved = this.pointerEntered = function(e){
                this.$pointerMoved(e.x, e.y);
            };

            this.pointerExited = function(e){
                this.$pointerMoved(-10, -10);
            };

            this.pointerDragEnded = function(e){
                if (this.model !== null && this.model.count() > 0 && this.position.offset >= 0) {
                    this.select(this.position.offset < 0 ? 0 : this.position.offset);
                }
            };

            this.keyPressed = function(e){
                if (this.model !== null && this.model.count() > 0){
                    switch(e.code) {
                        case "End":
                            if (e.ctrlKey) {
                                this.position.setOffset(this.position.metrics.getMaxOffset());
                            } else {
                                this.position.seekLineTo("end");
                            }
                            break;
                        case "Home":
                            if (e.ctrlKey) {
                                this.position.setOffset(0);
                            } else {
                                this.position.seekLineTo("begin");
                            }
                            break;
                        case "ArrowRight": this.position.seek(1); break;
                        case "ArrowDown" : this.position.seekLineTo("down"); break;
                        case "ArrowLeft" : this.position.seek(-1);break;
                        case "ArrowUp"   : this.position.seekLineTo("up");break;
                        case "PageUp"    : this.position.seek(this.pageSize(-1));break;
                        case "PageDown"  : this.position.seek(this.pageSize(1));break;
                        case "Space"     :
                        case "Enter"     : this.$select(this.position.offset); break;
                    }
                }
            };

            /**
             * Select the given list item. The method is called when an item
             * selection is triggered by a user interaction: key board, or pointer
             * @param  {Integer} o an item index
             * @method $select
             * @protected
             */
            this.$select = function(o) {
                this.select(o);
            };

            /**
             * Define key typed events handler
             * @param  {zebkit.ui.event.KeyEvent} e a key event
             * @method keyTyped
             */
            this.keyTyped = function (e){
                var i = this.lookupItem(e.key);
                if (i >= 0) {
                    this.$select(i);
                }
            };

            this.elementInserted = function(target, e,index){
                this.invalidate();
                if (this.selectedIndex >= 0 && this.selectedIndex >= index) {
                    this.selectedIndex++;
                }
                this.position.inserted(index, 1);
                this.repaint();
            };

            this.elementRemoved = function(target, e,index){
                this.invalidate();
                if (this.selectedIndex === index || this.model.count() === 0) {
                    this.select(-1);
                } else {
                    if (this.selectedIndex > index) {
                        this.selectedIndex--;
                    }
                }
                this.position.removed(index, 1);
                this.repaint();
            };

            this.elementSet = function (target, e, pe,index){
                if (this.selectedIndex === index) {
                    this.select(-1);
                }
                this.vrp();
            };

            /**
             * Find a next selectable list item starting from the given offset
             * with the specified direction
             * @param  {Integer} off a start item index to perform search
             * @param  {Integer} d   a direction increment. Cam be -1 or 1
             * @return {Integer} a next selectable item index
             * @method findSelectable
             * @protected
             */
            this.findSelectable = function(off, d) {
                var c = this.model.count(), i = 0, dd = Math.abs(d);
                while (this.isItemSelectable(off) === false && i < c) {
                    off = (c + off + d) % c;
                    i += dd;
                }
                return i < c ? off : -1;
            };

            this.posChanged = function (target, prevOffset, prevLine, prevCol) {
                var off = this.position.offset;
                if (off >= 0) {
                    off = this.findSelectable(off, prevOffset < off ? 1 : -1);

                    if (off !== this.position.offset) {
                        this.position.setOffset(off);
                        this.repaintByOffsets(prevOffset, off);
                        return;
                    }
                }

                if (this.isComboMode === true) {
                    this.makeItemVisible(off);
                } else {
                    this.select(off);
                }

                // this.makeItemVisible(off);
                this.repaintByOffsets(prevOffset, off);
            };

            /**
             * Set the list model to be rendered with the list component
             * @param {zebkit.data.ListModel} m a list model
             * @method setModel
             * @chainable
             */
            this.setModel = function (m){
                if (m !== this.model) {
                    if (m !== null && Array.isArray(m)) {
                        m = new zebkit.data.ListModel(m);
                    }

                    if (this.model !== null) {
                        this.model.off(this);
                    }

                    this.model = m;

                    if (this.model !== null) {
                        this.model.on(this);
                    }

                    this.vrp();
                }
                return this;
            };

            /**
             * Set the given position controller. List component uses position to
             * track virtual cursor.
             * @param {zebkit.util.Position} c a position
             * @method setPosition
             * @chainable
             */
            this.setPosition = function(c) {
                if (c !== this.position) {
                    if (this.position !== null) {
                        this.position.off(this);
                    }
                    this.position = c;
                    this.position.on(this);
                    this.position.setMetric(this);
                    this.repaint();
                }

                return this;
            };

            /**
             * Set the list items view provider. Defining a view provider allows developers
             * to customize list item rendering.
             * @param {Object|Function} v a view provider class instance or a function that
             * says which view has to be used for the given list model data. The function
             * has to satisfy the following method signature: "function(list, modelItem, index)"
             * @method setViewProvider
             * @chainable
             */
            this.setViewProvider = function (v){
                if (this.provider !== v) {
                    if (typeof v === "function") {
                        var o = new zebkit.Dummy();
                        o.getView = v;
                        v = o;
                    }

                    this.provider = v;
                    this.vrp();
                }
                return this;
            };

            /**
             * Scroll if necessary the given item to make it visible
             * @param  {Integer} index an item index
             * @chainable
             * @method makeItemVisible
             */
            this.makeItemVisible = function (index){
                if (index >= 0 && this.scrollManager !== null) {
                    this.validate();
                    var is = this.getItemSize(index);

                    if (is.width > 0 && is.height > 0) {
                        var l = this.getItemLocation(index);
                        this.scrollManager.makeVisible(l.x - this.scrollManager.getSX(),
                                                       l.y - this.scrollManager.getSY(),
                                                       is.width, is.height);
                    }
                }
                return this;
            };

            this.makeSelectedVisible = function(){
                if (this.selectedIndex >= 0) {
                    this.makeItemVisible(this.selectedIndex);
                }
                return this;
            };

            /**
             * The method returns the page size that has to be scroll up or down
             * @param  {Integer} d a scrolling direction. -1 means scroll up, 1 means
             * scroll down
             * @return {Integer} a number of list items to be scrolled
             * @method pageSize
             * @protected
             */
            this.pageSize = function(d){
                var offset = this.position.offset;
                if (offset >= 0) {
                    var vp = pkg.$cvp(this, {});
                    if (vp !== null) {
                        var sum = 0, i = offset;
                        for(;i >= 0 && i <= this.position.metrics.getMaxOffset() && sum < vp.height; i += d){
                            sum += (this.getItemSize(i).height);
                        }
                        return i - offset - d;
                    }
                }
                return 0;
            };
        },

        /**
         * Sets the views for the list visual elements. The following elements are
         * supported:
         *
         *   - "select" -  a selection view element
         *   - "topMarker" - a position marker view element that is rendered  on top of list item
         *   - "marker" - a position marker view element
         *
         * @param {Object} views view elements
         * @method setViews
         */
        function focused(){
            this.$super();
            this.repaint();
        }
    ]).events("selected");

    /**
     * The class is list component implementation that visualizes zebkit.data.ListModel.
     * It is supposed the model can have any type of items. Visualization of the items
     * is customized by defining a view provider.
     *
     * The general use case:
     *
     *       // create list component that contains three item
     *       var list = new zebkit.ui.List([
     *           "Item 1",
     *           "Item 2",
     *           "Item 3"
     *       ]);
     *
     *       ...
     *       // add new item
     *       list.model.add("Item 4");
     *
     *       ...
     *       // remove first item
     *       list.model.removeAt(0);
     *
     *
     * To customize list items views you can redefine item view provider as following:
     *
     *       // suppose every model item is an array that contains two elements,
     *       // first element points to the item icon and the second element defines
     *       // the list item text
     *       var list = new zebkit.ui.List([
     *           [ "icon1.gif", "Caption 1" ],
     *           [ "icon2.gif", "Caption 1" ],
     *           [ "icon3.gif", "Caption 1" ]
     *       ]);
     *
     *       // define new list item views provider that represents every
     *       // list model item as icon with a caption
     *       list.setViewProvider(new zebkit.ui.List.ViewProvider([
     *           function getView(target, i, value) {
     *               var caption = value[1];
     *               var icon    = value[0];
     *               return new zebkit.ui.CompRender(new zebkit.ui.ImageLabel(caption, icon));
     *           }
     *       ]));
     *
     * @class  zebkit.ui.List
     * @extends zebkit.ui.BaseList
     * @constructor
     * @param {zebkit.data.ListModel|Array} [model] a list model that should be passed as an instance
     * of zebkit.data.ListModel or as an array.
     * @param {Boolean} [isComboMode] true if the list navigation has to be triggered by
     * pointer cursor moving
     */
    pkg.List = Class(pkg.BaseList, [
        function (m, b){
            /**
             * Index of the first visible list item
             * @readOnly
             * @attribute firstVisible
             * @type {Integer}
             * @private
             */
            this.firstVisible = -1;

            /**
             * Y coordinate of the first visible list item
             * @readOnly
             * @attribute firstVisibleY
             * @type {Integer}
             * @private
             */
            this.firstVisibleY = this.psWidth_ = this.psHeight_ = 0;

            /**
             * Internal flag to track list items visibility status. It is set
             * to false to trigger list items metrics and visibility recalculation
             * @attribute visValid
             * @type {Boolean}
             * @private
             */
            this.visValid = false;
            this.setViewProvider(new this.clazz.ViewProvider());
            this.$supera(arguments);
        },

        function $clazz() {
            /**
             * List view provider class. This implementation renders list item using string
             * render. If a list item is an instance of "zebkit.draw.View" class than it will
             * be rendered as the view.
             * @class zebkit.ui.List.ViewProvider
             * @extends zebkit.draw.BaseViewProvider
             * @constructor
             */
            this.ViewProvider = Class(zebkit.draw.BaseViewProvider, []);

            this.Item = Class([
                function(value, caption) {
                    this.value = value;
                    if (arguments.length > 1) {
                        this.caption = caption;
                    } else {
                        this.caption = value;
                    }
                },

                function $prototype() {
                    this.toString = function() {
                        return this.caption;
                    };
                }
            ]);

            this.ItemViewProvider = Class(zebkit.draw.BaseViewProvider, [
                function getView(t, i, v) {
                    if (v !== null) {
                        if (typeof v.getCaption === 'function') {
                            v = v.getCaption();
                        } else if (v.caption !== undefined) {
                            v = v.caption;
                        }
                    }
                    return this.$super(t, i, v);
                }
            ]);

            /**
             * @for zebkit.ui.List
             */
        },

        function $prototype() {
            this.heights = this.widths = this.vArea = null;

            /**
             * Extra list item side gaps
             * @type {Integer}
             * @attribute gap
             * @default 2
             * @readOnly
             */
            this.gap = 2;

            /**
             * Set the left, right, top and bottom a list item paddings
             * @param {Integer} g a left, right, top and bottom a list item paddings
             * @method setItemGap
             * @chainable
             */
            this.setItemGap = function(g){
                if (this.gap !== g){
                    this.gap = g;
                    this.vrp();
                }
                return this;
            };

            this.paint = function(g){
                this.vVisibility();
                if (this.firstVisible >= 0){
                    var sx = this.scrollManager.getSX(),
                        sy = this.scrollManager.getSY();

                    try {
                        g.translate(sx, sy);
                        var y        = this.firstVisibleY,
                            x        = this.getLeft(),
                            yy       = this.vArea.y + this.vArea.height - sy,
                            count    = this.model.count(),
                            dg       = this.gap * 2;

                        for (var i = this.firstVisible; i < count; i++){
                            if (i !== this.selectedIndex && typeof this.provider.getCellColor === 'function') {
                                var bc = this.provider.getCellColor(this, i);
                                if (bc !== null) {
                                    g.setColor(bc);
                                    g.fillRect(x, y, this.width, this.heights[i]);
                                }
                            }

                            this.provider.getView(this, i, this.model.get(i))
                                         .paint(g, x + this.gap, y + this.gap,
                                                   this.widths[i] - dg,
                                                   this.heights[i]- dg, this);

                            y += this.heights[i];
                            if (y > yy) {
                                break;
                            }
                        }

                        g.translate(-sx,  -sy);
                    } catch(e) {
                        g.translate(-sx,  -sy);
                        throw e;
                    }
                }
            };

            this.setColor = function(c) {
                this.provider.setColor(c);
                return this;
            };

            this.setFont = function(f) {
                this.provider.setFont(f);
                return this;
            };

            this.recalc = function(){
                this.psWidth_ = this.psHeight_ = 0;
                if (this.model !== null) {
                    var count = this.model.count();
                    if (this.heights === null || this.heights.length !== count) {
                        this.heights = Array(count);
                    }

                    if (this.widths  === null || this.widths.length  !== count) {
                        this.widths = Array(count);
                    }

                    var provider = this.provider;
                    if (provider !== null) {
                        var dg = 2 * this.gap;
                        for(var i = 0;i < count; i++){
                            var ps = provider.getView(this, i, this.model.get(i)).getPreferredSize();
                            this.heights[i] = ps.height + dg;
                            this.widths [i] = ps.width  + dg;

                            if (this.widths[i] > this.psWidth_) {
                                this.psWidth_ = this.widths[i];
                            }
                            this.psHeight_ += this.heights[i];
                        }
                    }
                }
            };

            this.calcPreferredSize = function(l){
                return { width : this.psWidth_,
                         height: this.psHeight_ };
            };

            this.vVisibility = function(){
                this.validate();
                var prev = this.vArea;
                this.vArea = pkg.$cvp(this, {});

                if (this.vArea === null) {
                    this.firstVisible = -1;
                } else  {
                    if (this.visValid === false ||
                        (prev === null || prev.x !== this.vArea.x ||
                         prev.y !== this.vArea.y || prev.width !== this.vArea.width ||
                         prev.height !== this.vArea.height))
                    {
                        var top = this.getTop();
                        if (this.firstVisible >= 0){
                            var dy = this.scrollManager.getSY();
                            while (this.firstVisibleY + dy >= top && this.firstVisible > 0){
                                this.firstVisible--;
                                this.firstVisibleY -= this.heights[this.firstVisible];
                            }
                        } else {
                            this.firstVisible  = 0;
                            this.firstVisibleY = top;
                        }

                        if (this.firstVisible >= 0) {
                            var count = this.model === null ? 0 : this.model.count(),
                                hh    = this.height - this.getBottom();

                            for(; this.firstVisible < count; this.firstVisible++) {
                                var y1 = this.firstVisibleY + this.scrollManager.getSY(),
                                    y2 = y1 + this.heights[this.firstVisible] - 1;

                                if ((y1 >= top && y1 < hh) || (y2 >= top && y2 < hh) || (y1 < top && y2 >= hh)) {
                                    break;
                                }

                                this.firstVisibleY += (this.heights[this.firstVisible]);
                            }

                            if (this.firstVisible >= count) {
                                this.firstVisible =  -1;
                            }
                        }
                        this.visValid = true;
                    }
                }
            };

            this.getItemLocation = function(index){
                this.validate();
                var y = this.getTop() + this.scrollManager.getSY();
                for(var i = 0; i < index; i++) {
                    y += this.heights[i];
                }
                return { x:this.getLeft(), y : y };
            };

            this.getItemSize = function(i){
                this.validate();
                return { width:this.widths[i], height:this.heights[i] };
            };

            this.getItemIdxAt = function(x,y){
                this.vVisibility();
                if (this.vArea !== null && this.firstVisible >= 0) {
                    var yy    = this.firstVisibleY + this.scrollManager.getSY(),
                        hh    = this.height - this.getBottom(),
                        count = this.model.count();

                    for (var i = this.firstVisible; i < count; i++) {
                        if (y >= yy && y < yy + this.heights[i]) {
                            return i;
                        }

                        yy += (this.heights[i]);
                        if (yy > hh) {
                            break;
                        }
                    }
                }
                return  -1;
            };
        },

        function invalidate(){
            this.visValid = false;
            this.firstVisible = -1;
            this.$super();
        },

        function drawView(g,id,v,x,y,w,h) {
            this.$super(g, id, v, x, y, this.width - this.getRight() - x, h);
        },

        function catchScrolled(psx,psy){
            this.firstVisible = -1;
            this.visValid = false;
            this.$super(psx, psy);
        }
    ]);

    /**
     * List component consider its children UI components as a list model items. Every added to the component
     * UI children component becomes a list model element. The implementation allows developers to use
     * other UI components as its elements what makes list item view customization very easy and powerful:
     *
     *     // use image label as the component list items
     *     var list = new zebkit.ui.CompList();
     *     list.add(new zebkit.ui.ImageLabel("Caption 1", "icon1.gif"));
     *     list.add(new zebkit.ui.ImageLabel("Caption 2", "icon2.gif"));
     *     list.add(new zebkit.ui.ImageLabel("Caption 3", "icon3.gif"));
     *
     *
     * @class zebkit.ui.CompList
     * @constructor
     * @extends zebkit.ui.BaseList
     * @param {Boolean} [isComboMode] true if the list navigation has to be triggered by
     * pointer cursor moving
     */
    pkg.CompList = Class(pkg.BaseList, [
        function (b) {
            this.model = this;

            this.setViewProvider(new zebkit.Dummy([
                function $prototype() {
                    this.render = new pkg.CompRender();
                    this.getView = function (target,i, obj) {
                        this.render.setValue(obj);
                        return this.render;
                    };
                }
            ]));

            this.$supera(arguments);
        },

        function $clazz() {
            this.Label      = Class(pkg.Label, []);
            this.ImageLabel = Class(pkg.ImageLabel, []);
            this.ScrollableLayout = Class(pkg.ScrollManager, [
                function(layout, target) {
                    this.layout = layout;
                    this.$super(target);
                },

                function $prototype() {
                    this.calcPreferredSize = function(t) {
                        return this.layout.calcPreferredSize(t);
                    };

                    this.doLayout = function(t){
                        this.layout.doLayout(t);
                        for(var i = 0; i < t.kids.length; i++){
                            var kid = t.kids[i];
                            if (kid.isVisible === true) {
                                kid.setLocation(kid.x + this.getSX(),
                                                kid.y + this.getSY());
                            }
                        }
                    };

                    this.scrollStateUpdated = function(sx,sy,px,py){
                        this.target.vrp();
                    };
                }
            ]);
        },

        function $prototype() {
            this.max = null;

            this.get = function(i) {
                if (i < 0 || i >= this.kids.length) {
                    throw new RangeError(i);
                }
                return this.kids[i];
            };

            this.contains = function (c) {
                return this.indexOf(c) >= 0;
            };

            this.count = function () {
                return this.kids.length;
            };

            this.catchScrolled = function(px, py) {};

            this.getItemLocation = function(i) {
                return { x:this.kids[i].x, y:this.kids[i].y };
            };

            this.getItemSize = function(i) {
                return this.kids[i].isVisible === false ? { width:0, height: 0 }
                                                        : { width:this.kids[i].width,
                                                            height:this.kids[i].height};
            };

            this.recalc = function (){
                this.max = zebkit.layout.getMaxPreferredSize(this);
            };

            this.calcMaxItemSize = function() {
                this.validate();
                return { width:this.max.width, height:this.max.height };
            };

            this.getItemIdxAt = function(x, y) {
                return zebkit.layout.getDirectAt(x, y, this);
            };

            this.isItemSelectable = function(i) {
                return this.model.get(i).isVisible === true &&
                       this.model.get(i).isEnabled === true;
            };

            this.catchInput = function(child){
                if (this.isComboMode !== true) {
                    var p = child;
                    while (p !== this) {
                        if (p.stopCatchInput === true) {
                            return false;
                        }
                        p = p.parent;
                    }
                }
                return true;
            };

            this.makeComponent = function(e) {
                return new pkg.Label(e.toString());
            };
        },

        function setModel(m) {
            if (Array.isArray(m)) {
                for(var i = 0; i < m.length; i++) {
                    this.add(m[i]);
                }
            } else {
                throw new Error("Model cannot be updated");
            }

            return this;
        },

        function elementInserted(target, e, index) {
            this.insert(index, null, this.makeComponent(e));
            this.$super(target, e, index);
        },

        function setPosition(c) {
            if (c !== this.position){
                this.$super(c);
                if (zebkit.instanceOf(this.layout, zebkit.util.Position.Metric)) {
                    c.setMetric(this.layout);
                }
            }
            return this;
        },

        function setLayout(layout){
            if (layout !== this.layout){
                this.scrollManager = new this.clazz.ScrollableLayout(layout, this);
                this.$super(this.scrollManager);
                if (this.position !== null) {
                    this.position.setMetric(zebkit.instanceOf(layout, zebkit.util.Position.Metric) ? layout : this);
                }
            }

            return this;
        },

        function setAt(i, item) {
            if (i < 0 || i >= this.kids.length) {
                throw new RangeError(i);
            }
            return this.$super(i, item);
        },

        function insert(i, constr, e) {
            if (arguments.length === 2) {
                e = constr;
                constr = null;
            }

            if (i < 0 || i > this.kids.length) {
                throw new RangeError(i);
            }

            return this.$super(i, constr, pkg.$component(e, this));
        },

        function kidAdded(index,constr,comp){
            this.$super(index, constr, comp);
            if (this.model === this) {
                this.model.fire("elementInserted", [this, comp, index]);
            }
        },

        function kidRemoved(index, e, xtr) {
            this.$super(index, e, ctr);
            if (this.model === this) {
                this.model.fire("elementRemoved", [this, e, index]);
            }
        }
    ]).events("elementInserted", "elementRemoved", "elementSet");


    /**
     * Combo box UI component class. Combo uses a list component to show in drop down window.
     * You can use any available list component implementation:

            // use simple list as combo box drop down window
            var combo = new zebkit.ui.Combo(new zebkit.ui.List([
                "Item 1",
                "Item 2",
                "Item 3"
            ]));


            // use component list as combo box drop down window
            var combo = new zebkit.ui.Combo(new zebkit.ui.CompList([
                "Item 1",
                "Item 2",
                "Item 3"
            ]));


            // let combo box decides which list component has to be used
            var combo = new zebkit.ui.Combo([
                "Item 1",
                "Item 2",
                "Item 3"
            ]);

     * @class zebkit.ui.Combo
     * @extends zebkit.ui.Panel
     * @constructor
     * @param {Array|zebkit.ui.BaseList} data an combo items array or a list component
     */

    /**
     * Fired when a new value in a combo box component has been selected

         combo.on("selected", function(combo, value) {
             ...
         });

     * @event selected
     * @param {zebkit.ui.Combo} combo a combo box component where a new value
     * has been selected
     * @param {Object} value a previously selected index
     */

    /**
     * Implement the event handler method to detect when a combo pad window
     * is shown or hidden

         var p = new zebkit.ui.Combo();
         p.padShown = function(src, b) { ... }; // add event handler

     * @event padShown
     * @param {zebkit.ui.Combo} src a combo box component that triggers the event
     * @param {Boolean} b a flag that indicates if the combo pad window has been
     * shown (true) or hidden (false)
    */
    pkg.Combo = Class(pkg.Panel, [
        function(list, editable) {
            if (arguments.length === 1 && zebkit.isBoolean(list)) {
                editable = list;
                list = null;
            }

            if (arguments.length === 0) {
                editable = false;
            }

            if (arguments.length === 0 || list === null) {
                list = new this.clazz.List(true);
            }

            /**
             * Reference to combo box list component
             * @attribute list
             * @readOnly
             * @type {zebkit.ui.BaseList}
             */
            if (zebkit.instanceOf(list, pkg.BaseList) === false) {
                list = list.length > 0 && zebkit.instanceOf(list[0], pkg.Panel) ? new this.clazz.CompList(list, true)
                                                                                : new this.clazz.List(list, true);
            }

            /**
             * Maximal size the combo box height can have
             * @attribute maxPadHeight
             * @readOnly
             * @type {Integer}
             */
            this.maxPadHeight = 0;

            this.$lockListSelEvent = false;

            this.setList(list);

            this.$super();

            this.add("center", editable ? new this.clazz.EditableContentPan()
                                        : new this.clazz.ReadonlyContentPan());
            this.add("right", new this.clazz.ArrowButton());
        },

        function $clazz() {
            /**
             * UI panel class that is used to implement combo box content area
             * @class  zebkit.ui.Combo.ContentPan
             * @extends zebkit.ui.Panel
             * @constructor
             */
            this.ContentPan = Class(pkg.Panel, [
                function $prototype() {
                    /**
                     * Called whenever the given combo box value has been updated with the specified
                     * value. Implement the method to synchronize content panel with updated combo
                     * box value
                     * @method comboValueUpdated
                     * @param {zebkit.ui.Combo} combo a combo box component that has been updated
                     * @param {Object} value a value with which the combo box has been updated
                     */
                    this.comboValueUpdated = function(combo, value) {};

                    /**
                     * Indicates if the content panel is editable. Set the property to true
                     * to indicate the content panel implementation is editable. Editable
                     * means the combo box content can be editable by a user
                     * @attribute isEditable
                     * @type {Boolean}
                     * @readOnly
                     * @default undefined
                     */

                    /**
                     * Get a combo box the content panel belongs
                     * @method getCombo
                     * @return {zebkit.ui.Combo} a combo the content panel belongs
                     */
                    this.getCombo = function() {
                        for (var p = this.parent; p !== null && zebkit.instanceOf(p, pkg.Combo) === false; p = p.parent) {}
                        return p;
                    };
                }
            ]);

            /**
             * Combo box list pad component class
             * @extends zebkit.ui.ScrollPan
             * @class  zebkit.ui.Combo.ComboPadPan
             * @constructor
             * @param {zebkit.ui.Panel} c a target component
             */
            this.ComboPadPan = Class(pkg.ScrollPan, [
                function $prototype() {
                    this.$closeTime = 0;

                    this.owner = null;

                    /**
                     * A reference to combo that uses the list pad component
                     * @attribute owner
                     * @type {zebkit.ui.Combo}
                     * @readOnly
                     */
                    this.childKeyPressed = function(e){
                        if (e.code === "Escape" && this.parent !== null) {
                            this.removeMe();
                            if (this.owner !== null) {
                                this.owner.requestFocus();
                            }
                        }
                    };
                },

                function setParent(l) {
                    this.$super(l);
                    if (l === null && this.owner !== null) {
                        this.owner.requestFocus();
                    }

                    this.$closeTime = (l === null ? new Date().getTime() : 0);
                }
            ]);

            /**
             * Read-only content area combo box component panel class
             * @extends zebkit.ui.Combo.ContentPan
             * @constructor
             * @class  zebkit.ui.Combo.ReadonlyContentPan
             */
            this.ReadonlyContentPan = Class(this.ContentPan, [
                function $prototype() {
                    this.calcPsByContent = false;

                    this.getCurrentView = function() {
                        var list = this.getCombo().list,
                            selected = list.getSelected();

                        return selected !== null ? list.provider.getView(list, list.selectedIndex, selected)
                                                 : null;
                    };

                    this.paintOnTop = function(g){
                        var v = this.getCurrentView();
                        if (v !== null) {
                            var ps = v.getPreferredSize();
                            v.paint(g, this.getLeft(),
                                       this.getTop() + Math.floor((this.height - this.getTop() - this.getBottom() - ps.height) / 2),
                                       this.width, ps.height, this);
                        }
                    };

                    this.setCalcPsByContent = function(b) {
                        if (this.calcPsByContent !== b) {
                            this.calcPsByContent = b;
                            this.vrp();
                        }
                        return this;
                    };

                    this.calcPreferredSize = function(l) {
                        var p = this.getCombo();
                        if (p !== null && this.calcPsByContent !== true) {
                            return p.list.calcMaxItemSize();
                        } else {
                            var cv = this.getCurrentView();
                            return cv === null ? { width: 0, height: 0} : cv.getPreferredSize();
                        }
                    };

                    this.comboValueUpdated = function(combo, value) {
                        if (this.calcPsByContent === true) {
                            this.invalidate();
                        }
                    };
                }
            ]);

            /**
             * Editable content area combo box component panel class
             * @class zebkit.ui.Combo.EditableContentPan
             * @constructor
             * @extends zebkit.ui.Combo.ContentPan
             * @uses zebkit.EventProducer
             */

            /**
             * Fired when a content value has been updated.

            content.on(function(contentPan, newValue) {
                ...
            });

             * @param {zebkit.ui.Combo.ContentPan} contentPan a content panel that
             * updated its value
             * @param {Object} newValue a new value the content panel has been set
             * with
             * @event  contentUpdated
             */
            this.EditableContentPan = Class(this.ContentPan, [
                function() {
                    this.$super();

                    /**
                     * A reference to a text field component the content panel uses as a
                     * value editor
                     * @attribute textField
                     * @readOnly
                     * @private
                     * @type {zebkit.ui.TextField}
                     */
                    this.textField = new this.clazz.TextField("",  -1);
                    this.textField.view.target.on(this);
                    this.add("center", this.textField);
                },

                function $clazz() {
                    this.TextField = Class(pkg.TextField, []);
                },

                function $prototype() {
                    this.isEditable = true;

                    this.dontGenerateUpdateEvent = false;

                    this.canHaveFocus = true;

                    this.textUpdated = function(e){
                        if (this.dontGenerateUpdateEvent === false && e.transaction === false) {
                            this.fire("contentUpdated", [this, this.textField.getValue()]);
                        }
                    };

                    /**
                     * Called when the combo box content has been updated
                     * @param {zebkit.ui.Combo} combo a combo where the new value has been set
                     * @param {Object} v a new combo box value
                     * @method comboValueUpdated
                     */
                    this.comboValueUpdated = function(combo, v){
                        this.dontGenerateUpdateEvent = true;
                        try {
                            var txt = (v === null ? "" : v.toString());
                            this.textField.setValue(txt);
                            this.textField.select(0, txt.length);
                        } finally {
                            this.dontGenerateUpdateEvent = false;
                        }
                    };
                },

                function focused(){
                    this.$super();
                    this.textField.requestFocus();
                }
            ]);

            this.ArrowButton = Class(pkg.ArrowButton, [
                function() {
                    this.setFireParams(true,  -1);
                    this.$super();
                }
            ]);

            this.List     = Class(pkg.List, []);
            this.CompList = Class(pkg.CompList, []);
        },

        /**
         * @for zebkit.ui.Combo
         */
        function $prototype() {
            /**
             * Reference to combo box winpad list component
             * @attribute list
             * @readOnly
             * @type {zebkit.ui.BaseList}
             */
            this.list = null;

            /**
             * Reference to combo box button component
             * @attribute button
             * @readOnly
             * @type {zebkit.ui.Panel}
             */
             this.button = null;

            /**
             * Reference to combo box content component
             * @attribute content
             * @readOnly
             * @type {zebkit.ui.Panel}
             */
             this.content = null;

            /**
             * Reference to combo box pad component
             * @attribute winpad
             * @readOnly
             * @type {zebkit.ui.Panel}
             */
             this.winpad = null;

            /**
             * Reference to selection view
             * @attribute selectView
             * @readOnly
             * @type {zebkit.draw.View}
             */
            this.selectView = null;

            /**
             * A component the combo win pad has to be adjusted.
             * @attribute adjustPadTo
             * @default null
             * @type {zebkit.ui.Panel}
             */
            this.adjustPadTo = null;

            this.paint = function(g){
                if (this.content       !== null &&
                    this.selectView !== null &&
                    this.hasFocus())
                {
                    this.selectView.paint(g, this.content.x,
                                                this.content.y,
                                                this.content.width,
                                                this.content.height,
                                                this);
                }
            };

            this.catchInput = function (child) {
                return child !== this.button && (this.content === null || this.content.isEditable !== true);
            };

            this.canHaveFocus = function() {
                return this.winpad.parent === null && (this.content !== null && this.content.isEditable !== true);
            };

            this.contentUpdated = function(src, text){
                if (src === this.content) {
                    try {
                        this.$lockListSelEvent = true;
                        if (text === null) {
                            this.list.select(-1);
                        } else {
                            var m = this.list.model;
                            for(var i = 0;i < m.count(); i++){
                                var mv = m.get(i);
                                if (mv !== text) {
                                    this.list.select(i);
                                    break;
                                }
                            }
                        }
                    } finally {
                        this.$lockListSelEvent = false;
                    }

                    this.fire("selected", [ this, text ]);
                }
            };

            /**
             * Select the given value from the list as the combo box value
             * @param  {Integer} i an index of a list element to be selected
             * as the combo box value
             * @method select
             * @chainable
             */
            this.select = function(i) {
                this.list.select(i);
                return this;
            };

            // This method has been added to support selectedIndex property setter
            this.setSelectedIndex = function(i) {
                this.select(i);
                return this;
            };

            /**
             * Set combo box value selected value.
             * @param {Object} v a value
             * @method  setValue
             * @chainable
             */
            this.setValue = function(v) {
                this.list.setValue(v);
                return this;
            };

            /**
             * Get the current combo box selected value
             * @return {Object} a value
             * @method getValue
             */
            this.getValue = function() {
                return this.list.getValue();
            };

            /**
             * Define pointer pressed events handler
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerPressed
             */
            this.pointerPressed = function (e) {
                if (e.isAction() && this.content !== null                 &&
                    (new Date().getTime() - this.winpad.$closeTime) > 100 &&
                    e.x > this.content.x && e.y > this.content.y          &&
                    e.x < this.content.x + this.content.width             &&
                    e.y < this.content.y + this.content.height              )
                {
                    this.showPad();
                }
            };

            /**
             * Test if the combo window pad is shown
             * @return {Boolean} true if the combo window pad is shown
             * @method isPadShown
             */
            this.isPadShown = function() {
                return this.winpad !== null && this.winpad.parent !== null && this.winpad.isVisible === true;
            };

            /**
             * Hide combo drop down list
             * @method hidePad
             * @chainable
             */
            this.hidePad = function() {
                if (this.winpad !== null && this.winpad.parent !== null) {
                    this.winpad.removeMe();
                    var d = this.getCanvas();
                    if (d !== null) {
                        this.requestFocus();
                    }
                }
                return this;
            };

            /**
             * Show combo drop down list
             * @method showPad
             * @chainable
             */
            this.showPad = function() {
                var canvas = this.getCanvas();
                if (canvas !== null) {
                    var ps  = this.winpad.getPreferredSize(),
                        p   = zebkit.layout.toParentOrigin(0, 0, this.adjustPadTo === null ? this : this.adjustPadTo),
                        py  = p.y;

                    // if (this.winpad.hbar && ps.width > this.width) {
                    //     ps.height += this.winpad.hbar.getPreferredSize().height;
                    // }

                    if (this.maxPadHeight > 0 && ps.height > this.maxPadHeight) {
                        ps.height = this.maxPadHeight;
                    }

                    if (py + this.height + ps.height > canvas.height) {
                        if (py - ps.height >= 0) {
                            py -= (ps.height + this.height);
                        } else {
                            var hAbove = canvas.height - py - this.height;
                            if (py > hAbove) {
                                ps.height = py;
                                py -= (ps.height + this.height);
                            } else {
                                ps.height = hAbove;
                            }
                        }
                    }

                    this.winpad.setBounds(p.x,
                                          py + (this.adjustPadTo === null ? this.height
                                                                          : this.adjustPadTo.height),
                                          this.adjustPadTo === null ? this.width
                                                                    : this.adjustPadTo.width,
                                          ps.height);

                    this.list.makeItemVisible(this.list.selectedIndex);
                    canvas.getLayer(pkg.PopupLayerMix.id).add(this, this.winpad);
                    this.list.requestFocus();
                    if (this.padShown !== undefined) {
                        this.padShown(true);
                    }

                    return this;
                }
            };

            /**
             * Bind the given list component to the combo box component.
             * @param {zebkit.ui.BaseList} l a list component
             * @method setList
             * @chainable
             */
            this.setList = function(l){
                if (this.list !== l) {
                    this.hidePad();

                    if (this.list !== null) {
                        this.list.off("selected", this);
                    }

                    this.list = l;

                    if (this.list !== null) {
                        this.list.on("selected", this);
                    }

                    var $this = this;
                    this.winpad = new this.clazz.ComboPadPan(this.list, [
                        function setParent(p) {
                            this.$super(p);
                            if ($this.padShown !== undefined) {
                                $this.padShown($this, p !== null);
                            }
                        }
                    ]);

                    this.winpad.owner = this;
                    if (this.content !== null) {
                        this.content.comboValueUpdated(this, this.list.getSelected());
                    }
                    this.vrp();
                }
                return this;
            };

            /**
             * Define key pressed events handler
             * @param  {zebkit.ui.event.KeyEvent} e a key event
             * @method keyPressed
             */
            this.keyPressed = function (e) {
                if (this.list !== null && this.list.model !== null) {
                    var index = this.list.selectedIndex;
                    switch(e.code) {
                        case "Enter"     : this.showPad(); break;
                        case "ArrowLeft" :
                        case "ArrowUp"   : if (index > 0) {
                            this.list.select(index - 1);
                        } break;
                        case "ArrowDown" :
                        case "ArrowRight": if (this.list.model.count() - 1 > index) {
                            this.list.select(index + 1);
                        } break;
                    }
                }
            };

            /**
             * Define key typed  events handler
             * @param  {zebkit.ui.event.KeyEvent} e a key event
             * @method keyTyped
             */
            this.keyTyped = function(e) {
                this.list.keyTyped(e);
            };

            /**
             * Set the given combo box selection view
             * @param {zebkit.draw.View} c a view
             * @method setSelectView
             * @chainable
             */
            this.setSelectView = function (c){
                if (c !== this.selectView) {
                    this.selectView = zebkit.graphics.$view(c);
                    this.repaint();
                }
                return this;
            };

            /**
             * Set the maximal height of the combo box pad element.
             * @param {Integer} h a maximal combo box pad size
             * @method setMaxPadHeight
             * @chainable
             */
            this.setMaxPadHeight = function(h){
                if (this.maxPadHeight !== h) {
                    this.hidePad();
                    this.maxPadHeight = h;
                }
                return this;
            };

            /**
             * Make the commbo editable
             * @param {Boolean} b  true to make the combo ediatable
             * @chainable
             * @method setEditable
             */
            this.setEditable = function(b) {
                if (this.content === null || this.content.isEditable !== b) {
                    var ctr = "center";
                    if (this.content !== null) {
                        ctr = this.content.constraints;
                        this.content.removeMe();
                    }
                    this.add(ctr, b ? new this.clazz.EditableContentPan()
                                    : new this.clazz.ReadonlyContentPan());
                }
                return this;
            };

            /**
             * Combo box button listener method. The method triggers showing
             * combo box pad window when the combo button has been pressed
             * @param  {zebkit.ui.Button} src a button that has been pressed
             * @method fired
             */
            this.fired = function(src) {
                if ((new Date().getTime() - this.winpad.$closeTime) > 100) {
                    this.showPad();
                }
            };

            /**
             * Combo pad list listener method. Called every time an item in
             * combo pad list has been selected.
             * @param  {zebkit.ui.BaseList} src a list
             * @param  {Integer} data a selected index
             * @method selected
             * @protected
             */
            this.selected = function(src, data) {
                if (this.$lockListSelEvent === false) {
                    this.hidePad();
                    if (this.content !== null) {
                        this.content.comboValueUpdated(this, this.list.getSelected());
                        if (this.content.isEditable === true) {
                            this.content.requestFocus();
                        }
                        this.repaint();
                    }

                    this.fire("selected", [ this, data ]);
                }
            };
        },

        function focused(){
            this.$super();
            this.repaint();
        },

        function kidAdded(index, s, c){
            if (zebkit.instanceOf(c, pkg.Combo.ContentPan)) {
                if (this.content !== null) {
                    throw new Error("Content panel is set");
                }

                this.content = c;

                if (this.list !== null) {
                    c.comboValueUpdated(this, this.list.getSelected());
                }
            } else if (this.button === null) {
                this.button = c;
            }

            if (c.isEventFired() ) {
                c.on(this);
            }

            this.$super(index, s, c);
        },

        function kidRemoved(index, l, ctr) {
            if (l.isEventFired()) {
                l.off(this);
            }

            if (this.content === l) {
                this.content = null;
            } else if (this.button === l) {
                this.button = null;
            }

            this.$super(index, l, ctr);
        },

        function setVisible(b) {
            if (b === false) {
                this.hidePad();
            }
            this.$super(b);
            return this;
        },

        function setParent(p) {
            if (p === null) {
                this.hidePad();
            }
            this.$super(p);
        }
    ]).events("selected");


    /**
     *  Border panel UI component class. The component renders titled border around the
     *  given  content UI component. Border title can be placed on top or
     *  bottom border line and aligned horizontally (left, center, right). Every
     *  zebkit UI component can be used as a border title element.
     *  @param {zebkit.ui.Panel|String} [title] a border panel title. Can be a
     *  string or any other UI component can be used as the border panel title
     *  @param {zebkit.ui.Panel} [content] a content UI component of the border
     *  panel
     *  @param {Integer} [constraints] a title constraints. The constraints gives
     *  a possibility to place border panel title in different places. Generally
     *  the title can be placed on the top or bottom part of the border panel.
     *  Also the title can be aligned horizontally.
     *
     *  @example
     *
     *      // create border panel with a title located at the
     *      // top and aligned at the canter
     *      var bp = new zebkit.ui.BorderPan("Title",
     *                                       new zebkit.ui.Panel(),
     *                                       "top", "center");
     *
     *  @constructor
     *  @class zebkit.ui.BorderPan
     *  @extends zebkit.ui.Panel
     */
    pkg.BorderPan = Class(pkg.Panel, [
        function(title, center, o, a) {
            if (arguments.length > 0) {
                title = pkg.$component(title, this);
            }

            if (arguments.length > 2) {
                this.orient = o;
            }

            if (arguments.length > 3) {
                this.alignment = a;
            }

            this.$super();
            if (arguments.length > 0) {
                this.add("caption", title);
            }

            if (arguments.length > 1) {
                this.add("center", center);
            }
        },

        function $clazz() {
            this.Label = Class(pkg.Label, []);
            this.ImageLabel = Class(pkg.ImageLabel, []);
            this.Checkbox = Class(pkg.Checkbox, []);
        },

        function $prototype() {
            /**
             * Border panel label content component
             * @attribute content
             * @type {zebkit.ui.Panel}
             * @readOnly
             */
            this.content = null;

            /**
             * Border panel label component
             * @attribute label
             * @type {zebkit.ui.Panel}
             * @readOnly
             */
             this.label = null;

            /**
             * Vertical gap. Define top and bottom paddings between
             * border panel border and the border panel content
             * @attribute vGap
             * @type {Integer}
             * @readOnly
             * @default 0
             */

             /**
              * Horizontal gap. Define left and right paddings between
              * border panel border and the border panel content
              * @attribute hGap
              * @type {Integer}
              * @readOnly
              * @default 0
              */
            this.vGap = this.hGap = 2;

             /**
              * Border panel label indent
              * @type {Integer}
              * @attribute indent
              * @readOnly
              * @default 4
              */
            this.indent = 4;

            /**
             * Border panel title area arrangement. Border title can be placed
             * either at the top or bottom area of border panel component.
             * @type {String}
             * @attribute orient
             * @readOnly
             * @default "top"
             */
            this.orient = "top";

            /**
             * Border panel title horizontal alignment.
             * @type {String}
             * @attribute alignment
             * @readOnly
             * @default "left"
             */
            this.alignment = "left";

             /**
              * Get the border panel title info. The information
              * describes a rectangular area the title occupies, the
              * title location and alignment
              * @return {Object} a title info
              *
              *  {
              *      x: {Integer}, y: {Integer},
              *      width: {Integer}, height: {Integer},
              *      orient: {Integer}
              *  }
              *
              * @method getTitleInfo
              * @protected
              */
            this.getTitleInfo = function() {
                return (this.label !== null) ? { x      : this.label.x,
                                                 y      : this.label.y,
                                                 width  : this.label.width,
                                                 height : this.label.height,
                                                 orient: this.orient }
                                             : null;
            };

            this.calcPreferredSize = function(target){
                var ps = this.content !== null && this.content.isVisible === true ? this.content.getPreferredSize()
                                                                                  : { width:0, height:0 };
                if (this.label !== null && this.label.isVisible === true){
                    var lps = this.label.getPreferredSize();
                    ps.height += lps.height;
                    ps.width = Math.max(ps.width, lps.width + this.indent);
                }
                ps.width  += (this.hGap * 2);
                ps.height += (this.vGap * 2);
                return ps;
            };

            this.doLayout = function (target){
                var h = 0,
                    right  = this.getRight(),
                    left   = this.getLeft(),
                    top    = this.orient === "top"   ? this.top    : this.getTop(),
                    bottom = this.orient === "bottom"? this.bottom : this.getBottom();

                if (this.label !== null && this.label.isVisible === true){
                    var ps = this.label.getPreferredSize();
                    h = ps.height;
                    this.label.setBounds((this.alignment === "left") ? left + this.indent
                                                                      : ((this.alignment === "right") ? this.width - right - ps.width - this.indent
                                                                                                       : Math.floor((this.width - ps.width) / 2)),
                                         (this.orient === "bottom") ? (this.height - bottom - ps.height) : top,
                                         ps.width, h);
                }

                if (this.content !== null && this.content.isVisible === true){
                    this.content.setBounds(left + this.hGap,
                                           (this.orient === "bottom" ? top : top + h) + this.vGap,
                                            this.width  - right - left - 2 * this.hGap,
                                            this.height - top - bottom - h - 2 * this.vGap);
                }
            };

            /**
             * Set vertical and horizontal paddings between the border panel border and the content
             * of the border panel
             * @param {Integer} vg a top and bottom paddings
             * @param {Integer} hg a left and right paddings
             * @method setGaps
             * @chainable
             */
            this.setGaps = function(vg, hg){
                if (this.vGap !== vg || hg !== this.hGap){
                    this.vGap = vg;
                    this.hGap = hg;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set border panel title orientation. The title area can be
             * placed either at the top or at the bottom of border panel
             * component.
             * @param {String} o a border title orientation. Can be "top" or "bottom"
             * @method setOrientation
             * @chainable
             */
            this.setOrientation = function(o) {
                if (this.orient !== o) {
                    this.orient = zebkit.util.validateValue(o, "top", "bottom");
                    this.vrp();
                }
                return this;
            };

            /**
             * Set border panel title horizontal alignment.
             * @param {String} a a horizontal alignment. Use "left", "right", "center" as
             * the parameter value.
             * @method setAlignment
             * @chainable
             */
            this.setAlignment = function(a) {
                if (this.alignment !== a) {
                    this.alignment = zebkit.util.validateValue(a, "left", "right", "center");
                    this.vrp();
                }
                return this;
            };
        },

        function setBorder(br) {
            if (arguments.length === 0) {
                br = "plain";
            }

            br = zebkit.draw.$view(br);
            if (zebkit.instanceOf(br, zebkit.draw.TitledBorder) === false) {
                br = new zebkit.draw.TitledBorder(br, "center");
            }
            return this.$super(br);
        },

        function kidAdded(index, ctr, lw) {
            this.$super(index, ctr, lw);
            if ((ctr === null && this.content === null) || "center" === ctr) {
                this.content = lw;
            } else if (this.label === null) {
                this.label = lw;
            }
        },

        function kidRemoved(index, lw, ctr){
            this.$super(index, lw, ctr);
            if (lw === this.label) {
                this.label = null;
            } else if (this.content === lw) {
                this.content = null;
            }
        }
    ]);

    /**
     * Splitter panel UI component class. The component splits its area horizontally or vertically into two areas.
     * Every area hosts an UI component. A size of the parts can be controlled by pointer cursor dragging. Gripper
     * element is children UI component that can be customized. For instance:
     *
     *     // create split panel
     *     var sp = new zebkit.ui.SplitPan(new zebkit.ui.Label("Left panel"),
     *                                     new zebkit.ui.Label("Right panel"));
     *
     *     // customize gripper background color depending on its state
     *     sp.gripper.setBackground(new zebkit.draw.ViewSet({
     *          "over" : "yellow"
     *          "out" : null,
     *          "pressed.over" : "red"
     *     }));
     *
     *
     * @param {zebkit.ui.Panel} [first] a first UI component in splitter panel
     * @param {zebkit.ui.Panel} [second] a second UI component in splitter panel
     * @param {String} [o] an orientation of splitter element: "vertical" or "horizontal"
     * @class zebkit.ui.SplitPan
     * @constructor
     * @extends zebkit.ui.Panel
     */
    pkg.SplitPan = Class(pkg.Panel, [
        function(f,s,o) {
            if (arguments.length > 2) {
                this.orient = o;
            }

            this.$super();

            if (arguments.length > 0) {
                this.add("left", f);
                if (arguments.length > 1) {
                    this.add("right", s);
                }
            }

            this.add("center", new this.clazz.Bar(this));
        },

        function $clazz() {
            this.Bar = Class(pkg.EvStatePan, [
                function(target) {
                    this.target = target;
                    this.$super();
                },

                function $prototype() {
                    this.prevLoc = 0;

                    this.pointerDragged = function(e){
                        var x = this.x + e.x, y = this.y + e.y;
                        if (this.target.orient === "vertical"){
                            if (this.prevLoc !== x){
                                x = this.target.normalizeBarLoc(x);
                                if (x > 0){
                                    this.prevLoc = x;
                                    this.target.setGripperLoc(x);
                                }
                            }
                        } else {
                            if (this.prevLoc !== y) {
                                y = this.target.normalizeBarLoc(y);
                                if (y > 0){
                                    this.prevLoc = y;
                                    this.target.setGripperLoc(y);
                                }
                            }
                        }
                    };

                    this.pointerDragStarted = function (e){
                        var x = this.x + e.x,
                            y = this.y + e.y;

                        if (e.isAction()) {
                            if (this.target.orient === "vertical"){
                                x = this.target.normalizeBarLoc(x);
                                if (x > 0) {
                                    this.prevLoc = x;
                                }
                            } else {
                                y = this.target.normalizeBarLoc(y);
                                if (y > 0) {
                                    this.prevLoc = y;
                                }
                            }
                        }
                    };

                    this.pointerDragEnded = function(e){
                        var xy = this.target.normalizeBarLoc(this.target.orient === "vertical" ? this.x + e.x
                                                                                               : this.y + e.y);
                        if (xy > 0) {
                            this.target.setGripperLoc(xy);
                        }
                    };

                    this.getCursorType = function(t, x, y) {
                        return (this.target.orient === "vertical" ? pkg.Cursor.W_RESIZE
                                                                  : pkg.Cursor.N_RESIZE);
                    };
                }
            ]);
        },

        function $prototype() {
            /**
             * A minimal size of the left (or top) sizable panel
             * @attribute leftMinSize
             * @type {Integer}
             * @readOnly
             * @default 50
             */

            /**
             * A minimal size of right (or bottom) sizable panel
             * @attribute rightMinSize
             * @type {Integer}
             * @readOnly
             * @default 50
             */

            /**
             * Indicates if the splitter bar can be moved
             * @attribute isMoveable
             * @type {Boolean}
             * @readOnly
             * @default true
             */

            /**
             * A gap between gripper element and first and second UI components
             * @attribute gap
             * @type {Integer}
             * @readOnly
             * @default 1
             */

            /**
             * A reference to gripper UI component
             * @attribute gripper
             * @type {zebkit.ui.Panel}
             * @readOnly
             */

            /**
             * A reference to left (top) sizable UI component
             * @attribute leftComp
             * @type {zebkit.ui.Panel}
             * @readOnly
             */

            /**
             * A reference to right (bottom) sizable UI component
             * @attribute rightComp
             * @type {zebkit.ui.Panel}
             * @readOnly
             */

            this.leftMinSize = this.rightMinSize = 50;
            this.isMoveable = true;
            this.gap = 1;
            this.orient = "vertical";

            this.minXY = this.maxXY = 0;
            this.barLocation = 70;
            this.leftComp = this.rightComp = this.gripper = null;

            this.normalizeBarLoc = function(xy){
                if (xy < this.minXY) {
                    xy = this.minXY;
                } else if (xy > this.maxXY) {
                    xy = this.maxXY;
                }

                return (xy > this.maxXY || xy < this.minXY) ?  -1 : xy;
            };

            /**
             * Set split panel orientation.
             * @param  {String} o an orientation ("horizontal" or "vertical")
             * @method setOrientation
             * @chainable
             */
            this.setOrientation = function(o) {
                if (o !== this.orient) {
                    this.orient = zebkit.util.validateValue(o, "horizontal", "vertical");
                    this.vrp();
                }
                return this;
            };

            /**
             * Set gripper element location
             * @param  {Integer} l a location of the gripper element
             * @method setGripperLoc
             * @chainable
             */
            this.setGripperLoc = function(l){
                if (l !== this.barLocation){
                    this.barLocation = l;
                    this.vrp();
                }
                return this;
            };

            this.calcPreferredSize = function(c){
                var fSize = pkg.$getPS(this.leftComp),
                    sSize = pkg.$getPS(this.rightComp),
                    bSize = pkg.$getPS(this.gripper);

                if (this.orient === "horizontal"){
                    bSize.width = Math.max(((fSize.width > sSize.width) ? fSize.width : sSize.width), bSize.width);
                    bSize.height = fSize.height + sSize.height + bSize.height + 2 * this.gap;
                }
                else {
                    bSize.width = fSize.width + sSize.width + bSize.width + 2 * this.gap;
                    bSize.height = Math.max(((fSize.height > sSize.height) ? fSize.height : sSize.height), bSize.height);
                }
                return bSize;
            };

            this.doLayout = function(target){
                var right  = this.getRight(),
                    top    = this.getTop(),
                    bottom = this.getBottom(),
                    left   = this.getLeft(),
                    bSize  = pkg.$getPS(this.gripper);

                if (this.orient === "horizontal"){
                    var w = this.width - left - right;
                    if (this.barLocation < top) {
                        this.barLocation = top;
                    } else if (this.barLocation > this.height - bottom - bSize.height) {
                        this.barLocation = this.height - bottom - bSize.height;
                    }

                    if (this.gripper !== null){
                        if (this.isMoveable){
                            this.gripper.setBounds(left, this.barLocation, w, bSize.height);
                        } else {
                            this.gripper.toPreferredSize();
                            this.gripper.setLocation(Math.floor((w - bSize.width) / 2), this.barLocation);
                        }
                    }

                    if (this.leftComp !== null){
                        this.leftComp.setBounds(left, top, w, this.barLocation - this.gap - top);
                    }

                    if (this.rightComp !== null){
                        this.rightComp.setLocation(left, this.barLocation + bSize.height + this.gap);
                        this.rightComp.setSize(w, this.height - this.rightComp.y - bottom);
                    }
                } else {
                    var h = this.height - top - bottom;
                    if (this.barLocation < left) {
                        this.barLocation = left;
                    } else if (this.barLocation > this.width - right - bSize.width) {
                        this.barLocation = this.width - right - bSize.width;
                    }

                    if (this.gripper !== null){
                        if (this.isMoveable === true){
                            this.gripper.setBounds(this.barLocation, top, bSize.width, h);
                        } else {
                            this.gripper.setBounds(this.barLocation, Math.floor((h - bSize.height) / 2),
                                                   bSize.width, bSize.height);
                        }
                    }

                    if (this.leftComp !== null){
                        this.leftComp.setBounds(left, top, this.barLocation - left - this.gap, h);
                    }

                    if (this.rightComp !== null){
                        this.rightComp.setLocation(this.barLocation + bSize.width + this.gap, top);
                        this.rightComp.setSize(this.width - this.rightComp.x - right, h);
                    }
                }
            };

            /**
             * Set gap between gripper element and sizable panels
             * @param  {Integer} g a gap
             * @method setGap
             * @chainable
             */
            this.setGap = function (g){
                if (this.gap !== g){
                    this.gap = g;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set the minimal size of the left (or top) sizeable panel
             * @param  {Integer} m  a minimal possible size
             * @method setLeftMinSize
             * @chainable
             */
            this.setLeftMinSize = function (m){
                if (this.leftMinSize !== m){
                    this.leftMinSize = m;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set the minimal size of the right (or bottom) sizeable panel
             * @param  {Integer} m  a minimal possible size
             * @method setRightMinSize
             * @chainable
             */
            this.setRightMinSize = function(m){
                if (this.rightMinSize !== m){
                    this.rightMinSize = m;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set the given gripper movable state
             * @param  {Boolean} b the gripper movable state.
             * @method setGripperMovable
             */
            this.setGripperMovable = function (b){
                if (b !== this.isMoveable){
                    this.isMoveable = b;
                    this.vrp();
                }
                return this;
            };
        },

        function kidAdded(index, ctr, c){
            this.$super(index, ctr, c);

            if ((ctr === null && this.leftComp === null) || "left" === ctr) {
                this.leftComp = c;
            } else if ((ctr === null && this.rightComp === null) || "right" === ctr) {
                this.rightComp = c;
            } else {
                if ("center" === ctr) {
                    this.gripper = c;
                } else {
                    throw new Error("" + ctr);
                }
            }
        },

        function kidRemoved(index, c, ctr){
            this.$super(index, c, ctr);
            if (c === this.leftComp) {
                this.leftComp = null;
            } else if (c === this.rightComp) {
                this.rightComp = null;
            } else if (c === this.gripper) {
                this.gripper = null;
            }
        },

        function resized(pw,ph) {
            var ps = this.gripper.getPreferredSize();
            if (this.orient === "vertical"){
                this.minXY = this.getLeft() + this.gap + this.leftMinSize;
                this.maxXY = this.width - this.gap - this.rightMinSize - ps.width - this.getRight();
            } else {
                this.minXY = this.getTop() + this.gap + this.leftMinSize;
                this.maxXY = this.height - this.gap - this.rightMinSize - ps.height - this.getBottom();
            }
            this.$super(pw, ph);
        }
    ]);

    /**
     * Extendable  UI panel class. Implement collapsible panel where
     * a user can hide of show content by pressing special control
     * element:
     *
     *       // create extendable panel that contains list as its content
     *       var ext = zebkit.ui.CollapsiblePan("Title", new zebkit.ui.List([
     *           "Item 1",
     *           "Item 2",
     *           "Item 3"
     *       ]));
     *
     *
     * @constructor
     * @class zebkit.ui.CollapsiblePan
     * @extends zebkit.ui.Panel
     * @param {zebkit.ui.Panel|String} l a title label text or
     * @param {zebkit.ui.Panel} c a content of the extender panel
     * component
     */

     /**
      * Fired when extender is collapsed or extended
      *
      *       var ex = new zebkit.ui.CollapsiblePan("Title", pan);
      *       ex.on(function (src, isCollapsed) {
      *           ...
      *       });
      *
      * @event fired
      * @param {zebkit.ui.CollapsiblePan} src an extender UI component that generates the event
      * @param {Boolean} isCollapsed a state of the extender UI component
      */
    pkg.CollapsiblePan = Class(pkg.Panel, [
        function(lab, content){
            this.$super();

            this.headerPan = new this.clazz.Header();
            this.togglePan = new this.clazz.Toogle();
            this.togglePan.on(this);

            this.add("top", this.headerPan);

            this.headerPan.add(this.togglePan);
            this.headerPan.add(pkg.$component(arguments.length === 0 || lab === null ? "" : lab, this));

            if (arguments.length > 1 && content !== null) {
                this.contentPan = content;
                content.setVisible(this.getValue());
                this.add("center", this.contentPan);
            }
        },

        function $clazz() {
            this.Label = Class(pkg.Label,[]);

            this.ImageLabel = Class(pkg.ImageLabel, []);

            this.Header = Class(pkg.EvStatePan, []);

            this.Toogle = Class(pkg.Checkbox, [
                function $prototype() {
                    this.cursorType = pkg.Cursor.HAND;
                },

                function $clazz() {
                    this.layout = new zebkit.layout.FlowLayout();
                }
            ]);

            this.GroupPan = Class(pkg.Panel, [
                function() {
                    this.group = new pkg.Group(true);

                    this.$super();
                    for(var i = 0; i < arguments.length; i++) {
                        arguments[i].togglePan.setGroup(this.group);
                        this.add(arguments[i]);
                        arguments[i].setBorder(null);
                    }
                },

                function $prototype() {
                    this.doLayout = function(t) {
                        var y     = t.getTop(),
                            x     = t.getLeft(),
                            w     = t.width  - x - t.getRight(),
                            eh    = t.height - y - t.getBottom(),
                            kid   = null,
                            i     = 0;

                        // setup sizes for not selected item and calculate the vertical
                        // space that can be used for an expanded item
                        for(i = 0; i < t.kids.length; i++) {
                            kid = t.kids[i];
                            if (kid.isVisible) {
                                if (kid.getValue() === false) {
                                    var psh = kid.getPreferredSize().height;
                                    eh -= psh;
                                    kid.setSize(w, psh);
                                }
                            }
                        }

                        for(i = 0; i < t.kids.length; i++) {
                            kid = t.kids[i];
                            if (kid.isVisible) {
                                kid.setLocation(x, y);
                                if (kid.getValue()) {
                                    kid.setSize(w, eh);
                                }
                                y += kid.height;
                            }
                        }
                    };

                    this.calcPreferredSize = function(t) {
                        var w = 0,
                            h = 0;

                        for(var i = 0; i < t.kids.length; i++) {
                            var kid = t.kids[i];
                            if (kid.isVisible) {
                                var ps = kid.getPreferredSize();
                                h += ps.height;
                                if (ps.width > w) {
                                    w = ps.width;
                                }
                            }
                        }
                        return { width:w, height:h };
                    };

                    this.compAdded = function(e) {
                        if (this.group.selected === null) {
                            e.kid.setValue(true);
                        }
                    };

                    this.compRemoved = function(e) {
                        if (this.group.selected === e.kid.togglePan) {
                            e.kid.setValue(false);
                        }
                        e.kid.setGroup(null);
                    };
                }
            ]);
        },

        function $prototype() {
            /**
             * Title panel
             * @type {zebkit.ui.Panel}
             * @attribute headerPan
             * @readOnly
             */
            this.headerPan = null;

            /**
             * Content panel
             * @type {zebkit.ui.Panel}
             * @readOnly
             * @attribute contentPan
             */
            this.contentPan = null;

            /**
             * Toggle UI element
             * @type {zebkit.ui.Checkbox}
             * @readOnly
             * @attribute togglePan
             */
            this.togglePan = null;

            this.setValue = function(b) {
                if (this.togglePan !== null) {
                    this.togglePan.setValue(b);
                }
                return this;
            };

            this.getValue = function(b) {
                return (this.togglePan !== null) ? this.togglePan.getValue() : false;
            };

            this.setGroup = function(g) {
                if (this.togglePan !== null) {
                    this.togglePan.setGroup(g);
                }
                return this;
            };

            this.toggle = function() {
                if (this.togglePan !== null) {
                    this.togglePan.toggle();
                }
                return this;
            };

            this.fired = function(src) {
                var value = this.getValue();
                if (this.contentPan !== null) {
                    this.contentPan.setVisible(value);
                }
            };

            this.compRemoved = function(e) {
                if (this.headerPan === e.kid) {
                    this.headerPan = null;
                } else if (e.kid === this.contentPan) {
                    this.contentPan = null;
                } else if (e.kid === this.togglePan) {
                    this.togglePan.off(this);
                    this.togglePan = null;
                }
            };
        }
    ]);

    /**
     * Status bar UI component class
     * @class zebkit.ui.StatusBarPan
     * @constructor
     * @param {Integer} [gap] a gap between status bar children elements
     * @extends zebkit.ui.Panel
     */
    pkg.StatusBarPan = Class(pkg.Panel, [
        function (gap){
            this.$super(new zebkit.layout.PercentLayout("horizontal", (arguments.length === 0 ? 2 : gap)));
        },

        function $clazz() {
            this.Label    = Class(pkg.Label, []);
            this.Line     = Class(pkg.Line,  []);

            this.Combo = Class(pkg.Combo, []);
            this.Combo.inheritProperties = true;

            this.Checkbox = Class(pkg.Checkbox, []);
        },

        function $prototype() {
            this.borderView = null;

            /**
             * Set the specified border to be applied for status bar children components
             * @param {zebkit.draw.View} v a border
             * @method setBorderView
             * @chainable
             */
            this.setBorderView = function(v){
                if (v !== this.borderView){
                    this.borderView = v;
                    for (var i = 0; i < this.kids.length; i++) {
                        this.kids[i].setBorder(this.borderView);
                    }
                    this.repaint();
                }
                return this;
            };

            this.addCombo = function(ctr, data) {
                if (arguments.length === 1) {
                    data = ctr;
                    ctr = null;
                }

                return this.add(ctr, new this.clazz.Combo(data));
            };

            this.addCheckbox = function(ctr, data) {
                if (arguments.length === 1) {
                    data = ctr;
                    ctr = null;
                }

                return this.add(ctr, new this.clazz.Checkbox(data));
            };
        },

        function insert(i, s, d) {
            if (zebkit.isString(d)) {
                if (d === "|") {
                    if (s !== null) {
                        s = { ay: "stretch" };
                    }
                    d = new this.clazz.Line();
                    d.direction   = "vertical";
                    d.constraints = { ay : "stretch" };
                } else {
                    d = new this.clazz.Label(d);
                }
            }

            return this.$super(i, s, d.setBorder(this.borderView));
        }
    ]);

    /**
     * Panel class that uses zebkit.layout.StackLayout as a default layout manager.
     * @class  zebkit.ui.StackPan
     * @param {zebkit.ui.Panel} [varname]* number of components to be added to the stack
     * panel
     * @constructor
     * @extends zebkit.ui.Panel
     */
    pkg.StackPan = Class(pkg.Panel, [
        function() {
            this.$super(new zebkit.layout.StackLayout());
            for(var i = 0; i < arguments.length; i++) {
                this.add(arguments[i]);
            }
        }
    ]);


    /**
     * Simple ruler panel class. The ruler can render minimal and maximal values of the
     * specified range.
     * @param  {String} [o] ruler orientation. Use "horizontal" or "vertical" as the
     * argument value
     * @constructor
     * @class zebkit.ui.RulerPan
     * @extends zebkit.ui.Panel
     */
    pkg.RulerPan = Class(pkg.Panel, [
        function(o) {
            this.$super();
            this.setLabelsRender(new this.clazz.PercentageLabels());
            if (arguments.length > 0) {
                this.setOrientation(o);
            }
        },

        function $clazz() {
            // TODO: complete the class
            this.LabelsHighlighter = zebkit.Interface([
                function paintLabel(g, x, y, w, h, v, value) {
                    this.$super(g, x, y, w, h, v, value);

                    if (this.$labelsInfo === undefined) {
                        this.$labelsInfo = [];
                    }

                    var found = false;
                    for (var i = 0; i < this.$labelsInfo.length; i++) {
                        var info = this.$labelsInfo[i];
                        if (info.value === value) {
                            if (info.x !== x || info.y !== y || info.w !== w || info.h !== h) {
                                info.x = x;
                                info.y = y;
                                info.w = w;
                                info.h = h;
                            }
                            found = true;
                            break;
                        }
                    }

                    if (found === false) {
                        this.$labelsInfo.push({
                            value : value,
                            x     : x,
                            y     : y,
                            w     : w,
                            h     : h
                        });
                    }
                },

                function invalidate(p) {
                    this.$labelsInfo = [];
                    this.$selectedLabel = null;
                    this.$super();
                },

                function setParent(p) {
                    if (p === null && this.$labelsInfo) {
                        this.$labelsInfo = [];
                        this.$selectedLabel = null;
                    }
                    return this.$super(p);
                },

                function paint(g) {
                    if (this.highlighterView !== null && this.$selectedLabel !== null) {
                        this.highlighterView.paint(g, this.$selectedLabel.x,
                                                       this.$selectedLabel.y,
                                                       this.$selectedLabel.w,
                                                       this.$selectedLabel.h,
                                                    this);
                    }

                    this.$super(g);
                },

                function $prototype() {
                    this.catchInput = true;
                    this.$selectedLabel = null;
                    this.highlighterView = zebkit.draw.$view("yellow");

                    this.getLabelAt = function(x, y) {
                        if (this.$labelsInfo !== undefined) {
                            for (var i = 0; i < this.$labelsInfo.length; i++) {
                                var inf = this.$labelsInfo[i];
                                if (x >= inf.x && x < inf.w + inf.x && y >= inf.y && y < inf.h + inf.y) {
                                    return inf;
                                }
                            }
                        }

                        return null;
                    };

                    this.pointerMoved = function(e) {
                        if (this.highlighterView !== null) {
                            var label = this.getLabelAt(e.x, e.y);
                            if (this.$selectedLabel !== label) {
                                this.$selectedLabel = label;
                                this.repaint();
                            }
                        }
                    };

                    this.pointerExited = function(e) {
                        if (this.highlighterView !== null) {
                            var label = this.getLabelAt(e.x, e.y);
                            if (this.$selectedLabel !== null) {
                                this.$selectedLabel = null;
                                this.repaint();
                            }
                        }
                    };

                    this.seHighlighterView = function(v) {
                        if (this.highlighterView !== v) {
                            this.highlighterView = v;
                            this.repaint();
                        }
                        return this;
                    };

                    this.pointerClicked = function(e) {
                        var label = this.getLabelAt(e.x, e.y);
                        if (label !== null) {
                            this.parent.setValue(label.value);
                        }
                    };
                }
            ]);

            /**
             * Numeric label renderer factory.
             * @param  {Integer} [numPrecision] precision of displayed numbers.
             * @class zebkit.ui.RulerPan.NumLabels
             * @extends zebkit.draw.BaseViewProvider
             */
            this.NumLabels = Class(zebkit.draw.BaseViewProvider, [
                function(numPrecision) {
                    this.$super(new zebkit.draw.BoldTextRender(""));
                    if (arguments.length > 0) {
                        this.numPrecision = numPrecision;
                    }
                },

                function $prototype() {
                    /**
                     * Number precision.
                     * @attribute numPrecision
                     * @type {Integer}
                     * @readOnly
                     * @default  -1
                     */
                    this.numPrecision = -1;
                },

                /**
                 * Get a view to render the given number.
                 * @param  {zebkit.ui.RulerPan} t a target ruler panel.
                 * @param  {Number} v a number to be rendered
                 * @return {zebkit.draw.View}  a view to render the number
                 * @method getView
                 */
                function getView(t, v) {
                    if (v !== null && v !== undefined && this.numPrecision !== -1 && zebkit.isNumber(v)) {
                        v = v.toFixed(this.numPrecision);
                    }
                    return this.$super(t, v);
                },

                function $clazz() {
                    this.color = "gray";
                    this.font  = new zebkit.Font("Arial", "bold", 12);
                }
            ]);

            /**
             * Percentage label renderer factory.
             * @param  {Integer} [numPrecision] precision of displayed numbers.
             * @class zebkit.ui.RulerPan.PercentageLabels
             * @extends zebkit.ui.RulerPan.NumLabels
             */
            this.PercentageLabels = Class(this.NumLabels, [
                function(numPrecision) {
                    if (arguments.length === 0) {
                        numPrecision = 0;
                    }
                    this.$super(numPrecision);
                },

                function getView(t, v) {
                    var min = t.getMin(),
                        max = t.getMax();

                    v = ((v - min) * 100) / (max - min);
                    if (this.numPrecision !== -1) {
                        v = v.toFixed(this.numPrecision);
                    }

                    return this.$super(t, v + "%");
                }
            ]);
        },

        /**
         * @for zebkit.ui.RulerPan
         */
        function $prototype() {
            /**
             * Gap between stroke and labels
             * @attribute gap
             * @type {Integer}
             * @readOnly
             * @default 2
             */
            this.gap = 2;

            /**
             * Stroke color.
             * @attribute color
             * @type {String}
             * @readOnly
             * @default "gray"
             */
            this.color = "gray";

            /**
             * Stroke line width
             * @attribute lineWidth
             * @type {Integer}
             * @default 1
             * @readOnly
             */
            this.lineWidth  = 1;

            /**
             * Stroke line size
             * @attribute strokeSize
             * @type {Integer}
             * @default 4
             * @readOnly
             */
            this.strokeSize = 4;

            /**
             * Ruler orientation ("horizontal" or "vertical").
             * @attribute orient
             * @type {String}
             * @readOnly
             * @default "horizontal"
             */
            this.orient = "horizontal";

            /**
             * Ruler labels alignment
             * @type {String}
             * @attribute labelsAlignment
             * @default "normal"
             * @readOnly
             */
            this.labelsAlignment = "normal"; // "invert"

            /**
             * Ruler labels provider
             * @type {zebkit.draw.BaseViewProvider}
             * @attribute provider
             * @readOnly
             * @protected
             */
            this.provider = null;

            /**
             * Indicates if labels have to be rendered
             * @attribute showLabels
             * @type {Boolean}
             * @default true
             * @readOnly
             */
            this.showLabels = true;

            /**
             * Indicate if stroke has to be rendered
             * @type {Boolean}
             * @attribute showStrokes
             * @readOnly
             * @default true
             */
            this.showStrokes = true;

            this.$min    = 0;
            this.$max    = 100;

            this.$minGap = this.$maxGap = 0;
            this.$psW    = this.$psH = 0;
            this.$maxLabSize = 0;

            /**
             * Show ruler labels with percentage.
             * @param  {Integer} [precision] a precision
             * @chainable
             * @method showPercentage
             */
            this.showPercentage = function(precision) {
                this.setLabelsRender(new this.clazz.PercentageLabels(arguments.length > 0 ? precision
                                                                                          : 0));
                return this;
            };

            /**
             * Show ruler labels with number.
             * @param  {Integer} [precision] a precision
             * @chainable
             * @method showNumbers
             */
            this.showNumbers = function(precision) {
                this.setLabelsRender(new this.clazz.NumLabels(arguments.length > 0 ? precision : 0));
                return this;
            };

            /**
             * Set the ruler color.
             * @param {String} c a color
             * @method setColor
             * @chainable
             */
            this.setColor = function(c) {
                if (c !== this.color) {
                    this.color = c;
                    this.repaint();
                }
                return this;
            };

            /**
             * Set the ruler gap between stroke and labels.
             * @param {Integer} gap a gap
             * @method setGap
             * @chainable
             */
            this.setGap = function(gap) {
                if (this.gap !== gap) {
                    this.gap = gap;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set visibility of labels
             * @param {Boolean} b a boolean value that indicates if the
             * labels has to be shown
             * @method setShowLabels
             * @chainable
             */
            this.setShowLabels = function(b) {
                if (this.showLabels !== b) {
                    this.showLabels = b;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set visibility of strokes
             * @param {Boolean} b a boolean value that indicates if the
             * strokes have to be shown
             * @method setShowStrokes
             * @chainable
             */
            this.setShowStrokes = function(b) {
                if (this.showStrokes !== b) {
                    this.showStrokes = b;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set the labels font
             * @param {String|zebkit.Font} font a font of labels
             * @method setLabelsFont
             * @chainable
             */
            this.setLabelsFont = function() {
                if (this.provider !== null) {
                    this.provider.setFont.apply(this.provider,
                                                arguments);
                    this.vrp();
                }
                return this;
            };

            /**
             * Set the labels color
             * @param {String} color a color of labels
             * @method setLabelsColor
             * @chainable
             */
            this.setLabelsColor = function() {
                if (this.provider !== null) {
                    this.provider.setColor.apply(this.provider,
                                                 arguments);
                    this.vrp();
                }
                return this;
            };

            /**
             * Set the stroke size.
             * @param {Integer} strokeSize a stroke size
             * @method setStrokeSize
             * @chainable
             */
            this.setStrokeSize = function(strokeSize) {
                if (this.strokeSize !== strokeSize) {
                    this.strokeSize = strokeSize;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set the labels render
             * @param {zebkit.draw.BaseViewProvider} r labels render
             * @method setLabelsRender
             * @chainable
             */
            this.setLabelsRender = function(p) {
                if (this.provider !== p) {
                    this.provider = p;
                    if (this.showLabels === true) {
                        this.vrp();
                    }
                }
                return this;
            };

            /**
             * Set the ruler labels alignment. Label alignment specifies a side the labels has
             * to be placed relatively stroke.
             * @param {String} a labels alignment. The value can be "normal" or "invert"
             * @method setLabelsAlignment
             * @chainable
             */
            this.setLabelsAlignment = function(a) {
                if (this.labelsAlignment !== a) {
                    zebkit.util.validateValue(a, "normal", "invert");
                    this.labelsAlignment = a;
                    this.repaint();
                }
                return this;
            };

            /**
             * Set the ruler range.
             * @param {Number} min a minimal value of the range
             * @param {Number} max a maximal value of the range
             * @method setRange
             * @chainable
             */
            this.setRange = function(min, max) {
                if (min >= max) {
                    throw new Error("Invalid range [" + min + "," + max + "]");
                }

                if (this.$min !== min || this.$max !== max) {
                    this.$min = min;
                    this.$max = max;
                    this.vrp();
                }
                return this;
            };

            /**
             * Get the ruler effective size. The size includes only pixels that are
             * used to be transformed into range values.
             * @return {Integer} a ruler size
             * @protected
             * @method $getRulerSize
             */
            this.$getRulerSize = function() {
                var s = (this.orient === "horizontal" ? this.width  - this.getLeft() - this.getRight()
                                                      : this.height - this.getTop()  - this.getBottom());
                return s - this.$minGap - this.$maxGap;
            };

            /**
             * Get a minimal value in the ruler values range
             * @return {Number} a minimal range value
             * @method getMin
             */
            this.getMin = function() {
                return this.$min;
            };

            /**
             * Get a maximal value in the ruler values range
             * @return {Number} a maximal range value
             * @method getMax
             */
            this.getMax = function() {
                return this.$max;
            };

            /**
             * Project the given range value to appropriate ruler component coordinate
             * @param  {Number} v a range value
             * @return {Integer} coordinate
             * @method toLocation
             */
            this.toLocation = function(v) {
                var max = this.getMax(),
                    min = this.getMin(),
                    xy  = Math.floor((this.$getRulerSize() * (v - min)) / (max - min));

                return  (this.orient === "vertical") ? this.height - this.getBottom() - this.$minGap - xy
                                                     : this.getLeft() + this.$minGap + xy;
            };

            /**
             * Project the given ruler component coordinate to a range value.
             * @param  {Integer} xy a x or y (depending on the ruler orientation) coordinate
             * @return {Number} a range value
             * @method toValue
             */
            this.toValue = function(xy) {
                var min = this.getMin(),
                    max = this.getMax(),
                    sl  = (this.orient === "horizontal") ? this.getLeft() + this.$minGap
                                                         : this.getTop()  + this.$minGap,
                    ss = this.$getRulerSize();

                if (this.orient === "vertical") {
                    xy = this.height - xy - 1;
                }

                if (xy < sl) {
                    xy = sl;
                } else if (xy > sl + ss) {
                    xy = sl + ss;
                }

                return min + ((max - min) * (xy - sl)) / ss;
            };

            /**
             * Set the ruler orientation
             * @param {String} o an orientation. Use "horizontal" or "vertical" values.
             * @method setOrientation
             * @chainable
             */
            this.setOrientation = function(o) {
                if (this.orient !== o) {
                    this.orient = zebkit.util.validateValue(o, "vertical", "horizontal");
                    this.vrp();
                }
                return this;
            };

            this.calcPreferredSize = function() {
                return {
                    width  : this.$psW,
                    height : this.$psH
                };
            };

            this.recalc = function() {
                this.$maxLabSize = this.$psW = this.$psH = this.$maxGap = this.$minGap = 0;
                if (this.isVisible) {
                    this.recalcMetrics();
                }
            };

            /**
             * Called when the ruler requires its metric recalculation
             * @method recalcMetrics
             */
            this.recalcMetrics = function() {
                if (this.provider !== null && this.showLabels === true) {
                    // TODO: pay attention since view render shares single render
                    // don't store instance of view and then store another instance
                    // of view
                    var minView   = this.provider.getView(this, this.getMin()),
                        minViewPs = minView === null ? { width: 0, height: 0 } : minView.getPreferredSize(),
                        maxView   = this.provider.getView(this, this.getMax()),
                        maxViewPs = maxView === null ? { width: 0, height: 0 } : maxView.getPreferredSize();

                    if (this.orient === "horizontal") {
                        this.$minGap = Math.round(minViewPs.width / 2);
                        this.$maxGap = Math.round(maxViewPs.width / 2);
                        this.$maxLabSize = Math.max(minViewPs.height, maxViewPs.height);
                    } else {
                        this.$maxLabSize = Math.max(minViewPs.width, maxViewPs.width);
                        this.$minGap = Math.round(minViewPs.height / 2);
                        this.$maxGap = Math.round(maxViewPs.height / 2);
                    }
                }

                if (this.orient === "vertical") {
                    this.$psH = 50 * this.lineWidth  + this.$minGap + this.$maxGap;
                    this.$psW = (this.showStrokes ? this.strokeSize : 0) +
                                (this.$maxLabSize === 0 ? 0 : this.$maxLabSize + this.gap);
                } else {
                    this.$psW = 50 * this.lineWidth + this.$minGap + this.$maxGap;
                    this.$psH = (this.showStrokes ? this.strokeSize : 0) +
                                (this.$maxLabSize === 0 ? 0 : this.$maxLabSize + this.gap);
                }
            };

            //   =================================================================
            //            ^                            ^
            //            |  top                       | top
            //        . . .                        +---------+ . . .
            //       ||   ^                        | Label   |   ^
            //       ||   | strokeSize             |         |   |  $maxLabSize
            //       ||   |                        +---------+ . |
            //            ^                                      ^
            //            |  gap                        . . . . .|  gap
            //    +---------+ . . .                    ||        ^
            //    | Label   |   ^                      ||        |
            //    |         |   |  $maxLabSize         ||        | strokeSize
            //    +---------+ . ^                       . . . . .^
            //                  |  bottom                        | bottom
            // ==================================================================

            this.paint = function(g) {
                if (this.provider !== null) {
                    var min  = this.getMin(),
                        max  = this.getMax(),
                        view = null,
                        yy   = 0,
                        xx   = 0,
                        ps   = null,
                        ss   = this.showStrokes ? this.strokeSize : 0;

                    g.setColor(this.color);

                    if (this.orient === "horizontal") {
                        yy   = this.getTop();
                        xx   = this.getLeft() + this.$minGap;

                        if (this.showLabels) {
                            view = this.provider.getView(this, min);
                            if (view !== null) {
                                ps = view.getPreferredSize();
                                view.paint(g,
                                           this.toLocation(min) - Math.round(ps.width / 2),
                                           this.labelsAlignment === "normal" ? yy + ss + this.gap
                                                                             : yy + this.$maxLabSize - ps.height,
                                           ps.width,
                                           ps.height,
                                           this);
                            }

                            view = this.provider.getView(this, max);
                            if (view !== null) {
                                ps = view.getPreferredSize();
                                view.paint(g,
                                           this.toLocation(max) - Math.round(ps.width / 2),
                                           this.labelsAlignment === "normal" ? yy + ss + this.gap
                                                                             : yy + this.$maxLabSize - ps.height,
                                           ps.width,
                                           ps.height,
                                           this);
                            }

                            if (this.labelsAlignment !== "normal") {
                                yy += (this.$maxLabSize + this.gap);
                            }
                        }

                        g.drawLine(xx, yy, xx, yy + ss, this.lineWidth);
                        xx = this.width - this.getRight() - this.$maxGap - 1;
                        g.drawLine(xx, yy, xx, yy + ss, this.lineWidth);

                    } else {
                        yy   = this.getTop() + this.$maxGap;
                        xx   = this.getLeft();
                        if (this.showLabels) {
                            view = this.provider.getView(this, min);
                            if (view !== null) {
                                ps = view.getPreferredSize();
                                this.paintLabel(g,
                                                this.labelsAlignment === "normal" ? xx + this.$maxLabSize - ps.width
                                                                                  : ss + this.gap + xx,
                                                this.toLocation(min) - Math.round(ps.height / 2),
                                                ps.width, ps.height,
                                                view, min);
                            }

                            view = this.provider.getView(this, max);
                            if (view !== null) {
                                ps = view.getPreferredSize();
                                this.paintLabel(g,
                                                this.labelsAlignment === "normal" ? xx + this.$maxLabSize - ps.width
                                                                                  : ss + this.gap + xx,
                                                this.toLocation(max) - Math.round(ps.height / 2),
                                                ps.width,
                                                ps.height,
                                                view, max);
                            }

                            if (this.labelsAlignment === "normal") {
                                xx += (this.$maxLabSize + this.gap);
                            }
                        }

                        g.drawLine(xx, yy, xx + ss, yy, this.lineWidth);
                        yy = this.height - this.getBottom() - this.$minGap - 1;
                        g.drawLine(xx, yy, xx + ss, yy, this.lineWidth);
                    }
                }
            };

            this.paintLabel = function(g, x, y, w, h, v, value) {
                if (v !== null) {
                    v.paint(g, x, y, w, h, this);
                }
            };

            this.getLabelAt = function(x, y) {
                return null;
            };
        }
    ]);

    /**
     * Pointer ruler class. The ruler uses generator class instance to get and render labels values
     * @param  {String} o an orientation.
     * @constructor
     * @class zebkit.ui.PointRulerPan
     * @extends zebkit.ui.RulerPan
     */
    pkg.PointRulerPan = Class(pkg.RulerPan, [
        function() {
            this.$supera(arguments);
            this.$generator = new this.clazz.DeltaPointsGenerator(10);
        },

        function $clazz() {
            /**
             * Basic class to implement sequence of points values
             * @class zebkit.ui.PointRulerPan.PointsGenerator
             * @constructor
             */
            this.PointsGenerator = Class([
                function $prototype() {
                    /**
                     * Generate next point value in the sequence or null if end of sequence has been reached.
                     * @param  {zebkit.ui.RulerPan} ruler a ruler
                     * @param  {Integer} index a point index
                     * @return {Number} a value for the given point with the specified index
                     * @method pointValue
                     */
                    this.pointValue = function(ruler, index) {
                        return null;
                    };
                }
            ]);

            /**
             * Delta point generator implementation. The generator uses fixed delta value
             * to calculate next value of the points sequence.
             * @param {Number} [delta] a delta
             * @class zebkit.ui.PointRulerPan.DeltaPointsGenerator
             * @extends zebkit.ui.PointRulerPan.PointsGenerator
             * @constructor
             */
            this.DeltaPointsGenerator = Class(this.PointsGenerator, [
                function(delta) {
                    if (arguments.length > 0) {
                        this.$delta = delta;
                    }
                },

                function $prototype() {
                    /**
                     * Delta
                     * @attribute $delta
                     * @type {Number}
                     * @readOnly
                     * @protected
                     */
                    this.$delta = 0;

                    this.pointValue = function(ruler, i) {
                        if (this.$delta === 0) {
                            return null;
                        } else {
                            var v = ruler.getMin() + i * this.$delta;
                            return (v > ruler.getMax()) ? null : v;
                        }
                    };
                }
            ]);
        },

        /**
         * @for zebkit.ui.PointRulerPan
         */
        function $prototype() {
            this.$generator = null;

            /**
             * Set the points values generator
             * @param {zebkit.ui.PointRulerPan.PointsGenerator} g a point generator
             * @method setPointsGenerator
             */
            this.setPointsGenerator = function(g) {
                if (this.$generator !== g) {
                    this.$generator = g;
                    this.vrp();
                }
                return this;
            };

            /**
             * Setup delta points generator. The generator builds points sequence basing on incrementing
             * the sequence with fixed delta number.
             * @param  {Number} delta a delta
             * @chainable
             * @method useDeltaPointsGenerator
             */
            this.useDeltaPointsGenerator = function(delta) {
                this.setPointsGenerator(new this.clazz.DeltaPointsGenerator(delta));
                return this;
            };

            this.recalcMetrics = function() {
                if (this.provider !== null && this.showLabels === true) {
                    var i   = 0,
                        v   = null,
                        min = this.getMin(),
                        max = this.getMax();

                    while ((v = this.$generator.pointValue(this, i++)) !== null) {
                        var view = this.provider.getView(this, v);
                        if (view !== null) {
                            var ps = view.getPreferredSize();
                            if (this.orient === "horizontal") {
                                if (ps.height > this.$maxLabSize) {
                                    this.$maxLabSize = ps.height;
                                }

                                if (min === v) {
                                    this.$minGap = Math.round(ps.width / 2);
                                } else if (max === v) {
                                    this.$maxGap = Math.round(ps.width / 2);
                                }

                            } else {
                                if (ps.width > this.$maxLabSize) {
                                    this.$maxLabSize = ps.width;
                                }

                                if (min === v) {
                                    this.$minGap = Math.round(ps.height / 2);
                                } else if (max === v) {
                                    this.$maxGap = Math.round(ps.height / 2);
                                }
                            }
                        }
                    }
                }

                if (this.orient === "vertical") {
                    this.$psH = 50 + this.$minGap + this.$maxGap;
                    this.$psW = (this.showStrokes ? this.strokeSize : 0) +
                                (this.$maxLabSize === 0 ? 0 : this.$maxLabSize + this.gap);
                } else {
                    this.$psW = 50 + this.$minGap + this.$maxGap;
                    this.$psH = (this.showStrokes ? this.strokeSize : 0) +
                                (this.$maxLabSize === 0 ? 0 : this.$maxLabSize + this.gap);
                }
            };

            this.paint = function(g) {
                if (this.$generator !== null) {
                    var y          = this.getTop(),
                        x          = this.getLeft(),
                        prevLabLoc = null,
                        prevPs     = null,
                        v          = null,
                        i          = 0,
                        j          = 0,
                        ss         = this.showStrokes ? this.strokeSize : 0;

                    g.beginPath();
                    while ((v = this.$generator.pointValue(this, i++)) !== null) {
                        var loc = this.toLocation(v);

                        if (this.provider !== null && this.showLabels === true) {
                            var view     = this.provider.getView(this, v),
                                rendered = false;

                            if (view !== null) {
                                var ps = view.getPreferredSize();

                                if (this.orient === "horizontal") {
                                    if (prevLabLoc === null || loc > prevLabLoc + prevPs.width) {
                                        this.paintLabel(g,
                                                        loc - Math.floor(ps.width / 2),
                                                        this.labelsAlignment === "normal" ?  y  + ss + this.gap
                                                                                          :  y,
                                                        ps.width, ps.height,
                                                        view, v);

                                        prevLabLoc = loc;
                                        prevPs     = ps;
                                        rendered   = true;
                                    }
                                } else {
                                    if (prevLabLoc === null || Math.round(loc + ps.height/2) < prevLabLoc) {
                                        prevLabLoc = loc - Math.floor(ps.height / 2);
                                        this.paintLabel(g,
                                                        this.labelsAlignment === "normal" ? x  + ss + this.gap
                                                                                          : x,
                                                        prevLabLoc,
                                                        ps.width,
                                                        ps.height,
                                                        view, v);
                                        rendered = true;
                                    }
                                }
                            }

                            if (rendered === true && this.showStrokes) {
                                if (this.orient === "horizontal") {
                                    if (this.labelsAlignment === "normal") {
                                        g.moveTo(loc + 0.5, y);
                                        g.lineTo(loc + 0.5, y + this.strokeSize);
                                    } else {
                                        g.moveTo(loc + 0.5, y + this.$maxLabSize + this.gap);
                                        g.lineTo(loc + 0.5, y + this.$maxLabSize + this.gap + this.strokeSize);
                                    }
                                } else {
                                    if (this.labelsAlignment === "normal") {
                                        g.moveTo(x, loc + 0.5);
                                        g.lineTo(x + this.strokeSize, loc + 0.5);
                                    } else {
                                        g.moveTo(x + this.$maxLabSize + this.gap, loc + 0.5);
                                        g.lineTo(x + this.$maxLabSize + this.gap + this.strokeSize, loc + 0.5);
                                    }
                                }
                            }

                        } else {
                            if (this.showStrokes) {
                                if (this.orient === "horizontal") {
                                    if (this.labelsAlignment === "normal") {
                                        g.moveTo(loc + 0.5, y);
                                        g.lineTo(loc + 0.5, y + this.strokeSize);
                                    } else {
                                        g.moveTo(loc + 0.5, y + this.$maxLabSize + this.gap);
                                        g.lineTo(loc + 0.5, y + this.$maxLabSize + this.gap + this.strokeSize);
                                    }
                                } else {
                                    if (this.labelsAlignment === "normal") {
                                        g.moveTo(x, loc + 0.5);
                                        g.lineTo(x + this.strokeSize, loc + 0.5);
                                    } else {
                                        g.moveTo(x + this.$maxLabSize + this.gap, loc + 0.5);
                                        g.lineTo(x + this.$maxLabSize + this.gap + this.strokeSize, loc + 0.5);
                                    }
                                }
                            }
                        }
                    }

                    g.lineWidth = this.lineWidth;
                    g.setColor(this.color);
                    g.stroke();
                }
            };
        }
    ]);

    /**
     * Linear ruler class. The ruler draws strokes using dedicated pixel delta value.
     * @param  {String} [o] an orientation (use "vertical" or "horizontal" as the parameter value)
     * @class zebkit.ui.LinearRulerPan
     * @constructor
     * @extends zebkit.ui.RulerPan
     */
    pkg.LinearRulerPan = Class(pkg.RulerPan, [
        function $prototype() {
            this.strokeStep = 2;
            this.longStrokeRate = this.strokeStep * 8;

            this.setStrokeStep = function(strokeStep, longStrokeRate) {
                var b = false;
                if (strokeStep !== this.strokeStep) {
                    this.strokeStep = strokeStep;
                    b = true;
                }

                if (arguments.length > 1) {
                    if (this.longStrokeRate !== longStrokeRate) {
                        this.longStrokeRate = longStrokeRate;
                        b = true;
                    }
                } else if (this.longStrokeRate <= 2 * strokeStep) {
                    this.longStrokeRate = strokeStep * 8;
                    b = true;
                }

                if (b) {
                    this.repaint();
                }

                return this;
            };

            this.paint = function(g) {
                var i          = 0,
                    ss         = this.showStrokes ? this.strokeSize : 0,
                    ps         = null,
                    prevLabLoc = null,
                    prevPs     = null,
                    rendered   = false,
                    v          = null,
                    view       = null,
                    loc        = 0;

                g.beginPath();

                if (this.orient === "horizontal") {
                    var y          = this.getTop(),
                        xx         = this.getLeft() + this.$minGap,
                        maxX       = this.width - this.getRight() - this.$maxGap - 1;

                    for (i = 0; xx <= maxX; i++, xx += this.strokeStep) {
                        if (i % this.longStrokeRate === 0) {
                            rendered = false;

                            if (this.provider !== null && this.showLabels) {
                                v    = this.toValue(xx);
                                view = this.provider.getView(this, v);

                                if (view !== null) {
                                    ps = view.getPreferredSize();

                                    loc = xx - Math.round(ps.width / 2);
                                    if (prevLabLoc === null || loc > prevLabLoc + prevPs.width) {
                                        this.paintLabel(g,
                                                        loc,
                                                        this.labelsAlignment === "normal" ? y + 2 * ss + this.gap
                                                                                          : y,
                                                        ps.width, ps.height, view, v);

                                        prevLabLoc = loc;
                                        prevPs  = ps;
                                        rendered = true;
                                    }
                                }
                            }

                            if (this.showStrokes) {
                                if (this.labelsAlignment === "normal") {
                                    g.moveTo(xx + 0.5, y);
                                    g.lineTo(xx + 0.5, y + (rendered ? 2 * ss : ss));
                                } else {
                                    g.moveTo(xx + 0.5, y + this.$maxLabSize + this.gap + (rendered ? 0 : ss));
                                    g.lineTo(xx + 0.5, y + this.$maxLabSize + this.gap + 2 * ss);
                                }
                            }
                        } else if (this.showStrokes) {
                            if (this.labelsAlignment === "normal") {
                                g.moveTo(xx + 0.5, y);
                                g.lineTo(xx + 0.5, y + ss);
                            } else {
                                g.moveTo(xx + 0.5, y + this.$maxLabSize + this.gap + ss);
                                g.lineTo(xx + 0.5, y + this.$maxLabSize + this.gap + 2 * ss);
                            }
                        }
                    }
                } else {
                    var x    = this.getLeft(),
                        yy   = this.height - this.getBottom() - this.$minGap - 1,
                        minY = this.getTop() + this.$maxGap;

                    for (i = 0; yy >= minY; i++, yy -= this.strokeStep) {
                        if (i % this.longStrokeRate === 0) {
                            rendered = false;

                            if (this.provider !== null && this.showLabels) {
                                v    = this.toValue(yy);
                                view = this.provider.getView(this, v);

                                if (view !== null) {
                                    ps = view.getPreferredSize();

                                    loc = yy - Math.round(ps.height / 2);
                                    if (prevLabLoc === null || (loc + ps.height) < prevLabLoc) {
                                        this.paintLabel(g,
                                                        this.labelsAlignment === "normal" ? x + 2 * ss + this.gap
                                                                                          : x,
                                                        loc,
                                                        ps.width, ps.height, view, v);

                                        prevLabLoc = loc;
                                        rendered = true;
                                    }
                                }
                            }

                            if (this.showStrokes) {
                                if (this.labelsAlignment === "normal") {
                                    g.moveTo(x, yy + 0.5);
                                    g.lineTo(x + (rendered ? 2 * ss : ss), yy + 0.5);
                                } else {
                                    g.moveTo(x + this.$maxLabSize + this.gap + (rendered ? 0 : ss), yy + 0.5);
                                    g.lineTo(x + this.$maxLabSize + this.gap + 2 * ss, yy + 0.5);
                                }
                            }
                        } else if (this.showStrokes) {
                            if (this.labelsAlignment === "normal") {
                                g.moveTo(x, yy + 0.5);
                                g.lineTo(x + ss, yy + 0.5);
                            } else {
                                g.moveTo(x + this.$maxLabSize + this.gap + ss, yy + 0.5);
                                g.lineTo(x + this.$maxLabSize + this.gap + 2 * ss, yy + 0.5);
                            }
                        }
                    }
                }

                g.setColor(this.color);
                g.lineWidth = this.lineWidth;
                g.stroke();
            };
        },

        function recalcMetrics() {
            this.$super();
            if (this.orient === "horizontal") {
                this.$psH += this.strokeSize;
            } else {
                this.$psW += this.strokeSize;
            }
        }
    ]);

    /**
     * Slider UI component class.
     * @class  zebkit.ui.Slider
     * @param {String} [o]  a slider orientation ("vertical or "horizontal")
     * @constructor
     * @extends zebkit.ui.Panel
     * @uses   zebkit.ui.HostDecorativeViews
     * @uses   zebkit.EvenetProducer
     */
    pkg.Slider = Class(pkg.Panel, pkg.HostDecorativeViews, [
        function(o) {
            this.views = {
                marker: null,
                gauge : null
            };

            this.$super();

            var ruler = null;
            if (arguments.length > 0) {
                if (zebkit.instanceOf(o, zebkit.ui.RulerPan)) {
                    this.orient = o.orient;
                    ruler = o;
                } else {
                    ruler = new pkg.RulerPan(o);
                }
            } else {
                ruler = new pkg.RulerPan(this.orient);
            }

            this.add("ruler", ruler);
            this.add("gauge", new this.clazz.GaugePan());
        },

        function $clazz() {
            this.GaugePan = Class(pkg.Panel, []);
        },

        function $prototype() {
            /**
             * Current slider value
             * @type {Number}
             * @attribute value
             * @readOnly
             */
            this.value = 0;

            /**
             * Slider orientation.
             * @type {String}
             * @attribute orient
             * @readOnly
             */
            this.orient = "horizontal";

            /**
             * Gap between slider handle and ruler
             * @type {Integer}
             * @attribute gap
             * @readOnly
             * @default 4
             */
            this.gap = 4;

            this.canHaveFocus = true;

            /**
             * Granularity of sliding.
             * @type {Number}
             * @attribute granularity
             * @readOnly
             * @default 1
             */
            this.granularity = 1;

            /**
             * Ruler component.
             * @type {zebkit.ui.RulerPan}
             * @attribute ruler
             * @readOnly
             */
            this.ruler = null;


            this.gauge = null;


            this.handle = null;

            this.$dragged = false;
            this.$dxy = this.$val = 0;

            this.compAdded = function(e) {
                if (e.constraints === "ruler") {
                    this.ruler = e.kid;
                    this.orient = this.ruler.orient;
                    this.setValue(this.ruler.getMin());
                } else if (e.constraints === "gauge") {
                    this.gauge = e.kid;
                }
            };

            this.compRemoved = function(e) {
                if (this.gauge === e.kid) {
                    this.gauge = null;
                }
            };

            this.setHandleView = function(v) {
                if (this.handle !== v) {
                    this.handle = zebkit.draw.$view(v);
                    this.vrp();
                }
                return this;
            };

            /**
             * Get maximal possible value.
             * @return {Number} a value
             * @method getMax
             */
            this.getMax = function() {
                return this.ruler.getMax();
            };

            /**
             * Get minimal possible value.
             * @return {Number} a value
             * @method getMin
             */
            this.getMin = function() {
                return this.ruler.getMin();
            };

            this.toLocation = function(v) {
                return (this.orient === "horizontal") ? this.ruler.toLocation(v) + this.ruler.x
                                                      : this.ruler.toLocation(v) + this.ruler.y;
            };

            this.getHandleView = function() {
                var h = this.orient === "horizontal" ? this.views.horHandle
                                                     : this.views.verHandle;
                return h === undefined ? null : h;
            };

            this.getHandlePreferredSize = function() {
                var h = this.orient === "horizontal" ? this.views.horHandle
                                                     : this.views.verHandle;
                return h === undefined || h === null ? { width: 0, height: 0}
                                                     : h.getPreferredSize();
            };

            /**
             * Set orientation
             * @param {String} o an orientation. Use "horizontal" or "vertical" as the parameter value
             * @method setOrientation
             * @chainable
             */
            this.setOrientation = function(o) {
                if (this.orient !== o) {
                    this.orient = zebkit.util.validateValue(o, "vertical", "horizontal");
                    this.ruler.setOrientation(o);
                    this.vrp();
                }
                return this;
            };

            this.pointerDragged = function(e){
                if (this.$dragged) {
                    var max  = this.ruler.getMax(),
                        min  = this.ruler.getMin(),
                        dxy = (this.orient === "horizontal" ? e.x - this.$sxy : this.$sxy - e.y);

                    // TODO: ruler.toValue
                    this.setValue(this.$val + dxy * ((max - min)/ this.ruler.$getRulerSize()));
                }
            };

            //
            //    +---------------------------------------------------------
            //    |        ^
            //    |        | top
            //    |      . . . . . . . . . . . . . . . . . . . . . . . . . .
            //    | left .                ------ ----------------------
            //    |<---->.               |      |                    ^
            //    |      .               |      |                    |
            //    |      . ==============|      |=================   |  handler
            //    |      . ==============|      |=================   | preferred
            //    |      .               |      |                    |   height
            //    \      .               |      |                    |
            //    |      .                ------
            //    |      .                  ^
            //    |      .                  | gap
            //    |      .  |---|---|---|---|---|---|---|---|---|---|---|  ^
            //    |      .              |               |               |  | 2 * netSize
            //    |      .              ^
            //    |      .              | gap
            //    |      .            Num_1            Num_2          Num_3
            //

            this.paintOnTop = function(g) {
                var left        = this.getLeft(),
                    top         = this.getTop(),
                    right       = this.getRight(),
                    bottom      = this.getBottom(),
                    handleView  = this.getHandleView(),
                    handlePs    = this.getHandlePreferredSize(),
                    w           = this.width  - left - right,
                    h           = this.height - top  - bottom;

                if (this.orient === "horizontal") {
                    if (handleView !== null) {
                        handleView.paint(g, this.getHandleLoc(),
                                            top,
                                            handlePs.width,
                                            handlePs.height,
                                            this);
                    }
                } else {
                    if (handleView !== null) {
                        handleView.paint(g, left,
                                            this.getHandleLoc(),
                                            handlePs.width,
                                            handlePs.height,
                                            this);
                    }
                }

                if (this.hasFocus() && this.views.marker) {
                    this.views.marker.paint(g, left, top, w, h, this);
                }
            };

            this.getHandleLoc = function() {
                var hs = this.getHandlePreferredSize();
                return (this.orient === "horizontal") ? this.toLocation(this.value) - Math.round(hs.width  / 2)
                                                      : this.toLocation(this.value) - Math.round(hs.height / 2);
            };

            this.getHandleBounds = function() {
                var bs = this.getHandlePreferredSize();
                return this.orient === "horizontal" ? {
                    x: this.getHandleLoc(),
                    y: this.getTop(),
                    width : bs.width,
                    height: bs.height
                }
                                                   : {
                    x: this.getLeft(),
                    y: this.getHandleLoc(),
                    width : bs.width,
                    height: bs.height
                };
            };

            this.catchInput = function(target) {
                return target !== this.ruler || this.ruler.catchInput !== true;
            };

            this.doLayout = function(t) {
                var gaugePs = this.gauge !== null && this.gauge.isVisible ? this.gauge.getPreferredSize() : null,
                    hs      = this.getHandlePreferredSize(),
                    h2s     = this.orient === "vertical" ? Math.round(hs.height / 2)
                                                         : Math.round(hs.width / 2);


                if (this.orient === "vertical") {
                    var y = this.getTop() + (this.ruler.$maxGap >= h2s ? 0 : h2s - this.ruler.$maxGap);

                    this.ruler.setLocation(this.getLeft() + hs.width + this.gap, y);
                    this.ruler.setSize(this.ruler.getPreferredSize().width,
                        this.height - y - this.getBottom() -
                                       (this.ruler.$minGap >= h2s ? 0 : (h2s - this.ruler.$minGap)));

                    if (this.gauge !== null && this.gauge.isVisible) {
                        this.gauge.setBounds(this.getLeft() + Math.floor((hs.width - gaugePs.width) / 2),
                                             this.getTop(),
                                             gaugePs.width,
                                             this.height - this.getTop() - this.getBottom());
                    }

                } else {
                    var x = this.getLeft() + (this.ruler.$minGap >= h2s ? 0 : h2s - this.ruler.$minGap);

                    this.ruler.setLocation(x, this.getTop() + hs.height + this.gap);
                    this.ruler.setSize(this.width - x - this.getRight() -
                                       (this.ruler.$maxGap >= h2s ? 0 : (h2s - this.ruler.$maxGap)),
                                        this.ruler.getPreferredSize().height);



                    if (this.gauge !== null && this.gauge.isVisible) {
                        this.gauge.setBounds(this.getLeft(),
                                             this.getTop() + Math.floor((hs.height - gaugePs.height) / 2),
                                             this.width - this.getLeft() - this.getRight(),
                                             gaugePs.height);
                    }
                }
            };

            this.calcPreferredSize = function(l) {
                var ps  = this.getHandlePreferredSize();

                if (this.ruler.isVisible === true) {
                    var rps = this.ruler.getPreferredSize(),
                        h2s = 0;

                    if (this.orient === "horizontal") {
                        h2s = Math.round(ps.width / 2);
                        ps.height += (this.gap + rps.height);
                        ps.width = 10 * ps.width +
                                   Math.max(h2s, this.ruler.isVisible ? this.ruler.$minGap : 0) +
                                   Math.max(h2s, this.ruler.isVisible ? this.ruler.$maxGap : 0);
                    } else {
                        h2s = Math.round(ps.height / 2);
                        ps.height = 10 * ps.height +
                                    Math.max(h2s, this.ruler.isVisible ? this.ruler.$minGap : 0) +
                                    Math.max(h2s, this.ruler.isVisible ? this.ruler.$maxGap : 0);

                        ps.width += (this.gap + rps.width);
                    }
                }
                return ps;
            };

            /**
             * Set the slider value that has to be withing the given defined range.
             * If the value is out of the defined range then the value will be
             * adjusted to maximum or minimum possible value.
             * @param {Number} v a value
             * @method setValue
             * @chainable
             */
            this.setValue = function(v) {
                // normalize value
                v = Math.round(v / this.granularity) * this.granularity;

                var max = this.getMax(),
                    min = this.getMin();

                // align value
                if (v > max) {
                    v = max;
                } else if (v < min) {
                    v = min;
                }

                var prev = this.value;
                if (this.value !== v){
                    this.value = v;
                    this.fire("fired", [this, prev]);
                    this.repaint();
                }

                return this;
            };

            this.keyPressed = function(e) {
                switch(e.code) {
                    case "ArrowDown":
                    case "ArrowLeft":
                        this.setValue(this.value - this.granularity);
                        break;
                    case "ArrowUp":
                    case "ArrowRight":
                        this.setValue(this.value + this.granularity);
                        break;
                    case "Home":
                        this.setValue(this.getMin());
                        break;
                    case "End":
                        this.setValue(this.getMax());
                        break;
                }
            };

            this.pointerClicked = function (e){
                if (e.isAction()) {
                    var x = e.x,
                        y = e.y,
                        handle = this.getHandleBounds();

                    if (x < handle.x ||
                        y < handle.y ||
                        x >= handle.x + handle.width ||
                        y >= handle.y + handle.height   )
                    {
                        if (this.getComponentAt(x, y) !== this.ruler) {
                            var l = ((this.orient === "horizontal") ? x - this.ruler.x
                                                                    : y - this.ruler.y);
                            this.setValue(this.ruler.toValue(l));
                        }
                    }
                }
            };

            this.pointerDragStarted = function(e){
                var r = this.getHandleBounds();
                if (e.x >= r.x           &&
                    e.y >= r.y           &&
                    e.x < r.x + r.width  &&
                    e.y < r.y + r.height   )
                {
                    this.$dragged = true;
                    this.$sxy     = this.orient === "horizontal" ? e.x : e.y;
                    this.$val     = this.value;
                }
            };

            this.pointerDragEnded = function(e) {
                this.$dragged = false;
            };

            /**
             * Set the granularity. Granularity defines a delta to a slider value
             * can be decreased or increased.
             * @param {Number} g a granularity.
             * @method setGranularity
             * @chainable
             */
            this.setGranularity = function(g) {
                if (g >= (this.getMax() - this.getMin())) {
                    throw new Error("Invalid granularity " + g);
                }

                if (this.granularity !== g) {
                    this.granularity = g;
                    this.setValue(this.value);
                }
                return this;
            };

            /**
             * Set the range the slider value can be changed.
             * @param {Number} min a minimal possible value
             * @param {Number} max a maximal possible value
             * @param {Number} [granularity] a granularity
             * @method setRange
             * @chainable
             */
            this.setRange = function(min, max, granularity) {
                if (this.getMin() !== min || this.getMax() !== max || granularity !== this.granularity) {
                    this.ruler.setRange(min, max);
                    this.setGranularity(arguments.length > 2 ? granularity : this.granularity); // validate granularity
                    this.vrp();
                }
                return this;
            };

            /**
             * Set the ruler to be used
             * @param {zebkit.ui.RulerPan} r a ruler
             * @method setRuler
             * @chainable
             */
            this.setRuler = function(r) {
                this.setByConstraints("ruler", r);
                return this;
            };

            this.showPercentage = function() {
                this.ruler.showPercentage();
                return this;
            };

            this.showNumbers = function() {
                this.ruler.showNumbers();
                return this;
            };

            /**
             * Set the gap between the slider handle and the ruler.
             * @param {Integer} g a gap
             * @method setRulerGap
             * @chainable
             */
            this.setRulerGap = function(g) {
                if (g !== this.gap) {
                    this.gap = g;
                    this.vrp();
                }
                return this;
            };
        },

        function focused() {
            this.$super();
            this.repaint();
        }
    ]).events("fired");


    /**
     * Tabs UI panel. The component is used to organize switching between number of pages where every
     * page is an UI component.
     *
     * Filling tabs component with pages is the same to how you add an UI component to a panel. For
     * instance in the example below three pages with "Titl1", "Title2", "Title3" are added:
     *
     *     var tabs = new zebkit.ui.Tabs();
     *     tabs.add("Title1", new zebkit.ui.Label("Label as a page"));
     *     tabs.add("Title2", new zebkit.ui.Button("Button as a page"));
     *     tabs.add("Title3", new zebkit.ui.TextArea("Text area as a page"));
     *
     *  You can access tabs pages UI component the same way like you access a panel children components
     *
     *     ...
     *     tabs.kids[0] // access the first page
     *
     *  And you can remove it with standard panel inherited API:
     *
     *     ...
     *     tabs.removeAt(0); // remove first tab page
     *
     *
     *  To customize tab page caption and icon you should access tab object and do it with API it provides:
     *
     *
     *      // update a tab caption
     *      tabs.getTab(0).setCaption("Test");
     *
     *      // update a tab icon
     *      tabs.getTab(0).setIcon("my.gif");
     *
     *      // set a particular font and color for the tab in selected state
     *      tabs.getTab(0).setColor(true, "blue");
     *      tabs.getTab(0).setFont(true, new zebkit.Font("Arial", "bold", 16));
     *
     *      // set other caption for the tab in not selected state
     *      tabs.getTab(0).setCaption(false, "Test");
     *
     * @param {String} [o] the tab panel orientation:
     *
     *     "top"
     *     "bottom"
     *     "left"
     *     "right"
     *
     * @class zebkit.ui.Tabs
     * @uses  zebkit.ui.HostDecorativeViews
     * @uses  zebkit.EventProducer
     * @constructor
     * @extends zebkit.ui.Panel
     */

    /**
     * Fired when a new tab page has been selected
     *
     *     tabs.on(function(src, selectedIndex) {
     *        ...
     *     });
     *
     * @event selected
     * @param {zebkit.ui.Tabs} src a tabs component that triggers the event
     * @param {Integer} selectedIndex a tab page index that has been selected
     */
    pkg.Tabs = Class(pkg.Panel, pkg.HostDecorativeViews, [
        function(o) {
            /**
             * Selected tab page index
             * @attribute selectedIndex
             * @type {Integer}
             * @readOnly
             */
            this.vgap = this.hgap = this.tabAreaX = 0;
            this.repaintWidth = this.repaintHeight = this.repaintX = this.repaintY = 0;

            this.tabAreaY = this.tabAreaWidth = this.tabAreaHeight = 0;
            this.overTab = this.selectedIndex = -1;

            this.pages = [];
            this.views = {};

            if (pkg.Tabs.font !== undefined) {
                this.render.setFont(pkg.Tabs.font);
            }

            if (pkg.Tabs.fontColor !== undefined) {
                this.render.setColor(pkg.Tabs.fontColor);
            }

            this.$super();

            // since alignment pass as the constructor argument the setter has to be called after $super
            // because $super can re-set title alignment

            if (arguments.length > 0) {
                this.setAlignment(o);
            }
        },

        function $clazz() {
            /**
             * Tab view class that defines the tab page title and icon
             * @param {String|Image} [icon]  an path to an image or image object
             * @param {String} [caption] a tab caption
             * @class zebkit.ui.Tabs.TabView
             * @extends zebkit.ui.CompRender
             * @constructor
             */
            this.TabView = Class(pkg.CompRender, [
                function(icon, caption) {
                    if (arguments.length === 0) {
                        caption = "";
                    } else if (arguments.length === 1) {
                        caption = icon;
                        icon = null;
                    }

                    var tp = new this.clazz.TabPan();
                    this.$super(tp);

                    var $this = this;
                    tp.getImagePan().imageLoaded = function(img) {
                        $this.vrp();

                        // if the icon has zero width and height the repaint
                        // doesn't trigger validation. So let's do it on
                        // parent level
                        if ($this.owner !== null && $this.owner.parent !== null) {
                            $this.owner.repaint();
                        }
                    };

                    var r1 = new this.clazz.captionRender(caption),
                        r2 = new this.clazz.captionRender(caption);

                    r2.setColor(this.clazz.fontColor);
                    r1.setColor(this.clazz.selectedFontColor);
                    r2.setFont (zebkit.$font(this.clazz.font));
                    r1.setFont (zebkit.$font(this.clazz.selectedFont));

                    this.getCaptionPan().setView(
                        new zebkit.draw.ViewSet(
                            {
                                "selected": r1,
                                "*"       : r2
                            },
                            [
                                function setFont(id, f) {
                                    var v = this.views[id];
                                    if (v) {
                                        v.setFont(zebkit.$font(f));
                                        this.recalc();
                                    }
                                    return this;
                                },

                                function setCaption(id, s) {
                                    var v = this.views[id];
                                    if (v) {
                                        v.setValue(s);
                                        this.recalc();
                                    }
                                    return this;
                                },

                                function getCaption(id) {
                                    var v = this.views[id];
                                    return (v === null || v === undefined ? null : v.getValue());
                                }
                            ]
                        )
                    );

                    this.setIcon(icon);
                },

                function $clazz() {
                    this.captionRender = zebkit.draw.StringRender;
                    this.font = new zebkit.Font("Arial", 14);

                    this.TabPan = Class(pkg.Panel, [
                        function() {
                            this.$super();
                            this.add(new pkg.ImagePan(null));
                            this.add(new pkg.ViewPan());
                        },

                        function getImagePan() {
                            return this.kids[0];
                        },

                        function getViewPan() {
                            return this.kids[1];
                        }
                    ]);
                },

                function $prototype() {
                    this.owner = null;

                    this.ownerChanged = function(v) {
                        this.owner = v;
                    };

                    this.vrp = function() {
                        if (this.owner !== null) {
                            this.owner.vrp();
                        }
                    };

                    /**
                     * Set the given tab caption for the specified tab or both - selected and not selected - states.
                     * @param {Boolean} [b] the tab state. true means selected state.
                     * @param {String} s the tab caption
                     * @method setCaption
                     * @chainable
                     */
                    this.setCaption = function(b, s) {
                        if (arguments.length === 1) {
                            this.setCaption(true, b);
                            this.setCaption(false, b);
                        } else {
                            this.getCaptionPan().view.setCaption(this.$toId(b), s);
                            this.vrp();
                        }
                        return this;
                    };

                    /**
                     * Get the tab caption for the specified tab state
                     * @param {Boolean} b the tab state. true means selected state.
                     * @return {String} the tab caption
                     * @method getCaption
                     */
                    this.getCaption = function (b) {
                        return this.getCaptionPan().view.getCaption(this.$toId(b));
                    };

                    /**
                     * Set the given tab caption text color for the specified tab or both
                     * selected and not selected states.
                     * @param {Boolean} [b] the tab state. true means selected state.
                     * @param {String} c the tab caption
                     * @method setColor
                     * @chainable
                     */
                    this.setColor = function(b, c) {
                        if (arguments.length === 1) {
                            this.setColor(true, b);
                            this.setColor(false, b);
                        } else {
                            var v = this.getCaptionPan().view.views[this.$toId(b)];
                            if (v) {
                                v.setColor(c);
                                this.vrp();
                            }
                        }
                        return this;
                    };

                    /**
                     * Set the given tab caption text font for the specified or both
                     * selected not slected states.
                     * @param {Boolean} [b] the tab state. true means selected state.
                     * @param {zebkit.Font} f the tab text font
                     * @method setFont
                     * @chainable
                     */
                    this.setFont = function(b, f) {
                        if (arguments.length === 1) {
                            this.setFont(true, b);
                            this.setFont(false, b);
                        } else {
                            this.getCaptionPan().view.setFont(this.$toId(b), zebkit.$font(f));
                            this.vrp();
                        }
                        return this;
                    };

                    this.getCaptionPan = function () {
                        return this.target.getViewPan();
                    };

                    /**
                     * Set the tab icon.
                     * @param {String|Image} c an icon path or image object
                     * @method setIcon
                     * @chainable
                     */
                    this.setIcon = function (c) {
                        this.target.getImagePan().setImage(c);
                        this.target.getImagePan().setVisible(c !== null);
                        return this;
                    };

                    /**
                     * The method is invoked every time the tab selection state has been updated
                     * @param {zebkit.ui.Tabs} tabs the tabs component the tab belongs
                     * @param {Integer} i an index of the tab
                     * @param {Boolean} b a new state of the tab
                     * @method selected
                     */
                    this.selected = function(tabs, i, b) {
                        this.getCaptionPan().view.activate(this.$toId(b), this);
                    };

                    this.$toId = function(b) {
                        return b ? "selected" : "*";
                    };
                }
            ]);
        },

        /**
         * @for zebkit.ui.Tabs
         */
        function $prototype() {
            /**
             * Tab orientation
             * @attribute orient
             * @type {String}
             * @readOnly
             */
            this.orient = "top";

            /**
             * Sides gap
             * @attribute sideSpace
             * @type {Integer}
             * @readOnly
             * @default 1
             */
            this.sideSpace = 1;

            /**
             * Declare can have focus attribute to make the component focusable
             * @type {Boolean}
             * @attribute canHaveFocus
             * @readOnly
             */
            this.canHaveFocus = true;

            /**
             * Define pointer moved event handler
             * @param  {zebkit.ui.event.PointerEvent} e a key event
             * @method pointerMoved
             */
            this.pointerMoved = function(e) {
                var i = this.getTabAt(e.x, e.y);
                if (this.overTab !== i) {
                    this.overTab = i;
                    if (this.views.overTab) {
                        this.repaint(this.repaintX, this.repaintY,
                                     this.repaintWidth, this.repaintHeight);
                    }
                }
            };

            /**
             * Define pointer drag ended event handler
             * @param  {zebkit.ui.event.PointerEvent} e a key event
             * @method pointerDragEnded
             */
            this.pointerDragEnded = function(e) {
                var i = this.getTabAt(e.x, e.y);
                if (this.overTab !== i) {
                    this.overTab = i;
                    if (this.views.overTab) {
                        this.repaint(this.repaintX, this.repaintY,
                                     this.repaintWidth, this.repaintHeight);
                    }
                }
            };

            /**
             * Define pointer exited event handler
             * @param  {zebkit.ui.event.PointerEvent} e a key event
             * @method pointerExited
             */
            this.pointerExited = function(e) {
                if (this.overTab >= 0) {
                    this.overTab = -1;
                    if (this.views.overTab) {
                        this.repaint(this.repaintX, this.repaintY,
                                     this.repaintWidth, this.repaintHeight);
                    }
                }
            };

            /**
             * Navigate to a next tab page following the given direction starting
             * from the given page
             * @param  {Integer} page a starting page index
             * @param  {Integer} d a navigation direction. 1 means forward and -1 means backward
             * navigation.
             * @return {Integer} a new tab page index
             * @method next
             */
            this.next = function (page, d){
                for(; page >= 0 && page < Math.floor(this.pages.length / 2); page += d) {
                    if (this.isTabEnabled(page) === true) {
                        return page;
                    }
                }
                return -1;
            };

            this.getTitleInfo = function(){
                var b   = (this.orient === "left" || this.orient === "right"),
                    res = b ? { x      : this.tabAreaX,
                                y      : 0,
                                width  : this.tabAreaWidth,
                                height : 0,
                                orient : this.orient }
                            : { x      : 0,
                                y      : this.tabAreaY,
                                width  : 0,
                                height : this.tabAreaHeight,
                                orient : this.orient };

                if (this.selectedIndex >= 0){
                    var r = this.getTabBounds(this.selectedIndex);
                    if (b) {
                        res.y = r.y;
                        res.height = r.height;
                    } else {
                        res.x = r.x;
                        res.width = r.width;
                    }
                }
                return res;
            };

            /**
             * Test if the given tab page is in enabled state
             * @param  {Integer} index a tab page index
             * @return {Boolean} a tab page state
             * @method isTabEnabled
             */
            this.isTabEnabled = function (index){
                return this.kids[index].isEnabled;
            };

            this.paintOnTop = function(g){
                var ts = g.$states[g.$curState];
                // stop painting if the tab area is outside of clip area
                if (zebkit.util.isIntersect(this.repaintX, this.repaintY,
                                            this.repaintWidth, this.repaintHeight,
                                            ts.x, ts.y, ts.width, ts.height))
                {
                    var i = 0;
                    for(i = 0; i < this.selectedIndex; i++) {
                        this.paintTab(g, i);
                    }

                    for(i = this.selectedIndex + 1;i < Math.floor(this.pages.length / 2); i++) {
                        this.paintTab(g, i);
                    }

                    if (this.selectedIndex >= 0){
                        this.paintTab(g, this.selectedIndex);
                        if (this.hasFocus()) {
                            this.drawMarker(g, this.getTabBounds(this.selectedIndex));
                        }
                    }
                }
            };

            /**
             * Draw currently activate tab page marker.
             * @param  {CanvasRenderingContext2D} g a graphical context
             * @param  {Object} r a tab page title rectangular area
             * @method drawMarker
             */
            this.drawMarker = function(g,r){
                var marker = this.views.marker;
                if (marker) {
                    //TODO: why only "out" is checked ?
                    var bv   = (this.views.outTab === null || this.views.outTab === undefined ? null : this.views.outTab),
                        left = bv ? bv.getLeft() : 0,
                        top  = bv ? bv.getTop()  : 0;
                    marker.paint(g, r.x + left, r.y + top,
                                    r.width  - left - (bv === null ? 0 : bv.getRight()),
                                    r.height - top  - (bv === null ? 0 : bv.getBottom()), this);
                }
            };

            /**
             * Paint the given tab page title
             * @param  {CanvasRenderingContext2D} g a graphical context
             * @param  {Integer} pageIndex a tab page index
             * @method paintTab
             */
            this.paintTab = function (g, pageIndex){
                var b       = this.getTabBounds(pageIndex),
                    page    = this.kids[pageIndex],
                    tab     = this.views.outTab,
                    tabover = this.views.overTab,
                    tabon   = this.views.selectedTab,
                    v       = this.pages[pageIndex * 2],
                    ps      = v.getPreferredSize();

                if (this.selectedIndex === pageIndex && tabon) {
                    tabon.paint(g, b.x, b.y, b.width, b.height, page);
                } else if (tab) {
                    tab.paint(g, b.x, b.y, b.width, b.height, page);
                }

                if (this.overTab >= 0 && this.overTab === pageIndex && tabover) {
                    tabover.paint(g, b.x, b.y, b.width, b.height, page);
                }

                v.paint(g, b.x + Math.floor((b.width  - ps.width ) / 2),
                           b.y + Math.floor((b.height - ps.height) / 2),
                           ps.width, ps.height, page);
            };

            /**
             * Get the given tab page title rectangular bounds
             * @param  {Integer} i a tab page index
             * @return {Object} a tab page rectangular bounds
             *
             *    {x:{Integer}, y:{Integer}, width:{Integer}, height:{Integer}}
             *
             * @protected
             * @method getTabBounds
             */
            this.getTabBounds = function(i){
                return this.pages[2 * i + 1];
            };

            this.calcPreferredSize = function(target){
                var max = zebkit.layout.getMaxPreferredSize(target);
                if (this.orient === "bottom" || this.orient === "top"){
                    max.width = Math.max(max.width, 2 * this.sideSpace + this.tabAreaWidth);
                    max.height += this.tabAreaHeight + this.sideSpace;
                } else {
                    max.width += this.tabAreaWidth + this.sideSpace;
                    max.height = Math.max(max.height, 2 * this.sideSpace + this.tabAreaHeight);
                }
                return max;
            };

            this.doLayout = function(target) {
                var right  = this.orient === "right"  ? this.right  : this.getRight(),
                    top    = this.orient === "top"    ? this.top    : this.getTop(),
                    bottom = this.orient === "bottom" ? this.bottom : this.getBottom(),
                    left   = this.orient === "left"   ? this.left   : this.getLeft(),
                    b      = (this.orient === "top" || this.orient === "bottom");

                if (b) {
                    this.repaintX = this.tabAreaX = left ;
                    this.repaintY = this.tabAreaY = (this.orient === "top") ? top
                                                                            : this.height - bottom - this.tabAreaHeight;
                    if (this.orient === "bottom") {
                        this.repaintY -= (this.border !== null ? this.border.getBottom() : 0);
                    }
                } else {
                    this.repaintX = this.tabAreaX = (this.orient === "left" ? left
                                                                            : this.width - right - this.tabAreaWidth);
                    this.repaintY = this.tabAreaY = top ;
                    if (this.orient === "right") {
                        this.repaintX -= (this.border !== null ? this.border.getRight() : 0);
                    }
                }

                var count = this.kids.length,
                    sp    = 2 * this.sideSpace,
                    xx    = (this.orient === "right"  ? this.tabAreaX : this.tabAreaX + this.sideSpace),
                    yy    = (this.orient === "bottom" ? this.tabAreaY : this.tabAreaY + this.sideSpace),
                    r     = null,
                    i     = 0;

                for(i = 0; i < count; i++ ){
                    r = this.getTabBounds(i);

                    r.x = xx;
                    r.y = yy;

                    if (b) {
                        xx += r.width;
                        if (i === this.selectedIndex) {
                            xx -= sp;
                            if (this.orient === "bottom") {
                                r.y -= (this.border !== null ? this.border.getBottom() : 0);
                            }
                        }
                    } else {
                        yy += r.height;
                        if (i === this.selectedIndex) {
                            yy -= sp;
                            if (this.orient === "right") {
                                r.x -= (this.border !== null ? this.border.getRight() : 0);
                            }
                        }
                    }
                }

                // make visible tab title
                if (this.selectedIndex >= 0){
                    var dt = 0;

                    r = this.getTabBounds(this.selectedIndex);
                    if (b) {
                        r.x -= this.sideSpace;
                        r.y -= ((this.orient === "top") ? this.sideSpace : 0);
                        dt = (r.x < left) ? left - r.x
                                          : (r.x + r.width > this.width - right) ? this.width - right - r.x - r.width : 0;
                    } else {
                        r.x -= (this.orient === "left") ? this.sideSpace : 0;
                        r.y -= this.sideSpace;
                        dt = (r.y < top) ? top - r.y
                                         : (r.y + r.height > this.height - bottom) ? this.height - bottom - r.y - r.height : 0;
                    }

                    for(i = 0;i < count; i ++ ){
                        var br = this.getTabBounds(i);
                        if (b) {
                            br.x += dt;
                        } else {
                            br.y += dt;
                        }
                    }
                }

                for(i = 0;i < count; i++){
                    var l = this.kids[i];
                    if (i === this.selectedIndex) {
                        // TODO: temporary
                        l.setVisible(true);
                        if (b) {
                            l.setBounds(left + this.hgap,
                                        ((this.orient === "top") ? top + this.repaintHeight : top) + this.vgap,
                                        this.width - left - right - 2 * this.hgap,
                                        this.height - this.repaintHeight - top - bottom - 2 * this.vgap);
                        } else {
                            l.setBounds(((this.orient === "left") ? left + this.repaintWidth : left) + this.hgap,
                                        top + this.vgap,
                                        this.width - this.repaintWidth - left - right - 2 * this.hgap,
                                        this.height - top - bottom - 2 * this.vgap);
                        }
                    } else {
                        // TODO: bring back
                        //l.setSize(0, 0);

                        // TODO: temporary
                        l.setVisible(false);
                    }
                }
            };

            /**
             * Define recalc method to compute the component metrical characteristics
             * @method recalc
             */
            this.recalc = function(){
                var count = Math.floor(this.pages.length / 2);
                if (count > 0) {
                    this.tabAreaHeight = this.tabAreaWidth = 0;

                    var bv   = this.views.outTab ? this.views.outTab : null,
                        b    = (this.orient === "left" || this.orient === "right"),
                        max  = 0,
                        i    = 0,
                        r    = null,
                        hadd = bv === null ? 0 : bv.getLeft() + bv.getRight(),
                        vadd = bv === null ? 0 : bv.getTop()  + bv.getBottom();

                    for(i = 0; i < count; i++){
                        var ps =  this.pages[i * 2] != null ? this.pages[i * 2].getPreferredSize()
                                                            : { width:0, height:0};

                        r = this.getTabBounds(i);
                        if (b) {
                            r.height = ps.height + vadd;
                            if (ps.width + hadd > max) {
                                max = ps.width + hadd;
                            }
                            this.tabAreaHeight += r.height;
                        } else {
                            r.width = ps.width + hadd;
                            if (ps.height + vadd > max) {
                                max = ps.height + vadd;
                            }
                            this.tabAreaWidth += r.width;
                        }
                    }

                    // align tabs widths or heights to have the same size
                    for(i = 0; i < count; i++ ){
                        r = this.getTabBounds(i);
                        if (b) {
                            r.width  = max;
                        } else {
                            r.height = max;
                        }
                    }

                    if (b) {
                        this.tabAreaWidth   = max + this.sideSpace;
                        this.tabAreaHeight += (2 * this.sideSpace);
                        this.repaintHeight  = this.tabAreaHeight;
                        this.repaintWidth   = this.tabAreaWidth + (this.border !== null ? (this.orient === "left" ? this.border.getLeft()
                                                                                                                  : this.border.getRight())
                                                                                        : 0);
                    } else {
                        this.tabAreaWidth += (2 * this.sideSpace);
                        this.tabAreaHeight = this.sideSpace + max;
                        this.repaintWidth  = this.tabAreaWidth;
                        this.repaintHeight = this.tabAreaHeight + (this.border !== null ? (this.orient === "top" ? this.border.getTop()
                                                                                                                 : this.border.getBottom())
                                                                                        : 0);
                    }

                    // make selected tab page title bigger
                    if (this.selectedIndex >= 0) {
                        r = this.getTabBounds(this.selectedIndex);
                        if (b) {
                            r.height += 2 * this.sideSpace;
                            r.width += this.sideSpace +  (this.border !== null ? (this.orient === "left" ? this.border.getLeft()
                                                                                                         : this.border.getRight())
                                                                               : 0);
                        } else {
                            r.height += this.sideSpace + (this.border !== null ? (this.orient === "top" ? this.border.getTop()
                                                                                                        : this.border.getBottom())
                                                                               : 0);
                            r.width += 2 * this.sideSpace;
                        }
                    }
                }
            };

            /**
             * Get tab index located at the given location
             * @param  {Integer} x a x coordinate
             * @param  {Integer} y a y coordinate
             * @return {Integer} an index of the tab that is
             * detected at the given location. -1 if no any
             * tab can be found
             * @method getTabAt
             */
            this.getTabAt = function(x,y){
                this.validate();
                if (x >= this.tabAreaX && y >= this.tabAreaY &&
                    x < this.tabAreaX + this.tabAreaWidth    &&
                    y < this.tabAreaY + this.tabAreaHeight     )
                {
                    var tb = null;

                    // handle selected as a special case since it can overlap neighborhood titles
                    if (this.selectedIndex >= 0) {
                        tb = this.getTabBounds(this.selectedIndex);
                        if (x >= tb.x && y >= tb.y && x < tb.x + tb.width && y < tb.y + tb.height) {
                            return this.selectedIndex;
                        }
                    }

                    for(var i = 0; i < Math.floor(this.pages.length / 2); i++) {
                        if (this.selectedIndex !== i) {
                            tb = this.getTabBounds(i);
                            if (x >= tb.x && y >= tb.y && x < tb.x + tb.width && y < tb.y + tb.height) {
                                return i;
                            }
                        }
                    }
                }
                return -1;
            };

            /**
             * Define key pressed event handler
             * @param  {zebkit.ui.event.KeyEvent} e a key event
             * @method keyPressed
             */
            this.keyPressed = function(e){
                if (this.selectedIndex !== -1 && this.pages.length > 0){
                    var nxt = 0;
                    switch(e.code) {
                        case "ArrowUp":
                        case "ArrowLeft":
                            nxt = this.next(this.selectedIndex - 1,  -1);
                            if (nxt >= 0) {
                                this.select(nxt);
                            }
                            break;
                        case "ArrowDown":
                        case "ArrowRight":
                            nxt = this.next(this.selectedIndex + 1, 1);
                            if (nxt >= 0) {
                                this.select(nxt);
                            }
                            break;
                    }
                }
            };

            /**
             * Define pointer clicked  event handler
             * @param  {zebkit.ui.event.PointerEvent} e a key event
             * @method pointerClicked
             */
            this.pointerClicked = function(e){
                if (e.isAction()){
                    var index = this.getTabAt(e.x, e.y);
                    if (index >= 0 && this.isTabEnabled(index)) {
                        this.select(index);
                    }
                }
            };

            /**
             * Switch to the given tab page
             * @param  {Integer} index a tab page index to be navigated
             * @method select
             * @chainable
             */
            this.select = function(index){
                if (this.selectedIndex !== index){
                    var prev = this.selectedIndex;
                    this.selectedIndex = index;

                    if (prev >= 0) {
                        this.pages[prev * 2].selected(this, prev, false);
                    }

                    if (index >= 0) {
                        this.pages[index * 2].selected(this, index, true);
                    }

                    this.fire("selected", [this, this.selectedIndex]);
                    this.vrp();
                }

                return this;
            };

            /**
             * Get the given tab. Using the tab you can control tab caption,
             * icon.
             * @param {Integer} pageIndex a tab page index
             * @return  {zebkit.ui.Tabs.TabView}
             * @method getTab
             */
            this.getTab = function(pageIndex){
                return this.pages[pageIndex * 2];
            };

            /**
             * Set tab side spaces.
             * @param {Integer} sideSpace  [description]
             * @method setSideSpace
             * @chainable
             */
            this.setSideSpace = function(sideSpace){
                if (sideSpace !== this.sideSpace) {
                    this.sideSpace = sideSpace;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set tab page vertical and horizontal gaps
             * @param {Integer} vg a vertical gaps
             * @param {Integer} hg a horizontal gaps
             * @method setPageGaps
             * @chainable
             */
            this.setPageGaps = function (vg, hg){
                if (this.vgap !== vg || hg !== this.hgap){
                    this.vgap = vg;
                    this.hgap = hg;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set the tab page element alignments
             * @param {String} o an alignment. The valid value is one of the following:
             * "left", "right", "top", "bottom"
             * @method  setAlignment
             * @chainable
             */
            this.setAlignment = function(o){
                if (this.orient !== o) {
                    this.orient = zebkit.util.validateValue(o, "top", "bottom", "left", "right");
                    this.vrp();
                }
                return this;
            };

            /**
             * Set enabled state for the given tab page
             * @param  {Integer} i a tab page index
             * @param  {Boolean} b a tab page enabled state
             * @method enableTab
             * @chainable
             */
            this.enableTab = function(i,b){
                var c = this.kids[i];
                if (c.isEnabled !== b){
                    c.setEnabled(b);
                    if (b === false && this.selectedIndex === i) {
                        this.select(-1);
                    }
                    this.repaint();
                }
                return this;
            };

            /**
             *  Set number of views to render different Tab component elements
             *  @param {Object} a set of views as dictionary where key is a view
             *  name and the value is a view instance, string(for color), or render
             *  function. The following view elements can be passed:
             *
             *
             *      {
             *         "out"       : <view to render not selected tab page>,
             *         "over"      : <view to render a tab page when pointer is over>
             *         "selected"  : <a view to render selected tab page>
             *         "marker"    : <a marker view to be rendered around tab page title>
             *      }
             *
             *
             *  @method  setViews
             */
        },

        function focused(){
            this.$super();
            if (this.selectedIndex >= 0){
                var r = this.getTabBounds(this.selectedIndex);
                this.repaint(r.x, r.y, r.width, r.height);
            } else if (this.hasFocus() === false) {
                this.select(this.next(0, 1));
            }
        },

        function kidAdded(index,constr,c) {
            // correct wrong selection if inserted tab index is less or equals
            if (this.selectedIndex >= 0 && index <= this.selectedIndex) {
                this.selectedIndex++;
            }

            if (this.selectedIndex < 0) {
                this.select(this.next(0, 1));
            }

            return this.$super(index, constr, c);
        },

        function insert(index, constr, c) {
            var render = null;
            if (zebkit.instanceOf(constr, this.clazz.TabView)) {
                render = constr;
            } else {
                render = new this.clazz.TabView((constr === null ? "Page " + index
                                                                 : constr ));
                render.ownerChanged(this); // TODO: a little bit ugly but setting an owner is required to
                                           // keep tabs component informed when an icon has been updated
            }

            this.pages.splice(index * 2, 0, render, { x:0, y:0, width:0, height:0 });

            var r = this.$super(index, constr, c);
            // since we have added new page the repainting area is wider than
            // the added component (tab elements), so repaint the whole tab
            // component
            this.repaint();
            return r;
        },

        function removeAt(i){
            if (this.selectedIndex >= 0 && i <= this.selectedIndex) {
                if (i === this.selectedIndex) {
                    this.select(-1);
                } else {
                    this.selectedIndex--;
                    this.repaint();
                }
            }
            this.pages.splice(i * 2, 2);
            return this.$super(i);
        },

        function removeAll(){
            this.select(-1);
            this.pages.splice(0, this.pages.length);
            this.pages.length = 0;
            this.$super();
        },

        function setSize(w,h){
            if (this.width !== w || this.height !== h) {
                if (this.orient === "right" || this.orient === "bottom") {
                    this.tabAreaX = -1;
                }
                this.$super(w, h);
            }
            return this;
        }
    ]).events("selected");


    pkg.events.regEvents("menuItemSelected");

    /**
     * Menu event class
     * @constructor
     * @class zebkit.ui.event.MenuEvent
     * @extends zebkit.Event
     */
    pkg.event.MenuEvent = Class(zebkit.Event, [
        function $prototype() {
            /**
             * Index of selected menu item
             * @type {Integer}
             * @attribute index
             * @readOnly
             */
            this.index = -1;

            /**
             * Selected menu item component
             * @type {zebkit.ui.Panel}
             * @attribute item
             * @readOnly
             */
            this.item = null;

            /**
             * Fill menu event with specified parameters
             * @param  {zebkit.ui.Menu} src a source of the menu event
             * @param  {Integer} index an index of selected menu item
             * @param  {zebkit.ui.Panel} item a selected menu item
             * @protected
             * @chainable
             * @method $fillWith
             */
            this.$fillWith = function(src, index, item) {
                this.source = src;
                this.index  = index;
                this.item   = item;
                return this;
            };
        }
    ]);

    var MENU_EVENT = new pkg.event.MenuEvent();

    /**
     * Show the given popup menu.
     * @param  {zebkit.ui.Panel} context  an UI component of zebkit hierarchy
     * @param  {zebkit.ui.Menu}  menu a menu to be shown
     * @for  zebkit.ui
     * @method showPopupMenu
     */
    pkg.showPopupMenu = function(context, menu) {
        context.getCanvas().getLayer(pkg.PopupLayerMix.id).add(menu);
    };

    /**
     * Menu item panel class. The component holds menu item content like caption, icon, sub-menu
     * sign elements. The area of the component is split into three parts: left, right and center.
     * Central part keeps content, left side keeps checked sign element and the right side keeps
     * sub-menu sign element.
     * @param  {String|zebkit.ui.Panel} content a menu item content string or component. Caption
     * string can encode the item id, item icon and item checked state. For instance:
     *
     *         {
     *             content:  "Test" | {zebkit.ui.Panel}
     *             checked:  {Boolean},                   // optional
     *             group  :  {zebkit.ui.Group},           // optional
     *             icon   :  "path/to/image" | {Image},   // optional
     *             handler:  {Function}                   // optional
     *             id     :  {String}                     // optional
     *         }
     *
     * @example
     *
     *
     *     // create menu item with icon and "Item 1" title
     *     var mi = new zebkit.ui.MenuItem({
     *         content: "Menu item label"
     *     });
     *
     *
     * @class zebkit.ui.MenuItem
     * @extends zebkit.ui.Panel
     * @constructor
     *
     */
    pkg.MenuItem = Class(pkg.Panel, [
        function(c) {
            this.$super();

            if (zebkit.isString(c)) {
                this.add(new this.clazz.Checkbox()).setVisible(false);
                c = new this.clazz.ImageLabel(c, null);
            } else if (zebkit.instanceOf(c, pkg.Panel) === false) {
                var ch = null;

                if (c.checked === true || c.checked === false || c.group !== undefined) {
                    ch = (c.group !== undefined) ? new this.clazz.Radiobox()
                                                 : new this.clazz.Checkbox();

                    if (c.group !== undefined) {
                        ch.setGroup(c.group);
                    }

                    ch.setValue(c.checked === undefined ? false : c.checked);
                } else {
                    ch = new this.clazz.Checkbox();
                    ch.setVisible(false);
                }

                this.add(ch);

                if (c.id !== undefined) {
                    this.setId(c.id);
                }

                if (c.handler !== undefined) {
                    this.$handler = c.handler;
                }

                if (zebkit.instanceOf(c.content, zebkit.ui.Panel)) {
                    c = c.content;
                } else if (c.icon !== undefined) {
                    c = new this.clazz.ImageLabel(c.content, c.icon);
                } else {
                    c = new this.clazz.ImageLabel(c.content, null);
                }
            } else {
                this.add(new this.clazz.Checkbox()).setVisible(false);
            }

            this.add(c);
            this.add(new this.clazz.SubImage());

            this.setEnabled(c.isEnabled);
            this.setVisible(c.isVisible);
        },

        function $clazz() {
            this.SubImage  = Class(pkg.StatePan, []);
            this.Label     = Class(pkg.Label,    []);
            this.Checkbox  = Class(pkg.Checkbox, []);
            this.Radiobox  = Class(pkg.Radiobox, []);
            this.ImageLabel  = Class(pkg.ImageLabel, []);
        },

        function $prototype() {
            this.$handler = null;

            /**
             * Gap between checked, content and sub menu arrow components
             * @attribute gap
             * @type {Integer}
             * @readOnly
             * @default 8
             */
            this.gap = 8;

            /**
             * Callback method that is called every time the menu item has
             * been selected.
             * @method  itemSelected
             */
            this.itemSelected = function() {
                var content = this.getContent();
                if (zebkit.instanceOf(content, pkg.Checkbox)) {
                    content.setValue(!content.getValue());
                }

                if (this.getCheck().isVisible) {
                    this.getCheck().toggle();
                }

                if (this.$handler !== null) {
                    this.$handler.call(this);
                }
            };

            /**
             * Set the menu item icon.
             * @param {String|Image} img a path to an image or image object
             * @method setIcon
             * @chainable
             */
            this.setIcon = function(img) {
                this.getContent().setImage(img);
                return this;
            };

            /**
             * Get check state component
             * @return {zebkit.ui.Panel} a check state component
             * @method getCheck
             */
            this.getCheck = function() {
                return this.kids[0];
            };

            /**
             * Get checked state of the item
             * @return {Boolean} a checked state
             * @method isChecked
             */
            this.isChecked = function() {
                return this.getCheck().isVisible && this.getCheck().getValue();
            };

            /**
             * Set group
             * @param {zebkit.ui.Group} g a group
             * @param {Boolean} [v] a value
             * @method setGroup
             */
            this.setGroup = function(g, v) {
                this.getCheck().setGroup(g);
                this.getCheck().setVisible(true);
                if (arguments.length > 1) {
                    this.getCheck().setValue(v);
                }
            };

            /**
             * Get content component
             * @return {zebkit.ui.Panel} a content component
             * @method getContent
             */
            this.getContent = function() {
                return this.kids.length > 0 ? this.kids[1] : null;
            };

            /**
             * Get menu item child component to render sub item arrow element
             * @return {zebkit.ui.Panel} a sub item arrow component
             * @method getSub
             * @protected
             */
            this.getSub = function() {
                return this.kids.length > 1 ? this.kids[2] : null;
            };

            this.activateSub = function(b) {
                var kid = this.getSub();
                kid.setState(b ? "pressed" : "*");
                if (this.parent !== null && this.parent.noSubIfEmpty === true) {
                    kid.setVisible(b);
                }
            };

            this.$getCheckSize = function() {
                var ch = this.getCheck();
                return ch === null ? { width: 0, height: 0 } : ch.getPreferredSize() ;
            };

            this.$getContentSize = function() {
                var content = this.getContent();
                return (content !== null && content.isVisible === true) ? content.getPreferredSize()
                                                                        : { width : 0, height : 0 };
            };

            this.$getSubSize = function() {
                var sub = this.getSub();
                return (sub !== null && sub.isVisible === true) ? sub.getPreferredSize()
                                                                : { width : 0, height : 0 };
            };

            this.calcPreferredSize = function (target){
                var p1 = this.$getCheckSize(),
                    p2 = this.$getContentSize(),
                    p3 = this.$getSubSize(),
                    h  = Math.max(p1.height, p2.height, p3.height),
                    w  = p1.width + p2.width + p3.width,
                    i  = -1;

                if (p1.width > 0) {
                    i++;
                }

                if (p2.width > 0) {
                    i++;
                }

                if (p3.width > 0) {
                    i++;
                }

                return { width: w + (i > 0 ? this.gap * i : 0), height: h };
            };

            this.doLayout = function(target) {
                var left    = this.getCheck(),
                    right   = this.getSub(),
                    content = this.getContent(),
                    p1      = this.$getCheckSize(),
                    p3      = this.$getSubSize(),
                    t       = target.getTop(),
                    l       = target.getLeft(),
                    eh      = target.height - t - target.getBottom(),
                    ew      = target.width  - l - target.getRight();

                if (left !== null && left.isVisible === true) {
                    left.toPreferredSize();
                    left.setLocation(l, t + Math.floor((eh - left.height)/2));
                }

                var add = (p1.width > 0 ? this.gap : 0) + p1.width;
                l  += add;
                ew -= add;

                if (right !== null && right.isVisible === true) {
                    right.toPreferredSize();
                    right.setLocation(target.width - target.getRight() - right.width,
                                      t + Math.floor((eh - right.height)/2));
                }
                ew -= ((p3.width > 0 ? this.gap : 0) + p3.width);

                if (content !== null && content.isVisible === true) {
                    content.toPreferredSize();
                    if (content.width > ew) {
                        content.setSize(ew, content.height);
                    }
                    content.setLocation(l, t + Math.floor((eh - content.height)/2));
                }
            };
        },

        /**
         * Override setParent method to catch the moment when the
         * item is inserted to a menu
         * @param {zebkit.ui.Panel} p a parent
         * @method setParent
         */
        function setParent(p) {
            this.$super(p);
            if (p !== null && p.noSubIfEmpty === true) {
                this.getSub().setVisible(false);
            }
        }
    ]).hashable();

    /**
     * Menu UI component class. The class implements popup menu UI component.
     *
     *     var m = new Menu([
     *         {
     *             content: "Menu Item 1",
     *             sub    : [
     *                 {
     *                     content: "SubMenu Checked Item 1",
     *                     checked: true
     *                 },
     *                 {
     *                     content: "SubMenu Checked Item 2",
     *                     checked: false
     *                 },
     *                 "-", // line
     *                 {
     *                     content: "SubMenu Checked Item 3",
     *                     checked: false
     *                 }
     *             ]
     *          },
     *         "Menu Item 2",
     *         "Menu Item 3"
     *      ]);
     *
     * @class zebkit.ui.Menu
     * @constructor
     * @param {Object} [list] menu items description
     * @extends zebkit.ui.CompList
     */
    pkg.Menu = Class(pkg.CompList, [
        function (d) {
            this.menus = {};

            this.$super([], zebkit.isBoolean(d) ? d : true);

            if (arguments.length > 0) {
                for(var i = 0; i < d.length; i++) {
                    var item = d[i];
                    this.add(item);
                    if (zebkit.isString(item) === false && item.sub !== undefined) {
                        var sub = item.sub;
                        this.setMenuAt(this.kids.length - 1, zebkit.instanceOf(sub, pkg.Menu) ? sub
                                                                                              : new pkg.Menu(sub));
                    }
                }
            }
        },

        function $clazz() {
            this.MenuItem = Class(pkg.MenuItem, [
                function $clazz() {
                    this.Label = Class(pkg.MenuItem.Label, []);
                }
            ]);

            this.Line = Class(pkg.Line, []);
            this.Line.prototype.$isDecorative = true;
        },

        function $prototype() {
            this.$parentMenu = null;

            this.canHaveFocus = true;
            this.noSubIfEmpty = false;

            /**
             * Test if the given menu item is a decorative (not selectable) menu item.
             * Menu item is considered as decorative if it has been added with addDecorative(...)
             * method or has "$isDecorative" property set to "true"
             * @param  {Integer}  i a menu item index
             * @return {Boolean}  true if the given menu item is decorative
             * @method isDecorative
             */
            this.isDecorative = function(i){
                return this.kids[i].$isDecorative === true || this.kids[i].$$isDecorative === true;
            };

            /**
             * Define component events handler.
             * @param  {zebkit.ui.event.CompEvent} e  a component event
             * @method  childCompEnabled
             */
            this.childCompEnabled = this.childCompShown = function(e) {
                var src = e.source;
                for(var i = 0;i < this.kids.length; i++){
                    if (this.kids[i] === src) {
                        // clear selection if an item becomes not selectable
                        if (this.isItemSelectable(i) === false && (i === this.selectedIndex)) {
                            this.select(-1);
                        }
                        break;
                    }
                }
            };

            /**
             * Get a menu item by the given index
             * @param  {Integer} i a menu item index
             * @return {zebkit.ui.Panel} a menu item component
             * @method getMenuItem
             */
            this.getMenuItem = function(i) {
                if (zebkit.isString(i) === true) {
                    var item = this.byPath(i);
                    if (item !== null) {
                        return item;
                    }

                    for (var k in this.menus) {
                        item = this.menus[k].getMenuItem(i);
                        if (item !== null) {
                            return item;
                        }
                    }
                }
                return this.kids[i];
            };

            /**
             * Test if the menu has a selectable item
             * @return {Boolean} true if the menu has at least one selectable item
             * @method hasSelectableItems
             */
            this.hasSelectableItems = function(){
                for(var i = 0; i < this.kids.length; i++) {
                    if (this.isItemSelectable(i)) {
                        return true;
                    }
                }
                return false;
            };

            /**
             * Define pointer exited events handler
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerExited
             */
            this.pointerExited = function(e){
                this.position.setOffset(null);
            };

            /**
             * Get a sub menu for the given menu item
             * @param  {Integer} index a menu item index
             * @return {zebkit.ui.Menu} a sub menu or null if no sub menu
             * is defined for the given menu item
             * @method getMenuAt
             */
            this.getMenuAt = function(index) {
                if (index < this.kids.length) {
                    var hash = this.kids[index].$hash$;
                    return this.menus.hasOwnProperty(hash) ? this.menus[hash] : null;
                } else {
                    return null;
                }
            };

            // TODO: not stable API
            this.menuById = function(id) {
                if (this.id === id) {
                    return this;
                } else {
                    for (var i = 0; i < this.kids.length; i++)  {
                        var m = this.getMenuAt(i);
                        if (m !== null) {
                            var res = m.menuById(id);
                            if (res !== null) {
                                return res;
                            }
                        }
                    }
                    return null;
                }
            };

            // TODO: not stable API
            this.menuItemById = function(id) {
                for (var i = 0; i < this.kids.length; i++)  {
                    var mi = this.kids[i];
                    if (mi !== null && mi.id === id) {
                        return mi;
                    } else {
                        var m = this.getMenuAt(i);
                        if (m !== null) {
                            var res = m.menuItemById(id);
                            if (res !== null) {
                                return res;
                            }
                        }
                    }
                }
                return null;
            };

            /**
             * Set the given menu as a sub-menu for the specified menu item
             * @param {Integer} i an index of a menu item for that a sub menu
             * has to be attached
             * @param {zebkit.ui.Menu} m a sub menu to be attached
             * @method setMenuAt
             * @chainable
             */
            this.setMenuAt = function (i, m) {
                if (m === this) {
                    throw new Error("Menu cannot be sub-menu of its own");
                }

                if (this.isDecorative(i)) {
                    throw new Error("Decorative element cannot have a sub-menu");
                }

                var p = this.kids[i];
                if (p.activateSub !== undefined) {
                    var sub = this.menus.hasOwnProperty(p) ? this.menus[p] : null;
                    if (m !== null) {
                        if (sub === null) {
                            p.activateSub(true);
                        }
                    } else if (sub !== null) {
                        p.activateSub(false);
                    }
                }

                // if the menu is shown and the menu item is selected
                if (this.parent !== null && i === this.selectedIndex) {
                    this.select(-1);
                }

                if (p.$hash$ === undefined) {
                    throw new Error("Invalid key");
                }

                if (m === null) {
                    delete this.menus[p];
                } else {
                    this.menus[p] = m;
                }

                return this;
            };

            /**
             * Get the specified sub-menu index
             * @param  {zebkit.ui.Menu} menu a sub menu
             * @return {Integer} a sub menu index. -1 if the menu is
             * not a sub menu of the given menu
             * @method indexMenuOf
             */
            this.indexMenuOf = function(menu) {
                for(var i = 0; i < this.kids.length; i++) {
                    if (this.menus[this.kids[i]] === menu) {
                        return i;
                    }
                }
                return -1;
            };

            /**
             * Called when the menu or a sub-menu has been canceled (key ESCAPE has been pressed).
             * @param  {zebkit.ui.Menu} m a menu (or sub menu) that has been canceled
             * @method $canceled
             * @protected
             */
            this.$canceled = function(m) {
                if (this.$parentMenu !== null && this.$canceled !== undefined) {
                    this.$parentMenu.$canceled(m);
                }
            };

            /**
             * Get the top menu in the given shown popup menu hierarchy
             * @return {zebkit.ui.Menu} a top menu
             * @method $topMenu
             * @protected
             */
            this.$topMenu = function() {
                if (this.parent !== null) {
                    var t = this,
                        p = null;

                    while ((p = t.$parentMenu) !== null) {
                        t = p;
                    }
                    return t;
                }
                return null;
            };

            this.doScroll = function(dx, dy, source) {
                var sy = this.scrollManager.getSY(),
                    ps = this.layout.calcPreferredSize(this),
                    eh = this.height - this.getTop() - this.getBottom();

                if (this.height < ps.height && sy + ps.height >= eh && sy - dy <= 0) {
                    var nsy = sy - dy;
                    if (nsy + ps.height < eh) {
                        nsy = eh - ps.height;
                    }
                    if (sy !== nsy) {
                        this.scrollManager.scrollYTo(nsy);
                    }
                }
            };

            /**
             * Hide the menu and all visible sub-menus
             * @method $hideMenu
             * @protected
             */
            this.$hideMenu = function() {
                if (this.parent !== null) {
                    var ch = this.$childMenu();
                    if (ch !== null) {
                        ch.$hideMenu();
                    }

                    this.removeMe();
                    this.select(-1);
                }
            };

            /**
             * Get a sub menu that is shown at the given moment.
             * @return {zebkit.ui.Menu} a child sub menu. null if no child sub-menu
             * has been shown
             * @method $childMenu
             * @protected
             */
            this.$childMenu = function() {
                if (this.parent !== null) {
                    for(var k in this.menus) {
                        var m = this.menus[k];
                        if (m.$parentMenu === this) {
                            return m;
                        }
                    }
                }
                return null;
            };

            /**
             * Show the given sub menu
             * @param  {zebkit.ui.Menu} sub a sub menu to be shown
             * @method $showSubMenu
             * @protected
             */
            this.$showSubMenu = function(sub) {
                sub.setLocation(this.x + this.width - 10,
                                this.y + this.kids[this.selectedIndex].y);
                sub.toPreferredSize();
                this.parent.add(sub);
                sub.requestFocus();
            };

            this.triggerSelectionByPos = function(i) {
                return this.getMenuAt(i) !== null && this.$triggeredByPointer === true;
            };
        },

        /**
         * Override key pressed events handler to handle key events according to
         * context menu component requirements
         * @param  {zebkit.ui.event.KeyEvent} e a key event
         * @method keyPressed
         */
        function keyPressed(e){
            if (e.code === "Escape") {
                if (this.parent !== null) {
                    var p = this.$parentMenu;
                    this.$canceled(this);
                    this.$hideMenu();
                    if (p !== null) {
                        p.requestFocus();
                    }
                }
            } else {
                this.$super(e);
            }
        },

        function insert(i, ctr, c) {
            if (zebkit.isString(c)) {
                return this.$super(i, ctr, (c.match(/^\-+$/) !== null) ? new this.clazz.Line()
                                                                       : new this.clazz.MenuItem(c));
            } else if (zebkit.instanceOf(c, pkg.Panel)) {
                return this.$super(i, ctr, c);
            } else {
                return this.$super(i, ctr, new this.clazz.MenuItem(c));
            }
        },

        function setParent(p) {
            if (p !== null) {
                this.select(-1);
                this.position.setOffset(null);
            } else {
                this.$parentMenu = null;
            }
            this.$super(p);
        },

        /**
         * Add the specified component as a decorative item of the menu
         * @param {zebkit.ui.Panel} c an UI component
         * @method addDecorative
         */
        function addDecorative(c) {
            if (c.$isDecorative !== true) {
                c.$$isDecorative = true;
            }
            this.$getSuper("insert").call(this, this.kids.length, null, c);
        },

        function kidRemoved(i, c, ctr) {
            if (c.$$isDecorative !== undefined) {
                delete c.$$isDecorative;
            }
            this.setMenuAt(i, null);
            this.$super(i, c, ctr);
        },

        function isItemSelectable(i) {
            return this.$super(i) && this.isDecorative(i) === false;
        },

        function posChanged(target,prevOffset,prevLine,prevCol) {
            var off = target.offset;

            if (off >= 0) {
                var rs = null;

                // hide previously shown sub menu if position has been re-newed
                if (this.selectedIndex >= 0  && off !== this.selectedIndex) {
                    var sub = this.getMenuAt(this.selectedIndex);
                    if (sub !== null) {
                        sub.$hideMenu();
                        rs = -1; // request to clear selection
                        this.requestFocus();
                    }
                }

                // request fire selection if the menu is shown and position has moved to new place
                if (this.parent !== null && off !== this.selectedIndex && this.isItemSelectable(off)) {
                    if (this.triggerSelectionByPos(off)) {
                        rs = off;
                    }
                }

                if (rs !== null) {
                    this.select(rs);
                }
            }

            this.$super(target, prevOffset, prevLine, prevCol);
        },

        function fireSelected(prev) {
            if (this.parent !== null) {
                var sub = null;

                if (this.selectedIndex >= 0) {
                    sub = this.getMenuAt(this.selectedIndex);
                    if (sub !== null) { // handle sub menu here
                        if (sub.parent !== null) {
                            // hide menu since it has been already shown
                            sub.$hideMenu();
                        } else {
                            // show menu
                            sub.$parentMenu = this;
                            this.$showSubMenu(sub);
                        }
                    } else {
                        // handle an item menu selection here.
                        // hide the whole menu hierarchy
                        var k = this.kids[this.selectedIndex];
                        if (k.itemSelected !== undefined) {
                            k.itemSelected();
                        }

                        pkg.events.fire("menuItemSelected",
                                        MENU_EVENT.$fillWith(this,
                                                             this.selectedIndex,
                                                             this.kids[this.selectedIndex]));


                        // an atomic menu, what means a menu item has been selected
                        // remove this menu an all parents menus
                        var top = this.$topMenu();
                        if (top !== null) {
                            top.$hideMenu();
                        }
                    }

                } else if (prev >= 0) {
                    // hide child menus if null item has been selected
                    sub = this.getMenuAt(prev);
                    if (sub !== null && sub.parent !== null) {
                        // hide menu since it has been already shown
                        sub.$hideMenu();
                    }
                }
            }
            this.$super(prev);
        }
    ]);

    /**
     * Menu bar UI component class. Menu bar can be build in any part of UI application.
     * There is no restriction regarding the placement of the component.
     *
     *      var canvas = new zebkit.ui.zCanvas(300,200);
     *      canvas.setBorderLayout();
     *
     *      var mbar = new zebkit.ui.Menubar([
     *          {
     *              content: "Item 1",
     *              sub    : [
     *                  "Subitem 1.1",
     *                  "Subitem 1.2",
     *                  "Subitem 1.3"
     *              ]
     *          },
     *          {
     *              content: "Item 2",
     *              sub: [
     *                  "Subitem 2.1",
     *                  "Subitem 2.2",
     *                  "Subitem 2.3"
     *              ]
     *          },
     *          {
     *              content: "Item 3"
     *          }
     *      ]);
     *
     *      canvas.root.add("bottom", mbar);
     *
     * @class zebkit.ui.Menubar
     * @constructor
     * @extends zebkit.ui.Menu
     */
    pkg.Menubar = Class(pkg.Menu, [
        function $clazz() {
            this.MenuItem = Class(pkg.MenuItem, [
                function(c) {
                    this.$super(c);
                    this.getSub().setVisible(false);
                    this.getCheck().setVisible(false);
                },

                function $prototype() {
                    this.$getCheckSize = function() {
                        return pkg.$getPS(this.getCheck());
                    };
                },

                function $clazz() {
                    this.Label = Class(pkg.MenuItem.Label, []);
                }
            ]);
        },

        function $prototype() {
            this.canHaveFocus = false;

            this.triggerSelectionByPos = function (i) {
                return this.isItemSelectable(i) && this.selectedIndex >= 0;
            };

            // making menu bar not removable from its layer by overriding the method
            this.$hideMenu = function() {
                var child = this.$childMenu();
                if (child !== null) {
                    child.$hideMenu();
                }

                this.select(-1);
            };

            this.$showSubMenu = function(menu) {
                var d   = this.getCanvas(),
                    k   = this.kids[this.selectedIndex],
                    pop = d.getLayer(pkg.PopupLayer.id);

                if (menu.hasSelectableItems()) {
                    var abs = zebkit.layout.toParentOrigin(0, 0, k);
                    menu.setLocation(abs.x, abs.y + k.height + 1);
                    menu.toPreferredSize();
                    pop.add(menu);
                    menu.requestFocus();
                }
            };

            this.$canceled = function(m) {
                this.select(-1);
            };
        },

        // called when an item is selected by user with pointer click or key
        function $select(i) {
            // if a user again pressed the same item consider it as
            // de-selection
            if (this.selectedIndex >= 0 && this.selectedIndex === i) {
                i = -1;
            }
            this.$super(i);
        }
    ]);

    pkg.PopupLayerLayout = Class(zebkit.layout.Layout, [
        function $prototype() {
            this.calcPreferredSize = function (target){
                return { width:0, height:0 };
            };

            this.doLayout = function(target) {
                for(var i = 0; i < target.kids.length; i++){
                    var m = target.kids[i];
                    if (zebkit.instanceOf(m, pkg.Menu)) {
                        var ps = m.getPreferredSize(),
                            xx = (m.x + ps.width  > target.width ) ? target.width  - ps.width  : m.x,
                            yy = (m.y + ps.height > target.height) ? target.height - ps.height : m.y;

                        m.setSize(ps.width, ps.height);
                        if (xx < 0) {
                            xx = 0;
                        }
                        if (yy < 0) {
                            yy = 0;
                        }
                        m.setLocation(xx, yy);
                    }
                }
            };
        }
    ]);

    /**
     * UI popup layer interface that defines common part of popup layer
     * implementation.
     * @class zebkit.ui.PopupLayerMix
     * @interface zebkit.ui.PopupLayerMix
     */
    pkg.PopupLayerMix = zebkit.Interface([
        function $clazz() {
            this.id = "popup";
        },

        function $prototype() {
            this.$prevFocusOwner = null;

            this.getFocusRoot = function() {
                return this;
            };

            this.childFocusGained = function(e) {
                if (zebkit.instanceOf(e.source, pkg.Menu)) {
                    if (e.related !== null && zebkit.layout.isAncestorOf(this, e.related) === false ) {
                        this.$prevFocusOwner = e.related;
                    }
                } else {
                    // means other than menu type of component grabs the focus
                    // in this case we should not restore focus when the popup
                    // component will be removed
                    this.$prevFocusOwner = null;
                }

                // save the focus owner whose owner was not a pop up layer
                if (e.related !== null && zebkit.layout.isAncestorOf(this, e.related) === false && zebkit.instanceOf(e.source, pkg.Menu)) {
                    this.$prevFocusOwner = e.related;
                }
            };

            this.isTriggeredWith = function(e) {
                return e.isAction() === false && (e.identifier === "rmouse" || e.touchCounter === 2);
            };

            /**
             * Define children components input events handler.
             * @param  {zebkit.ui.event.KeyEvent} e an input event
             * @method childKeyPressed
             */
            this.childKeyPressed = function(e){
                var p = e.source.$parentMenu;
                if (p !== undefined && p !== null) {
                    switch (e.code) {
                        case "ArrowRight" :
                            if (p.selectedIndex < p.model.count() - 1) {
                                p.requestFocus();
                                p.position.seekLineTo("down");
                            }
                            break;
                        case "ArrowLeft" :
                            if (p.selectedIndex > 0) {
                                p.requestFocus();
                                p.position.seekLineTo("up");
                            }
                            break;
                    }
                }
            };

            this.$topMenu = function() {
                if (this.kids.length > 0) {
                    for (var i = this.kids.length - 1; i >= 0; i--) {
                        if (zebkit.instanceOf(this.kids[i], pkg.Menu)) {
                            return this.kids[i].$topMenu();
                        }
                    }
                }
                return null;
            };

            this.compRemoved = function(e) {
                // if last component has been removed and the component is a menu
                // than try to restore focus owner
                if (this.$prevFocusOwner !== null && this.kids.length === 0 && zebkit.instanceOf(e.kid, pkg.Menu)) {
                    this.$prevFocusOwner.requestFocus();
                    this.$prevFocusOwner = null;
                }
            };

            this.pointerPressed = function(e) {
                if (this.kids.length > 0) {
                    var top = this.$topMenu();
                    if (top !== null) {
                        top.$hideMenu();
                    }

                    // still have a pop up components, than remove it
                    if (this.kids.length > 0) {
                        this.removeAll();
                    }

                    return true;
                } else {
                    return false;
                }
            };

            // show popup
            this.layerPointerClicked = function (e) {
                if (this.kids.length === 0 && this.isTriggeredWith(e)) {
                    var popup = null;
                    if (e.source.popup !== undefined && e.source.popup !== null) {
                        popup = e.source.popup;
                    } else if (e.source.getPopup !== undefined) {
                        popup = e.source.getPopup(e.source, e.x, e.y);
                    }

                    if (popup !== null) {
                        popup.setLocation(e.absX, e.absY);
                        this.add(popup);
                        popup.requestFocus();
                    }

                    return true;
                } else {
                    return false;
                }
            };
        },

        function getComponentAt(x, y) {
            // if there is a component on popup layer and the component is
            // not the popup layer itself than return the component otherwise
            // return null what delegates getComponentAt() to other layer
            if (this.kids.length > 0) {
                // if pressed has happened over a popup layer no a menu
                var cc = this.$super(x, y);
                if (cc === this) {
                    var top = this.$topMenu();
                    if (top !== null) {
                        // if top menu is menu bar. menu bar is located in other layer
                        // we need check if the pressed has happened not over the
                        // menu bar
                        if (zebkit.instanceOf(top, pkg.Menubar)) {
                            var origin = zebkit.layout.toParentOrigin(top);
                            // is pointer pressed inside menu bar
                            if (x >= origin.x && y >= origin.y && x < origin.x + top.width && y < origin.y + top.height) {
                                return null;
                            }
                        }
                    }
                }
                return cc;
            } else {
                return null;
            }
        }
    ]);

    /**
     * Simple popup layer implementation basing on "zebkit.ui.Panel" component.
     * @class zebkit.ui.PopupLayer
     * @extends zebkit.ui.Panel
     * @constructor
     * @uses zebkit.ui.PopupLayerMix
     */
    pkg.PopupLayer = Class(pkg.Panel, pkg.PopupLayerMix, []);


    pkg.events.regEvents('winOpened', 'winActivated');

    /**
     * Window component event
     * @constructor
     * @class zebkit.ui.event.WinEvent
     * @extends zebkit.Event
     */
    pkg.event.WinEvent = Class(zebkit.Event, [
        function $prototype() {
            /**
             * Indicates if the window has been shown
             * @attribute isShown
             * @type {Boolean}
             * @readOnly
             */
            this.isShown = false;

            /**
             * Indicates if the window has been activated
             * @attribute isActive
             * @type {Boolean}
             * @readOnly
             */
             this.isActive = false;

            /**
             * Layer the source window belongs to
             * @type {zebkit.ui.Panel}
             * @attribute layer
             * @readOnly
             */
            this.layer = null;

            /**
             * Fill the event with parameters
             * @param  {zebkit.ui.Panel}  src  a source window
             * @param  {zebkit.ui.Panel}  layer  a layer the window belongs to
             * @param  {Boolean} isActive boolean flag that indicates the window status
             * @param  {Boolean} isShown  boolean flag that indicates the window visibility
             * @chainable
             * @method  $fillWidth
             */
            this.$fillWith = function(src, layer, isActive, isShown) {
                this.source = src;

                this.layer    = layer;
                this.isActive = isActive;
                this.isShown  = isShown;
                return this;
            };
        }
    ]);

    var WIN_EVENT = new pkg.event.WinEvent();

    /**
     * Show the given UI component as a modal window
     * @param  {zebkit.ui.Panel} context  an UI component of zebkit hierarchy
     * @param  {zebkit.ui.Panel} win a component to be shown as the modal window
     * @for  zebkit.ui
     * @method showModalWindow
     */
    pkg.showModalWindow = function(context, win) {
        pkg.showWindow(context, "modal", win);
    };

    /**
     * Show the given UI component as a window
     * @param  {zebkit.ui.Panel} context  an UI component of zebkit hierarchy
     * @param  {String} [type] a type of the window: "modal", "mdi", "info". The default
     * value is "info"
     * @param  {zebkit.ui.Panel} win a component to be shown as the window
     * @for  zebkit.ui
     * @method showWindow
     */
    pkg.showWindow = function(context, type, win) {
        if (arguments.length < 3) {
            win  = type;
            type = "info";
        }
        return context.getCanvas().getLayer("win").addWin(type, win);
    };

    /**
     * Activate the given window or a window the specified component belongs
     * @param  {zebkit.ui.Panel} win an UI component to be activated
     * @for zebkit.ui
     * @method activateWindow
     */
    pkg.activateWindow = function(win) {
        var l = win.getCanvas().getLayer("win");
        l.activate(zebkit.layout.getDirectChild(l, win));
    };

    /**
     * Window layer class. Window layer is supposed to be used for showing
     * modal and none modal internal window. There are special ready to use
     * "zebkit.ui.Window" UI component that can be shown as internal window, but
     * zebkit allows developers to show any UI component as modal or none modal
     * window. Add an UI component to window layer to show it as modal o none
     * modal window:
     *
     *       // create canvas
     *       var canvas   = new zebkit.ui.zCanvas();
     *
     *       // get windows layer
     *       var winLayer = canvas.getLayer(zebkit.ui.WinLayerMix.id);
     *
     *       // create standard UI window component
     *       var win = new zebkit.ui.Window();
     *       win.setBounds(10,10,200,200);
     *
     *       // show the created window as modal window
     *       winLayer.addWin("modal", win);
     *
     * Also shortcut method can be used
     *
     *       // create canvas
     *       var canvas   = new zebkit.ui.zCanvas();
     *
     *       // create standard UI window component
     *       var win = new zebkit.ui.Window();
     *       win.setBounds(10,10,200,200);
     *
     *       // show the created window as modal window
     *       zebkit.ui.showModalWindow(canvas, win);
     *
     * Window layer supports three types of windows:
     *
     *   - **"modal"** a modal window catches all input till it will be closed
     *   - **"mdi"** a MDI window can get focus, but it doesn't block switching
     *   focus to other UI elements
     *   - **"info"** an INFO window cannot get focus. It is supposed to show
     *   some information like tooltip.
     *
     * @class zebkit.ui.WinLayer
     * @constructor
     * @extends zebkit.ui.HtmlCanvas
     */
    pkg.WinLayerMix = zebkit.Interface([
        function $clazz() {
            this.id = "win";
        },

        function $prototype() {
            /**
             * Currently activated as a window children component
             * @attribute activeWin
             * @type {zebkit.ui.Panel}
             * @readOnly
             * @protected
             */
            this.activeWin = null;

            /**
             * Top modal window index
             * @type {Integer}
             * @attribute topModalIndex
             * @private
             * @default -1
             */
            this.topModalIndex = -1;

            this.pointerPressed = function(e) {
                if (this.topModalIndex < 0 && this.activeWin !== null) { // no a modal window has been shown
                    this.activate(null);
                }
            };

            this.layerKeyPressed = function(e) {
                if (this.kids.length > 0 &&
                    e.code === "Tab"     &&
                    e.shiftKey === true     )
                {
                    if (this.activeWin === null) {
                        this.activate(this.kids[this.kids.length - 1]);
                    } else {
                        var winIndex = this.kids.indexOf(this.activeWin) - 1;
                        if (winIndex < this.topModalIndex || winIndex < 0) {
                            winIndex = this.kids.length - 1;
                        }
                        this.activate(this.kids[winIndex]);
                    }

                    return true;
                } else {
                    return false;
                }
            };

            /**
             * Define children components input events handler.
             * @param  {zebkit.ui.event.FocusEvent} e a focus event
             * @method childFocusGained
             */
            this.childFocusGained = function (e) {
                this.activate(zebkit.layout.getDirectChild(this, e.source));
            };

            this.childPointerClicked = function (e) {
                if (this.kids.length > 0 && (this.activeWin === null || zebkit.layout.isAncestorOf(this.activeWin, e.source) === false)) {
                    // II) otherwise looking for a window starting from the topest one where the
                    // pressed event has occurred. Pay attention modal window can open MDI windows
                    for(var i = this.kids.length - 1; i >= 0 && i >= this.topModalIndex; i--) {
                        var d = this.kids[i];

                        if (d.isVisible === true  &&   // check pressed is inside of a MDI window that
                            d.isEnabled === true  &&   // is shown after currently active modal window
                            d.winType  !== "info" &&
                            zebkit.layout.isAncestorOf(d, e.source))
                        {
                            this.activate(d);
                            return true;
                        }
                    }
                }
            };

            /**
             * Get root a component to start focusing traversing
             * @return {zebkit.ui.Panel} a root component
             * @method getFocusRoot
             */
            this.getFocusRoot = function() {
                return this.activeWin;
            };

            /**
             * Activate the given win layer children component window.
             * @param  {zebkit.ui.Panel} c a component to be activated as window
             * @method activate
             */
            this.activate = function(c) {
                if (c !== null && (this.kids.indexOf(c) < 0 ||
                                   c.winType === "info"))
                {
                    throw new Error("Window cannot be activated");
                }

                if (c !== this.activeWin) {
                    var old = this.activeWin;

                    if (c === null) {
                        var type = this.activeWin.winType;
                        if (type === "modal") {
                            throw new Error("Modal window cannot be de-activated");
                        }

                        this.activeWin = null;
                        pkg.events.fire("winActivated", WIN_EVENT.$fillWith(old, this, false, false));

                        // TODO: special flag $dontGrabFocus is not very elegant solution
                        if (type === "mdi" && old.$dontGrabFocus !== true) {
                            pkg.focusManager.requestFocus(null);
                        }
                    } else {
                        if (this.kids.indexOf(c) < this.topModalIndex) {
                            throw new Error();
                        }

                        this.activeWin = c;
                        this.activeWin.toFront();

                        if (old !== null) {
                            pkg.events.fire("winActivated", WIN_EVENT.$fillWith(old, this, false, false));
                        }

                        pkg.events.fire("winActivated", WIN_EVENT.$fillWith(c, this, true, false));
                        this.activeWin.validate();

                        // TODO: special flag $dontGrabFocus is not very elegant
                        if (this.activeWin.winType === "mdi" && this.activeWin.$dontGrabFocus !== true) {
                            var newFocusable = pkg.focusManager.findFocusable(this.activeWin);
                            pkg.focusManager.requestFocus(newFocusable);
                        }
                    }
                }
            };

            /**
             * Add the given window with the given type and the listener to the layer.
             * @param {String} [type]   a type of the window: "modal",
             * "mdi" or "info"
             * @param {zebkit.ui.Panel} win an UI component to be shown as window
             * @method addWin
             */
            this.addWin = function(type, win) {
                // check if window type argument has been passed
                if (arguments.length > 1) {
                    win.winType = type;
                }
                this.add(win);
            };

            this.getComponentAt = function (x, y) {
                if (this.kids.length === 0) {
                    // layer is not active
                    return null;
                } else {
                    for (var i = this.kids.length - 1 ; i >= 0 && i >= this.topModalIndex; i--) {
                        var kid = this.kids[i];
                        if (kid.isVisible &&
                            kid.winType  !== "info"  &&
                            x >= kid.x               &&
                            y >= kid.y               &&
                            x  < kid.x + kid.width   &&
                            y  < kid.y + kid.height     )
                        {
                            return kid.getComponentAt(x - kid.x,
                                                      y - kid.y);
                        }
                    }

                    return this.activeWin !== null ? this : null;
                }
                return null;
            };
        },


        function kidAdded(index, constr, lw){
            this.$super(index, constr, lw);

            if (lw.winType === undefined) {
                lw.winType = "mdi";
            } else {
                zebkit.util.validateValue(lw.winType, "mdi", "modal", "info");
            }

            if (lw.winType === "modal") {
                this.topModalIndex = this.kids.length - 1;
                pkg.events.fire("winOpened", WIN_EVENT.$fillWith(lw, this, false, true));
                this.activate(lw);
            } else {
                pkg.events.fire("winOpened", WIN_EVENT.$fillWith(lw, this, false, true));
            }
        },

        function kidRemoved(index, lw, ctr){
            this.$getSuper("kidRemoved").call(this, index, lw, ctr);

            if (this.activeWin === lw) {
                this.activeWin = null;
                // TODO:  deactivated event can be used as a trigger of a window closing so
                // it is better don't fire it here this.fire("winActivated", lw, l);
                if (lw.winType === "mdi" && lw.$dontGrabFocus !== true) {
                    pkg.focusManager.requestFocus(null);
                }
            }

            var ci = index; //this.kids.indexOf(lw);
            if (ci < this.topModalIndex) { // correct top modal window index
                this.topModalIndex--;
            } else if (this.topModalIndex === ci) {
                // looking for a new modal window
                for (this.topModalIndex = this.kids.length - 1; this.topModalIndex >= 0; this.topModalIndex--){
                    if (this.kids[this.topModalIndex].winType === "modal") {
                        break;
                    }
                }
            }

            pkg.events.fire("winOpened", WIN_EVENT.$fillWith(lw, this, false, false));

            if (this.topModalIndex >= 0) {
                var aindex = this.kids.length - 1;
                while (this.kids[aindex].winType === "info") {
                    aindex--;
                }
                this.activate(this.kids[aindex]);
            }
        }
    ]);

    pkg.WinLayer = Class(pkg.Panel, pkg.WinLayerMix, []);

    /**
     * Window UI component class. Implements window like UI component. The window component has a header,
     * status bar and content areas. The header component is usually placed at the top of window, the
     * status bar component is placed at the bottom and the content component at places the central part
     * of the window. Also the window defines corner UI component that is supposed to be used to resize
     * the window. The window implementation provides the following possibilities:

        - Move window by dragging the window on its header
        - Resize window by dragging the window corner element
        - Place buttons in the header to maximize, minimize, close, etc the window
        - Indicates state of window (active or inactive) by changing
        the widow header style
        - Define a window icon component
        - Define a window status bar component

     * @class zebkit.ui.Window
     *
     * @param {String} [s] a window title
     * @param {zebkit.ui.Panel} [c] a window content
     * @constructor
     * @extends zebkit.ui.Panel
     */
    pkg.Window = Class(pkg.StatePan, [
        function (s, c) {
            //!!! for some reason state has to be set beforehand
            this.state = "inactive";

            this.prevH = this.prevX = this.prevY = 0;
            this.px = this.py = this.dx = this.dy = 0;
            this.prevW = this.action = -1;

            /**
             * Window caption panel. The panel contains window
             * icons, button and title label
             * @attribute caption
             * @type {zebkit.ui.Panel}
             * @readOnly
             */
            this.caption = this.createCaptionPan();

            /**
             * Window title component
             * @type {zebkit.ui.Panel}
             * @attribute title
             * @readOnly
             */
            this.title = this.createTitle();

            /**
             * Root window panel. The root panel has to be used to
             * add any UI components
             * @attribute root
             * @type {zebkit.ui.Panel}
             * @readOnly
             */
             if (arguments.length === 0) {
                c = s = null;
             } else if (arguments.length === 1) {
                if (zebkit.instanceOf(s, pkg.Panel)) {
                    c = s;
                    s = null;
                } else {
                    c = null;
                }
             }

            this.root = c === null ? this.createContentPan() : c;
            this.title.setValue(s === null ? "" : s);

            /**
             * Icons panel. The panel can contain number of icons.
             * @type {zebkit.ui.Panel}
             * @attribute icons
             * @readOnly
             */
            this.icons = new pkg.Panel(new zebkit.layout.FlowLayout("left", "center", "horizontal", 2));
            this.icons.add(new this.clazz.Icon());

            /**
             * Window buttons panel. The panel can contain number of window buttons
             * @type {zebkit.ui.Panel}
             * @attribute buttons
             * @readOnly
             */
            this.buttons = new pkg.Panel(new zebkit.layout.FlowLayout("center", "center"));

            this.caption.add("center", this.title);
            this.caption.add("left",   this.icons);
            this.caption.add("right",  this.buttons);

            /**
             * Window status panel.
             * @attribute status
             * @readOnly
             * @type {zebkit.ui.Panel}
             */
            this.status = new this.clazz.StatusPan();
            this.sizer  = new this.clazz.SizerPan();
            this.status.add(this.sizer);

            this.setSizeable(true);

            this.$super();
            this.setBorderLayout(2,2);

            this.add("center", this.root);
            this.add("top",    this.caption);
            this.add("bottom", this.status);
        },

        function $clazz() {
            this.CaptionPan = Class(pkg.StatePan, [
                function $prototype() {
                    this.state = "inactive";
                }
            ]);

            this.TitleLab   = Class(pkg.Label, []);
            this.StatusPan  = Class(pkg.Panel, []);
            this.ContentPan = Class(pkg.Panel, []);
            this.SizerPan   = Class(pkg.ViewPan, []);
            this.Icon       = Class(pkg.ImagePan, []);
            this.Button     = Class(pkg.Button, []);
        },

        function $prototype() {
            var MOVE_ACTION = 1, SIZE_ACTION = 2;

            this.sizer = this.caption = null;

            this.isPopupEditor = true;

            /**
             * Minimal possible size of the window
             * @default 40
             * @attribute minSize
             * @type {Integer}
             */
            this.minSize = 40;

            /**
             * Indicate if the window can be resized by dragging its by corner
             * @attribute isSizeable
             * @type {Boolean}
             * @default true
             * @readOnly
             */
            this.isSizeable = true;

            /**
             * Test if the window is shown as a window and activated
             * @return {Boolean} true is the window is shown as internal window and
             * is active.
             * @method isActive
             */
            this.isActive = function() {
                var c = this.getCanvas();
                return c !== null && c.getLayer("win").activeWin === this.getWinContainer();
            };

            this.pointerDragStarted = function(e){
                this.px = e.absX;
                this.py = e.absY;
                this.action = this.insideCorner(e.x, e.y) ? (this.isSizeable ? SIZE_ACTION : -1)
                                                          : MOVE_ACTION;
                if (this.action > 0) {
                    this.dy = this.dx = 0;
                }
            };

            this.pointerDragged = function(e){
                if (this.action > 0) {
                    var container = null;

                    if (this.action !== MOVE_ACTION){
                        container = this.getWinContainer();

                        var nw = this.dx + container.width,
                            nh = this.dy + container.height;

                        if (nw > this.minSize && nh > this.minSize) {
                            container.setSize(nw, nh);
                        }
                    }

                    this.dx = (e.absX - this.px);
                    this.dy = (e.absY - this.py);
                    this.px = e.absX;
                    this.py = e.absY;
                    if (this.action === MOVE_ACTION){
                        container = this.getWinContainer();
                        container.setLocation(this.dx + container.x, this.dy + container.y);
                    }
                }
            };

            this.pointerDragEnded = function(e){
                if (this.action > 0){
                    if (this.action === MOVE_ACTION){
                        var container = this.getWinContainer();
                        container.setLocation(this.dx + container.x, this.dy + container.y);
                    }
                    this.action = -1;
                }
            };

            this.getWinContainer = function() {
                return this;
            };

            /**
             * Test if the pointer cursor is inside the window corner component
             * @protected
             * @param  {Integer} px a x coordinate of the pointer cursor
             * @param  {Integer} py a y coordinate of the pointer cursor
             * @return {Boolean}  true if the pointer cursor is inside window
             * corner component
             * @method insideCorner
             */
            this.insideCorner = function(px,py){
                return this.getComponentAt(px, py) === this.sizer;
            };

            this.getCursorType = function(target,x,y){
                return (this.isSizeable && this.insideCorner(x, y)) ? pkg.Cursor.SE_RESIZE
                                                                    : null;
            };

            this.catchInput = function(c){
                var tp = this.caption;
                return c === tp ||
                      (zebkit.layout.isAncestorOf(tp, c) && zebkit.instanceOf(c, pkg.Button) === false) ||
                       this.sizer === c;
            };

            this.winOpened = function(e) {
                var state = this.isActive() ? "active" : "inactive";
                if (this.caption !== null && this.caption.setState !== undefined) {
                    this.caption.setState(state);
                }
                this.setState(state);
            };

            this.winActivated = function(e) {
                this.winOpened(e);
            };

            this.pointerDoubleClicked = function (e){
                var x = e.x, y = e.y, cc = this.caption;
                if (this.isSizeable === true &&
                    x > cc.x &&
                    x < cc.y + cc.width &&
                    y > cc.y &&
                    y < cc.y + cc.height)
                {
                    if (this.prevW < 0) {
                        this.maximize();
                    } else {
                        this.restore();
                    }
                }
            };

            /**
             * Test if the window has been maximized to occupy the whole
             * window layer space.
             * @return {Boolean} true if the window has been maximized
             * @method isMaximized
             */
            this.isMaximized = function() {
                return this.prevW !== -1;
            };

            /**
             * Create a caption component
             * @return {zebkit.ui.Panel} a zebkit caption component
             * @method createCaptionPan
             * @protected
             */
            this.createCaptionPan = function() {
                return new this.clazz.CaptionPan();
            };

            /**
             * Create a content component
             * @return {zebkit.ui.Panel} a content component
             * @method createContentPan
             * @protected
             */
            this.createContentPan = function() {
                return new this.clazz.ContentPan();
            };

            /**
             * Create a caption title label
             * @return {zebkit.ui.Label} a caption title label
             * @method createTitle
             * @protected
             */
            this.createTitle = function() {
                return new this.clazz.TitleLab();
            };

            this.setIcon = function(i, icon) {
                if (zebkit.isString(icon) || zebkit.instanceOf(icon, zebkit.draw.Picture)) {
                    icon = new pkg.ImagePan(icon);
                }
                this.icons.setAt(i, icon);
                return this;
            };

            /**
             * Make the window sizable or not sizeable
             * @param {Boolean} b a sizeable state of the window
             * @chainable
             * @method setSizeable
             */
            this.setSizeable = function(b){
                if (this.isSizeable !== b){
                    this.isSizeable = b;
                    if (this.sizer !== null) {
                        this.sizer.setVisible(b);
                    }
                }
                return this;
            };

            /**
             * Maximize the window
             * @method maximize
             * @chainable
             */
            this.maximize = function(){
                if (this.prevW < 0){
                    var d    = this.getCanvas(),
                        cont = this.getWinContainer(),
                        left = d.getLeft(),
                        top  = d.getTop();

                    this.prevX = cont.x;
                    this.prevY = cont.y;
                    this.prevW = cont.width;
                    this.prevH = cont.height;

                    cont.setBounds(left, top,
                                   d.width  - left - d.getRight(),
                                   d.height - top - d.getBottom());
                }
                return this;
            };

            /**
             * Restore the window size
             * @method restore
             * @chainable
             */
            this.restore = function(){
                if (this.prevW >= 0){
                    this.getWinContainer().setBounds(this.prevX, this.prevY,
                                                     this.prevW, this.prevH);
                    this.prevW = -1;
                }
                return this;
            };

            /**
             * Close the window
             * @method close
             * @chainable
             */
            this.close = function() {
                this.getWinContainer().removeMe();
                return this;
            };

            /**
             * Set the window buttons set.
             * @param {Object} buttons dictionary of buttons icons for window buttons.
             * The dictionary key defines a method of the window component to be called
             * when the given button has been pressed. So the method has to be defined
             * in the window component.
             * @method setButtons
             */
            this.setButtons = function(buttons) {
                // remove previously added buttons
                for(var i = 0; i < this.buttons.length; i++) {
                    var kid = this.buttons.kids[i];
                    if (kid.isEventFired()) {
                        kid.off();
                    }
                }

                this.buttons.removeAll();

                // add new buttons set
                for(var k in buttons) {
                    if (buttons.hasOwnProperty(k)) {

                        console.log("!!!!!!!!!!!!!!!! apply properties : " + JSON.stringify(buttons[k]));


                        var b = new this.clazz.Button();
                        b.properties(buttons[k]);
                        this.buttons.add(b);

                        (function(t, f) {
                            b.on(function() { f.call(t); });
                        })(this, this[k]);
                    }
                }
                return this;
            };
        },

        function focused(){
            this.$super();
            if (this.caption !== null) {
                this.caption.repaint();
            }
        }
    ]);


    /**
     * Tooltip UI component. The component can be used as a tooltip that shows specified content in
     * figured border.
     * @class  zebkit.ui.Tooltip
     * @param  {zebkit.util.Panel|String} a content component or test label to be shown in tooltip
     * @constructor
     * @extends zebkit.ui.Panel
     */
    pkg.Tooltip = Class(pkg.Panel, [
        function(content) {
            this.$super();
            if (arguments.length > 0) {
                this.add(pkg.$component(content, this));
                this.toPreferredSize();
            }
        },

        function $clazz() {
            this.Label = Class(pkg.Label, []);

            this.ImageLabel = Class(pkg.ImageLabel, []);

            this.TooltipBorder = Class(zebkit.draw.View, [
                function(col, size) {
                    if (arguments.length > 0) {
                        this.color = col;
                    }
                    if (arguments.length > 1) {
                        this.size  = size;
                    }
                    this.gap = 2 * this.size;
                },

                function $prototype() {
                    this.color = "black";
                    this.size  = 2;

                    this.paint = function (g,x,y,w,h,d) {
                        if (this.color !== null) {
                            this.outline(g,x,y,w,h,d);
                            g.setColor(this.color);
                            g.lineWidth = this.size;
                            g.stroke();
                        }
                    };

                    this.outline = function(g,x,y,w,h,d) {
                        g.beginPath();
                        h -= 2 * this.size;
                        w -= 2 * this.size;
                        x += this.size;
                        y += this.size;

                        var w2   = Math.round(w /2),
                            w3_8 = Math.round((3 * w)/8),
                            h2_3 = Math.round((2 * h)/3),
                            h3   = Math.round(h/3),
                            w4   = Math.round(w/4);

                        g.moveTo(x + w2, y);
                        g.quadraticCurveTo(x, y, x, y + h3);
                        g.quadraticCurveTo(x, y + h2_3, x + w4,  y + h2_3);
                        g.quadraticCurveTo(x + w4, y + h, x, y + h);
                        g.quadraticCurveTo(x + w3_8, y + h, x + w2, y + h2_3);
                        g.quadraticCurveTo(x + w, y + h2_3, x + w, y + h3);
                        g.quadraticCurveTo(x + w, y, x + w2, y);
                        g.closePath();
                        return true;
                    };
                }
            ]);
        },

        function setValue(v) {
            this.kids[0].setValue(v);
            return this;
        },

        function recalc() {
            this.$contentPs = (this.kids.length === 0 ? this.$super()
                                                      : this.kids[0].getPreferredSize());
        },

        function getBottom() {
            return this.$super() + this.$contentPs.height;
        },

        function getTop() {
            return this.$super() + Math.round(this.$contentPs.height / 6);
        },

        function getLeft() {
            return this.$super() + Math.round(this.$contentPs.height / 6);
        },

        function getRight() {
            return this.$super() + Math.round(this.$contentPs.height / 6);
        }
    ]);

    /**
     * Popup window manager class. The manager registering and triggers showing context popup menu
     * and tooltips. Menu appearing is triggered by right pointer click or double fingers touch event.
     * To bind a popup menu to an UI component you can either set "tooltip" property of the component
     * with a popup menu instance:

            // create canvas
            var canvas = new zebkit.ui.zCanvas();

            // create menu with three items
            var m = new zebkit.ui.Menu();
            m.add("Menu Item 1");
            m.add("Menu Item 2");
            m.add("Menu Item 3");

            // bind the menu to root panel
            canvas.root.popup = m;

     * Or implement "getPopup(target,x,y)" method that can rule showing popup menu depending on
     * the current cursor location:

            // create canvas
            var canvas = new zebkit.ui.zCanvas();

            // visualize 50x50 pixels hot component spot
            // to which the context menu is bound
            canvas.root.paint = function(g) {
                g.setColor("red");
                g.fillRect(50,50,50,50);
            }

            // create menu with three items
            var m = new zebkit.ui.Menu();
            m.add("Menu Item 1");
            m.add("Menu Item 2");
            m.add("Menu Item 3");

            // implement "getPopup" method that shows popup menu only
            // if pointer cursor located at red rectangular area of the
            // component
            canvas.root.getPopup = function(target, x, y) {
                // test if pointer cursor position is in red spot area
                // and return context menu if it is true
                if (x > 50 && y > 50 && x < 100 && y <  100)  {
                    return m;
                }
                return null;
            }

     *  Defining a tooltip for an UI component follows the same approach. Other you
     *  define set "tooltip" property of your component with a component that has to
     *  be shown as the tooltip:

             // create canvas
             var canvas = new zebkit.ui.zCanvas();

             // create tooltip
             var t = new zebkit.ui.Label("Tooltip");
             t.setBorder("plain");
             t.setBackground("yellow");
             t.setPadding(6);

             // bind the tooltip to root panel
             canvas.root.popup = t;

    *  Or you can implement "getTooltip(target,x,y)" method if the tooltip showing depends on
    *  the pointer cursor location:


            // create canvas
            var canvas = new zebkit.ui.zCanvas();

            // create tooltip
            var t = new zebkit.ui.Label("Tooltip");
            t.setBorder("plain");
            t.setBackground("yellow");
            t.setPadding(6);

            // bind the tooltip to root panel
            canvas.root.getPopup = function(target, x, y) {
                return x < 10 && y < 10 ? t : null;
            };

     * @class zebkit.ui.TooltipManager
     * @extends zebkit.ui.event.Manager
     * @constructor
     */

     /**
      * Fired when a menu item has been selected

             zebkit.ui.events.on("menuItemSelected", function(menu, index, item) {
                 ...
             });

      *
      * @event menuItemSelected
      * @param {zebkit.ui.Menu} menu a menu component that triggers the event
      * @param {Integer}  index a menu item index that has been selected
      * @param {zebkit.ui.Panel} item a menu item component that has been selected
      */
    pkg.TooltipManager = Class(zebkit.ui.event.Manager, [
        function $prototype() {
            this.$tooltipX = this.$tooltipY = 0;
            this.$toolTask = this.$targetTooltipLayer = this.$tooltip = this.$target = null;

            /**
             * Indicates if a shown tooltip has to disappear by pointer pressed event
             * @attribute hideTooltipByPress
             * @type {Boolean}
             * @default true
             */
            this.hideTooltipByPress = true;

            /**
             * Define interval (in milliseconds) between entering a component and showing
             * a tooltip for the entered component
             * @attribute showTooltipIn
             * @type {Integer}
             * @default 400
             */
            this.showTooltipIn = 400;

            /**
             * Indicates if tool tip position has to be synchronized with pointer position
             * @attribute syncTooltipPosition
             * @type {Boolean}
             * @default true
             */
            this.syncTooltipPosition = true;

            /**
             * Define pointer clicked event handler
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerClicked
             */
            // this.pointerClicked = function (e){

            //     // Right button
            //     // TODO: check if it is ok and compatible with touch
            //     if (this.isTriggeredWith(e)) {
            //         var popup = null;

            //         if (e.source.popup != null) {
            //             popup = e.source.popup;
            //         } else {
            //             if (e.source.getPopup != null) {
            //                 popup = e.source.getPopup(e.source, e.x, e.y);
            //             }
            //         }

            //         if (popup != null) {
            //             popup.setLocation(e.absX, e.absY);
            //             e.source.getCanvas().getLayer(pkg.PopupLayer.id).add(popup);
            //             popup.requestFocus();
            //         }
            //     }
            // };

            /**
             * Define pointer entered event handler
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerEntered
             */
            this.pointerEntered = function(e) {
                if (this.$target === null &&
                    ((e.source.tooltip !== undefined && e.source.tooltip !== null) || e.source.getTooltip !== undefined))
                {
                    this.$target = e.source;
                    this.$targetTooltipLayer = e.source.getCanvas().getLayer("win");
                    this.$tooltipX = e.x;
                    this.$tooltipY = e.y;
                    this.$toolTask = zebkit.util.tasksSet.run(
                        this,
                        this.showTooltipIn,
                        this.showTooltipIn
                    );
                }
            };

            /**
             * Define pointer exited event handler
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerExited
             */
            this.pointerExited = function(e) {
                // exited triggers tooltip hiding only for "info" tooltips
                if (this.$target !== null && (this.$tooltip === null || this.$tooltip.winType === "info")) {
                    this.stopShowingTooltip();
                }
            };

            /**
             * Define pointer moved event handler
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerMoved
             */
            this.pointerMoved = function(e) {
                // to prevent handling pointer moved from component of mdi
                // tooltip we have to check if target equals to source
                // instead of just checking if target is not a null
                if (this.$target === e.source) {
                    // store a new location for a tooltip
                    this.$tooltipX = e.x;
                    this.$tooltipY = e.y;

                    // wake up task try showing a tooltip
                    // at the new location
                    if (this.$toolTask !== null) {
                        this.$toolTask.resume(this.showTooltipIn);
                    }
                }
            };

            /**
             * Task body method
             * @private
             * @param  {Task} t a task context
             * @method run
             */
            this.run = function(t) {
                if (this.$target !== null) {
                    var ntooltip = (this.$target.tooltip !== undefined &&
                                   this.$target.tooltip !== null) ? this.$target.tooltip
                                                                  : this.$target.getTooltip(this.$target,
                                                                                            this.$tooltipX,
                                                                                            this.$tooltipY),
                        p = null,
                        tx = 0,
                        ty = 0;

                    if (this.$tooltip !== ntooltip) {

                        // hide previously shown tooltip
                        if (this.$tooltip !== null) {
                            this.hideTooltip();
                        }

                        // set new tooltip
                        this.$tooltip = ntooltip;

                        // if new tooltip exists than show it
                        if (ntooltip !== null) {
                            p = zebkit.layout.toParentOrigin(this.$tooltipX, this.$tooltipY, this.$target);

                            this.$tooltip.toPreferredSize();
                            tx = p.x;
                            ty = p.y - this.$tooltip.height;

                            var dw = this.$targetTooltipLayer.width;

                            if (tx + this.$tooltip.width > dw) {
                                tx = dw - this.$tooltip.width - 1;
                            }

                            this.$tooltip.setLocation(tx < 0 ? 0 : tx, ty < 0 ? 0 : ty);

                            if (this.$tooltip.winType === undefined) {
                                this.$tooltip.winType = "info";
                            }

                            this.$targetTooltipLayer.add(this.$tooltip);
                            if (this.$tooltip.winType !== "info") {
                                pkg.activateWindow(this.$tooltip);
                            }
                        }
                    } else {
                        if (this.$tooltip !== null && this.syncTooltipPosition === true) {
                            p  = zebkit.layout.toParentOrigin(this.$tooltipX,
                                                              this.$tooltipY,
                                                              this.$target);
                            tx = p.x;
                            ty = p.y - this.$tooltip.height;

                            this.$tooltip.setLocation(tx < 0 ? 0 : tx, ty < 0 ? 0 : ty);
                        }
                    }
                }
                t.pause();
            };

            this.winActivated = function(e) {
                // this method is called only for mdi window
                // consider every deactivation of a mdi window as
                // a signal to stop showing tooltip
                if (e.isActive === false && this.$tooltip !== null)  {
                    this.$tooltip.removeMe();
                }
            };

            this.winOpened = function(e) {
                if (e.isShown === false) {
                    // cleanup tooltip reference
                    this.$tooltip = null;

                    if (e.source.winType !== "info") {
                        this.stopShowingTooltip();
                    }
                }
            };

            /**
             * Stop showing tooltip
             * @private
             * @method stopShowingTooltip
             */
            this.stopShowingTooltip = function() {
                if (this.$target !== null) {
                    this.$target = null;
                }

                if (this.$toolTask !== null) {
                    this.$toolTask.shutdown();
                }

                this.hideTooltip();
            };

            /**
             * Hide tooltip if it has been shown
             * @method hideTooltip
             */
            this.hideTooltip = function(){
                if (this.$tooltip !== null) {
                    this.$tooltip.removeMe();
                    this.$tooltip = null;
                }
            };

            /**
             * Define pointer pressed event handler
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerPressed
             */
            this.pointerPressed = function(e) {
                if (this.hideTooltipByPress === true &&
                    e.pointerType === "mouse" &&
                    this.$target !== null && this.$target.hideTooltipByPress !== false &&
                    (this.$tooltip === null || this.$tooltip.winType === "info"))
                {
                    this.stopShowingTooltip();
                }
            };

            /**
             * Define pointer released event handler
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerReleased
             */
            this.pointerReleased = function(e) {
                if ((this.hideTooltipByPress === false || e.pointerType !== "mouse")     &&
                    (this.$target !== null && this.$target.hideTooltipByPress !== false) &&
                    (this.$tooltip === null || this.$tooltip.winType === "info"))
                {
                    this.stopShowingTooltip();
                }
            };
        }
    ]);


    pkg.Spin = new Class(pkg.Panel, [
        function(min, max) {
            this.$super(this);

            this.step     = 1;
            this.min      = arguments.length === 0 ? 0             : min;
            this.max      = arguments.length <   2 ? this.min + 10 : max;
            this.editor   = null;
            this.isLooped = true;

            this.layoutComponents(new this.clazz.TextField(this, this.min, this.max),
                                  new this.clazz.IncButton(),
                                  new this.clazz.DecButton());
        },

        function $clazz() {
            this.IncButton = Class(pkg.ArrowButton, []);
            this.DecButton = Class(pkg.ArrowButton, []);

            this.DecButton.prototype.increment = -1;
            this.IncButton.prototype.increment =  1;

            this.TextField = Class(pkg.TextField, [
                function (target, min, max) {
                    this.target = target;
                    this.min = min;
                    this.max = max;

                    var $this = this;
                    this.$super(new zebkit.data.SingleLineTxt([
                        function () {
                            this.$super("" + min);
                        },

                        function validate(value) {
                            return $this.isValideValue(value);
                        }
                    ]));
                },

                function isValideValue(v) {
                    if (/^[-+]?^[0]*[0-9]+$/.test(v) === true) {
                        var iv = parseInt(v);
                        return iv >= this.min && iv <= this.max;
                    }
                    return false;
                },

                function keyTyped(e) {
                    var model     = this.getModel(),
                        validate  = model.validate,
                        prevValue = this.getValue();

                    model.validate = null;

                    var pos = this.position.offset;
                    if (pos >= 0 && pos < prevValue.length && this.hasSelection() === false) {
                        this.select(pos, pos + 1);
                    }

                    this.$super(e);

                    if (this.isValideValue(this.getValue()) === false) {
                        this.setValue(prevValue);
                    }
                    model.validate = validate;
                },

                function calcPreferredSize(target) {
                    var font = this.getFont();
                    return { width  : Math.max(font.stringWidth("" + this.max), font.stringWidth("" + this.min)),
                             height : font.height };
                },

                function textUpdated(src, op, off, size, startLine, lines) {
                    this.$super(src, op, off, size, startLine, lines);
                    if (this.getModel().validate !== undefined) {
                        // TODO: manual text input is not allowed yet
                    }
                }
            ]);
        },

        function layoutComponents(text, inc, dec) {
            var buttons = new pkg.Panel(new zebkit.layout.PercentLayout("vertical"));
            this.setBorderLayout();

            var tfPan = new pkg.Panel(new zebkit.layout.FlowLayout("left", "center"));
            tfPan.layout.stretchLast = true;
            this.add("center", tfPan);

            tfPan.add(text);
            this.add("right", buttons);
            buttons.add(50, inc);
            buttons.add(50, dec);


            // this.setBorderLayout();
            // this.add("center", text);

            // var buttons = new pkg.Panel(new zebkit.layout.BorderLayout());
            // buttons.add("top", inc);
            // buttons.add("bottom", dec);
            // this.add("right", buttons);


            // this.setBorderLayout();
            // this.add("center", text );
            // this.add("left", dec);
            // this.add("right", inc);
        },

        function $install(child) {
            console.log("$install() : " + child.clazz.$name  + ","  + child.isEventFired("fired"));


            if (child.isEventFired("fired")) {
                child.on(this);
            } else if (zebkit.instanceOf(child, pkg.TextField)) {
                this.editor = child;
            }
        },

        function $uninstall(child) {
            if (child.isEventFired("fired")) {
                child.off(this);
            } else if (zebkit.instanceOf(child, pkg.TextField)) {
                this.editor = null;
            }
        },

        function compAdded(e) {
            this.$install(e.kid);
        },

        function compRemoved(e) {
            this.$uninstall(e.kid);
        },

        function childCompAdded(e) {
            // TODO: check it is called and kid is proper field
            this.$install(e.kid);
        },

        function childCompRemoved(e) {
            // TODO: check it is called and kid is proper field
            this.$uninstall(e.kid);
        },


        function keyPressed(e) {
            if (e.code === "ArrowDown") {
                this.setValue(this.getValue() - this.step);
            } else if (e.code === "ArrowUp") {
                this.setValue(this.getValue() + this.step);
            }
        },


        function setMinMax (min, max) {
            if (this.min !== min && this.max !== max) {
                this.min = min;
                this.max = max;
                this.setValue(this.getValue());
                this.vrp();
            }
        },

        function catchInput(c) {
            return zebkit.instanceOf(c, pkg.ArrowButton) === false;
        },

        function setLoopEnabled(b) {
            this.isLooped = b;
        },

        function fired(src) {
            this.setValue(this.getValue() + src.increment * this.step);
        },

        function getValue(){
            var value = this.editor.getValue();
            if (value === "") {
                return this.min;
            } else {
                return parseInt((value.indexOf('+') === 0) ? value.substring(1) : value);
            }
        },

        function setValue(v){
            if (v < this.min) {
                v = this.isLooped ? this.max : this.min;
            }

            if (v > this.max) {
                v = this.isLooped ? this.min : this.max;
            }

            var prev = this.getValue();
            if (prev !== v) {
                var prevValue = prev;
                this.editor.setValue("" + v);
                this.repaint();
                this.fire("fired", [ this, prevValue ]);
            }
        }
    ]).events("fired");
},true);
zebkit.package("ui.tree", function(pkg, Class) {
    'use strict';
    var ui = pkg.cd("..");

    /**
     * Tree UI components and all related to the component classes and interfaces.
     * Tree components are graphical representation of a tree model that allows a user
     * to navigate over the model item, customize the items rendering and
     * organize customizable editing of the items.
     *
     *       // create tree component instance to visualize the given tree model
     *       var tree = new zebkit.ui.tree.Tree({
     *           value: "Root",
     *           kids : [
     *               "Item 1",
     *               "Item 2",
     *               "Item 3"
     *           ]
     *       });
     *
     *       // make all tree items editable with text field component
     *       tree.setEditorProvider(new zebkit.ui.tree.DefEditors());
     *
     * One more tree  component implementation - "CompTree" - allows developers
     * to create tree whose nodes are  other UI components
     *
     *       // create tree component instance to visualize the given tree model
     *       var tree = new zebkit.ui.tree.CompTree({
     *           value: new zebkit.ui.Label("Root label item"),
     *           kids : [
     *               new zebkit.ui.Checkbox("Checkbox Item"),
     *               new zebkit.ui.Button("Button Item"),
     *               new zebkit.ui.TextField("Text field item")
     *           ]
     *       });
     *
     * @class zebkit.ui.tree
     * @access package
     */

    //  tree node metrics:
    //   |
    //   |-- <-gapx-> {icon} -- <-gapx-> {view}
    //

    /**
     * Simple private structure to keep a tree model item metrical characteristics
     * @constructor
     * @param {Boolean} b a state of an appropriate tree component node of the given
     * tree model item. The state is sensible for item that has children items and
     * the state indicates if the given tree node is collapsed (false) or expanded
     * (true)
     * @private
     * @class zebkit.ui.tree.ItemMetric
     */
    pkg.ItemMetric = function(b) {
        /**
         *  The whole width of tree node that includes a rendered item preferred
         *  width, all icons and gaps widths
         *  @attribute width
         *  @type {Integer}
         *  @readOnly
         */

        /**
         *  The whole height of tree node that includes a rendered item preferred
         *  height, all icons and gaps heights
         *  @attribute height
         *  @type {Integer}
         *  @readOnly
         */

        /**
         *  Width of an area of rendered tree model item. It excludes icons, toggle
         *  and gaps widths
         *  @attribute viewWidth
         *  @type {Integer}
         *  @readOnly
         */

        /**
         *  Height of an area of rendered tree model item. It excludes icons, toggle
         *  and gaps heights
         *  @attribute viewHeight
         *  @type {Integer}
         *  @readOnly
         */

        /**
         *  Indicates whether a node is in expanded or collapsed state
         *  @attribute isOpen
         *  @type {Boolean}
         *  @readOnly
         */

        this.width = this.height = this.x = this.y = this.viewHeight = 0;
        this.viewWidth = -1;
        this.isOpen = b;
    };

    /**
     * Default tree editor provider
     * @constructor
     * @class zebkit.ui.tree.DefEditors
     */
    pkg.DefEditors = Class([
        function() {
            /**
             * Internal component that are designed as default editor component
             * @private
             * @readOnly
             * @attribute tf
             * @type {zebkit.ui.TextField}
             */
            this.tf = new this.clazz.TextField(new zebkit.data.SingleLineTxt(""));
        },

        function $clazz() {
            this.TextField = Class(ui.TextField, []);
        },

        function $prototype() {
            /**
             * Get an UI component to edit the given tree model element
             * @param  {zebkit.ui.tree.Tree} src a tree component
             * @param  {zebkit.data.Item} item an data model item
             * @return {zebkit.ui.Panel} an editor UI component
             * @method getEditor
             */
            this.getEditor = function(src, item){
                var o = item.value;
                this.tf.setValue(o === null ? "" : o.toString());
                return this.tf;
            };

            /**
             * Fetch a model item from the given UI editor component
             * @param  {zebkit.ui.tree.Tree} src a tree UI component
             * @param  {zebkit.ui.Panel} editor an editor that has been used to edit the tree model element
             * @return {Object} an new tree model element value fetched from the given UI editor component
             * @method fetchEditedValue
             */
            this.fetchEditedValue = function(src, editor){
                return editor.view.target.getValue();
            };

            /**
             * The method is called to ask if the given input event should trigger an tree component item
             * @param  {zebkit.ui.tree.Tree} src a tree UI component
             * @param  {zebkit.ui.event.PointerEvent|zebkit.ui.event.KeyEvent} e   an input event: pointer
             * or key event
             * @return {Boolean} true if the event should trigger edition of a tree component item
             * @method @shouldStartEdit
             */
            this.shouldStartEdit = function(src,e){
                return  e.id === "pointerDoubleClicked" ||
                       (e.id === "keyPressed" && e.code === "Enter");
            };
        }
    ]);

    /**
     * Default tree editor view provider
     * @class zebkit.ui.tree.DefViews
     * @constructor
     * @extends zebkit.draw.BaseViewProvider
     */
    pkg.DefViews = Class(zebkit.draw.BaseViewProvider, [
        /**
         * Get a view for the given model item of the UI tree component
         * @param  {zebkit.ui.tree.Tree} tree  a tree component
         * @param  {zebkit.data.Item} item a tree model element
         * @return {zebkit.draw.View}  a view to visualize the given tree data model element
         * @method  getView
         */
        function getView(tree, item) {
            return this.$super(tree, item.value);
        }
    ]);


    /**
     * Abstract tree component that can used as basement for building own tree components.
     * The component is responsible for rendering tree, calculating tree nodes metrics,
     * computing visible area, organizing basic user interaction. Classes that inherit it
     * has to provide the following important things:

        * **A tree model item metric** Developers have to implement "getItemPreferredSize(item)"
          method to say which size the given tree item wants to have.
        * **Tree node item rendering** If necessary developers have to implement the way
          a tree item has to be visualized by implementing "this.paintItem(...)" method

     *
     * @class zebkit.ui.tree.BaseTree
     * @uses  zebkit.EventProducer
     * @constructor
     * @param {zebkit.data.TreeModel|Object} a tree model. It can be an instance of tree model
     * class or an object that described tree model. An example of such object is shown below:

            {
                value : "Root",
                kids  : [
                    {
                        value: "Child 1",
                        kids :[
                            "Sub child 1"
                        ]
                    },
                    "Child 2",
                    "Child 3"
                ]
            }

     * @param {Boolean} [nodeState] a default tree nodes state (expanded or collapsed)
     * @extends zebkit.ui.Panel
     * @uses  zebkit.ui.HostDecorativeViews
     */

     /**
      * Fired when a tree item has been toggled

            tree.on("toggled", function(src, item) {
               ...
            });

      * @event toggled
      * @param  {zebkit.ui.tree.BaseTree} src a tree component that triggers the event
      * @param  {zebkit.data.Item} item an tree item that has been toggled
      */

     /**
      * Fired when a tree item has been selected

          tree.on("selected", function(src, prevItem) {
             ...
          });

      * @event selected
      * @param  {zebkit.ui.tree.BaseTree} src a tree component that triggers the event
      * @param  {zebkit.data.Item} prevItem a previously selected tree item
      */


    /**
      * Fired when a tree item editing has been started

          tree.on("editingStarted", function(src, item, editor) {
             ...
          });

      * @event editingStarted
      * @param  {zebkit.ui.tree.BaseTree} src an tree component that triggers the event
      * @param  {zebkit.data.Item} item a tree item to be edited
      * @param  {zebkit.ui.Panel} editor an editor to be used to edit the given item
      */

    /**
      * Fired when a tree item editing has been stopped

          tree.on("editingStopped", function(src, item, oldValue, editor, isApplied) {
             ...
          });

      * @event editingStopped
      * @param  {zebkit.ui.tree.BaseTree} src a tree component that triggers the event
      * @param  {zebkit.data.Item} item a tree item that has been edited
      * @param  {Object} oldValue an old value of the edited tree item
      * @param  {zebkit.ui.Panel} editor an editor to be used to edit the given item
      * @param  {Boolean} isApplied flag that indicates if the edited value has been
      * applied to the given tree item
      */
    pkg.BaseTree = Class(ui.Panel, ui.HostDecorativeViews, [
        function (d, b) {
            if (arguments.length < 2) {
                b = true;
            }

            this.maxw = this.maxh = 0;

            this.views     = {};
            this.viewSizes = {};

            this._isVal = false;
            this.nodes = {};
            this.setLineColor("gray");

            this.isOpenVal = b;

            this.setSelectable(true);
            this.$super();
            this.setModel(d);
            this.scrollManager = new ui.ScrollManager(this);
        },

        function $prototype() {
             /**
              * Tree component line color
              * @attribute lnColor
              * @type {String}
              * @readOnly
              */
            this.visibleArea = this.lnColor = null;

             /**
              * Selected tree model item
              * @attribute selected
              * @type {zebkit.data.Item}
              * @default null
              * @readOnly
              */
            this.model = this.selected = this.firstVisible = null;

            /**
             * Horizontal gap between a node elements: toggle, icons and tree item view
             * @attribute gapx
             * @readOnly
             * @default 2
             * @type {Integer}
             */

            /**
             * Vertical gap between a node elements: toggle, icons and tree item view
             * @attribute gapy
             * @readOnly
             * @default 2
             * @type {Integer}
             */

            this.gapx = this.gapy = 2;
            this.canHaveFocus = true;

            /**
             * Test if the given tree component item is opened
             * @param  {zebkit.data.Item}  i a tree model item
             * @return {Boolean} true if the given tree component item is opened
             * @method isOpen
             */
            this.isOpen = function(i){
                this.validate();
                return this.$isOpen(i);
            };

            /**
             * Get calculated for the given tree model item metrics
             * @param  {zebkit.data.Item} i a tree item
             * @return {Object}   an tree model item metrics. Th
             * @method getItemMetrics
             */
            this.getItemMetrics = function(i){
                this.validate();
                return this.getIM(i);
            };

            /**
             * Called every time a pointer pressed in toggle area.
             * @param  {zebkit.data.Item} root an tree item where toggle has been done
             * @method togglePressed
             * @protected
             */
            this.togglePressed = function(root) {
                this.toggle(root);
            };

            this.itemPressed = function(root, e) {
                this.select(root);
            };

            this.pointerPressed = function(e){
                if (this.firstVisible !== null && e.isAction()) {
                    var x = e.x,
                        y = e.y,
                        root = this.getItemAt(this.firstVisible, x, y);

                    if (root !== null) {
                        x -= this.scrollManager.getSX();
                        y -= this.scrollManager.getSY();
                        var r = this.getToggleBounds(root);

                        if (x >= r.x && x < r.x + r.width && y >= r.y && y < r.y + r.height){
                            this.togglePressed(root);
                        } else if (x > r.x + r.width) {
                            this.itemPressed(root, e);
                        }
                    }
                }
            };

            this.vVisibility = function (){
                if (this.model === null) {
                    this.firstVisible = null;
                }
                else {
                    var nva = ui.$cvp(this, {});
                    if (nva === null) {
                        this.firstVisible = null;
                    } else {
                        if (this._isVal === false ||
                            (this.visibleArea === null              ||
                             this.visibleArea.x !== nva.x           ||
                             this.visibleArea.y !== nva.y           ||
                             this.visibleArea.width !== nva.width   ||
                             this.visibleArea.height !== nva.height   ))
                        {
                            this.visibleArea = nva;
                            if (this.firstVisible !== null) {
                                this.firstVisible = this.findOpened(this.firstVisible);
                                this.firstVisible = this.isOverVisibleArea(this.firstVisible) ? this.nextVisible(this.firstVisible)
                                                                                              : this.prevVisible(this.firstVisible);
                            } else {
                                this.firstVisible = (-this.scrollManager.getSY() > Math.floor(this.maxh / 2)) ? this.prevVisible(this.findLast(this.model.root))
                                                                                                              : this.nextVisible(this.model.root);
                            }
                        }
                    }
                }
                this._isVal = true;
            };

            this.recalc = function() {
                this.maxh = this.maxw = 0;
                if (this.model !== null && this.model.root !== null) {
                    this.$recalc(this.getLeft(), this.getTop(), null, this.model.root, true);
                    this.maxw -= this.getLeft();
                    this.maxh -= this.gapy;
                }
            };

            /**
             * Get tree model item  metrical bounds (location and size).
             * @param  {zebkit.data.Item} root an tree model item
             * @return {Object} a structure that keeps an item view location
             * and size:

                    {
                        x: {Integer},
                        y: {Integer},
                        width: {Integer},
                        height: {Integer}
                    }

             * @method getItemBounds
             * @protected
             */
            this.getItemBounds = function(root){
                var metrics = this.getIM(root),
                    toggle  = this.getToggleBounds(root),
                    image   = this.getIconBounds(root);

                toggle.x = image.x + image.width + (image.width > 0 || toggle.width > 0 ? this.gapx : 0);
                toggle.y = metrics.y + Math.floor((metrics.height - metrics.viewHeight) / 2);
                toggle.width = metrics.viewWidth;
                toggle.height = metrics.viewHeight;
                return toggle;
            };

            /**
             * Get toggle element bounds for the given tree model item.
             * @param  {zebkit.data.Item} root an tree model item
             * @return {Object} a structure that keeps an item toggle location
             * and size:
             *
             *     {
             *         x: {Integer},
             *         y: {Integer},
             *         width: {Integer},
             *         height: {Integer}
             *     }
             *
             * @method getToggleBounds
             * @protected
             */
            this.getToggleBounds = function(root){
                var node = this.getIM(root), d = this.getToggleSize(root);
                return { x     : node.x,
                         y     : node.y + Math.floor((node.height - d.height) / 2),
                         width : d.width,
                         height: d.height };
            };

            /**
             * Get current toggle element view. The view depends on the state of tree item.
             * @param  {zebkit.data.Item} i a tree model item
             * @protected
             * @return {zebkit.draw.View}  a toggle element view
             * @method getToogleView
             */
            this.getToggleView = function(i){
                var v = i.kids.length > 0 ? (this.getIM(i).isOpen ? this.views.expandedToggle
                                                                  : this.views.collapsedToggle) : null;

                return (v === undefined ? null : v);
            };

            /**
             * An abstract method that a concrete tree component implementations have to
             * override. The method has to return a preferred size the given tree model
             * item wants to have.
             * @param  {zebkit.data.Item} root an tree model item
             * @return {Object} a structure that keeps an item preferred size:
             *
             *     {
             *          width: {Integer},
             *          height: {Integer}
             *     }
             *
             * @method getItemPreferredSize
             * @protected
             */
            this.getItemPreferredSize = function(root) {
                throw new Error("Not implemented");
            };

            /**
             * An abstract method that a concrete tree component implementations should
             * override. The method has to render the given tree node of the specified
             * tree model item at the given location
             * @param  {CanvasRenderingContext2D} g a graphical context
             * @param  {zebkit.data.Item} root a tree model item to be rendered
             * @param  {zebkit.ui.tree.ItemMetric} node a tree node metrics
             * @param  {Ineteger} x a x location where the tree node has to be rendered
             * @param  {Ineteger} y a y location where the tree node has to be rendered
             * @method paintItem
             * @protected
             */
            this.$recalc = function (x,y,parent,root,isVis){
                var node = this.getIM(root);
                if (isVis === true) {
                    if (node.viewWidth < 0) {
                        var viewSize = this.getItemPreferredSize(root);
                        node.viewWidth  = viewSize.width;
                        node.viewHeight = viewSize.height;
                    }

                    var imageSize = this.getIconSize(root),
                        toggleSize = this.getToggleSize(root);

                    if (parent !== null){
                        var pImg = this.getIconBounds(parent);
                        x = pImg.x + Math.floor((pImg.width - toggleSize.width) / 2);
                    }

                    node.x = x;
                    node.y = y;
                    node.width = toggleSize.width + imageSize.width +
                                 node.viewWidth + (toggleSize.width > 0 ? this.gapx : 0) + 10 +
                                                  (imageSize.width  > 0 ? this.gapx : 0);

                    node.height = Math.max(((toggleSize.height > imageSize.height) ? toggleSize.height
                                                                                   : imageSize.height),
                                            node.viewHeight);

                    if (node.x + node.width > this.maxw) {
                        this.maxw = node.x + node.width;
                    }

                    this.maxh += (node.height + this.gapy);
                    x = node.x + toggleSize.width + (toggleSize.width > 0 ? this.gapx : 0);
                    y += (node.height + this.gapy);
                }

                var b = node.isOpen && isVis === true;
                if (b) {
                    var count = root.kids.length;
                    for(var i = 0; i < count; i++) {
                        y = this.$recalc(x, y, root, root.kids[i], b);
                    }
                }
                return y;
            };

            this.$isOpen = function(i) {
                return i === null || (i.kids.length > 0 && this.getIM(i).isOpen && this.$isOpen(i.parent));
            };

            /**
             * Get a tree node metrics by the given tree model item.
             * @param  {zebkit.data.Item} item a tree model item
             * @return {zebkit.ui.tree.ItemMetric} a tree node metrics
             * @protected
             * @method getIM
             */
            this.getIM = function (item) {
                if (this.nodes.hasOwnProperty(item.$hash$) === false){
                    var node = new pkg.ItemMetric(this.isOpenVal);
                    this.nodes[item.$hash$] = node;
                    return node;
                }
                return this.nodes[item.$hash$];
            };

            /**
             * Get a tree item that is located at the given location.
             * @param  {zebkit.data.Item} [root] a starting tree node
             * @param  {Integer} x a x coordinate
             * @param  {Integer} y a y coordinate
             * @return {zebkit.data.Item} a tree model item
             * @method getItemAt
             */
            this.getItemAt = function(root, x, y){
                this.validate();

                if (arguments.length < 3) {
                    x = arguments[0];
                    y = arguments[1];
                    root = this.model.root;
                }

                if (this.firstVisible !== null && y >= this.visibleArea.y && y < this.visibleArea.y + this.visibleArea.height){
                    var dx    = this.scrollManager.getSX(),
                        dy    = this.scrollManager.getSY(),
                        found = this.getItemAtInBranch(root, x - dx, y - dy);

                    if (found !== null) {
                        return found;
                    }

                    var parent = root.parent;
                    while (parent !== null) {
                        var count = parent.kids.length;
                        for(var i = parent.kids.indexOf(root) + 1;i < count; i ++ ){
                            found = this.getItemAtInBranch(parent.kids[i], x - dx, y - dy);
                            if (found !== null) {
                                return found;
                            }
                        }
                        root = parent;
                        parent = root.parent;
                    }
                }
                return null;
            };

            this.getItemAtInBranch = function(root,x,y){
                if (root !== null) {
                    var node = this.getIM(root);
                    if (x >= node.x && y >= node.y && x < node.x + node.width && y < node.y + node.height + this.gapy) {
                        return root;
                    }

                    if (this.$isOpen(root)) {
                        for(var i = 0;i < root.kids.length; i++) {
                            var res = this.getItemAtInBranch(root.kids[i], x, y);
                            if (res !== null) {
                                return res;
                            }
                        }
                    }
                }
                return null;
            };

            this.getIconView = function (i){
                return i.kids.length > 0 ? (this.getIM(i).isOpen ? this.views.expandedSign
                                                                 : this.views.collapsedSign)
                                         : this.views.leafSign;
            };

            this.getIconSize = function (i) {
                return i.kids.length > 0 ? (this.getIM(i).isOpen ? this.viewSizes.expandedSign
                                                                 : this.viewSizes.collapsedSign)
                                          : this.viewSizes.leafSign;
            };

            /**
             * Get icon element bounds for the given tree model item.
             * @param  {zebkit.data.Item} root an tree model item
             * @return {Object} a structure that keeps an item icon location
             * and size:
             *
             *     {
             *         x: {Integer},
             *         y: {Integer},
             *         width: {Integer},
             *         height: {Integer}
             *     }
             *
             * @method getToggleBounds
             * @protected
             */
            this.getIconBounds = function(root) {
                var node = this.getIM(root),
                    id   = this.getIconSize(root),
                    td   = this.getToggleSize(root);
                return { x:node.x + td.width + (td.width > 0 ? this.gapx : 0),
                         y:node.y + Math.floor((node.height - id.height) / 2),
                         width:id.width, height:id.height };
            };

            this.getToggleSize = function(i) {
                return this.$isOpen(i) ? this.viewSizes.expandedToggle
                                       : this.viewSizes.collapsedToggle;
            };

            this.isOverVisibleArea = function (i) {
                var node = this.getIM(i);
                return node.y + node.height + this.scrollManager.getSY() < this.visibleArea.y;
            };

            this.findOpened = function(item) {
                var parent = item.parent;
                return (parent === null || this.$isOpen(parent)) ? item : this.findOpened(parent);
            };

            this.findNext = function(item) {
                if (item !== null){
                    if (item.kids.length > 0 && this.$isOpen(item)){
                        return item.kids[0];
                    }
                    var parent = null;
                    while ((parent = item.parent) !== null){
                        var index = parent.kids.indexOf(item);
                        if (index + 1 < parent.kids.length) {
                            return parent.kids[index + 1];
                        }
                        item = parent;
                    }
                }
                return null;
            };

            this.findPrev = function (item){
                if (item !== null) {
                    var parent = item.parent;
                    if (parent !== null) {
                        var index = parent.kids.indexOf(item);
                        return (index - 1 >= 0) ? this.findLast(parent.kids[index - 1]) : parent;
                    }
                }
                return null;
            };

            this.findLast = function (item){
                return this.$isOpen(item) && item.kids.length > 0 ? this.findLast(item.kids[item.kids.length - 1])
                                                                  : item;
            };

            this.prevVisible = function (item){
                if (item === null || this.isOverVisibleArea(item)) {
                    return this.nextVisible(item);
                }

                var parent = null;
                while((parent = item.parent) !== null){
                    for(var i = parent.kids.indexOf(item) - 1;i >= 0; i-- ){
                        var child = parent.kids[i];
                        if (this.isOverVisibleArea(child)) {
                            return this.nextVisible(child);
                        }
                    }
                    item = parent;
                }
                return item;
            };

            this.isVerVisible = function (item){
                if (this.visibleArea === null) {
                    return false;
                }

                var node = this.getIM(item),
                    yy1  = node.y + this.scrollManager.getSY(),
                    yy2  = yy1 + node.height - 1,
                    by   = this.visibleArea.y + this.visibleArea.height;

                return ((this.visibleArea.y <= yy1 && yy1 < by) ||
                        (this.visibleArea.y <= yy2 && yy2 < by) ||
                        (this.visibleArea.y > yy1 && yy2 >= by)    );
            };

            this.nextVisible = function(item){
                if (item === null || this.isVerVisible(item) === true) {
                    return item;
                }

                var res = this.nextVisibleInBranch(item), parent = null;
                if (res !== null) {
                    return res;
                }

                while ((parent = item.parent) !== null){
                    var count = parent.kids.length;
                    for(var i = parent.kids.indexOf(item) + 1;i < count; i++){
                        res = this.nextVisibleInBranch(parent.kids[i]);
                        if (res !== null) {
                            return res;
                        }
                    }
                    item = parent;
                }
                return null;
            };

            this.nextVisibleInBranch = function (item){
                if (this.isVerVisible(item)) {
                    return item;
                }

                if (this.$isOpen(item)){
                    for(var i = 0;i < item.kids.length; i++){
                        var res = this.nextVisibleInBranch(item.kids[i]);
                        if (res !== null) {
                            return res;
                        }
                    }
                }
                return null;
            };

            this.paintSelectedItem = function(g, root, node, x, y) {
                var v = this.hasFocus() ? this.views.focusOnSelect
                                        : this.views.focusOffSelect;
                if (v !== null && v !== undefined) {
                    v.paint(g, x, y, node.viewWidth, node.viewHeight, this);
                }
            };

            this.paintTree = function (g,item){
                this.paintBranch(g, item);
                var parent = null;
                while( (parent = item.parent) !== null){
                    this.paintChild(g, parent, parent.kids.indexOf(item) + 1);
                    item = parent;
                }
            };

            this.paintBranch = function (g, root){
                if (root === null) {
                    return false;
                }

                var node = this.getIM(root),
                    dx   = this.scrollManager.getSX(),
                    dy   = this.scrollManager.getSY();

                if (zebkit.util.isIntersect(node.x + dx, node.y + dy,
                                           node.width, node.height,
                                           this.visibleArea.x, this.visibleArea.y,
                                           this.visibleArea.width, this.visibleArea.height))
                {
                    var toggle     = this.getToggleBounds(root),
                        toggleView = this.getToggleView(root),
                        image      = this.getIconBounds(root),
                        vx         = image.x + image.width + this.gapx,
                        vy         = node.y + Math.floor((node.height - node.viewHeight) / 2);

                    if (toggleView !== null) {
                        toggleView.paint(g, toggle.x, toggle.y, toggle.width, toggle.height, this);
                    }

                    if (image.width > 0) {
                        this.getIconView(root).paint(g, image.x, image.y,
                                                     image.width, image.height, this);
                    }

                    if (this.selected === root){
                        this.paintSelectedItem(g, root, node, vx, vy);
                    }

                    if (this.paintItem !== undefined) {
                        this.paintItem(g, root, node, vx, vy);
                    }

                    if (this.lnColor !== null){
                        g.setColor(this.lnColor);
                        var yy = toggle.y + Math.floor(toggle.height / 2) + 0.5;

                        g.beginPath();
                        g.moveTo(toggle.x + (toggleView === null ? Math.floor(toggle.width / 2)
                                                                 : toggle.width - 1), yy);
                        g.lineTo(image.x, yy);
                        g.stroke();
                    }
                } else {
                    if (node.y + dy > this.visibleArea.y + this.visibleArea.height ||
                        node.x + dx > this.visibleArea.x + this.visibleArea.width    )
                    {
                        return false;
                    }
                }
                return this.paintChild(g, root, 0);
            };

            this.$y = function (item, isStart){
                var node = this.getIM(item),
                    th = this.getToggleSize(item).height,
                    ty = node.y + Math.floor((node.height - th) / 2),
                    dy = this.scrollManager.getSY(),
                    y  = (item.kids.length > 0) ? (isStart ? ty + th : ty - 1)
                                                : ty + Math.floor(th / 2);

                return (y + dy < 0) ?  -dy - 1
                                    : ((y + dy > this.height) ? this.height - dy : y);
            };

            /**
             * Paint children items of the given root tree item.
             * @param  {CanvasRenderingContext2D} g a graphical context
             * @param  {zebkit.data.Item} root a root tree item
             * @param  {Integer} index an index
             * @return {Boolean}
             * @protected
             * @method paintChild
             */
            this.paintChild = function (g, root, index){
                var b = this.$isOpen(root);
                if (root === this.firstVisible && this.lnColor !== null) {
                    g.setColor(this.lnColor);
                    var xx = this.getIM(root).x + Math.floor((b ? this.viewSizes.expandedToggle.width
                                                                : this.viewSizes.collapsedToggle.width) / 2);
                    g.beginPath();
                    g.moveTo(xx + 0.5, this.getTop());
                    g.lineTo(xx + 0.5, this.$y(root, false));
                    g.stroke();
                }
                if (b === true && root.kids.length > 0){
                    var firstChild = root.kids.length > 0 ?root.kids[0] : null;
                    if (firstChild === null) {
                        return true;
                    }

                    var x = this.getIM(firstChild).x + Math.floor((this.$isOpen(firstChild) ? this.viewSizes.expandedToggle.width
                                                                                            : this.viewSizes.collapsedToggle.width) / 2),
                    count = root.kids.length;
                    if (index < count) {
                        var  node = this.getIM(root),
                             y    = (index > 0) ? this.$y(root.kids[index - 1], true)
                                                : node.y + Math.floor((node.height + this.getIconSize(root).height) / 2);

                        for(var i = index;i < count; i++ ) {
                            var child = root.kids[i];
                            if (this.lnColor !== null){
                                g.setColor(this.lnColor);
                                g.beginPath();
                                g.moveTo(x + 0.5, y);
                                g.lineTo(x + 0.5, this.$y(child, false));
                                g.stroke();
                                y = this.$y(child, true);
                            }
                            if (this.paintBranch(g, child) === false){
                                if (this.lnColor !== null && i + 1 !== count){
                                    g.setColor(this.lnColor);
                                    g.beginPath();
                                    g.moveTo(x + 0.5, y);
                                    g.lineTo(x + 0.5, this.height - this.scrollManager.getSY());
                                    g.stroke();
                                }
                                return false;
                            }
                        }
                    }
                }
                return true;
            };

            this.nextPage = function (item,dir){
                var sum = 0, prev = item;
                while (item !== null && sum < this.visibleArea.height){
                    sum += (this.getIM(item).height + this.gapy);
                    prev = item;
                    item = dir < 0 ? this.findPrev(item) : this.findNext(item);
                }
                return prev;
            };

            this.paint = function(g){
                if (this.model !== null){
                    this.vVisibility();
                    if (this.firstVisible !== null){
                        var sx = this.scrollManager.getSX(), sy = this.scrollManager.getSY();
                        try {
                            g.translate(sx, sy);
                            this.paintTree(g, this.firstVisible);
                            g.translate(-sx,  -sy);
                        } catch(e) {
                            g.translate(-sx,  -sy);
                            throw e;
                        }
                    }
                }
            };

            /**
             * Select the given item.
             * @param  {zebkit.data.Item} item an item to be selected. Use null value to clear
             * any selection
             * @method  select
             */
            this.select = function(item){
                if (this.isSelectable === true && this.selected !== item){
                    var old = this.selected,
                        m    = null;

                    this.selected = item;
                    if (this.selected !== null) {
                        this.makeVisible(this.selected);
                    }

                    this.fire("selected", [ this, old ]);

                    if (old !== null && this.isVerVisible(old)) {
                        m = this.getItemMetrics(old);
                        this.repaint(m.x + this.scrollManager.getSX(),
                                     m.y + this.scrollManager.getSY(),
                                     m.width, m.height);
                    }

                    if (this.selected !== null && this.isVerVisible(this.selected)) {
                        m = this.getItemMetrics(this.selected);
                        this.repaint(m.x + this.scrollManager.getSX(),
                                     m.y + this.scrollManager.getSY(),
                                     m.width, m.height);
                    }
                }
            };

            /**
             * Make the given tree item visible. Tree component rendered content can takes more space than
             * the UI component size is. In this case the content can be scrolled to make visible required
             * tree item.
             * @param  {zebkit.data.Item} item an item to be visible
             * @method makeVisible
             */
            this.makeVisible = function(item){
                this.validate();
                var r = this.getItemBounds(item);
                this.scrollManager.makeVisible(r.x, r.y, r.width, r.height);
            };

            /**
             * Toggle off or on recursively all items of the given item
             * @param  {zebkit.data.Item} root a starting item to toggle
             * @param  {Boolean} b  true if all items have to be in opened
             * state and false otherwise
             * @method toggleAll
             * @chainable
             */
            this.toggleAll = function (root,b){
                if (root.kids.length > 0){
                    if (this.getItemMetrics(root).isOpen !== b) {
                        this.toggle(root);
                    }

                    for(var i = 0; i < root.kids.length; i++ ){
                        this.toggleAll(root.kids[i], b);
                    }
                }
                return this;
            };

            /**
             * Toggle the given tree item
             * @param  {zebkit.data.Item} item an item to be toggled
             * @method toggle
             * @chainable
             */
            this.toggle = function(item){
                if (item.kids.length > 0){
                    this.validate();
                    var node = this.getIM(item);
                    node.isOpen = (node.isOpen ? false : true);
                    this.invalidate();

                    this.fire("toggled", [this, item]);

                    if (!node.isOpen && this.selected !== null){
                        var parent = this.selected;
                        do {
                            parent = parent.parent;
                        } while (parent !== item && parent !== null);

                        if (parent === item) {
                            this.select(item);
                        }
                    }

                    this.repaint();
                }
                return this;
            };

            this.itemInserted = function (model, item){
                this.vrp();
            };

            this.itemRemoved = function (model,item){
                if (item === this.firstVisible) {
                    this.firstVisible = null;
                }

                if (item === this.selected) {
                    this.select(null);
                }

                delete this.nodes[item];
                this.vrp();
            };

            this.itemModified = function (model, item, prevValue){
                var node = this.getIM(item);
                // invalidate an item metrics
                if (node !== null) {
                    node.viewWidth = -1;
                }
                this.vrp();
            };

            this.calcPreferredSize = function(target) {
                return this.model === null ? { width:0, height:0 }
                                           : { width:this.maxw, height:this.maxh };
            };

            /**
             * Say if items of the tree component should be selectable
             * @param {Boolean} b true is tree component items can be selected
             * @method setSelectable
             */
            this.setSelectable = function(b){
                if (this.isSelectable !== b){
                    if (b === false && this.selected !== null) {
                        this.select(null);
                    }
                    this.isSelectable = b;
                    this.repaint();
                }
                return this;
            };

            /**
             * Set tree component connector lines color
             * @param {String} c a color
             * @method setLineColor
             * @chainable
             */
            this.setLineColor = function (c){
                this.lnColor = c;
                this.repaint();
                return this;
            };

            /**
             * Set the given horizontal gaps between tree node graphical elements:
             * toggle, icon, item view
             * @param {Integer} gx horizontal gap
             * @param {Integer} gy vertical gap
             * @method setGaps
             * @chainable
             */
            this.setGaps = function(gx, gy){
                if (gx !== this.gapx || gy !== this.gapy){
                    this.gapx = gx;
                    this.gapy = gy;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set the given tree model to be visualized with the UI component.
             * @param {zebkit.data.TreeModel|Object} d a tree model
             * @method setModel
             * @chainable
             */
            this.setModel = function(d){
                if (this.model !== d) {
                    if (zebkit.instanceOf(d, zebkit.data.TreeModel) === false) {
                        d = new zebkit.data.TreeModel(d);
                    }

                    this.select(null);
                    if (this.model !== null) {
                        this.model.off(this);
                    }

                    this.model = d;
                    if (this.model !== null) {
                        this.model.on(this);
                    }

                    this.firstVisible = null;
                    delete this.nodes;
                    this.nodes = {};
                    this.vrp();
                }
                return this;
            };
        },

        function focused(){
            this.$super();
            if (this.selected !== null) {
                var m = this.getItemMetrics(this.selected);
                this.repaint(m.x + this.scrollManager.getSX(),
                             m.y + this.scrollManager.getSY(), m.width, m.height);
            }
        },

        /**
         * Set the number of views to customize rendering of different visual elements of the tree
         * UI component. The following decorative elements can be customized:
         *
         *   - **"collapsedSign"** - closed tree item icon view
         *   - **"expandedSign"**  - opened tree item icon view
         *   - **"leafSign"**  - leaf tree item icon view
         *   - **"expandedToggle"**    - toggle on view
         *   - **"collapsedToggle"**   - toggle off view
         *   - **"focusOffSelect"**   - a view to express an item selection when tree component doesn't hold focus
         *   - **"focusOnSelect"**   - a view to express an item selection when tree component holds focus
         *
         * For instance:

            // build tree UI component
            var tree = new zebkit.ui.tree.Tree({
                value: "Root",
                kids: [
                    "Item 1",
                    "Item 2"
                ]
            });

            // set " [x] " text render for toggle on and
            // " [o] " text render for toggle off tree elements
            tree.setViews({
                "expandedToggle" : new zebkit.draw.TextRender(" [x] "),
                "collapsedToggle": new zebkit.draw.TextRender(" [o] ")
            });

         * @param {Object} v dictionary of tree component decorative elements views
         * @method setViews
         * @chainable
         */
        function setViews(v) {
            // setting to 0 prevents exception when on/off view is not defined
            this.viewSizes.expandedToggle  = { width: 0, height : 0};
            this.viewSizes.collapsedToggle = { width: 0, height : 0};
            this.viewSizes.expandedSign    = { width: 0, height : 0};
            this.viewSizes.collapsedSign   = { width: 0, height : 0};
            this.viewSizes.leafSign        = { width: 0, height : 0};

            for(var k in v) {
                this.views[k] = zebkit.draw.$view(v[k]);
                if (this.viewSizes.hasOwnProperty(k) && this.views[k]) {
                    this.viewSizes[k] = this.views[k].getPreferredSize();
                }
            }

            this.vrp();
            return this;
        },

        function invalidate(){
            if (this.isValid === true){
                this._isVal = false;
            }
            this.$super();
        }
    ]).events("toggled", "selected", "editingStarted", "editingStopped");


    var ui = pkg.cd("..");

    /**
     * Tree UI component that visualizes a tree data model. The model itself can be passed as JavaScript
     * structure or as a instance of zebkit.data.TreeModel. Internally tree component keeps the model always
     * as zebkit.data.TreeModel class instance:

         var tree = new zebkit.ui.tree.Tree({
              value: "Root",
              kids : [  "Item 1", "Item 2"]
         });

     * or

         var model = new zebkit.data.TreeModel("Root");
         model.add(model.root, "Item 1");
         model.add(model.root, "Item 2");

         var tree = new zebkit.ui.tree.Tree(model);

     * Tree model rendering is fully customizable by defining an own views provider. Default views
     * provider renders tree model item as text. The tree node can be made editable by defining an
     * editor provider. By default tree modes are not editable.
     * @class  zebkit.ui.tree.Tree
     * @constructor
     * @extends zebkit.ui.tree.BaseTree
     * @param {Object|zebkit.data.TreeModel} [model] a tree data model passed as JavaScript
     * structure or as an instance
     * @param {Boolean} [b] the tree component items toggle state. true to have all items
     * in opened state.
     */
    pkg.Tree = Class(pkg.BaseTree, [
        function (d, b){
            if (arguments.length < 2) {
                b  = true;
            }

            this.setViewProvider(new pkg.DefViews());
            this.$super(d, b);
        },

        function $prototype() {
            this.itemGapY = 2;
            this.itemGapX = 4;

            /**
             * A tree model editor provider
             * @readOnly
             * @attribute editors
             * @default null
             * @type {zebkit.ui.tree.DefEditors}
             */
            this.editors = null;

            /**
             * A tree model items view provider
             * @readOnly
             * @attribute provider
             * @default an instance of zebkit.ui.tree.DefsViews
             * @type {zebkit.ui.tree.DefsViews}
             */
            this.provider = this.editedItem = this.pressedItem = null;

            this.setFont = function(f) {
                this.provider.setFont(f);
                this.vrp();
                return this;
            };

            this.childKeyPressed = function(e){
                if (e.code === "Escape") {
                    this.stopEditing(false);
                } else {
                    if (e.code === "Enter" &&
                           ((zebkit.instanceOf(e.source, ui.TextField) === false) ||
                            (zebkit.instanceOf(e.source.view.target, zebkit.data.SingleLineTxt))))
                    {
                        this.stopEditing(true);
                    }
                }
            };

            this.catchScrolled = function (psx, psy){
                if (this.kids.length > 0) {
                    this.stopEditing(false);
                }

                if (this.firstVisible === null) {
                    this.firstVisible = this.model.root;
                }
                this.firstVisible = (this.y < psy) ? this.nextVisible(this.firstVisible)
                                                   : this.prevVisible(this.firstVisible);
                this.repaint();
            };

            this.laidout = function() {
                this.vVisibility();
            };

            this.getItemPreferredSize = function(root) {
                var ps = this.provider.getView(this, root).getPreferredSize();
                ps.width  += this.itemGapX * 2;
                ps.height += this.itemGapY * 2;
                return ps;
            };

            this.paintItem = function(g, root, node, x, y) {
                if (root !== this.editedItem){
                    var v = this.provider.getView(this, root);
                    v.paint(g, x + this.itemGapX, y + this.itemGapY,
                            node.viewWidth, node.viewHeight, this);
                }
            };

            /**
             * Initiate the given item editing if the specified event matches condition
             * @param  {zebkit.data.Item} item an item to be edited
             * @param  {zebkit.Event} e an even that may trigger the item editing
             * @return {Boolean}  return true if an item editing process has been started,
             * false otherwise
             * @method  se
             * @private
             */
            this.se = function (item, e){
                if (item !== null){
                    this.stopEditing(true);
                    if (this.editors !== null && this.editors.shouldStartEdit(item, e)) {
                        this.startEditing(item);
                        return true;
                    }
                }
                return false;
            };

            this.pointerClicked = function(e){
                if (this.se(this.pressedItem, e)) {
                    this.pressedItem = null;
                }
            };

            this.pointerDoubleClicked = function(e) {
                if (this.se(this.pressedItem, e)) {
                    this.pressedItem = null;
                } else {
                    if (this.selected !== null &&
                        this.getItemAt(this.firstVisible, e.x, e.y) === this.selected)
                    {
                        this.toggle(this.selected);
                    }
                }
            };

            this.pointerReleased = function(e){
                if (this.se(this.pressedItem, e)) {
                    this.pressedItem = null;
                }
            };

            this.keyTyped = function(e){
                if (this.selected !== null){
                    switch(e.key) {
                        case '+': if (this.isOpen(this.selected) === false) {
                            this.toggle(this.selected);
                        } break;
                        case '-': if (this.isOpen(this.selected)) {
                            this.toggle(this.selected);
                        } break;
                    }
                }
            };

            this.keyPressed = function(e){
                var newSelection = null;
                switch(e.code) {
                    case "ArrowDown" :
                    case "ArrowRight": newSelection = this.findNext(this.selected); break;
                    case "ArrowUp"   :
                    case "ArrowLeft" : newSelection = this.findPrev(this.selected); break;
                    case "Home"      :
                        if (e.ctrlKey) {
                            this.select(this.model.root);
                        } break;
                    case "End"       :
                        if (e.ctrlKey) {
                            this.select(this.findLast(this.model.root));
                        } break;
                    case "PageDown"  :
                        if (this.selected !== null) {
                            this.select(this.nextPage(this.selected, 1));
                        } break;
                    case "PageUp"    :
                        if (this.selected !== null) {
                            this.select(this.nextPage(this.selected,  -1));
                        } break;
                    //!!!!case "Enter": if(this.selected !== null) this.toggle(this.selected);break;
                }
                if (newSelection !== null) {
                    this.select(newSelection);
                }
                this.se(this.selected, e);
            };

            /**
             * Start editing the given if an editor for the item has been defined.
             * @param  {zebkit.data.Item} item an item whose content has to be edited
             * @method startEditing
             * @protected
             */
            this.startEditing = function (item){
                this.stopEditing(true);
                if (this.editors !== null){
                    var editor = this.editors.getEditor(this, item);
                    if (editor !== null) {
                        this.editedItem = item;
                        var b  = this.getItemBounds(this.editedItem),
                            ps = editor.getPreferredSize();

                        editor.setBounds(b.x + this.scrollManager.getSX() + this.itemGapX,
                                         b.y - Math.floor((ps.height - b.height + 2 * this.itemGapY) / 2) +
                                         this.scrollManager.getSY() + this.itemGapY,
                                         ps.width, ps.height);

                        this.add(editor);
                        editor.requestFocus();
                        this.fire("editingStarted", [this, item, editor]);
                    }
                }
            };

            /**
             * Stop editing currently edited tree item and apply or discard the result of the
             * editing to tree data model.
             * @param  {Boolean} true if the editing result has to be applied to tree data model
             * @method stopEditing
             * @protected
             */
            this.stopEditing = function(applyData){
                if (this.editors !== null && this.editedItem !== null) {
                    var item     = this.editedItem,
                        oldValue = item.value,
                        editor   = this.kids[0];

                    try {
                        if (applyData)  {
                            this.model.setValue(this.editedItem,
                                                this.editors.fetchEditedValue(this.editedItem, this.kids[0]));
                        }
                    } finally {
                        this.editedItem = null;
                        this.removeAt(0);
                        this.requestFocus();
                        this.fire("editingStopped", [this, item, oldValue, editor, applyData]);
                    }
                }
            };
        },

        function toggle(item) {
            this.stopEditing(false);
            this.$super(item);
            return this;
        },

        function itemInserted(target,item){
            this.stopEditing(false);
            this.$super(target,item);
        },

        function itemRemoved(target,item){
            this.stopEditing(false);
            this.$super(target,item);
        },

        /**
         * Set the given editor provider. The editor provider is a class that is used to decide which UI
         * component has to be used as an item editor, how the editing should be triggered and how the
         * edited value has to be fetched from an UI editor.
         * @param {zebkit.ui.tree.DefEditors} p an editor provider
         * @method setEditorProvider
         */
        function setEditorProvider(p){
            if (p != this.editors){
                this.stopEditing(false);
                this.editors = p;
            }
            return this;
        },

        /**
         * Set tree component items view provider. Provider says how tree model items
         * have to be visualized.
         * @param {zebkit.ui.tree.DefViews} p a view provider
         * @method setViewProvider
         * @chainable
         */
        function setViewProvider(p){
            if (this.provider != p) {
                this.stopEditing(false);
                this.provider = p;
                delete this.nodes;
                this.nodes = {};
                this.vrp();
            }
            return this;
        },

        /**
         * Set the given tree model to be visualized with the UI component.
         * @param {zebkit.data.TreeModel|Object} d a tree model
         * @method setModel
         * @chainable
         */
        function setModel(d){
            this.stopEditing(false);
            this.$super(d);
            return this;
        },

        function paintSelectedItem(g, root, node, x, y) {
            if (root !== this.editedItem) {
                this.$super(g, root, node, x, y);
            }
        },

        function itemPressed(root, e) {
            this.$super(root, e);
            if (this.se(root, e) === false) {
                this.pressedItem = root;
            }
        },

        function pointerPressed(e){
            this.pressedItem = null;
            this.stopEditing(true);
            this.$super(e);
        }
    ]);


    var ui = pkg.cd("..");

    /**
     * Component tree component that expects other UI components to be a tree model values.
     * In general the implementation lays out passed via tree model UI components as tree
     * component nodes. For instance:

         var tree = new zebkit.ui.tree.Tree({
              value: new zebkit.ui.Label("Label root item"),
              kids : [
                    new zebkit.ui.Checkbox("Checkbox Item"),
                    new zebkit.ui.Button("Button item"),
                    new zebkit.ui.Combo(["Combo item 1", "Combo item 2"])
             ]
         });

     * But to prevent unexpected navigation it is better to use number of predefined
     * with component tree UI components:
     *
     *   - zebkit.ui.tree.CompTree.Label
     *   - zebkit.ui.tree.CompTree.Checkbox
     *   - zebkit.ui.tree.CompTree.Combo
     *
     * You can describe tree model keeping in mind special notation

         var tree = new zebkit.ui.tree.Tree({
              value: "Label root item",  // zebkit.ui.tree.CompTree.Label
              kids : [
                    "[ ] Checkbox Item 1", // unchecked zebkit.ui.tree.CompTree.Checkbox
                    "[x] Checkbox Item 2", // checked zebkit.ui.tree.CompTree.Checkbox
                    ["Combo item 1", "Combo item 2"] // zebkit.ui.tree.CompTree.Combo
             ]
         });

     *
     * @class  zebkit.ui.tree.CompTree
     * @constructor
     * @extends zebkit.ui.tree.BaseTree
     * @param {Object|zebkit.data.TreeModel} [model] a tree data model passed as JavaScript
     * structure or as an instance
     * @param {Boolean} [b] the tree component items toggle state. true to have all items
     * in opened state.
     */
    pkg.CompTree = Class(pkg.BaseTree, [
        function $clazz() {
            this.Label = Class(ui.Label, [
                function $prototype() {
                    this.canHaveFocus = true;
                }
            ]);

            this.Checkbox = Class(ui.Checkbox, []);

            this.Combo = Class(ui.Combo, [
                function keyPressed(e) {
                    if (e.code !== "ArrowUp" && e.code !== "ArrowDown") {
                        this.$super(e);
                    }
                }
            ]);

            this.TextField = Class(ui.TextField, [
                // let's skip parent invalidation
                function invalidate() {
                    this.isValid  = false;
                    this.cachedWidth = -1;
                }
            ]);

            this.createModel = function(item, root, tree) {
                var mi = new zebkit.data.Item();

                if (item.value !== undefined) {
                    mi.value = item.value !== null ? item.value : "";
                } else {
                    mi.value = item;
                }

                mi.value = ui.$component(mi.value, tree);
                mi.parent = root;
                if (item.kids !== undefined && item.kids.length > 0 && zebkit.instanceOf(item, ui.Panel) === false) {
                    for (var i = 0; i < item.kids.length; i++) {
                        mi.kids[i] = this.createModel(item.kids[i], mi, tree);
                    }
                }

                return mi;
            };
        },

        function $prototype() {
            this.$blockCIE = false;
            this.canHaveFocus = false;

            this.getItemPreferredSize = function(root) {
                return root.value.getPreferredSize();
            };

            this.childKeyTyped = function(e) {
                if (this.selected !== null){
                    switch(e.key) {
                        case '+': if (this.isOpen(this.selected) === false) {
                            this.toggle(this.selected);
                        } break;
                        case '-': if (this.isOpen(this.selected)) {
                            this.toggle(this.selected);
                        } break;
                    }
                }
            };

            this.setFont = function(f) {
                this.font = zebkit.isString(f) ? new zebkit.Font(f) : f;
                return this;
            };

            this.childKeyPressed = function(e) {
                if (this.isSelectable === true) {
                    var newSelection = null;
                    if (e.code === "ArrowDown") {
                        newSelection = this.findNext(this.selected);
                    } else if (e.code === "ArrowUp") {
                        newSelection = this.findPrev(this.selected);
                    }

                    if (newSelection !== null) {
                        this.select(newSelection);
                    }
                }
            };

            this.childPointerPressed = this.childFocusGained = function(e) {
                if (this.isSelectable === true && this.$blockCIE !== true) {
                    this.$blockCIE = true;
                    try {
                        var item = zebkit.data.TreeModel.findOne(this.model.root,
                                                                zebkit.layout.getDirectChild(this,
                                                                                            e.source));
                        if (item !== null) {
                            this.select(item);
                        }
                    } finally {
                        this.$blockCIE = false;
                    }
                }
            };

            this.childFocusLost = function(e) {
                if (this.isSelectable === true) {
                    this.select(null);
                }
            };

            this.catchScrolled = function(psx, psy){
                this.vrp();
            };

            this.doLayout = function() {
                this.vVisibility();

                // hide all components
                for(var i = 0; i < this.kids.length; i++) {
                    this.kids[i].setVisible(false);
                }

                if (this.firstVisible !== null) {
                    var $this = this,
                        started = 0;

                    this.model.iterate(this.model.root, function(item) {
                        var node = $this.nodes[item];  // slightly improve performance
                                                       // (instead of calling $this.getIM(...))

                        if (started === 0 && item === $this.firstVisible) {
                            started = 1;
                        }

                        if (started === 1) {
                            var sy = $this.scrollManager.getSY();

                            if (node.y + sy < $this.height) {
                                var image = $this.getIconBounds(item),
                                    x = image.x + image.width +
                                               (image.width > 0 || $this.getToggleSize(item).width > 0 ? $this.gapx : 0) +
                                               $this.scrollManager.getSX(),
                                    y = node.y + Math.floor((node.height - node.viewHeight) / 2) + sy;

                                item.value.setVisible(true);
                                item.value.setLocation(x, y);
                                item.value.width  = node.viewWidth;
                                item.value.height = node.viewHeight;
                            } else {
                                started = 2;
                            }
                        }

                        return (started === 2) ? 2 : (node.isOpen === false ? 1 : 0);
                    });
                }
            };

            this.itemInserted = function(target, item) {
                this.add(item.value);
            };
        },

        function itemRemoved(target,item){
            this.$super(target,item);
            this.remove(item.value);
        },

        function setModel(model) {
            var old = this.model;

            if (model !== null && zebkit.instanceOf(model, zebkit.data.TreeModel) === false) {
                model = new zebkit.data.TreeModel(this.clazz.createModel(model, null, this));
            }

            this.$super(model);

            if (old !== this.model) {
                this.removeAll();
                if (this.model !== null) {
                    var $this = this;
                    this.model.iterate(this.model.root, function(item) {
                        $this.add(item.value);
                    });
                }
            }
            return this;
        },

        function recalc() {
            // track with the flag a node metrics has been updated
            this.$isMetricUpdated  = false;
            this.$super();

            // if a node size has been changed we have to force calling
            // repaint method for the whole tree component to render
            // tree lines properly
            if (this.$isMetricUpdated === true) {
                this.repaint();
            }
        },

        function recalc_(x,y,parent,root,isVis) {
            // in a case of component tree node view size has to be synced with
            // component
            var node = this.getIM(root);
            if (isVis === true) {
                var viewSize = this.getItemPreferredSize(root);
                if (this.$isMetricUpdated === false && (node.viewWidth  !== viewSize.width  ||
                                                        node.viewHeight !== viewSize.height  ))
                {
                    this.$isMetricUpdated = true;
                }

                node.viewWidth  = viewSize.width;
                node.viewHeight = viewSize.height;
            }
            return this.$super(x,y,parent,root,isVis);
        },

        function select(item) {
            if (this.isSelectable === true && item !== this.selected) {
                var old = this.selected;

                if (old !== null && old.value.hasFocus()) {
                    ui.focusManager.requestFocus(null);
                }

                this.$super(item);

                if (item !== null) {
                    item.value.requestFocus();
                }
            }
        },

        function makeVisible(item) {
           item.value.setVisible(true);
           this.$super(item);
        }
    ]);
},true);
zebkit.package("ui.grid", function(pkg, Class) {
    'use strict';
    var ui = pkg.cd("..");

    //      ---------------------------------------------------
    //      | x |    col0 width     | x |   col2 width    | x |
    //      .   .
    //    Line width
    //   -->.   .<--

    /**
     * The package contains number of classes and interfaces to implement
     * UI Grid component. The grid allows developers to visualize matrix
     * model, customize the model data editing and rendering.
     *
     *     // create grid that contains 3 rows and four columns
     *     var grid = new zebkit.ui.grid.Grid([
     *         [ "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" ],
     *         [ "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" ],
     *         [ "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" ].
     *     ]);
     *
     *     // add grid top caption
     *     grid.add("top", new zebkit.ui.grid.GridCaption([
     *         "Title 1",
     *         "Title 2",
     *         "Title 3",
     *         "Title 5",
     *         "Title 6"
     *     ]));
     *
     * @class zebkit.ui.grid
     * @access package
     */


    /**
     * Structure to keep grid cells visibility.
     * @constructor
     * @class zebkit.ui.grid.CellsVisibility
     */
    pkg.CellsVisibility = function() {
        this.hasVisibleCells = function(){
            return this.fr !== null && this.fc !== null &&
                   this.lr !== null && this.lc !== null   ;
        };

        /**
         * First visible row.
         * @attribute fr
         * @type {Integer}
         * @default null
         */

        /**
         * First visible column.
         * @attribute fc
         * @type {Integer}
         * @default null
         */

        /**
         * Last visible row.
         * @attribute lr
         * @type {Integer}
         * @default null
         */

        /**
         * Last visible column.
         * @attribute lc
         * @type {Integer}
         * @default null
         */

        // first visible row (row and y), first visible
        // col, last visible col and row
        this.fr = this.fc = this.lr = this.lc = null;

        // TODO: replace array with human readable variables
        // this.firstCol = -1
        // this.firstRow = -1
        // this.lastCol = -1
        // this.lastRow = -1

        // this.firstColX = 0
        // this.firstRowY = 0
        // this.lastColX = 0
        // this.lastRowY = 0
    };

    /**
     *  Interface that describes a grid component metrics
     *  @class zebkit.ui.grid.Metrics
     *  @interface zebkit.ui.grid.Metrics
     */
    pkg.Metrics = zebkit.Interface([
        "abstract",

            /**
             * Get a structure that describes a grid component
             * columns and rows visibility
             * @return {zebkit.ui.grid.CellsVisibility} a grid cells visibility
             * @method getCellsVisibility
             */
            function getCellsVisibility() {},

            /**
             * Get the given column width of a grid component
             * @param {Integer} col a column index
             * @method getColWidth
             * @return {Integer} a column width
             */
            function getColWidth(col) {},

            /**
             * Get the given row height of a grid component
             * @param {Integer} row a row index
             * @method getRowHeight
             * @return {Integer} a row height
             */
            function getRowHeight(row) {},

            /**
             * Get the given column preferred width of a grid component
             * @param {Integer} col a column index
             * @method getPSColWidth
             * @return {Integer} a column preferred width
             */
            function getPSColWidth(col) {},

            /**
             * Get the given row preferred height of a grid component
             * @param {Integer} row a row index
             * @method getPSRowHeight
             * @return {Integer} a row preferred height
             */
            function getPSRowHeight(row) {},

            /**
             * Set the given row height of a grid component
             * @param {Integer} row a row index
             * @param {Integer} height a row height
             * @method setRowHeight
             */
            function setRowHeight(row, height) {},

            /**
             * Set the given column width of a grid component
             * @param {Integer} col a column index
             * @param {Integer} width a column width
             * @method setColWidth
             */
            function setColWidth(col, width) {},

            /**
             * Get number of rows in a grid component
             * @return {Integer} a number of rows
             * @method getGridRows
             */
            function getGridRows() {},

            /**
             * Get number of columns in a grid component
             * @return {Integer} a number of columns
             * @method getGridCols
             */
            function getGridCols() {}
    ]);

     /**
      * Get a x origin of a grid component. Origin indicates how
      * the grid component content has been scrolled
      * @method getXOrigin
      * @return {Integer} a x origin
      */

    /**
      * Get a y origin of a grid component. Origin indicates how
      * the grid component content has been scrolled
      * @method getYOrigin
      * @return {Integer} a y origin
      */

      /**
       * Grid line size
       * @attribute lineSize
       * @type {Integer}
       * @readOnly
       */

      /**
       * Indicate if a grid sizes its rows and cols basing on its preferred sizes
       * @attribute isUsePsMetric
       * @type {Boolean}
       * @readOnly
       */

    /**
     * Default grid cell views provider. The class rules how a grid cell content,
     * background has to be rendered and aligned. Developers can implement an own
     * views providers and than setup it for a grid by calling "setViewProvider(...)"
     * method.
     * @param {zebkit.draw.Render} [render] a string render
     * @class zebkit.ui.grid.DefViews
     * @extends zebkit.draw.BaseViewProvider
     * @constructor
     */
    pkg.DefViews = Class(zebkit.draw.BaseViewProvider, [
        function $prototype() {
            /**
             * Default cell background
             * @attribute background
             * @type {String|zebkit.draw.View}
             * @default null
             */
            this.background = null;

            /**
             * Get a renderer to draw the specified grid model value.
             * @param  {zebkit.ui.grid.Grid} target a target Grid component
             * @param  {Integer} row  a grid cell row
             * @param  {Integer} col  a grid cell column
             * @param  {Object} obj   a model value for the given grid cell
             * @return {zebkit.draw.View}  an instance of  view to be used to
             * paint the given cell model value
             * @method  getView
             */

            /**
             * Get an horizontal alignment a content in the given grid cell
             * has to be adjusted. The method is optional.
             * @param  {zebkit.ui.grid.Grid} target a target grid component
             * @param  {Integer} row   a grid cell row
             * @param  {Integer} col   a grid cell column
             * @return {String}  a horizontal alignment ("left", "center", "right")
             * @method  getXAlignment
             */

             /**
              * Get a vertical alignment a content in the given grid cell
              * has to be adjusted. The method is optional.
              * @param  {zebkit.ui.grid.Grid} target a target grid component
              * @param  {Integer} row   a grid cell row
              * @param  {Integer} col   a grid cell column
              * @return {String}  a vertical alignment ("top", "center", "bottom")
              * @method  getYAlignment
              */

             /**
              * Get the given grid cell color
              * @param  {zebkit.ui.grid.Grid} target a target grid component
              * @param  {Integer} row   a grid cell row
              * @param  {Integer} col   a grid cell column
              * @return {String}  a cell color to be applied to the given grid cell
              * @method  getCellColor
              */
        }
    ]);

    /**
     * Stripped rows interface to extend a grid view provider.
     *
     *      var grid = new zebkit.ui.grid.Grid([ ... ]);
     *
     *      // Make grid rows stripped with blue and green colors
     *      grid.provider.extend(zebkit.ui.grid.StrippedRows({
     *          oddView : "blue",
     *          evenView: "green"
     *      }));
     *
     *
     * @class zebkit.ui.grid.StrippedRows
     * @interface zebkit.ui.grid.StrippedRows
     */
    pkg.StrippedRows = zebkit.Interface([
        function $prototype() {
            /**
             * Odd rows view or color
             * @attribute oddView
             * @type {String|zebkit.draw.View}
             */
            this.oddView  = null;

            /**
             * Even rows view or color
             * @attribute evenView
             * @type {String|zebkit.draw.View}
             */
            this.evenView = null;

            /**
             * Get a cell view.
             * @param  {zebkit.ui.grid.Grid} grid [description]
             * @param  {Integer} row  a cell row
             * @param  {Integer} col  a cell column
             * @return {String|zebkit.draw.View}  a color or view
             * @method getCellColor
             */
            this.getCellColor = function(grid, row, col) {
                return row % 2  === 0 ? this.evenView
                                      : this.oddView;
            };
        }
    ]);

    /**
     * Simple grid cells editors provider implementation. By default the editors provider
     * uses a text field component or check box component as a cell content editor. Check
     * box component is used if a cell data type is boolean, otherwise text filed is applied
     * as the cell editor.

            // grid with tree columns and three rows
            // first and last column will be editable with text field component
            // second column will be editable with check box component
            var grid = new zebkit.ui.grid.Grid([
                ["Text Cell", true, "Text cell"],
                ["Text Cell", false, "Text cell"],
                ["Text Cell", true, "Text cell"]
            ]);

            // make grid cell editable
            grid.setEditorProvider(new zebkit.ui.grid.DefEditors());


     * It is possible to customize a grid column editor by specifying setting "editors[col]" property
     * value. You can define an UI component that has to be applied as an editor for the given column
     * Also you can disable editing by setting appropriate column editor class to null:

            // grid with tree columns and three rows
            // first and last column will be editable with text field component
            // second column will be editable with check box component
            var grid = new zebkit.ui.grid.Grid([
                ["Text Cell", true, "Text cell"],
                ["Text Cell", false, "Text cell"],
                ["Text Cell", true, "Text cell"]
            ]);

            // grid cell editors provider
            var editorsProvider = new zebkit.ui.grid.DefEditors();

            // disable the first column editing
            editorsProvider.editors[0] = null;

            // make grid cell editable
            grid.setEditorProvider(editorsProvider);

     * @constructor
     * @class zebkit.ui.grid.DefEditors
     */
    pkg.DefEditors = Class([
        function() {
            this.textEditor     = new this.clazz.TextField("", 150);
            this.boolEditor     = new this.clazz.Checkbox(null);
            this.selectorEditor = new this.clazz.Combo();

            this.editors = {};
        },

        function $clazz() {
            this.TextField = Class(ui.TextField, []);
            this.Checkbox  = Class(ui.Checkbox,  []);
            this.Combo     = Class(ui.Combo, [
                function padShown(src, b) {
                    if (b === false) {
                        this.parent.stopEditing(true);
                        this.setSize(0,0);
                    }
                },

                function resized(pw, ph) {
                    this.$super(pw, ph);
                    if (this.width > 0 && this.height > 0 && this.hasFocus()) {
                        this.showPad();
                    }
                }
            ]);
        },

        function $prototype() {
            /**
             * Fetch an edited value from the given UI editor component.
             * @param  {zebkit.ui.grid.Grid} grid a target grid component
             * @param  {Integer} row a grid cell row that has been edited
             * @param  {Integer} col a grid cell column that has been edited
             * @param  {Object} data an original cell content
             * @param  {zebkit.ui.Panel} editor an editor that has been used to
             * edit the given cell
             * @return {Object} a value that can be applied as a new content of
             * the edited cell content
             * @method  fetchEditedValue
             */
            this.fetchEditedValue = function(grid, row, col, data, editor) {
                return editor.getValue();
            };

            /**
             * Get an editor UI component to be used for the given cell of the specified grid
             * @param  {zebkit.ui.grid.Grid} grid a grid whose cell is going to be edited
             * @param  {Integer} row  a grid cell row
             * @param  {Integer} col  a grid cell column
             * @param  {Object}  v    a grid cell model data
             * @return {zebkit.ui.Panel} an editor UI component to be used to edit the given cell
             * @method  getEditor
             */
            this.getEditor = function(grid, row, col, v) {
                var editor = null;
                if (this.editors.hasOwnProperty(col)) {
                    editor = this.editors[col];
                    if (editor !== null) {
                        editor.setValue(v);
                    }
                    return editor;
                } else {
                    editor = zebkit.isBoolean(v) ? this.boolEditor
                                                 : this.textEditor;

                    editor.setValue(v);
                    editor.setPadding(0);
                    var ah = Math.floor((grid.getRowHeight(row) - editor.getPreferredSize().height)/2);
                    editor.setPadding(ah, grid.cellInsetsLeft, ah, grid.cellInsetsRight);
                    return editor;
                }
            };

            /**
             * Test if the specified input event has to trigger the given grid cell editing
             * @param  {zebkit.ui.grid.Grid} grid a grid
             * @param  {Integer} row  a grid cell row
             * @param  {Integer} col  a grid cell column
             * @param  {zebkit.Event} e  an event to be evaluated
             * @return {Boolean} true if the given input event triggers the given cell editing
             * @method shouldStart
             */
            this.shouldStart = function(grid, row, col, e){
                return e.id === "pointerClicked";
            };

            /**
             * Test if the specified input event has to canceling the given grid cell editing
             * @param  {zebkit.ui.grid.Grid} grid a grid
             * @param  {Integer} row  a grid cell row
             * @param  {Integer} col  a grid cell column
             * @param  {zebkit.Event} e  an event to be evaluated
             * @return {Boolean} true if the given input event triggers the given cell editing
             * cancellation
             * @method shouldCancel
             */
            this.shouldCancel = function(grid,row,col,e){
                return e.id === "keyPressed" && "Escape" === e.code;
            };

            /**
             * Test if the specified input event has to trigger finishing the given grid cell editing
             * @param  {zebkit.ui.grid.Grid} grid [description]
             * @param  {Integer} row  a grid cell row
             * @param  {Integer} col  a grid cell column
             * @param  {zebkit.Event} e  an event to be evaluated
             * @return {Boolean} true if the given input event triggers finishing the given cell editing
             * @method shouldFinish
             */
            this.shouldFinish = function(grid,row,col,e){
                return e.id === "keyPressed" && "Enter" === e.code;
            };
        }
    ]);

    /**
     * Grid caption base UI component class. This class has to be used
     * as base to implement grid caption components
     * @class  zebkit.ui.grid.BaseCaption
     * @extends zebkit.ui.Panel
     * @uses zebkit.EventProducer
     * @constructor
     * @param {Array} [titles] a caption component titles
     */

    /**
     * Fire when a grid row selection state has been changed
     *
     *     caption.on("captionResized", function(caption, rowcol, phw) {
     *         ...
     *     });
     *
     * @event captionResized
     * @param  {zebkit.ui.grid.BaseCaption} caption a caption
     * @param  {Integer} rowcol a row or column that has been resized
     * @param  {Integer} pwh a previous row or column size
     */
    pkg.BaseCaption = Class(ui.Panel, [
        function(titles) {
            this.$super();

            if (arguments.length > 0) {
                for(var i = 0; i < titles.length; i++) {
                    this.setLabel(i, titles[i]);
                }
            }
        },

        function $prototype() {
            this.selectedColRow = -1;

            this.orient = this.metrics = this.pxy = null;

            /**
             * Minimal possible grid cell size
             * @type {Integer}
             * @default 10
             * @attribute minSize
             */
            this.minSize = 10;

            /**
             * Size of the active area where cells size can be changed by pointer dragging event
             * @attribute activeAreaSize
             * @type {Integer}
             * @default 5
             */
            this.activeAreaSize = 5;

            /**
             * Indicate if the grid cell size has to be adjusted according
             * to the cell preferred size by pointer double click event.
             * @attribute isAutoFit
             * @default true
             * @type {Boolean}
             */

            /**
             * Indicate if the grid cells are resize-able.
             * to the cell preferred size by pointer double click event.
             * @attribute isResizable
             * @default true
             * @type {Boolean}
             */
            this.isAutoFit = this.isResizable = true;

            this.getCursorType = function (target, x, y) {
                return this.metrics !== null    &&
                       this.selectedColRow >= 0 &&
                       this.isResizable         &&
                       this.metrics.isUsePsMetric === false ? ((this.orient === "horizontal") ? ui.Cursor.W_RESIZE
                                                                                              : ui.Cursor.S_RESIZE)
                                                            : null;
            };

            /**
             * Define pointer dragged events handler.
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerDragged
             */
            this.pointerDragged = function(e){
                if (this.pxy !== null) {
                    var b  = (this.orient === "horizontal"),
                        rc = this.selectedColRow,
                        ns = (b ? this.metrics.getColWidth(rc) + e.x
                                : this.metrics.getRowHeight(rc) + e.y) - this.pxy;

                    this.captionResized(rc, ns);

                    if (ns > this.minSize) {
                        this.pxy = b ? e.x : e.y;
                    }
                }
            };

            /**
             * Define pointer drag started events handler.
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerDragStarted
             */
            this.pointerDragStarted = function(e) {
                if (this.metrics !== null &&
                    this.isResizable      &&
                    this.metrics.isUsePsMetric === false)
                {
                    this.calcRowColAt(e.x, e.y);

                    if (this.selectedColRow >= 0) {
                        this.pxy = (this.orient === "horizontal") ? e.x
                                                                  : e.y;
                    }
                }
            };

            /**
             * Define pointer drag ended events handler.
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerDragEnded
             */
            this.pointerDragEnded = function (e){
                if (this.pxy !== null) {
                    this.pxy = null;
                }

                if (this.metrics !== null) {
                    this.calcRowColAt(e.x, e.y);
                }
            };

            /**
             * Define pointer moved events handler.
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerMoved
             */
            this.pointerMoved = function(e) {
                if (this.metrics !== null) {
                    this.calcRowColAt(e.x, e.y);
                }
            };

            this.pointerExited = function(e) {
                if (this.selectedColRow !== -1) {
                    this.selectedColRow = -1;
                    this.fire("captionResizeSelected", [this, this.selectedColRow] );
                }
            };

            /**
             * Define pointer clicked events handler.
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerClicked
             */
            this.pointerDoubleClicked = function(e) {
                if (this.pxy     === null    &&
                    this.metrics !== null    &&
                    this.selectedColRow >= 0 &&
                    this.isAutoFit === true     )
                {
                    var size = this.getCaptionPS(this.selectedColRow);
                    if (this.orient === "horizontal") {
                        this.metrics.setColWidth (this.selectedColRow, size);
                    } else {
                        this.metrics.setRowHeight(this.selectedColRow, size);
                    }
                    this.captionResized(this.selectedColRow, size);
                }
            };

            /**
             * Get the given row or column caption preferred size
             * @param  {Integer} rowcol a row or column of a caption
             * @return {Integer}  a size of row or column caption
             * @method getCaptionPS
             */
            this.getCaptionPS = function(rowcol) {
                return 0;
            };

            this.captionResized = function(rowcol, ns) {
                if (ns > this.minSize) {
                    if (this.orient === "horizontal") {
                        var pw = this.metrics.getColWidth(rowcol);
                        this.metrics.setColWidth(rowcol, ns);
                        this.fire("captionResized", [this, rowcol, pw]);
                    } else  {
                        var ph = this.metrics.getRowHeight(rowcol);
                        this.metrics.setRowHeight(rowcol, ns);
                        this.fire("captionResized", [this, rowcol, ph]);
                    }
                }
            };

            this.calcRowColAt = function(x, y) {
                var $this = this,
                    newSelected = this.getCaptionAt(x, y, function(m, xy, xxyy, wh, i) {
                        xxyy += (wh + Math.floor(m.lineSize / 2));
                        return (xy < xxyy + $this.activeAreaSize &&
                                xy > xxyy - $this.activeAreaSize   );

                    });

                if (newSelected !== this.selectedColRow) {
                    this.selectedColRow = newSelected;
                    this.fire("captionResizeSelected", [this, this.selectedColRow]);
                }
            };

            /**
             * Compute a column (for horizontal caption component) or row (for
             * vertically aligned caption component) at the given location
             * @param  {Integer} x a x coordinate
             * @param  {Integer} y an y coordinate
             * @param  {Function} [f] an optional match function. The method can be passed
             * if you need to detect a particular area of row or column. The method gets
             * a grid metrics as the first argument, a x or y location to be detected,
             * a row or column y or x coordinate, a row or column height or width and
             * row or column index. The method has to return true if the given location
             * is in.
             * @return {Integer}  a row or column
             * @method calcRowColAt
             */
            this.getCaptionAt = function(x,y,f) {
                if (this.metrics !== null &&
                    x >= 0                &&
                    y >= 0                &&
                    x < this.width        &&
                    y < this.height         )
                {
                    var m     = this.metrics,
                        cv    = m.getCellsVisibility(),
                        isHor = (this.orient === "horizontal");

                    if ((isHor && cv.fc !== null) ||
                        (isHor === false && cv.fr !== null))
                    {
                        var gap  = m.lineSize,
                            xy   = isHor ? x : y,
                            xxyy = isHor ? cv.fc[1] - this.x + m.getXOrigin()
                                         : cv.fr[1] - this.y + m.getYOrigin();

                        for (var i = (isHor ? cv.fc[0] : cv.fr[0]);i <= (isHor ? cv.lc[0] : cv.lr[0]); i++) {
                            var wh = isHor ? m.getColWidth(i)
                                           : m.getRowHeight(i);

                            if ((arguments.length > 2 && f(m, xy, xxyy, wh, i)) ||
                                (arguments.length < 3 && xy > xxyy && xy < xxyy + wh))
                            {
                                return i;

                            }
                            xxyy += wh + gap;
                        }
                    }
                }
                return -1;
            };

            /**
             * Set the grid caption labels
             * @param {Object} [labels]* labels
             * @method setLabels
             * @chainable
             */
            this.setLabels = function() {
                for (var i = 0; i < arguments.length; i++) {
                    this.setLabel(i, arguments[i]);
                }
                return this;
            };

            this.setLabel = function(i, lab) {
                return this;
            };

            /**
             * Implement the method to be aware when number of rows or columns in
             * a grid model has been updated
             * @param  {zebkit.ui.grid.Grid} target a target grid
             * @param  {Integer} prevRows a previous number of rows
             * @param  {Integer} prevCols a previous number of columns
             * @method matrixResized
             */

            /**
             * Implement the method to be aware when a grid model data has been
             * re-ordered.
             * @param  {zebkit.ui.grid.Grid} target a target grid
             * @param  {Object} sortInfo an order information
             * @method matrixSorted
             */
        },

        function setParent(p) {
            this.$super(p);

            this.metrics = this.orient = null;
            if (p === null || zebkit.instanceOf(p, pkg.Metrics)) {
                this.metrics = p;
                if (this.constraints !== null) {
                    this.orient = (this.constraints === "top"   ||
                                   this.constraints === "bottom"  ) ? "horizontal"
                                                                    : "vertical";
                }
            }
        }
    ]).events("captionResized", "captionResizeSelected");


    var ui = pkg.cd("..");

    /**
     * Caption cell render. This class can be used to customize grid caption
     * cells look and feel.
     * @param  {zebkit.draw.Render} a render to be used to draw grid caption cells.
     * @constructor
     * @class zebkit.ui.grid.CaptionViewProvider
     * @extends zebkit.ui.grid.DefViews
     */
    pkg.CaptionViewProvider = Class(pkg.DefViews, [
        function $prototype() {
            this.meta = null;

            this.$getCellMeta = function(rowcol) {
                if (this.meta === null) {
                    this.meta = {};
                }

                if (this.meta.hasOwnProperty(rowcol)) {
                    return this.meta[rowcol];
                } else {
                    this.meta[rowcol] = {
                        ax : null,
                        ay : null,
                        bg : null
                    };
                    return this.meta[rowcol];
                }
            };

            this.getXAlignment = function(target, rowcol) {
                return this.meta === null || this.meta.hasOwnProperty(rowcol) === false ? null
                                                                                        : this.meta[rowcol].ax;
            };

            this.getYAlignment = function(target, rowcol) {
                return this.meta === null || this.meta.hasOwnProperty(rowcol) === false ? null
                                                                                        : this.meta[rowcol].ay;
            };

            this.getCellBackground = function(target, rowcol) {
                return this.meta === null || this.meta.hasOwnProperty(rowcol) === false ? null
                                                                                        : this.meta[rowcol].bg;
            };

            this.setLabelAlignments = function(rowcol, ax, ay) {
                var m = this.$getCellMeta(rowcol);
                if (m.ax !== ax || m.ay !== ay) {
                    m.ax = ax;
                    m.ay = ay;
                    return true;
                } else {
                    return false;
                }
            };

            this.setCellBackground = function(rowcol, bg) {
                var m = this.$getCellMeta(rowcol);
                if (m.bg !== bg) {
                    m.bg = zebkit.draw.$view(bg);
                    return true;
                } else {
                    return false;
                }
            };
        }
    ]);

    /**
     * Grid caption class that implements rendered caption.
     * Rendered means all caption titles, border are painted
     * as a number of views.
     * @param  {Array} [titles] a caption titles. Title can be a string or
     * a zebkit.draw.View class instance
     * @param  {zebkit.draw.BaseTextRender} [render] a text render to be used
     * to paint grid titles
     * @constructor
     * @class zebkit.ui.grid.GridCaption
     * @extends zebkit.ui.grid.BaseCaption
     */
    pkg.GridCaption = Class(pkg.BaseCaption, [
        function(titles, render) {
            this.titles = {};

            this.setViewProvider(new pkg.CaptionViewProvider(render));

            if (arguments.length === 0) {
                this.$super();
            } else {
                this.$super(titles);
            }
        },

        function $prototype() {
            this.psW = this.psH = 0;

            /**
             * Grid caption view provider.
             * @attribute provider
             * @type {zebkit.ui.grid.CaptionViewProvider}
             * @readOnly
             */
            this.provider = null;

            /**
             * Default vertical cell view alignment.
             * @attribute defYAlignment
             * @type {String}
             * @default "center"
             */
            this.defYAlignment = "center";

            /**
             * Default horizontal cell view alignment.
             * @attribute defYAlignment
             * @type {String}
             * @default "center"
             */
            this.defXAlignment = "center";

            /**
             * Default cell background view.
             * @attribute defCellBg
             * @type {zebkit.draw.View}
             * @default null
             */
            this.defCellBg = null;

            /**
             * Set the given caption view provider.
             * @param {zebkit.ui.grid.CaptionViewProvider} p a caption view provider.
             * @method setViewProvider
             * @chainable
             */
            this.setViewProvider = function(p) {
                if (p !== this.provider) {
                    this.provider = p;
                    this.vrp();
                }
                return this;
            };

            /**
             * Get rendered caption cell object.
             * @param  {Ineteger} rowcol a row or column
             * @return {Object} a rendered caption cell object
             * @method getTitle
             */
            this.getTitle = function(rowcol) {
                return this.titles.hasOwnProperty(rowcol) ? this.titles[rowcol]
                                                          : null;
            };

            this.calcPreferredSize = function (l) {
                return { width:this.psW, height:this.psH };
            };

            this.setFont = function(f) {
                this.provider.setFont(f);
                this.vrp();
                return this;
            };

            this.setColor = function(c) {
                this.provider.setColor(c);
                this.repaint();
                return this;
            };

            this.recalc = function(){
                this.psW = this.psH = 0;
                if (this.metrics !== null){
                    var m     = this.metrics,
                        isHor = (this.orient === "horizontal"),
                        size  = isHor ? m.getGridCols() : m.getGridRows();

                    for (var i = 0;i < size; i++) {
                        var v = this.provider.getView(this, i, this.getTitle(i));
                        if (v !== null) {
                            var ps = v.getPreferredSize();
                            if (isHor === true) {
                                if (ps.height > this.psH) {
                                    this.psH = ps.height;
                                }
                                this.psW += ps.width;
                            } else {
                                if (ps.width > this.psW) {
                                    this.psW = ps.width;
                                }
                                this.psH += ps.height;
                            }
                        }
                    }

                    if (this.psH === 0) {
                        this.psH = pkg.Grid.DEF_ROWHEIGHT;
                    }

                    if (this.psW === 0) {
                        this.psW = pkg.Grid.DEF_COLWIDTH;
                    }


                    if (this.lineColor !== null) {
                        if (isHor) {
                            this.psH += this.metrics.lineSize;
                        } else {
                            this.psW += this.metrics.lineSize;
                        }
                    }
                }
            };

            /**
             * Put the given title for the given caption cell.
             * @param  {Integer} rowcol a grid caption cell index
             * @param  {String|zebkit.draw.View|zebkit.ui.Panel} title a title of the given
             * grid caption cell. Can be a string or zebkit.draw.View or zebkit.ui.Panel
             * class instance
             * @method setLabel
             * @chainable
             */
            this.setLabel = function(rowcol, value) {
                if (value === null) {
                    if (this.titles.hasOwnProperty(rowcol)) {
                        delete this.titles[rowcol];
                    }
                } else {
                    this.titles[rowcol] = value;
                }

                this.vrp();
                return this;
            };

            /**
             * Set the specified alignments of the given caption column or row.
             * @param {Integer} rowcol a row or column depending on the caption orientation
             * @param {String} xa a horizontal caption cell alignment. Use "left", "right" or
             * "center" as the title alignment value.
             * @param {String} ya a vertical caption cell alignment. Use "top", "bottom" or
             * "center" as the title alignment value.
             * @method setLabelAlignments
             * @chainable
             */
            this.setLabelAlignments = function(rowcol, xa, ya){
                if (this.provider.setLabelAlignments(rowcol, xa, ya)) {
                    this.repaint();
                }
                return this;
            };

            /**
             * Set the given caption cell background
             * @param {Integer} rowcol a caption cell row or column
             * @param {zebkit.draw.View|String} bg a color or view
             * @method setCellBackground
             * @chainable
             */
            this.setCellBackground = function(rowcol, bg) {
                if (this.provider.setCellBackground(rowcol, bg)) {
                    this.repaint();
                }
                return this;
            };

            /**
             * Get cell caption preferred size.
             * @param  {Integer} rowcol row or col of the cell depending the caption
             * orientation.
             * @return {Integer} a preferred width or height of the cell
             * @method getCaptionPS
             * @protected
             */
            this.getCaptionPS = function(rowcol) {
                var v = this.provider.getView(this, rowcol, this.getTitle(rowcol));
                return (v !== null) ? (this.orient === "horizontal" ? v.getPreferredSize().width
                                                                    : v.getPreferredSize().height)
                                    : 0;
            };

            this.paintOnTop = function(g) {
                if (this.metrics !== null) {
                    var cv = this.metrics.getCellsVisibility();

                    if ((cv.fc !== null && cv.lc !== null && this.orient === "horizontal")||
                        (cv.fr !== null && cv.lr !== null && this.orient === "vertical"  )   )
                    {
                        var isHor  = (this.orient === "horizontal"),
                            gap    = this.metrics.lineSize,
                            top    = this.getTop(),
                            left   = this.getLeft(),
                            bottom = this.getBottom(),
                            right  = this.getRight(),
                            x      = isHor ? cv.fc[1] - this.x + this.metrics.getXOrigin()// + gap
                                           : left, //left,
                            y      = isHor ? top    //top + (this.lineColor !== null ? this.metrics.lineSize : 0)
                                           : cv.fr[1] - this.y + this.metrics.getYOrigin(), // - gap,
                            size   = isHor ? this.metrics.getGridCols()
                                           : this.metrics.getGridRows();


                        //           top
                        //           >|<
                        //  +=========|===========================
                        //  ||        |
                        //  ||   +====|============+     +========
                        //  ||   ||   |            ||   ||
                        //  ||--------> left       ||   ||
                        //  ||   ||<-------------->||   ||
                        //  ||   ||       ww       ||   ||
                        //  ||   ||                ||   ||
                        // >-------< lineSize      ||   ||
                        //  ||   ||                ||   ||
                        //  x   first
                        //      visible

                        for(var i = (isHor ? cv.fc[0] : cv.fr[0]); i <= (isHor ? cv.lc[0] : cv.lr[0]); i++) {
                            var ww = isHor ? this.metrics.getColWidth(i)
                                           : this.width - left - right,
                                hh = isHor ? this.height - top - bottom
                                           : this.metrics.getRowHeight(i),
                                v = this.provider.getView(this, i, this.getTitle(i));

                            if (v !== null) {
                                var xa = this.provider.getXAlignment(this, i, v),
                                    ya = this.provider.getYAlignment(this, i, v),
                                    bg = this.provider.getCellBackground(this, i, v);

                                if (xa === null) {
                                    xa = this.defXAlignment;
                                }

                                if (ya === null) {
                                    ya = this.defYAlignment;
                                }

                                if (bg === null) {
                                    bg = this.defCellBg;
                                }

                                var ps = v.getPreferredSize(),
                                    vx = xa === "center" ? Math.floor((ww - ps.width)/2)
                                                         : (xa === "right" ? ww - ps.width - ((i === size - 1) ? right : 0)
                                                                           : (i === 0 ? left: 0)),
                                    vy = ya === "center" ? Math.floor((hh - ps.height)/2)
                                                         : (ya === "bottom" ? hh - ps.height - ((i === size - 1) ? bottom : 0)
                                                                            : (i === 0 ? top: 0));
                                if (bg !== null) {
                                    if (isHor) {
                                        bg.paint(g, x, 0, ww + gap , this.height, this);
                                    } else  {
                                        bg.paint(g, 0, y, this.width, hh + gap, this);
                                    }
                                }

                                g.save();

                                if (isHor) {
                                    g.clipRect(x, y, ww, hh);
                                    try {
                                        v.paint(g, x + vx, y + vy, ps.width, ps.height, this);
                                    } catch(e) {
                                        g.restore();
                                        throw e;
                                    }
                                 } else {
                                    g.clipRect(x, y, ww, hh);
                                    try {
                                        v.paint(g, x + vx, y + vy, ps.width, ps.height, this);
                                    } catch(e) {
                                        g.restore();
                                        throw e;
                                    }
                                }

                                g.restore();
                            }

                            if (isHor) {
                                x += ww + gap;
                            } else {
                                y += hh + gap;
                            }
                        }
                    }
                }
            };
        }
    ]);

    /**
     * Predefined left vertical grid caption.
     * @constructor
     * @class zebkit.ui.grid.LeftGridCaption
     * @extends zebkit.ui.grid.GridCaption
     */
    pkg.LeftGridCaption = Class(pkg.GridCaption, [
        function $prototype() {
            this.constraints = "left";
        }
    ]);


    var ui = pkg.cd("..");

    /**
     * Grid caption class that implements component based caption.
     * Component based caption uses other UI component as the
     * caption titles.
     * @param  {Array} a caption titles. Title can be a string or
     * a zebkit.ui.Panel class instance
     * @constructor
     * @class zebkit.ui.grid.CompGridCaption
     * @extends zebkit.ui.grid.BaseCaption
     */
    pkg.CompGridCaption = Class(pkg.BaseCaption, [
        function(titles) {
            if (arguments.length === 0) {
                this.$super();
            } else {
                this.$super(titles);
            }

            this.setLayout(new this.clazz.Layout());
        },

        function $clazz() {
            this.Layout = Class(zebkit.layout.Layout, [
                function $prototype() {
                    this.doLayout = function (target) {
                        var m    = target.metrics,
                            b    = target.orient === "horizontal",
                            top  = target.getTop(),
                            left = target.getLeft(),
                            wh   = (b ? target.height - top  - target.getBottom()
                                      : target.width  - left - target.getRight()),
                            xy   = (b ? left + m.getXOrigin()
                                      : top  + m.getYOrigin()) + m.lineSize;


                        for (var i = 0; i < target.kids.length; i++) {
                            var kid = target.kids[i],
                                cwh = (b ? m.getColWidth(i)
                                         : m.getRowHeight(i));

                            if (kid.isVisible === true) {
                                if (b) {
                                    kid.setBounds(xy, top, cwh, wh);
                                } else {
                                    kid.setBounds(left, xy, wh, cwh);
                                }
                            }

                            xy += (cwh + m.lineSize);
                        }
                    };

                    this.calcPreferredSize = function (target) {
                        return zebkit.layout.getMaxPreferredSize(target);
                    };
                }
            ]);

            this.Link = Class(ui.Link, []);

            this.StatusPan = Class(ui.StatePan, []);

            /**
             * Title panel that is designed to be used as CompGridCaption UI component title element.
             * The panel keeps a grid column or row title, a column or row sort indicator. Using the
             * component you can have sortable grid columns.
             * @constructor
             * @param {String} a grid column or row title
             * @class zebkit.ui.grid.CompGridCaption.TitlePan
             */
            var clazz = this;
            this.TitlePan = Class(ui.Panel, [
                function(content) {
                    this.$super();

                    /**
                     * Image panel to keep grid caption icon
                     * @attribute icon
                     * @type {zebkit.ui.ImagePan}
                     * @readOnly
                     */
                    this.icon = new ui.ImagePan(null);

                    /**
                     * Title content
                     * @attribute content
                     * @type {zebkit.ui.Panel}
                     * @readOnly
                     */
                    this.content   = zebkit.instanceOf(content, ui.Panel) ? content : new clazz.Link(content);
                    this.statusPan = new clazz.StatusPan();
                    this.statusPan.setVisible(this.isSortable);

                    this.add(this.icon);
                    this.add(this.content);
                    this.add(this.statusPan);
                },

                function $clazz() {
                    this.layout = new zebkit.layout.FlowLayout("center", "center", "horizontal", 8);
                },

                function $prototype() {
                    this.sortState = 0;

                    /**
                     * Indicates if the title panel has to initiate a column sorting
                     * @default false
                     * @attribute isSortable
                     * @readOnly
                     * @type {Boolean}
                     */
                    this.isSortable = false;
                },

                function getGridCaption() {
                    var c = this.parent;
                    while(c !== null && zebkit.instanceOf(c, pkg.BaseCaption) === false) {
                        c = c.parent;
                    }
                    return c;
                },

                function matrixSorted(target, info) {
                    if (this.isSortable) {
                        var col = this.parent.indexOf(this);
                        if (info.col === col) {
                            this.sortState = info.name === 'descent' ? 1 : -1;
                            this.statusPan.setState(info.name);
                        } else {
                            this.sortState = 0;
                            this.statusPan.setState("*");
                        }
                    }
                },

                /**
                 * Set the caption icon
                 * @param {String|Image} path a path to an image or image object
                 * @method setIcon
                 * @chainable
                 */
                function setIcon(path) {
                    this.icon.setImage(path);
                    return this;
                },

                function matrixResized(target, prevRows, prevCols){
                    if (this.isSortable) {
                        this.sortState = 0;
                        this.statusPan.setState("*");
                    }
                },

                function fired(target) {
                    if (this.isSortable === true) {
                        var f = this.sortState === 1 ? zebkit.data.ascent
                                                     : zebkit.data.descent,
                            model = this.getGridCaption().metrics.model,
                            col   = this.parent.indexOf(this);
                        model.sortCol(col, f);
                    }
                },

                function kidRemoved(index, kid, ctr) {
                    if (kid.isEventFired()) {
                        kid.off(this);
                    }
                    this.$super(index, kid, ctr);
                },

                function kidAdded(index, constr, kid) {
                    if (kid.isEventFired()) {
                        kid.on(this);
                    }
                    this.$super(index, constr, kid);
                }
            ]);
        },

        /**
         * @for zebkit.ui.grid.CompGridCaption
         */
        function $prototype() {
            this.catchInput = function(t) {
                return t.isEventFired() === false;
            };

            this.scrolled = function() {
                this.vrp();
            };

            /**
             * Put the given title component for the given caption cell.
             * @param  {Integer} rowcol a grid caption cell index
             * @param  {String|zebkit.ui.Panel|zebkit.draw.View} title a title of the given grid caption cell.
             * Can be a string or zebkit.draw.View or zebkit.ui.Panel class instance
             * @method setLabel
             * @chainable
             */
            this.setLabel = function(rowcol, t) {
                // add empty titles
                for(var i = this.kids.length - 1;  i >= 0 && i < rowcol; i++) {
                    this.add(new this.clazz.TitlePan(""));
                }

                if (zebkit.isString(t)) {
                    t = new this.clazz.TitlePan(t);
                } else if (zebkit.instanceOf(t, zebkit.draw.View)) {
                    var p = new ui.ViewPan();
                    p.setView(t);
                    t = p;
                }

                if (rowcol < this.kids.length) {
                    this.setAt(rowcol, t);
                } else {
                    this.add(t);
                }

                return this;
            };

            /**
             * Set the given column sortable state
             * @param {Integer} col a column
             * @param {Boolean} b true if the column has to be sortable
             * @method setSortable
             * @chainable
             */
            this.setSortable = function(col, b) {
                var c = this.kids[col];
                if (c.isSortable !== b) {
                    c.isSortable = b;
                    c.statusPan.setVisible(b);
                }
                return this;
            };

            this.matrixSorted = function(target, info) {
                for(var i = 0; i < this.kids.length; i++) {
                    if (this.kids[i].matrixSorted) {
                        this.kids[i].matrixSorted(target, info);
                    }
                }
            };

            this.matrixResized = function(target,prevRows,prevCols){
                for(var i = 0; i < this.kids.length; i++) {
                    if (this.kids[i].matrixResized) {
                        this.kids[i].matrixResized(target,prevRows,prevCols);
                    }
                }
            };

            this.getCaptionPS = function(rowcol) {
                return rowcol < this.kids.length ? (this.orient === "horizontal" ? this.kids[rowcol].getPreferredSize().width
                                                                                 : this.kids[rowcol].getPreferredSize().height)
                                                 : 0;
            };
        },

        function captionResized(rowcol, ns) {
            this.$super(rowcol, ns);
            this.vrp();
        },

        function setParent(p) {
            if (this.parent !== null && this.parent.scrollManager !== undefined && this.parent.scrollManager !== null) {
                this.parent.scrollManager.off(this);
            }

            if (p !== null && p.scrollManager !== undefined && p.scrollManager !== null) {
                p.scrollManager.on(this);
            }

            this.$super(p);
        },

        function insert(i, constr, c) {
            if (zebkit.isString(c)) {
                c = new this.clazz.TitlePan(c);
            }
            this.$super(i,constr, c);
        }
    ]);

    /**
     * Predefined left vertical component grid caption.
     * @constructor
     * @class zebkit.ui.grid.LeftCompGridCaption
     * @extends zebkit.ui.grid.CompGridCaption
     */
    pkg.LeftCompGridCaption = Class(pkg.CompGridCaption, [
        function $prototype() {
            this.constraints = "left";
        }
    ]);


    var ui = pkg.cd("..");


    //
    //  -- Grid should be responsible for rendering caption lines
    //  -- Grid layouts horizontal caption taking in account:
    //     -- Top gap
    //     -- Line size
    //  -- The same with vertical caption
    //
    //
    // ......................................... Grid .........................
    // .                     grid.getTop()                                    .
    // .    +------------------------------------------------------------+    .
    // .    |                 grid.lineSize                              |    .
    // .    ....+-------------------------+...+----------------------+...GridCaption
    // .    .   |                         |   |                      |   .    .
    // .    .   |     Caption Title 1     |   |    Caption Title 2   |   .    .
    // .    .   |                         |   |                      |   .    .
    // .    ....+-------------------------+...+----------------------+....    .
    // .    |                  grid.lineSize                             |    .
    // .    +   +-------------------------+   +----------------------+   |    .
    // .    |   |\__ (fc.x, fc.y)         |   |                      |   |    .
    // .    |   |   ...................   |   |                      |   |    .
    // .    |   |   .    cell view    .   |   |                      |   |    .
    // .    |   |   .                 .   |   |                      |   |    .
    // .    |   |   ...................   |   |                      |   |    .
    // .    |   |       cell insets       |   |                      |   |    .
    // .    |   +-------------------------+   +----------------------+   |    .
    // .    |                                                            |    .
    // .<---| grid.getLeft()                            grid.getRight()  |--->.
    //
    //


    /**
     * Class to that defines grid cell selection mode. This implementation
     * allows users single grid cell selection.
     * @constructor
     * @param  {zebkit.ui.grid.Grid} target a target grid the selection mode
     * instance class belongs
     * @class zebkit.ui.grid.CellSelectMode
     */
    pkg.CellSelectMode = Class([
        function(target) {
            this.target = target;
        },

        function $prototype() {
            /**
             * Target grid cell selection mode belongs
             * @attribute target
             * @type {zebkit.ui.grid.Grid}
             */
            this.target = null;

            this.selectedRow = -1;
            this.selectedCol = -1;
            this.prevSelectedRow = -1;
            this.prevSelectedCol = -1;

            /**
             *  Callback method that is called every time the select mode is
             *  attached to the given target grid component
             *  @param  {zebkit.ui.grid.Grid} target a target grid component
             *  @method  install
             */

            /**
             *  Callback method that is called every time the select mode is
             *  detached from the given target grid component
             *  @param  {zebkit.ui.grid.Grid} target a target grid component
             *  @method  uninstall
             */

            /**
             * Evaluates if the given cell is selected.
             * @param  {Integer}  row a cell row
             * @param  {Integer}  col a cell column
             * @return {Boolean} true if the given cell is selected
             * @method isSelected
             */
            this.isSelected = function(row, col) {
                return row >= 0 && row === this.selectedRow &&
                       col >= 0 && col === this.selectedCol;
            };

            /**
             * Callback method that is called every time a grid position
             * marker has been updated.
             * @param  {zebkit.util.Position} pos a position manager
             * @param  {Integer} prevOffset a previous position offset
             * @param  {Integer} prevLine  a previous position line
             * @param  {Integer} prevCol  a previous position column
             * @method posChanged
             */
            this.posChanged = function(pos, prevOffset, prevLine, prevCol) {
                this.prevSelectedRow = prevLine;
                this.prevSelectedCol = prevCol;
                this.target.select(pos.currentLine, pos.currentCol, true);
            };

            /**
             * Clear all selected cells
             * @chainable
             * @method clearSelect
             */
            this.clearSelect = function() {
                if (this.selectedRow >= 0 || this.selectedCol >= 0) {
                    var prevRow = this.selectedRow,
                        prevCol = this.selectedCol;
                    this.selectedCol = this.selectedRow = -1;
                    this.fireSelected(prevRow, prevCol, false);
                }
                return this;
            };

            /**
             * Select or de-select the given grid cell
             * @param  {Integer} row a row of selected or de-selected cell
             * @param  {Integer} col a column of selected or de-selected cell
             * @param  {Boolean} b a selection status
             * @chainable
             * @method select
             */
            this.select = function(row, col, b) {
                if (arguments.length === 2) {
                    b = true;
                }

                if (this.isSelected(row, col) !== b) {
                    this.clearSelect();

                    if (b) {
                        this.selectedRow = row;
                        this.selectedCol = col;
                        this.fireSelected(row, col, b);
                    }
                }
                return this;
            };

            /**
             * Fire selected or de-selected event.
             * @param  {Integer} row a selected or de-selected row
             * @param  {Integer} col a selected or de-selected column
             * @param  {Boolean} b   a state of selected cell
             * @method fireSelected
             * @protected
             */
            this.fireSelected = function(row, col, b) {
                this.target.fire("selected", [ this.target, row, col, b]);
                this.repaintTarget(row, col);
            };

            /**
             * Force cells repainting.
             * @param  {Integer} row a cell row
             * @param  {Integer} col a cell column
             * @method  repaintTarget
             * @protected
             */
            this.repaintTarget = function(row, col) {
                this.target.repaintCells(row, col);
            };
        }
    ]);

    /**
     * Row selection mode class. In this mode it is possible to select single
     * grid row.
     * @param  {zebkit.ui.grid.Grid} target a target grid the selection mode
     * instance class belongs
     * @extends zebkit.ui.grid.CellSelectMode
     * @class zebkit.ui.grid.RowSelectMode
     */
    pkg.RowSelectMode = Class(pkg.CellSelectMode, [
        function $prototype() {
            this.isSelected = function(row, col) {
                return row >= 0 && this.selectedRow === row;
            };

            this.repaintTarget = function(row, col) {
                this.target.repaintRows(row, row + 1);
            };
        }
    ]);

    /**
     * Column selection mode class. In this mode it is possible to select single
     * grid column.
     * @param  {zebkit.ui.grid.Grid} target a target grid the selection mode
     * instance class belongs
     * @extends zebkit.ui.grid.CellSelectMode
     * @class zebkit.ui.grid.ColSelectMode
     */
    pkg.ColSelectMode = Class(pkg.CellSelectMode, [
        function $prototype() {
            this.isSelected = function(row, col) {
                return col >= 0 && this.selectedCol === col;
            };

            this.repaintTarget = function(row, col) {
                this.target.repaintCols(col, col + 1);
            };
        }
    ]);


    /**
     * Grid UI component class. The grid component visualizes "zebkit.data.Matrix" data model.
     * Grid cell visualization can be customized by defining and setting an own view provider.
     * Grid component supports cell editing. Every existent UI component can be configured
     * as a cell editor by defining an own editor provider.
     *
     *
     *       // create a grid that contains three rows and tree columns
     *       var grid  = new zebkit.ui.grid.Grid([
     *           [ "Cell 1.1", "Cell 1.2", "Cell 1.3"],
     *           [ "Cell 2.1", "Cell 2.2", "Cell 2.3"],
     *           [ "Cell 3.1", "Cell 3.2", "Cell 3.3"]
     *       ]);
     *
     *       // add the top caption
     *       grid.add("top", new zebkit.ui.grid.GridCaption([
     *           "Caption title 1", "Caption title 2", "Caption title 3"
     *       ]));
     *
     *       // set rows size
     *       grid.setRowsHeight(45);
     *
     *
     * Grid can have top and left captions.
     * @class  zebkit.ui.grid.Grid
     * @constructor
     * @param {zebkit.data.Matrix|Array} [model] a matrix model to be visualized with the grid
     * component. It can be an instance of zebkit.data.Matrix class or an array that contains
     * embedded arrays. Every embedded array is a grid row.
     * @param {Integer} [rows]  a number of rows
     * @param {Integer} [columns] a number of columns
     * @extends zebkit.ui.Panel
     * @uses zebkit.ui.grid.Metrics
     * @uses zebkit.ui.HostDecorativeViews
     */

    /**
     * Fire when a grid row selection state has been changed
     *
     *       grid.on("selected", function(grid, row, col, status) {
     *           ...
     *       });
     *
     * @event selected
     * @param  {zebkit.ui.grid.Grid} grid a grid that triggers the event
     * @param  {Integer} row a selected row
     * @param  {Integer} col a selected column
     * @param {Boolean} status a selection status. true means rows have been selected
     */
    pkg.Grid = Class(ui.Panel, zebkit.util.Position.Metric, pkg.Metrics, ui.HostDecorativeViews, [
        function(model) {
            if (arguments.length === 0) {
                model = new this.clazz.Matrix(5, 5);
            } else if (arguments.length === 2) {
                model = new this.clazz.Matrix(arguments[0], arguments[1]);
            }

            this.setSelectMode("row");

            this.views = {};
            this.visibility = new pkg.CellsVisibility();
            this.$super();

            this.add("corner", new this.clazz.CornerPan());
            this.setModel(model);
            this.setViewProvider(new this.clazz.DefViews());
            this.setPosition(new zebkit.util.Position(this));
            this.scrollManager = new ui.ScrollManager(this);
        },

        function $clazz() {
            this.Matrix    = Class(zebkit.data.Matrix, []);
            this.DEF_COLWIDTH  = 80;
            this.DEF_ROWHEIGHT = 25;
            this.CornerPan = Class(ui.Panel, []);
            this.DefViews = Class(pkg.DefViews, []);
        },

        function $prototype() {
            this.psWidth_    = this.psHeight_  = this.colOffset = 0;
            this.rowOffset   = this.pressedCol = 0;
            this.visibleArea = null;

            /**
             * Scroll manager
             * @attribute scrollManager
             * @type {zebkit.ui.ScrollManager}
             * @protected
             * @readOnly
             */
            this.scrollManager = null;

            /**
             * Reference to top caption component
             * @attribute topCaption
             * @type {zebkit.ui.grid.GridCaption|zebkit.ui.grid.CompGridCaption}
             * @default null
             * @readOnly
             */
            this.topCaption = null;

            /**
             * Reference to left caption component
             * @attribute leftCaption
             * @type {zebkit.ui.grid.GridCaption|zebkit.ui.grid.CompGridCaption}
             * @default null
             * @readOnly
             */
             this.leftCaption = null;

            /**
             * Cell editors provider
             * @type {zebkit.ui.grid.DefEditors}
             * @attribute editors
             * @readOnly
             * @default null
             */
            this.editors = null;

            /**
             * Currently activated cell editor.
             * @type {zebkit.ui.Panel}
             * @attribute editor
             * @readOnly
             * @default null
             */
            this.editor = null;

            /**
             * Grid cell select mode
             * @attribute selectMode
             * @type {zebkit.ui.grid.SelectMode}
             * @readOnly
             * @default row
             */
            this.selectMode = null;

            /**
             * Calculated grid columns widths
             * @attribute colWidths
             * @type {Array}
             * @protected
             * @readOnly
             */
            this.colWidths = null;

            /**
             * Calculated grid columns heights
             * @attribute rowHeights
             * @type {Array}
             * @protected
             * @readOnly
             */
            this.rowHeights = null;


            this.position = this.stub = null;

            /**
             *  Grid model.
             *  @type {zebkit.data.Matrix}
             *  @attribute model
             */
            this.model = null;

            /**
             * Currently editing row. -1 if no row is editing
             * @attribute editingRow
             * @type {Integer}
             * @default -1
             * @readOnly
             */
             this.editingRow = -1;

            /**
             * Currently editing column. -1 if no column is editing
             * @attribute editingCol
             * @type {Integer}
             * @default -1
             * @readOnly
             */
            this.editingCol = this.pressedRow = -1;

            /**
             * Grid navigation mode
             * @attribute navigationMode
             * @default "row"
             * @type {String}
             */
            this.navigationMode = "row";

            /**
             * Grid line size
             * @attribute lineSize
             * @default 1
             * @type {Integer}
             */
             this.lineSize = 1;

            /**
             * Grid cell top padding
             * @attribute cellInsetsTop
             * @default 1
             * @type {Integer}
             * @readOnly
             */
            this.cellInsetsTop = 1;

            /**
             * Grid cell left padding
             * @attribute cellInsetsLeft
             * @default 2
             * @type {Integer}
             * @readOnly
             */
            this.cellInsetsLeft = 2;

            /**
             * Grid cell bottom padding
             * @attribute cellInsetsBottom
             * @default 1
             * @type {Integer}
             * @readOnly
             */
             this.cellInsetsBottom = 1;

            /**
             * Grid cell right padding
             * @attribute cellInsetsRight
             * @default 2
             * @type {Integer}
             * @readOnly
             */
            this.cellInsetsRight = 2;

            /**
             * Default cell content horizontal alignment
             * @type {String}
             * @attribute defXAlignment
             * @default "left"
             */
            this.defXAlignment = "left";

            /**
             * Default cell content vertical alignment
             * @type {String}
             * @attribute defYAlignment
             * @default "center"
             */
            this.defYAlignment = "center";

            /**
             * Indicate if horizontal lines have to be rendered
             * @attribute drawHorLines
             * @type {Boolean}
             * @readOnly
             * @default true
             */
            this.drawHorLines = true;

            /**
             * Indicate if vertical lines have to be rendered
             * @attribute drawVerLines
             * @type {Boolean}
             * @readOnly
             * @default true
             */
            this.drawVerLines = true;

            /**
             * Line color
             * @attribute lineColor
             * @type {String}
             * @default gray
             * @readOnly
             */
            this.lineColor = "gray";

            /**
             * Indicate if caption lines have to be rendered
             * @type {Boolean}
             * @attribute  drawCaptionLines
             * @default true
             */
            this.drawCaptionLines = true;

            /**
             * Indicate if size of grid cells have to be calculated
             * automatically basing on its preferred heights and widths
             * @attribute isUsePsMetric
             * @type {Boolean}
             * @default false
             * @readOnly
             */
            this.isUsePsMetric = false;

            /**
             * Defines if the pos marker has to be renederd over rendered data
             * @attribute paintPosMarkerOver
             * @type {Boolean}
             * @default true
             */
            this.paintPosMarkerOver = true;

            /**
             * Initial (not scrolled) y coordinate of first cell
             * @method $initialCellY
             * @return {Integer} initial y coordinate
             * @private
             */
            this.$initialCellY = function() {
                var ly = 0;
                if (this.topCaption !== null && this.topCaption.isVisible) {
                    ly = this.topCaption.y + this.topCaption.height;
                } else {
                    ly = this.getTop();
                }

                ly += this.lineSize;
                return ly;
            };

            /**
             * Initial (not scrolled) x coordinate of first cell
             * @method $initialCellX
             * @return {Integer} initial x coordinate
             * @private
             */
            this.$initialCellX = function() {
                var lx = 0;
                if (this.leftCaption !== null && this.leftCaption.isVisible) {
                    lx += this.leftCaption.x + this.leftCaption.width;
                } else {
                    lx = this.getLeft();
                }

                lx += this.lineSize;
                return lx;
            };

            /**
             * Set the grid cell content default horizontal alignment.
             * @param {String} ax a horizontal alignment. Use "left", "right" or "center"
             * as the alignment value.
             * @method setDefCellXAlignment
             * @chainable
             */
            this.setDefCellXAlignment = function(ax) {
                this.setDefCellAlignments(ax, this.defYAlignment);
                return this;
            };

            /**
             * Set the grid cell default vertical alignment.
             * @param {String} ay a vertical alignment. Use "top", "bottom" or "center"
             * as the alignment value.
             * @method setDefCellYAlignment
             * @chainable
             */
            this.setDefCellYAlignment = function(ay) {
                this.setDefCellAlignments(this.defXAlignment, ay);
                return this;
            };

            /**
             * Set the grid cell default horizontal and vertical alignments.
             * @param {String} ax a horizontal alignment. Use "left", "right" or "center"
             * @param {String} ay a horizontal alignment. Use "top", "bottom" or "center"
             * as the alignment value.
             * @method setDefCellAlignments
             * @chainable
             */
            this.setDefCellAlignments = function(ax, ay) {
                if (this.defXAlignment !== ax || this.defYAlignment !== ay) {
                    this.defXAlignment = ax;
                    this.defYAlignment = ay;
                    this.repaint();
                }
                return this;
            };

            /**
             * Return a view that is used to render the given grid cell.
             * @param  {Integer} row a grid cell row
             * @param  {Integer} col a grid cell column
             * @return {zebkit.draw.View} a cell view
             * @method getCellView
             */
            this.getCellView = function(row, col) {
                return this.provider.getView(this, row, col, this.model.get(row, col));
            };

            this.colVisibility = function(col, x, d, b){
                var cols = this.getGridCols();
                if (cols === 0) {
                    return null;
                } else {
                    var left = this.getLeft(),
                        dx   = this.scrollManager.getSX(),
                        xx1  = Math.min(this.visibleArea.x + this.visibleArea.width,
                                        this.width - this.getRight()),
                        xx2  = Math.max(left, this.visibleArea.x +
                                        this.getLeftCaptionWidth());

                    for(; col < cols && col >= 0; col += d) {
                        if (x + dx < xx1 && (x + this.colWidths[col] + dx) > xx2) {
                            if (b) {
                                return [col, x];
                            }
                        } else if (b === false)  {
                            return this.colVisibility(col, x, (d > 0 ?  -1 : 1), true);
                        }

                        if (d < 0) {
                            if (col > 0) {
                                x -= (this.colWidths[col - 1] + this.lineSize);
                            }
                        } else {
                            if (col < cols - 1) {
                                x += (this.colWidths[col] + this.lineSize);
                            }
                        }
                    }
                    return b ? null : ((d > 0) ? [col -1, x]
                                               : [0, this.$initialCellX() ]);
                }
            };

            this.rowVisibility = function(row,y,d,b) {
                var rows = this.getGridRows();
                if (rows === 0) {
                    return null;
                } else {
                    var top = this.getTop(),
                        dy  = this.scrollManager.getSY(),
                        yy1 = Math.min(this.visibleArea.y + this.visibleArea.height,
                                       this.height - this.getBottom()),
                        yy2 = Math.max(this.visibleArea.y,
                                       top + this.getTopCaptionHeight());

                    for(; row < rows && row >= 0; row += d){
                        if (y + dy < yy1 && (y + this.rowHeights[row] + dy) > yy2){
                            if (b) {
                                return [row, y];
                            }
                        } else {
                            if (b === false) {
                                return this.rowVisibility(row, y, (d > 0 ?  -1 : 1), true);
                            }
                        }

                        if (d < 0){
                            if (row > 0) {
                                y -= (this.rowHeights[row - 1] + this.lineSize);
                            }
                        } else {
                            if (row < rows - 1) {
                                y += (this.rowHeights[row] + this.lineSize);
                            }
                        }
                    }
                    return b ? null : ((d > 0) ? [row - 1, y]
                                               : [0, this.$initialCellY()]);
                }
            };

            this.vVisibility = function(){
                var va = ui.$cvp(this, {});
                if (va === null) {
                    this.visibleArea = null;
                    this.visibility.fr = null; // say no visible cells are available
                } else {
                    // visible area has not been calculated or
                    // visible area has been changed
                    if (this.visibleArea === null            ||
                        va.x !== this.visibleArea.x          ||
                        va.y !== this.visibleArea.y          ||
                        va.width  !== this.visibleArea.width ||
                        va.height !== this.visibleArea.height  )
                    {
                        this.iColVisibility(0);
                        this.iRowVisibility(0);
                        this.visibleArea = va;
                    }

                    var v = this.visibility,
                        b = v.hasVisibleCells();

                    if (this.colOffset !== 100) {
                        if (this.colOffset > 0 && b){
                            v.lc = this.colVisibility(v.lc[0], v.lc[1],  -1, true);
                            v.fc = this.colVisibility(v.lc[0], v.lc[1],  -1, false);
                        } else {
                            if (this.colOffset < 0 && b) {
                                v.fc = this.colVisibility(v.fc[0], v.fc[1], 1, true);
                                v.lc = this.colVisibility(v.fc[0], v.fc[1], 1, false);
                            } else {
                                v.fc = this.colVisibility(0, this.$initialCellX(), 1, true);
                                v.lc = (v.fc !== null) ? this.colVisibility(v.fc[0], v.fc[1], 1, false)
                                                       : null;
                            }
                        }
                        this.colOffset = 100;
                    }

                    if (this.rowOffset !== 100) {
                        if (this.rowOffset > 0 && b) {
                            v.lr = this.rowVisibility(v.lr[0], v.lr[1],  -1, true);
                            v.fr = this.rowVisibility(v.lr[0], v.lr[1],  -1, false);
                        } else {
                            if(this.rowOffset < 0 && b){
                                v.fr = this.rowVisibility(v.fr[0], v.fr[1], 1, true);
                                v.lr = (v.fr !== null) ? this.rowVisibility(v.fr[0], v.fr[1], 1, false) : null;
                            } else {
                                v.fr = this.rowVisibility(0, this.$initialCellY(), 1, true);
                                v.lr = (v.fr !== null) ? this.rowVisibility(v.fr[0], v.fr[1], 1, false) : null;
                            }
                        }
                        this.rowOffset = 100;
                    }
                }
            };

            /**
             * Make the given cell visible.
             * @param  {Integer} row a cell row
             * @param  {Integer} col a cell column
             * @method makeVisible
             * @chainable
             */
            this.makeVisible = function(row, col) {
                var top  = this.getTop()  + this.getTopCaptionHeight(),
                    left = this.getLeft() + this.getLeftCaptionWidth(),
                    o    = ui.calcOrigin(this.getColX(col),
                                         this.getRowY(row),

                                         // width depends on marker mode: cell or row
                                         this.getLineSize(row) > 1 ? this.colWidths[col] + this.lineSize
                                                                   : this.psWidth_,
                                         this.rowHeights[row] + this.lineSize,
                                         this.scrollManager.getSX(),
                                         this.scrollManager.getSY(),
                                         this, top, left,
                                         this.getBottom(),
                                         this.getRight());

                this.scrollManager.scrollTo(o[0], o[1]);
                return this;
            };

            this.$se = function(row, col, e) {
                if (row >= 0) {
                    this.stopEditing(true);

                    if (this.editors !== null &&
                        this.editors.shouldStart(this, row, col, e))
                    {
                        return this.startEditing(row, col);
                    }
                }
                return false;
            };

            this.getXOrigin = function() {
                return this.scrollManager.getSX();
            };

            this.getYOrigin = function () {
                return this.scrollManager.getSY();
            };

            /**
             * Get a preferred width the given column wants to have
             * @param  {Integer} col a column
             * @return {Integer} a preferred width of the given column
             * @method getColPSWidth
             */
            this.getColPSWidth = function(col){
                return this.getPSSize(col, false);
            };

            /**
             * Get a preferred height the given row wants to have
             * @param  {Integer} col a row
             * @return {Integer} a preferred height of the given row
             * @method getRowPSHeight
             */
            this.getRowPSHeight = function(row) {
                return this.getPSSize(row, true);
            };

            this.recalc = function(){
                if (this.isUsePsMetric) {
                    this.rPsMetric();
                } else {
                    this.rCustomMetric();
                }

                this.psHeight_ = this.psWidth_ = 0;

                var cols = this.getGridCols(),
                    rows = this.getGridRows();

                if (cols > 0) {
                    this.psWidth_ += ((cols + 1) * this.lineSize);
                }

                // if left caption is visible add extra line size since vertical line has to
                // be rendered at the left side of left caption
                if (this.leftCaption !== null && this.leftCaption.isVisible) {
                    this.psWidth_ += this.lineSize;
                }

                if (rows > 0) {
                    this.psHeight_ += ((rows + 1) * this.lineSize);
                }

                // if top caption is visible add extra line size since horizontal line has to
                // be rendered at the top side of top caption
                if (this.topCaption !== null && this.topCaption.isVisible) {
                    this.psHeight_ += this.lineSize;
                }

                // accumulate column widths
                var i = 0;
                for (;i < cols; i++) {
                    this.psWidth_ += this.colWidths[i];
                }

                // accumulate row heights
                for (i = 0; i < rows; i++) {
                    this.psHeight_ += this.rowHeights[i];
                }
            };

            /**
             * Get number of rows in the given grid
             * @return {Integer} a number of rows
             * @method getGridRows
             */
            this.getGridRows = function() {
                return this.model !== null ? this.model.rows : 0;
            };

            /**
             * Get number of columns in the given grid
             * @return {Integer} a number of columns
             * @method getGridColumns
             */
            this.getGridCols = function(){
                return this.model !== null ? this.model.cols : 0;
            };

            /**
             * Get the  given grid row height
             * @param  {Integer} row a grid row
             * @return {Integer} a height of the given row
             * @method getRowHeight
             */
            this.getRowHeight = function(row){
                this.validateMetric();
                return this.rowHeights[row];
            };

            /**
             * Get the given grid column width
             * @param  {Integer} col a grid column
             * @return {Integer} a width of the given column
             * @method getColWidth
             */
            this.getColWidth = function(col){
                this.validateMetric();
                return this.colWidths[col];
            };

            this.getCellsVisibility = function(){
                this.validateMetric();
                return this.visibility;
            };

            /**
             * Get the given column top-left corner x coordinate
             * @param  {Integer} col a column
             * @return {Integer} a top-left corner x coordinate of the given column
             * @method getColX
             */
            this.getColX = function (col){
                // speed up a little bit by avoiding calling validateMetric method
                if (this.isValid === false) {
                    this.validateMetric();
                }

                var start = 0,
                    d     = 1,
                    x     = 0;

                if (this.visibility.hasVisibleCells()) {
                    start = this.visibility.fc[0];
                    x     = this.visibility.fc[1];
                    d     = (col > this.visibility.fc[0]) ? 1 : -1;
                } else {
                    if (this.leftCaption !== null && this.leftCaption.isVisible) {
                        x = this.leftCaption.x + this.leftCaption.width + this.lineSize;
                    } else {
                        x = this.getLeft() + this.lineSize;
                    }
                }

                for(var i = start;i !== col; x += ((this.colWidths[i] + this.lineSize) * d),i += d) {}
                return x;
            };

            /**
             * Get the given row top-left corner y coordinate
             * @param  {Integer} row a row
             * @return {Integer} a top-left corner y coordinate
             * of the given column
             * @method getColX
             */
            this.getRowY = function (row){
                // speed up a little bit by avoiding calling validateMetric method
                if (this.isValid === false) {
                    this.validateMetric();
                }

                var start = 0,
                    d     = 1,
                    y     = 0;

                if (this.visibility.hasVisibleCells()){
                    start = this.visibility.fr[0];
                    y     = this.visibility.fr[1];
                    d     = (row > this.visibility.fr[0]) ? 1 : -1;
                } else {
                    if (this.topCaption !== null && this.topCaption.isVisible) {
                        y = this.topCaption.y + this.topCaption.height + this.lineSize;
                    } else {
                        y = this.getTop() + this.lineSize;
                    }
                }

                for(var i = start;i !== row; y += ((this.rowHeights[i] + this.lineSize) * d),i += d) {}
                return y;
            };

            this.childPointerEntered  =
            this.childPointerExited   =
            this.childPointerReleased =
            this.childPointerReleased =
            this.childPointerPressed  =
            this.childKeyReleased     =
            this.childKeyTyped        =
            this.childKeyPressed      = function(e){
                if (this.editingRow >= 0) {
                    if (this.editors.shouldCancel(this,
                                                  this.editingRow,
                                                  this.editingCol, e))
                    {
                        this.stopEditing(false);
                    } else {
                        if (this.editors.shouldFinish(this,
                                                      this.editingRow,
                                                      this.editingCol, e))
                        {
                            this.stopEditing(true);
                        }
                    }
                }
            };

            this.iColVisibility = function(off) {
                this.colOffset = (this.colOffset === 100) ? this.colOffset = off
                                                          : ((off !== this.colOffset) ? 0 : this.colOffset);
            };

            this.iRowVisibility = function(off) {
                this.rowOffset = (this.rowOffset === 100) ? off
                                                          : (((off + this.rowOffset) === 0) ? 0 : this.rowOffset);
            };

            /**
             * Get top grid caption height. Return zero if no top caption element has been defined
             * @return {Integer} a top caption height
             * @protected
             * @method  getTopCaptionHeight
             */
            this.getTopCaptionHeight = function(){
                return (this.topCaption !== null && this.topCaption.isVisible === true) ? this.topCaption.height : 0;
            };

            /**
             * Get left grid caption width. Return zero if no left caption element has been defined
             * @return {Integer} a left caption width
             * @protected
             * @method  getLeftCaptionWidth
             */
            this.getLeftCaptionWidth = function(){
                return (this.leftCaption !== null && this.leftCaption.isVisible === true) ? this.leftCaption.width : 0;
            };

            this.paint = function(g){
                this.vVisibility();

                if (this.visibility.hasVisibleCells()) {
                    var dx = this.scrollManager.getSX(),
                        dy = this.scrollManager.getSY(),
                        th = this.getTopCaptionHeight(),
                        tw = this.getLeftCaptionWidth();

                    g.save();
                    try {
                        g.translate(dx, dy);

                        if (th > 0 || tw > 0) {
                            g.clipRect(tw - dx, th - dy, this.width  - tw, this.height - th);
                        }

                        if (this.paintPosMarkerOver !== true) {
                            this.paintPosMarker(g);
                        }

                        this.paintData(g);
                        if (this.lineSize > 0 && (this.drawHorLines === true || this.drawVerLines === true)) {
                            this.paintNet(g);
                        }

                        if (this.paintPosMarkerOver === true) {
                            this.paintPosMarker(g);
                        }
                    } catch(e) {
                        g.restore();
                        throw e;
                    }

                    g.restore();
                }
            };

            this.paintOnTop = function(g) {
                // paint lines over captions
                if (this.drawCaptionLines && (this.drawHorLines === true || this.drawVerLines === true)) {
                    var v  = this.visibility,
                        i  = 0;

                    if (this.leftCaption !== null && this.leftCaption.isVisible) {
                        g.setColor(this.lineColor);
                        g.beginPath();
                        if (g.lineWidth !== this.lineSize) {
                            g.lineWidth = this.lineSize;
                        }

                        var sx   = this.leftCaption.x - this.lineSize,
                            y    = v.fr[1] - this.lineSize / 2 + this.scrollManager.getSY(),
                            minY = (this.topCaption !== null &&  this.topCaption.isVisible ? this.topCaption.y + this.topCaption.height
                                                                                           : this.getTop());

                        g.moveTo(this.leftCaption.x - this.lineSize / 2, this.getTop());
                        g.lineTo(this.leftCaption.x - this.lineSize / 2,
                                 Math.min(this.leftCaption.y + this.leftCaption.height,
                                          this.height - this.getBottom()));

                        sx = this.leftCaption.x;
                        for(;i <= v.lr[0] + 1; i++) {
                            if (y >= minY) {
                                g.moveTo(sx, y);
                                g.lineTo(sx + this.leftCaption.width + this.lineSize, y);
                            }
                            y += this.rowHeights[i] + this.lineSize;
                        }

                        g.stroke();
                    }

                    if (this.topCaption !== null && this.topCaption.isVisible) {

                        g.setColor(this.lineColor);
                        g.beginPath();
                        if (g.lineWidth !== this.lineSize) {
                            g.lineWidth = this.lineSize;
                        }

                        var sy   = this.topCaption.y - this.lineSize,
                            minX = this.leftCaption !== null && this.leftCaption.isVisible ? this.leftCaption.x + this.leftCaption.width
                                                                                           : this.getLeft(),
                            x    = v.fc[1] - this.lineSize / 2 + this.scrollManager.getSX();

                        g.moveTo(this.topCaption.x - this.getLeftCaptionWidth(), sy + this.lineSize / 2);
                        g.lineTo(Math.min(this.topCaption.x + this.topCaption.width,
                                          this.width - this.getRight()),
                                 sy + this.lineSize / 2);

                        sy = this.topCaption.y;
                        for (i = v.fc[0]; i <= v.lc[0] + 1; i++) {
                            if (x >= minX) {
                                g.moveTo(x, sy);
                                g.lineTo(x, sy + this.topCaption.height + this.lineSize);
                            }
                            x += this.colWidths[i] + this.lineSize;
                        }
                        g.stroke();
                    }
                }
            };

            /**
             * Scroll action handler
             * @param  {Integer} psx a previous horizontal scroll offset
             * @param  {Integer} psy a previous vertical scroll offset
             * @method catchScrolled
             */
            this.catchScrolled = function (psx, psy){
                var offx = this.scrollManager.getSX() - psx,
                    offy = this.scrollManager.getSY() - psy;

                if (offx !== 0) {
                    this.iColVisibility(offx > 0 ? 1 :  - 1);
                }

                if (offy !== 0) {
                    this.iRowVisibility(offy > 0 ? 1 :  - 1);
                }

                this.stopEditing(false);
                this.repaint();
            };

            //TODO: zebkit doesn't support yet the method
            this.isInvalidatedByChild = function (c){
                return c !== this.editor || this.isUsePsMetric;
            };

            /**
             * Stop editing a grid cell.
             * @param  {Boolean} applyData true if the edited data has to be applied as a new
             * grid cell content
             * @protected
             * @method stopEditing
             */
            this.stopEditing = function(applyData){
                if (this.editors !== null &&
                    this.editingRow >= 0  &&
                    this.editingCol >= 0    )
                {
                    try {
                        if (zebkit.instanceOf(this.editor, pkg.Grid)) {
                            this.editor.stopEditing(applyData);
                        }

                        var data = this.getDataToEdit(this.editingRow, this.editingCol);
                        if (applyData){
                            this.setEditedData(this.editingRow,
                                               this.editingCol,
                                               this.editors.fetchEditedValue( this,
                                                                              this.editingRow,
                                                                              this.editingCol,
                                                                              data, this.editor));
                        }
                        this.repaintRows(this.editingRow, this.editingRow);
                    } finally {
                        this.editingCol = this.editingRow = -1;
                        if (this.indexOf(this.editor) >= 0) {
                            this.remove(this.editor);
                        }
                        this.editor = null;
                        this.requestFocus();
                    }
                }
            };

            /**
             * Set if horizontal and vertical lines have to be painted
             * @param {Boolean} hor true if horizontal lines have to be painted
             * @param {Boolean} ver true if vertical lines have to be painted
             * @method setDrawLines
             * @chainable
             */
            this.setDrawLines = function(hor, ver){
                if (this.drawVerLines !== hor || this.drawHorLines !== ver) {
                    this.drawHorLines = hor;
                    this.drawVerLines = ver;
                    this.repaint();
                }
                return this;
            };

            /**
             * Set the given grid cell select mode.
             * @param {zebki.ui.grid.SelectMode|String} mode a select mode. It is possible
             * to specify the mode with one of the following string constant:
             *
             *    - "row" - single row select mode
             *    - "col" - single column select mode
             *    - "cell" - single cell select mode
             *
             *
             * @method setSelectMode
             * @chainable
             */
            this.setSelectMode = function(mode) {
                this.clearSelect();

                var prevSelMode = this.selectMode;
                if (mode !== this.selectMode) {
                    if (prevSelMode !== null && typeof prevSelMode.uninstall === 'function') {
                        prevSelMode.uninstall(this);
                    }

                    if (zebkit.isString(mode)) {
                        if (mode.toLowerCase() === "row") {
                            this.selectMode = new pkg.RowSelectMode(this);
                            this.setNavigationMode(mode);
                        } else if (mode.toLowerCase() === "col") {
                            this.selectMode = new pkg.ColSelectMode(this);
                            this.setNavigationMode(mode);
                        } else if (mode.toLowerCase() === "cell") {
                            this.selectMode = new pkg.CellSelectMode(this);
                            this.setNavigationMode(mode);
                        } else {
                            throw new Error("Invalid select mode '" + mode + "'");
                        }
                    } else if (mode === null) {
                        this.selectMode = null;
                    } else {
                        this.selectMode = mode;
                    }

                    if (this.selectMode !== null && typeof this.selectMode.install === 'function') {
                        this.selectMode.install(this);
                    }
                }
                return this;
            };

            /**
             * Set navigation mode. It is possible to use "row" or "cell" or "col" navigation mode.
             * In first case navigation happens over row, in the second
             * case navigation happens over cell.
             * @param {String} mode a navigation mode ("row" or "cell" or "col")
             * @method setNavigationMode
             * @chainable
             */
            this.setNavigationMode = function(mode) {
                if (this.position !== null) {
                    this.position.setOffset(null);
                }

                if (mode.toLowerCase() === "row") {
                    this.navigationMode = "row";

                    this.getLineSize = function(row) {
                        return 1;
                    };

                    this.getMaxOffset = function() {
                        return this.getGridRows() - 1;
                    };

                    this.getLines = function() {
                        return this.getGridRows();
                    };

                } else if (mode.toLowerCase() === "cell") {
                    this.navigationMode = "cell";

                    this.getLines = function() {
                        return this.getGridRows();
                    };

                    this.getLineSize = function(row) {
                        return this.getGridCols();
                    };

                    this.getMaxOffset = function() {
                        return this.getGridRows() * this.getGridCols() - 1;
                    };
                } else if (mode.toLowerCase() === "col") {
                    this.navigationMode = "col";

                    this.getLineSize = function(row) {
                        return this.getGridCols();
                    };

                    this.getMaxOffset = function() {
                        return this.getGridCols() - 1;
                    };

                    this.getLines = function() {
                        return 1;
                    };
                } else if (mode === null) {
                    this.navigationMode = null;
                } else {
                    throw new Error("Invalid navigation mode value : '" + mode + "'");
                }

                return this;
            };

            /**
             * Position changed event handler.
             * @param  {zebkit.util.Position} target a position manager
             * @param  {Integer} prevOffset a previous position offset
             * @param  {Integer} prevLine a previous position line
             * @param  {Integer} prevCol a previous position column
             * @method posChanged
             */
            this.posChanged = function(target, prevOffset, prevLine, prevCol) {
                var row = this.position.currentLine,
                    col = this.position.currentCol;

                if (row >= 0) {
                    this.makeVisible(row, col);

                    if (this.selectMode !== null) {
                        this.selectMode.posChanged(target, prevOffset, prevLine, prevCol);
                    }

                    if (this.navigationMode === "row") {
                        this.repaintRows(prevLine, row);
                    } else if (this.navigationMode === "col") {
                        this.repaintCols(prevCol, col);
                    } else if (this.navigationMode === "cell") {
                        this.repaintCells(row, col, prevLine, prevCol);
                    }
                } else {
                    this.repaintRows(prevLine, prevLine);
                }
            };

            /**
             * Implement key released handler.
             * @param  {zebkit.ui.event.KeyEvent} e a key event
             * @method keyReleased
             */
            this.keyReleased = function(e) {
                if (this.position !== null) {
                    this.$se(this.position.currentLine,
                             this.position.currentCol, e);
                }
            };

            /**
             * Implement key type handler.
             * @param  {zebkit.ui.event.KeyEvent} e a key event
             * @method keyTyped
             */
            this.keyTyped = function(e){
                if (this.position !== null) {
                    this.$se(this.position.currentLine, this.position.currentCol, e);
                }
            };

            /**
             * Implement key pressed handler.
             * @param  {zebkit.ui.event.KeyEvent} e a key event
             * @method keyPressed
             */
            this.keyPressed = function(e){
                if (this.position !== null) {
                    switch(e.code) {
                        case "ArrowLeft"   : this.position.seek(-1); break;
                        case "ArrowUp"     : this.position.seekLineTo("up"); break;
                        case "ArrowRight"  : this.position.seek(1); break;
                        case "ArrowDown"   : this.position.seekLineTo("down");break;
                        case "PageUp"      : this.position.seekLineTo("up", this.pageSize(-1));break;
                        case "PageDown"    : this.position.seekLineTo("down", this.pageSize(1));break;
                        case "End"         :
                            if (e.ctrlKey) {
                                this.position.setOffset(this.getLines() - 1);
                            } break;
                        case "Home"        :
                            if (e.ctrlKey) {
                                this.position.setOffset(0);
                            } break;
                    }

                    this.$se(this.position.currentLine, this.position.currentCol, e);
                }
            };

            /**
             * Checks if the given grid cell is selected
             * @param  {Integer}  row a grid row
             * @param  {Integer}  col a grid col
             * @return {Boolean}  true if the given row is selected
             * @method isSelected
             */
            this.isSelected = function(row, col) {
                return this.selectMode === null ? false
                                                    : this.selectMode.isSelected(row, col);
            };

            /**
             * Repaint range of grid rows
             * @param  {Integer} r1 the first row to be repainted
             * @param  {Integer} r2 the last row to be repainted
             * @method repaintRows
             * @chainable
             */
            this.repaintRows = function(r1, r2){
                if (r1 < 0) {
                    r1 = r2;
                }

                if (r2 < 0) {
                    r2 = r1;
                }

                if (r1 > r2) {
                    var i = r2;
                    r2 = r1;
                    r1 = i;
                }

                var rows = this.getGridRows();
                if (r1 >= 0 && r1 < rows) {
                    if (r2 >= rows) {
                        r2 = rows - 1;
                    }

                    var y1 = this.getRowY(r1),
                        y2 = ((r1 === r2) ? y1 + 1 : this.getRowY(r2)) + this.rowHeights[r2];

                    this.repaint(0, y1 + this.scrollManager.getSY(), this.width, y2 - y1);
                }

                return this;
            };

            /**
             * Repaint range of grid columns
             * @param  {Integer} c1 the first column to be repainted
             * @param  {Integer} c2 the last column to be repainted
             * @method repaintCols
             * @chainable
             */
            this.repaintCols = function(c1, c2){
                if (c1 < 0) {
                    c1 = c2;
                }

                if (c2 < 0) {
                    c2 = c1;
                }

                if (c1 > c2) {
                    var i = c2;
                    c2 = c1;
                    c1 = i;
                }

                var cols = this.getGridCols();
                if (c1 >= 0 && c1 < cols) {
                    if (c2 >= cols) {
                        c2 = cols - 1;
                    }

                    var x1 = this.getColX(c1),
                        x2 = ((c1 === c2) ? x1 + 1 : this.getColX(c2)) + this.colWidths[c2];

                    this.repaint(x1 + this.scrollManager.getSX(), 0, x2 - x1, this.height);
                }

                return this;
            };

            /**
             * Repaint cells.
             * @param  {Integer} r1 first row
             * @param  {Integer} c1 first column
             * @param  {Integer} [r2] second row
             * @param  {Integer} [c2] second column
             * @method repaintCells
             * @chainable
             */
            this.repaintCells = function(r1, c1, r2, c2) {
                var cols = this.getGridCols(),
                    rows = this.getGridRows(),
                    i    = 0;

                if (arguments.length === 2) {
                    c2 = c1;
                    r2 = r1;
                }

                if (r1 < 0) {
                    r1 = r2;
                } else if (r2 < 0) {
                    r2 = r1;
                }

                if (c1 < 0) {
                    c1 = c2;
                } else if (c2 < 0) {
                    c2 = c1;
                }

                if (c1 > c2) {
                    i = c2;
                    c2 = c1;
                    c1 = i;
                }

                if (r1 > r2) {
                    i = r2;
                    r2 = r1;
                    r1 = i;
                }

                if (r1 >= 0 && c1 >= 0 && r1 < rows && c1 < cols) {
                    if (c2 > cols) {
                        c2 = cols - 1;
                    }

                    if (r2 > rows) {
                        r2 = rows - 1;
                    }

                    var x1 = this.getColX(c1),
                        x2 = ((c1 === c2) ? x1 + 1 : this.getColX(c2)) + this.colWidths[c2],
                        y1 = this.getRowY(r1),
                        y2 = ((r1 === r2) ? y1 + 1 : this.getRowY(r2)) + this.rowHeights[r2];

                    this.repaint(x1 + this.scrollManager.getSX(),
                                 y1 + this.scrollManager.getSY(),
                                 x2 - x1, y2 - y1);
                }

                return this;
            };

            /**
             * Detect a cell by the given location
             * @param  {Integer} x a x coordinate relatively the grid component
             * @param  {Integer} y a y coordinate relatively the grid component
             * @return {Object} an object that contains detected grid cell row as
             * "row" field and a grid column as "col" field. null is returned if
             * no cell can be detected.
             * @method cellByLocation
             */
            this.cellByLocation = function(x,y){
                this.validate();

                var dx  = this.scrollManager.getSX(),
                    dy  = this.scrollManager.getSY(),
                    v   = this.visibility,
                    ry1 = v.fr[1] + dy,
                    rx1 = v.fc[1] + dx,
                    row = -1,
                    col = -1,
                    i   = 0,
                    ry2 = v.lr[1] + this.rowHeights[v.lr[0]] + dy,
                    rx2 = v.lc[1] + this.colWidths[v.lc[0]] + dx;

                if (y > ry1 && y < ry2) {
                    for(i = v.fr[0];i <= v.lr[0]; ry1 += this.rowHeights[i] + this.lineSize, i++) {
                        if (y > ry1 && y < ry1 + this.rowHeights[i]) {
                            row = i;
                            break;
                        }
                    }
                }
                if (x > rx1 && x < rx2) {
                    for (i = v.fc[0];i <= v.lc[0]; rx1 += this.colWidths[i] + this.lineSize, i++ ) {
                        if (x > rx1 && x < rx1 + this.colWidths[i]) {
                            col = i;
                            break;
                        }
                    }
                }
                return (col >= 0 && row >= 0) ? { row: row, col: col } : null;
            };

            this.doLayout = function(target) {
                var topHeight = (this.topCaption !== null &&
                                 this.topCaption.isVisible === true) ? this.topCaption.getPreferredSize().height
                                                                     : 0,
                    leftWidth = (this.leftCaption !== null &&
                                 this.leftCaption.isVisible === true) ? this.leftCaption.getPreferredSize().width : 0,
                    topY      = this.getTop(),
                    leftX     = this.getLeft();

                if (topHeight > 0) {
                //    topHeight += this.lineSize;
                    topY      += this.lineSize;
                }

                if (leftWidth > 0) {
                  //  leftWidth += this.lineSize;
                    leftX     += this.lineSize;
                }

                if (this.topCaption !== null){
                    this.topCaption.setBounds(leftX + leftWidth,
                                              topY,
                                              Math.min(target.width - this.getLeft() - this.getRight() - leftWidth,
                                                       this.psWidth_),
                                              topHeight);
                }

                if (this.leftCaption !== null){
                    this.leftCaption.setBounds(leftX,
                                               topY + topHeight,
                                               leftWidth,
                                               Math.min(target.height - this.getTop() - this.getBottom() - topHeight,
                                                        this.psHeight_));
                }

                if (this.stub !== null && this.stub.isVisible === true)
                {
                    if (leftWidth > 0 && topHeight > 0) {
                        this.stub.setBounds(leftX, topY,
                                            leftWidth,
                                            topHeight);
                    } else {
                        this.stub.setSize(0, 0);
                    }
                }

                if (this.editors !== null &&
                    this.editor  !== null &&
                    this.editor.parent === this &&
                    this.editor.isVisible === true)
                {
                    var w = this.colWidths[this.editingCol],
                        h = this.rowHeights[this.editingRow],
                        x = this.getColX(this.editingCol),
                        y = this.getRowY(this.editingRow);

                    if (this.isUsePsMetric){
                        x += this.cellInsetsLeft;
                        y += this.cellInsetsTop;
                        w -= (this.cellInsetsLeft + this.cellInsetsRight);
                        h -= (this.cellInsetsTop + this.cellInsetsBottom);
                    }

                    this.editor.setBounds(x + this.scrollManager.getSX(),
                                          y + this.scrollManager.getSY(), w, h);
                }
            };

            this.canHaveFocus = function (){
                return this.editor === null;
            };

            /**
             * Clear grid row or rows selection
             * @method clearSelect
             * @chainable
             */
            this.clearSelect = function() {
                if (this.selectMode !== null) {
                    this.selectMode.clearSelect();
                }
                return this;
            };

            /**
             * Mark as selected or unselected the given grid cell
             * @param  {Integer} row a grid row
             * @param  {Integer} [col] a grid row,
             * @param  {boolean} [b] a selection status. true if the parameter
             * has not been specified
             * @method select
             * @chainable
             */
            this.select = function(row, col, b) {
                if (this.selectMode !== null) {
                    if (arguments.length === 1) {
                        col = -1;
                        b   = false;
                    } else if (arguments.length === 2) {
                        if (zebkit.isInteger(col)) {
                            b = false;
                        } else {
                            b = col;
                            col = -1;
                        }
                    }

                    this.selectMode.select(row, col, b);
                }

                return this;
            };

            this.laidout = function () {
                this.vVisibility();
            };

            this.pointerClicked = function(e) {
                if (e.isAction() && this.visibility.hasVisibleCells()){
                    this.stopEditing(true);

                    if (e.isAction()){
                        var p = this.cellByLocation(e.x, e.y);
                        if (p !== null) {
                            if (this.position !== null){
                                var row = this.position.currentLine,
                                    col = this.position.currentCol,
                                    ls  = this.getLineSize(p.row),
                                    lns = this.getLines();

                                // normalize column depending on marker mode: row or cell
                                // in row mode marker can select only the whole row, so
                                // column can be only 1  (this.getLineSize returns 1)
                                if (row === p.row % lns && col === p.col % ls) {
                                    this.makeVisible(row, col);
                                } else {
                                    this.position.setRowCol(p.row % lns, p.col % ls);
                                }
                            }

                            if (this.$se(p.row, p.col, e)) {
                                // TODO: initiated editor has to get pointer clicked event
                            }
                        }
                    }
                }
            };

            this.calcPreferredSize = function(target) {
                return {
                    width : this.psWidth_  +
                           ((this.leftCaption !== null  &&
                             this.leftCaption.isVisible === true) ? this.leftCaption.getPreferredSize().width : 0),
                    height: this.psHeight_ +
                           ((this.topCaption !== null  &&
                             this.topCaption.isVisible === true) ? this.topCaption.getPreferredSize().height : 0)
                };
            };

            /**
             * Paint vertical and horizontal grid component lines
             * @param  {CanvasRenderingContext2D} g a HTML5 canvas 2D context
             * @method paintNet
             * @protected
             */
            this.paintNet = function(g) {
                var v    = this.visibility,
                    i    = 0,
                    prevWidth = g.lineWidth;

                g.setColor(this.lineColor);
                g.lineWidth = this.lineSize;
                g.beginPath();

                if (this.drawHorLines === true) {
                    var y  = v.fr[1] - this.lineSize / 2,
                        x1 = v.fc[1] - this.lineSize,
                        x2 = v.lc[1] + this.colWidths[v.lc[0]] + this.lineSize;

                    for (i = v.fr[0]; i <= v.lr[0] + 1; i++) {
                        g.moveTo(x1, y);
                        g.lineTo(x2, y);
                        y += this.rowHeights[i] + this.lineSize;
                    }
                }

                if (this.drawVerLines === true) {
                    var x   = v.fc[1] - this.lineSize / 2,
                        y1  = v.fr[1] - this.lineSize,
                        y2  = v.lr[1] + this.rowHeights[v.lr[0]];

                    for (i = v.fc[0]; i <= v.lc[0] + 1; i++) {
                        g.moveTo(x, y1);
                        g.lineTo(x, y2);
                        x += this.colWidths[i] + this.lineSize;
                    }
                }
                g.stroke();
                g.lineWidth = prevWidth;
            };

            /**
             * Paint grid data
             * @param  {CanvasRenderingContext2D} g a HTML5 canvas 2d context
             * @method paintData
             * @protected
             */
            this.paintData = function(g) {
                var y    = this.visibility.fr[1],
                    addW = this.cellInsetsLeft + this.cellInsetsRight,
                    addH = this.cellInsetsTop  + this.cellInsetsBottom,
                    ts   = g.$states[g.$curState],
                    cx   = ts.x,
                    cy   = ts.y,
                    cw   = ts.width,
                    ch   = ts.height,
                    res  = {};

                for(var i = this.visibility.fr[0];i <= this.visibility.lr[0] && y < cy + ch; i++) {
                    if (y + this.rowHeights[i] > cy) {
                        var x  = this.visibility.fc[1],
                            yv = y + this.cellInsetsTop;

                        for (var j = this.visibility.fc[0];j <= this.visibility.lc[0]; j++) {
                            if (this.isSelected(i, j) === true) {
                                this.paintCellSelection(g, i, j, x, y);
                            } else {
                                var bg = this.provider.getCellColor !== undefined ? this.provider.getCellColor(this, i, j)
                                                                                  : this.provider.background;
                                if (bg !== null) {
                                    if (bg.paint !== undefined) {
                                        bg.paint(g, x, y, this.colWidths[j], this.rowHeights[i], this);
                                    } else {
                                        g.setColor(bg);
                                        g.fillRect(x, y, this.colWidths[j], this.rowHeights[i]);
                                    }
                                }
                            }

                            var v = (i === this.editingRow &&
                                     j === this.editingCol   ) ? null
                                                               : this.provider.getView(this, i, j,
                                                                                       this.model.get(i, j));

                            if (v !== null) {
                                var xv = x + this.cellInsetsLeft,
                                    w  = this.colWidths[j]  - addW,
                                    h  = this.rowHeights[i] - addH;

                                res.x = xv > cx ? xv : cx;
                                res.width = Math.min(xv + w, cx + cw) - res.x;
                                res.y = yv > cy ? yv : cy;
                                res.height = Math.min(yv + h, cy + ch) - res.y;

                                if (res.width > 0 && res.height > 0) {
                                    // TODO: most likely the commented section should be removed
                                    // if (this.isUsePsMetric !== true) {
                                    //     v.paint(g, x, y, w, h, this);
                                    // }
                                    //else {
                                        var ax = this.provider.getXAlignment !== undefined ? this.provider.getXAlignment(this, i, j)
                                                                                           : this.defXAlignment,
                                            ay = this.provider.getYAlignment !== undefined ? this.provider.getYAlignment(this, i, j)
                                                                                           : this.defYAlignment,
                                            vw = w, // cell width
                                            vh = h, // cell height
                                            xx = xv,
                                            yy = yv,
                                            id = -1,
                                            ps = (ax !== null || ay !== null) ? v.getPreferredSize(vw, vh)
                                                                              : null;

                                        if (ax !== null) {
                                            xx = xv + ((ax === "center") ? Math.floor((w - ps.width) / 2)
                                                                         : ((ax === "right") ? w - ps.width : 0));
                                            vw = ps.width;
                                        }

                                        if (ay !== null) {
                                            yy = yv + ((ay === "center") ? Math.floor((h - ps.height) / 2)
                                                                         : ((ay === "bottom") ? h - ps.height : 0));
                                            vh = ps.height;
                                        }

                                        if (xx < res.x || yy < res.y || (xx + vw) > (xv + w) || (yy + vh) > (yv + h)) {
                                            id = g.save();
                                            g.clipRect(res.x, res.y, res.width, res.height);
                                        }

                                        v.paint(g, xx, yy, vw, vh, this);
                                        if (id >= 0) {
                                           g.restore();
                                        }
                                   // }
                                }
                            }
                            x += (this.colWidths[j] + this.lineSize);
                        }
                    }
                    y += (this.rowHeights[i] + this.lineSize);
                }
            };

            /**
             * Get position marker view taking in account focus state.
             * @return {zebkit.draw.View} a position marker view
             * @private
             * @method  $getPosMarker
             */
            this.$getPosMarker = function() {
                return this.hasFocus() ? (this.views.marker    === undefined ? null : this.views.marker)
                                       : (this.views.offmarker === undefined ? null : this.views.offmarker);
            };

            /**
             * Paint position marker.
             * @param  {CanvasRenderingContext2D} g a graphical 2D context
             * @protected
             * @method paintPosMarker
             */
            this.paintPosMarker = function(g) {
                if (this.position       !== null &&
                    this.position.offset >= 0       )
                {
                    var view = this.$getPosMarker(),
                        row  = this.position.currentLine,
                        col  = this.position.currentCol,
                        v    = this.visibility;

                    // depending on position changing mode (cell or row) analyze
                    // whether the current position is in visible area
                    if (view !== null) {
                        if (this.navigationMode === "row") {
                            if (row >= v.fr[0] && row <= v.lr[0]) {
                                view.paint(g,   v.fc[1],
                                                this.getRowY(row),
                                                v.lc[1] - v.fc[1] + this.colWidths[v.lc[0]],
                                                this.rowHeights[row], this);
                            }
                        } else if (this.navigationMode === "cell") {
                            if (col >= v.fc[0] && col <= v.lc[0] && row >= v.fr[0] && row <= v.lr[0]) {
                                view.paint(g,   this.getColX(col),
                                                this.getRowY(row),
                                                this.colWidths[col],
                                                this.rowHeights[row], this);
                            }
                        } else if (this.navigationMode === "col") {
                            if (col >= v.fc[0] && col <= v.lc[0]) {
                                view.paint(g,   this.getColX(col),
                                                v.fr[1],
                                                this.colWidths[col],
                                                v.lr[1] - v.fr[1] + this.rowHeights[v.lr[0]], this);
                            }
                        }
                    }
                }
            };

            /**
             * Paint a selection for the given grid cell
             * @param  {CanvasRenderingContext2D} g a graphical 2D context
             * @param  {Integer} row a cell row.
             * @param  {Integer} col a cell column.
             * @param  {Integer} x a cell x location.
             * @param  {Integer} y a cell y location.
             * @protected
             * @method paintCellSelection
             */
            this.paintCellSelection = function(g, row, col, x, y) {
                if (this.editingRow < 0) {
                    var v = ui.focusManager.focusOwner === this ? this.views.focusOnSelect
                                                                : this.views.focusOffSelect;
                    if (v !== null && v !== undefined)  {
                        v.paint(g, x, y, this.colWidths[col], this.rowHeights[row], this);
                    }
                }
            };

            this.rPsMetric = function(){
                var cols  = this.getGridCols(),
                    rows  = this.getGridRows(),
                    addW  = this.cellInsetsLeft + this.cellInsetsRight,
                    addH  = this.cellInsetsTop  + this.cellInsetsBottom,
                    capPS = null,
                    i     = 0;

                if (this.colWidths === null || this.colWidths.length !== cols) {
                    this.colWidths = Array(cols);
                    for (;i < cols; i++) {
                        this.colWidths[i] = 0;
                    }
                } else {
                    for (;i < cols; i++) {
                        this.colWidths[i] = 0;
                    }
                }

                if (this.rowHeights === null || this.rowHeights.length !== rows) {
                    this.rowHeights = Array(rows);
                    for (i = 0; i < rows; i++) {
                        this.rowHeights[i] = 0;
                    }
                } else {
                    for (i = 0;i < rows; i++) {
                        this.rowHeights[i] = 0;
                    }
                }

                for(i = 0; i < cols; i++ ){
                    for(var j = 0; j < rows; j++ ){
                        var v = this.provider.getView(this, j, i, this.model.get(j, i));
                        if (v !== null){
                            var ps = v.getPreferredSize();
                            ps.width  += addW;
                            ps.height += addH;
                            if (ps.width  > this.colWidths[i] ) {
                                this.colWidths [i] = ps.width;
                            }

                            if (ps.height > this.rowHeights[j]) {
                                this.rowHeights[j] = ps.height;
                            }
                        } else {
                            if (pkg.Grid.DEF_COLWIDTH > this.colWidths [i]) {
                                this.colWidths [i] = pkg.Grid.DEF_COLWIDTH;
                            }

                            if (pkg.Grid.DEF_ROWHEIGHT > this.rowHeights[j]) {
                                this.rowHeights[j] = pkg.Grid.DEF_ROWHEIGHT;
                            }
                        }
                    }
                }

                if (this.topCaption !== null && this.topCaption.isVisible === true) {
                    for(i = 0;i < cols; i++ ) {
                        capPS = this.topCaption.getCaptionPS(i);
                        if (capPS  > this.colWidths[i]) {
                            this.colWidths[i] = capPS;
                        }
                    }
                }

                if (this.leftCaption !== null && this.leftCaption.isVisible === true) {
                    for(i = 0;i < rows; i++ ) {
                        capPS = this.leftCaption.getCaptionPS(i);
                        if (capPS  > this.rowHeights[i]) {
                            this.rowHeights[i] = capPS;
                        }
                    }
                }
            };

            this.getPSSize = function (rowcol, b) {
                if (this.isUsePsMetric === true) {
                    return b ? this.getRowHeight(rowcol) : this.getColWidth(rowcol);
                } else {
                    var max   = 0,
                        count = b ? this.getGridCols()
                                  : this.getGridRows();

                    for(var j = 0;j < count; j ++ ){
                        var r = b ? rowcol : j,
                            c = b ? j : rowcol,
                            v = this.provider.getView(this, r, c, this.model.get(r, c));

                        if (v !== null){
                            var ps = v.getPreferredSize();
                            if (b) {
                                if (ps.height > max) {
                                    max = ps.height;
                                }
                            } else {
                                if (ps.width > max) {
                                    max = ps.width;
                                }
                            }
                        }
                    }

                    return max +
                           (b ? this.cellInsetsTop + this.cellInsetsBottom
                              : this.cellInsetsLeft + this.cellInsetsRight);
                }
            };

            this.rCustomMetric = function(){
                var start = 0;
                if (this.colWidths !== null) {
                    start = this.colWidths.length;
                    if (this.colWidths.length !== this.getGridCols()) {
                        this.colWidths.length = this.getGridCols();
                    }
                } else {
                    this.colWidths = Array(this.getGridCols());
                }

                for(; start < this.colWidths.length; start ++ ) {
                    this.colWidths[start] = pkg.Grid.DEF_COLWIDTH;
                }

                start = 0;
                if (this.rowHeights !== null) {
                    start = this.rowHeights.length;
                    if (this.rowHeights.length !== this.getGridRows()) {
                        this.rowHeights.length = this.getGridRows();
                    }
                } else {
                    this.rowHeights = Array(this.getGridRows());
                }

                for(; start < this.rowHeights.length; start++) {
                    this.rowHeights[start] = pkg.Grid.DEF_ROWHEIGHT;
                }
            };

            /**
             * Calculate number of rows to be scrolled up or down to scroll one page
             * @param  {Integer} d a direction. 1 for scroll down and -1 for scroll up
             * @return {Integer}  a page size in rows to be scrolled up or down
             * @method pageSize
             * @protected
             */
            this.pageSize = function(d) {
                this.validate();
                if (this.visibility.hasVisibleCells() && this.position !== null) {
                    var off = this.position.offset;
                    if (off >= 0) {
                        var hh  = this.visibleArea.height - this.getTopCaptionHeight(),
                            sum = 0,
                            poff = off;

                        for (; off >= 0 && off < this.getGridRows() && sum < hh; off += d) {
                            sum += this.rowHeights[off] + this.lineSize;
                        }

                        return Math.abs(poff - off);
                    }
                }
                return 0;
            };

            /**
             * Set the given height for the specified grid row. The method has no effect
             * if the grid component is forced to use preferred size metric.
             * @param {Integer} row a grid row
             * @param {Integer} h   a height of the grid row
             * @method setRowHeight
             * @chainable
             */
            this.setRowHeight = function(row, h) {
                this.setRowsHeight(row, 1, h);
                return this;
            };

            /**
             * Set the given height for all or the specified range of rows
             * @param {Integer} [row] start row
             * @param {Integer} [len] number of rows whose height has to be set
             * @param {Integer} h  a height
             * @method setRowsHeight
             * @chainable
             */
            this.setRowsHeight = function(row, len, h) {
                if (this.isUsePsMetric === false){
                    if (arguments.length === 1) {
                        h   = arguments[0];
                        row = 0;
                        len = this.getGridRows();
                    }

                    if (len !== 0) {
                        this.validateMetric();
                        var b = false;
                        for(var i=row; i < row + len; i++) {
                            if (this.rowHeights[i] !== h) {
                                this.psHeight_ += (h - this.rowHeights[i]);
                                this.rowHeights[i] = h;
                                b = true;
                            }
                        }

                        if (b === true) {
                            this.stopEditing(false);

                            this.cachedHeight = this.getTop() + this.getBottom() + this.psHeight_;
                            if (this.topCaption !== null && this.topCaption.isVisible === true) {
                                this.cachedHeight += this.topCaption.getPreferredSize().height;
                            }

                            if (this.parent !== null) {
                                this.parent.invalidate();
                            }

                            this.iRowVisibility(0);
                            this.invalidateLayout();
                            this.repaint();
                        }
                    }

                    return this;
                }
            };

            /**
             * Set the given width for the specified grid column. The method has no effect
             * if the grid component is forced to use preferred size metric.
             * @param {Integer} column a grid column
             * @param {Integer} w   a width of the grid column
             * @method setColWidth
             * @chainable
             */
            this.setColWidth = function (col,w){
                this.setColsWidth(col, 1, w);
                return this;
            };

            /**
             * Set the given width for all or the specified range of columns
             * @param {Integer} [col] start column
             * @param {Integer} [len] number of columns whose height has to be set
             * @param {Integer} w  a width
             * @method setColsWidth
             * @chainable
             */
            this.setColsWidth = function(col, len, w){
                if (this.isUsePsMetric === false){
                    if (arguments.length === 1) {
                        w   = arguments[0];
                        col = 0;
                        len = this.getGridCols();
                    }

                    if (len !== 0)  {
                        this.validateMetric();
                        var b = false;
                        for(var i = col; i < col + len; i++) {
                            if (this.colWidths[i] !== w){
                                this.psWidth_ += (w - this.colWidths[i]);
                                this.colWidths[i] = w;
                                b = true;
                            }
                        }

                        if (b === true) {
                            this.stopEditing(false);

                            this.cachedWidth = this.getRight() + this.getLeft() + this.psWidth_;
                            if (this.leftCaption !== null && this.leftCaption.isVisible === true) {
                                this.cachedWidth += this.leftCaption.getPreferredSize().width;
                            }

                            if (this.parent !== null) {
                                this.parent.invalidate();
                            }

                            this.iColVisibility(0);
                            this.invalidateLayout();
                            this.repaint();
                        }
                    }

                    return this;
                }
            };

            this.matrixResized = function(target, prevRows, prevCols) {
                this.clearSelect();

                this.vrp();
                if (this.position !== null) {
                    this.position.setOffset(null);
                }

                for(var i = 0; i < this.kids.length; i++) {
                    if (this.kids[i].matrixResized !== undefined) {
                        this.kids[i].matrixResized(target,prevRows,prevCols);
                    }
                }
            };

            this.cellModified = function(target,row,col,prevValue) {
                if (this.isUsePsMetric){
                    this.invalidate();
                }

                for(var i=0; i < this.kids.length; i++) {
                    if (this.kids[i].cellModified !== undefined) {
                        this.kids[i].cellModified(target,row,col, prevValue);
                    }
                }
            };

            this.matrixSorted = function(target, info) {
                this.clearSelect();
                this.vrp();

                for(var i=0; i < this.kids.length; i++) {
                    if (this.kids[i].matrixSorted !== undefined) {
                        this.kids[i].matrixSorted(target, info);
                    }
                }
            };

            /**
             * Set the given editor provider. Editor provider is a way to customize
             * cell editing.
             * @param {Object} p an editor provider
             * @method setEditorProvider
             * @chainable
             */
            this.setEditorProvider = function(p){
                if (p !== this.editors){
                    this.stopEditing(true);
                    this.editors = p;
                }
                return this;
            };

            /**
             * Force to size grid columns and rows according to its preferred size
             * @param {Boolean} b use true to use preferred size
             * @method setUsePsMetric
             * @chainable
             */
            this.setUsePsMetric = function(b){
                if (this.isUsePsMetric !== b){
                    this.isUsePsMetric = b;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set the position controller.
             * @param {zebkit.util.Position} p a position controller
             * @method setPosition
             * @chainable
             */
            this.setPosition = function(p){
                if (this.position !== p){
                    if (this.position !== null) {
                        this.position.off(this);
                    }

                    /**
                     * Virtual cursor position controller
                     * @readOnly
                     * @attribute position
                     * @type {zebkit.util.Position}
                     */
                    this.position = p;
                    if (this.position !== null) {
                        this.position.on(this);
                        this.position.setMetric(this);
                    }
                    this.repaint();
                }

                return this;
            };

            /**
             * Set the given cell view provider. Provider is a special
             * class that says how grid cells content has to be rendered,
             * aligned, colored
             * @param {Object} p a view provider
             * @method setViewProvider
             * @chainable
             */
            this.setViewProvider = function(p){
                if (this.provider !== p){
                    this.provider = p;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set the given matrix model to be visualized and controlled
             * with the grid component
             * @param {zebkit.data.Matrix|Array} d a model passed as an
             * instance of  matrix model or an array that contains
             * model rows as embedded arrays.
             * @method setModel
             * @chainable
             */
            this.setModel = function(d){
                if (d !== this.model) {
                    this.clearSelect();
                    if (Array.isArray(d)) {
                        d = new this.clazz.Matrix(d);
                    }

                    if (this.model !== null) {
                        this.model.off(this);
                    }

                    this.model = d;
                    if (this.model !== null) {
                        this.model.on(this);
                    }

                    if (this.position !== null) {
                        this.position.setOffset(null);
                    }

                    this.vrp();
                }

                return this;
            };

            /**
             * Set the given top, left, right, bottom cell paddings
             * @param {Integer} p a top, left, right and bottom cell paddings
             * @method setCellPadding
             * @chainable
             */
            this.setCellPadding = function (p){
                return this.setCellPaddings(p,p,p,p);
            };

            /**
             * Set the given top, left, right, bottom cell paddings
             * @param {Integer} t a top cell padding
             * @param {Integer} l a left cell padding
             * @param {Integer} b a bottom cell padding
             * @param {Integer} r a right cell padding
             * @method setCellPaddings
             * @chainable
             */
            this.setCellPaddings = function (t,l,b,r){
                if (t !== this.cellInsetsTop    || l !== this.cellInsetsLeft ||
                    b !== this.cellInsetsBottom || r !== this.cellInsetsRight)
                {
                    this.cellInsetsTop = t;
                    this.cellInsetsLeft = l;
                    this.cellInsetsBottom = b;
                    this.cellInsetsRight = r;
                    this.vrp();
                }

                return this;
            };

            /**
             * Set the given color to render the grid vertical and horizontal lines
             * @param {String} c a color
             * @method setLineColor
             * @chainable
             */
            this.setLineColor = function (c){
                if (c !== this.lineColor){
                    this.lineColor = c;
                    if (this.drawVerLines || this.drawHorLines) {
                        this.repaint();
                    }
                }
                return this;
            };

            /**
             * Control rendering of grid lines on grid caption.
             * @param {Boolean} b a flag to control lines rendering on caption
             * @method setDrawCaptionLines
             * @chainable
             */
            this.setDrawCaptionLines = function (b){
                if (b !== this.drawCaptionLines){
                    this.drawCaptionLines = b;
                    this.vrp();
                }
                return this;
            };

            /**
             * Set the given grid lines size
             * @param {Integer} s a size
             * @method setLineSize
             * @chainable
             */
            this.setLineSize = function (s){
                if (s !== this.lineSize){
                    this.lineSize = s;
                    this.vrp();
                }
                return this;
            };

            /**
             * Start editing the given grid cell. Editing is initiated only if an editor
             * provider has been set and the editor provider defines not-null UI component
             * as an editor for the given cell.
             * @param  {Integer} row a grid cell row
             * @param  {Integer} col a grid cell column
             * @return {Boolean}  true if a cell editor has been initiated, otherwise
             * returns false.
             * @method startEditing
             */
            this.startEditing = function(row, col){
                this.stopEditing(true);
                if (this.editors !== null) {
                    var editor = this.editors.getEditor(this, row, col,
                                                        this.getDataToEdit(row, col));

                    if (editor !== null){
                        this.editingRow = row;
                        this.editingCol = col;
                        if (editor.isPopupEditor === true) {
                            var p = zebkit.layout.toParentOrigin(this.getColX(col) + this.scrollManager.getSX(),
                                                                 this.getRowY(row) + this.scrollManager.getSY(),
                                                                 this);

                            editor.setLocation(p.x, p.y);
                            ui.makeFullyVisible(this.getCanvas(), editor);
                            this.editor = editor;

                            var $this = this;
                            this.editor.winOpened = function(e) {
                                if (e.isShown === false){
                                    $this.stopEditing(e.source.isAccepted !== undefined ? e.source.isAccepted() : false);
                                }
                            };
                            ui.showModalWindow(this, editor, this);
                        } else {
                            this.add("editor", editor);
                            this.repaintRows(this.editingRow, this.editingRow);
                        }
                        ui.focusManager.requestFocus(editor);

                        return true;
                    }
                }
                return false;
            };

            /**
             * Fetch a data from matrix model that has to be edited
             * @param  {Integer} row a row
             * @param  {Integer} col a column
             * @return {Object} a matrix model data to be edited
             * @method getDataToEdit
             * @protected
             */
            this.getDataToEdit = function (row, col){
                return this.model.get(row, col);
            };

            /**
             * Apply the given edited data to grid matrix model
             * @param  {Integer} row a row
             * @param  {Integer} col a column
             * @param  {Object}  an edited matrix model data to be applied
             * @method setEditedData
             * @protected
             */
            this.setEditedData = function (row,col,value){
                this.model.put(row, col, value);
            };

            /**
             * Set the grid left caption titles
             * @param title* number of titles
             * @method setLeftCaption
             * @chainable
             */
            this.setLeftCaption = function() {
                if (this.leftCaption !== null) {
                    this.leftCaption.removeMe();
                }

                var a = Array.prototype.slice.call(arguments);
                this.add("left", this.$hasPanelIn(a) ? new pkg.CompGridCaption(a)
                                                     : new pkg.GridCaption(a));
                return this;
            };

            /**
             * Set the grid top caption titles
             * @param title* number of titles
             * @method setTopCaption
             * @chainable
             */
            this.setTopCaption = function() {
                if (this.topCaption !== null) {
                    this.topCaption.removeMe();
                }

                var a = Array.prototype.slice.call(arguments);
                this.add("top", this.$hasPanelIn(a) ? new pkg.CompGridCaption(a)
                                                    : new pkg.GridCaption(a));
                return this;
            };

            this.$hasPanelIn = function(a) {
                for(var i = 0; i < a.length; i++) {
                    if (zebkit.instanceOf(a[i], zebkit.ui.Panel)) {
                        return true;
                    }
                }

                return false;
            };
        },

        function focused() {
            this.$super();
            this.repaint();
        },

        function invalidate(){
            this.$super();
            this.iColVisibility(0);
            this.iRowVisibility(0);
        },

        function kidAdded(index, ctr, c){
            this.$super(index, ctr, c);

            if ((ctr === null && this.topCaption === null) || "top" === ctr){
                this.topCaption = c;
            } else if ("editor" === ctr) {
                this.editor = c;
            } else if ((ctr === null && this.leftCaption === null) || "left" === ctr) {
                this.leftCaption = c;
            } else if ((ctr === null && this.stub === null) || "corner" === ctr){
                this.stub = c;
            }
        },

        function kidRemoved(index, c, ctr) {
            this.$super(index, c, ctr);
            if (c === this.editor) {
                this.editor = null;
            } else if (c === this.topCaption) {
                this.topCaption = null;
            } else if (c === this.leftCaption){
                this.leftCaption = null;
            } else if (c === this.stub) {
                this.stub = null;
            }
        }

        /**
         *  Set number of views to render different grid component elements
         *  @param {Object} a set of views as dictionary where key is a view
         *  name and the value is a view instance, string (for color, border),
         *  or render function. The following view elements can be passed:
         *
         *
         *      {
         *         "focusOnSelect" : <view to render selected row for the grid that holds focus>,
         *         "focusOffSelect": <view to render selected row for the grid that doesn't hold focus>
         *      }
         *
         *
         *  @method  setViews
         */
    ]).events("selected");


    var ui = pkg.cd("..");

    /**
     * Special UI panel that manages to stretch grid columns to occupy the whole panel space.
     *
     *     ...
     *
     *     var canvas = new zebkit.ui.zCanvas(),
     *         grid = new zebkit.ui.grid.Grid(100,10),
     *         pan  = new zebkit.ui.grid.GridStretchPan(grid);
     *
     *     canvas.root.setBorderLayout();
     *     canvas.root.add("center", pan);
     *
     *     ...
     *
     * @constructor
     * @param {zebkit.ui.grid.Grid} grid a grid component that has to be added in the panel
     * @class zebkit.ui.grid.GridStretchPan
     * @extends zebkit.ui.Panel
     */
    pkg.GridStretchPan = Class(ui.Panel, [
        function (grid) {
            this.$super(this);

            this.grid = grid;

            this.$widths = [];
            this.$prevWidth = 0;
            this.$propW = -1;
            this.add(grid);
        },

        function $prototype() {
            this.$props = this.$strPs = null;

            /**
             * Target grid component
             * @type {zebkit.ui.Grid}
             * @readOnly
             * @attribute grid
             */
            this.grid = null;

            this.calcPreferredSize = function(target) {
                this.recalcPS();
                return (target.kids.length === 0 ||
                        target.grid.isVisible === false) ? { width:0, height:0 }
                                                         : { width:this.$strPs.width,
                                                             height:this.$strPs.height };
            };

            this.doLayout = function(target){
                this.recalcPS();
                if (target.kids.length > 0){
                    var grid = this.grid,
                        left = target.getLeft(),
                        top  = target.getTop();

                    if (grid.isVisible === true) {
                        grid.setBounds(left, top,
                                       target.width  - left - target.getRight(),
                                       target.height - top  - target.getBottom());

                        for(var i = 0; i < this.$widths.length; i++) {
                            grid.setColWidth(i, this.$widths[i]);
                        }
                    }
                }
            };

            this.captionResized = function(src, col, pw){

                console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!");

                if (col < this.$widths.length - 1) {
                    var grid = this.grid,
                        w    = grid.getColWidth(col),
                        dt   = w - pw;

                    if (dt < 0) {
                        grid.setColWidth(col + 1, grid.getColWidth(col + 1) - dt);
                    } else {
                        var ww = grid.getColWidth(col + 1) - dt,
                            mw = this.getMinWidth();

                        if (ww < mw) {
                            grid.setColWidth(col, w - (mw - ww));
                            grid.setColWidth(col + 1, mw);
                        } else {
                            grid.setColWidth(col + 1, ww);
                        }
                    }

                    this.$propW = -1;
                }
            };

            this.getMinWidth = function () {
                return zebkit.instanceOf(this.grid.topCaption, pkg.BaseCaption) ? this.grid.topCaption.minSize
                                                                                : 10;
            };

            this.calcColWidths = function(targetAreaW){
                var grid = this.grid,
                    cols = grid.getGridCols(),
                    ew   = targetAreaW - (cols + 1) * grid.lineSize,
                    sw   = 0;

                if (this.$widths.length !== cols) {
                    this.$widths = Array(cols);
                }

                for(var i = 0; i < cols; i++){
                    if (this.$props.length - 1 === i) {
                        this.$widths[i] = ew - sw;
                    } else {
                        this.$widths[i] = Math.round(ew * this.$props[i]);
                        sw += this.$widths[i];
                    }
                }
            };

            this.recalcPS = function() {
                var grid = this.grid;
                if (grid !== null && grid.isVisible === true) {
                    // calculate size excluding padding where
                    // the target grid columns have to be stretched
                    var p        = this.parent,
                        isScr    = zebkit.instanceOf(p, ui.ScrollPan),
                        taWidth  = (isScr ? p.width - p.getLeft() - p.getRight() - this.getRight() - this.getLeft()
                                          : this.width - this.getRight() - this.getLeft()),
                        taHeight = (isScr ? p.height - p.getTop() - p.getBottom() - this.getBottom() - this.getTop()
                                          : this.height - this.getBottom() - this.getTop());

                    // exclude left caption
                    if (this.grid.leftCaption !== null &&
                        this.grid.leftCaption.isVisible === true)
                    {
                        taWidth -= (this.grid.leftCaption.getPreferredSize().width + this.grid.lineSize);
                    }

                    taWidth -= (this.grid.getLeft() + this.grid.getRight());

                    console.log("GridStretchPan.recalcPS(): " + taWidth + "," + this.$prevWidth);


                    if (this.$strPs === null || this.$prevWidth !== taWidth) {
                        var cols = grid.getGridCols();

                        if (this.$propW < 0 || this.$props === null || this.$props.length !== cols) {

                            console.log("Grid cols: " + cols);

                            // calculate col proportions
                            if (this.$props === null || this.$props.length !== cols) {
                                this.$props = Array(cols);
                            }
                            this.$propW = 0;

                            var i = 0, w = 0;
                            for(i = 0; i < cols; i++){
                                w = grid.getColWidth(i);

                                console.log(" >>> col width[" + i + "] = " + w);

                                if (w === 0) {
                                    w = grid.getColPSWidth(i);
                                }
                                this.$propW += w;
                            }

                            for(i = 0; i < cols; i++) {
                                w = grid.getColWidth(i);
                                if (w === 0) {
                                    w = grid.getColPSWidth(i);
                                }
                                this.$props[i] = w / this.$propW;
                            }
                        }

                        this.$prevWidth  = taWidth;
                        this.calcColWidths(taWidth);
                        this.$strPs  = {
                            width : taWidth,
                            height: grid.getPreferredSize().height
                        };

                        // check if the calculated height is greater than
                        // height of the parent component and re-calculate
                        // the metrics if vertical scroll bar is required
                        // taking in account horizontal reduction because of
                        // the scroll bar visibility
                        if (isScr === true &&
                            p.height > 0 &&
                            (p.vBar !== undefined || p.vBar === null) &&
                            p.autoHide === false &&
                            taHeight < this.$strPs.height)
                        {
                            taWidth -= p.vBar.getPreferredSize().width;
                            this.calcColWidths(taWidth);
                            this.$strPs.width = taWidth;
                        }
                    }
                }
            };
        },

        function kidAdded(index,constr,l){
            this.$propsW = -1;
            if (l.topCaption !== null) {
                l.topCaption.on(this);
            }
            this.scrollManager = l.scrollManager;
            this.$super(index, constr, l);
        },

        function kidRemoved(i, l, ctr){
            this.$propsW = -1;
            if (l.topCaption !== null) {
                l.topCaption.off(this);
            }
            this.scrollManager = null;
            this.$super(i, l, ctr);
        },

        function invalidate(){
            this.$strPs = null;
            this.$super();
        }
    ]);
},true);
zebkit.package("ui.design", function(pkg, Class) {
    var ui = pkg.cd("..");

    /**
     * The package contains number of UI components that can be helpful to
     * perform visual control of an UI component. You can control an UI component
     * size and location.
     *
     *     var root = (new zebkit.ui.zCanvas(400, 300)).root;
     *     root.setRasterLayout();
     *     root.setPadding(8);
     *
     *     // Add check box component wrapped with shaper panel
     *     // to control the component size and location
     *     var ch = new zebkit.ui.Checkbox("Check-box")
     *                           .setBounds(10, 10, 100, 30);
     *
     *     root.add(new zebkit.ui.design.ShaperPan(ch));
     *
     * @class  zebkit.ui.design
     * @access package
     */
    var CURSORS = {
        left        : ui.Cursor.W_RESIZE,
        right       : ui.Cursor.E_RESIZE,
        top         : ui.Cursor.N_RESIZE,
        bottom      : ui.Cursor.S_RESIZE,
        topLeft     : ui.Cursor.NW_RESIZE,
        topRight    : ui.Cursor.NE_RESIZE,
        bottomLeft  : ui.Cursor.SW_RESIZE,
        bottomRight : ui.Cursor.SE_RESIZE,
        center      : ui.Cursor.MOVE,
        none        : ui.Cursor.DEFAULT
    };

    /**
     * A designer border view. The border view visually indicates areas
     * of border with different size possibilities. The border logically
     * split area around a component to number of predefined areas such
     * as: "center", "bottom", "right", "left", "topRight", "topLeft",
     * "bottomLeft", "bottomRight", "none". See illustration below:
     *
     *
     *      |topLeft|-----------| top |-------------|topRight|
     *          |                                        |
     *          |                                        |
     *      |  left |            center             |  right |
     *          |                                        |
     *          |                                        |
     *      |bottomLeft|-------|bottom|-------------|bottomRight|
     *
     *
     * @param {String} [color] a bordar color
     * @param {Integer} [gap] a bordar gap
     * @constructor
     * @class zebkit.ui.design.ShaperBorder
     * @extends zebkit.draw.View
     */
    pkg.ShaperBorder = Class(zebkit.draw.View, [
        function(color, gap) {
            if (arguments.length > 0) {
                this.color = color;
                if (arguments.length > 1) {
                    this.gap = gap;
                }
            }
        },

        function $prototype() {
            /**
             * Border color
             * @attribute color
             * @type {String}
             * @default "blue"
             */
            this.color = "blue";

            /**
             * Border gap.
             * @attribute gap
             * @type {Number}
             * @default 7
             */
            this.gap = 8;

            function contains(x, y, gx, gy, ww, hh) {
                return gx <= x && (gx + ww) > x && gy <= y && (gy + hh) > y;
            }

            this.paint = function(g,x,y,w,h,d) {
                if (this.color !== null) {

                    var cx = Math.floor((w - this.gap)/2),
                        cy = Math.floor((h - this.gap)/2);

                    g.setColor(this.color);
                    g.beginPath();
                    g.rect(x, y, this.gap, this.gap);
                    g.rect(x + cx, y, this.gap, this.gap);
                    g.rect(x, y + cy, this.gap, this.gap);
                    g.rect(x + w - this.gap, y, this.gap, this.gap);
                    g.rect(x, y + h - this.gap, this.gap, this.gap);
                    g.rect(x + cx, y + h - this.gap, this.gap, this.gap);
                    g.rect(x + w - this.gap, y + cy, this.gap, this.gap);
                    g.rect(x + w - this.gap, y + h - this.gap, this.gap, this.gap);
                    g.fill();

                    g.beginPath();

                    // very strange thing with rect() method if it called with w or h
                    // without decreasing with gap it is ok, otherwise moving   a
                    // component with the border outside parent component area leaves
                    // traces !
                    //
                    // adding 0.5 (to center line) solves the problem with traces
                    g.rect(x + Math.floor(this.gap / 2) + 0.5,
                           y + Math.floor(this.gap / 2) + 0.5,
                           w - this.gap,
                           h - this.gap );

                    g.stroke();
                }
            };

            /**
             * Detect area type by the given location of the given component
             * @param  {zebkit.ui.Panel} target a target component
             * @param  {Integer} x a x coordinate
             * @param  {Integer} y an y coordinate
             * @return {String} a detected area type
             * @protected
             * @method detectAt
             */
            this.detectAt = function(target, x, y) {
                if (contains(x, y, this.gap, this.gap, target.width - 2 * this.gap, target.height - 2 * this.gap)) {
                    return "center";
                }

                if (contains(x, y, 0, 0, this.gap, this.gap)) {
                    return "topLeft";
                }

                if (contains(x, y, 0, target.height - this.gap, this.gap, this.gap)) {
                    return "bottomLeft";
                }

                if (contains(x, y, target.width - this.gap, 0, this.gap, this.gap)) {
                    return "topRight";
                }

                if (contains(x, y, target.width - this.gap, target.height - this.gap, this.gap, this.gap)) {
                    return "bottomRight";
                }

                var mx = Math.floor((target.width - this.gap) / 2);
                if (contains(x, y, mx, 0, this.gap, this.gap)) {
                    return "top";
                }

                if (contains(x, y, mx, target.height - this.gap, this.gap, this.gap)) {
                    return "bottom";
                }

                var my = Math.floor((target.height - this.gap) / 2);
                if (contains(x, y, 0, my, this.gap, this.gap)) {
                    return "left";
                }

                return contains(x, y, target.width - this.gap, my, this.gap, this.gap) ? "right"
                                                                                       : null;
            };
        }
    ]);

    pkg.DesignPan = Class(ui.Panel, [
        function() {
            this.statusBar    = new ui.StatusBarPan();
            this.inspectorPan = new ui.Panel();
            this.compsPan     = new ui.Panel([
                function() {
                    this.shaper = new pkg.ShaperPan();
                    this.$super();

                    var $this = this;
                    this.shaper.on("moved", function(t, px, py) {
                        $this.repaint();
                    });
                },

                function catchInput(c) {
                    return zebkit.instanceOf(c, pkg.ShaperPan) === false;
                },

                function getPopup(t, x, y) {
                    var c = this.getComponentAt(x, y);
                    console.log(":::: " + c.clazz.$name);
                    if (c !== null && c !== this) {
                        return new ui.Menu([
                            "Remove ",
                            "To preferred size",
                            "-",
                            "Properties"
                        ]);
                    }
                    return null;
                },

                function pointerClicked(e) {
                    var c = this.getComponentAt(e.x, e.y);
                    if (c !== null && c !== this && zebkit.instanceOf(c.parent, pkg.ShaperPan) === false) {
                        c = zebkit.layout.getDirectChild(this, c);
                        this.shaper.setValue(c);
                        this.shaper.setState("selected");
                    }
                },

                function paintOnTop(g) {
                    if (this.shaper.isSelected()) {
                        var tx = this.shaper.getValue().x ,
                            ty = this.shaper.getValue().y;

                        console.log("!!! " + tx);

                        for (var i = 0; i < this.kids.length; i++) {
                            var kid = this.kids[i];

                            if (this.shaper.getValue() !== kid && kid.x === tx) {
                                g.setColor("blue");
                                g.drawLine(tx, ty, tx, kid.y)
                            }
                        }
                    }
                }
            ]);

            this.statusBar.add(10, "(x,y) = 1");
            this.statusBar.add("|");
            this.statusBar.add(10, "(x,y) = 2");
            this.statusBar.add("|");
            this.statusBar.add(10, "(x,y) = 3");
           // this.statusBar.add(10, new  this.statusBar.clazz.Combo([ "Item 1", "Item 2", "Item 3"]));
            this.statusBar.addCombo(10, [ "Item 1", "Item 2", "Item 3"]);

            this.$super();
            this.setBorderLayout();
            this.add(new ui.SplitPan(this.inspectorPan, this.compsPan));
            this.add("bottom", this.statusBar);


            for(var i = 0; i < arguments.length; i++) {
                this.compsPan.add(arguments[i]);
            }
        }
    ]);

    /**
     * This is UI component class that implements possibility to embeds another
     * UI components to control the component size and location visually.
     *
     *       // create canvas
     *       var canvas = new zebkit.ui.zCanvas(300,300);
     *
     *       // create two UI components
     *       var lab = new zebkit.ui.Label("Label");
     *       var but = new zebkit.ui.Button("Button");
     *
     *       // add created before label component as target of the shaper
     *       // component and than add the shaper component into root panel
     *       canvas.root.add(new zebkit.ui.design.ShaperPan(lab).properties({
     *           bounds: [ 30,30,100,40]
     *       }));
     *
     *       // add created before button component as target of the shaper
     *       // component and than add the shaper component into root panel
     *       canvas.root.add(new zebkit.ui.design.ShaperPan(but).properties({
     *           bounds: [ 130,130,100,50]
     *       }));
     *
     * @class  zebkit.ui.design.ShaperPan
     * @constructor
     * @extends zebkit.ui.Panel
     * @param {zebkit.ui.Panel} [target] a target UI component whose size and location
     * has to be controlled
     */
    pkg.ShaperPan = Class(ui.StatePan, ui.ApplyStateProperties, [
        function(t) {
            this.border = new pkg.ShaperBorder();
            this.$super();
            if (arguments.length > 0) {
                this.setValue(t);
            }
        },

        function $prototype() {
            this.layout = new zebkit.layout.BorderLayout();

           /**
            * Indicates if controlled component can be moved
            * @attribute isMoveEnabled
            * @type {Boolean}
            * @default true
            */
           this.isMoveEnabled = true;

           /**
            * Indicates if controlled component can be sized
            * @attribute isResizeEnabled
            * @type {Boolean}
            * @default true
            */
            this.isResizeEnabled = true;

            /**
             * Minimal possible height or controlled component
             * @attribute minHeight
             * @type {Integer}
             * @default 12
             */
            this.minHeight = 12;

            /**
             * Minimal possible width or controlled component
             * @attribute minWidth
             * @type {Integer}
             * @default 12
             */
            this.minWidth = 12;


            /**
             * Resize aspect ratio (width/height). 0 value means no aspect ratio
             * has been defined.
             * @attribute aspectRatio
             * @type {Number}
             * @default 0
             */
            this.aspectRatio = 0; //2/3;


            this.$cursorState     = null;
            this.$dragCursorState = null;
            this.$px = 0;
            this.$py = 0;
            this.$targetParent = null;

            this.catchInput       = true;

            this.canHaveFocus     = true;

            this.$detectAt = function(t, x, y) {
                if (this.border !== null && this.border.detectAt !== undefined) {
                    return this.border.detectAt(t, x, y);
                } else {
                    return null;
                }
            };

            this.getCursorType = function (t, x ,y) {
                this.$cursorState = this.$detectAt(t, x, y);

                if (this.$cursorState === null) {
                    return null
                } else if (this.$cursorState === "center")  {
                    if (this.isMoveEnabled === false) {
                        return null;
                    }
                } else {
                    if (this.isResizeEnabled === false) {
                        return null;
                    }
                }

                var cur = CURSORS[this.$cursorState];
                return cur === undefined ? null : cur;
            };

            this.pointerExited = function() {
                this.$dragCursorState = this.$cursorState = null;
            };

            /**
             * Define key pressed events handler
             * @param  {zebkit.ui.event.KeyEvent} e a key event
             * @method keyPressed
             */
            this.keyPressed = function(e) {
                if (this.kids.length > 0){
                    var dx = (e.code === "ArrowLeft" ? -1 : (e.code === "ArrowRight" ? 1 : 0)),
                        dy = (e.code === "ArrowUp"   ? -1 : (e.code === "ArrowDown"  ? 1 : 0)),
                        w  = this.width  + dx,
                        h  = this.height + dy,
                        x  = this.x + dx,
                        y  = this.y + dy;

                    if (e.shiftKey) {
                        var minW = this.border !== null ? this.border.getLeft() + this.border.getRight()
                                                        : 10,
                            minH = this.border !== null ? this.border.getTop() + this.border.getBottom()
                                                        : 10;

                        if (this.isResizeEnabled === true && w > minW && h > minH) {
                            this.setSize(w, h);
                        }
                    } else if (this.isMoveEnabled) {
                        if (x + this.width/2  > 0 &&
                            y + this.height/2 > 0 &&
                            x < this.parent.width  - this.width/2  &&
                            y < this.parent.height - this.height/2    )
                        {
                            this.setLocation(x, y);
                        }
                    }
                }
            };

            /**
             * Define pointer drag started events handler
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerDragStarted
             */
            this.pointerDragStarted = function(e) {
                this.$dragCursorState = null;
                if (this.$cursorState !== null && (this.isResizeEnabled || this.isMoveEnabled)) {
                    if ((this.isMoveEnabled   && this.$cursorState === "center") ||
                        (this.isResizeEnabled && this.$cursorState !== "center")   )
                    {
                        this.$px = e.absX;
                        this.$py = e.absY;
                        this.$dragCursorState = this.$cursorState;
                    }
                }
            };

            /**
             * Define pointer dragged events handler
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerDragged
             */
            this.pointerDragged = function(e){
                if (this.$dragCursorState !== null) {
                    var dy = (e.absY - this.$py),
                        dx = (e.absX - this.$px),
                        s  = this.$dragCursorState;

                    this.$px = e.absX;
                    this.$py = e.absY;

                    if (s === "center") {
                        this.setLocation(this.x + dx, this.y + dy);
                    } else {
                        var m = { top    : (s === "top"    || s === "topLeft"     || s === "topRight"   ) ? 1 : 0,
                                  left   : (s === "left"   || s === "topLeft"     || s === "bottomLeft" ) ? 1 : 0,
                                  right  : (s === "right"  || s === "topRight"    || s === "bottomRight") ? 1 : 0,
                                  bottom : (s === "bottom" || s === "bottomRight" || s === "bottomLeft" ) ? 1 : 0 },
                            nh = this.height - dy * m.top  + dy * m.bottom,
                            nw = this.width  - dx * m.left + dx * m.right;

                        this.setBounds(this.x + m.left * dx, this.y + m.top  * dy, nw, nh);
                    }
                }
            };

            this.pointerDragEnded = function(e) {
                this.$px = this.$py = 0;
                this.$dragCursorState = null;
            };

            /**
             * Set the border color for the given focus state.
             * @param {String} id a focus state. Use "focuson" or "focusoff" as the
             * parameter value
             * @param {String} color a border color
             * @method setBorderColor
             * @chainable
             */
            this.setBorderColor = function(color) {
                if (this.border !== null && this.border.color !== color) {
                    this.border.color = color;
                    this.repaint();
                }
                return this;
            };

            /**
             * Get a component whose shape is controlled
             * @return {zebkit.ui.Panel} a controlled component
             * @method getValue
             */
            this.getValue = function() {
                if (this.kids.length > 0) {
                    var t = this.byConstraints(null) || this.byConstraints("center");
                    return t;
                } else {
                    return null;
                }
            };

            /**
             * Set the controlled with the shape controller component.
             * @param {zebkit.ui.Panel} v a component to be controlled
             * @method setValue
             * @chainable
             */
            this.setValue = function(v) {
                var ov = this.getValue();
                if (ov !== v) {
                    var top  = this.getTop(),
                        left = this.getLeft();

                    if (ov !== null) {
                        ov.removeMe();

                        // attach the target back to its parent
                        if (this.$targetParent !== null) {
                            this.removeMe();
                            ov.setBounds(this.x + ov.x, this.y + ov.y, ov.width, ov.height);
                            this.$targetParent.add(ov);
                        }
                    }

                    this.$targetParent = null;
                    if (v !== null) {
                        if (v.parent !== null) {
                            // detach the shaper from old parent
                            if (this.parent !== null && this.parent !== v.parent) {
                                this.removeMe();
                            }

                            // save target parent and detach it from it
                            this.$targetParent = v.parent;
                            v.removeMe();

                            // add the shaper to the target parent
                            this.$targetParent.add(this);
                        }

                        // calculate location and size the shaper requires
                        // taking in account gaps
                        if (v.width === 0 || v.height === 0) {
                            v.toPreferredSize();
                        }

                        // set shaper bounds
                        this.setBounds(v.x - left, v.y - top,
                                       v.width + left + this.getRight(),
                                       v.height + top + this.getBottom());

                        this.add(v);
                    }
                }
                return this;
            };

            this.isSelected = function() {
                return this.state === "selected";
            };
        },

        function setSize(w, h) {
            if (this.aspectRatio !== 0) {
                w = Math.floor(h * this.aspectRatio);
            }

            if (w >= this.minWidth && h >= this.minHeight && (this.width !== w || this.height !== h)) {
                var pw = this.width,
                    ph = this.height;

                this.$super(w, h);
                this.fire("sized", [ this, (w - pw), (h - ph) ]);
            }
            return this;
        },

        function setLocation(x, y) {
            if (this.x !== x || this.y !== y) {
                var px = this.x,
                    py = this.y;

                this.$super(x, y);
                this.fire("moved", [ this, (x - px), (y - py) ]);
            }
            return this;
        },

        function setState(s) {
            var prev = this.state;
            this.$super(s);

            if (prev !== this.state) {
                if (this.state === "selected") {
                    this.fire("selected", [ this, true ]);
                } else if (prev === "selected") {
                    this.fire("selected", [ this, false ]);
                }
            }

            return this;
        },

        function focused() {
            this.$super();
            this.setState(this.hasFocus() ? "selected"
                                          : "unselected" );
        },

        function kidRemoved(i, kid, ctr) {
            if (ctr === null || ctr === "center") {
                this.fire("detached", [this, kid]);
            }
        },

        function kidAdded(i, constr, d) {
            if (constr === null || constr === "center") {
                this.fire("attached", [ this, d ]);
            }
        }
    ]).events("selected", "sized", "moved", "attached", "detached");

    /**
     * Special tree model implementation that represents zebkit UI component
     * hierarchy as a simple tree model.
     * @param  {zebkit.ui.Panel} target a root UI component
     * @constructor
     * @class zebkit.ui.design.FormTreeModel
     * @extends zebkit.data.TreeModel
     */
    pkg.FormTreeModel = Class(zebkit.data.TreeModel, [
        function (target) {
            this.$super(this.buildModel(target, null));
        },

        function $prototype() {
            /**
             * Build tree model by the given UI component.
             * @param  {zebkit.ui.Panel} comp a component
             * @return {zebkit.data.Item} a root tree model item
             * @method buildModel
             */
            this.buildModel = function(comp, root){
                var b    = this.exclude !== undefined && this.exclude(comp),
                    item = b ? root : this.createItem(comp);

                for(var i = 0; i < comp.kids.length; i++) {
                    var r = this.buildModel(comp.kids[i], item);
                    if (r !== null) {
                        r.parent = item;
                        item.kids.push(r);
                    }
                }
                return b ? null : item;
            };

            /**
             * Find a tree item that relates to the given component.
             * @param  {zebkit.ui.Panel} c a component.
             * @return {zebkit.data.Item} a tree item.
             * @method itemByComponent
             */
            this.itemByComponent = function (c, r) {
                if (arguments.length < 2) {
                    r = this.root;
                }

                if (r.comp === c) {
                    return c;
                } else {
                    for(var i = 0; i < r.kids.length; i++) {
                        var item = this.itemByComponent(c, r.kids[i]);
                        if (item !== null) {
                            return item;
                        }
                    }
                    return null;
                }
            };

            this.createItem = function(comp){
                var name = comp.clazz.$name;
                if (name === undefined) {
                    name = comp.toString();
                }

                var index = name.lastIndexOf('.'),
                    item = new zebkit.data.Item(index > 0 ? name.substring(index + 1) : name);

                item.comp = comp;
                return item;
            };
        }
    ])

}, true);
zebkit.package("web", function(pkg, Class) {
    'use strict';
    /**
     * Web specific stuff to provide abstracted method to work in WEB context.
     * @class zebkit.web
     * @access package
     */

    /**
     * Device ratio.
     * @attribute $deviceRatio
     * @readOnly
     * @private
     * @type {Number}
     */
    pkg.$deviceRatio = window.devicePixelRatio !== undefined ? window.devicePixelRatio
                                                             : (window.screen.deviceXDPI !== undefined ? window.screen.deviceXDPI / window.screen.logicalXDPI // IE
                                                                                                       : 1);

    pkg.$windowSize = function() {
        // iOS retina devices can have a problem with performance
        // in landscape mode because of a bug (full page size is
        // just 1 pixels column more than video memory that can keep it)
        // So, just make width always one pixel less.
        return  {
            width : window.innerWidth, //   - 1,
            height: window.innerHeight
        };
    };

    /**
     * Calculates view port of a browser window
     * @return {Object} a browser window view port size.
     *
     *    ```json
     *    {
     *      width : {Integer},
     *      height: {Integer}
     *    }
     *    ```
     *
     * @method $viewPortSize
     * @for  zebkit.web
     * @private
     */
    pkg.$viewPortSize = function() {
        var ws   = pkg.$windowSize(),
            body = document.body,
            css  = [ "margin-left", "margin-right", "margin-top", "margin-bottom",
                     "padding-left", "padding-right", "padding-top", "padding-bottom",
                     "border-left-width", "border-right-width", "border-top-width", "border-bottom-width"];

        for(var i = 0; i < css.length;) {
            ws.width  -= (pkg.$measure(body, css[i++]) + pkg.$measure(body, css[i++]));
            ws.height -= (pkg.$measure(body, css[i++]) + pkg.$measure(body, css[i++]));
        }
        return ws;
    };

    pkg.$measure = function(e, cssprop) {
        var value = window.getComputedStyle(e, null).getPropertyValue(cssprop);
        return (value === null || value === '') ? 0
                                                : parseInt(/(^[0-9\.]+)([a-z]+)?/.exec(value)[1], 10);
    };


    /**
     * Tests if the given DOM element is in document
     * @private
     * @param  {Element} element a DOM element
     * @return {Boolean} true if the given DOM element is in document
     * @method $contains
     * @for  zebkit.web
     */
    pkg.$contains = function(element) {
        // TODO: not sure it is required, probably it can be replaced with document.body.contains(e);
        return (document.contains !== undefined && document.contains(element)) ||
               (document.body.contains !== undefined && document.body.contains(element)); // !!! use body for IE
    };

    /**
     * Test if the given page coordinates is inside the given element
     * @private
     * @param  {Element} element a DOM element
     * @param  {Number} pageX an x page coordinate
     * @param  {Number} pageY an y page coordinate
     * @return {Boolean} true if the given point is inside the specified DOM element
     * @method $isInsideElement
     */
    pkg.$isInsideElement = function(element, pageX, pageY) {
        var r = element.getBoundingClientRect();
        return r !== null            &&
               pageX >= r.left       &&
               pageY >= r.top        &&
               pageX <= r.right - 1  &&
               pageY <= r.bottom - 1   ;
    };

    var $focusInOutSupported = (function() {
        var support = false,
            parent  = document.lastChild,
            a       = document.createElement('a');

        a.href = '#';
        a.setAttribute("style", "position:fixed;left:-99em;top:-99em;");
        a.addEventListener('focusin', function() {
            support = true;
        });

        parent.appendChild(a).focus();
        parent.removeChild(a);
        return support;
    })();

    pkg.$focusin = function(element, f, b) {
        return element.addEventListener($focusInOutSupported ? "focusin" : "focus", f, b);
    };

    pkg.$focusout = function(element, f, b) {
        return element.addEventListener($focusInOutSupported ? "focusout" : "blur", f, b);
    };

    pkg.$eventsBlackHole = function(e) {
        e.preventDefault();
        e.stopPropagation();
    };

    /**
     * Creates HTML element that "eats" (doesn't propagate and prevents default) all input (touch, mouse, key)
     * events that it gets.
     * @return {HTMLElement} a created HTML element.
     * @method  $createBlockedElement
     * @protected
     * @for  zebkit.web
     */
    pkg.$createBlockedElement = function() {
        var be = document.createElement("div");
        be.style.height = be.style.width  = "100%";
        be.style.left = be.style.top = "0px";
        be.style.position = "absolute";
        be.style["z-index"] = "100000";
        be.setAttribute("zebkit", "blockedElement");

        be.onmouseup   = be.onmousedown = be.onmouseout =
        be.onmouseover = be.onmousemove = be.onkeydown  =
        be.onkeypress  = be.onkeyup = pkg.$eventsBlackHole;

        var events = [ "touchstart", "touchend", "touchmove",
                       "pointerdown", "pointerup", "pointermove",
                       "pointerenter", "pointerleave" ];

        for(var i = 0 ; i < events.length ; i++ ) {
           be.addEventListener(events[i], pkg.$eventsBlackHole, false);
        }

        return be;
    };

    /**
     * Extend standard 2D HTML Canvas context instance with the given set of methods.
     * If new methods clash with already existent 2D context method the old one is overwritten
     * with new one and old method is saved using its name prefixed with "$" character
     * @param  {CanvasRenderingContext2D} ctx  a 2D HTML Canvas context instance
     * @param  {Array} methods list of methods to be added to the context
     * @method $extendContext
     * @private
     */
    pkg.$extendContext = function(ctx, methods) {
        for(var k in methods) {
            if (k === "$init") {
                methods[k].call(ctx);
            } else {
                var old = ctx[k];
                if (old !== undefined) {
                    var kk = "$" + k;
                    if (ctx[kk] === undefined) {
                        ctx[kk] = old;
                    }
                }
                ctx[k] = methods[k];
            }
        }
    };

    /**
     * Adjusts the given HTML Canvas element to the required size that takes in account device DPI.
     * Extend the canvas 2D context with extra methods and variables that are used with zebkit UI
     * engine.
     * @param  {HTMLCanvasElement} c a HTML canvas element
     * @param  {Integer} w  a required width of the given canvas
     * @param  {Integer} h  a required height of the given canvas
     * @param  {Boolean} [forceResize] flag to force canvas resizing even if the canvas has identical width and height.
     * It is required to re-create canvas 2D context to work properly.
     * @return {CanvasRenderingContext2D} a 2D context of the canvas element
     * @method $canvas
     * @protected
     * @for  zebkit.web
     */
    pkg.$canvas = function(c, w, h, forceResize) {
        // fetch current CSS size of canvas
        var cs = window.getComputedStyle(c, null),
            cw = parseInt(cs.getPropertyValue("width"),  10),
            ch = parseInt(cs.getPropertyValue("height"), 10),
            ctx = c.getContext("2d"),
            updateRatio = false;

        // if CSS width or height has not been set for the canvas
        // it has to be done, otherwise scaling on hi-DPI screen
        // will not work
        if (isNaN(parseInt(c.style.width ))||
            isNaN(parseInt(c.style.height))  )
        {
            c.style.width  = "" + cw + "px";
            c.style.height = "" + ch + "px";
            updateRatio = true;
        }

        // setup new canvas CSS size if appropriate width and height
        // parameters have been passed and they don't match current CSS
        // width and height
        if (arguments.length > 1) {
            if (cw !== w || ch !== h) {
                c.style.width  = "" + w + "px";
                c.style.height = "" + h + "px";
                updateRatio = true;
            }
            cw = w;
            ch = h;
        }

        // canvas 2D context is singleton so check if the
        // context has already been modified to prevent
        // redundancy
        if (ctx.$ratio === undefined) {
            ctx.$ratio = (ctx.webkitBackingStorePixelRatio ||   // backing store ratio
                          ctx.mozBackingStorePixelRatio    ||
                          ctx.msBackingStorePixelRatio     ||
                          ctx.backingStorePixelRatio       ||
                          ctx.backingStorePixelRatio       || 1);

            ctx.$getImageData = ctx.getImageData;
            ctx.$scale        = ctx.scale;          // save original method if at some stage
                                                    // it will be overridden (zebkit does it)
                                                    // only original method has to be used to
                                                    // adjust canvas to screen DPI
            if (pkg.$deviceRatio != ctx.$ratio) {
                var r = pkg.$deviceRatio / ctx.$ratio;
                ctx.getImageData= function(x, y, w, h) {
                    return this.$getImageData(x * r, y * r, w, h);
                };
            }

            // populate extra method to 2D context
            pkg.$extendContext(ctx, zebkit.draw.Context2D);
        }

        ctx.$scaleRatio      = 1;
        ctx.$scaleRatioIsInt = true;

        // take in account that canvas can be visualized on
        // Retina screen where the size of canvas (backstage)
        // can be less than it is real screen size. Let's
        // make it match each other
        if (ctx.$ratio != pkg.$deviceRatio) {
            var ratio = ctx.$ratio !== 1 ? pkg.$deviceRatio / ctx.$ratio
                                         : pkg.$deviceRatio;


            if (Number.isInteger(ratio)) {
                cw = cw * ratio;
                ch = ch * ratio;
            } else {
                if (pkg.config("approximateRatio") === true) {
                    ratio = Math.round(ratio);
                    cw = cw * ratio;
                    ch = ch * ratio;
                } else {
                    // adjust ratio
                    //  -- get adjusted with ratio width
                    //  -- floor it and re-calculate ratio again
                    //  -- the result is slightly corrected ratio that fits better
                    //  to keep width as integer
                    ratio = Math.floor(cw * ratio) / cw;
                    cw = Math.floor(cw * ratio);
                    ch = Math.floor(ch * ratio);
                    ctx.$scaleRatioIsInt = Number.isInteger(ratio);
                }
            }

            ctx.$scaleRatio = ratio;

            // adjust canvas size if it is necessary
            if (c.width != cw || c.height != ch || updateRatio === true || forceResize === true) {
                c.width  = cw;
                c.height = ch;
                ctx.$scale(ratio, ratio);
            }
        } else if (c.width != cw || c.height != ch || forceResize === true) { // adjust canvas size if it is necessary
            c.width  = cw;
            c.height = ch;
        }

        // TODO: top works not good in FF and it is better don't use it
        // So, ascent has to be taking in account as it was implemented
        // before
        if (ctx.textBaseline !== "top" ) {
            ctx.textBaseline = "top";
        }

        return ctx;
    };


    //  zebkit dependencies:
    //      -- zebkit.ui.event.Clipboard
    //      -- zebkit.web.$fetchKeyCode
    //
    //

    // IE doesn't allow standard window.Event instantiation
    // this is a workaround to avoid the problem
    function CustomEvent(event, params ) {
        params = params || { bubbles: false, cancelable: false, detail: undefined };
        var evt = document.createEvent( 'CustomEvent' );
        evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
        return evt;
    }
    CustomEvent.prototype = window.Event.prototype;

    function $dupKeyEvent(e, id, target)  {
        var k = new CustomEvent(id);
        k.keyCode   = e.keyCode;
        k.key       = e.key;
        k.code      = e.code;
// TODO: cannot be set in strict mode and most likely it is set with dispactEvent() function
// properly
//        k.target    = target;
        k.ctrlKey   = e.ctrlKey;
        k.altKey    = e.altKey;
        k.shiftKey  = e.shiftKey;
        k.metaKey   = e.metaKey;
        k.which     = e.which;

      // TODO: cannot be set in strict mode and most likely it is set with dispactEvent() function
      // properly
      //  k.timeStamp = e.timeStamp;
        return k;
    }

    /**
     * Clipboard support class. The class is light abstraction that helps to perform
     * textual data exchange via system (browser) clipboard. Browsers have different approaches
     * and features regarding clipboard implementation and clipboard API. This class
     * hides the native specific and provides simple way to exchange data via clipboard.
     * @param  {String} [triggerKeyCode] a key code that starts triggering clipboard copy
     * paste actions. It depends on platform. On Linux "Control" + <xxx> combination
     * should be used, but on Mac OSX "MetaLeft" + xxx.
     * To handle copy, paste and cut event override the following methods:
     *    - **copy**   "clipCopy(focusOwnerComponent, data)"
     *    - **paste**  "clipPaste(focusOwnerComponent, data)"
     *    - **cut**    "clipCut(focusOwnerComponent, data)"
     * @constructor
     * @class zebkit.web.Clipboard
     * @extends zebkit.ui.event.Clipboard
     */
    pkg.Clipboard = Class(zebkit.ui.event.Clipboard, [
        function(triggerKeyCode) {
            if (document.getElementById(this.clazz.id) !== null) {
                throw new Error("Duplicated clipboard element");
            }

            if (arguments.length > 0 && triggerKeyCode !== null) {
                this.triggerKeyCode = triggerKeyCode;
            } else {
                this.triggerKeyCode = zebkit.isMacOS ? "MetaLeft"
                                                     : "Control";
            }

            if (this.triggerKeyCode !== null) {
                this.$clipboard = document.createElement("textarea");
                this.$clipboard.setAttribute("style", "display:none;position:fixed;left:-99em;top:-99em;");
                this.$clipboard.setAttribute("id", this.clazz.id);

                this.$element = null;

                var $this = this;

                window.addEventListener("keydown", function(e) {
                    var dest = $this.getDestination();
                    if (dest !== null) {
                        if (dest.clipCopy !== undefined || dest.clipPaste !== undefined) {
                            if (zebkit.web.$fetchKeyCode(e) === $this.triggerKeyCode) {
                                // value has to be set, otherwise some browsers (Safari) do not generate
                                // "copy" event
                                $this.$on("1");
                            }
                        }
                    }
                }, true);

                this.$clipboard.onkeydown = function(ee) {
                    $this.$element.dispatchEvent($dupKeyEvent(ee, 'keydown', this.$element));
                    $this.$clipboard.value = "1";
                    $this.$clipboard.select();
                };

                this.$clipboard.onkeyup = function(ee) {
                    if (zebkit.web.$fetchKeyCode(ee) === $this.triggerKeyCode) {
                        $this.$clipboard.style.display = "none";
                        $this.$element.focus();
                    }

                    $this.$element.dispatchEvent($dupKeyEvent(ee,'keyup', $this.$element));
                };

                this.$clipboard.onfocus = function(e) {
                    if ($this.$element === null && e.relatedTarget !== null) {
                        $this.$element = e.relatedTarget;
                    }
                };

                this.$clipboard.onblur = function() {
                    this.value = "";
                    this.style.display = "none";

                    //!!! pass focus back to canvas
                    //    it has to be done for the case when cmd+TAB (switch from browser to
                    //    another application)
                    $this.$element.focus();
                };

                this.$clipboard.oncopy = function(ee) {
                    var dest = $this.getDestination();
                    if (dest          !== null &&
                        dest.clipCopy !== undefined)
                    {
                        var v = dest.clipCopy();
                        $this.$clipboard.value = (v === null || v === undefined ? "" : v);
                        $this.$clipboard.select();
                        if ($this.clipCopy !== undefined) {
                            $this.clipCopy(v, $this.$clipboard.value);
                        }
                    }
                };

                this.$clipboard.oncut = function(ee) {
                    var dest = $this.getDestination();
                    if (dest !== null && dest.cut !== undefined) {
                        $this.$clipboard.value = dest.cut();
                        $this.$clipboard.select();
                        if ($this.clipCut !== undefined) {
                            $this.clipCut(dest, $this.$clipboard.value);
                        }
                    }
                };

                if (zebkit.isFF === true) {
                    this.$clipboard.addEventListener("input", function(ee) {
                        var dest = $this.getDestination();
                        if (dest !== null && dest.clipPaste !== undefined) {
                            dest.clipPaste($this.$clipboard.value);
                            if ($this.clipPaste !== undefined) {
                                $this.clipPaste(dest, $this.$clipboard.value);
                            }
                        }

                    }, false);
                } else {
                    this.$clipboard.onpaste = function(ee) {
                        var dest = $this.getDestination();
                        if (dest !== null && dest.clipPaste !== undefined) {
                            var txt = (ee.clipboardData === undefined) ? window.clipboardData.getData('Text')  // IE
                                                                       : ee.clipboardData.getData('text/plain');
                            dest.clipPaste(txt);
                            if ($this.clipPaste !== undefined) {
                                $this.clipPaste(dest, txt);
                            }
                        }
                        $this.$clipboard.value = "";
                    };
                }

                document.body.appendChild(this.$clipboard);
            }
        },

        function $clazz() {
            this.id = "zebkitClipboardBuffer";
        },

        function $prototype() {
            /**
             * Clipboard trigger key code.
             * @private
             * @readOnly
             * @attribute triggerKeyCode
             * @type {String}
             */
            this.triggerKeyCode = null;

            /**
             * Write the given content into clipboard. This method not necessary work on
             * all browsers by default. Many browsers issue security restrictions regarding
             * clipboard data manipulation.
             * @param  {String} txt a content
             * @method  write
             */
            this.write = function(txt) {
                try {
                    this.$on(txt);
                    if (document.execCommand !== undefined && document.execCommand("copy") !== true) {
                        throw new Error("Unsupported 'copy' clipboard command");
                    }
                } finally {
                    this.$off();
                }
            };

            /**
             * Read clipboard content. This method not necessary work on
             * all browsers by default. Many browsers issue security restrictions regarding
             * clipboard data manipulation.
             * @return {String} a clipboard content.
             * @method  read
             */
            this.read = function() {
                try {
                    var clip = this.$on("");
                    if (document.execCommand !== undefined && document.execCommand("paste", null, null)) {
                        return clip.value;
                    } else {
                        throw new Error("Unsupported 'paste' clipboard command");
                    }
                } finally {
                    this.$off();
                }
            };

            /**
             * Return focus from a hidden element back to initial one.
             * @private
             * @method $off
             */
            this.$off = function() {
                if (this.$clipboard.style.display !== "none") {
                    this.$clipboard.value = "";
                    this.$clipboard.style.display = "none";

                    //!!! pass focus back to canvas
                    //    it has to be done for the case when cmd+TAB (switch from browser to
                    //    another application)
                    this.$element.focus();
                }
            };

            /**
             * Pass focus to hidden html element to catch input.
             * @private
             * @method $on
             */
            this.$on = function(txt) {
                this.$off();

                this.$element = document.activeElement;
                this.$clipboard.style.display = "block";

                // value has to be set, otherwise some browsers (Safari) do not generate
                // "copy" event
                this.$clipboard.value = arguments.length > 0 ? txt : "1";
                this.$clipboard.select();
                this.$clipboard.focus();
                return this.$clipboard;
            };
        }
    ]);

    new pkg.Clipboard();


    // TODO List:
    //    [+] add pressure level field to pointer events
    //    [-] group field
    //    [+] round for pageX/pageY
    //    [+] double click
    //    [+] check if button field is required or can be removed from pointer event
    //    [+] support global status keeping and updating (ctrl/alt/shift)
    //    [+] "lmouse" and "rmouse" should be constants
    //    [-] list of active touches or pointers have to be available
    //    [-] meX/meY -> (x, y) ?

    if (pkg.doubleClickDelta === undefined) {
        pkg.doubleClickDelta = 280;
    }

    var PI4                      = Math.PI/4,  // used to calculate touch event gamma (direction
        PI4_3                    = PI4 * 3,    // in polar coordinate)
        $enteredElement          = null,
        $tmpWinMouseMoveListener = null,
        $lastPointerReleased     = null,
        $pointerPressedEvents    = {},         // collect all pointer pressed events
        LMOUSE = "lmouse",
        RMOUSE = "rmouse";

    /**
     * Normalized pointer event that is fired with mouse, touch, pen devices.
     * @class  zebkit.web.PointerEvent
     * @extends  zebkit.ui.event.PointerEvent
     * @constructor
     */
    pkg.PointerEvent = Class(zebkit.ui.event.PointerEvent, [
        function $prototype() {
            this.isAction = function() {
                return this.identifier !== RMOUSE && this.touchCounter === 1;
            };

            this.$fillWith = function(identifier, e) {
                this.pageX      = Math.round(e.pageX);
                this.pageY      = Math.round(e.pageY);
                this.target     = e.target;
                this.identifier = identifier;
                this.altKey     = e.altKey   !== undefined ? e.altKey   : false;
                this.shiftKey   = e.shiftKey !== undefined ? e.shiftKey : false;
                this.ctrlKey    = e.ctrlKey  !== undefined ? e.ctrlKey  : false;
                this.metaKey    = e.metaKey  !== undefined ? e.metaKey  : false;
                this.pressure   = e.pressure !== undefined ? e.pressure : 0.5;
            };

            this.getTouches = function() {
                var touches = [], i = 0;
                for(var k in pkg.$pointerPressedEvents) {
                    var pe = pkg.$pointerPressedEvents[k];
                    touches[i++] = {
                        pageX      : pe.pageX,
                        pageY      : pe.pageY,
                        identifier : pe.identifier,
                        target     : pe.target,
                        pressure   : pe.pressure,
                        pointerType: pe.stub.pointerType
                    };
                }
                return touches;
            };
        }
    ]);

    var ME_STUB      = new pkg.PointerEvent(), // instance of mouse event
        TOUCH_STUB   = new pkg.PointerEvent(), // instance of touch event
        POINTER_STUB = new pkg.PointerEvent(); // instance of pointer event

    ME_STUB.pointerType      = "mouse";
    TOUCH_STUB.pointerType   = "touch";
    POINTER_STUB.pointerType = "unknown"; // type of pointer events have to be copied from original WEB PointerEvent

    // !!!
    // global mouse move events handler (registered by drag out a canvas surface)
    // has to be removed every time a mouse button released with the given function
    function $cleanDragFix() {
        if ($tmpWinMouseMoveListener !== null      &&
            $pointerPressedEvents.hasOwnProperty(LMOUSE) === false &&
            $pointerPressedEvents.hasOwnProperty(RMOUSE) === false   )
        {
            window.removeEventListener("mousemove", $tmpWinMouseMoveListener, true);
            $tmpWinMouseMoveListener = null;
            return true;
        }
        return false;
    }

    function isIn(t, id) {
        for(var i = 0; i < t.length; i++) {
            if (t[i].identifier === id) {
                return true;
            }
        }
        return false;
    }

    /**
     * Pointer event unifier is special class to normalize input events from different pointer devices (like
     * mouse, touch screen, pen etc) and various browsers. The class transform all the events to special
     * neutral pointer event.
     * @param  {DOMElement} element a DOM element to normalize pointer events
     * @param  {Object} destination a destination object that implements number of pointer events
     * handlers:
     *
     *      {
     *          $pointerPressed     : function(e) { ... },
     *          $pointerReleased    : function(e) { ... },
     *          $pointerClicked     : function(e) { ... },
     *          $pointerMoved       : function(e) { ... },
     *          $pointerDragStarted : function(e) { ... },
     *          $pointerDragged     : function(e) { ... },
     *          $pointerDragEnded   : function(e) { ... }
     *      }
     *
     *
     * @constructor
     * @class zebkit.web.PointerEventUnifier
     */
    pkg.PointerEventUnifier = Class([
        function $clazz() {
            // !!!!
            // TODO: this method works only for mouse (constant of mouse event ids is in)
            // not clear if it is ok
            //
            // the document mouse up happens when we drag outside a canvas.
            // in this case canvas doesn't catch mouse up, so we have to do it
            // by global mouseup handler
            document.addEventListener("mouseup", function(e) {
                // ignore any mouse buttons except left
                // and right buttons
                if (e.button === 0 || e.button === 2) {
                    var id = e.button === 0 ? LMOUSE : RMOUSE;

                    // !!!!
                    // Check if the event target is not the canvas itself
                    // On desktop  "mouseup" event is generated only if
                    // you drag mouse outside a canvas and than release a mouse button
                    // At the same time in Android native browser (and may be other mobile
                    // browsers) "mouseup" event is fired every time you touch
                    // canvas or any other element. So check if target is not a canvas
                    // before doing releasing, otherwise it brings to error on mobile
                    if ($pointerPressedEvents.hasOwnProperty(id)) {
                        var mp = $pointerPressedEvents[id];
                        if (mp.$adapter.element !== e.target && mp.$adapter.element.contains(e.target) === false) {
                            try {
                                mp.$adapter.$UP(id, e, ME_STUB);
                            } finally {
                                if ($enteredElement !== null) {
                                    $enteredElement = null;
                                    mp.$adapter.destination.$pointerExited(ME_STUB);
                                }
                            }
                        }
                    }
                }
            },  false); // false is important since if mouseUp  happens on
                        // canvas the canvas gets the event first and than stops
                        // propagating to prevent it
        },

        function $prototype() {
            this.$timer = null;
            this.$queue = [];

            this.$touchedAt = function(pageX, pageY, d) {
                var lx = pageX - d,
                    ty = pageY - d,
                    rx = pageX + d,
                    by = pageY + d;

                for(var k in $pointerPressedEvents) {
                    if (k !== LMOUSE && k !== RMOUSE) {
                        var e = $pointerPressedEvents[k];
                        if (e.pageX >= lx && e.pageY >= ty && e.pageX <= rx && e.pageY <= by) {
                            return true;
                        }
                    }
                }
                return false;
            };

            this.$DRAG = function(id, e, stub) {
                // a pointer touched has been pressed and pressed target zebkit component exists
                // emulate mouse dragging events if mouse has moved on the canvas where mouse
                // pressed event occurred
                if ($pointerPressedEvents.hasOwnProperty(id)) {
                    // get appropriate pointerPressed event that has occurred before
                    var mp = $pointerPressedEvents[id];

                    // ignore moved if there still start events that are waiting for to be fired
                    if (mp.$adapter.element === this.element) {
                        // target component exists and mouse cursor moved on the same
                        // canvas where mouse pressed occurred
                        if (this.$timer === null) {  // ignore drag for if the queue of touches is not empty
                            stub.$fillWith(id, e);

                            var dx = stub.pageX - mp.pageX,
                                dy = stub.pageY - mp.pageY,
                                d  = mp.direction;

                            // accumulate shifting of pointer
                            mp.$adx += dx;
                            mp.$ady += dy;

                            // update stored touch coordinates with a new one
                            mp.pageX  = stub.pageX;
                            mp.pageY  = stub.pageY;

                            // we can recognize direction only if move was not too small
                            if (Math.abs(mp.$adx) > 4 || Math.abs(mp.$ady) > 4) {
                                // compute gamma, this is corner in polar coordinate system
                                var gamma = Math.atan2(mp.$ady, mp.$adx);

                                // using gamma we can figure out direction
                                if (gamma > -PI4) {
                                    d = (gamma < PI4) ? "right" : (gamma < PI4_3 ? "bottom" : "left");
                                } else {
                                    d = (gamma > -PI4_3) ? "top" : "left";
                                }

                                mp.direction = d;

                                // clear accumulated shift
                                mp.$ady = mp.$adx = 0;

                                mp.gamma = gamma;
                            }

                            stub.direction = mp.direction;
                            stub.dx = dx;
                            stub.dy = dy;

                            try {
                                if (mp.isDragged === false) {
                                    this.destination.$pointerDragStarted(stub);
                                }

                                if (mp.isDragged === false || dx !== 0 || dy !== 0) {
                                    this.destination.$pointerDragged(stub);
                                }
                            } finally {
                                mp.isDragged = true;
                            }
                        }
                    } else {
                        mp.$adapter.$DRAG(id, e, stub);
                    }
                }
            };

            this.$fireUP = function(id, e, mp, stub, destination) {
                try {
                    // store coordinates and target
                    stub.$fillWith(id, e);

                    // TODO: uncomment it and replace with sub or so
                    //if (tt.group != null) tt.group.active = false;

                    // add press coordinates what can help to detect source
                    // of the event
                    stub.pressPageX = mp.pressPageX;
                    stub.pressPageY = mp.pressPageY;

                    // fire dragged or clicked
                    if (mp.isDragged === true) {
                        destination.$pointerDragEnded(stub);
                    } else {
                        // TODO: sometimes browser scrolls page during the click
                        // to detect it we have to check pageX / pageY coordinates with
                        // initial one to suppress not valid pointer clicked events
                        if (mp.pressPageY === stub.pageY && mp.pressPageX === stub.pageX) {
                            if ($lastPointerReleased !== null &&
                                $lastPointerReleased.identifier === id &&
                                (new Date().getTime() - $lastPointerReleased.time) <= pkg.doubleClickDelta)
                            {
                                destination.$pointerDoubleClicked(stub);
                            } else {
                                if (mp.group === stub.touchCounter) {  // TODO: temporary solution
                                    destination.$pointerClicked(stub);
                                }
                            }
                        }
                    }

                    // always complete pointer pressed with appropriate
                    // release event
                    destination.$pointerReleased(stub);
                } finally {
                    // clear handled pressed and dragged state
                    if (stub.touchCounter > 0) {
                        stub.touchCounter--;
                    }
                    $lastPointerReleased = $pointerPressedEvents.hasOwnProperty(id) ? $pointerPressedEvents[id] : null;
                    delete $pointerPressedEvents[id];

                    // remove global move listener if necessary
                    $cleanDragFix();
                }
            };

            //  Possible cases of mouse up events:
            //
            //   a) +-------------+        b) +----------------+       c) +---------------+
            //      |  E          |           | E +----+       |          | E       +-----|
            //      |      p--u   |          |    | p--|-u     |          |         |  p--|-u
            //      |             |           |   +----+       |          |         +-----|
            //      +-------------+           +----------------+          +---------------+
            // (out to document/body)      (out from kid to element)   (out from kid to document)
            //
            //   d) +--------+--------+    e) +----------+----------+    f) +---------+-------+
            //      | E      |        |       |  E +-----|-----+    |       | E +-----|       |
            //      |     p--|--u     |       |    | p---|--u  |    |       |   |  p--|-u     |
            //      |        |        |       |    +-----|-----+    |       |   +-----|       |
            //      +--------+--------+       +----------+----------+       +---------+-------+
            //     (out from element to       (out from kid of element     (out from kid element
            //      other element)            to kid of another element)    to another element)
            // Contract:
            //   -- handle only mouse events whose destination is the passed element
            //   -- does stop propagation if event has been handled
            //   -- clear drag  fix ?
            this.$UP = function(id, e, stub) {
                // remove timer if it has not been started yet since we already have
                // got UP event and have to fire pressed events from queue with the
                // UP handler
                if (this.$timer !== null) {
                    clearTimeout(this.$timer);
                    this.$timer = null;
                }

                // test if the pressed event for the given id has not been fired yet
                var isPressedInQ = false;
                for(var i = 0; i < this.$queue.length; i++) {
                    if (this.$queue[i].identifier === id) {
                        isPressedInQ = true;
                        break;
                    }
                }

                // fire collected in queue pressed events
                this.$firePressedFromQ();

                // check if a pointer state is in pressed state
                if ($pointerPressedEvents.hasOwnProperty(id)) {
                    // get pointer pressed state for the given id
                    var mp = $pointerPressedEvents[id];

                    // mouse up can happen in another element than
                    // mouse down occurred. let the original element
                    // (where mouse down is happened) to handle it
                    if (this.element !== mp.$adapter.element) {
                        $enteredElement = null;
                        // wrap with try-catch to prevent inconsistency
                        try {
                            stub.$fillWith(id, e);
                            mp.$adapter.destination.$pointerExited(stub);
                            $enteredElement = this.element;
                            this.destination.$pointerEntered(stub);
                        } catch(ee) {
                            // keep it for exceptional cases
                            $enteredElement = this.element;
                            throw ee;
                        } finally {
                            mp.$adapter.$UP(id, e, stub);
                        }
                    } else {
                        if (isPressedInQ) {  // the mouse pressed and mouse released has happened in different
                                             // point in a time to let UI show visual state, for instance mouse
                                             // down and up
                            var $this = this;
                            setTimeout(function() {
                                $this.$fireUP(id, e, mp, stub, $this.destination);
                            }, 50);
                        } else {
                            this.$fireUP(id, e, mp, stub, this.destination);
                        }
                    }
                }
            };

            this.$indexOfQ = function(id) {
                for(var i = 0; i < this.$queue.length; i++) {
                    if (id === this.$queue[i].identifier) {
                        return i;
                    }
                }
                return -1;
            };

            this.$firePressedFromQ = function() {
                // fire collected pointer pressed events
                if (this.$queue.length > 0) {
                    var l = this.$queue.length;
                    for(var i = 0; i < l; i++) {
                        var t = this.$queue[i];

                        try {
                            // reg the event
                            $pointerPressedEvents[t.identifier] = t;

                            t.stub.$fillWith(t.identifier, t);
                            t.group = l; // TODO: temporary solution

                            if (this.destination.$pointerPressed(t.stub) === true) {
                                if (t.stub.touchCounter > 0) {
                                    t.stub.touchCounter--;
                                }
                                delete $pointerPressedEvents[t.identifier];
                            }
                        } catch(ex) {
                            // don't forget to decrease counter
                            if (t.stub.touchCounter > 0) {
                                t.stub.touchCounter--;
                            }
                            delete $pointerPressedEvents[t.identifier];
                            zebkit.dumpError(ex);
                        }
                    }
                    this.$queue.length = 0;
                }
            };

            this.$DOWN = function(id, e, stub) {
                $cleanDragFix();


                // remove not fired pointer pressed from queue if necessary
                var i = this.$indexOfQ(id);
                if (i >= 0) {
                    this.$queue.splice(i, 1);
                }

                // release mouse pressed if it has not happened before
                if ($pointerPressedEvents.hasOwnProperty(id)) {
                    var mp = $pointerPressedEvents[id];
                    mp.$adapter.$UP(id, e, mp.stub);
                }

                // count pointer pressed
                stub.touchCounter++;

                try {
                    var q = {
                        target      : e.target,
                        direction   : null,
                        identifier  : id,
                        shiftKey    : e.shiftKey,
                        altKey      : e.altKey,
                        metaKey     : e.metaKey,
                        ctrlKey     : e.ctrlKey,
                        time        : (new Date()).getTime(),
                        $adapter    : this,
                        $adx        : 0,
                        $ady        : 0,
                        isDragged   : false,
                        stub        : stub
                    };

                    q.pageX = q.pressPageX = Math.round(e.pageX);
                    q.pageY = q.pressPageY = Math.round(e.pageY);

                    // put pointer pressed in queue
                    this.$queue.push(q);

                    // initiate timer to send collected new touch events
                    // if any new has appeared. the timer helps to collect
                    // events in one group
                    if (this.$queue.length > 0 && this.$timer === null) {
                        var $this = this;
                        this.$timer = setTimeout(function() {
                            $this.$timer = null;
                            $this.$firePressedFromQ(); // flush queue
                        }, 25);
                    }
                } catch(ee) {
                    // restore touch counter if an error has happened
                    if (stub.touchCounter > 0) {
                        stub.touchCounter--;
                    }
                    throw ee;
                }
            };

            this.$MMOVE = function(e) {
                var pageX = Math.round(e.pageX),
                    pageY = Math.round(e.pageY);

                // ignore extra mouse moved event that can appear in IE
                if (this.$mousePageY !== pageY ||
                    this.$mousePageX !== pageX   )
                {

                    this.$mousePageX = pageX;
                    this.$mousePageY = pageY;

                    if ($pointerPressedEvents.hasOwnProperty(LMOUSE) ||
                        $pointerPressedEvents.hasOwnProperty(RMOUSE)   )
                    {
                        if ($pointerPressedEvents.hasOwnProperty(LMOUSE)) {
                            this.$DRAG(LMOUSE, e, ME_STUB);
                        }

                        if ($pointerPressedEvents.hasOwnProperty(RMOUSE)) {
                            this.$DRAG(RMOUSE, e, ME_STUB);
                        }
                    } else {
                        // initialize native fields
                        ME_STUB.$fillWith("mouse", e);
                        this.destination.$pointerMoved(ME_STUB);
                    }
                }
            };
        },

        function (element, destination) {
            if (element === null || element === undefined) {
                throw new Error("Invalid DOM element");
            }

            if (destination === null || destination === undefined) {
                throw new Error("Invalid destination");
            }

            this.destination = destination;
            this.element     = element;

            var $this = this;

            element.onmousedown = function(e) {
                // ignore any mouse buttons except left
                // and right buttons or long touch emulates mouse event what causes generations of
                // mouse down event after touch start event. Let's suppress it
                if ((e.button !== 0 && e.button !== 2) ||
                     $this.$touchedAt(e.pageX, e.pageY, 0))
                {
                    e.preventDefault();
                } else {
                    $this.$DOWN(e.button === 0 ? LMOUSE : RMOUSE, e, ME_STUB);
                    e.stopPropagation();
                }
            };


            //  Possible cases of mouse up events:
            //
            //   a) +-------------+        b) +----------------+       c) +---------------+
            //      |  E          |           | E +----+       |          | E       +-----|
            //      |      p--u   |          |    | p--|-u     |          |         |  p--|-u
            //      |             |           |   +----+       |          |         +-----|
            //      +-------------+           +----------------+          +---------------+
            // (out to document/body)      (out from kid to element)   (out from kid to document)
            //
            //   d) +--------+--------+    e) +----------+----------+    f) +---------+-------+
            //      | E      |        |       |  E +-----|-----+    |       | E +-----|       |
            //      |     p--|--u     |       |    | p---|--u  |    |       |   |  p--|-u     |
            //      |        |        |       |    +-----|-----+    |       |   +-----|       |
            //      +--------+--------+       +----------+----------+       +---------+-------+
            //     (out from element to       (out from kid of element     (out from kid element
            //      other element)            to kid of another element)    to another element)
            // Contract:
            //   -- handle only mouse events whose destination is the passed element
            //   -- does stop propagation if event has been handled
            //   -- clear drag  fix ?
            element.onmouseup = function(e) {
                // ignore any mouse buttons except left
                // and right buttons
                if (e.button !== 0 && e.button !== 2) {
                    e.preventDefault();
                } else {
                    var id = e.button === 0 ? LMOUSE : RMOUSE;

                    $this.$UP(id, e, ME_STUB);

                    if (e.stopPropagation !== undefined) {
                        e.stopPropagation();
                    }
                }
            };

            // mouse over has to setup if necessary current over element variable
            // it requires to detect repeat mouse over event that happens when
            // for instance we switch between browser and other application, but
            // mouse cursor stays at the same place
            element.onmouseover = function(e) {
                // this code prevent mouse over for first touch on iOS and Android
                if ($this.$touchedAt(e.pageX, e.pageY, 0)) {
                    e.preventDefault();
                } else {
                    var id = e.button === 0 ? LMOUSE : RMOUSE;

                    // if a button has not been pressed handle mouse entered to detect
                    // zebkit component the mouse pointer entered and send appropriate
                    // mouse entered event to it
                    if ($pointerPressedEvents.hasOwnProperty(id) === false) {
                        // just for the sake of error prevention
                        // clean global move listeners
                        $cleanDragFix();

                        // if entered element is null or the target element
                        // is not a children/element of the entered element than
                        // fires pointer entered event
                        if ($enteredElement === null || ($enteredElement.contains(e.target) === false && $enteredElement !== e.target)) {
                            ME_STUB.$fillWith("mouse", e);
                            $enteredElement = element;
                            destination.$pointerEntered(ME_STUB);
                        }
                    } else {
                        // remove any previously registered listener if
                        //  -- a mouse button has been pressed
                        //  -- a mouse button has been pressed on the canvas we have entered
                        if (element === e.target || element.contains(e.target)) {
                            $cleanDragFix();
                        }
                    }

                    e.stopPropagation();
                }
            };

            //  Possible cases of mouse out events:
            //
            //   a) +-------------+        b) +----------------+       c) +---------------+
            //      |  E          |           | E +----+       |          | E       +-----|
            //      |        *----|->         |   | *--|->     |          |         |  *--|->
            //      |             |           |   +----+       |          |         +-----|
            //      +-------------+           +----------------+          +---------------+
            // (out to document/body)      (out from kid to element)   (out from kid to document)
            //
            //   d) +--------+--------+    e) +----------+----------+    f) +---------+-------+
            //      | E      |        |       |  E +-----|-----+    |       | E +-----|       |
            //      |     *--|-->     |       |    | *---|-->  |    |       |   |  *--|->     |
            //      |        |        |       |    +-----|-----+    |       |   +-----|       |
            //      +--------+--------+       +----------+----------+       +---------+-------+
            //     (out from element to       (out from kid of element     (out from kid element
            //      other element)            to kid of another element)    to another element)
            //
            //   1) a mouse button doesn't have to be pressed on any element
            //   2) e.target always equals to element (E), just because we register event handler
            //      for element. This guarantees element will get mouse out event only from itself
            //      and its children elements
            //   3) mouse out should trigger pointerExited event only if the relatedTarget element
            //      is not the element (E) or kid of the element (E)
            //   4) if a mouse button has been pressed than mouse out registers mouse move listener
            //      to track drag events if the listener has nor been registered yet.
            //   5) mouse out set to null $enteredElement

            element.onmouseout = function(e) {
                var id = e.button === 0 ? LMOUSE : RMOUSE;

                // no pressed button exists
                if ($pointerPressedEvents.hasOwnProperty(id) === false) {
                    // the target element is the not a kid of the element
                    if ($enteredElement !== null && (e.relatedTarget !== null    &&
                                                     e.relatedTarget !== element &&
                                                     element.contains(e.relatedTarget) === false))
                    {
                        $enteredElement = null;
                        ME_STUB.$fillWith("mouse", e);

                        if (zebkit.web.$isInsideElement(element, e.pageX, e.pageY) === false) {
                            destination.$pointerExited(ME_STUB);
                        }
                    }
                } else {
                    var mp = $pointerPressedEvents[id];

                    // if a button has been pressed but the mouse cursor is outside of
                    // the canvas, for a time being start listening mouse moved events
                    // of Window to emulate mouse moved events in canvas
                    if ($tmpWinMouseMoveListener === null &&
                        e.relatedTarget !== null &&
                        element.contains(e.relatedTarget) === false)
                    {
                        // !!! ignore touchscreen devices
                        if (id === LMOUSE || id === RMOUSE) {
                            $tmpWinMouseMoveListener = function(ee) {
                                ee.stopPropagation();

                                if ($pointerPressedEvents.hasOwnProperty(LMOUSE)) {
                                    $this.$DRAG(LMOUSE, {
                                        pageX  : ee.pageX,
                                        pageY  : ee.pageY,
                                        target : mp.target,
                                    }, ME_STUB);
                                }

                                if ($pointerPressedEvents.hasOwnProperty(RMOUSE)) {
                                    $this.$DRAG(RMOUSE, {
                                        pageX  : ee.pageX,
                                        pageY  : ee.pageY,
                                        target : mp.target,
                                    }, ME_STUB);
                                }

                                ee.preventDefault();
                            };

                            window.addEventListener("mousemove", $tmpWinMouseMoveListener, true);
                        }
                    }
                }

                $this.$mousePageX = $this.$mousePageY = -1;
                e.stopPropagation();
            };

            if ("onpointerdown" in window || "onmspointerdown" in window) {
                var names = "onpointerdown" in window ? [ "pointerdown",
                                                          "pointerup",
                                                          "pointermove",
                                                          "pointerenter",
                                                          "pointerleave" ]
                                                      : [ "MSPointerDown",
                                                          "MSPointerUp",
                                                          "MSPointerMove",
                                                          "MSPointerEnter",
                                                          "MSPointerLeave" ];

                //
                // in windows 8 IE10  pointerType can be a number !!!
                // what is nit the case fo rinstanvce for Win 8.1
                //

                element.addEventListener(names[0], function(e) {
                    var pt = e.pointerType;
                    if (pt === 4) {
                        pt = "mouse";
                    } else if (pt === 2) {
                        pt = "touch";
                    } else if (pt === 3) {
                        pt = "pen";
                    }

                    if (pt !== "mouse")  {
                        POINTER_STUB.touch = e;
                        POINTER_STUB.pointerType = pt;
                        $this.$DOWN(e.pointerId, e, POINTER_STUB);
                    }
                }, false);

                element.addEventListener(names[1], function(e) {
                    var pt = e.pointerType;
                    if (pt === 4) {
                        pt = "mouse";
                    } else if (pt === 2) {
                        pt = "touch";
                    } else if (pt === 3) {
                        pt = "pen";
                    }

                    if (pt !== "mouse") {
                        POINTER_STUB.touch = e;
                        POINTER_STUB.pointerType = pt;
                        $this.$UP(e.pointerId, e, POINTER_STUB);
                    }
                }, false);

                element.addEventListener(names[2], function(e) {
                    var pt = e.pointerType;
                    if (pt === 4) {
                        pt = "mouse";
                    } else if (pt === 2) {
                        pt = "touch";
                    } else if (pt === 3) {
                        pt = "pen";
                    }

                    if (pt !== "mouse") {
                        POINTER_STUB.touch = e;
                        POINTER_STUB.pointerType = pt;
                        $this.$DRAG(e.pointerId, e, POINTER_STUB);
                    } else {
                        //e.pointerType = pt;
                        $this.$MMOVE(e);
                    }
                }, false);
            } else {
                element.addEventListener("touchstart", function(e) {
                    var allTouches = e.touches,
                        newTouches = e.changedTouches; // list of touch events that become
                                                       // active with the current touch start

                    // fix android bug: parasite event for multi touch
                    // or stop capturing new touches since it is already fixed
                    // TODO: have no idea what it is
                    // if (TOUCH_STUB.touchCounter > e.touches.length) {
                    //     return;
                    // }

                    // android devices fire mouse move if touched but not moved
                    // let save coordinates what should prevent mouse move event
                    // generation
                    //
                    // TODO: not clear if second tap will fire mouse move or if the
                    // second tap will have any influence to first tap mouse move
                    // initiation
                    $this.$mousePageX = Math.round(e.pageX);
                    $this.$mousePageY = Math.round(e.pageY);

                    // fire touches that has not been fired yet
                    for(var i = 0; i < newTouches.length; i++) {  // go through all touches
                        var newTouch = newTouches[i];
                        $this.$DOWN(newTouch.identifier, newTouch, TOUCH_STUB);
                    }

                    // clear touches that still is not in list of touches
                    for (var k in $pointerPressedEvents) {
                        if (isIn(allTouches, k) === false) {
                            var tt = $pointerPressedEvents[k];
                            if (tt.group != null) {
                                tt.group.active = false;
                            }
                            $this.$UP(tt.identifier, tt, TOUCH_STUB);
                        }
                    }

                    //!!!
                    //TODO: this calling prevents generation of phantom mouse move event
                    //but it is not clear if it will stop firing touch end/move events
                    //for some mobile browsers. Check it !
                    e.preventDefault();
                }, false);

                element.addEventListener("touchend", function(e) {
                    // update touches
                    var t = e.changedTouches;
                    for (var i = 0; i < t.length; i++) {
                        var tt = t[i];
                        $this.$UP(tt.identifier, tt, TOUCH_STUB);
                    }

                    e.preventDefault();
                }, false);

                element.addEventListener("touchmove", function(e) {
                    var mt = e.changedTouches;

                    // clear dx, dy for not updated touches
                    for(var k in $this.touches) {
                        $pointerPressedEvents[k].dx = $pointerPressedEvents[k].dy = 0;
                    }

                    for(var i = 0; i < mt.length; i++) {
                        var nmt = mt[i];
                        if ($pointerPressedEvents.hasOwnProperty(nmt.identifier)) {
                            var t = $pointerPressedEvents[nmt.identifier];
                            if (t.pageX !== Math.round(nmt.pageX) ||
                                t.pageY !== Math.round(nmt.pageY)   )
                            {
                                // TODO: analyzing time is not enough to generate click event since
                                // a user can put finger and wait for a long time. the best way is
                                // normalize time with movement (number of movement of dx/dy accumulation)
                                //if (t.isDragged) {// || (new Date().getTime() - t.time) > 200) {
                                if (t.isDragged || Math.abs(nmt.pageX - t.pageX) + Math.abs(nmt.pageY - t.pageY) > 4) {
                                    $this.$DRAG(nmt.identifier, nmt, TOUCH_STUB);
                                }
                            }
                        }
                    }

                    e.preventDefault();
                }, false);

                element.onmousemove = function(e) {
                    $this.$MMOVE(e);
                    e.stopPropagation();
                };
            }

            // TODO: not sure it has to be in pointer unifier
            element.oncontextmenu = function(e) {
                e.preventDefault();
            };
        }
    ]);


    /**
     *  Mouse wheel support class. Installs necessary mouse wheel listeners and handles mouse wheel
     *  events in zebkit UI. The mouse wheel support is plugging that is configured by a JSON
     *  configuration.
     *  @class zebkit.web.MouseWheelSupport
     *  @param  {DOMElement} element
     *  @param  {Object} destination
     *  @constructor
     */
    pkg.MouseWheelSupport = Class([
        function(element, destination) {
            var META = this.clazz.$META;
            for(var k in META) {
                if (META[k].test()) {
                    var $wheelMeta = META[k],
                        $clazz     = this.clazz;

                    element.addEventListener(k,
                        function(e) {
                            var dy = e[$wheelMeta.dy] !== undefined ? e[$wheelMeta.dy] * $wheelMeta.dir : 0,
                                dx = e[$wheelMeta.dx] !== undefined ? e[$wheelMeta.dx] * $wheelMeta.dir : 0;

                            // some version of FF can generates dx/dy  < 1
                            if (Math.abs(dy) < 1) {
                                dy *= $clazz.dyZoom;
                            }

                            if (Math.abs(dx) < 1) {
                                dx *= $clazz.dxZoom;
                            }

                            dy = Math.abs(dy) > $clazz.dyNorma ? dy % $clazz.dyNorma : dy;
                            dx = Math.abs(dx) > $clazz.dxNorma ? dx % $clazz.dxNorma : dx;

                            // do floor since some mouse devices can fire float as
                            if (destination.$doScroll(Math.floor(dx),
                                                      Math.floor(dy), "wheel"))
                            {
                                e.preventDefault();
                            }
                        },
                        false);
                    break;
                }
            }
        },

        function $clazz() {
            this.dxZoom = this.dyZoom = 20;
            this.dxNorma = this.dyNorma = 80;

            this.$META = {
                wheel: {
                    dy  : "deltaY",
                    dx  : "deltaX",
                    dir : 1,
                    test: function() {
                        return "WheelEvent" in window;
                    }
                },
                mousewheel: {
                    dy  : "wheelDelta",
                    dx  : "wheelDeltaX",
                    dir : -1,
                    test: function() {
                        return document.onmousewheel !== undefined;
                    }
                },
                DOMMouseScroll: {
                    dy  : "detail",
                    dir : 1,
                    test: function() {
                        return true;
                    }
                }
            };
        },

        function $prototype() {
            /**
             * Indicates if the wheel scrolling is done following natural
             * direction.
             * @attribute naturalDirection
             * @type {Boolean}
             * @default true
             */
            this.naturalDirection = true;
        }
    ]);


    // Key CODES meta
    // pr  - preventDefault, false if not defined
    // rp  - repeatable key, true if not defined
    // map  - map code to another code
    // ignore  - don't fire the given event, false by default
    var CODES = {
            "KeyA"  : { keyCode: 65 },
            "KeyB"  : { keyCode: 66 },
            "KeyC"  : { keyCode: 67 },
            "KeyD"  : { keyCode: 68 },
            "KeyE"  : { keyCode: 69 },
            "KeyF"  : { keyCode: 70 },
            "KeyG"  : { keyCode: 71 },
            "KeyH"  : { keyCode: 72 },
            "KeyI"  : { keyCode: 73 },
            "KeyJ"  : { keyCode: 74 },
            "KeyK"  : { keyCode: 75 },
            "KeyL"  : { keyCode: 76 },
            "KeyM"  : { keyCode: 77 },
            "KeyN"  : { keyCode: 78 },
            "KeyO"  : { keyCode: 79 },
            "KeyP"  : { keyCode: 80 },
            "KeyQ"  : { keyCode: 81 },
            "KeyR"  : { keyCode: 82 },
            "KeyS"  : { keyCode: 83 },
            "KeyT"  : { keyCode: 84 },
            "KeyU"  : { keyCode: 85 },
            "KeyV"  : { keyCode: 86 },
            "KeyW"  : { keyCode: 87 },
            "KeyX"  : { keyCode: 88 },
            "KeyY"  : { keyCode: 89 },
            "KeyZ"  : { keyCode: 90 },
            "Digit0": { keyCode: 48 },
            "Digit1": { keyCode: 49 },
            "Digit2": { keyCode: 50 },
            "Digit3": { keyCode: 51 },
            "Digit4": { keyCode: 52 },
            "Digit5": { keyCode: 53 },
            "Digit6": { keyCode: 54 },
            "Digit7": { keyCode: 55 },
            "Digit8": { keyCode: 56 },
            "Digit9": { keyCode: 57 },

            "F1":  { keyCode: 112, key: "F1",   rp: false  },
            "F2":  { keyCode: 113, key: "F2",   rp: false  },
            "F3":  { keyCode: 114, key: "F3",   rp: false  },
            "F4":  { keyCode: 115, key: "F4",   rp: false  },
            "F5":  { keyCode: 116, key: "F5",   rp: false  },
            "F6":  { keyCode: 117, key: "F6",   rp: false  },
            "F7":  { keyCode: 118, key: "F7",   rp: false  },
            "F8":  { keyCode: 119, key: "F8",   rp: false  },
            "F9":  { keyCode: 120, key: "F9",   rp: false  },
            "F10": { keyCode: 121, key: "F10",  rp: false  },
            "F11": { keyCode: 122, key: "F11",  rp: false  },
            "F12": { keyCode: 123, key: "F12",  rp: false  },
            "F13": { keyCode: 124, key: "F13",  rp: false  },
            "F14": { keyCode: 125, key: "F14",  rp: false  },
            "F15": { keyCode: 126, key: "F15",  rp: false  },

            "Numpad0"       : { keyCode: 96  },
            "Numpad1"       : { keyCode: 97  },
            "Numpad2"       : { keyCode: 98  },
            "Numpad3"       : { keyCode: 99  },
            "Numpad4"       : { keyCode: 100 },
            "Numpad5"       : { keyCode: 101 },
            "Numpad6"       : { keyCode: 102 },
            "Numpad7"       : { keyCode: 103 },
            "Numpad8"       : { keyCode: 104 },
            "Numpad9"       : { keyCode: 105 },
            "NumpadDecimal" : { keyCode: 110, key: "Decimal"  },
            "NumpadSubtract": { keyCode: 109, key: "Subtract" },
            "NumpadDivide"  : { keyCode: 111, key: "Divide"   },
            "NumpadMultiply": { keyCode: 106, key: "Multiply" },
            "NumpadAdd"     : { keyCode: 107, key: "Add"      },
            "NumLock"       : { keyCode: (zebkit.isFF ? 144 : 12) , key: "NumLock", rp: false, ignore : true },

            "Comma"        : { keyCode: 188 },
            "Period"       : { keyCode: 190 },
            "Semicolon"    : { keyCode: (zebkit.isFF ? 59  : 186) },
            "Quote"        : { keyCode: 222 },
            "BracketLeft"  : { keyCode: 219 },
            "BracketRight" : { keyCode: 221 },
            "Backquote"    : { keyCode: 192 },
            "Backslash"    : { keyCode: 220 },
            "Minus"        : { keyCode: (zebkit.isFF ? 173 : 189) },
            "Equal"        : { keyCode: (zebkit.isFF ? 61  : 187) },

            "NumpadEnter"  : { map: "Enter" },
            "Enter"        : { keyCode: 13, key: "\n" },

            "Slash"        : { keyCode: 191 },
            "Space"        : { keyCode: 32, pr: true, key: " " },
            "Delete"       : { keyCode: 46, key: "Delete" },

            "IntlRo"     : { keyCode: (zebkit.isFF ? 167 : 193), key: "IntlRo"},

            "Backspace"  :  { keyCode: 8, pr: true, key: "Backspace" },
            "Tab":          { keyCode: 9, pr: true, key: "\t" },
            "ContextMenu":  { keyCode: zebkit.isFF ? 93 : 0, pr: true, key: "ContextMenu" },

            "ArrowLeft"   : { keyCode: 37, pr: true,  key: "ArrowLeft"   },
            "ArrowRight"  : { keyCode: 39, pr: true,  key: "ArrowRight"  },
            "ArrowUp"     : { keyCode: 38, pr: true,  key: "ArrowUp"     },
            "ArrowDown"   : { keyCode: 40, pr: true,  key: "ArrowDown"   },
            "PageUp"      : { keyCode: 33, pr: true,  key: "PaheUp"      },
            "PageDown"    : { keyCode: 34, pr: true,  key: "PageDown"    },
            "Home"        : { keyCode: 36, pr: true,  key: "Home"        },
            "End"         : { keyCode: 35, pr: true,  key: "End"         },

            "Escape"      : { keyCode: 27,  pr: true,  key: "Escape",   rp: false },
            "CapsLock"    : { keyCode: 20,             key: "CapsLock", rp: false, ignore : true },

            "Shift"       : { keyCode: 16,  pr: true, key: "Shift", rp: false,},
            "ShiftLeft"   : { map: "Shift" },
            "ShiftRight"  : { map: "Shift" },

            "Alt"         : { keyCode: 18,  pr: true,  key: "Alt",  rp: false, },
            "AltLeft"     : { map: "Alt" },
            "AltRight"    : { map: "Alt" },

            "Control"     : { keyCode: 17,  pr: true,  key: "Control",  rp: false },
            "ControlRight": { map: "Control" },
            "ControlLeft" : { map: "Control" },

            "MetaLeft"    : { keyCode: 91,  pr: true,  key: "Meta", rp: false },
            "MetaRight"   : { keyCode: 93,  pr: true,  key: "Meta", rp: false },
            "OSLeft"      : { keyCode: 224,  map: "MetaLeft" },
            "OSRight"     : { keyCode: 224,  map: "MetaRight"  }
        },

        CODES_MAP = {};

    // codes to that are not the same for different browsers
    function $initializeCodesMap() {
        var k    = null,
            code = null;

        // validate codes mapping
        for(k in CODES) {
            code = CODES[k];
            if (code.map !== undefined)  {
                if (CODES[code.map] === undefined) {
                    throw new Error("Invalid mapping for code = '" + k + "'");
                }
            } else if (code.keyCode === undefined) {
                throw new Error("unknown keyCode  for code = '" + k + "'");
            }
        }

        // build codes map table for the cases when "code" property
        CODES_MAP = {};
        for(k in CODES) {
            code = CODES[k];
            if (code.map !== undefined) {
                if (code.keyCode !== undefined) {
                    CODES_MAP[code.keyCode] = code.map;
                }
            } else {
                CODES_MAP[code.keyCode] = k;
            }
        }
    }

    pkg.$fetchKeyCode = function(e) {
        var code = e.code;
        if (code !== undefined) {
            if (CODES.hasOwnProperty(code) && CODES[code].hasOwnProperty("map")) {
                code = CODES[code].map;
            }
        } else {
            code = CODES_MAP[(e.which || e.keyCode || 0)];
            if (code === undefined) {
                code = null;
            }
        }
        return code;
    };

    $initializeCodesMap();

    /**
     * Input key event class.
     * @class  zebkit.web.KeyEvent
     * @extends zebkit.ui.event.KeyEvent
     * @constructor
     */
    pkg.KeyEvent = Class(zebkit.ui.event.KeyEvent, [
        function $prototype() {
            /**
             * Fulfills the given abstract event with fields from the specified native WEB key event
             * @param  {KeyboardEvent} e a native WEB event
             * @method $fillWith
             * @chainable
             * @protected
             */
            this.$fillWith = function(e) {
                // code defines integer that in a case of
                // key pressed/released is zero or equals to physical key layout integer identifier
                // but for keyTyped should depict Unicode character code
                var keyCode = (e.which || e.keyCode || 0);

                this.code = pkg.$fetchKeyCode(e);

                if (this.code === "Enter" || this.code === "Space" || this.code === "Tab") {
                    this.key = CODES[this.code].key;
                } else if (e.key != null) {
                    this.key = e.key;
                } else if (e.type === "keypress") {
                    this.key = e.charCode > 0 && keyCode >= 32 ? String.fromCharCode(e.charCode)
                                                               : null;
                } else {
                    if (e.keyIdentifier != null) {
                        if (e.keyIdentifier[0] === 'U' &&  e.keyIdentifier[1] === '+') {
                            this.key = String.fromCharCode(parseInt(e.keyIdentifier.substring(2), 16));
                        } else {
                            this.key = e.keyIdentifier;
                        }
                    } else {
                        if (this.code != null && CODES.hasOwnProperty(this.code) === true && CODES[this.code].key != null) {
                            this.key = CODES[this.code].key;
                        } else {
                            this.key = e.charCode > 0 && keyCode >= 32 ? String.fromCharCode(e.charCode)
                                                                       : null;
                        }
                    }
                }

                this.altKey   = e.altKey;
                this.shiftKey = e.shiftKey;
                this.ctrlKey  = e.ctrlKey;
                this.metaKey  = e.metaKey;
                return this;
            };
        }
    ]);


    var KEY_DOWN_EVENT  = new pkg.KeyEvent(),
        KEY_UP_EVENT    = new pkg.KeyEvent(),
        KEY_PRESS_EVENT = new pkg.KeyEvent(),
        wasMetaLeftPressed  = false,
        wasMetaRightPressed = false;

    /**
     * Class that is responsible for translating native DOM element key event into abstract event that further
     * can be transfered to zebkit UI engine (or any other destination). Browsers key events support can be
     * implemented with slight differences from the standards. The goal of the class is key events unification.
     * The class fires three types of key events to passed event destination code:
     *    - $keyPressed(e)
     *    - $keyReleased(e)
     *    - $keyTyped(e)
     *
     * For instance imagine we have a DOM Element and want to have identical sequence and parameters of key
     * events the DOM element triggers. It can be done as follow:
     *
     *      new KeyEventUnifier(domElement, {
     *          "$keyPressed" : function(e) {
     *              ...
     *          },
     *
     *          "$keyReleased" : function(e) {
     *              ...
     *          },
     *
     *          "$keyTyped" : function(e) {
     *              ...
     *          }
     *      });
     *
     * @param  {HTMLElement} element
     * @param  {Object} destination a destination listener that can listen
     * @constructor
     * @class  zebkit.web.KeyEventUninfier
     */
    pkg.KeyEventUnifier = Class([
        function(element, destination) {
            var $this = this;

            //   Alt + x  was pressed  (for IE11 consider sequence of execution of "alt" and "x" keys)
            //   Chrome/Safari/FF  keydown -> keydown -> keypressed
            // ----------------------------------------------------------------------------------------------------------------------
            //          |     which   |    keyCode   | charCode |      code        |     key        |   keyIdentifier   |  char
            // ----------------------------------------------------------------------------------------------------------------------
            //          |             |              |          |                  |                |                   |
            //  Chrome  |    unicode/ |    unicode/  |   0      |  undefined       |  undefined     | Mnemonic + Unistr |   No
            //          |     code    |     code     |          |                  |                |  "Alt" + "U-0058" |
            //          |   18 + 88   |    18 + 88   |          |                  |                |                   |
            //----------+-----------------------------------------------------------------------------------------------|------------
            //          |             |              |          |                  |                |                   |
            //  IE11    |  unicode/   |  unicode/    |          |                  |                |                   |  Alt => ""
            //          |   code      |    code      |    0     |   undefined      |   "Alt","x"    |   undefined       |  x => "x"
            //          |    18, 88   |   18, 88     |          |                  |                |                   |
            //          |             |              |          |                  |                |                   |
            //----------+-------------|--------------|----------|------------------|----------------|-------------------|------------
            //          |   unicode/  |   unicode/   |          |                  |                |                   |
            //          |   code      |     code     |    0     |  undefined       | undefined      | Mnemonic + Unistr |   No
            //  Safari  |   18 + 88   |   18 + 88    |          |                  |                |  "Alt" + "U-0058" |
            //          |             |              |          |                  |                |                   |
            //----------+-----------------------------------------------------------------------------------------------|------------
            //          |             |              |          |                  |                |                   |
            //  FF      |   unicode/  |   unicode/   |    0     |  Mnemonic        | Mnemonic/char  |                   |  No
            //          |    code     |     code     |          |("AltLeft"+"KeyX")|  "Alt"+"≈"     |   undefined       |
            //          |  18 + 88    |  18 + 88     |          |                  |                |                   |
            //
            element.onkeydown = function(e) {
                var code = KEY_DOWN_EVENT.code,
                    pts  = KEY_DOWN_EVENT.timeStamp,
                    ts   = new Date().getTime();

                // fix loosing meta left keyup event in some browsers
                var fc = pkg.$fetchKeyCode(e);

                // ignore some keys that cannot be handled properly
                if (CODES[fc] != null && CODES[fc].ignore === true) {
                    return;
                }

                if (wasMetaLeftPressed === true && (e.metaKey !== true ||  fc === "MetaLeft")) {
                    wasMetaLeftPressed = false;
                    try {
                        KEY_DOWN_EVENT.code      = "MetaLeft";
                        KEY_DOWN_EVENT.repeat    = 0;
                        KEY_DOWN_EVENT.metaKey   = true;
                        KEY_DOWN_EVENT.timeStamp = ts;
                        destination.$keyReleased(KEY_DOWN_EVENT);
                    } catch(ex) {
                        zebkit.dumpError(ex);
                    } finally {
                        KEY_DOWN_EVENT.code = null;
                        code = null;
                    }
                }

                // fix loosing meta right keyup event in some browsers
                if (wasMetaRightPressed === true && (e.metaKey !== true || fc === "MetaRight")) {
                    wasMetaRightPressed = false;
                    try {
                        KEY_DOWN_EVENT.code      = "MetaRight";
                        KEY_DOWN_EVENT.repeat    = 0;
                        KEY_DOWN_EVENT.metaKey   = true;
                        KEY_DOWN_EVENT.timeStamp = ts;
                        destination.$keyReleased(KEY_DOWN_EVENT);
                    } catch(ex) {
                        zebkit.dumpError(ex);
                    } finally {
                        KEY_DOWN_EVENT.code = null;
                        code = null;
                    }
                }

                // we suppose key down object is shared with key up that means it
                // holds state of key (we can understand whether a key has been
                // still held or was released by checking if the code equals)
                KEY_DOWN_EVENT.$fillWith(e);
                KEY_DOWN_EVENT.timeStamp = ts;

                // calculate repeat counter
                if (KEY_DOWN_EVENT.code === code && e.metaKey !== true && (ts - pts) < 1000) {
                    KEY_DOWN_EVENT.repeat++;
                } else {
                    KEY_DOWN_EVENT.repeat = 1;
                }

                //!!!!
                // Suppress some standard browser actions.
                // Since container of zCanvas catch all events from its children DOM
                // elements don't prevent the event for the children DOM element
                var key = CODES[KEY_DOWN_EVENT.code];
                if (key != null && key.pr === true && e.target === element) {
                    // TODO: may be put the "if" logic into prevent default
                    $this.preventDefault(e, key);
                }
                e.stopPropagation();

                // fire key pressed event
                try {
                    destination.$keyPressed(KEY_DOWN_EVENT);
                } catch(ex) {
                    zebkit.dumpError(ex);
                }

                if (KEY_DOWN_EVENT.code === "MetaLeft") {
                    wasMetaLeftPressed = true;
                } else if (KEY_DOWN_EVENT.code === "MetaRight") {
                    wasMetaRightPressed = true;
                } else {
                    // if meta key is kept than generate key released event for
                    // all none-Meta keys. it is required since Meta + <a key>
                    // will never fire key released for <a key> (except state keys
                    // like shift, control etc)
                    if (e.metaKey === true) {
                        // only repeat
                        if (key == null || key.rp !== false) {
                            try {
                                KEY_UP_EVENT.$fillWith(e);
                                KEY_UP_EVENT.repeat = 0;
                                KEY_UP_EVENT.timeStamp = ts;
                                destination.$keyReleased(KEY_UP_EVENT);
                            } catch(ex) {
                                zebkit.dumpError(ex);
                            }
                        }
                    } else if (KEY_DOWN_EVENT.code === "Space" ||
                               KEY_DOWN_EVENT.code === "Enter" ||
                               KEY_DOWN_EVENT.code === "Tab"     )
                    {
                        // since space and enter key press event triggers preventDefault
                        // standard key press can never happen so let's emulate it here
                        KEY_PRESS_EVENT.$fillWith(e);
                        KEY_PRESS_EVENT.repeat = KEY_DOWN_EVENT.repeat;
                        KEY_PRESS_EVENT.timeStamp = ts;
                        destination.$keyTyped(KEY_PRESS_EVENT);
                    }
                }
            };

            element.onkeyup = function(e) {
                e.stopPropagation();

                KEY_UP_EVENT.$fillWith(e);

                // ignore some keys that cannot be handled properly
                if (CODES[KEY_UP_EVENT.code] != null && CODES[KEY_UP_EVENT.code].ignore === true) {
                    return;
                }

                if (wasMetaLeftPressed === true && KEY_UP_EVENT.code === "MetaLeft") {
                    wasMetaLeftPressed = false;
                }

                if (wasMetaRightPressed === true && KEY_UP_EVENT.code === "MetaRight") {
                    wasMetaRightPressed = false;
                }

                var key = CODES[KEY_UP_EVENT.code];
                if (e.metaKey !== true || (key != null && key.rp === false)) {
                    KEY_UP_EVENT.repeat    = 0;
                    KEY_UP_EVENT.timeStamp = new Date().getTime();
                    try {
                        destination.$keyReleased(KEY_UP_EVENT);
                    } finally {
                        // clean repeat counter
                        if (KEY_DOWN_EVENT.code === KEY_UP_EVENT.code) {
                            KEY_DOWN_EVENT.repeat = 0;
                        }
                    }
                }
            };

            //   Alt + x  was pressed  (for IE11 consider sequence of execution of "alt" and "x" keys)
            // ----------------------------------------------------------------------------------------------------------------------
            //          |     which   |    keyCode   | charCode |      code        |     key        |   keyIdentifier   |  char
            // ----------------------------------------------------------------------------------------------------------------------
            //          |             |              |          |                  |                |                   |
            //  Chrome  |    unicode/ |    unicode/  |   8776   |  undefined       |  undefined     | Mnemonic + Unistr |   No
            //          |     code    |     code     |   (≈)    |                  |                |     "U-0058"      |
            //          |   8776 (≈)  |    8776 (≈)  |          |                  |                |                   |
            //----------+-----------------------------------------------------------------------------------------------|------------
            //          |             |              |          |                  |                |                   |
            //  IE11    |  unicode/   |  unicode/    |          |                  |                |                   |
            //          |   code      |    code      |  88 (x)  |   undefined      |     "x"        |   undefined       |   "x"
            //          |    88 (x)   |   88 (x)     |          |                  |                |                   |
            //          |             |              |          |                  |                |                   |
            //----------+-------------|--------------|----------|------------------|----------------|-------------------|------------
            //          |   unicode/  |   unicode/   |          |                  |                |                   |
            //          |   code      |     code     | 8776 (≈) |  undefined       | undefined      |                   |   No
            //  Safari  |   8776 (≈)  |   8776 (≈)   |          |                  |                |        ""         |
            //          |             |              |          |                  |                |                   |
            //----------+-----------------------------------------------------------------------------------------------|------------
            //          |             |              |          |                  |                |                   |
            //  FF      |   unicode/  |    0         |   8776   |  Mnemonic        | Mnemonic/char  |                   |   No
            //          |    code     |              |   (≈)    |  ("KeyX")        |      "≈"       |   undefined       |
            //          |  8776 (≈)   |              |          |                  |                |                   |
            //
            element.onkeypress = function(e) {
                e.stopPropagation();

                // pressed meta key should bring to ignorance keypress event since the event
                // is emulated in keydown event handler.
                if (e.metaKey !== true) {
                    KEY_PRESS_EVENT.$fillWith(e);

                    KEY_PRESS_EVENT.code   = KEY_DOWN_EVENT.code;      // copy code of keydown key since key press can contain undefined code
                    KEY_PRESS_EVENT.repeat = KEY_DOWN_EVENT.repeat;

                    if (KEY_PRESS_EVENT.code !== "Space" &&
                        KEY_PRESS_EVENT.code !== "Enter" &&
                        KEY_PRESS_EVENT.code !== "Tab"   &&
                        KEY_PRESS_EVENT.code !== "ContextMenu")
                    {
                        // Since container of zCanvas catch all events from its children DOM
                        // elements don't prevent the event for the children DOM element
                        KEY_PRESS_EVENT.timeStamp = new Date().getTime();
                        destination.$keyTyped(KEY_PRESS_EVENT);
                    }
                }
            };
        },

        function $prototype() {
            this.preventDefault = function(e, key) {
                e.preventDefault();
            };
        }
    ]);
},false);
zebkit.package("ui.web", function(pkg, Class) {
    'use strict';
    /**
     * Cursor manager class. Allows developers to control pointer cursor type by implementing an own
     * getCursorType method or by specifying a cursor by cursorType field. Imagine an UI component
     * needs to change cursor type. It
     *  can be done by one of the following way:
     *
     *   - **Implement getCursorType method by the component itself if the cursor type depends on cursor location**

          var p = new zebkit.ui.Panel([
               // implement getCursorType method to set required
               // pointer cursor type
               function getCursorType(target, x, y) {
                   return zebkit.ui.Cursor.WAIT;
               }
          ]);

     *   - **Define "cursorType" property in component if the cursor type doesn't depend on cursor location**

          var myPanel = new zebkit.ui.Panel();
          ...
          myPanel.cursorType = zebkit.ui.Cursor.WAIT;


     *  @class zebkit.ui.web.CursorManager
     *  @constructor
     *  @extends zebkit.ui.event.Manager
     */
    pkg.CursorManager = Class(zebkit.ui.event.CursorManager, [
        function $prototype() {
            this.$isFunc = false;
            this.source = this.target = null;

            /**
             * Define pointer moved events handler.
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerMoved
             */
            this.pointerMoved = function(e) {
                if (this.$isFunc === true) {
                    this.cursorType = this.source.getCursorType(this.source, e.x, e.y);
                    this.target.style.cursor = (this.cursorType === null) ? "default"
                                                                          : this.cursorType;
                }
            };

            /**
             * Define pointer entered events handler.
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerEntered
             */
            this.pointerEntered = function(e) {
                if ((e.source.cursorType    !== undefined && e.source.cursorType !== null) ||
                     e.source.getCursorType !== undefined)
                {
                    this.$isFunc = (typeof e.source.getCursorType === 'function');
                    this.target = e.target;
                    this.source = e.source;

                    this.cursorType = this.$isFunc === true ? this.source.getCursorType(this.source, e.x, e.y)
                                                            : this.source.cursorType;

                    this.target.style.cursor = (this.cursorType === null) ? "default"
                                                                          : this.cursorType;
                }
            };

            /**
             * Define pointer exited events handler.
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerExited
             */
            this.pointerExited  = function(e){
                if (this.source !== null) {
                    this.cursorType = "default";
                    if (this.target.style.cursor != this.cursorType) {
                        this.target.style.cursor = this.cursorType;
                    }
                    this.source = this.target = null;
                    this.$isFunc = false;
                }
            };

            /**
             * Define pointer dragged events handler.
             * @param  {zebkit.ui.event.PointerEvent} e a pointer event
             * @method pointerDragged
             */
            this.pointerDragged = function(e) {
                if (this.$isFunc === true) {
                    this.cursorType = this.source.getCursorType(this.source, e.x, e.y);
                    this.target.style.cursor = (this.cursorType === null) ? "default"
                                                                          : this.cursorType;
                }
            };
        }
    ]);

    // TODO: make sure it should be done here, instead of json config
    pkg.cd("..").cursorManager = new pkg.CursorManager();


    var ui = pkg.cd("..");

    /**
     *  WEB based zebkit UI components.
     *
     * @class zebkit.ui.web
     * @access package
     */

    /**
     * HTML element UI component wrapper class. The class represents an HTML element as if it is standard
     * UI component. It helps to use some standard HTML element as zebkit UI components and embeds it
     * in zebkit UI application layout.
     * @class zebkit.ui.web.HtmlElement
     * @constructor
     * @param {String|HTMLElement} [element] an HTML element to be represented as a standard zebkit UI
     * component. If the passed parameter is string it denotes a name of an HTML element. In this case
     * a new HTML element will be created.
     * @extends zebkit.ui.Panel
     */
    pkg.HtmlElement = Class(ui.Panel, [
        function(e) {
            if (arguments.length === 0) {
                e = "div";
            }

            if (zebkit.isString(e)) {
                e = document.createElement(e);
                e.style.border   = "0px solid transparent";   // clean up border
                e.style.fontSize = this.clazz.$bodyFontSize;  // DOM element is wrapped with a container that
                                                              // has zero sized font, so let's set body  font
                                                              // for the created element
            }

            // sync padding and margin of the DOM element with
            // what appropriate properties are set
            e.style.margin = e.style.padding = "0px";

            this.element = e;

            // this is set to make possible to use set z-index for HTML element
            this.element.style.position = "relative";

            if (e.parentNode !== null && e.parentNode.getAttribute("data-zebcont") !== null) {
                throw new Error("DOM element '" + e + "' already has container");
            }

            // container is a DIV element that is used as a wrapper around original one
            // it is done to make HtmlElement implementation more universal making
            // all DOM elements capable to be a container for another one
            this.$container = document.createElement("div");

            // prevent stretching to a parent container element
            this.$container.style.display = "inline-block";

            // cut content
            this.$container.style.overflow = "hidden";

            // it fixes problem with adding, for instance, DOM element as window what can prevent
            // showing components added to popup layer
            this.$container.style["z-index"] = "0";

            // coordinates have to be set to initial zero value in CSS
            // otherwise the DOM layout can be wrong !
            this.$container.style.left = this.$container.style.top = "0px";

            this.$container.visibility = "hidden";  // before the component will be attached
                                                    // to parent hierarchy the component has to be hidden

            // container div will always few pixel higher than its content
            // to prevent the bloody effect set font to zero
            // border and margin also have to be zero
            this.$container.style.fontSize = this.$container.style.padding = this.$container.style.padding = "0px";

            // add id
            this.$container.setAttribute("id", "container-" + this.toString());

            // mark wrapper with a special attribute to recognize it exists later
            this.$container.setAttribute("data-zebcont", "true");

            // let html element interact
            this.$container.style["pointer-events"] = "auto";

            // if passed DOM element already has parent
            // attach it to container first and than
            // attach the container to the original parent element
            if (e.parentNode !== null) {
                // !!!
                // Pay attention container position cannot be set to absolute
                // since how the element has to be laid out is defined by its
                // original parent
                e.parentNode.replaceChild(this.$container, e);
                this.$container.appendChild(e);
            } else {
                // to force all children element be aligned
                // relatively to the wrapper we have to set
                // position CSS to absolute or absolute
                this.$container.style.position = "absolute";
                this.$container.appendChild(e);
            }

            // set ID if it has not been already defined
            if (e.getAttribute("id") === null) {
                e.setAttribute("id", this.toString());
            }

            this.$super();

            // attach listeners
            if (this.$initListeners !== undefined) {
                this.$initListeners();
            }

            var fe = this.$getElementRootFocus();
            // reg native focus listeners for HTML element that can hold focus
            if (fe !== null) {
                var $this = this;

                zebkit.web.$focusin(fe, function(e) {
                    // sync native focus with zebkit focus if necessary
                    if ($this.hasFocus() === false) {
                        $this.requestFocus();
                    }
                }, false);

                zebkit.web.$focusout(fe, function(e) {
                    // sync native focus with zebkit focus if necessary
                    if ($this.hasFocus()) {
                        ui.focusManager.requestFocus(null);
                    }
                }, false);
            }
        },

        function $clazz() {
            this.$bodyFontSize = window.getComputedStyle(document.body, null)
                                       .getPropertyValue('font-size');
        },

        function $prototype() {
            this.$blockElement = this.$canvas = null;

            this.ePsW = this.ePsH = 0;

            /**
             * Every zebkit HTML element is wrapped with a container (div) HTML element.
             * It is required since not all HTML elements are designed to be a container
             * (for instance HTMLCanvas element), where every zebkit has to be a container.
             * @attribute $container
             * @readOnly
             * @private
             * @type {HTMLElement}
             */
            this.$container = null;

            /**
             * Reference to HTML element the UI component wraps
             * @attribute element
             * @readOnly
             * @type {HTMLElement}
             */
            this.element = null;

            /**
             * Indicates that this component is a DOM element wrapper
             * @attribute isDOMElement
             * @type {Boolean}
             * @private
             * @readOnly
             */
            this.isDOMElement = true;   // indication of the DOM element that is used by DOM element manager to track
                                        // and manage its visibility

            this.$sizeAdjusted = false;

            this.wrap = function(c) {
                this.setStackLayout();
                this.add(c);
                return this;
            };

            /**
             * Set the CSS font of the wrapped HTML element
             * @param {String|zebkit.Font} f a font
             * @method setFont
             * @chainable
             */
            this.setFont = function(f) {
                this.setStyle("font", f.toString());
                this.vrp();
                return this;
            };

            /**
             * Set the CSS color of the wrapped HTML element
             * @param {String} c a color
             * @chainable
             * @method setColor
             */
            this.setColor = function(c) {
                this.setStyle("color", c.toString());
                return this;
            };

            this.getColor = function() {
                return window.getComputedStyle(this.element, "color");
            };

            /**
             * Apply the given set of CSS styles to the wrapped HTML element
             * @param {Object} styles a dictionary of CSS styles
             * @chainable
             * @method setStyles
             */
            this.setStyles = function(styles) {
                for(var k in styles) {
                    this.$setStyle(this.element, k, styles[k]);
                }
                this.vrp();
                return this;
            };

            /**
             * Apply the given CSS style to the wrapped HTML element
             * @param {String} a name of the CSS style
             * @param {String} a value the CSS style has to be set
             * @chainable
             * @method setStyle
             */
            this.setStyle = function(name, value) {
                this.$setStyle(this.element, name, value);
                this.vrp();
                return this;
            };

            this.$setStyle = function(element, name, value) {
                name = name.trim();
                var i = name.indexOf(':');
                if (i > 0) {
                    if (zebkit[name.substring(0, i)] !== true) {
                        return;
                    }
                    name = name.substring(i + 1);
                }

                if (element.style[name] !== value) {
                    element.style[name] = value;
                }
                return this;
            };

            /**
             * Set the specified attribute to the wrapped HTML element
             * @param {String} name  a name of attribute
             * @param {String} value a value of the attribute
             * @chainable
             * @method setAttribute
             */
            this.setAttribute = function(name, value) {
                this.element.setAttribute(name, value);
                return this;
            };

            /**
             * Set the specified attributes set to the wrapped HTML element
             * @param {Object} attrs the dictionary of attributes where name of an
             * attribute is a key of the dictionary and
             * @method  setAttributes
             * @chainable
             */
            this.setAttributes = function(attrs) {
                for(var name in attrs) {
                    this.element.setAttribute(name, attrs[name]);
                }
                return this;
            };

            /**
             * Implements "update" method to be aware when the component is visible.
             * It is used to adjust wrapped HTML element visibility and size. Update
             * is the first rendering method that is called, so it is right place
             * to sync HTML element visibility before paint method execution
             * @param  {CanvasRenderingContext2D} g a 2D canvas context
             * @method update
             */
            this.update = function(g) {
                // this method is used as an indication that the component
                // is visible and no one of his parent is invisible
                if (this.$container.style.visibility === "hidden") {
                    this.$container.style.visibility = "visible";
                }

                // calling paint says that the component in DOM tree
                // that is time to correct CSS size if necessary
                if (this.$sizeAdjusted !== true) {
                    this.setSize(this.width, this.height);
                }
            };

            this.calcPreferredSize = function(target) {
                return {
                    width : this.ePsW,
                    height: this.ePsH
                };
            };

            var $store = [
                "paddingTop","paddingLeft","paddingBottom","paddingRight",
                "border","borderStyle","borderWidth", "borderTopStyle",
                "borderTopWidth", "borderBottomStyle","borderBottomWidth",
                "borderLeftStyle","borderLeftWidth", "borderRightStyle",
                "visibility", "borderRightWidth", "width", "height", "position"
            ];

            // the method calculates the given HTML element preferred size
            this.recalc = function() {
                // if component has a layout set it is up to a layout manager to calculate
                // the component preferred size. In this case the HTML element is a container
                // whose preferred size is defined by its content
                if (this.layout === this) {
                    var e         = this.element,
                        vars      = {},
                        domParent = null,
                        k         = null,
                        cv        = this.$container.style.visibility,
                        b         = !zebkit.web.$contains(this.$container);

                    // element doesn't have preferred size if it is not a member of
                    // an html page, so add it if for a while
                    if (b) {
                        // save previous parent node since
                        // appendChild will overwrite it
                        domParent = this.$container.parentNode;
                        document.body.appendChild(this.$container);
                    }

                    // save element metrics
                    for(var i = 0; i < $store.length; i++) {
                        k = $store[i];
                        vars[k] = e.style[k];
                    }

                    // force metrics to be calculated automatically
                    if (cv !== "hidden")  {
                        this.$container.style.visibility = "hidden";
                    }

                    e.style.padding  = "0px";
                    e.style.border   = "none";
                    e.style.position = e.style.height = e.style.width = "auto";

                    // fetch preferred size
                    this.ePsW = e.offsetWidth;
                    this.ePsH = e.offsetHeight;

                    for(k in vars) {
                        var v = vars[k];
                        if (v !== null && e.style[k] !== v) {
                            e.style[k] = v;
                        }
                    }

                    if (this.$container.style.visibility !== cv) {
                        this.$container.style.visibility = cv;
                    }

                    if (b) {
                        document.body.removeChild(this.$container);
                        // restore previous parent node
                        if (domParent !== null) {
                            domParent.appendChild(this.$container);
                        }
                    }
                }
            };

            /**
             * Set the inner content of the wrapped HTML element
             * @param {String} an inner content
             * @method setContent
             * @chainable
             */
            this.setContent = function(content) {
                this.element.innerHTML = content;
                this.vrp();
                return this;
            };

            this.$getElementRootFocus = function() {
                return null;
            };

            this.canHaveFocus = function() {
                return this.$getElementRootFocus() !== null;
            };

            this.$focus = function() {
                if (this.canHaveFocus() && document.activeElement !== this.$getElementRootFocus()) {
                    this.$getElementRootFocus().focus();
                }
            };

            this.$blur = function() {
                if (this.canHaveFocus() && document.activeElement === this.$getElementRootFocus()) {
                    this.$getElementRootFocus().blur();
                }
            };
        },

        function toFront() {
            this.$super();
            var pnode = this.$container.parentNode;
            if (pnode !== null && pnode.lastChild !== this.$container) {
                pnode.removeChild(this.$container);
                pnode.appendChild(this.$container);
            }
            return this;
        },

        function toBack() {
            this.$super();
            var pnode = this.$container.parentNode;
            if (pnode !== null && pnode.firstChild !== this.$container) {
                pnode.removeChild(this.$container);
                pnode.insertBefore(this.$container, pnode.firstChild);
            }
            return this;
        },

        function setEnabled(b) {
            if (this.isEnabled !== b) {
                if (b) {
                    this.$container.removeChild(this.$blockElement);
                } else {
                    if (this.$blockElement === null) {
                        this.$blockElement = zebkit.web.$createBlockedElement();
                    }
                    this.$container.appendChild(this.$blockElement);
               }
            }
            return this.$super(b);
        },

        function setSize(w, h) {
            // by the moment the method setSize is called the DOM element can be not a part of
            // HTML layout. In this case offsetWidth/offsetHeihght are always zero what prevents
            // us from proper calculation of CSS width and height. Postpone
            if (zebkit.web.$contains(this.$container)) {
                var  contStyle      = this.$container.style,
                     elemStyle      = this.element.style,
                     prevVisibility = contStyle.visibility;

                if (contStyle.visibility !== "hidden") {
                    contStyle.visibility = "hidden"; // to make sizing smooth
                }

                // HTML element size is calculated as sum of CSS "width"/"height", paddings, border
                // So the passed width and height has to be corrected (before it will be applied to
                // an HTML element) by reduction of extra HTML gaps. For this we firstly set the
                // width and size
                elemStyle.width  = "" + w + "px";
                elemStyle.height = "" + h + "px";

                var ww = 2 * w - this.element.offsetWidth,
                    hh = 2 * h - this.element.offsetHeight;

                if (ww !== w || hh !== h) {
                    // than we know the component metrics and can compute necessary reductions
                    elemStyle.width   = "" + ww + "px";
                    elemStyle.height  = "" + hh + "px";
                }

                this.$sizeAdjusted = true;

                // visibility correction is done by HTML elements manager
                if (contStyle.visibility !== prevVisibility) {
                    contStyle.visibility = prevVisibility;
                }
            } else {
                this.$sizeAdjusted = false;
            }

            return this.$super(w, h);
        },

        function setPadding(t,l,b,r) {
            if (arguments.length === 1) {
                l = b = r = t;
            }

            this.setStyles({
                paddingTop    : '' + t + "px",
                paddingLeft   : '' + l + "px",
                paddingRight  : '' + r + "px",
                paddingBottom : '' + b + "px"
            });

            if (this.top !== t || this.left !== l || this.right !== r || this.bottom !== b) {
                // changing padding has influence to CSS size the component has to have
                // so we have to request CSS size recalculation
                this.$sizeAdjusted = false;
            }

            this.$super.apply(this, arguments);
            return this;
        },

        function setBorder(b) {
            if (arguments.length === 0) {
                b = "plain";
            }

            b = zebkit.draw.$view(b);

            if (b === null) {
               this.setStyle("border", "none");
            } else {
                this.setStyles({
                    //!!!! bloody FF fix, the border can be made transparent
                    //!!!! only via "border" style
                    border : "0px solid transparent",

                    //!!! FF understands only decoupled border settings
                    borderTopStyle : "solid",
                    borderTopColor : "transparent",
                    borderTopWidth : "" + b.getTop() + "px",

                    borderLeftStyle : "solid",
                    borderLeftColor : "transparent",
                    borderLeftWidth : "" + b.getLeft() + "px",

                    borderBottomStyle : "solid",
                    borderBottomColor : "transparent",
                    borderBottomWidth : "" + b.getBottom() + "px",

                    borderRightStyle : "solid",
                    borderRightColor : "transparent",
                    borderRightWidth : "" + b.getRight() + "px"
                });
            }

            // changing border can have influence to
            // CSS size, so request recalculation of the CSS
            // size
            if (this.border != b) {
                this.$sizeAdjusted = false;
            }

            return this.$super(b);
        },

        function validate() {
            // lookup root canvas
            if (this.$canvas === null && this.parent !== null) {
                this.$canvas = this.getCanvas();
            }

            this.$super();
        },

        function focused() {
            this.$super();

            // sync state of zebkit focus with native focus of the HTML Element
            if (this.hasFocus()) {
                this.$focus();
            } else {
                this.$blur();
            }
        }
    ]).hashable();

    /**
     *  This special private manager that plays key role in integration of HTML ELement into zebkit UI hierarchy.
     *  Description to the class contains technical details of implementation that should not be interested for
     *  end users.
     *
     *  HTML element integrated into zebkit layout has to be tracked regarding:
     *    1) DOM hierarchy. A new added into zebkit layout DOM element has to be attached to the first found
     *       parent DOM element
     *    2) Visibility. If a zebkit UI component change its visibility state it has to have side effect to all
     *       children HTML elements on any subsequent hierarchy level
     *    3) Moving a zebkit UI component has to correct location of children HTML element on any subsequent
     *       hierarchy level.
     *
     *  The implementation of HTML element component has the following specific:
     *    1) Every original HTML is wrapped with "div" element. It is necessary since not all HTML element has been
     *       designed to be a container for another HTML element. By adding extra div we can consider the wrapper as
     *       container. The wrapper element is used to control visibility, location, enabled state
     *    2) HTML element has "isDOMElement" property set to true
     *    3) HTML element visibility depends on an ancestor component visibility. HTML element is visible if:
     *       - the element isVisible property is true
     *       - the element has a parent DOM element set
     *       - all his ancestors are visible
     *       - size of element is more than zero
     *       - getCanvas() != null
     *
     *  The visibility state is controlled with "e.style.visibility"
     *
     *  To support effective DOM hierarchy tracking a zebkit UI component defines "$domKid" property that contains
     *  direct DOM element the UI component hosts and other UI components that host DOM element. This is sort of tree:
     *
     *  <pre>
     *    +---------------------------------------------------------
     *    |  p1 (zebkit component)
     *    |   +--------------------------------------------------
     *    |   |  p2 (zebkit component)
     *    |   |    +---------+      +-----------------------+
     *    |   |    |   h1    |      | p3 zebkit component   |
     *    |   |    +---------+      |  +---------------+    |
     *    |   |                     |  |    h3         |    |
     *    |   |    +---------+      |  |  +---------+  |    |
     *    |   |    |   h2    |      |  |  |   p4    |  |    |
     *    |   |    +---------+      |  |  +---------+  |    |
     *    |   |                     |  +---------------+    |
     *    |   |                     +-----------------------+
     *
     *     p1.$domKids : {
     *         p2.$domKids : {
     *             h1,    * leaf elements are always DOM element
     *             h2,
     *             p3.$domKids : {
     *                h3
     *             }
     *         }
     *     }
     *   </pre>
     *
     *  @constructor
     *  @private
     *  @class zebkit.ui.web.HtmlElementMan
     *  @extends zebkit.ui.event.Manager
     */
    pkg.HtmlElementMan = Class(zebkit.ui.event.Manager, [
        function $prototype() {
            /**
             * Evaluates if the given zebkit HTML UI component is in invisible state.
             * @param  {zebkit.ui.HtmlElement}  c an UI HTML element wrapper
             * @private
             * @method $isInInvisibleState
             * @return {Boolean} true if the HTML element wrapped with zebkit UI is in invisible state
             */
            function $isInInvisibleState(c) {
                if (c.isVisible === false            ||
                    c.$container.parentNode === null ||
                    c.width       <= 0               ||
                    c.height      <= 0               ||
                    c.parent      === null           ||
                    zebkit.web.$contains(c.$container) === false)
                {
                    return true;
                }

                var p = c.parent;
                while (p !== null && p.isVisible === true && p.width > 0 && p.height > 0) {
                    p = p.parent;
                }

                return p !== null || ui.$cvp(c) === null;
            }

            //    +----------------------------------------
            //    |             ^      DOM1
            //    |             .
            //    |             .  (x,y) -> (xx,yy) than correct left
            //                  .  and top of DOM2 relatively to DOM1
            //    |    +--------.--------------------------
            //    |    |        .       zebkit1
            //    |    |        .
            //    |    |  (left, top)
            //    |<............+-------------------------
            //    |    |        |           DOM2
            //    |    |        |
            //
            //  Convert DOM (x, y) zebkit coordinates into appropriate CSS top and left
            //  locations relatively to its immediate DOM element. For instance if a
            //  zebkit component contains DOM component every movement of zebkit component
            //  has to bring to correction of the embedded DOM elements
            function $adjustLocation(c) {
                if (c.$container.parentNode !== null) {
                    // hide DOM component before move
                    // makes moving more smooth
                    var prevVisibility = null;
                    if (c.$container.style.visibility !== "hidden") {
                        prevVisibility = c.$container.style.visibility;
                        c.$container.style.visibility = "hidden";
                    }

                    // find a location relatively to the first parent HTML element
                    var p = c, xx = c.x, yy = c.y;
                    while (((p = p.parent) !== null) && p.isDOMElement !== true) {
                        xx += p.x;
                        yy += p.y;
                    }

                    c.$container.style.left = "" + xx + "px";
                    c.$container.style.top  = "" + yy + "px";
                    if (prevVisibility !== null) {
                        c.$container.style.visibility = prevVisibility;
                    }
                }
            }


            // attach to appropriate DOM parent if necessary
            // c parameter has to be DOM element
            function $resolveDOMParent(c) {
                // try to find an HTML element in zebkit (pay attention, in zebkit hierarchy !)
                // hierarchy that has to be a DOM parent for the given component
                var parentElement = null;
                for(var p = c.parent; p !== null; p = p.parent) {
                    if (p.isDOMElement === true) {
                        parentElement = p.$container;
                        break;
                    }
                }

                // parentElement is null means the component has
                // not been inserted into DOM hierarchy
                if (parentElement !== null && c.$container.parentNode === null) {
                    // parent DOM element of the component is null, but a DOM container
                    // for the element has been detected. We need to add it to DOM
                    // than we have to add the DOM to the found DOM parent element
                    parentElement.appendChild(c.$container);

                    // adjust location of just attached DOM component
                    $adjustLocation(c);
                } else {
                    // test consistency whether the DOM element already has
                    // parent node that doesn't match the discovered
                    if (parentElement           !== null &&
                        c.$container.parentNode !== null &&
                        c.$container.parentNode !== parentElement)
                    {
                        throw new Error("DOM parent inconsistent state ");
                    }
                }
            }

            // iterate over all found children HTML elements
            // !!! pay attention you have to check existence
            // of "$domKids" field before the method calling
            function $domElements(c, callback) {
                for (var k in c.$domKids) {
                    var e = c.$domKids[k];
                    if (e.isDOMElement === true) {
                        callback.call(this, e);
                    } else if (e.$domKids !== undefined) { // prevent unnecessary method call by condition
                        $domElements(e, callback);
                    }
                }
            }

            this.compShown = function(e) {
                // 1) if c is DOM element than we have make it is visible if
                //      -- c.isVisible == true : the component visible  AND
                //      -- all elements in parent chain is visible      AND
                //      -- the component is in visible area
                //
                // 2) if c is not a DOM component his visibility state can have
                //    side effect to his children HTML elements (on any level)
                //    In this case we have to do the following:
                //      -- go through all children HTML elements
                //      -- if c.isVisible == false: make invisible every children element
                //      -- if c.isVisible != false: make visible every children element whose
                //         visibility state satisfies the following conditions:
                //          -- kid.isVisible == true
                //          -- all parent to c are in visible state
                //          -- the kid component is in visible area
                var c = e.source;
                if (c.isDOMElement === true) {
                    c.$container.style.visibility = (c.isVisible === false || $isInInvisibleState(c) ? "hidden"
                                                                                                     : "visible");
                } else if (c.$domKids !== undefined) {
                    $domElements(c, function(e) {
                        e.$container.style.visibility = (e.isVisible === false || $isInInvisibleState(e) ? "hidden" : "visible");
                    });
                }
            };

            this.compMoved = function(e) {
                var c = e.source;

                // if we move a zebkit component that contains
                // DOM element(s) we have to correct the DOM elements
                // locations relatively to its parent DOM
                if (c.isDOMElement === true) {
                    // root canvas location cannot be adjusted since it is up to DOM tree to do it
                    if (c.$isRootCanvas !== true) {
                        var dx   = e.prevX - c.x,
                            dy   = e.prevY - c.y,
                            cont = c.$container;

                        cont.style.left = ((parseInt(cont.style.left, 10) || 0) - dx) + "px";
                        cont.style.top  = ((parseInt(cont.style.top,  10) || 0) - dy) + "px";
                    }
                } else if (c.$domKids !== undefined) {
                    $domElements(c, function(e) {
                        $adjustLocation(e);
                    });
                }
            };

            function isLeaf(c) {
                if (c.$domKids !== undefined) {
                    for(var k in c.$domKids) {
                        if (c.$domKids.hasOwnProperty(k)) {
                            return false;
                        }
                    }
                }
                return true;
            }

            function detachFromParent(p, c) {
                // DOM parent means the detached element doesn't
                // have upper parents since it is relative to the
                // DOM element
                if (p.isDOMElement !== true && p.$domKids !== undefined) {
                    // delete from parent
                    delete p.$domKids[c.$hash$];

                    // parent is not DOM and doesn't have kids anymore
                    // what means the parent has to be also detached
                    if (isLeaf(p)) {
                        // parent of parent is not null and is not a DOM element
                        if (p.parent !== null && p.parent.isDOMElement !== true) {
                            detachFromParent(p.parent, p);
                        }

                        // remove $domKids from parent since the parent is leaf
                        delete p.$domKids;
                    }
                }
            }

            function removeDOMChildren(c) {
                // DOM element cannot have children dependency tree
                if (c.isDOMElement !== true && c.$domKids !== undefined) {
                    for(var k in c.$domKids) {
                        if (c.$domKids.hasOwnProperty(k)) {
                            var kid = c.$domKids[k];

                            // DOM element
                            if (kid.isDOMElement === true) {
                                kid.$container.parentNode.removeChild(kid.$container);
                            } else {
                                removeDOMChildren(kid);
                            }
                        }
                    }
                    delete c.$domKids;
                }
            }

            this.compRemoved = function(e) {
                var c = e.kid;

                // if detached element is DOM element we have to
                // remove it from DOM tree
                if (c.isDOMElement === true) {
                    // DOM component can be detached from document
                    // with a parent component removal so let's
                    // check if it has a DOM parent
                    if (c.$container.parentNode !== null) {
                        c.$container.parentNode.removeChild(c.$container);
                    }
                } else {
                    removeDOMChildren(c);
                }

                detachFromParent(e.source, c);
            };

            this.compAdded = function(e) {
                var p = e.source,  c = e.kid;
                if (c.isDOMElement === true) {
                    $resolveDOMParent(c);
                } else {
                    if (c.$domKids !== undefined) {
                        $domElements(c, function(e) {
                            $resolveDOMParent(e);
                        });
                    } else {
                        return;
                    }
                }

                if (p.isDOMElement !== true) {
                    // we come here if parent is not a DOM element and
                    // inserted children is DOM element or an element that
                    // embeds DOM elements
                    while (p !== null && p.isDOMElement !== true) {
                        if (p.$domKids === undefined) {
                            // if reference to kid DOM element or kid DOM elements holder
                            // has bot been created we have to continue go up to parent of
                            // the parent to register the whole chain of DOM and DOM holders
                            p.$domKids = {};
                            p.$domKids[c.$genHash()] = c;
                            c = p;
                            p = p.parent;
                        } else {
                            var id = c.$genHash();
                            if (p.$domKids.hasOwnProperty(id)) {
                                throw new Error("Inconsistent state for " + c + ", " + c.clazz.$name);
                            }
                            p.$domKids[id] = c;
                            break;
                        }
                    }
                }
            };
        }
    ]);

    // instantiate manager
    pkg.$htmlElementMan = new pkg.HtmlElementMan();

    if (zebkit.ui.event.FocusManager !== undefined) {
        zebkit.ui.event.FocusManager.extend([
            function requestFocus(c) {
                this.$super(c);

                var canvas = null;

                // if the requested for the focus UI componet doesn't belong to a canvas that holds a native
                // focus then let's give native focus to the canvas
                if (c !== null && c !== this.focusOwner && (c.isDOMElement !== true || c.$getElementRootFocus() === null)) {
                    canvas = c.getCanvas();
                    if (canvas !== null && document.activeElement !== canvas.element) {
                        canvas.element.focus();
                    }

                    // if old focus onwer sits on canvas that doesn't hold the native focus
                    // let's clear it
                    if (this.focusOwner !== null && this.focusOwner.getCanvas() !== canvas) {
                        this.requestFocus(null);
                    }
                } else if (this.focusOwner !== null && this.focusOwner.isDOMElement !== true) {
                    // here we check if focus owner belongs to a canvas that has native focus
                    // and if it is not true we give native focus to the canvas
                    canvas = this.focusOwner.getCanvas();
                    if (canvas !== null && document.activeElement !== canvas.element) {
                        canvas.element.focus();
                    }
                }
            },

            function pointerPressed(e){
                if (e.isAction()) {
                    // the problem is a target canvas element get mouse pressed
                    // event earlier than it gets focus what is inconsistent behavior
                    // to fix it a timer is used
                    if (document.activeElement !== e.source.getCanvas().element) {
                        var $this = this;
                        setTimeout(function() {
                            $this.requestFocus(e.source);
                        });
                    } else {
                        this.$$super(e);
                    }
                }
            }
        ]);
    }


    var ui = pkg.cd("..");

    /**
     * HTML Canvas native DOM element wrapper.
     * @constructor
     * @param  {HTMLCanvas} [e] HTML canvas element to be wrapped as a zebkit UI
     * component or nothing to create a new canvas element
     * @class zebkit.ui.web.HtmlCanvas
     * @extends zebkit.ui.web.HtmlElement
     */
    pkg.HtmlCanvas = Class(pkg.HtmlElement,  [
        function(e) {
            if (arguments.length > 0 && e !== null && e.tagName !== "CANVAS") {
                throw new Error("Invalid element '" + e + "'");
            }

            /**
             * Keeps rectangular "dirty" area of the canvas component
             * @private
             * @attribute $da
             * @type {Object}
             *       { x:Integer, y:Integer, width:Integer, height:Integer }
             */
            this.$da = { x: 0, y: 0, width: -1, height: 0 };

            this.$super(arguments.length === 0  || e === null ? "canvas" : e);

            // let HTML Canvas be WEB event transparent
            this.$container.style["pointer-events"] = "none";

            // check if this element has been created
            if (arguments.length === 0 || e === null) {
                // prevent canvas selection
                this.element.onselectstart = function() { return false; };
            }
        },

        function $clazz() {
            this.$ContextMethods = {
                reset : function(w, h) {
                    this.$curState = 0;
                    var s = this.$states[0];
                    s.srot = s.rotateVal = s.x = s.y = s.width = s.height = s.dx = s.dy = 0;
                    s.crot = s.sx = s.sy = 1;
                    s.width = w;
                    s.height = h;
                    this.setFont(ui.font);
                    this.setColor("white");
                },

                $init : function() {
                    // pre-allocate canvas save $states stack
                    this.$states = Array(70);
                    for(var i=0; i < this.$states.length; i++) {
                        var s = {};
                        s.srot = s.rotateVal = s.x = s.y = s.width = s.height = s.dx = s.dy = 0;
                        s.crot = s.sx = s.sy = 1;
                        this.$states[i] = s;
                    }
                },

                translate : function(dx, dy) {
                    if (dx !== 0 || dy !== 0) {
                        var c = this.$states[this.$curState];
                        c.x  -= dx;
                        c.y  -= dy;
                        c.dx += dx;
                        c.dy += dy;
                        this.$translate(dx, dy);
                    }
                },

                rotate : function(v) {
                    var c = this.$states[this.$curState];
                    c.rotateVal += v;
                    c.srot = Math.sin(c.rotateVal);
                    c.crot = Math.cos(c.rotateVal);
                    this.$rotate(v);
                },

                scale : function(sx, sy) {
                    var c = this.$states[this.$curState];
                    c.sx = c.sx * sx;
                    c.sy = c.sy * sy;
                    this.$scale(sx, sy);
                },

                save : function() {
                    this.$curState++;
                    var c = this.$states[this.$curState], cc = this.$states[this.$curState - 1];
                    c.x = cc.x;
                    c.y = cc.y;
                    c.width = cc.width;
                    c.height = cc.height;

                    c.dx = cc.dx;
                    c.dy = cc.dy;
                    c.sx = cc.sx;
                    c.sy = cc.sy;
                    c.srot = cc.srot;
                    c.crot = cc.crot;
                    c.rotateVal = cc.rotateVal;

                    this.$save();
                    return this.$curState - 1;
                },

                restoreAll : function() {
                    while(this.$curState > 0) {
                        this.restore();
                    }
                },

                restore : function() {
                    if (this.$curState === 0) {
                        throw new Error("Context restore history is empty");
                    }

                    this.$curState--;
                    this.$restore();
                    return this.$curState;
                },

                clipRect : function(x,y,w,h){
                    var c = this.$states[this.$curState];
                    if (c.x !== x || y !== c.y || w !== c.width || h !== c.height) {
                        var xx = c.x,
                            yy = c.y,
                            ww = c.width,
                            hh = c.height,
                            xw = x + w,
                            xxww = xx + ww,
                            yh = y + h,
                            yyhh = yy + hh;

                        c.x      = x > xx ? x : xx;
                        c.width  = (xw < xxww ? xw : xxww) - c.x;
                        c.y      = y > yy ? y : yy;
                        c.height = (yh < yyhh ? yh : yyhh) - c.y;

                        if (c.x !== xx || yy !== c.y || ww !== c.width || hh !== c.height) {
                            // begin path is very important to have proper clip area
                            this.beginPath();
                            this.rect(x, y, w, h);
                            this.closePath();
                            this.clip();
                        }
                    }
                }
            };
        },

        function $prototype(clazz) {
            this.$rotateValue = 0;
            this.$crotate = false;
            this.$scaleX = 1;
            this.$scaleY = 1;
            this.$translateX = 0;
            this.$translateY = 0;

            /**
             *  Canvas context
             *  @attribute $context
             *  @private
             *  @type {CanvasRenderingContext2D}
             */
            this.$context = null;


            // set border for canvas has to be set as zebkit border, since canvas
            // is DOM component designed for rendering, so setting DOM border
            // doesn't allow us to render zebkit border
            this.setBorder = function(b) {
                return ui.Panel.prototype.setBorder.apply(this, arguments);
            };

            this.rotate = function(r) {
                this.$rotateValue += r;
                if (this.$context !== null) {
                    this.$context.rotate(r);
                }

                this.vrp();
                return this;
            };

            this.translate = function(dx, dy) {
                this.$translateX += dx;
                this.$translateY += dy;

                if (this.$context !== null) {
                    this.$context.translate(this.$translateX,
                                            this.$translateY);
                }

                this.vrp();
                return this;
            };

            /**
             * Rotate the component coordinate system around the component center.
             * @param  {Number} r a rotation value
             * @chainable
             * @method crotate
             */
            this.crotate = function(r) {
                this.$rotateValue += r;
                this.$crotate = true;

                if (this.$context !== null) {
                    var cx = Math.floor(this.width  / 2),
                        cy = Math.floor(this.height / 2);

                    this.$context.translate(cx, cy);
                    this.$context.rotate(r);
                    this.$context.translate(-cx, -cy);
                }

                this.vrp();
                return this;
            };


            this.scale = function(sx, sy) {
                if (this.$context !== null) {
                    this.$context.scale(sx, sy);
                }
                this.$scaleX = this.$scaleX * sx;
                this.$scaleY = this.$scaleY * sy;
                this.vrp();
                return this;
            };

            this.clearTransformations = function() {
                this.$scaleX = 1;
                this.$scaleY = 1;
                this.$rotateValue = 0;
                this.$crotate = false;
                this.$translateX = 0;
                this.$translateY = 0;
                if (this.$context !== null) {
                    this.$context = zebkit.web.$canvas(this.element, this.width, this.height, true);
                    this.$context.reset(this.width, this.height);
                }
                this.vrp();
                return this;
            };

            // set passing for canvas has to be set as zebkit padding, since canvas
            // is DOM component designed for rendering, so setting DOM padding
            // doesn't allow us to hold painting area proper
            this.setPadding = function() {
                return ui.Panel.prototype.setPadding.apply(this, arguments);
            };

            this.setSize = function(w, h) {
                if (this.width !== w || h !== this.height) {
                    var pw  = this.width,
                        ph  = this.height;


                    this.$context = zebkit.web.$canvas(this.element, w, h);
                    // canvas has one instance of context, the code below
                    // test if the context has been already full filled
                    // with necessary methods and if it is not true
                    // fill it
                    if (this.$context.$states === undefined) {
                        zebkit.web.$extendContext(this.$context, clazz.$ContextMethods);
                    }

                    this.$context.reset(w, h);

                    // if canvas has been rotated apply the rotation to the context
                    if (this.$rotateValue !== 0) {
                        var cx = Math.floor(w / 2),
                            cy = Math.floor(h / 2);

                        if (this.$crotate) {
                            this.$context.translate(cx, cy);
                        }

                        this.$context.rotate(this.$rotateValue);

                        if (this.$crotate) {
                            this.$context.translate(-cx, -cy);
                        }
                    }

                    // if canvas has been scaled apply it to it
                    if (this.$scaleX !== 1 || this.$scaleY !== 1) {
                        this.$context.scale(this.$scaleX, this.$scaleY);
                    }

                    // if canvas has been scaled apply it to it
                    if (this.$translateX !== 0 || this.$translateY !== 0) {
                        this.$context.translate(this.$translateX, this.$translateY);
                    }

                    this.width  = w;
                    this.height = h;

                    // sync state of visibility
                    // TODO: probably it should be in html element manager, manager has
                    // to catch resize event and if size is not 0 correct visibility
                    // now manager doesn't set style visibility to "visible" state
                    // if component size is zero
                    if (this.$container.style.visibility === "hidden" && this.isVisible) {
                        this.$container.style.visibility = "visible";
                    }

                    this.invalidate();

                    // TODO: think to replace it with vrp()
                    this.validate();
                    this.repaint();

                    if (w !== pw || h !== ph) {
                        this.resized(pw, ph);
                    }
                }
                return this;
            };

            /**
             * Convert (x, y) location in
             * @param  {Integer} x a x coordinate
             * @param  {Integer} y an y coordinate
             * @return {Array} two elements array where first
             * element is converted x coordinate and the second element
             * is converted y coordinate.
             * @protected
             * @method project
             */
            this.project = function(x, y) {
                var c = this.$context.$states[this.$context.$curState],
                    xx = x,
                    yy = y;

                if (c.sx !== 1 || c.sy !== 1 || c.rotateVal !== 0) {
                    xx = Math.round((c.crot * x + y * c.srot) / c.sx);
                    yy = Math.round((y * c.crot - c.srot * x) / c.sy);
                }

                xx -= c.dx;
                yy -= c.dy;

                if (this.$crotate) {
                    // rotation relatively rect center should add correction basing
                    // on idea the center coordinate in new coordinate system has
                    // to have the same coordinate like it has in initial coordinate
                    // system
                    var cx = Math.floor(this.width / 2),
                        cy = Math.floor(this.height / 2),
                        dx = Math.round((c.crot * cx + cy * c.srot) / c.sx),
                        dy = Math.round((c.crot * cy - cx * c.srot) / c.sy);
                    xx -= (dx - cx);
                    yy -= (dy - cy);
                }

                return [ xx, yy ];
            };
        },

        // TODO: make sure it is workable solution
        function getComponentAt(x, y) {
            if (this.$translateX !== 0 || this.$translateY !== 0 || this.$rotateValue !== 0 || this.$scaleX !== 1 || this.$scaleY !== 1) {
                var xy = this.project(x, y);
                return this.$super(xy[0], xy[1]);
            } else {
                return this.$super(x, y);
            }
        }
    ]);

    /**
     * Class that wrapped window component with own HTML Canvas.
     * @param  {zebkit.ui.Window} [content] a window component or window root. If
     * content is not defined it will be instantiated automatically. If the component
     * is not passed the new window component (zebkit.ui.Window) will be created.
     * @constructor
     * @extends zebkit.ui.web.HtmlCanvas
     * @class zebkit.ui.web.HtmlWindow
     */
    pkg.HtmlWindow = Class(pkg.HtmlCanvas, [
        function(content) {
            this.$super();

            if (arguments.length === 0) {
                this.win = new ui.Window();
            } else if (zebkit.instanceOf(content, ui.Window)) {
                this.win = content;
            } else {
                this.win = new ui.Window(content);
            }

            var $this = this;
            this.win.getWinContainer = function() {
                return $this;
            };

            /**
             * Root window panel
             * @attribute root
             * @type {zebkit.ui.Panel}
             */
            this.root = this.win.root;
            this.setBorderLayout();
            this.add("center", this.win);
        },

        function $prototype() {
            /**
             * Target window
             * @attribute win
             * @type {zebkit.ui.Window}
             * @readOnly
             */
            this.win = null;

            this.winOpened = function(e) {
                this.win.winOpened(e);
            };

            this.winActivated = function(e){
                this.win.winActivated(e);
            };
        }
    ]);

    pkg.HtmlButton = Class(pkg.HtmlElement, [
        function(c) {
            this.$super("button");
            this.setAttribute("type", "button");
            this.setContent(c);

            var $this = this;
            this.element.onclick = function() {
                $this.fire("fired", $this);
            };
        }
    ]).events("fired");

    /**
     * WEB based HTML components wrapped with as zebkit components.
     * @class zebkit.ui.web.HtmlFocusableElement
     * @constructor
     * @extends zebkit.ui.web.HtmlElement
     */
    pkg.HtmlFocusableElement = Class(pkg.HtmlElement, [
        function $prototype() {
            this.$getElementRootFocus = function() {
                return this.element;
            };
        }
    ]);

    /**
     * HTML input element wrapper class. The class can be used as basis class
     * to wrap HTML elements that can be used to enter a textual information.
     * @constructor
     * @param {String} text a text the text input component has to be filled with
     * @param {String} element an input element name
     * @class zebkit.ui.web.HtmlTextInput
     * @extends zebkit.ui.web.HtmlElement
     */
    pkg.HtmlTextInput = Class(pkg.HtmlFocusableElement, [
        function(text, e) {
            if (text === null) {
                text = "";
            }
            this.$super(e);
            this.setAttribute("tabindex", 0);
            this.setValue(text);

            this.$keyUnifier = new zebkit.web.KeyEventUnifier(this.element, this);
            this.$keyUnifier.preventDefault = function(e, key) {};
        },

        function $prototype() {
            this.cursorType = ui.Cursor.TEXT;

            this.$keyTyped = function(e) {
                e.source = this;
                ui.events.fire("keyTyped", e);
            };

            this.$keyPressed = function(e) {
                e.source = this;
                return ui.events.fire("keyPressed", e);
            };

            this.$keyReleased = function(e) {
                e.source = this;
                return ui.events.fire("keyReleased", e);
            };

            /**
             * Get a text of the text input element
             * @return {String} a text of the  text input element
             * @method getValue
             */
            this.getValue = function() {
                return this.element.value.toString();
            };

            /**
             * Set the text
             * @param {String} t a text
             * @method setValue
             * @chainable
             */
            this.setValue = function(t) {
                if (this.element.value !== t) {
                    this.element.value = t;
                    this.vrp();
                }
                return this;
            };
        }
    ]);

    /**
     * HTML input text element wrapper class. The class wraps standard HTML text field
     * and represents it as zebkit UI component.
     * @constructor
     * @class zebkit.ui.web.HtmlTextField
     * @param {String} [text] a text the text field component has to be filled with
     * @extends zebkit.ui.web.HtmlTextInput
     */
    pkg.HtmlTextField = Class(pkg.HtmlTextInput, [
        function(text) {
            this.$super(text, "input");
            this.element.setAttribute("type",  "text");
        }
    ]);

    /**
     * HTML input text area element wrapper class. The class wraps standard HTML text area
     * element and represents it as zebkit UI component.
     * @constructor
     * @param {String} [text] a text the text area component has to be filled with
     * @class zebkit.ui.web.HtmlTextArea
     * @extends zebkit.ui.web.HtmlTextInput
     */
    pkg.HtmlTextArea = Class(pkg.HtmlTextInput, [
        function(text) {
            this.$super(text, "textarea");
            this.element.setAttribute("rows", 10);
        },

        /**
         * Set the text area resizeable or not resizeable.
         * @param {Boolean} b true to make the text area component resizeable
         * @method setResizeable
         * @chainable
         */
        function setResizeable(b) {
            this.setStyle("resize", b === false ? "none" : "both");
            return this;
        }
    ]);

    /**
     * HTML Link component.
     * @param  {String} text  a text of link
     * @param  {String} [href] an href of the link
     * @extends zebkit.ui.web.HtmlElement
     * @class zebkit.ui.web.HtmlLink
     * @uses zebkit.EventProducer
     * @constructor
     * @event fired
     * @param {zebkit.ui.web.Link} src a link that has been pressed
     */
    pkg.HtmlLink = Class(pkg.HtmlElement, [
        function(text, href) {
            this.$super("a");
            this.setContent(text);
            this.setAttribute("href", arguments.length < 2 ? "#": href);
            var $this = this;
            this.element.onclick = function(e) {
                $this.fire("fired", $this);
            };
        }
    ]).events("fired");

    /**
     * This special wrapper component that has to be used to put HtmlElement into
     * "zebkit.ui.ScrollPan"
     * @example
     *
     *      var htmlElement = new zebkit.ui.web.HtmlElement();
     *      ...
     *      var scrollPan = new zebkit.ui.ScrollPan(new zebkit.ui.web.HtmlScrollContent(htmlElement));
     *
     *
     * @param  {zebkit.ui.web.HtmlElement} t target html component that is going to
     * scrolled.
     * @class zebkit.ui.web.HtmlScrollContent
     * @extends zebkit.ui.web.HtmlElement
     * @constructor
     */
    pkg.HtmlScrollContent = Class(pkg.HtmlElement, [
        function(t) {
            this.$super();
            this.scrollManager = new ui.ScrollPan.ContentPanScrollManager(t);
            this.setLayout(new ui.ScrollPan.ContentPanLayout());
            this.add("center",t);
            this.setBackground("blue");
        }
    ]);


    var ui = pkg.cd("..");

    /**
     * The base class for HTML developing HTML layers.
     * @class zebkit.ui.web.HtmlLayer
     * @constructor
     * @extends zebkit.ui.web.HtmlCanvas
     */
    pkg.HtmlLayer = Class(pkg.HtmlCanvas, []);

    /**
     *  Root layer implementation. This is the simplest UI layer implementation
     *  where the layer always try grabbing all input event
     *  @class zebkit.ui.web.RootLayer
     *  @constructor
     *  @extends zebkit.ui.web.HtmlLayer
     *  @uses zebkit.ui.RootLayerMix
     */
    pkg.RootLayer = Class(pkg.HtmlLayer, ui.RootLayerMix, [
        function $clazz() {
            this.layout = new zebkit.layout.RasterLayout();
        }
    ]);

    /**
     *  Window layer implementation.
     *  @class zebkit.ui.web.WinLayer
     *  @constructor
     *  @extends zebkit.ui.web.HtmlLayer
     *  @uses zebkit.ui.WinLayerMix
     */
    pkg.WinLayer = Class(pkg.HtmlLayer, ui.WinLayerMix, [
        function() {
            this.$super();

            // TODO: why 1000 and how to avoid z-index manipulation
            // the layer has to be placed above other elements that are virtually
            // inserted in the layer
            this.element.style["z-index"] = 10000;
        },

        function $clazz() {
            this.layout = new zebkit.layout.RasterLayout();
        }
    ]);

    /**
     *  Popup layer implementation.
     *  @class zebkit.ui.web.PopupLayer
     *  @constructor
     *  @extends zebkit.ui.web.HtmlLayer
     *  @uses zebkit.ui.PopupLayerMix
     */
    pkg.PopupLayer = Class(pkg.HtmlLayer, ui.PopupLayerMix, [
        function $clazz() {
            this.layout = new ui.PopupLayerLayout([
                function doLayout(target){
                    // TODO:
                    // prove of concept. if layer is active don't allow WEB events comes to upper layer
                    // since there can be another HtmlElement that should not be part of interaction
                    if (target.kids.length > 0) {
                        if (target.$container.style["pointer-events"] !== "auto") {
                            target.$container.style["pointer-events"] = "auto";
                        }
                    } else if (target.$container.style["pointer-events"] !== "none") {
                        target.$container.style["pointer-events"] = "none";  // make the layer transparent for pointer events
                    }

                    this.$super(target);
                }
            ]);
        }
    ]);


    // TODO: dependencies to remove
    //     -- taskSets (util.js)

    pkg.CanvasEvent = Class(zebkit.Event, []);

    var ui = pkg.cd(".."),
        COMP_EVENT = new ui.event.CompEvent();

    // keep pointer owners (the component where cursor/finger placed in)
    pkg.$pointerOwner        = {};
    pkg.$pointerPressedOwner = {};

    /**
     *  zCanvas zebkit UI component class. This is starting point for building zebkit UI. The class is a wrapper
     *  for HTML5 Canvas element. The main goals of the class is catching all native HTML5 Canvas element  events
     *  and translating its into Zebkit UI events.
     *
     *  zCanvas instantiation can trigger a new HTML Canvas will be created and added to HTML DOM tree.
     *  It happens if developer doesn't pass an HTML Canvas element reference or an ID of existing HTML
     *  Canvas element. To re-use an existent in DOM tree HTML5 canvas element pass an id of the canvas
     *  element:
     *
     *       // a new HTML canvas element is created and added into HTML DOM tree
     *       var canvas = zebkit.ui.zCanvas();
     *
     *       // a new HTML canvas element is created into HTML DOM tree
     *       var canvas = zebkit.ui.zCanvas(400,500);  // pass canvas size
     *
     *       // stick to existent HTML canvas element
     *       var canvas = zebkit.ui.zCanvas("ExistentCanvasID");
     *
     *  zCanvas has layered structure. Every layer is responsible for showing and controlling a dedicated
     *  type of UI elements like windows pop-up menus, tool tips and so on. To start building UI use root layer.
     *  The layer is standard zebkit UI panel that is accessible via "root" zCanvas field:
     *
     *       // create canvas
     *       var canvas = zebkit.ui.zCanvas(400,500);
     *
     *       // save reference to canvas root layer where
     *       // hierarchy of UI components have to be hosted
     *       var root = canvas.root;
     *
     *       // fill root with UI components
     *       var label = new zebkit.ui.Label("Label");
     *       label.setBounds(10,10,100,50);
     *       root.add(label);
     *
     *  @class zebkit.ui.zCanvas
     *  @extends zebkit.ui.web.HtmlCanvas
     *  @constructor
     *  @param {String|Canvas} [element] an ID of a HTML canvas element or reference to an HTML Canvas element.
     *  @param {Integer} [width] a width of an HTML canvas element
     *  @param {Integer} [height] a height of an HTML canvas element
     */

    /**
     * Implement the event handler method  to catch canvas initialized event. The event is triggered once the
     * canvas has been initiated and all properties listeners of the canvas are set upped. The event can be
     * used to load saved data.
     *
     *     var p = new zebkit.ui.zCanvas(300, 300, [
     *          function canvasInitialized() {
     *              // do something
     *          }
     *     ]);
     *
     * @event  canvasInitialized
     */
    ui.zCanvas = pkg.zCanvas = Class(pkg.HtmlCanvas, [
        function(element, w, h) {
            // no arguments
            if (arguments.length === 0) {
                w = 400;
                h = 400;
                element = null;
            } else if (arguments.length === 1) {
                w = -1;
                h = -1;
            } else if (arguments.length === 2) {
                h = w;
                w = element;
                element = null;
            }

            // if passed element is string than consider it as
            // an ID of an element that is already in DOM tree
            if (element !== null && zebkit.isString(element)) {
                var id = element;
                element = document.getElementById(id);

                // no canvas can be detected
                if (element === null) {
                    throw new Error("Canvas id='" + id + "' element cannot be found");
                }
            }

            /**
             * Dictionary to track layers by its ids.
             * @attribute $layers
             * @private
             * @type {Object}
             */
            this.$layers = {};

            this.$super(element);


            // since zCanvas is top level element it doesn't have to have
            // absolute position
            this.$container.style.position = "relative";

            // let canvas zCanvas listen WEB event
            this.$container.style["pointer-events"] = "auto";

            // if canvas is not yet part of HTML let's attach it to
            // body.
            if (this.$container.parentNode === null) {
                document.body.appendChild(this.$container);
            }

            // force canvas to have a focus
            if (this.element.getAttribute("tabindex") === null) {
                this.element.setAttribute("tabindex", "1");
            }

            if (w < 0) {
                w = this.element.offsetWidth;
            }

            if (h < 0) {
                h = this.element.offsetHeight;
            }

            // !!!
            // save canvas in list of created Zebkit canvases
            // do it before calling setSize(w,h) method
            this.clazz.$canvases.push(this);

            this.setSize(w, h);

            // sync canvas visibility with what canvas style says
            var cvis = (this.element.style.visibility === "hidden" ? false : true);
            if (this.isVisible !== cvis) {
                this.setVisible(cvis);
            }

            // call event method if it is defined
            if (this.canvasInitialized !== undefined) {
                this.canvasInitialized();
            }

            //var $this = this;

            // this method should clean focus if
            // one of of a child DOM element gets focus
            zebkit.web.$focusin(this.$container, function(e) {
                // TODO: fix and uncomment
                // if (e.target !== $this.$container &&
                //     e.target.parentNode !== null &&
                //     e.target.parentNode.getAttribute("data-zebcont") === null) // TODO: BUG, data-zebcont is not set anymore, use $canvases instead
                // {
                //     ui.focusManager.requestFocus(null);
                // } else {
                //     // clear focus if a focus owner component is hosted with another zCanvas
                //     if (e.target === $this.$container &&
                //         ui.focusManager.focusOwner !== null &&
                //         ui.focusManager.focusOwner.getCanvas() !== $this)
                //     {
                //         ui.focusManager.requestFocus(null);
                //     }
                // }
            }, true);
        },

        function $clazz () {
            this.$canvases  = [];

            this.$getCanvasByElement = function(e) {
                for (var i = 0; i < this.$canvases.length; i++) {
                    if (this.$canvases[i] === e) {
                        return this.$canvases[i];
                    }
                }
                return null;
            };
        },

        function $prototype() {
            /**
             * Indicates this the root canvas element
             * @attribute $isRootCanvas
             * @type {Boolean}
             * @private
             * @default true
             * @readOnly
             */
            this.$isRootCanvas = true;

            /**
             * Indicate if the canvas has to be stretched to fill the whole view port area.
             * @type {Boolean}
             * @attribute isSizeFull
             * @readOnly
             */
            this.isSizeFull = false;


            this.offx = this.offy = 0;

            /**
             * Transforms the pageX coordinate into relatively to the canvas origin
             * coordinate taking in account the canvas transformation
             * @param  {Number} pageX a pageX coordinate
             * @param  {Number} pageY a pageY coordinate
             * @return {Integer} an x coordinate that is relative to the canvas origin
             * @method $toElementX
             * @protected
             */
            this.$toElementX = function(pageX, pageY) {
                // offset has to be added here since "calcOffset" can called (for instance page reloading)
                // to early

                pageX -= (this.offx);
                pageY -= (this.offy);

                var c = this.$context.$states[this.$context.$curState];
                return ((c.sx !== 1 || c.sy !== 1 || c.rotateVal !== 0) ? Math.round((c.crot * pageX + pageY * c.srot)/c.sx)
                                                                        : pageX) - c.dx;
            };

            /**
             * Transforms the pageY coordinate into relatively to the canvas origin
             * coordinate taking in account the canvas transformation
             * @param  {Number} pageX a pageX coordinate
             * @param  {Number} pageY a pageY coordinate
             * @return {Integer} an y coordinate that is relative to the canvas origin
             * @method $toElementY
             * @protected
             */
            this.$toElementY = function(pageX, pageY) {
                // offset has to be added here since "calcOffset" can called (for instance page reloading)
                // to early
                pageX -= (this.offx);
                pageY -= (this.offy);

                var c = this.$context.$states[this.$context.$curState];
                return ((c.sx !== 1 || c.sy !== 1 || c.rotateVal !== 0) ? Math.round((pageY * c.crot - c.srot * pageX)/c.sy)
                                                                        : pageY) - c.dy;
            };

            this.load = function(jsonPath){
                return this.root.load(jsonPath);
            };

            // TODO: may be rename to dedicated method $doWheelScroll
            this.$doScroll = function(dx, dy, src) {
                if (src === "wheel" && pkg.$pointerOwner.mouse !== null && pkg.$pointerOwner.mouse !== undefined) {
                    var owner = pkg.$pointerOwner.mouse;
                    while (owner !== null && owner.doScroll === undefined) {
                        owner = owner.parent;
                    }

                    if (owner !== null) {
                        return owner.doScroll(dx, dy, src);
                    }
                }
                return false;
            };

            /**
             * Catches key typed events, adjusts and distributes it to UI hierarchy
             * @param  {zebkit.ui.event.KeyEvent} e an event
             * @private
             * @method $keyTyped
             * @return {Boolean}  true if the event has been processed
             */
            this.$keyTyped = function(e) {
                if (ui.focusManager.focusOwner !== null) {
                    e.source = ui.focusManager.focusOwner;
                    return ui.events.fire("keyTyped", e);
                } else {
                    return false;
                }
            };

            /**
             * Catches key pressed events, adjusts and distributes it to UI hierarchy
             * @param  {zebkit.ui.event.KeyEvent} e an event
             * @private
             * @method $keyPressed
             * @return {Boolean}  true if the event has been processed
             */
            this.$keyPressed = function(e) {
                // go through layers to detect layerKeyPressed event handler
                for(var i = this.kids.length - 1;i >= 0; i--){
                    var l = this.kids[i];
                    if (l.layerKeyPressed !== undefined && l.layerKeyPressed(e) === true) {
                        return true;
                    }
                }

                if (ui.focusManager.focusOwner !== null) {
                    e.source = ui.focusManager.focusOwner;
                    return ui.events.fire("keyPressed", e);
                } else {
                    e.source = this;
                    return ui.events.fire("keyPressed", e);
                }
            };

            /**
             * Catches key released events, adjusts and distributes it to UI hierarchy
             * @param  {zebkit.ui.event.KeyEvent} e an event
             * @private
             * @method $keyReleased
             * @return {Boolean}  true if the event has been processed
             */
            this.$keyReleased = function(e){
                if (ui.focusManager.focusOwner !== null) {
                    e.source = ui.focusManager.focusOwner;
                    return ui.events.fire("keyReleased", e);
                } else {
                    return false;
                }
            };

            /**
             * Catches pointer entered events, adjusts and distributes it to UI hierarchy
             * @param  {zebkit.ui.event.PointerEvent} e an event
             * @private
             * @method $pointerEntered
             */
            this.$pointerEntered = function(e) {
                // TODO: review it quick and dirty fix try to track a situation
                //       when the canvas has been moved
                this.recalcOffset();

                var x = this.$toElementX(e.pageX, e.pageY),
                    y = this.$toElementY(e.pageX, e.pageY),
                    d = this.getComponentAt(x, y),
                    o = pkg.$pointerOwner.hasOwnProperty(e.identifier) ? pkg.$pointerOwner[e.identifier] : null;

                // also correct current component on that  pointer is located
                if (d !== o) {
                    // if pointer owner is not null but doesn't match new owner
                    // generate pointer exit and clean pointer owner
                    if (o !== null) {
                        delete pkg.$pointerOwner[e.identifier];
                        ui.events.fire("pointerExited", e.update(o, x, y));
                    }

                    // if new pointer owner is not null and enabled
                    // generate pointer entered event ans set new pointer owner
                    if (d !== null && d.isEnabled === true){
                        delete pkg.$pointerOwner[e.identifier];
                        ui.events.fire("pointerEntered", e.update(d, x, y));
                    }
                }
            };

            /**
             * Catches pointer exited events, adjusts and distributes it to UI hierarchy
             * @param  {zebkit.ui.event.PointerEvent} e an event
             * @private
             * @method $pointerExited
             */
            this.$pointerExited = function(e) {
                var o = pkg.$pointerOwner.hasOwnProperty(e.identifier) ? pkg.$pointerOwner[e.identifier] : null;
                if (o !== null) {
                    delete pkg.$pointerOwner[e.identifier];
                    return ui.events.fire("pointerExited", e.update(o,
                                                                    this.$toElementX(e.pageX, e.pageY),
                                                                    this.$toElementY(e.pageX, e.pageY)));
                }
            };

            /**
             * Catches pointer moved events, adjusts and distributes it to UI hierarchy.
             * @param  {zebkit.ui.event.PointerEvent} e an event
             * @private
             * @method $pointerMoved
             */
            this.$pointerMoved = function(e){
                // if a pointer button has not been pressed handle the normal pointer moved event
                var x = this.$toElementX(e.pageX, e.pageY),
                    y = this.$toElementY(e.pageX, e.pageY),
                    d = this.getComponentAt(x, y),
                    o = pkg.$pointerOwner.hasOwnProperty(e.identifier) ? pkg.$pointerOwner[e.identifier] : null,
                    b = false;

                // check if pointer already inside a component
                if (o !== null) {
                    if (d !== o) {
                        delete pkg.$pointerOwner[e.identifier];
                        b = ui.events.fire("pointerExited", e.update(o, x, y));

                        if (d !== null && d.isEnabled === true) {
                            pkg.$pointerOwner[e.identifier] = d;
                            b = ui.events.fire("pointerEntered", e.update(d, x, y)) || b;
                        }
                    } else if (d !== null && d.isEnabled === true) {
                        b = ui.events.fire("pointerMoved", e.update(d, x, y));
                    }
                } else if (d !== null && d.isEnabled === true) {
                    pkg.$pointerOwner[e.identifier] = d;
                    b = ui.events.fire("pointerEntered", e.update(d, x, y));
                }

                return b;
            };

            /**
             * Catches pointer drag started events, adjusts and distributes it to UI hierarchy.
             * @param  {zebkit.ui.event.PointerEvent} e an event
             * @private
             * @method $pointerDragStarted
             */
            this.$pointerDragStarted = function(e) {
                var x = this.$toElementX(e.pageX, e.pageY),
                    y = this.$toElementY(e.pageX, e.pageY),
                    d = this.getComponentAt(x, y);

                // if target component can be detected fire pointer start dragging and
                // pointer dragged events to the component
                if (d !== null && d.isEnabled === true) {
                    return ui.events.fire("pointerDragStarted", e.update(d, x, y));
                }

                return false;
            };

            /**
             * Catches pointer dragged events, adjusts and distributes it to UI hierarchy.
             * @param  {zebkit.ui.event.PointerEvent} e an event
             * @private
             * @method $pointerDragged
             */
            this.$pointerDragged = function(e){
                if (pkg.$pointerOwner.hasOwnProperty(e.identifier)) {
                    return ui.events.fire("pointerDragged", e.update(pkg.$pointerOwner[e.identifier],
                                                                           this.$toElementX(e.pageX, e.pageY),
                                                                           this.$toElementY(e.pageX, e.pageY)));
                }

                return false;
            };

            /**
             * Catches pointer drag ended events, adjusts and distributes it to UI hierarchy.
             * @param  {zebkit.ui.event.PointerEvent} e an event
             * @private
             * @method $pointerDragEnded
             */
            this.$pointerDragEnded = function(e) {
                if (pkg.$pointerOwner.hasOwnProperty(e.identifier)) {
                    return ui.events.fire("pointerDragEnded", e.update(pkg.$pointerOwner[e.identifier],
                                                                       this.$toElementX(e.pageX, e.pageY),
                                                                       this.$toElementY(e.pageX, e.pageY)));
                }
                return false;
            };

            this.$isAbsorbedByLayer = function(id, method, e) {
                e.id = id;
                for(var i = this.kids.length - 1; i >= 0; i--){
                    var layer = this.kids[i];
                    if (layer[method] !== undefined) {
                        if (layer[method](e) === true) {
                            return true;
                        }
                    }
                }
                return false;
            };

            /**
             * Catches pointer clicked events, adjusts and distributes it to UI hierarchy.
             * @param  {zebkit.ui.event.PointerEvent} e an event
             * @private
             * @method $pointerClicked
             */
            this.$pointerClicked = function(e) {
                var x = this.$toElementX(e.pageX, e.pageY),
                    y = this.$toElementY(e.pageX, e.pageY),
                    d = this.getComponentAt(x, y);

                // zoom in zoom out can bring to a situation
                // d is null, in this case offset should be recalculated
                // TODO: the cause of the issue has to be investigated deeper
                if (d === null) {
                    this.recalcOffset();
                    x = this.$toElementX(e.pageX, e.pageY);
                    y = this.$toElementY(e.pageX, e.pageY);
                    d = this.getComponentAt(x, y);
                }

                if (d !== null) {
                    e = e.update(d, x, y);
                    if (this.$isAbsorbedByLayer("pointerClicked", "layerPointerClicked", e)) {
                        return true;
                    } else {
                        return ui.events.fire("pointerClicked", e);
                    }
                } else {
                    return false;
                }
            };

            this.$pointerDoubleClicked = function(e) {
                var x = this.$toElementX(e.pageX, e.pageY),
                    y = this.$toElementY(e.pageX, e.pageY),
                    d = this.getComponentAt(x, y);

                return d !== null ? ui.events.fire("pointerDoubleClicked", e.update(d, x, y))
                                  : false;
            };

            /**
             * Catches pointer released events, adjusts and distributes it to UI hierarchy.
             * @param  {zebkit.ui.event.PointerEvent} e an event
             * @private
             * @method $pointerReleased
             */
            this.$pointerReleased = function(e) {
                var x  = this.$toElementX(e.pageX, e.pageY),
                    y  = this.$toElementY(e.pageX, e.pageY);

                // release pressed state
                if (pkg.$pointerPressedOwner.hasOwnProperty(e.identifier)) {
                    try {
                        e = e.update(pkg.$pointerPressedOwner[e.identifier], x, y);
                        if (this.$isAbsorbedByLayer("pointerReleased", "layerPointerReleased", e) !== true) {
                            ui.events.fire("pointerReleased", e);
                        }
                    } finally {
                        delete pkg.$pointerPressedOwner[e.identifier];
                    }
                }

                // mouse released can happen at new location, so move owner has to be corrected
                // and mouse exited entered event has to be generated.
                // the correction takes effect if we have just completed dragging or mouse pressed
                // event target doesn't match pkg.$pointerOwner
                if (e.pointerType === "mouse" && (e.pressPageX !== e.pageX || e.pressPageY !== e.pageY)) {
                    var nd = this.getComponentAt(x, y),
                        po = this.getComponentAt(this.$toElementX(e.pressPageX, e.pressPageY),
                                                 this.$toElementY(e.pressPageX, e.pressPageY));

                    if (nd !== po) {
                        if (po !== null) {
                            delete pkg.$pointerOwner[e.identifier];
                            ui.events.fire("pointerExited", e.update(po, x, y));
                        }

                        if (nd !== null && nd.isEnabled === true){
                            pkg.$pointerOwner[e.identifier] = nd;
                            ui.events.fire("pointerEntered", e.update(nd, x, y));
                        }
                    }
                }
            };

            /**
             * Catches pointer pressed events, adjusts and distributes it to UI hierarchy.
             * @param  {zebkit.ui.event.PointerEvent} e an event
             * @private
             * @method $pointerPressed
             */
            this.$pointerPressed = function(e) {
                var x  = this.$toElementX(e.pageX, e.pageY),
                    y  = this.$toElementY(e.pageX, e.pageY);

                // free previous pointer pressed state if it was hung up
                if (pkg.$pointerPressedOwner.hasOwnProperty(e.identifier)) {
                    try {
                        ui.events.fire("pointerReleased", e.update(pkg.$pointerPressedOwner[e.identifier], x, y));
                    } finally {
                        delete pkg.$pointerPressedOwner[e.identifier];
                    }
                }

                e.source = null;
                e.x  = x;
                e.y  = y;

                if (this.$isAbsorbedByLayer("pointerPressed", "layerPointerPressed", e)) {
                    return true;
                }

                var d = this.getComponentAt(x, y);
                if (d !== null && d.isEnabled === true) {
                    if (pkg.$pointerOwner[e.identifier] !== d) {
                        pkg.$pointerOwner[e.identifier] = d;
                        ui.events.fire("pointerEntered",  e.update(d, x, y));
                    }

                    pkg.$pointerPressedOwner[e.identifier] = d;

                    // TODO: prove the solution (return true) !?
                    if (ui.events.fire("pointerPressed", e.update(d, x, y)) === true) {
                        delete pkg.$pointerPressedOwner[e.identifier];
                        return true;
                    }
                }

                return false;
            };

            this.getComponentAt = function(x, y) {
                // goes through the layers from top to bottom
                for(var i = this.kids.length; --i >= 0; ){
                    var c = this.kids[i].getComponentAt(x, y);
                    if (c !== null) {
                        // detect a composite parent component that catches
                        // input and return the found composite
                        // TODO: probably this is not good place to detect composition, but it is done here
                        // since real destination component has to be detected before delegating it to event
                        // manager. One of the reason is adjusting (pointer) event coordinates to found
                        // destination component. Event manager knows nothing about an event structure,
                        // whether it has or not coordinates.
                        var p = c;
                        while ((p = p.parent) !== null) {
                            // test if the parent catches input events (what means the parent is a composite component)
                            // and store the composite as result
                            if (p.catchInput !== undefined && (p.catchInput === true || (p.catchInput !== false && p.catchInput(c)))) {
                                c = p;
                            }
                        }
                        return c;
                    }
                }
                return null;
            };

            this.recalcOffset = function() {
                // calculate the DOM element offset relative to window taking in account scrolling
                var poffx = this.offx,
                    poffy = this.offy,
                    ba    = this.$container.getBoundingClientRect();

                this.offx = Math.round(ba.left + zebkit.web.$measure(this.$container, "border-left-width") +
                                                 zebkit.web.$measure(this.$container, "padding-left") + window.pageXOffset);
                this.offy = Math.round(ba.top +  zebkit.web.$measure(this.$container, "padding-top" ) +
                                                 zebkit.web.$measure(this.$container, "border-top-width") + window.pageYOffset);

                if (this.offx !== poffx || this.offy !== poffy) {
                    // force to fire component re-located event
                    this.relocated(this, poffx, poffy);
                }
            };

            /**
             * Get the canvas layer by the specified layer ID. Layer is a children component
             * of the canvas UI component. Every layer has an ID assigned to it the method
             * actually allows developers to get the canvas children component by its ID
             * @param  {String} id a layer ID
             * @return {zebkit.ui.Panel} a layer (children) component
             * @method getLayer
             */
            this.getLayer = function(id) {
                return this.$layers[id];
            };

            // override relocated and resized
            // to prevent unnecessary repainting
            this.relocated = function(px,py) {
                COMP_EVENT.source = this;
                COMP_EVENT.px     = px;
                COMP_EVENT.py     = py;
                ui.events.fire("compMoved", COMP_EVENT);
            };

            this.resized = function(pw,ph) {
                COMP_EVENT.source = this;
                COMP_EVENT.prevWidth  = pw;
                COMP_EVENT.prevHeight = ph;
                ui.events.fire("compSized", COMP_EVENT);
                // don't forget repaint it
                this.repaint();
            };

            this.$initListeners = function() {
                // TODO: hard-coded
                new zebkit.web.PointerEventUnifier(this.$container, this);
                new zebkit.web.KeyEventUnifier(this.element, this); // element has to be used since canvas is
                                                             // styled to have focus and get key events
                new zebkit.web.MouseWheelSupport(this.$container, this);
            };

            /**
             * Force the canvas to occupy the all available view port area
             * @param {Boolean} b true to force the canvas be stretched over all
             * available view port area
             * @chainable
             * @method setSizeFull
             */
            this.setSizeFull = function(b) {
                if (this.isSizeFull !== b) {
                    this.isSizeFull = b;

                    if (b === true) {
                        if (zebkit.web.$contains(this.$container) !== true) {
                            throw new Error("zCanvas is not a part of DOM tree");
                        }

                        this.setLocation(0, 0);

                        // adjust body to kill unnecessary gap for in-line-block zCanvas element
                        // otherwise body size will be slightly horizontally bigger than visual
                        // view-port height what causes scroll appears
                        document.body.style["font-size"] = "0px";

                        var ws = zebkit.web.$viewPortSize();
                        this.setSize(ws.width, ws.height);
                    }
                }
                return this;
            };
        },

        function setSize(w, h) {
            if (this.width !== w || h !== this.height) {
                this.$super(w, h);

                // let know to other zebkit canvases that
                // the size of an element on the page has
                // been updated and they have to correct
                // its anchor.
                pkg.$elBoundsUpdated();
            }
            return this;
        },

        function setVisible(b) {
            var prev = this.isVisible;
            this.$super(b);

            // Since zCanvas has no parent component calling the super
            // method above doesn't trigger repainting. So, do it here.
            if (b !== prev) {
                this.repaint();
            }
            return this;
        },

        function vrp() {
            this.$super();
            if (zebkit.web.$contains(this.element) && this.element.style.visibility === "visible") {
                this.repaint();
            }
        },

        function kidAdded(i,constr,c){
            if (this.$layers.hasOwnProperty(c.id)) {
                throw new Error("Layer '" + c.id + "' already exist");
            }

            this.$layers[c.id] = c;
            if (c.id === "root") {
                this.root = c;
            }

            this.$super(i, constr, c);
        },

        function kidRemoved(i, c, ctr) {
            delete this.$layers[c.id];
            if (c.id === "root") {
                this.root = null;
            }
            this.$super(i, c, ctr);
        }
    ]);

    // canvases location has to be corrected if document layout is invalid
    pkg.$elBoundsUpdated = function() {
        for(var i = pkg.zCanvas.$canvases.length - 1; i >= 0; i--) {
            var c = pkg.zCanvas.$canvases[i];
            if (c.isSizeFull === true) {
                //c.setLocation(window.pageXOffset, -window.pageYOffset);

                var ws = zebkit.web.$viewPortSize();

                // browser (mobile) can reduce size of browser window by
                // the area a virtual keyboard occupies. Usually the
                // content scrolls up to the size the VK occupies, so
                // to leave zebkit full screen content in the window
                // with the real size (not reduced) size take in account
                // scrolled metrics
                c.setSize(ws.width  + window.pageXOffset,
                          ws.height + window.pageYOffset);
            }
            c.recalcOffset();
        }
    };

    var $wrt = null, $winSizeUpdated = false, $wpw = -1, $wph = -1;
    window.addEventListener("resize", function(e) {
        if ($wpw !== window.innerWidth || $wph !== window.innerHeight) {
            $wpw = window.innerWidth;
            $wph = window.innerHeight;

            if ($wrt !== null) {
                $winSizeUpdated = true;
            } else {
                $wrt = zebkit.util.tasksSet.run(
                    function() {
                        if ($winSizeUpdated === false) {
                            pkg.$elBoundsUpdated();
                            this.shutdown();
                            $wrt = null;
                        }
                        $winSizeUpdated = false;
                    }, 200, 150
                );
            }
        }
    }, false);

    window.onbeforeunload = function(e) {
        var msgs = [];
        for (var i = pkg.zCanvas.$canvases.length - 1; i >= 0; i--) {
            if (pkg.zCanvas.$canvases[i].saveBeforeLeave !== undefined) {
                var m = pkg.zCanvas.$canvases[i].saveBeforeLeave();
                if (m !== null && m !== undefined) {
                    msgs.push(m);
                }
            }
        }

        if (msgs.length > 0) {
            var message = msgs.join("  ");
            if (e === undefined) {
                e = window.event;
            }

            if (e) {
                e.returnValue = message;
            }

            return message;
        }
    };

    // TODO: this is deprecated events that can have significant impact to
    // page performance. That means it has to be removed and replace with something
    // else
    //
    // bunch of handlers to track HTML page metrics update
    // it is necessary since to correct zebkit canvases anchor
    // and track when a canvas has been removed
    document.addEventListener("DOMNodeInserted", function(e) {
        pkg.$elBoundsUpdated();
    }, false);

    document.addEventListener("DOMNodeRemoved", function(e) {
        // remove canvas from list
        for(var i = pkg.zCanvas.$canvases.length - 1; i >= 0; i--) {
            var canvas = pkg.zCanvas.$canvases[i];
            if (zebkit.web.$contains(canvas.element) !== true) {
                pkg.zCanvas.$canvases.splice(i, 1);
                if (canvas.saveBeforeLeave !== undefined) {
                    canvas.saveBeforeLeave();
                }
            }
        }

        pkg.$elBoundsUpdated();
    }, false);


    var ui = pkg.cd("..");

    /**
     * Simple video panel that can be used to play a video:
     *
     *
     *       // create canvas, add video panel to the center and
     *       // play video
     *       var canvas = zebkit.ui.zCanvas(500,500).root.properties({
     *           layout: new zebkit.layout.BorderLayout(),
     *           center: new zebkit.ui.web.VideoPan("trailer.mpg")
     *       });
     *
     *
     * @param {String} url an URL to a video
     * @class zebkit.ui.web.VideoPan
     * @extends zebkit.ui.Panel
     * @constructor
     */
    pkg.VideoPan = Class(ui.Panel, [
        function(src) {
            var $this = this;

            /**
             * Original video DOM element that is created
             * to play video
             * @type {Video}
             * @readOnly
             * @attribute video
             */
            this.video  = document.createElement("video");
            this.source = document.createElement("source");
            this.source.setAttribute("src", src);
            this.video.appendChild(this.source);

            this.$super();

            // canplaythrough is video event
            this.video.addEventListener("canplaythrough", function() {
                $this.fire("playbackStateUpdated", [$this, "ready"]);
                $this.repaint();
                $this.$continuePlayback();
            }, false);

            this.video.addEventListener("ended", function() {
                $this.fire("playbackStateUpdated", [$this, "end"]);
                $this.$interruptCancelTask();
            }, false);

            this.video.addEventListener("pause", function() {
                $this.fire("playbackStateUpdated", [$this, "pause"]);
                $this.$interruptCancelTask();
            }, false);

            this.video.addEventListener("play", function() {
                $this.$continuePlayback();
                $this.fire("playbackStateUpdated", [$this, "play"]);
            }, false);

            // progress event indicates a loading progress
            // the event is useful to detect recovering from network
            // error
            this.video.addEventListener("progress", function() {
                // if playback has been postponed due to an error
                // let's say that problem seems fixed and delete
                // the cancel task
                if ($this.$cancelTask !== null) {
                    $this.$interruptCancelTask();

                    // detect if progress event has to try to start animation that has not been
                    // started yet or has been canceled for a some reason
                    if ($this.video.paused === false) {
                        $this.$continuePlayback();
                        $this.fire("playbackStateUpdated", [$this, "continue"]);
                    }
                }
            }, false);

            this.source.addEventListener("error", function(e) {
                $this.$interruptCancelTask();
                $this.$lastError = e.toString();
                $this.fire("playbackStateUpdated", [$this, "error"]);
                $this.repaint();
                $this.pause();
            }, false);

            this.video.addEventListener("stalled", function() {
                $this.$cancelPlayback();
            }, false);

            this.video.addEventListener("loadedmetadata", function (e) {
                $this.videoWidth   = this.videoWidth;
                $this.videoHeight  = this.videoHeight;
                $this.$aspectRatio = $this.videoHeight > 0 ? $this.videoWidth / $this.videoHeight : 0;
                $this.vrp();
            }, false);
        },

        function $clazz() {
            this.SignLabel = Class(ui.Panel, [
                function $clazz() {
                    this.font = new zebkit.Font("bold", 18);
                },

                function setColor(c) {
                    this.kids[0].setColor(c);
                    return this;
                },

                function(title) {
                    this.$super(new zebkit.layout.FlowLayout("center", "center"));
                    this.add(new ui.Label(title).setFont(this.clazz.font));
                    this.setBorder(new zebkit.draw.Border("gray", 1, 8));
                    this.setPadding(6);
                    this.setBackground("white");
                    this.setColor("black");
                }
            ]);
        },

        function $prototype(clazz) {
            this.videoWidth = this.videoHeight = 0;

            this.cancelationTimeout = 20000; // 20 seconds

            this.showSign = true;

            this.$animStallCounter = this.$aspectRatio = 0;
            this.$adjustProportions = true;
            this.$lastError = this.$videoBound = this.$cancelTask = null;
            this.$animCurrentTime = -1;

            this.views = {
                pause  : new clazz.SignLabel("Pause, press to continue").toView(),
                replay : new clazz.SignLabel("Press to re-play").toView(),
                play   : new clazz.SignLabel("Press to play").toView(),
                error  : new clazz.SignLabel("Failed, press to re-try").setColor("red").toView(),
                waiting: new clazz.SignLabel("Waiting ...").setColor("orange").toView()
            };

            this.paint = function(g) {
                if (this.video.paused === false &&
                    this.video.ended  === false &&
                    this.$cancelTask  === null    )
                {
                    if (this.video.currentTime !== this.$animCurrentTime) {
                        this.$animStallCounter = 0;
                        this.repaint();
                    } else {
                        if (this.$animStallCounter > 180) {
                            this.$cancelPlayback();
                        } else {
                            this.$animStallCounter++;
                            this.repaint();
                        }
                    }
                }

                this.$animCurrentTime = this.video.currentTime;

                if (this.$videoBound === null) {
                    this.calcVideoBound();
                }

                g.drawImage(this.video, this.$videoBound.x,
                                        this.$videoBound.y,
                                        this.$videoBound.width,
                                        this.$videoBound.height);

                // draw status sign
                if (this.showSign) {
                    var sign = null;

                    if (this.$lastError !== null) {
                        sign = this.views.error;
                    } else {
                        if (this.$cancelTask !== null) {
                            sign =  this.views.waiting;
                        } else if (this.video.ended) {
                            sign = this.views.replay;
                        } else if (this.video.paused) {
                            if (this.video.currentTime === 0) {
                                sign = this.views.play;
                            } else {
                                sign = this.views.pause;
                            }
                        }
                    }

                    if (sign !== null) {
                        this.paintViewAt(g, "center", "center",  sign);
                    }
                }
            };

            /**
             * Set autoplay for video
             * @param  {Boolean} b an autoplay flag
             * @method autoplay
             * @chainable
             */
            this.autoplay = function(b) {
                this.video.autoplay = b;
                return this;
            };

            /**
             * Pause video
             * @method pause
             * @chainable
             */
            this.pause = function() {
                if (this.video.paused === false) {
                    this.video.pause();
                    this.repaint();
                }
                return this;
            };

            /**
             * Mute sound
             * @param  {Boolean} b true to mute the video sound
             * @method mute
             * @chainable
             */
            this.mute = function(b) {
                this.video.muted = b;
                return this;
            };

            /**
             * Start or continue playing video
             * @method play
             * @chainable
             */
            this.play = function() {
                if (this.video.paused === true) {
                    if (this.$lastError !== null) {
                        this.$lastError = null;
                        this.video.load();
                    }

                    this.video.play();
                    this.repaint();
                }
                return this;
            };

            /**
             * Adjust video proportion to fill maximal space with correct ratio
             * @param  {Boolean} b true if the video proportion has to be adjusted
             * @method adjustProportions
             * @chainable
             */
            this.adjustProportions = function(b) {
                if (this.$adjustProportions !== b) {
                    this.$adjustProportions = b;
                    this.vrp();
                }
                return this;
            };

            this.calcPreferredSize = function(target) {
                return {
                    width  : this.videoWidth,
                    height : this.videoHeight
                };
            };

            this.pointerClicked = function(e) {
                if (this.isPaused()) {
                    this.play();
                } else {
                    this.pause();
                }
            };

            /**
             * Check if the video is paused
             * @method isPaused
             * @return {Boolean} true if the video has been paused
             */
            this.isPaused = function() {
                return this.video.paused;
            };

            /**
             * Check if the video is ended
             * @method isEnded
             * @return {Boolean} true if the video has been ended
             */
            this.isEnded = function() {
                return this.video.ended;
            };

            this.getDuration = function() {
                return this.video.duration;
            };

            this.compSized = function(e) {
                this.$calcVideoBound();
            };

            this.recalc = function() {
                this.$calcVideoBound();
            };

            this.$calcVideoBound = function() {
                this.$videoBound = {
                    x      : this.getLeft(),
                    y      : this.getTop(),
                    width  : this.width  - this.getLeft() - this.getBottom(),
                    height : this.height - this.getTop()  - this.getBottom()
                };

                if (this.$adjustProportions === true && this.$aspectRatio !== 0) {
                    var ar = this.$videoBound.width / this.$videoBound.height;

                    //    ar = 3:1       ar' = 10:3      ar' > ar
                    //   +-------+   +--------------+
                    //   | video |   |    canvas    |   =>  decrease canvas width proportionally ar/ar'
                    //   +-------+   +--------------+
                    //
                    //    ar = 3:1       ar' = 2:1       ar' < ar
                    //   +-----------+   +------+
                    //   |  video    |   |canvas|   =>  decrease canvas height proportionally ar'/ar
                    //   +-----------+   +------+
                    if (ar < this.$aspectRatio) {
                        this.$videoBound.height = Math.floor((this.$videoBound.height * ar) / this.$aspectRatio);
                    } else {
                        this.$videoBound.width = Math.floor((this.$videoBound.width * this.$aspectRatio)/ ar);
                    }

                    this.$videoBound.x = Math.floor((this.width  - this.$videoBound.width )/2);
                    this.$videoBound.y = Math.floor((this.height - this.$videoBound.height)/2);
                }
            };

            this.$continuePlayback = function() {
                this.$interruptCancelTask();
                if (this.video.paused === false && this.video.ended === false) {
                    this.$animCurrentTime  = this.video.currentTime;
                    this.$animStallCounter = 0;
                    this.repaint();
                }
            };

            this.$cancelPlayback = function() {
                if (this.video.paused === true || this.video.ended === true) {
                    this.$interruptCancelTask();
                } else {
                    if (this.$cancelTask === null) {
                        var $this = this;
                        this.$postponedTime = new Date().getTime();

                        this.$cancelTask = zebkit.environment.setInterval(function() {
                            var dt = new Date().getTime() - $this.$postponedTime;
                            if (dt > $this.cancelationTimeout) {
                                try {
                                    if ($this.video.paused === false) {
                                        $this.$lastError = "Playback failed";
                                        $this.pause();
                                        $this.repaint();
                                        $this.fire("playbackStateUpdated", [$this, "error"]);
                                    }
                                } finally {
                                    $this.$interruptCancelTask();
                                }
                            } else {
                                $this.fire("playbackStateUpdated", [$this, "wait"]);
                            }
                        }, 200);
                    }
                }
            };

            this.$interruptCancelTask = function() {
                if (this.$cancelTask !== null) {
                    zebkit.environment.clearInterval(this.$cancelTask);
                    this.$postponedTime = this.$cancelTask = null;
                }
            };
        }
    ]).events("playbackStateUpdated");
},true);