API Docs for: v2.1.0
Show:

File: src\node\class.node.js

/*
 * Copyright (c) 2014 Gwennael Buchet
 *
 * License/Terms of Use
 *
 * Permission is hereby granted, free of charge and for the term of intellectual property rights on the Software, to any
 * person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify
 * and propagate free of charge, anywhere in the world, all or part of the Software subject to the following mandatory conditions:
 *
 *   •    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 *  Any failure to comply with the above shall automatically terminate the license and be construed as a breach of these
 *  Terms of Use causing significant harm to Gwennael Buchet.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
 *  OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 *  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 *  Except as contained in this notice, the name of Gwennael Buchet shall not be used in advertising or otherwise to promote
 *  the use or other dealings in this Software without prior written authorization from Gwennael Buchet.
 *
 *  These Terms of Use are subject to French law.
 */

/**
 * Base class for a Node in the Scene Graph.
 * Each node encapsulates its position, dimension, scale and rotation, ...
 * @class CGSGNode
 * @extends CGSGObject
 * @module Node
 * @main Node
 * @constructor
 * @param {Number} x Relative position on X
 * @param {Number} y Relative position on Y
 * @type {CGSGNode}
 * @author Gwennael Buchet (gwennael.buchet@gmail.com)
 */
var CGSGNode = CGSGObject.extend(
        {
            initialize : function(x, y) {

                /**
                 * The name of this nodes. Should be unique, but no control is done.
                 * @property name
                 * @default ""
                 * @type {String}
                 */
                this.name = "";
                /**
                 * Indicate whether this node is selected or not.
                 * Use CGSGView::scenegraph.selectNode(nodeToSelect) to select a node
                 * @property isSelected
                 * @readonly
                 * @default false
                 * @type {Boolean}
                 */
                this.isSelected = false;
                /**
                 * The type of this class. Must be redefined by inherited classes
                 * @property classType
                 * @readonly
                 * @type {String}
                 */
                this.classType = "CGSGNODE";

                /**
                 * The 8 handleboxes that will be the resize handles
                 * the resize handles will be in this order:
                 *  0  1  2
                 *  3     4
                 *  5  6  7
                 * @property handles
                 * @readonly
                 * @type {Array}
                 */
                this.handles = [];

                /**
                 * Level of transparency of the node.
                 * @default 1.0
                 * @property globalAlpha
                 * @type {Number}
                 */
                this.globalAlpha = 1.0;
                /**
                 * Indicate if the node is visible (and so selectable) or not
                 * @property isVisible
                 * @default true
                 * @type {Boolean}
                 */
                this.isVisible = true;

                /**
                 * If true, the node will be proportionally resized
                 * @property isProportionalResize
                 * @type {Boolean}
                 */
                this.isProportionalResize = false;

                /**
                 * Set to true if the node can't be resized only in height or width
                 * @property isProportionalResizeOnly
                 * @default false
                 * @type {boolean}
                 */
                this.isProportionalResizeOnly = false;

                /**
                 * Define the method the detection (or "pick") method will be used for this node.
                 * Possible values CGSGPickNodeMethod.REGION and CGSGPickNodeMethod.GHOST.
                 *
                 * <ul>
                 *     <li>REGION : the detection returns true if the mouse cursor is inside the bounding box of the node</li>
                 *     <li>GHOST : the detection will use the "renderGhost" method of the node to achieve a more accurate detection</li>
                 * </ul>
                 *
                 * @property pickNodeMethod
                 * @default CGSGPickNodeMethod.REGION
                 * @type {CGSGPickNodeMethod}
                 */
                this.pickNodeMethod = CGSGPickNodeMethod.REGION;

                /**
                 * List of the children (empty if this nodes is a leaf)
                 * @property children
                 * @readonly
                 * @type {Array}
                 */
                this.children = [];

                /**
                 * The constraint region when moving the node
                 * @property regionConstraint
                 * @default null
                 * @type {null}
                 */
                this.regionConstraint = null;

                /**
                 * Pivot point to apply a rotation.
                 * The point is a value between [0, 0] and [1, 1].
                 * [0, 0] is the top left corner of the bounding box and [1, 1] the bottom right corner.
                 * @property rotationCenter
                 * @default null
                 * @type {CGSGPosition}
                 */
                this.rotationCenter = null;

                /**
                 * can be fulfilled by the developer to put in whatever he needs
                 * @property userData
                 * @default {} Empty object
                 * @type {*}
                 */
                this.userData = {};

                /**
                 * selection attributes
                 * If true, this node is clickable and so will be checked by the pickNode function
                 * @property isClickable
                 * @default true
                 * @type {Boolean}
                 */
                this.isClickable = true;
                /**
                 * If true, this node can be resized by the user. In that case, the dimension property will be affected, not the scale one.
                 * @property isResizable
                 * @default false
                 * @type {Boolean}
                 */
                this.isResizable = false;
                /**
                 * If true, the node can be dragged by the user
                 * @property isDraggable
                 * @default false
                 * @type {Boolean}
                 */
                this.isDraggable = false;

                /**
                 * If true, the absolute matrix will be recomputed after each movement (and so in animation).
                 * Set it to false to gain performance if you don't need to keep trace of absolute position (no need to collision, picknode, ...)
                 * @property needToKeepAbsoluteMatrix
                 * @default true
                 * @type {Boolean}
                 */
                this.needToKeepAbsoluteMatrix = true;

                /**
                 * Color for the line around this node when selected
                 * @property selectionLineColor
                 * @default "#FF6890"
                 * @type {String}
                 */
                this.selectionLineColor = null;
                /**
                 * Width for the line around this node when selected
                 * @property selectionLineWidth
                 * @default 2
                 * @type {Number}
                 */
                this.selectionLineWidth = null;
                /**
                 * Color for the handle boxes around this node when selected
                 * @property handleSize
                 * @type {Number}
                 */
                this.handleSize = null;
                /**
                 * Color for the handle boxes around this node when selected
                 * @property handleColor
                 * @type {String}
                 */
                this.handleColor = null;

                /**
                 * Updated by the scene itself. Don't update it manually.
                 * True if the mice is over the node, false otherwise
                 * @property isMouseOver
                 * @readonly
                 * @type {Boolean}
                 */
                this.isMouseOver = false;
                /**
                 * Updated by the scene itself. Don't update it manually.
                 * True if the node is being moved manually, false otherwise
                 * @property isMoving
                 * @readonly
                 * @type {Boolean}
                 */
                this.isMoving = false;
                /**
                 * Updated by the scene itself. Don't update it manually.
                 * True if the node is being resized manually, false otherwise
                 * @property isResizing
                 * @readonly
                 * @type {Boolean}
                 */
                this.isResizing = false;

                /**
                 * ID for the node. Should be filled by the developer. The framework will never use it.
                 * @property _id
                 * @type {Number}
                 * @private
                 */
                this._id = 0;
                /**
                 * parent of this node
                 * @property _parentNode
                 * @type {CGSGNode}
                 * @private
                 */
                this._parentNode = null;

                this._isCached = false;
                //fake canvas to pre-render static display
                this._cacheCanvas = null;
                this._cacheCtx = null;

                /**
                 * @property shadowOffsetX
                 * @default 0
                 * @type {number}
                 */
                this.shadowOffsetX = 0;
                /**
                 * @property shadowOffsetY
                 * @default 0
                 * @type {number}
                 */
                this.shadowOffsetY = 0;
                /**
                 * @property shadowBlur
                 * @default 0
                 * @type {number}
                 */
                this.shadowBlur = 0;
                /**
                 * @property shadowColor
                 * @default "#333333"
                 * @type {string}
                 */
                this.shadowColor = "#333333";

                /**
                 * Relative position of this nodes on the canvas container, relatively to the position of its parent node.
                 * Never use it to move the node, use translateBy/translateWith/translateTo instead
                 * @readonly
                 * @property position
                 * @default CGSGPosition(0, 0)
                 * @type {CGSGPosition}
                 */
                this.position = new CGSGPosition(0, 0);
                /**
                 * Absolute position of this nodes on the canvas container. Generated value. Don't modify it manually
                 * Never use it to move the node, use translateBy/translateWith/translateTo instead
                 * @readonly
                 * @property _absPos
                 * @private
                 * @type {CGSGPosition}
                 */
                this._absPos = new CGSGPosition(0, 0);
                /**
                 * Dimension of this nodes on the canvas container
                 * Never use it to resize the node, use resizeBy/resizeWith/resizeTo instead
                 * @readonly
                 * @property dimension
                 * @default CGSGDimension(0, 0)
                 * @type {CGSGDimension}
                 */
                this.dimension = new CGSGDimension(0, 0);
                /**
                 * Relative scale of this nodes on the canvas container, relatively to the scale of its parent node.
                 * Never use it to scale or resize the node, use scaleBy/scaleWith/scaleTo instead
                 * @readonly
                 * @property scale
                 * @default CGSGScale(1, 1)
                 * @type {CGSGScale}
                 */
                this.scale = new CGSGScale(1, 1);
                /**
                 * Absolute scale of this nodes on the canvas container. Generated value. Don't modify it manually
                 * Never use it to scale the node, use scaleBy/scaleWith/scaleTo instead
                 * @readonly
                 * @property _absSca
                 * @private
                 * @type {CGSGScale}
                 */
                this._absSca = new CGSGScale(1, 1);
                /**
                 * Relative rotation of this nodes on the canvas container, relatively to the rotation of its parent node.
                 * Never use it to rotate or resize the node, use rotateBy/rotateWith/rotateTo instead
                 * @readonly
                 * @property rotation
                 * @default CGSGRotation(0)
                 * @type {CGSGRotation}
                 */
                this.rotation = new CGSGRotation(0);
                /**
                 * Absolute rotation of this nodes on the canvas container. Generated value. Don't modify it manually
                 * Never use it to rotate or resize the node, use rotateBy/rotateWith/rotateTo instead
                 * @readonly
                 * @private
                 * @property _absRot
                 * @type {CGSGRotation}
                 */
                this._absRot = new CGSGRotation(0);

                /**
                 * @property _isDrag
                 * @type {Boolean}
                 * @private
                 */
                this._isDrag = false;

                /**
                 * true if this node is traversable (recursively) (ie : by the picknode, a traverser, ...)
                 * @property isTraversable
                 * @type {Boolean}
                 */
                this.isTraversable = true;

                /**
                 * Indicate if this node is managed by the collision manager
                 * @property isCollisionManaged
                 * @type {Boolean}
                 */
                this.isCollisionManaged = false;

                /**
                 * Callback on mouse over the node
                 * @property onMouseOver
                 * @default null
                 * @type {function}
                 *
                 * @example
                 *  this.onMouseOver = function (event) {
             *      event.data.node; //CGSGNode
             *      event.data.position; //Array of CGSGPosition
             *      event.data.nativeEvent; //Event
             *  }
                 */
                this.onMouseOver = null;
                /**
                 * Callback on mouse enter on the node
                 * @property onMouseEnter
                 * @default null
                 * @type {function}
                 *
                 * @example
                 *  this.onMouseEnter = function (event) {
             *      event.data.node; //CGSGNode
             *      event.data.position; //Array of CGSGPosition
             *      event.data.nativeEvent; //Event
             *  }
                 */
                this.onMouseEnter = null;
                /**
                 * Callback on mouse out
                 * @property onMouseOut
                 * @default null
                 * @type {function}
                 *
                 * @example
                 *  this.onMouseOut = function (event) {
             *      event.data.node; //CGSGNode
             *      event.data.position; //Array of CGSGPosition
             *      event.data.nativeEvent; //Event
             *  }
                 */
                this.onMouseOut = null;
                /**
                 * Callback on mouse up
                 * @property onMouseUp
                 * @default null
                 * @type {function}
                 *
                 * @example
                 *  this.onMouseUp = function (event) {
             *      event.data.node; //CGSGNode
             *      event.data.position; //Array of CGSGPosition
             *      event.data.nativeEvent; //Event
             *  }
                 */
                this.onMouseUp = null;
                /**
                 * Callback on mouse or touch click
                 * @property onClick
                 * @default null
                 * @type {function}
                 *
                 * @example
                 *  this.onClick = function (event) {
             *      event.data.node; //CGSGNode
             *      event.data.position; //Array of CGSGPosition
             *      event.data.nativeEvent; //Event
             *  }
                 */
                this.onClick = null;
                /**
                 * Callback on mouse or touch double click
                 * @property onDblClick
                 * @default null
                 * @type {function}
                 *
                 * @example
                 *  this.onDblClick = function (event) {
             *      event.data.node; //CGSGNode
             *      event.data.position; //Array of CGSGPosition
             *      event.data.nativeEvent; //Event
             *  }
                 */
                this.onDblClick = null;
                /**
                 * Callback on drag this node
                 * @property onDrag
                 * @default null
                 * @type {function}
                 *
                 * @example
                 *  this.onDrag = function (event) {
             *      event.data.node; //CGSGNode
             *      event.data.position; //Array of CGSGPosition
             *      event.data.nativeEvent; //Event
             *  }
                 */
                this.onDrag = null;
                /**
                 * Callback on end of drag this node
                 * @property onDragEnd
                 * @default null
                 * @type {function}
                 *
                 * @example
                 *  this.onDragEnd = function (event) {
             *      event.data.node; //CGSGNode
             *      event.data.position; //Array of CGSGPosition
             *      event.data.nativeEvent; //Event
             *  }
                 */
                this.onDragEnd = null;
                /**
                 * Callback on resize this node
                 * @property onResize
                 * @default null
                 * @type {function}
                 *
                 * @example
                 *  this.onResize = function (event) {
             *      event.data.node; //CGSGNode
             *      event.data.position; //Array of CGSGPosition
             *      event.data.nativeEvent; //Event
             *  }
                 */
                this.onResize = null;
                /**
                 * Callback on end resize this node
                 * @property onResizeEnd
                 * @default null
                 * @type {function}
                 *
                 * @example
                 *  this.onResizeEnd = function (event) {
             *      event.data.node; //CGSGNode
             *      event.data.position; //Array of CGSGPosition
             *      event.data.nativeEvent; //Event
             *  }
                 */
                this.onResizeEnd = null;
                /**
                 * Callback on select this node
                 * @property onSelect
                 * @default null
                 * @type {function}
                 *
                 * @example
                 *  this.onSelect = function (event) {
             *      event.data.node; //CGSGNode
             *      event.data.position; //Array of CGSGPosition
             *      event.data.nativeEvent; //Event
             *  }
                 */
                this.onSelect = null;

                /**
                 * Callback on deselect this node
                 * @property onDeselect
                 * @default null
                 * @type {function}
                 *
                 * @example
                 *  this.onDeselect = function (event) {
             *      event.data.node; //CGSGNode
             *      event.data.position; //Array of CGSGPosition
             *      event.data.nativeEvent; //Event
             *  }
                 */
                this.onDeselect = null;

                /**
                 * Callback on when a child is removed
                 * @property onChildRemove
                 * @default null
                 * @type {function}
                 *
                 * @example
                 *  this.onDeselect = function (event) {
             *      event.data.node; //CGSGNode
             *      event.data.position; //Array of CGSGPosition
             *      event.data.nativeEvent; //Event
             *  }
                 */
                this.onChildRemove = null;

                /**
                 * Callback before the node is rendered, children included
                 * @property onBeforeRender
                 * @default null
                 * @type {function}
                 * */
                this.onBeforeRender = null;
                /**
                 * Callback after the scene is rendered
                 * @property onAfterRender
                 * @default null
                 * @type {function}
                 * */
                this.onAfterRender = null;
                /**
                 * @property onAfterRenderStart
                 * @default null
                 * @type {function}
                 * */
                this.onAfterRenderStart = null;
                /**
                 * Callback fired while translating
                 * @property onTranslate
                 * @default null
                 * @type {function}
                 * */
                this.onTranslate = null;

                /**
                 * Threshold applied when detecting selection. Defalt value is picked from CGSG.globalDetectSelectionThreshold.
                 * Value could be changed after.
                 *
                 * @property detectSelectionThreshold
                 * @type {Number}
                 * @example
                 * var n = new CGSGNode(10, 10);
                 * n.detectSelectionThreshold = 10; // node will be picked in an area [0, 0, 30, 30] (x, y, w, h)
                 */
                this.detectSelectionThreshold = CGSG.globalDetectSelectionThreshold;

                /**
                 * Array of colors to fill the background of the node. Will be overrided with CSS content.
                 * CGSGNode extensions should (but not mandatory) use this attribute
                 *
                 * CSS managed.
                 * @property bkgcolors
                 * @type {Array}
                 */
                this.bkgcolors = [];

                /**
                 * Color to stroke the node. Will be overrided with CSS content
                 * CGSGNode extensions should (but not mandatory) use this attribute as the stroke color for their node
                 *
                 * CSS managed.
                 * @property lineColor
                 * @type {String}
                 */
                this.lineColor = null;
                /**
                 * Width of the line that stroke the node. Will be overrided with CSS content.
                 * CGSGNode extensions should (but not mandatory) use this attribute as the strokeWidth for their node
                 * Let 0 if you don't want to stroke the node.
                 *
                 * CSS managed.
                 * @property lineWidth
                 * @default 0
                 * @type {Number}
                 */
                this.lineWidth = 0;

                /**
                 * Corner radius. Used by only few official nodes and maybe by some community's nodes.
                 *
                 * CSS managed.
                 *
                 * @property borderRadius
                 * @type {number}
                 * @default 0
                 */
                this.borderRadius = 0;

                this._cls = [];
                this._clsBBox = null;
                this.setClass("cgsgnode");
                this.setClassBBox("cgsgnode-bbox");

                //initialize the position and dimension
                this.translateTo(x, y, true);
                this.resizeTo(0, 0);

                /**
                 * Set to true if dimension of the node is not the original one anymore
                 * @property _isDimensionChanged
                 * @default false
                 * @private
                 */
                this._isDimensionChanged = false;

                // initialize the selection handleBoxes
                for (var i = 0 ; i < 8 ; i++) {
                    var handleBox = new CGSGHandleBox(this, this.handleSize, this.handleColor,
                                                      this.selectionLineColor, this.selectionLineWidth, 0, 0);
                    this.handles.push(handleBox);
                }

                this.computeAbsoluteMatrix(true);
            },

            /**
             * @method moveLocalZIndex
             * @param s {Number} step
             */
            moveLocalZIndex : function(s) {
                var i = this.getLocalZIndex();

                if (!isNaN(i)) {
                    this.setLocalZIndex(this.getLocalZIndex() + s);
                }
            },

            /**
             * @method setLocalZIndex
             * @param i {Number} index
             */
            setLocalZIndex : function(i) {
                if (cgsgExist(this._parentNode)) {
                    i = Math.max(0, Math.min(CGSGMath.fixedPoint(i), this._parentNode.children.length - 1));

                    var n = this.getLocalZIndex();
                    var p = this._parentNode;

                    if (i != n) {
                        p.detachChild(this);
                        p.addChildAt(this, i);
                    }
                }
            },

            setLocalZIndexToLast : function() {
                this.setLocalZIndex(this._parentNode.children.length - 1);
            },

            getLocalZIndex : function() {
                if (!cgsgExist(this._parentNode))
                    return NaN;

                return this._parentNode.children.indexOf(this);
            },

            /**
             * return the relative region of this node
             * @public
             * @method getRegion
             * @return {CGSGRegion}
             */
            getRegion : function() {
                return new CGSGRegion(this.position.x, this.position.y, this.getWidth(), this.getHeight());
            },

            /**
             * return the absolute region of this node
             * @public
             * @method getAbsoluteRegion
             * @return {CGSGRegion}
             */
            getAbsoluteRegion : function() {
                return new CGSGRegion(this.getAbsLeft(), this.getAbsTop(), this.getAbsWidth(),
                                      this.getAbsHeight());
            },

            //// RENDERING MANIPULATION //////

            /**
             * Wipes the canvas context
             * @method _clearContext
             * @param c {?} context
             * @param w {Number} canvasWidth
             * @param h {Number} canvasHeight
             * @private
             */
            _clearContext : function(c, w, h) {
                c.clearRect(0, 0, w, h);
            },

            /**
             * Use this method to make the node precomputed or not.
             * If it's precomputed, it won't be redraw every frame, but only when the "invalidate" method is called.
             * @method setPrecomputed
             * @param p {Boolean} isPrecomputed
             */
            setPrecomputed : function(p) {
                this._isCached = p;
                this.invalidate();
            },

            /**
             * Force the redraw of the node if it's precomputed
             * @method invalidate
             */
            invalidate : function() {
                if (this._isCached) {
                    this._preCompute();
                }
            },

            /**
             * Reload theme (colors, ...) from loaded CSS file
             * To be overrided
             * @method invalidateTheme
             *
             */
            invalidateTheme : function() {

                //Use of "this._cls" class names which define the current CSS classes used by this object.
                var fs = CGSG.cssManager.getAttrInArray(this._cls, "background");//"background-color");
                var lw = CGSG.cssManager.getAttrInArray(this._cls, "border-width");
                var ss = CGSG.cssManager.getAttrInArray(this._cls, "border-color");
                var a = CGSG.cssManager.getFloat(CGSG.cssManager.getAttrInArray(this._cls, "opacity"));
                var r = CGSG.cssManager.getNumber(CGSG.cssManager.getAttrInArray(this._cls, "border-radius"));

                if (cgsgExist(r))
                    this.borderRadius = r;

                var rgb;

                if (cgsgExist(fs)) {
                    //value is given as "rgb(xx, yy, zz)". Let's convert it to hex
                    rgb = CGSGColor.fromString(fs);
                    if (cgsgExist(rgb.r) && cgsgExist(rgb.g) && cgsgExist(rgb.b)) {
                        this.bkgcolors[0] = CGSGColor.rgb2hex(rgb.r, rgb.g, rgb.b);
                    }
                    else {
                        //value is given as "linear-gradient(rgb(150, 150, 150), rgb(127, 127, 127))".
                        // Let's convert it to 2 hex.
                        var srgb1 = fs.substring(fs.indexOf("rgb"), fs.indexOf(")") + 1);
                        var srgb2 = fs.substring(fs.lastIndexOf("rgb"), fs.lastIndexOf(")"));
                        var rgb1 = CGSGColor.fromString(srgb1);
                        var rgb2 = CGSGColor.fromString(srgb2);
                        this.bkgcolors[0] = CGSGColor.rgb2hex(rgb1.r, rgb1.g, rgb1.b);
                        this.bkgcolors[1] = CGSGColor.rgb2hex(rgb2.r, rgb2.g, rgb2.b);
                    }

                }
                if (cgsgExist(lw))
                    this.lineWidth = CGSG.cssManager.getNumber(lw);
                if (cgsgExist(ss))
                    this.lineColor = ss;

                //avoid to override previous value if no one was defined
                if (cgsgExist(a))
                    this.globalAlpha = a;

                if (cgsgExist(this._clsBBox)) {
                    var sc = CGSG.cssManager.getAttr(this._clsBBox, "background-color");
                    var sw = CGSG.cssManager.getNumber(CGSG.cssManager.getAttr(this._clsBBox, "border-width"));
                    var slc = CGSG.cssManager.getAttr(this._clsBBox, "outline-color");
                    var slw = CGSG.cssManager.getNumber(CGSG.cssManager.getAttr(this._clsBBox, "outline-width"));

                    if (cgsgExist(sc))
                        this.handleColor = sc;
                    if (cgsgExist(sw))
                        this.handleSize = sw;
                    if (cgsgExist(slc))
                        this.selectionLineColor = slc;
                    if (cgsgExist(slw))
                        this.selectionLineWidth = slw;
                }
            },

            /**
             * Set CSS class for this node (not for bounding box, use 'setClassBBox' instead).
             * CSS class must define attributes used by this node.
             * @method setClass
             * @param {String} cls
             */
            setClass : function(cls) {
                this._cls = [];
                this._cls.push(cls);
                this.invalidateTheme();
            },

            /**
             * Add CSS class for this node (not for bounding box, use 'setClassBBox' instead).
             * CSS class must define attributes used by this node.
             * @method addClass
             * @param {String} cls
             */
            addClass : function(cls) {
                this._cls.push(cls);
                this.invalidateTheme();
            },

            /**
             * remove CSS class for this node (not for bounding box, use 'setClassBBox' instead).
             * @method removeClass
             * @param {String} cls
             */
            removeClass : function(cls) {
                this._cls = this._cls.without(cls);
                this.invalidateTheme();
            },

            /**
             * Set CSS class for the bounding box of this node (not for node itself, use 'setClass' instead).
             * CSS class must define attributes used by BBox.
             * @method setClassBBox
             * @param {String} cls
             */
            setClassBBox : function(cls) {
                this._clsBBox = cls;
                this.invalidateTheme();
            },

            /**
             * @method _applyShadow
             * @param c {CanvasRenderingContext2D}
             * @private
             */
            _applyShadow : function(c) {
                if (this.shadowOffsetX !== 0 || this.shadowOffsetY !== 0) {
                    c.shadowOffsetX = this.shadowOffsetX;
                    c.shadowOffsetY = this.shadowOffsetY;
                    c.shadowBlur = this.shadowBlur;
                    c.shadowColor = this.shadowColor;
                }
            },

            /**
             * @method _preCompute
             * @private
             */
            _preCompute : function() {
                if (!cgsgExist(this._cacheCanvas)) {
                    this._cacheCanvas = document.createElement('canvas');
                    this._cacheCtx = this._cacheCanvas.getContext('2d');
                }
                this._cacheCanvas.width = this.getWidth() + this.shadowOffsetX; //CGSG.canvas.width;
                this._cacheCanvas.height = this.getHeight() + this.shadowOffsetY; //CGSG.canvas.height;
                cgsgClearContext(this._cacheCtx);

                this._applyShadow(this._cacheCtx);
                this.render(this._cacheCtx);
            },

            /**
             * internal method of the framework that encapsulate all the work aroud the rendering method
             * @method doRender
             * @param c {CanvasRenderingContext2D} context
             * @param t {Boolean} isThemeInvalidated
             */
            doRender : function(c, t) {
                if (t) {
                    this.invalidateTheme();
                }

                var ctx = c;

                var startEvt = new CGSGEvent(this, {context : c});

                if (cgsgExist(this.onBeforeRender)) {
                    CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_BEFORE_RENDER, startEvt);
                    ctx = startEvt.data.context;
                    //this.onBeforeRender({context: context});
                }

                //save current state
                this.beforeRender(ctx);

                if (this.globalAlpha > 0) {
                    ctx.globalAlpha = this.globalAlpha;

                    if (this._isCached) {
                        //render the pre-rendered canvas
                        ctx.drawImage(this._cacheCanvas, 0, 0);
                    }
                    else {
                        if (!cgsgExist(this.bkgcolors[1])) {
                            ctx.fillStyle = this.bkgcolors[0];
                        }
                        else {
                            var gradient = ctx.createLinearGradient(0, 0, 0, this.dimension.height);
                            for (var i = 0, len = this.bkgcolors.length ; i < len ; i++)
                                gradient.addColorStop(i, this.bkgcolors[i]);
                            ctx.fillStyle = gradient;
                        }

                        if (this.lineWidth > 0) {
                            ctx.strokeStyle = this.lineColor;
                            ctx.lineWidth = this.lineWidth;
                        }

                        this._applyShadow(ctx);
                        this.render(ctx);
                    }
                }

                if (!CGSG.isBoundingBoxOnTop && this.isSelected)
                    this.renderBoundingBox(ctx);

                var endEvt = new CGSGEvent(this, {context : ctx});

                //restore state
                this.afterRender(endEvt.data.context, t);

                if (cgsgExist(this.onAfterRender)) {
                    CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_AFTER_RENDER, endEvt);
                    ctx = endEvt.data.context;
                    //this.onAfterRender({context: c});
                }

            },

            /**
             * Empty rendering function. Must be overrided by the inherited classes
             * @method render
             * @param {CanvasRenderingContext2D} context the context into render the node
             * */
            render : function(context) {
            },

            /**
             * internal method of the framework that encapsulate all the work around the ghost rendering method
             * @method doRenderGhost
             * @param c {CanvasRenderingContext2D} ghost Context
             */
            doRenderGhost : function(c) {
                //save current state
                this.beforeRenderGhost(c);

                if (this.globalAlpha > 0) {
                    if (this._isCached) {
                        //render the pre-rendered canvas
                        c.drawImage(this._cacheCanvas, 0, 0);
                    }
                    else {
                        c.fillStyle = this.bkgcolors[0];

                        if (this.lineWidth > 0) {
                            c.strokeStyle = this.lineColor;
                            c.lineWidth = this.lineWidth;
                        }

                        this.renderGhost(c);
                    }
                }

                //restore state
                this.afterRenderGhost(c);
            },

            /**
             * Empty ghost rendering function.
             * Render here your custom nodes with a single color (CGSG.ghostColor).
             * This will be used by the SceneGraph to know if the mouse cursor is over this nodes.
             *
             * @method renderGhost
             * @param ghostCtx {CanvasRenderingContext2D} The context for the ghost rendering
             */
            renderGhost : function(ghostCtx) {
                this.render(ghostCtx);
            },

            /**
             * Render the selection box and handle boxes around the bounding box of this node when selected
             * @protected
             * @method renderBoundingBox
             * @param c {CanvasRenderingContext2D} context the context into render the node
             * */
            renderBoundingBox : function(c) {
                //this.computeAbsoluteMatrix(true);

                var w = this.getWidth(), h = this.getHeight();

                c.strokeStyle = this.selectionLineColor;

                c.lineWidth = this.selectionLineWidth / this._absSca.y;
                c.beginPath();
                //top line
                c.moveTo(0, 0);
                c.lineTo(w, 0);
                //bottom line
                c.moveTo(0, h);
                c.lineTo(w, h);
                c.stroke();
                c.closePath();

                c.lineWidth = this.selectionLineWidth / this._absSca.x;
                c.beginPath();
                //left line
                c.moveTo(0, 0);
                c.lineTo(0, h);
                //right line
                c.moveTo(w, 0);
                c.lineTo(w, h);
                c.stroke();
                c.closePath();

                //draw the resize handles
                if (this.isResizable) {
                    // draw the handle boxes
                    var hx = this.handleSize / (2 * this._absSca.x);
                    var hy = this.handleSize / (2 * this._absSca.y);

                    // 0  1  2
                    // 3     4
                    // 5  6  7

                    // top left, middle, right
                    this.handles[0].translateTo(-hx, -hy);
                    this.handles[1].translateTo(w / 2 - hx, -hy);
                    this.handles[2].translateTo(w - hx, -hy);

                    // middle left
                    this.handles[3].translateTo(-hx, h / 2 - hy);

                    // middle right
                    this.handles[4].translateTo(w - hx, h / 2 - hy);

                    // bottom left, middle, right
                    this.handles[6].translateTo(w / 2 - hx, h - hy);
                    this.handles[5].translateTo(-hx, h - hy);
                    this.handles[7].translateTo(w - hx, h - hy);

                    if (this.isProportionalResizeOnly) {
                        this.handles[1].isVisible = false;
                        this.handles[3].isVisible = false;
                        this.handles[4].isVisible = false;
                        this.handles[6].isVisible = false;
                    }

                    var i;
                    for (i = 0 ; i < 8 ; i++) {
                        this.handles[i].size = this.handleSize;
                        this.handles[i].fillColor = this.handleColor;
                        this.handles[i].strokeColor = this.selectionLineColor;
                        this.handles[i].lineWidth = this.selectionLineWidth;
                        this.handles[i].render(c);
                    }
                }
            },

            /**
             * Must be called before to start the rendering of the nodes
             * @protected
             * @method beforeRender
             * @param c {CanvasRenderingContext2D} context the context into render the nodes
             * */
            beforeRender : function(c) {
                //first save the current c state
                c.save();

                //move the c to the nodes's relative position
                c.translate(this.position.x, this.position.y);
                c.scale(this.scale.x, this.scale.y);

                // translate c to center of canvas
                if (cgsgExist(this.rotationCenter)) {
                    c.translate(this.dimension.width * this.rotationCenter.x,
                                this.dimension.height * this.rotationCenter.y);
                    c.rotate(this.rotation.angle);
                    c.translate(-this.dimension.width * this.rotationCenter.x,
                                -this.dimension.height * this.rotationCenter.y);
                }
                else {
                    c.rotate(this.rotation.angle);
                }

                if (this.onBeforeRenderEnd) {
                    CGSG.eventManager.dispatch(this, cgsgEventTypes.BEFORE_RENDER_END,
                                               new CGSGEvent(this, {c : c}));
                }
            },

            /**
             * Must be called after a render
             * @protected
             * @method afterRender
             * @param c {CanvasRenderingContext2D} The context into render the nodes
             * @param t {Boolean} isThemeInvalidated true if you need to reload theme for children of this node
             * */
            afterRender : function(c, t) {
                if (cgsgExist(this.onAfterRenderStart)) {
                    CGSG.eventManager.dispatch(this, cgsgEventTypes.AFTER_RENDER_START,
                                               new CGSGEvent(this, {context : c}));
                }

                //render all children
                if (!this.isALeaf()) {
                    //draw children
                    for (var i = 0, len = this.children.length ; i < len ; ++i) {
                        var childNode = this.children[i];
                        if (childNode.isVisible) {
                            childNode.doRender(c, t);
                        }
                    }
                }

                //restore the context state
                c.restore();
            },

            /**
             * Must be called before begin to render the nodes in GHOST mode
             * @protected
             * @method beforeRenderGhost
             * @param c {CanvasRenderingContext2D} context the context into render the nodes
             */
            beforeRenderGhost : function(c) {
                //first save the current context state
                c.save();
                //move the context to the nodes's relative position
                c.translate(this._absPos.x, this._absPos.y);
                c.rotate(this._absRot.angle);
                c.scale(this._absSca.x, this._absSca.y);
            },

            /**
             * Must be called before begin to render
             * @protected
             * @method afterRenderGhost
             * @param c {CanvasRenderingContext2D} context the context into render the nodes
             * */
            afterRenderGhost : function(c) {
                //restore the context state
                c.restore();
            },

            /**
             * Mark this nodes as selected
             * @method setSelected
             * @param s {Boolean} is Selected
             * */
            setSelected : function(s) {
                this.isSelected = s;
                this._isDrag = true;

                if (s && this.onSelect !== null) {
                    //this.onSelect({node: this});
                    CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_SELECT, new CGSGEvent(this, {node : this}));
                }
                else if (this.onDeselect !== null) {
                    CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_DESELECT, new CGSGEvent(this, {node : this}));
                    //this.onDeselect({node: this});
                }
            },

            /**
             * Returns a region which represents the total surface covered by this node and its children too.
             * @protected
             * @method getCompleteRegion
             * @return {CGSGRegion}
             */
            getCompleteRegion : function() {
                return new CGSGRegion(this.getMinAbsoluteLeft(), this.getMinAbsoluteTop(), this.getMaxAbsoluteRight(),
                                      this.getMaxAbsoluteBottom());
            },

            /**
             * Returns a position which represents the lowest position covered between this node and its children too.
             * @protected
             * @method getCompletePosition
             * @return {CGSGPosition}
             */
            getCompletePosition : function() {
                return new CGSGPosition(this.getMinAbsoluteLeft(), this.getMinAbsoluteTop());
            },

            /**
             * Returns a position which represents the highest dimension covered between this node and its children too.
             * @protected
             * @method getCompleteDimension
             * @return {CGSGDimension}
             */
            getCompleteDimension : function() {
                return new CGSGDimension(this.getMaxAbsoluteRight(), this.getMaxAbsoluteBottom());
            },

            /**
             * return this if this nodes is under the mice cursor
             * Can be overrided by inherited klass to optimize this perform.
             * This default function used the ghost rendering method
             * @protected
             * @method detectSelection
             * @param pos {CGSGPosition} mousePosition A CGSGPosition object
             * @param c {CanvasRenderingContext2D} ghost Context
             * @param s {CGSGScale} absoluteScale
             */
            detectSelection : function(pos, c, s) {
                if (this.pickNodeMethod == CGSGPickNodeMethod.REGION) {
                    if (pos.x >= this._absPos.x - this.detectSelectionThreshold
                            &&
                        pos.x <
                        this._absPos.x + this.detectSelectionThreshold + this.getWidth() * s.x
                            && pos.y >= this._absPos.y - this.detectSelectionThreshold
                        &&
                        pos.y <
                        this._absPos.y + this.detectSelectionThreshold + this.getHeight() * s.y
                        ) {
                        return this;
                    }
                }
                else /*if (this.pickNodeMethod == CGSGPickNodeMethod.GHOST)*/ {

                    //todo: is this node is in cache, so check if there is a painted pixel at x,y from getImageData()

                    // draw shape onto ghost context
                    this.doRenderGhost(c);

                    // get image data at the mouse x,y pixel
                    var id = c.getImageData(pos.x, pos.y, 1, 1);

                    cgsgClearContext(c);

                    // if the mouse pixel exists, select this nodes
                    if (id.data[0] != 0 || id.data[1] != 0 || id.data[2] != 0) {
                        return this;
                    }
                }

                return null;
            },

            /**
             * return this if this nodes is under the region
             * Can be overrided by inherited klass to optimize this perform.
             * This default function used the ghost rendering method
             * @protected
             * @method detectSelectionInRegion
             * @param rg {CGSGRegion} region The region to check
             * @param c {CanvasRenderingContext2D} ghostContext
             */
            detectSelectionInRegion : function(rg, c) {

                //if (this.pickNodeMethod == CGSGPickNodeMethod.REGION) {

                var us = this.getAbsoluteRegion();
                //select this node only if it is totally inside the region
                if (cgsgRegionIsInRegion(us, rg, 0)) {
                    return this;
                }

                /*}
                 else { //if (this.pickNodeMethod == CGSGPickNodeMethod.GHOST) {
                 // draw shape onto ghost context
                 this.doRenderGhost(c);

                 // get image data at the mouse x,y pixel
                 if (!rg.isEmpty()) {
                 var id = c.getImageData(rg.position.x, rg.position.y, rg.dimension.width, rg.dimension.height);

                 cgsgClearContext(c);

                 var len = id.data.length;
                 var count = 0;
                 // if the a pixel exists in the region then, select this node
                 for (var i = 0 ; i < len ; i += 4) {
                 if (id.data[i] != 0 || id.data[i + 1] != 0 || id.data[i + 2] != 0) {
                 count += 4;
                 //return this;
                 }
                 }
                 if (count >= len)
                 return this;
                 }
                 }*/

                return null;
            },

            /**
             * Check if this nodes is under the cursor position.
             * @public
             * @method pickNode
             * @param pos {CGSGPosition} mousePosition position of the mouse on the canvas
             * @param absSca {CGSGScale} absoluteScale a CGSGScale absolute relativeScale of all parents
             * @param c {CanvasRenderingContext2D} ghostContext a copy of the canvas context
             * @param {Boolean} recursively if false, don't traverse the children of this nodes
             * @param {Function} condition Condition to be picked
             * ie: "color=='yellow'" or "classType=='CGSGNodeImage' && this.globalAlpha>0.5"
             */
            pickNode : function(pos, absSca, c, recursively, condition) {
                var selectedNode = null;
                var childAbsoluteScale = null;
                if (cgsgExist(absSca)) {
                    childAbsoluteScale = absSca.multiply(this.scale);
                }
                else {
                    childAbsoluteScale = this.getAbsoluteScale(false);
                }

                if (this.isTraversable && (this.isClickable || this.isResizable || this.isDraggable)) {
                    if (!cgsgExist(condition) || condition(this) === true) {
                        this.computeAbsoluteMatrix(false);

                        // First of all, try to to see if resize handler has been picked
                        if (this.isSelected && this.isResizable) {
                            for (var h = 0 ; h < 8 ; h++) {
                                var selectionHandle = this.handles[h];

                                if (selectionHandle.checkIfSelected(pos, CGSG.resizeHandleThreshold)) {
                                    return this;
                                }
                            }
                        }

                        selectedNode =
                        this.detectSelection(pos, c, childAbsoluteScale);
                    }
                }

                //traverse the children if asked
                if (this.isTraversable && recursively && !this.isALeaf()) {
                    for (var i = this.children.length - 1 ; i >= 0 ; --i) {
                        var childNode = this.children[i];
                        var selectedChild = childNode.pickNode(pos,
                                                               childAbsoluteScale, c,
                                                               recursively, condition);
                        if (cgsgExist(selectedChild)) {
                            selectedNode = selectedChild;
                            break;
                        }
                    }

                    childAbsoluteScale = null;
                }

                return selectedNode;
            },

            /**
             * Return all nodes (Array) in the given region
             * @public
             * @method pickNodes
             * @param {CGSGRegion} region of the canvas to check
             * @param {CGSGScale} absoluteScale a CGSGScale absolute relativeScale of all parents
             * @param {CanvasRenderingContext2D} ghostContext a copy of the canvas context
             * @param {Boolean} recursively if false, don't traverse the children of this nodes
             * @param {Function} condition Condition to be picked
             * ie: "color=='yellow'" or "classType=='CGSGNodeImage' && this.globalAlpha>0.5"
             */
            pickNodes : function(region, absoluteScale, ghostContext, recursively, condition) {
                var selectedNodes = [];

                //if (region.dimension.width == 0 && region.dimension.height == 0)
                if (region.isEmpty())
                    return selectedNodes;

                var childAbsoluteScale = null;
                if (cgsgExist(absoluteScale)) {
                    childAbsoluteScale = absoluteScale.multiply(this.scale);
                }
                else {
                    childAbsoluteScale = this.getAbsoluteScale(false);
                }

                if (this.isTraversable && (/*this.isClickable ||*/ this.isResizable || this.isDraggable)) {
                    if (!cgsgExist(condition) || condition(this) === true) {
                        this.computeAbsoluteMatrix(false);
                        var selected = this.detectSelectionInRegion(region, ghostContext);
                        if (cgsgExist(selected)) {
                            selectedNodes.push(selected);
                        }
                    }
                }

                //traverse the children if asked
                if (this.isTraversable && recursively && !this.isALeaf()) {
                    for (var i = this.children.length - 1 ; i >= 0 ; --i) {
                        var childNode = this.children[i];
                        var selectedChildren = childNode.pickNodes(region,
                                                                   childAbsoluteScale, ghostContext,
                                                                   recursively, condition);
                        if (selectedChildren !== null && selectedChildren !== undefined) {
                            selectedNodes = selectedNodes.concat(selectedChildren);
                        }
                    }

                    childAbsoluteScale = null;
                }

                return selectedNodes;
            },

            /**
             * Return true if this nodes has no child
             * @method isALeaf
             * */
            isALeaf : function() {
                return this.children.length <= 0;
            },

            //// TRANSFORMATION MANIPULATION //////

            /**
             * Replace current relative position by this new one
             * @method translateTo
             * @param {Number} x
             * @param {Number} y
             * @param {Boolean} computeAbsoluteValue (default: true)
             */
            translateTo : function(x, y, computeAbsoluteValue) {
                this.position.translateTo(x, y);
                if (this.needToKeepAbsoluteMatrix && computeAbsoluteValue !== false) {
                    this._absPos = this.getAbsolutePosition(true);
                }

                if (cgsgExist(this.onTranslate)) {
                    CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_TRANSLATE, new CGSGEvent(this, null));
                }
            },

            /**
             * Add new coordinate to the current relative one
             * @method translateWith
             * @param {Number} x
             * @param {Number} y
             * @param {Boolean} computeAbsoluteValue (default: true)
             * */
            translateWith : function(x, y, computeAbsoluteValue) {
                this.position.translateWith(x, y);
                if (this.needToKeepAbsoluteMatrix && computeAbsoluteValue !== false) {
                    this._absPos = this.getAbsolutePosition(true);
                }

                if (cgsgExist(this.onTranslate)) {
                    CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_TRANSLATE, new CGSGEvent(this, null));
                }
            },

            /**
             * Add new coordinate to the current relative one
             * @method translateBy
             * @param {Number} x
             * @param {Number} y
             * @param {Boolean} computeAbsoluteValue (default: true)
             * */
            translateBy : function(x, y, computeAbsoluteValue) {
                this.position.translateBy(x, y);
                if (this.needToKeepAbsoluteMatrix && computeAbsoluteValue !== false) {
                    this._absPos = this.getAbsolutePosition(true);
                }

                if (cgsgExist(this.onTranslate)) {
                    CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_TRANSLATE, new CGSGEvent(this, null));
                }
            },

            /**
             * Replace current dimension by these new ones
             * @method resizeTo
             * @param {Number} w
             * @param {Number} h
             * */
            resizeTo : function(w, h) {
                this.dimension.resizeTo(w, h);
                this._endResize();
            },

            /**
             * Multiply current dimension by these new ones
             * @method resizeTBy
             * @param {Number} wf
             * @param {Number} hf
             * */
            resizeBy : function(wf, hf) {
                this.dimension.resizeBy(wf, hf);
                this._endResize();
            },

            /**
             * Increase/decrease current dimension with adding values
             * @method resizeWith
             * @param {Number} w
             * @param {Number} h
             * */
            resizeWith : function(w, h) {
                this.dimension.resizeWith(w, h);
                this._endResize();
            },

            _endResize : function() {
                this._isDimensionChanged = true;
                this.invalidate();
                if (cgsgExist(this.onResize)) {
                    CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_RESIZE, new CGSGEvent(this, {node : this}));
                }
            },

            /**
             * Replace current relative relativeScale by this new one
             * @method scaleTo
             * @param {Number} sx
             * @param {Number} sy
             * @param {Boolean} computeAbsoluteValue (default: true)
             * */
            scaleTo : function(sx, sy, computeAbsoluteValue) {
                this.scale.x = sx;
                this.scale.y = sy;
                if (this.needToKeepAbsoluteMatrix && computeAbsoluteValue !== false) {
                    this._absSca = this.getAbsoluteScale(true);
                }

                if (cgsgExist(this.onScale)) {
                    CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_SCALE, new CGSGEvent(this, {node : this}));
                }
            },

            /**
             * Multiply this relativeScale factor by the current relative relativeScale
             * @method scaleBy
             * @param {Number} sfx
             * @param {Number} sfy
             * @param {Boolean} computeAbsoluteValue (default: true)
             * */
            scaleBy : function(sfx, sfy, computeAbsoluteValue) {
                this.scale.x *= sfx;
                this.scale.y *= sfy;
                if (this.needToKeepAbsoluteMatrix && computeAbsoluteValue !== false) {
                    this._absSca = this.getAbsoluteScale(true);
                }

                if (cgsgExist(this.onScale)) {
                    CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_SCALE, new CGSGEvent(this, {node : this}));
                }
            },

            /**
             * Add to the current relative Scale
             * @method scaleWith
             * @param {Number} x
             * @param {Number} y
             * @param {Boolean} computeAbsoluteValue (default: true)
             * */
            scaleWith : function(x, y, computeAbsoluteValue) {
                this.scale.x += x;
                this.scale.y += y;
                if (this.needToKeepAbsoluteMatrix && computeAbsoluteValue !== false) {
                    this._absSca = this.getAbsoluteScale(true);
                }

                if (cgsgExist(this.onScale)) {
                    CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_SCALE, new CGSGEvent(this, {node : this}));
                }
            },

            /**
             * Replace current relative relativeRotation by this new oneScale
             * @method rotateTo
             * @param {Number} a
             * @param {Boolean} computeAbsoluteValue (default: true)
             *
             * */
            rotateTo : function(a, computeAbsoluteValue) {
                this.rotation.rotateTo(a);
                if (this.needToKeepAbsoluteMatrix && computeAbsoluteValue !== false) {
                    this._absRot = this.getAbsoluteRotation(true);
                }

                if (cgsgExist(this.onRotate)) {
                    CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_ROTATE, new CGSGEvent(this, {node : this}));
                }
            },

            /**
             * Multiply this relativeScale factor by the current relative relativeScale
             * @method rotateBy
             * @param {Number} af
             * @param {Boolean} computeAbsoluteValue (default: true)
             * */
            rotateBy : function(af, computeAbsoluteValue) {
                this.rotation.rotateBy(af);
                if (this.needToKeepAbsoluteMatrix && computeAbsoluteValue !== false) {
                    this._absRot = this.getAbsoluteRotation(true);
                }

                if (cgsgExist(this.onRotate)) {
                    CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_ROTATE, new CGSGEvent(this, {node : this}));
                }
            },

            /**
             * Add this angle to the current relative relativeRotation
             * @method rotateWith
             * @param {Number} a
             * @param {Boolean} computeAbsoluteValue (default: true)
             * */
            rotateWith : function(a, computeAbsoluteValue) {
                this.rotation.rotateWith(a);
                if (this.needToKeepAbsoluteMatrix && computeAbsoluteValue !== false) {
                    this._absRot = this.getAbsoluteRotation(true);
                }

                if (cgsgExist(this.onRotate)) {
                    CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_ROTATE, new CGSGEvent(this, {node : this}));
                }
            },

            //// CHILDREN MANIPULATION //////

            /**
             * Add a new nodes into this one, at the end of the list
             * @method addChild
             * @param {CGSGNode} newNode the nodes to add as a child
             * */
            addChild : function(newNode) {
                newNode._parentNode = this;
                this.children[this.children.length] = newNode;
            },

            /**
             * Add a new nodes at a particular index in the list of children.
             * If the index is too large, the nodes will be inserted at the end of the list
             * @method addChildAt
             * @param {CGSGNode} newNode the nodes to insert as a child
             * @param index {Number} index the position of the new child in the list
             * */
            addChildAt : function(newNode, i) {
                if (i > this.children.length) {
                    i = this.children.length;
                }

                for (var j = this.children.length ; j >= i ; --j) {
                    this.children[j] = this.children[j - 1];
                }

                newNode._parentNode = this;
                this.children[i] = newNode;

                if (cgsgExist(this.onChildAdd)) {
                    CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_CHILD_ADD, new CGSGEvent(this, {node : this}));
                }
            },

            /**
             * Remove the child passed in parameter and delete it
             * @method removeChild
             * @param {CGSGNode} node the nodes to remove
             * @param {Boolean} searchRecursively if true, search the nodes on all the tree from this nodes
             * @return {Boolean} true if the child was correctly removed or false if the nodes was not found.
             * */
            removeChild : function(node, searchRecursively) {
                var index = this.children.indexOf(node);

                if (index >= 0) {
                    this.children.without(node);

                    if (cgsgExist(this.onChildRemove)) {
                        CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_CHILD_REMOVED,
                                                   new CGSGEvent(this, {node : this}));
                    }
                    node.free();

                    return true;
                }

                if (searchRecursively) {
                    for (var i = 0, len = this.children.length ; i < len ; ++i) {
                        var childNode = this.children[i];
                        if (childNode.removeChild(node, true)) {
                            return true;
                        }
                    }
                }

                return false;
            },

            /**
             * remove all children, delete them and reset the current parameters
             * @method removeAll
             * */
            removeAll : function() {
                /*for (var i = this.children.length; i >=0; --i) {
                 var childNode = this.children[i];
                 childNode.removeAll();
                 this.removeChild(childNode, true);
                 }*/

                this.free();
            },

            /**
             * Detach the nodes in index 'index' without delete it. So it's not a child anymore
             * @method detachChildAt
             * @param i {Number} index
             */
            detachChildAt : function(i) {
                if (i >= 0 && i < this.children.length) {
                    this.detachChild(this.children[i]);
                }
            },

            /**
             * Detach the nodes without delete it. So it's not a child anymore
             * @method detachChild
             * @param {CGSGNode} childNode
             */
            detachChild : function(childNode) {
                if (cgsgExist(childNode)) {
                    childNode._parentNode = null;
                    /*this.children = */
                    this.children.without(childNode);
                }
            },

            /**
             * Execute/Eval the script passed in parameter in "this" scope.
             * Used to set new value to an attribute of a node
             * @method evalSet
             * @param a {String} attribute The attribute to be changed
             * @param v {*} value The new value for the attribute
             *
             * @example node.evalSet("position.y", 12);
             */
            evalSet : function(a, v) {
                var rgb;
                //check for common properties to optimize performances
                if (a == "position.x") {
                    this.translateTo(v, this.position.y, this.needToKeepAbsoluteMatrix);
                }
                else if (a == "position.y") {
                    this.translateTo(this.position.x, v, this.needToKeepAbsoluteMatrix);
                }
                else if (a == "dimension.width") {
                    this.resizeTo(v, this.dimension.height);
                }
                else if (a == "dimension.height") {
                    this.resizeTo(this.dimension.width, v);
                }
                else if (a == "scale.x") {
                    this.scaleTo(v, this.scale.y, this.needToKeepAbsoluteMatrix);
                }
                else if (a == "scale.y") {
                    this.scaleTo(this.scale.x, v, this.needToKeepAbsoluteMatrix);
                }
                else if (a == "rotation" || a == "rotation.angle") {
                    this.rotateTo(v, this.needToKeepAbsoluteMatrix);
                }
                else if (a == "globalAlpha" || a == "opacity") {
                    this.globalAlpha = v;
                    this.invalidate();
                }
                else if (a == "isVisible") {
                    this.isVisible = v;
                }
                else if (a == "rotationCenter.x") {
                    this.rotationCenter.x = v;
                }
                else if (a == "rotationCenter.y") {
                    this.rotationCenter.y = v;
                }
                else if (a == "bkgcolors[0].r") {
                    rgb = CGSGColor.hex2rgb(this.bkgcolors[0]);
                    this.bkgcolors[0] = CGSGColor.rgb2hex(v, rgb.g, rgb.b);
                    this.invalidate();
                }
                else if (a == "bkgcolors[0].g") {
                    rgb = CGSGColor.hex2rgb(this.bkgcolors[0]);
                    this.bkgcolors[0] = CGSGColor.rgb2hex(rgb.r, v, rgb.b);
                    this.invalidate();
                }
                else if (a == "bkgcolors[0].b") {
                    rgb = CGSGColor.hex2rgb(this.bkgcolors[0]);
                    this.bkgcolors[0] = CGSGColor.rgb2hex(rgb.r, rgb.g, v);
                    this.invalidate();
                }
                else if (a == "fillStyle.r") {
                    this.fillStyle.r = v;
                    this.invalidate();
                }
                else if (a == "fillStyle.g") {
                    this.fillStyle.g = v;
                    this.invalidate();
                }
                else if (a == "fillStyle.b") {
                    this.fillStyle.b = v;
                    this.invalidate();
                }

                //generic property
                else {
                    eval("this." + a + "=" + v);
                    this.invalidate();
                }

                /*if (this.needToKeepAbsoluteMatrix) {
                 if (a.indexOf("position") == 0) {
                 this._absPos = this.getAbsolutePosition(true);
                 }
                 else if (a.indexOf("rotation") == 0) {
                 this._absRot = this.getAbsoluteRotation(true);
                 }
                 else if (a.indexOf("scale") == 0) {
                 this._absSca = this.getAbsoluteScale(true);
                 }
                 }*/
            },

            /**
             * Set the region inside which one this node ca be placed an can move
             * @public
             * @method setRegionConstraint
             * @param {CGSGRegion} region a CGSGRegion relatively to this parent region. Can be null.
             */
            setRegionConstraint : function(region) {
                this.regionConstraint = region;
            },

            /**
             * @public
             * @method getAbsolutePosition
             * @param {boolean} recursive flag indicating if computation should be recusive or not
             * @return {CGSGPosition} the absolute positions of this node
             */
            getAbsolutePosition : function(recursive) {
                var n = this;
                var translation = this.position.copy();
                while (n._parentNode !== null) {
                    translation.multiplyEquals(n._parentNode.scale);
                    n = n._parentNode;
                }

                if (this._parentNode !== null) {
                    translation.addEquals(this._parentNode.getAbsolutePosition(false));
                }

                if (recursive !== false) {
                    for (var c = 0 ; c < this.children.length ; c++) {
                        if (cgsgExist(this.children[c])) {
                            this.children[c]._absPos = this.children[c].getAbsolutePosition(recursive);
                        }
                    }
                }

                return translation;
            },

            /**
             * @public
             * @method getAbsoluteScale
             * @param {boolean} recursive flag indicating if computation should be recusive or not
             * @return {CGSGScale} the absolute scale of this node
             */
            getAbsoluteScale : function(recursive) {
                var n = this;
                var s = this.scale.copy();
                while (n._parentNode !== null) {
                    s.multiplyEquals(n._parentNode.scale);
                    n = n._parentNode;
                }

                if (recursive !== false) {
                    for (var c = 0 ; c < this.children.length ; c++) {
                        if (cgsgExist(this.children[c])) {
                            this.children[c]._absSca = this.children[c].getAbsoluteScale(recursive);
                        }
                    }
                }
                return s;
            },

            /**
             * @public
             * @method getAbsoluteRotation
             * @param {boolean} recursive flag indicating if computation should be recusive or not
             * @return {CGSGRotation} the absolute rotation of this node
             */
            getAbsoluteRotation : function(recursive) {
                var n = this;
                var r = this.rotation.copy();
                while (n._parentNode !== null) {
                    r.addEquals(n._parentNode.rotation.angle);
                    n = n._parentNode;
                }

                if (recursive !== false) {
                    for (var c = 0 ; c < this.children.length ; c++) {
                        if (cgsgExist(this.children[c])) {
                            this.children[c]._absRot = this.children[c].getAbsoluteRotation(recursive);
                        }
                    }
                }

                return r;
            },

            /**
             * Compute the absolute position, rotation and scale in the canvas container
             * @public
             * @method computeAbsoluteMatrix
             * @param {Boolean} recursive if !== false, compute recursively
             * */
            computeAbsoluteMatrix : function(recursive) {
                this._absPos = this.getAbsolutePosition(false);
                this._absSca = this.getAbsoluteScale(false);
                this._absRot = this.getAbsoluteRotation(false);

                if (recursive !== false) {
                    //for (var c = 0; c < this.children.length; c++) {
                    cgsgIterate(this.children, (function(i, child) {
                        if (cgsgExist(child)) {
                            child.computeAbsoluteMatrix(recursive);
                        }
                    }).bind(this));
                    //}
                }
            },

            /**
             * Returns the x position with the lowest value between this node and its children.
             *
             * @method getMinAbsoluteLeft
             * @return {Number}
             */
            getMinAbsoluteLeft : function() {
                var retval = this._absPos.x;

                if (this.children.length > 0) {
                    cgsgIterate(this.children.length, (function(i, child) {
                        if (retval < child._absPos.x) {
                            retval = child._absPos.x;
                        }
                    }).bind(this));
                }

                return retval;
            },

            /**
             * Returns the right border's position with the highest value between this node and its children.
             *
             * @method getMostAbsoluteRight
             * @return {Number}
             */
            getMaxAbsoluteRight : function() {
                var retval = this._absPos.x + (this.getWidth() * this._absSca.x);

                if (this.children.length > 0) {
                    cgsgIterate(this.children.length, (function(i, child) {
                        var absRight = this._absPos.x + (this.getWidth() * this._absSca.x);
                        if (retval < absRight) {
                            retval = absRight;
                        }
                    }).bind(this));
                }

                return retval;
            },

            /**
             * Returns the y position with the lowest value between this node and its children.
             *
             * @method getMinAbsoluteTop
             * @return {Number}
             */
            getMinAbsoluteTop : function() {
                var retval = this._absPos.y;

                if (this.children.length > 0) {
                    cgsgIterate(this.children.length, (function(i, child) {
                        if (retval < child._absPos.y) {
                            retval = child._absPos.y;
                        }
                    }).bind(this));
                }

                return retval;
            },

            /**
             * Returns the bottom border's position with the highest value between this node and its children.
             *
             * @method getAbsBottom
             * @return {Number}
             */
            getMaxAbsoluteBottom : function() {
                var retval = this._absPos.y + (this.getHeight() * this._absSca.y);

                if (this.children.length > 0) {
                    cgsgIterate(this.children.length, (function(i, child) {
                        var absRight = this._absPos.y + (this.getHeight() * this._absSca.y);
                        if (retval < absRight) {
                            retval = absRight;
                        }
                    }).bind(this));
                }

                return retval;
            },
            /**
             *
             * @method getAbsLeft
             * @return {Number}
             */
            getAbsLeft           : function() {
                return this._absPos.x;
            },

            /**
             * @method getAbsRight
             * @return {Number}
             */
            getAbsRight : function() {
                return this._absPos.x + this.getAbsWidth();
            },

            /**
             * @method getAbsTop
             * @return {Number}
             */
            getAbsTop : function() {
                return this._absPos.y;
            },

            /**
             * @method getAbsBottom
             * @return {Number}
             */
            getAbsBottom : function() {
                return this._absPos.y + this.getAbsHeight();
            },

            /**
             * @method getAbsWidth
             * @return {Number}
             */
            getAbsWidth : function() {
                return this.getWidth() * this._absSca.x;
            },

            /**
             * @method getAbsHeight
             * @return {Number}
             */
            getAbsHeight : function() {
                return this.getHeight() * this._absSca.y;
            },

            /**
             * @method getWidth
             * @return {Number}
             */
            getWidth : function() {
                return this.dimension.width;
            },

            /**
             * @method getHeight
             * @return {Number}
             */
            getHeight : function() {
                return this.dimension.height;
            },

            /**
             * Return center of the node, based on its position and dimension
             * @method getCenter
             * @return {CGSGPosition}
             */
            getCenter : function() {
                var x = this.position.x + (this.getWidth() / 2);
                var y = this.position.y + (this.getHeight() / 2);

                return new CGSGPosition(x, y);
            },

            /**
             * Test if this node is colliding the node in parameter. Don't forget to add nodes to CGSGCollisionManager.
             *
             * @public
             * @method isColliding
             * @return {Boolean} true if the 2 nodes are colliding. They are colliding if the distance between them is minus than the threshold parameter
             * @param {CGSGNode} node a CGSGNode
             * @param {Number} threshold space between the 2 nodes before considering they are colliding
             */
            isColliding : function(node, threshold) {
                return CGSG.collisionManager.isColliding(this, node, threshold);
            },

            /**
             * @public
             * @method getListOfCollidingBrothers
             * @return {Array} a Array of nodes this one is colliding with (can be empty)
             * @param {Number} threshold space between the 2 nodes before considering they are colliding
             */
            getListOfCollidingBrothers : function(threshold) {
                var listOfCollidingNodes = [];
                var brother = null;
                //for (var n = 0; n < this._parentNode.children.length; n++) {
                cgsgIterate(this._parentNode.children, (function(i, brother) {
                    if (brother !== this && this.isColliding(brother, threshold)) {
                        listOfCollidingNodes.push(brother);
                    }
                }).bind(this));
                //}

                return listOfCollidingNodes;
            },

            /**
             * @public
             * @method isCollidingABrother
             * @param {Number} threshold space between the 2 nodes before considering they are colliding
             * @return {Boolean} true if this node is colliding one of the other children of its parent node
             */
            isCollidingABrother : function(threshold) {
                var retval = false;

                cgsgIterate(this._parentNode.children, (function(i, brother) {
                    if (brother !== this && this.isColliding(brother, threshold)) {
                        retval = true;

                        return retval; // break the loop
                    }
                    return false;
                }).bind(this));

                return retval;
            },

            /*
             * TODO : to be completed
             * Return the list of lines going joigning nodes' peaks
             * param onlyBrothers a Boolean. Default = true
             * param threshold distance from which the detectection is done
             * return an array of CGSGVector2D (can be empty)
             */
            /*getMagneticLines : function(onlyBrothers, threshold) {
             if (!cgsgExist(onlyBrothers)) {
             onlyBrothers = true;
             }

             //compute vectors
             var topVector = this.getAbsTop();
             var bottomVector = this.getAbsBottom();
             var leftVector = this.getAbsLeft();
             var rightVector = this.getAbsRight();

             //line = a point and a normalized CGSGVector2D (ie : [0, 1] or [1, 0])
             var listOfLines = [];

             var brother = null;
             for (var n = 0; n < this._parentNode.children.length; n++) {
             brother = this._parentNode.children[n];

             //vectors v & v' are colinear if and only if xy’ - yx’ = 0.

             }

             return listOfLines;
             },*/

            /**
             * Must be overrided by inherited classes
             * @method copy
             * @param {CGSGNode} node
             * @return {CGSGNode} a copy of this node
             */
            copy : function(node) {
                if (node === null || node === undefined) {
                    node = new CGSGNode(this.position.x, this.position.y);
                    node.resizeTo(this.dimension.width, this.dimension.height);
                }
                node.classType = this.classType;
                node.name = this.name;
                node.globalAlpha = this.globalAlpha;
                node.bkgcolors = this.bkgcolors;
                node.lineColor = this.lineColor;
                node.lineWidth = this.lineWidth;
                node.isVisible = this.isVisible;
                node.isProportionalResize = this.isProportionalResize;
                node.pickNodeMethod = this.pickNodeMethod;
                node.needToKeepAbsoluteMatrix = this.needToKeepAbsoluteMatrix;

                //list of the children (empty if this nodes is a leaf)
                //this.children = [];

                if (this.regionConstraint !== null) {
                    node.regionConstraint = this.regionConstraint.copy();
                }

                //can be fulfilled by the developer to put in whatever he needs
                node.userData = this.userData;

                //selection attributes
                //if true, this nodes is clickable and so will be checked by the pickNode function
                node.isClickable = this.isClickable;
                //if true, this nodes can be selected and so can be transformed (dimension)
                node.isResizable = this.isResizable;
                node.isDraggable = this.isDraggable;
                node.isTraversable = this.isTraversable;

                node.selectionLineColor = this.selectionLineColor;
                node.selectionLineWidth = this.selectionLineWidth;
                node.handleSize = this.handleSize;
                node.handleColor = this.handleColor;
                node._id = this._id;
                node.translateTo(this.position.x, this.position.y);
                node.resizeTo(this.dimension.width, this.dimension.height);
                node.scaleTo(this.scale.x, this.scale.y);
                node.rotateTo(this.rotation.angle);

                node.selectableZone =
                new CGSGRegion(node.position.x, node.position.y, node.dimension.width, node.dimension.height);

                //all the events for the node
                node.onMouseOver = this.onMouseOver;
                node.onMouseOut = this.onMouseOut;
                node.onClick = this.onClick;
                node.onDblClick = this.onDblClick;
                node.onDrag = this.onDrag;
                node.onDragEnd = this.onDragEnd;
                node.onResize = this.onResize;
                node.onResizeEnd = this.onResizeEnd;
                node.onSelect = this.onSelect;
                node.onDeselect = this.onDeselect;

                node.computeAbsoluteMatrix(true);

                return node;
            },

            /**
             * free memory taken by this object and it's children.
             * The 'userData' property won't be freed
             * @method free
             */
            free : function() {
                for (var c = this.children.length - 1 ; c >= 0 ; c--) {
                    this.children[c].free();
                }

                this.children.clear();

                cgsgFree(this);
            }
        }
    )
    ;