/**Diagramo namespace. We must place as much "global application" variables in here to avoid
* conflict and to achieve better management*/
var DIAGRAMO = {
    /**Set it on true if you want visual debug clues.
    * Note: See to set the Connector's visualDebug (Connector.visualDebug) to false too
    **/
    debug: false,

    /**Keeps temporary connector solution.
    * TODO: move to CONNECTOR_MANAGER?!*/
    debugSolutions: [],

    /** enables/disables rendering of currentCloud
    * TODO: in further can be used for option like "Show Cloud" or "Highlight about to connect points" */
    visualMagnet: true,

    /** enables/disables rendering fill color as gradient*/
    gradientFill: true,

    /**On canvas fit action this will be the distance between canvas work area and it's border*/
    CANVAS_FIT_PADDING: 10
};


/**Switch on/off debug
* @param {Boolean} value optional value
* */
DIAGRAMO.switchDebug = function (value) {
    if (value == undefined) {
        DIAGRAMO.debug = !DIAGRAMO.debug;
    }
    else {
        DIAGRAMO.debug = value;
    }

    var iconDebug = document.getElementById('iconDebug');

    if (DIAGRAMO.debug) {
        iconDebug.src = './assets/images/icon_debug_true.gif';
    }
    else {
        iconDebug.src = './assets/images/icon_debug_false.gif';
    }

    draw();
};


/*Activate the bellow block of code if anything else fails
*Helped me a lot on a remote iPad (https://bitbucket.org/scriptoid/diagramo/issue/118)
*with no available OSX around.
*TODO: Could be added as normal lister into a future verbosity option
**/
if (false) {
    /**@see http://stackoverflow.com/questions/10197895/ipad-javascript-error-not-helpful*/
    window.onerror = function (desc, page, line, chr) {
        alert('Description: ' + desc
                + ' Page: ' + page
                + ' Line: ' + line
                + ' Position: ' + chr
        );
    };
}


/**Describe the file version of the file
* This will make easier to make upgrades in the future.
* Any time change in file format will be appear this number
* must be increased and also update the importer.js
* library
* */
DIAGRAMO.fileVersion = 4;


/**Activate or deactivate the undo feature
*@deprecated
***/
var doUndo = true;

/**Usually an instance of a Command (see /lib/commands/*.js)*/
var currentMoveUndo = null;

var CONNECTOR_MANAGER = new ConnectorManager();
var CONTAINER_MANAGER = new ContainerFigureManager();

/**An currentCloud - {Array} of 2 {ConnectionPoint} ids.
* Cloud highlights 2 {ConnectionPoint}s whose are able to connect. */
var currentCloud = [];

/**The width of grid cell. 
*Must be an odd number.
*Must coincide with the size of the image used as canvas tile 
**/
var GRIDWIDTH = 30;

/**The distance (from a snap line) that will trigger a snap*/
var SNAP_DISTANCE = 5;

/**The half of light distance between upper and lower border for gradient filling*/
var gradientLightStep = 0.06;

var fillColor = null;
var strokeColor = '#000000';
var currentText = null;

/** Instance of Browser Class for defining browser */
var Browser = new Browser();

/**Default top&bottom padding of Text editor's textarea*/
var defaultEditorPadding = 6;

/**Default border width of Text editor's textarea*/
var defaultEditorBorderWidth = 1;

/**
*Scrollbar width
*19px is the width added to a scrollable area (Zack discovered this into Chrome)
*We might compute this dimension too but for now it's fine
*even if we are wrong by a pixel or two
**/
var scrollBarWidth = 19;

var FIGURE_ESCAPE_DISTANCE = 30; /**the distance by which the connectors will escape Figure's bounds*/

/**the distance by which the connectors will be able to connect with Figure*/
var FIGURE_CLOUD_DISTANCE = 4;

/*It will store a reference to the function that will create a figure( ex: figureForKids:buildFigure3()) will be stored into this
*variable so upon click on canvas this function will create the object*/
var createFigureFunction = null;
var createFigureName = null;
/**A variable that tells us if CTRL is pressed*/
var CNTRL_PRESSED = false;

/**A variable that tells us if SHIFT is pressed*/
var SHIFT_PRESSED = false;

/**Current connector. It is null if no connector selected
* @deprecated
* TODO: we should base ONLY on selectedConnectorId
**/
var connector = null;


/**Connector type
* TODO: this should not be present here but retrieved from Connector object
**/
var connectorType = '';


/**It contains all the figure sets. Each figure set upon loading it will add a new
* entry to this array*/
var figureSets = [];

/**Used to generate nice formatted SVG files */
var INDENTATION = 0;

/**Export the Canvas as SVG. It will descend to whole graph of objects and ask
*each one to convert to SVG (and use the proper indentation)
*Note: Placed out of editor.php so we can safelly add '<?...' string
*@author Alex
*@deprecated
**/
function toSVG() {
    return '';

    /* Note: Support for SVG is suspended 
    * 
    var canvas = getCanvas();
    //@see http://www.w3schools.com/svg/svg_example.asp
    var v2 = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
    v2 += "\n" + '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="' 
    + canvas.width +'" height="' + canvas.height 
    + '" viewBox="0 0 ' + canvas.width + ' ' + canvas.height + '" version="1.1">';
    INDENTATION++;
    v2 += STACK.toSVG();
    v2 += CONNECTOR_MANAGER.toSVG();
    INDENTATION--;
    v2 += "\n" + '</svg>';
    
    return v2;
    */
}


/**
*Supposelly stop any selection from happening
*/
function stopselection(ev) {

    /*If we are selecting text within anything with the className text, we allow it
    * This gives us the option of using textareas, inputs and any other item we
    * want to allow text selection in.
    **/
    if (ev.target.className == "text") {
        return true;
    }
    return false;
}

var STACK = new Stack();


/**keeps track if the MLB is pressed*/
var mousePressed = false;

/**the default application state*/
var STATE_NONE = 'none';

/**we have figure to be created**/
var STATE_FIGURE_CREATE = 'figure_create';

/**we selected a figure (for further editing for example)*/
var STATE_FIGURE_SELECTED = 'figure_selected';

/**we are selecting the start of a connector*/
var STATE_CONNECTOR_PICK_FIRST = 'connector_pick_first';

/**we are selecting the end of a connector*/
var STATE_CONNECTOR_PICK_SECOND = 'connector_pick_second';

/**we selected a connector (for further editing for example)*/
var STATE_CONNECTOR_SELECTED = 'connector_selected';

/**move a connection point of a connector*/
var STATE_CONNECTOR_MOVE_POINT = 'connector_move_point';

/**we are dragging the mouse over a group of figures.*/
var STATE_SELECTING_MULTIPLE = 'selecting_multiple';

/**we have a group selected (either temporary or permanent)*/
var STATE_GROUP_SELECTED = 'group_selected';

/**we have a container selected*/
var STATE_CONTAINER_SELECTED = 'container_selected';

/**we have a text editing*/
var STATE_TEXT_EDITING = 'text_editing';

/**connector continue create*/
var STATE_CONNECTOR_CREATE = 'connector_create';
/** 当前选择的是创建线状态 */
var STATE_FORM_CREATE_LINE = false;
/** formId **/
var CCForm_FK_MapData = null;
/**节点编号**/
var CCForm_NodeID = null;

/**Begin - Form Controls***************/
var CCForm_Controls = {
    Line: 'Line',
    Label: 'Label',
    Button: 'Button',
    HyperLink: 'HyperLink',
    Image: 'Image',
    Fieldset: 'Fieldset',
    TextBox: 'TextBox',
    TextBoxInt: 'TextBoxInt',
    TextBoxMoney: 'TextBoxMoney',
    TextBoxFloat: 'TextBoxFloat',
    Date: 'Date',
    DateTime: 'DateTime',
    RadioButton: 'RadioButton',
    RadioButtList: 'RadioButtonList',
    CheckBox: 'CheckBox',
    DropDownListEnum: 'DropDownListEnum',
    DropDownListTable: 'DropDownListTable',
    CheckBoxList: 'CheckBoxList',
    Dtl: 'Dtl',
    AthMulti: 'AthMulti',
    AthSingle: 'AthSingle',
    AthImg: 'AthImg',
    Chart: 'Chart',
    FrmCheck: 'FrmCheck',
    SubFlowDtl: 'SubFlowDtl',
    ThreadDtl: 'ThreadDtl',
    FrmTransferCustom: 'FrmTransferCustom'
};
/**End - FormControl***********/

/**Keeps current state*/
var state = STATE_NONE;

/**The (current) selection area*/
var selectionArea = new Polygon();
selectionArea.points.push(new Point(0, 0));
selectionArea.points.push(new Point(0, 0));
selectionArea.points.push(new Point(0, 0));
selectionArea.points.push(new Point(0, 0));
selectionArea.style.strokeStyle = 'grey';
selectionArea.style.gradientBounds = [];
selectionArea.style.lineWidth = '1';

/**Toggle grid visible or not*/
var gridVisible = false;

/**Makes figure snap to grid*/
var snapTo = false;

/*show propertyWin show or hide*/
var propertyWinVisible = false;

/**Keeps last coodinates while dragging*/
var lastClick = [];

/**Default line width*/
var defaultLineWidth = 2;

/**Default handle line width*/
var defaultThinLineWidth = 1;

/**Current instance of TextEditorPopup*/
var currentTextEditor = null;

/**Current selected figure id ( -1 if none selected)*/
var selectedFigureId = -1;

/**Currently selected figure thumbnail (for D&D)*/
var selectedFigureThumb = null

/**Current selected group (-1 if none selected)*/
var selectedGroupId = -1;

/**Current selecte connector (-1 if none selected)*/
var selectedConnectorId = -1;

/**Current selectet container (-1 if none selected)*/
var selectedContainerId = -1;

/**Currently selected ConnectionPoint (if -1 none is selected)*/
var selectedConnectionPointId = -1;

/**Set on true while we drag*/
var dragging = false;

/**Currently inserted image filename*/
var insertedImageFileName = null;

/**Holds a wrapper around canvas object*/
var canvasProps = null;

/**Currently holds two elements the type: figure|group and the id*/
var clipboardBuffer = [];

/**Return current canvas.
* Current canvas will ALWAYS have the 'a' as DOM id
* @return {HTMLCanvasElement} the DOM element of <canvas> tag
* @author Alex Gheorghiu <alex@scriptoid.com>
**/
function getCanvas() {
    var canvas = document.getElementById("a");
    if (canvas.getAttribute("Height") != "8000") {
        canvas.setAttribute('Height', '8000');
    }
    return canvas;
}


/**Return work area container.
* Work area container will ALWAYS have the 'container' as DOM id
* @return {HTMLElement} the DOM element of work area container
* @author Artyom Pokatilov <artyom.pokatilov@gmail.com>
**/
function getWorkAreaContainer() {
    /**Id of work area HTML element, which includes canvas*/
    var workAreaContainer = document.getElementById("container");
    return workAreaContainer;
}


/**Return the 2D context of current canvas
* @author Alex Gheorghiu <alex@scriptoid.com>
**/
function getContext() {
    var canvas = getCanvas();
    if (canvas.getContext) {
        return canvas.getContext("2d");
    }
    else {
        alert('You need a HTML5 web browser. Any Safari,Firefox, Chrome or Explorer supports this.');
    }
}

/**Keeps current figure set id*/
var currentSetId = 'basic';

/**
* Reveals the figure set named by 'name' and hide previously displayed set
* @param {String} id - the (div) id value of the set
* @author Alex
**/
function setFigureSet(id) {
    Log.info("main.js id = " + id);

    //alert(name);
    var div = document.getElementById(id);
    if (div != null) {
        if (currentSetId != null) {
            Log.info("main.js currentSetId = " + currentSetId);
            var currentFigureSet = document.getElementById(currentSetId);
            currentFigureSet.style.display = 'none';
        }
        div.style.display = 'block';
        currentSetId = id;
    }
}


/**Update an object (Figure or Connector)
*@param {Number} shapeId - the id of the updating object
*@param {String} property - (or an {Array} of {String}s). The 'id' under which the property is stored
*TODO: is there any case where we are using property as an array ?
*@param {String} newValue - the new value of the property
*@param {Boolean} [skipCommand = false] - if true than current {Command} won't be added to the {History}
*@param {String} [previousValue] - the previous value of the property
*@author Zack, Alex, Artyom
**/
function updateShape(shapeId, property, newValue, skipCommand, previousValue) {

    // set default values of optional params
    skipCommand = skipCommand || false;

    var obj = STACK.figureGetById(shapeId); //try to find it inside {Figure}s


    //TODO: this horror must dissapear
    if (!obj) {
        obj = CONNECTOR_MANAGER.connectorGetById(shapeId);
    }


    //container?
    if (!obj) {
        obj = STACK.containerGetById(shapeId);
    }
    var objSave = obj; //keep a reference to initial shape

    /*Example of property 'primitives.1.str' */
    var props = property.split(".");


    /*Going down the object's hierarchy down to the property's parent
    *Example:
    *  for props = ['primitives','1','str'] 
    *  figure
    *      |_primitives
    *          |_1 (it's a Text)
    *             |_str
    */
    //Log.info("Object before descend: " +  obj.oType);
    var figure = obj; //TODO: Why this variable when we already have objSave?
    for (var i = 0; i < props.length - 1; i++) {
        obj = obj[props[i]];
    }

    //the property name
    var propName = props[props.length - 1];

    /*Now we are located at Figure level or somewhere in a primitive or another object.
    *Here we will search for setXXX (where XXX is the property name) method first and if we find one we will use it
    *if not we will simply update the property directly.
    **/

    /**Compute setXXX and getXXX*/
    var propSet = "set" + Util.capitaliseFirstLetter(propName);
    var propGet = "get" + Util.capitaliseFirstLetter(propName);

    if (propSet in obj) { //@see https://developer.mozilla.org/en/JavaScript/Reference/Operators/Special_Operators/in_Operator
        /*If something is complicated enough to need a function,
        *  likelyhood is it will need access to its parent figure.
        *So we will let the parent to do the update as it likes, if it has
        * a method of form set<property_name> in place
        */

        if ((typeof (previousValue) !== 'undefined' && previousValue != obj[propGet])
            || (typeof (previousValue) === 'undefined' && newValue != obj[propGet]())) { //update ONLY if new value differ from the old one

            var undo = new ShapeChangePropertyCommand(shapeId, property, newValue, previousValue);
            undo.execute();

            if (!skipCommand) {
                History.addUndo(undo);
            }
        }
        //TODO: Do we really need this anymore?
        obj[propSet](newValue);
    }
    else {
        if ((typeof (previousValue) !== 'undefined' && obj[propName] != previousValue)
            || (typeof (previousValue) === 'undefined' && obj[propName] != newValue)) { //try to change it ONLY if new value is different than the last one
            var undo = new ShapeChangePropertyCommand(shapeId, property, newValue, previousValue);
            undo.execute();

            if (!skipCommand) {
                History.addUndo(undo);
            }
            obj[propName] = newValue;
        }
    }

    //connector's text special case
    if (objSave instanceof Connector && propName == 'str') {
        //Log.info("updateShape(): it's a connector 2");
        objSave.updateMiddleText();
    }
    //Log.groupEnd();
    draw();
}


/**Setup the editor panel for a special shape. 
*@param shape - can be either Connector or Figure. If null is provided the
*editor panel will be disabled
**/
function setUpEditPanel(shape) {
    //var propertiesPanel = canvas.edit; //access the edit div
    var propertiesPanel = document.getElementById("edit");
    propertiesPanel.innerHTML = "";
    if (shape == null) {
        //do nothing
    }
    else {
        switch (shape.oType) {
            case 'Group':
                //do nothing. We do not want to offer this to groups
                break;
            case 'Container':
                Builder.constructPropertiesPanel(propertiesPanel, shape);
                break;
            case 'CanvasProps':
                Builder.constructCanvasPropertiesPanel(propertiesPanel, shape);
                break;
            default: //both Figure and Connector
                Builder.constructPropertiesPanel(propertiesPanel, shape);
        }
    }
}

/**Setup edit mode for Text primitive.
*@param shape - parent of Text primitive. Can be either {Connector} or {Figure}.
*@param {Number} textPrimitiveId - the id value of Text primitive (from {Stack}
*@author Artyom
**/
function setUpTextEditorPopup(shape, textPrimitiveId) {
    // get elements of Text Editor and it's tools
    var textEditor = document.getElementById('text-editor'); //a <div> inside editor page
    var textEditorTools = document.getElementById('text-editor-tools'); //a <div> inside editor page

    // set current Text editor to use it further in code
    currentTextEditor = Builder.constructTextPropertiesPanel(textEditor, textEditorTools, shape, textPrimitiveId);
}


/**Setup the creation function (that -later, upon calling - will create the actual {Figure}
* Note: It will also set the current state to STATE_FIGURE_CREATE
* @param  {Function} fFunction - the function used to create the figure
* @param  {String} thumbURL - the URL to the thumb of the image
**/
function createFigure(fFunction, thumbURL, fName) {
    //Log.info('createFigure (' + fFunction + ',' + thumbURL + ')');

    createFigureFunction = fFunction;
    createFigureName = fName;
    selectedFigureThumb = thumbURL;


    selectedFigureId = -1;
    selectedConnectorId = -1;
    selectedConnectionPointId = -1;

    if (state == STATE_TEXT_EDITING) {
        currentTextEditor.destroy();
        currentTextEditor = null;
    }

    state = STATE_FIGURE_CREATE;
    draw();

}

/*
* Resets any state to STATE_NONE
*/
function resetToNoneState() {
    // clear text editing mode
    if (state == STATE_TEXT_EDITING) {
        currentTextEditor.destroy();
        currentTextEditor = null;
    }

    // deselect everything
    selectedFigureId = -1;
    selectedConnectionPointId = -1;
    selectedConnectorId = -1;
    selectedContainerId = -1;

    // if group selected
    if (state == STATE_GROUP_SELECTED) {
        var selectedGroup = STACK.groupGetById(selectedGroupId);

        // if group is temporary then destroy it
        if (!selectedGroup.permanent) {
            STACK.groupDestroy(selectedGroupId);
        }

        //deselect current group
        selectedGroupId = -1;
    }

    state = STATE_NONE;
}

// Id of the DOM object for errors of image upload
var uploadImageErrorDivId = 'upload-image-error';

/** Show "Insert image" dialog
* Insert image dialog can be triggered in 1 case:
*  1 - from quick toolbar

*  Description:
*  Call popup to get image from url or upload target image from local
*  @author Artyom Pokatilov <artyom.pokatilov@gmail.com>
**/
function showInsertImageDialog() {
    resetToNoneState();

    draw();

    //setUploadedImagesList();

    var dialogContent = document.getElementById('insert-image-dialog');
    $.modal(dialogContent, { minWidth: '380px', containerId: 'upload-image-dialog', overlayClose: true });
    // update dialog's position
    $.modal.setPosition();
}

/** Show filenames of uploaded images in "Insert image" dialog
* Get filenames from server and insert them into dialog
*  @author Artyom Pokatilov <artyom.pokatilov@gmail.com>
**/
function setUploadedImagesList() {
    //see: http://api.jquery.com/jQuery.get/
    $.post("./common/controller.php",
        { action: 'getUploadedImageFileNames' },
        function (data) {
            var list = document.getElementById('insert-image-reuse');
            var reuseGroup = document.getElementById('insert-image-reuse-group');

            data = JSON.parse(data);

            if (data != null && data.length) {
                var i,
                    length = data.length,
                    option;

                // add options with filenames to select
                for (i = 0; i < length; i++) {
                    option = document.createElement('option');
                    option.value = data[i];
                    option.textContent = data[i];
                    list.appendChild(option);
                }

                // enable reuse select and group button
                list.disabled = false;
                reuseGroup.disabled = false;
            } else {
                // disable reuse select and group button
                list.disabled = true;
                reuseGroup.disabled = true;
            }
        }
    );
}

/** Insert image into current diagram
* Insert image can be triggered in 1 case:
*  1 - from insert image dialog
*
* @param {String} imageFileName - filename of uploaded image
* @param {String} errorMessage - error message
*
*  Description:
*  1) Call popup to get image from url or upload target image from local
*  2) Save image to backend
*  3) Close popup
*  4) If there were errors - go to 5 else - go to 6
*  5) Alert error message
*  6) Create figure with target image and text caption
*
*  @author Artyom Pokatilov <artyom.pokatilov@gmail.com>
**/
function insertImage(imageFileName, errorMessage) {
    if (errorMessage) {
        // set upload errors to dialog
        var errorDiv = document.getElementById(uploadImageErrorDivId);
        errorDiv.innerHTML = errorMessage;

        // update dialog's position
        $.modal.setPosition();
    } else {
        // close current insert image dialog
        $.modal.close();

        insertedImageFileName = imageFileName;
        action('insertImage');
    }
}

/**Activate snapToGrip  option*/
function snapToGrid() {
    Log.info("snapToGrid called;");
    snapTo = !snapTo;

    if (snapTo) {
        if (!gridVisible) {
            gridVisible = true;
            backgroundImage = null; // reset cached background image of canvas
            document.getElementById("gridCheckbox").checked = true;

            //trigger a repaint;
            draw();
        }
    }
}

/**是否显示属性窗口*/
function propertyForm() {
    Log.info("propertyForm show or hidden;");
    propertyWinVisible = !propertyWinVisible;

    if (propertyWinVisible) {
        $('#right').show();
        document.getElementById("propertyCheckbox").checked = true;
        $('#container').css('right', '247px');
    } else {
        $('#right').hide();
        document.getElementById("propertyCheckbox").checked = false;
        $('#container').css('right', '0px');
    }
}
/**Makes grid visible or invisible, depedinding of previous value
*If the "snap to" was active and grid made invisible the "snap to"
*will be disabled
**/
function ShowGrid() {

    /**If grid was visible and snap to was check we need to take measures*/
    if (gridVisible) {
        if (snapTo) {
            snapTo = false;
            document.getElementById("snapCheckbox").checked = false;
        }
    }

    gridVisible = !gridVisible;
    backgroundImage = null; // reset cached background image of canvas

    //trigger a repaint;
    draw();
}


/**Click is disabled because we need to handle mouse down and mouse up....etc etc etc*/
function onClick(ev) {
    var coords = getCanvasXY(ev);
    var x = coords[0];
    var y = coords[1];

    //here is the problem....how do we know we clicked on canvas
    /*var fig=STACK.figures[STACK.figureGetMouseOver(x,y,null)];
    if(CNTRL_PRESSED && fig!=null){
    TEMPORARY_GROUP.addPrimitive();
    STACK.figureRemove(fig);
    STACK.figureAdd(TEMPORARY_GROUP);
    }
    else if(STACK.figureGetMouseOver(x,y,null)!=null){
    TEMPORARY_GROUP.primitives=[];
    TEMPORARY_GROUP.addPrimitive(fig);
    STACK.figureRemove(fig);
    }*/
    //draw();
}


/**
* @deprecated Does not seem to be used
* */
function onDoubleClick(ev) {
    var coords = getCanvasXY(ev);
    var HTMLCanvas = getCanvas();
    var x = coords[0];
    var y = coords[1];
    lastClick = [x, y];
    //    Log.info("onMouseDown at (" + x + "," + y + ")");
    //alert('lastClick: ' + lastClick + ' state: ' + state);

    //mousePressed = true;
    alert("Double click triggered");
}


/**Receives the ASCII character code but not the keyboard code
*@param {Event} ev - the event generated when kay is pressed
*@see <a href="http://www.quirksmode.org/js/keys.html">http://www.quirksmode.org/js/keys.html</a>
**/
function onKeyPress(ev) {


    //ignore texts
    if (ev.target.className == "text") {
        return true;
    }


    draw();
    return false;
}


/**
*Receives the key code of keyboard but not the ASCII
*Treats the key pressed event
*@param {Event} ev - the event generated when key is down
*@see <a href="http://www.quirksmode.org/jskey/keys.html">http://www.quirksmode.org/js/keys.html</a>
**/
function onKeyDown(ev) {
    //1 - avoid text elements (if you are on a text area and you press the arrow you do not want the figures to move on canvas)
    if (ev.target.className == "text") {
        return true;
    }

    //2 - "enhance" event TODO: I'm not sure this is really necessary
    ev.KEY = ev.keyCode;

    switch (ev.KEY) {
        case KEY.ESCAPE: //Esc
            //alert('So do you want to escape me');
            //cancel any figure creation
            createFigureFunction = null;

            if (selectedFigureId != -1 || selectedConnectionPointId != -1 || selectedConnectorId != -1) {
                redraw = true;
            }

            //deselect any figure
            selectedFigureId = -1;
            selectedConnectionPointId = -1;
            selectedConnectorId = -1;

            // clear current text editor
            if (state == STATE_TEXT_EDITING) {
                currentTextEditor.destroy();
                currentTextEditor = null;
            }

            state = STATE_NONE;
            break;

        case KEY.DELETE: //Delete
            //delete any Figure or Group
            //            alert('Delete pressed' + this);
            switch (state) {

                case STATE_FIGURE_SELECTED: //delete a figure ONLY when the figure is selected
                    if (selectedFigureId != -1) {
                        var cmdDelFig = new FigureDeleteCommand(selectedFigureId);
                        cmdDelFig.execute();
                        History.addUndo(cmdDelFig);
                    }
                    break;

                case STATE_GROUP_SELECTED:
                    if (selectedGroupId != -1) {
                        var cmdDelGrp = new GroupDeleteCommand(selectedGroupId);
                        cmdDelGrp.execute();
                        History.addUndo(cmdDelGrp);
                    }

                    break;

                case STATE_CONNECTOR_SELECTED:
                    Log.group("Delete connector");
                    if (selectedConnectorId != -1) {
                        var cmdDelCon = new ConnectorDeleteCommand(selectedConnectorId);
                        cmdDelCon.execute();
                        History.addUndo(cmdDelCon);
                    }
                    Log.groupEnd();
                    break;

                case STATE_CONTAINER_SELECTED:
                    Log.group("Delete container");
                    if (selectedContainerId != -1) {
                        var cmdDelContainer = new ContainerDeleteCommand(selectedContainerId);
                        cmdDelContainer.execute();
                        History.addUndo(cmdDelContainer);
                    }
                    Log.groupEnd();
                    break;
            }
            break;

        case KEY.SHIFT: //Shift
            SHIFT_PRESSED = true;
            break;

        case KEY.CTRL: //Ctrl
        case KEY.COMMAND_LEFT:
        case KEY.COMMAND_FIREFOX:
            CNTRL_PRESSED = true;
            break;

        case KEY.LEFT: //Arrow Left
        case KEY.A:
            action("left");
            return false;
            break;

        case KEY.UP: //Arrow Up
        case KEY.W:
            //            alert('up');
            action("up");
            return false;
            break;

        case KEY.RIGHT: //Arrow Right
        case KEY.D:
            action("right");
            return false;
            break;

        case KEY.DOWN: //Arrow Down
        case KEY.S:
            if (CNTRL_PRESSED) {
                //Log.info("CTRL-S pressed  ");
                save(true);
                ev.preventDefault();
            } else {
                action("down");
            }
            break;

        case KEY.Z:
            if (CNTRL_PRESSED) {
                //action('undo');
            }
            break;

        //        case KEY.Y:     
        //            if(CNTRL_PRESSED){     
        //                action('redo');     
        //            }     
        //            break;     

        case KEY.G:
            if (CNTRL_PRESSED) {
                //action('group');
            }
            break;

        case KEY.U:
            if (CNTRL_PRESSED) {
                //action('ungroup');
            }
            break;

        case KEY.D:
            if (CNTRL_PRESSED) {
                if (ev.preventDefault) {
                    ev.preventDefault();
                }
                else {
                    ev.returnValue = false;
                }
                //action('duplicate');
            }
            break;

        case KEY.C:
            if (CNTRL_PRESSED) {
                if (selectedFigureId != -1) {
                    clipboardBuffer[0] = "figure";
                    clipboardBuffer[1] = selectedFigureId;
                } else if (selectedGroupId != -1) {
                    clipboardBuffer[0] = "group";
                    clipboardBuffer[1] = selectedGroupId;
                } else if (selectedConnectorId != -1) {
                    clipboardBuffer[0] = "connector";
                    clipboardBuffer[1] = selectedConnectorId;
                } else {
                    clipboardBuffer[0] = "";
                    clipboardBuffer[1] = -1;
                }
            }
            break;

        case KEY.V:
            if (CNTRL_PRESSED) {
                /*Description:
                * If figure or group copied here is what can happen:
                * - if no selection -> STATE_NONE:
                *      - if figure copied then duplicate it
                *      - if group copied then check if it`s not permanent, becase then we have lost it, and if not, then duplicate it
                * - if figure selected (ONLY if figure copied):
                *      - if it is same figure as copied, then duplicate it
                *        also because the duplicate will become selected, add it to the clipboard thus allowing another paste
                *      - if it is another figure, then apply style to it
                * - if group selected:
                *      //TODO: for Janis see comments/description on GroupCloneCommands
                *      - if it is same group as copied, then duplicate it, in this case we can also duplicate permanent group
                *        also because the duplicate will become selected, add it to the clipboard thus allowing another paste
                *      - if it is another group, and if we have COPIED THE FIGURE, then apply its style to all group
                * 
                */
                if (clipboardBuffer[0]) {// if something was copied
                    switch (state) {
                        case STATE_NONE:
                            if (clipboardBuffer[0] == "figure") {
                                selectedFigureId = clipboardBuffer[1];
                                action('duplicate');
                            } else if (clipboardBuffer[0] == "group") {
                                selectedGroupId = clipboardBuffer[1];
                                if (STACK.groupGetById(selectedGroupId)) { //if this is true, then the group isn`t permanent
                                    action('duplicate');
                                }
                            }
                            break;
                        case STATE_FIGURE_SELECTED:
                            if (clipboardBuffer[0] == "figure") {
                                if (clipboardBuffer[1] == selectedFigureId) {
                                    action('duplicate');
                                    //this means we add the copied to the clipboard, thus allowing another paste (it is ok, since the style is same)
                                    clipboardBuffer[1] = selectedFigureId;
                                } else { //apply style
                                    //TODO: I do think style should be applied by users directly.
                                    var copiedFigure = STACK.figureGetById(clipboardBuffer[1]);
                                    var selectedFigure = STACK.figureGetById(selectedFigureId);
                                    selectedFigure.applyAnotherFigureStyle(copiedFigure);
                                }
                            }
                            break;
                        case STATE_GROUP_SELECTED:
                            if (clipboardBuffer[1] == selectedGroupId) {
                                action('duplicate');
                                //this means we add the copied to the clipboard, thus allowing another paste (it is ok, since the style is same)
                                clipboardBuffer[1] = selectedGroupId;
                            } else {
                                //TODO: I do think style should be applied by users directly, even less to spread a figure's style to a whole group
                                if (clipboardBuffer[0] == "figure") { //if we have copied the figure, apply style to group
                                    var copiedFigure = STACK.figureGetById(clipboardBuffer[1]);
                                    var groupFigures = STACK.figureGetByGroupId(selectedGroupId);
                                    for (var i = 0; i < groupFigures.length; i++) {
                                        groupFigures[i].applyAnotherFigureStyle(copiedFigure);
                                    }
                                }
                            }
                            break;
                        case STATE_CONNECTOR_SELECTED:
                            if (clipboardBuffer[1] == selectedConnectorId) {
                                action('duplicate');
                                clipboardBuffer[1] = selectedConnectorId;
                            } else {
                                if (clipboardBuffer[0] == "connector") { //if we have copied the connector
                                    var copiedCon = CONNECTOR_MANAGER.connectorGetById(clipboardBuffer[1]);
                                    var selectedCon = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId);
                                    selectedCon.applyAnotherConnectorStyle(copiedCon);
                                }
                            }
                            break;
                    }
                }
            }
            break;

        case KEY.S:
            if (CNTRL_PRESSED) {
                //Log.info("CTRL-S pressed  ");
                save();
                ev.preventDefault();
            }
            break;

        case KEY.P:
            if (currentDiagramId !== null) {
                if (CNTRL_PRESSED) {
                    Log.info("CTRL-P pressed  ");
                    print_diagram();
                    ev.preventDefault();
                }
            }
            break;
    }
    draw();
    return false;
}


/**
*Treats the key up event
*@param {Event} ev - the event generated when key is up
**/
function onKeyUp(ev) {
    switch (ev.keyCode) {
        case KEY.SHIFT: //Shift
            SHIFT_PRESSED = false;
            break;

        case KEY.ALT: //Alt
            CNTRL_PRESSED = false;
            break;

        case KEY.CTRL: //Ctrl
            CNTRL_PRESSED = false;
            break;
    }
    return false;
}


/**
*Treats the mouse down event
*@param {Event} ev - the event generated when button is pressed
**/
function onMouseDown(ev) {
    var coords = getCanvasXY(ev);
    var HTMLCanvas = getCanvas();
    var x = coords[0];
    var y = coords[1];
    lastClick = [x, y];
    //    Log.info("onMouseDown at (" + x + "," + y + ")");
    //alert('lastClick: ' + lastClick + ' state: ' + state);

    mousePressed = true;
    //alert("onMouseDown() + state " + state + " none state is " + STATE_NONE);

    switch (state) {
        case STATE_TEXT_EDITING:

            /*Description:
            * If we have active text editor in popup and we click mouse.  Here is what can happen:
            * - if we clicked inside current text editor that nothing is happening
            * - if we clicked canvas:
            *      - we trigger onblur of text editor for IE and FF manually
            *      - we will remove text editor
            *      - we will switch to STATE_NONE
            *      - we will run STATE_NONE case next (without break;)
            */

            if (currentTextEditor.mouseClickedInside(ev)) {
                break;
            } else {
                // IE and Firefox doesn't trigger blur event when mouse clicked canvas
                // that is why we trigger this event manually
                if (Browser.msie || Browser.mozilla) {
                    currentTextEditor.blurTextArea();
                }

                currentTextEditor.destroy();
                currentTextEditor = null;

                state = STATE_NONE;
            }

            // TODO: Further needs to be the same behaviour as for the STATE_NONE case
            // to avoid repeating of the code next "break" statement commented to let execution flow down
            // break;


        case STATE_NONE:
            //alert("onMouseDown() - STATE_NONE");
            snapMonitor = [0, 0];


            /*Description:
            * We are in None state when no action was done....yet.  Here is what can happen:
            * - if we clicked a Connector than that Connector should be selected  
            *  (Connectors are more important than Figures :p)
            * - if we clicked a Group:
            *      - select the Group
            * - if we clicked a Figure:
            *      - select the Figure
            * - if we clicked a Container (Figures more important than Container)
            *      - select the Container
            * - if we did not clicked anything....
            *      - we will stay in STATE_NONE
            *      - allow to edit canvas
            *      
            */

            var selectedObject = Util.getObjectByXY(x, y); // get object under cursor
            switch (selectedObject.type) {
                case 'Connector':
                    selectedConnectorId = selectedObject.id;
                    state = STATE_CONNECTOR_SELECTED;
                    setUpEditPanel(CONNECTOR_MANAGER.connectorGetById(selectedConnectorId));
                    Log.info('onMouseDown() + STATE_NONE  - change to STATE_CONNECTOR_SELECTED');
                    redraw = true;
                    break;
                case 'Group':
                    selectedGroupId = selectedObject.id;
                    state = STATE_GROUP_SELECTED;
                    setUpEditPanel(null);
                    Log.info('onMouseDown() + STATE_NONE + group selected  =>  change to STATE_GROUP_SELECTED');
                    break;
                case 'Figure':
                    selectedFigureId = selectedObject.id;
                    state = STATE_FIGURE_SELECTED;
                    setUpEditPanel(STACK.figureGetById(selectedFigureId));
                    Log.info('onMouseDown() + STATE_NONE + lonely figure => change to STATE_FIGURE_SELECTED');
                    redraw = true;
                    break;
                case 'Container':
                    selectedContainerId = selectedObject.id;
                    state = STATE_CONTAINER_SELECTED;
                    setUpEditPanel(STACK.containerGetById(selectedContainerId));
                    Log.info('onMouseDown() + STATE_NONE  - change to STATE_CONTAINER_SELECTED');
                    redraw = true;
                    break;
                default:
                    if (STATE_FORM_CREATE_LINE == true) {
                        var canvas = getCanvas();
                        canvas.style.cursor = 'crosshair';
                        state = STATE_CONNECTOR_CREATE;
                        onMouseDown(ev);
                    }
                    break;
            }
            break;


        case STATE_FIGURE_CREATE:
            //            selectedConnectorId = -1;
            //            createFigureFunction = null;
            //
            //            mousePressed = false;
            //            redraw = true;
            throw "canvas> onMouseDown> STATE_FIGURE_CREATE> : this should not happen";
            break;

        case STATE_FIGURE_SELECTED:
            snapMonitor = [0, 0];

            //CONNECTOR
            if (HandleManager.handleGet(x, y) != null) { //Clicked a handler (of a Figure or Connector)
                Log.info("onMouseDown() + STATE_FIGURE_SELECTED - handle selected");
                /*Nothing important (??) should happen here. We just clicked the handler of the figure*/
                HandleManager.handleSelectXY(x, y);
            }
            else { //We did not clicked a handler
                var selectedObject = Util.getObjectByXY(x, y); // get object under cursor
                switch (selectedObject.type) {
                    case 'Connector':
                        state = STATE_CONNECTOR_SELECTED;
                        selectedConnectorId = selectedObject.id;
                        selectedFigureId = -1;
                        var con = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId);
                        HandleManager.shapeSet(con);
                        setUpEditPanel(con);
                        Log.info('onMouseDown() + STATE_FIGURE_SELECTED  - change to STATE_CONNECTOR_SELECTED');
                        redraw = true;
                        break;
                    case 'Group':
                        if (SHIFT_PRESSED) {
                            var figuresToAdd = [];
                            /* TODO: for what reason we have this condition in STATE_FIGURE_SELECTED?
                            * Seems like escaping of bigger problem */
                            if (selectedFigureId != -1) { //add already selected figure
                                figuresToAdd.push(selectedFigureId);
                            }

                            var groupFigures = STACK.figureGetByGroupId(selectedObject.id);
                            for (var i = 0; i < groupFigures.length; i++) {
                                figuresToAdd.push(groupFigures[i].id);
                            }

                            // create group for current figure joined with clicked group
                            selectedGroupId = STACK.groupCreate(figuresToAdd);
                            Log.info('onMouseDown() + STATE_FIGURE_SELECTED + SHIFT + another group => STATE_GROUP_SELECTED');
                        } else {
                            selectedGroupId = selectedObject.id;
                            Log.info('onMouseDown() + STATE_FIGURE_SELECTED + group figure => change to STATE_GROUP_SELECTED');
                        }

                        selectedFigureId = -1;
                        state = STATE_GROUP_SELECTED;
                        setUpEditPanel(null);
                        redraw = true;
                        break;
                    case 'Figure': //lonely figure
                        if (SHIFT_PRESSED) {
                            var figuresToAdd = [];
                            /* TODO: for what reason we have this condition in STATE_FIGURE_SELECTED?
                            * Seems like escaping of bigger problem */
                            if (selectedFigureId != -1) { //add already selected figure
                                figuresToAdd.push(selectedFigureId);
                            }

                            figuresToAdd.push(selectedObject.id); //add ONLY clicked selected figure

                            selectedFigureId = -1;
                            // we have two figures, create a group
                            selectedGroupId = STACK.groupCreate(figuresToAdd);
                            state = STATE_GROUP_SELECTED;
                            setUpEditPanel(null);
                            Log.info('onMouseDown() + STATE_FIGURE_SELECTED + SHIFT  + min. 2 figures => STATE_GROUP_SELECTED');
                        } else {
                            selectedFigureId = selectedObject.id;
                            HandleManager.clear();
                            setUpEditPanel(STACK.figureGetById(selectedFigureId));
                            Log.info('onMouseDown() + STATE_FIGURE_SELECTED + single figure => change to STATE_FIGURE_SELECTED (different figure)');
                        }

                        redraw = true;
                        break;
                    case 'Container':
                        selectedFigureId = -1;
                        selectedContainerId = selectedObject.id;
                        state = STATE_CONTAINER_SELECTED;
                        Log.info('onMouseDown() + STATE_FIGURE_SELECTED + single container  - change to STATE_CONTAINER_SELECTED');

                        setUpEditPanel(STACK.containerGetById(selectedContainerId));
                        redraw = true;
                        break;
                    default:    //mouse down on empty space
                        if (!SHIFT_PRESSED) { //if Shift isn`t pressed
                            selectedFigureId = -1;
                            state = STATE_NONE;
                            setUpEditPanel(canvasProps);
                            Log.info('onMouseDown() + STATE_FIGURE_SELECTED  - change to STATE_NONE');
                        }
                        redraw = true;
                        break;
                }
            }

            break;

        case STATE_GROUP_SELECTED:
            //GROUPS

            //if selected group is temporary and we pressed outside of it's border we will destroy it
            var selectedGroup = STACK.groupGetById(selectedGroupId);

            if (HandleManager.handleGet(x, y) != null) { //handle ?
                HandleManager.handleSelectXY(x, y);
                redraw = true;
                Log.info('onMouseDown() + STATE_GROUP_SELECTED  + handle selected => STATE_GROUP_SELECTED');
            }
            else {
                // get object under cursor
                var selectedObject = Util.getObjectByXY(x, y);
                switch (selectedObject.type) {
                    case 'Connector':
                        //destroy current group (if temporary)
                        if (!selectedGroup.permanent) {
                            STACK.groupDestroy(selectedGroupId);
                        }

                        //deselect current group
                        selectedGroupId = -1;

                        selectedConnectorId = selectedObject.id;
                        state = STATE_CONNECTOR_SELECTED;
                        redraw = true;
                        Log.info('onMouseDown() + STATE_GROUP_SELECTED  + handle selected => STATE_CONNECTOR_SELECTED');
                        break;
                    case 'Group':
                        if (selectedObject.id != selectedGroupId) {
                            if (SHIFT_PRESSED) {
                                var figuresToAdd = [];

                                //add figures from current group
                                var groupFigures = STACK.figureGetByGroupId(selectedGroupId);
                                for (var i = 0; i < groupFigures.length; i++) {
                                    figuresToAdd.push(groupFigures[i].id);
                                }

                                //add figures from clicked group
                                groupFigures = STACK.figureGetByGroupId(selectedObject.id);
                                for (var i = 0; i < groupFigures.length; i++) {
                                    figuresToAdd.push(groupFigures[i].id);
                                }

                                //destroy current group (if temporary)
                                if (!selectedGroup.permanent) {
                                    STACK.groupDestroy(selectedGroupId);
                                }

                                selectedGroupId = STACK.groupCreate(figuresToAdd);
                            } else {
                                //destroy current group (if temporary)
                                if (!selectedGroup.permanent) {
                                    STACK.groupDestroy(selectedGroupId);
                                }

                                selectedGroupId = selectedObject.id;
                            }

                            redraw = true;
                            Log.info('onMouseDown() + STATE_GROUP_SELECTED  + (different) group figure => STATE_GROUP_SELECTED');
                        }
                        else { //figure from same group
                            //do nothing
                        }
                        break;
                    case 'Figure': //lonely figure
                        if (SHIFT_PRESSED) {
                            var figuresToAdd = [];
                            var groupFigures = STACK.figureGetByGroupId(selectedGroupId);
                            for (var i = 0; i < groupFigures.length; i++) {
                                figuresToAdd.push(groupFigures[i].id);
                            }
                            figuresToAdd.push(selectedObject.id);

                            //destroy current group (if temporary)
                            if (!selectedGroup.permanent) {
                                STACK.groupDestroy(selectedGroupId);
                            }

                            selectedGroupId = STACK.groupCreate(figuresToAdd);

                            Log.info('onMouseDown() + STATE_GROUP_SELECTED + SHIFT  + add lonely figure to other');
                        } else {
                            //destroy current group (if temporary)
                            if (!selectedGroup.permanent) {
                                STACK.groupDestroy(selectedGroupId);
                            }

                            //deselect current group
                            selectedGroupId = -1;

                            state = STATE_FIGURE_SELECTED;
                            selectedFigureId = selectedObject.id;
                            Log.info('onMouseDown() + STATE_GROUP_SELECTED  + lonely figure => STATE_FIGURE_SELECTED');

                            setUpEditPanel(STACK.figureGetById(selectedFigureId));
                            redraw = true;
                        }
                        break;
                    case 'Container':
                        //destroy current group (if temporary)
                        if (!selectedGroup.permanent) {
                            STACK.groupDestroy(selectedGroupId);
                        }

                        //deselect current group
                        selectedGroupId = -1;

                        state = STATE_CONTAINER_SELECTED;
                        selectedContainerId = selectedObject.id;
                        Log.info('onMouseDown() + STATE_GROUP_SELECTED  + container => STATE_CONTAINER_SELECTED');

                        setUpEditPanel(STACK.containerGetById(selectedContainerId));
                        redraw = true;
                        break;
                    default:    //mouse down on empty space
                        if (!SHIFT_PRESSED) { //if Shift isn`t pressed
                            if (!selectedGroup.permanent) {
                                STACK.groupDestroy(selectedGroupId);
                            }

                            selectedGroupId = -1;
                            state = STATE_NONE;
                            setUpEditPanel(canvasProps);
                            redraw = true;
                            Log.info('onMouseDown() + STATE_GROUP_SELECTED  + mouse on empty => STATE_NONE');
                        }
                        break;
                }
            }

            break;


        case STATE_CONNECTOR_PICK_FIRST:
            //moved so it can be called from undo action
            //connectorPickFirst(x, y, ev);
            break;

        case STATE_CONNECTOR_PICK_SECOND:
            state = STATE_NONE;
            break;


        case STATE_CONNECTOR_SELECTED:
            var cps = CONNECTOR_MANAGER.connectionPointGetAllByParent(selectedConnectorId);
            var start = cps[0];
            var end = cps[1];
            var figureConnectionPointId;

            //did we click any of the connection points?
            if (start.point.near(x, y, 3)) {
                Log.info("Picked the start point");
                selectedConnectionPointId = start.id;
                state = STATE_CONNECTOR_MOVE_POINT;
                HTMLCanvas.style.cursor = 'default';

                //this acts like clone of the connector
                var undoCmd = new ConnectorAlterCommand(selectedConnectorId);
                History.addUndo(undoCmd);

                // check if current cloud for connection point
                figureConnectionPointId = CONNECTOR_MANAGER.connectionPointGetByXYRadius(x, y, FIGURE_CLOUD_DISTANCE, ConnectionPoint.TYPE_FIGURE, end);
                if (figureConnectionPointId !== -1) {
                    currentCloud = [selectedConnectionPointId, figureConnectionPointId];
                }
            }
            else if (end.point.near(x, y, 3)) {
                Log.info("Picked the end point");
                selectedConnectionPointId = end.id;
                state = STATE_CONNECTOR_MOVE_POINT;
                HTMLCanvas.style.cursor = 'default';

                //this acts like clone of the connector
                var undoCmd = new ConnectorAlterCommand(selectedConnectorId);
                History.addUndo(undoCmd);

                // check if current cloud for connection point
                figureConnectionPointId = CONNECTOR_MANAGER.connectionPointGetByXYRadius(x, y, FIGURE_CLOUD_DISTANCE, ConnectionPoint.TYPE_FIGURE, start);
                if (figureConnectionPointId !== -1) {
                    currentCloud = [selectedConnectionPointId, figureConnectionPointId];
                }
            }
            else { //no connection point selected

                //see if handler selected
                if (HandleManager.handleGet(x, y) != null) {
                    Log.info("onMouseDown() + STATE_CONNECTOR_SELECTED - handle selected");
                    HandleManager.handleSelectXY(x, y);

                    //TODO: just copy/paste code ....this acts like clone of the connector
                    var undoCmd = new ConnectorAlterCommand(selectedConnectorId);
                    History.addUndo(undoCmd);
                }
                else {
                    // get object under cursor
                    var selectedObject = Util.getObjectByXY(x, y);
                    switch (selectedObject.type) {
                        case 'Connector':
                            selectedConnectorId = selectedObject.id;
                            selectedFigureId = -1;
                            var con = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId);
                            HandleManager.shapeSet(con);
                            setUpEditPanel(con);
                            redraw = true;
                            break;
                        case 'Group':
                            selectedConnectorId = -1;
                            selectedGroupId = selectedObject.id; // set Group as active element
                            state = STATE_GROUP_SELECTED;
                            setUpEditPanel(null);
                            redraw = true;
                            break;
                        case 'Figure':
                            selectedConnectorId = -1;
                            selectedFigureId = selectedObject.id; // set Figure as active element
                            state = STATE_FIGURE_SELECTED;
                            setUpEditPanel(STACK.figureGetById(selectedFigureId));
                            redraw = true;
                            break;
                        case 'Container':
                            selectedConnectorId = -1;
                            selectedContainerId = selectedObject.id; // set Container as active element
                            state = STATE_CONTAINER_SELECTED;
                            setUpEditPanel(STACK.containerGetById(selectedContainerId));
                            redraw = true;
                            break;
                        default:    // nothing else selected
                            selectedConnectorId = -1;
                            state = STATE_NONE;
                            setUpEditPanel(canvasProps); // set canvas as active element
                            redraw = true;
                            break;
                    }
                }
            }
            break; //end case STATE_CONNECTOR_SELECTED 
        case STATE_CONTAINER_SELECTED:

            /*Description:
            * If we have a Container selected and we do click here is what can happen:
            * - if we clicked a handle of current selected shape (it should be Container) then just select that Handle
            * - if we clicked a Connector, Group or than it should be selected  (Connectors, Groups and Figures are more important than Containers :p)
            * - if we clicked a Container:
            *      - did we clicked another Container?
            *          - select that Container
            *      - did we click same Container?
            *          - do nothing
            */

            if (HandleManager.handleGet(x, y) != null) { //Clicked a handler (of a Figure or Connector)
                Log.info("onMouseDown() + STATE_CONTAINER_SELECTED - handle selected");
                /*Nothing important (??) should happen here. We just clicked the handler of the figure*/
                HandleManager.handleSelectXY(x, y);
            }
            else {
                // get object under cursor
                var selectedObject = Util.getObjectByXY(x, y);
                switch (selectedObject.type) {
                    case 'Connector':
                        selectedContainerId = -1;
                        selectedConnectorId = selectedObject.id;
                        state = STATE_CONNECTOR_SELECTED;
                        Log.info('onMouseDown() + STATE_CONTAINER_SELECTED  - change to STATE_CONNECTOR_SELECTED');
                        setUpEditPanel(CONNECTOR_MANAGER.connectorGetById(selectedConnectorId));
                        redraw = true;
                        break;
                    case 'Group':
                        selectedContainerId = -1;
                        selectedGroupId = selectedObject.id;
                        state = STATE_GROUP_SELECTED;
                        Log.info('onMouseDown() + STATE_CONTAINER_SELECTED + group selected  =>  change to STATE_GROUP_SELECTED');
                        setUpEditPanel(null);
                        redraw = true;
                        break;
                    case 'Figure':
                        selectedContainerId = -1;
                        selectedFigureId = selectedObject.id;
                        state = STATE_FIGURE_SELECTED;
                        Log.info('onMouseDown() + STATE_CONTAINER_SELECTED + lonely figure => change to STATE_FIGURE_SELECTED');
                        setUpEditPanel(STACK.figureGetById(selectedFigureId));
                        redraw = true;
                        break;
                    case 'Container':
                        if (selectedObject.id != selectedContainerId) { //a different one
                            selectedContainerId = selectedObject.id;
                            Log.info('onMouseDown() + STATE_NONE  - change to STATE_CONTAINER_SELECTED');
                            setUpEditPanel(STACK.containerGetById(selectedContainerId));
                            redraw = true;
                        }
                        break;
                    default:    // nothing else selected
                        selectedContainerId = -1;
                        state = STATE_NONE;
                        setUpEditPanel(canvasProps);
                        HandleManager.clear();
                        Log.info('onMouseDown() + STATE_CONTAINER_SELECTED + click on nothing - change to STATE_NONE');
                        redraw = true;
                        break;
                }
            }
            break; //end STATE_CONTAINER_SELECTED
        case STATE_CONNECTOR_CREATE:
            selectedFigureId = -1;
            state = STATE_CONNECTOR_PICK_FIRST;
            connectorType = Connector.TYPE_STRAIGHT;
            redraw = true;
            break;

        default:
            //alert("onMouseDown() - switch default - state is " + state);
    }

    draw();

    return false;
}


/**
*Treats the mouse up event
*@param {Event} ev - the event generated when key is up
**/
function onMouseUp(ev) {
    Log.info("main.js>onMouseUp()");

    var coords = getCanvasXY(ev);
    var x = coords[0];
    var y = coords[1];

    lastClick = [];
    mousePressed = true;

    switch (state) {

        case STATE_NONE:
            /*Description:
            * TODO: Nothing should happen here
            */

            if (HandleManager.handleGetSelected()) {
                HandleManager.clear();
            }
            break;

        case STATE_FIGURE_SELECTED:
            /*Description:
            * This means that we have a figure selected and just released the mouse:
            * - if we were altering (rotate/resize) the Figure that will stop (Handler will be deselected)
            * - if we were moving the figure .... that will stop (but figure remains selected)
            */

            mousePressed = false;
            HandleManager.handleSelectedIndex = -1; //reset only the handler....the Figure is still selected

            break;


        case STATE_CONTAINER_SELECTED:
            /*Description:
            * This means that we have a Container selected and just released the mouse:
            * - if we were altering (rotate/resize) the Container that will stop (Handler will be deselected)
            * - if we were moving the Container .... that will stop (but figure remains selected)
            */

            mousePressed = false;
            HandleManager.handleSelectedIndex = -1; //reset only the handler....the Figure is still selected

            break;


        case STATE_GROUP_SELECTED:
            Log.info('onMouseUp() + STATE_GROUP_SELECTED ...');

            mousePressed = false;
            HandleManager.handleSelectedIndex = -1; //reset only the handler....the Group is still selected

            break;

        case STATE_SELECTING_MULTIPLE:
            /*Description
            *From figures select only those that do not belong to any group
            **/
            Log.info('onMouseUp() + STATE_SELECTING_MULTIPLE => STATE_NONE');
            Log.info('onMouseUp() selection area: ' + selectionArea);

            state = STATE_NONE;

            var figuresToAdd = [];

            /* 
            * If SHIFT pressed add the new selection to the already selected figures, 
            * so here add to array already selected ones
            */
            if (SHIFT_PRESSED) {

                //if one figure already selected add it
                if (selectedFigureId != -1) {
                    figuresToAdd.push(selectedFigureId);
                }

                //if group already selected add it
                if (selectedGroupId != -1) {
                    var selectedGroup = STACK.groupGetById(selectedGroupId);
                    var groupFigures = STACK.figureGetByGroupId(selectedGroupId);
                    for (var i = 0; i < groupFigures.length; i++) {
                        figuresToAdd.push(groupFigures[i].id);
                    }
                }
            }

            //add free figures (not belonging to any group) that overlaps with selection region
            /*TODO: 
            * From Janis to Alex: Why we are selecting only figures that 
            *  doesn`t belong to any group, I think we must also add grouped 
            *  figures
            * From Alex to Janis: We do not support groups in groups neither 
            *  figures that belong to more than one group
            */
            for (var i = 0; i < STACK.figures.length; i++) {
                if (STACK.figures[i].groupId == -1) { //we only want ungrouped items
                    var points = STACK.figures[i].getPoints();
                    if (points.length == 0) { //if no  point at least to add bounds TODO: lame 'catch all' condition
                        points.push(new Point(STACK.figures[i].getBounds()[0], STACK.figures[i].getBounds()[1])); //top left
                        points.push(new Point(STACK.figures[i].getBounds()[2], STACK.figures[i].getBounds()[3])); //bottom right
                        points.push(new Point(STACK.figures[i].getBounds()[0], STACK.figures[i].getBounds()[3])); //bottom left
                        points.push(new Point(STACK.figures[i].getBounds()[2], STACK.figures[i].getBounds()[1])); //top right
                    }

                    // flag shows if figure added to figuresToAdd array
                    var figureAddFlag = false;


                    /**Idea: We want to select both figures completely encompassed by 
                    * selection (case 1) and those that are intersected by selection (case 2)*/

                    //1 - test if any figure point inside selection
                    for (var a = 0; a < points.length; a++) {
                        if (Util.isPointInside(points[a], selectionArea.getPoints())) {
                            figuresToAdd.push(STACK.figures[i].id);
                            // set flag not to add figure twice
                            figureAddFlag = true;
                            break;
                        }
                    }

                    //2 - test if any figure intersected by selection
                    if (!figureAddFlag) { //run this ONLY if is not already proposed for addition
                        figureAddFlag = Util.polylineIntersectsRectangle(points, selectionArea.getBounds(), true);
                    }

                    //select figures whose line intersects selectionArea
                    if (figureAddFlag) {
                        figuresToAdd.push(STACK.figures[i].id);
                    }
                } //end if
            } //end for

            if (selectedGroupId != -1) {
                var selectedGroup = STACK.groupGetById(selectedGroupId);
                //destroy current group (if temporary)
                if (!selectedGroup.permanent) {
                    STACK.groupDestroy(selectedGroupId);
                }
            }

            //See what to do with collected figures
            if (figuresToAdd.length >= 2) { //if we selected at least 2 figures then we can create a group
                selectedGroupId = STACK.groupCreate(figuresToAdd);
                state = STATE_GROUP_SELECTED;
                setUpEditPanel(null); //because of shift in this case we also need to reset the edit panel
                Log.info('onMouseUp() + STATE_SELECTING_MULTIPLE  + min. 2 figures => STATE_GROUP_SELECTED');
            }
            else if (figuresToAdd.length == 1) { // if we only select one figure, then it is not a group, it's a simple selection
                selectedFigureId = figuresToAdd[0];
                selectedGroupId = -1;
                state = STATE_FIGURE_SELECTED;
                Log.info('onMouseUp() + STATE_SELECTING_MULTIPLE  + 1 figure => STATE_FIGURE_SELECTED');
            }
            break;


        case STATE_CONNECTOR_PICK_SECOND:

            //store undo command
            var cmdCreateCon = new ConnectorCreateCommand(selectedConnectorId);
            History.addUndo(cmdCreateCon);

            //reset all {ConnectionPoint}s' color
            CONNECTOR_MANAGER.connectionPointsResetColor();

            //reset current connection cloud
            currentCloud = [];

            //select the current connector
            state = STATE_CONNECTOR_SELECTED;
            var con = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId);
            setUpEditPanel(con);
            redraw = true;
            break;
        case STATE_CONNECTOR_MOVE_POINT:
            /**
            *Description:
            *Simply alter the connector until mouse will be released 
            **/

            //reset all {ConnectionPoint}s' color
            CONNECTOR_MANAGER.connectionPointsResetColor();

            //reset current connection cloud
            currentCloud = [];
            state = STATE_CONNECTOR_SELECTED; //back to selected connector
            selectedConnectionPointId = -1; //but deselect the connection point
            redraw = true;
            break;
        case STATE_CONNECTOR_SELECTED:
            if (currentMoveUndo) {
                var turns = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId).turningPoints;
                var newTurns = [turns.length];
                for (var i = 0; i < turns.length; i++) {
                    newTurns[i] = turns[i].clone();
                }
                currentMoveUndo.currentValue = newTurns;
                History.addUndo(currentMoveUndo);
                state = STATE_NONE;
                selectedConnectorId = -1;
                HandleManager.clear(); //clear current selection
            }
            break;
    }
    currentMoveUndo = null;
    mousePressed = false;
    draw();
}

/**Remembers last move. Initially it's null but once set it's a [x,y] array*/
var lastMove = null;

/**It will accumulate the changes on either X or Y coordinates for snap effect.
*As we need to escape the "gravity/attraction" of the grid system we need to "accumulate" more changes
*and if those changes become greater than a certain threshold we will initiate a snap action
*Zack : "Because of the snap to grid function we need to move more than a certain amount of pixels
*so we will not be snapped back to the old location"
*Initially it's [0,0] but once more and more changes got added a snap effect will be triggered
*and some of it's elements will be reset to 0.
*So snapMonitor = [sumOfChagesOnX, sumOfChangesOnY]
**/
var snapMonitor = [0, 0];

/**Treats the mouse move event
*@param {Event} ev - the event generated when key is up
**/
function onMouseMove(ev) {
    //    //resize canvas.
    //    if(lastMousePosition != null){
    //        resize(ev);
    //    }
    var redraw = false;
    var coords = getCanvasXY(ev);

    if (coords == null) {
        Log.error("main.js onMouseMove() null coordinates");
        return;
    }

    var x = coords[0];
    var y = coords[1];

    var canvas = getCanvas();

    /*change cursor
    *More here: http://www.javascriptkit.com/dhtmltutors/csscursors.shtml
    */

    switch (state) {

        case STATE_NONE:

            /*Description:
            * We are in None state when no action was done....yet.  Here is what can happen:
            * - if the mouse is pressed, through onMouseDown(), then it's the begining of a multiple selection
            * - if mouse is not pressed then change the cursor type to:
            *      - "move" if  over a figure or connector
            *      - "default" if over "over a connector "empty" space
            */
            if (mousePressed) {
                state = STATE_SELECTING_MULTIPLE;
                selectionArea.points[0] = new Point(x, y);
                selectionArea.points[1] = new Point(x, y);
                selectionArea.points[2] = new Point(x, y);
                selectionArea.points[3] = new Point(x, y); //the selectionArea has no size until we start dragging the mouse
                Log.debug('onMouseMove() - STATE_NONE + mousePressed = STATE_SELECTING_MULTIPLE');
            }
            else {
                if (STACK.figureIsOver(x, y)) { //over a figure
                    canvas.style.cursor = 'move';
                    Log.debug('onMouseMove() - STATE_NONE - mouse cursor = move (over figure)');
                }
                else if (CONNECTOR_MANAGER.connectorGetByXY(x, y) != -1) { //over a connector
                    canvas.style.cursor = 'move';
                    Log.debug('onMouseMove() - STATE_NONE - mouse cursor = move (over connector)');
                }
                else if (STACK.containerGetByXY(x, y) != -1) { //container has a lower priority than figure
                    canvas.style.cursor = 'move';
                    Log.debug("onMouseMove() - STATE_NONE - mouse cursor = move (over container)");
                }
                else { //default cursor
                    canvas.style.cursor = 'default';
                    Log.debug('onMouseMove() - STATE_NONE - mouse cursor = default');
                }
            }

            break;


        case STATE_SELECTING_MULTIPLE:
            selectionArea.points[1].x = x; //top right

            selectionArea.points[2].x = x; //bottom right
            selectionArea.points[2].y = y;

            selectionArea.points[3].y = y; //bottom left
            redraw = true;
            break;


        case STATE_FIGURE_CREATE:
            if (createFigureFunction) { //creating a new figure
                canvas.style.cursor = 'crosshair';
            }
            break;



        case STATE_FIGURE_SELECTED:

            /*Description:
            * We have a figure selected.  Here is what can happen:
            * - if the mouse is pressed
            *      - if over a Figure's Handler then execute Handler's action
            *      - else (it is over the Figure)
            *          if SHIFT _NOT_ pressed
            *              then move figure
            *          else (SHIFT pressed)
            *              enter STATE_SELECTING_MULTIPLE state 
            * - if mouse is not pressed then change the cursor type to :
            *      - "move" if over a figure or connector
            *      - "handle" if over current figure's handle
            *      - "default" if over "nothing"
            */

            if (mousePressed) { // mouse is (at least was) pressed
                if (lastMove != null) { //we are in dragging mode
                    /*We need to use handleGetSelected() as if we are using handleGet(x,y) then 
                    *as we move the mouse....it can move faster/slower than the figure and we 
                    *will lose the Handle selection.
                    **/
                    var handle = HandleManager.handleGetSelected();

                    if (handle != null) { //We are over a Handle of selected Figure               
                        canvas.style.cursor = handle.getCursor();
                        handle.action(lastMove, x, y);
                        redraw = true;
                    }
                    else { /*no handle is selected*/
                        if (!SHIFT_PRESSED) {//just translate the figure                            
                            canvas.style.cursor = 'move';
                            var translateMatrix = generateMoveMatrix(STACK.figureGetById(selectedFigureId), x, y);
                            Log.info("onMouseMove() + STATE_FIGURE_SELECTED : translation matrix" + translateMatrix);
                            var cmdTranslateFigure = new FigureTranslateCommand(selectedFigureId, translateMatrix);
                            History.addUndo(cmdTranslateFigure);
                            cmdTranslateFigure.execute();


                            /*Algorithm described:
                            if figure belong to an existing container: 
                            if we moved it outside of current container (even partially?!)
                            unglue it from container
                                           
                            if figure dropped inside a container
                            add it to the (new) container        
                            */
                            var figure = STACK.figureGetById(selectedFigureId);
                            var figBounds = figure.getBounds();

                            var containerId = CONTAINER_MANAGER.getContainerForFigure(selectedFigureId);
                            if (containerId !== -1) { //we are glued to a container

                                var container = STACK.containerGetById(containerId);
                                var contBounds = container.getBounds();

                                //Test if figure' bounds are inside container's bounds?                                
                                if (Util.areBoundsInBounds(figBounds, contBounds)) {
                                    //do nothing we are still in same container
                                }
                                else {
                                    //TODO: CONTAINER_MANAGER.removeFigure(containerId, selectedFigureId);
                                    //throw "main->onMouseMove->FigureSelected: removed from a container";
                                    CONTAINER_MANAGER.removeFigure(containerId, selectedFigureId);
                                }
                            }
                            else { //not in any container
                                var newContainerId = -1;
                                for (var c = 0; c < STACK.containers.length; c++) {
                                    var tempCont = STACK.containers[c];
                                    if (Util.areBoundsInBounds(figBounds, tempCont.getBounds())) {
                                        newContainerId = STACK.containers[c].id;
                                        break;
                                    }
                                }

                                if (newContainerId !== -1) {
                                    //TODO: add figure to container
                                    //throw "main->onMouseMove->FigureSelected: add figure to container";
                                    CONTAINER_MANAGER.addFigure(newContainerId, selectedFigureId);
                                }

                            }
                            redraw = true;
                        } else { //we are entering a figures selection sesssion
                            state = STATE_SELECTING_MULTIPLE;
                            selectionArea.points[0] = new Point(x, y);
                            selectionArea.points[1] = new Point(x, y);
                            selectionArea.points[2] = new Point(x, y);
                            selectionArea.points[3] = new Point(x, y); //the selectionArea has no size until we start dragging the mouse
                            redraw = true;
                        }
                    }
                }
            }
            else { //no mouse press (only change cursor)
                var handle = HandleManager.handleGet(x, y); //TODO: we should be able to replace it with .getSelectedHandle()

                if (handle != null) { //We are over a Handle of selected Figure               
                    canvas.style.cursor = handle.getCursor();
                }
                else {
                    /*move figure only if no handle is selected*/
                    var tmpFigId = STACK.figureGetByXY(x, y); //pick first figure from (x, y)
                    if (tmpFigId != -1) {
                        canvas.style.cursor = 'move';
                    }
                    else {
                        canvas.style.cursor = 'default';
                    }
                }
            }

            break;

        case STATE_TEXT_EDITING:

            /*Description:
            * We have a text editor.  Here is what can happen:
            * - if the mouse is pressed
            *      - this should never happen
            * - if mouse is not pressed then change the cursor type to :
            *      - "move" if over a figure or connector
            *      - "handle" if over current figure's handle
            *      - "default" if over "nothing"
            */

            if (!mousePressed) {

                var handle = HandleManager.handleGet(x, y); //TODO: we should be able to replace it with .getSelectedHandle()

                if (handle != null) { //We are over a Handle of selected Figure
                    canvas.style.cursor = handle.getCursor();
                    Log.info('onMouseMove() - STATE_TEXT_EDITING + over a Handler = change cursor to: ' + canvas.style.cursor);
                }
                else {
                    /*move figure only if no handle is selected*/
                    var tmpFigId = STACK.figureGetByXY(x, y); //pick first figure from (x, y)
                    if (tmpFigId != -1) {
                        canvas.style.cursor = 'move';
                        Log.info("onMouseMove() + STATE_TEXT_EDITING + over a figure = change cursor");
                    }
                    else {
                        canvas.style.cursor = 'default';
                        Log.info("onMouseMove() + STATE_TEXT_EDITING + over nothin = change cursor to default");
                    }
                }
            } else {
                throw "main:onMouseMove() - this should never happen";
            }

            break;

        case STATE_CONTAINER_SELECTED:
            //BRUTE COPY FROM FIGURE
            if (mousePressed) { // mouse is (at least was) pressed
                if (lastMove != null) { //we are in dragging mode
                    /*We need to use handleGetSelected() as if we are using handleGet(x,y) then 
                    *as we move the mouse....it can move faster/slower than the figure and we 
                    *will lose the Handle selection.
                    **/
                    var handle = HandleManager.handleGetSelected();

                    if (handle != null) { //We are over a Handle of selected Container               
                        canvas.style.cursor = handle.getCursor();
                        handle.action(lastMove, x, y);
                        redraw = true;
                        Log.info('onMouseMove() - STATE_CONTAINER_SELECTED + drag - mouse cursor = ' + canvas.style.cursor);
                    }
                    else { /*no handle is selected*/
                        //                        if (!SHIFT_PRESSED){//just translate the figure                            
                        canvas.style.cursor = 'move';
                        var translateMatrix = generateMoveMatrix(STACK.containerGetById(selectedContainerId), x, y);
                        Log.info("onMouseMove() + STATE_CONTAINER_SELECTED : translation matrix" + translateMatrix);
                        var cmdTranslateContainer = new ContainerTranslateCommand(selectedContainerId, translateMatrix);
                        History.addUndo(cmdTranslateContainer);
                        cmdTranslateContainer.execute();
                        redraw = true;
                    }
                }
            }
            else { //no mouse press (only change cursor)
                var handle = HandleManager.handleGet(x, y); //TODO: we should be able to replace it with .getSelectedHandle()

                if (handle != null) { //We are over a Handle of selected Figure               
                    canvas.style.cursor = handle.getCursor();
                    Log.info('onMouseMove() - STATE_CONTAINER_SELECTED + over a Handler = change cursor to: ' + canvas.style.cursor);
                }
                else {
                    /*move figure only if no handle is selected*/
                    if (STACK.containerGetByXY(x, y) !== -1) {//pick first container from (x, y)
                        canvas.style.cursor = 'move';
                        Log.info("onMouseMove() + STATE_CONTAINER_SELECTED + over a container's edge = change cursor");
                    }
                    else {
                        canvas.style.cursor = 'default';
                    }
                }
            }
            //END BRUTE COPY FROM FIGURE

            break;


        case STATE_GROUP_SELECTED:
            //Log.info('onMouseMove() - STATE_GROUP_SELECTED ...');
            /*Description:
            *TODO: implement
            * We have a group selected.  Here is what can happen:
            * - if the mouse is pressed
            *      - if over a Group's Handler then execute Handler's action
            *      - else (it is over one of Group's Figures):
            *          if SHIFT _NOT_ pressed:
            *              then move whole group
            *          else (SHIFT pressed)
            *              enter STATE_SELECTING_MULTIPLE state         
            * - if mouse is not pressed then change the cursor type to :
            *      - drag group?
            *      - cursor ? 
            *          - "move" if over a figure or connector or group
            *          - "handle" if over current group's handle
            *          - "default" if over "nothing"
            */

            if (mousePressed) {
                if (lastMove != null) {
                    //Log.debug('onMouseMove() - STATE_GROUP_SELECTED + mouse pressed');
                    /*We need to use handleGetSelected() as if we are using handleGet(x,y) then 
                    *as we move the mouse....it can move faster/slower than the figure and we 
                    *will lose the Handle selection.
                    **/
                    var handle = HandleManager.handleGetSelected();

                    if (handle != null) { //over a handle
                        Log.info('onMouseMove() - STATE_GROUP_SELECTED + mouse pressed  + over a Handle');
                        //HandleManager.handleSelectXY(x, y);
                        canvas.style.cursor = handle.getCursor();
                        handle.action(lastMove, x, y);
                        redraw = true;
                    }
                    else { //not over any handle -so it must be translating
                        if (!SHIFT_PRESSED) {
                            Log.info('onMouseMove() - STATE_GROUP_SELECTED + mouse pressed + NOT over a Handle');
                            canvas.style.cursor = 'move';
                            var mTranslate = generateMoveMatrix(STACK.groupGetById(selectedGroupId), x, y);
                            var cmdTranslateGroup = new GroupTranslateCommand(selectedGroupId, mTranslate);
                            cmdTranslateGroup.execute();
                            History.addUndo(cmdTranslateGroup);
                            redraw = true;
                        } else {
                            state = STATE_SELECTING_MULTIPLE;
                            selectionArea.points[0] = new Point(x, y);
                            selectionArea.points[1] = new Point(x, y);
                            selectionArea.points[2] = new Point(x, y);
                            selectionArea.points[3] = new Point(x, y); //the selectionArea has no size until we start dragging the mouse
                            redraw = true;
                            Log.info('onMouseMove() - STATE_GROUP_SELECTED + mousePressed + SHIFT => STATE_SELECTING_MULTIPLE');
                        }
                    }
                }
            }
            else { //mouse not pressed (only change cursor)
                Log.debug('onMouseMove() - STATE_GROUP_SELECTED + mouse NOT pressed');
                if (HandleManager.handleGet(x, y) != null) {
                    canvas.style.cursor = HandleManager.handleGet(x, y).getCursor();
                }
                else if (CONNECTOR_MANAGER.connectorGetByXY(x, y) != -1) {
                    //nothing for now
                }
                else if (STACK.figureIsOver(x, y)) {
                    canvas.style.cursor = 'move';
                }
                else {
                    canvas.style.cursor = 'default';
                }
            }

            break;


        case STATE_CONNECTOR_PICK_FIRST:
            if (mousePressed) {
                connectorPickFirst(x, y, ev);
                //change FCP (figure connection points) color
                var fCpId = CONNECTOR_MANAGER.connectionPointGetByXY(x, y, ConnectionPoint.TYPE_FIGURE); //find figure's CP

                if (fCpId != -1) { //we are over a figure's CP
                    var fCp = CONNECTOR_MANAGER.connectionPointGetById(fCpId);
                    fCp.color = ConnectionPoint.OVER_COLOR;
                    selectedConnectionPointId = fCpId;
                }
                else { //change back old connection point to normal color
                    if (selectedConnectionPointId != -1) {
                        var oldCp = CONNECTOR_MANAGER.connectionPointGetById(selectedConnectionPointId);
                        oldCp.color = ConnectionPoint.NORMAL_COLOR;
                        //                    canvas.style.cursor = 'normal';
                        selectedConnectionPointId = -1;
                    }
                }
                canvas.style.cursor = 'crosshair';
                redraw = true;
            } else {
                state = STATE_NONE;
            }
            break;


        case STATE_CONNECTOR_PICK_SECOND:
            //moved to allow undo to access it
            connectorPickSecond(x, y, ev);
            redraw = true;
            break;


        case STATE_CONNECTOR_SELECTED:
            /*Description:
            *In case you move the mouse and you have the connector selected:
            *  - if adjusting the endpoints
            *      - alter the shape of connector in real time (gluing and unglued it, etc)
            *      (EXTRA option: do as little changes as possible to existing shape
            *  - if adjusting the handlers
            *      - alter the shape of connector in real time
            **/

            //alert('Move but we have a connector');
            //change cursor to move if over a connector's CP
            //var connector = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId);
            var cps = CONNECTOR_MANAGER.connectionPointGetAllByParent(selectedConnectorId);
            var start = cps[0];
            var end = cps[1];
            if (start.point.near(x, y, 3) || end.point.near(x, y, 3)) {
                canvas.style.cursor = 'move';
            }
            else if (HandleManager.handleGet(x, y) != null) { //over a handle?. Handles should appear only for selected figures
                canvas.style.cursor = HandleManager.handleGet(x, y).getCursor();
            }
            else {
                canvas.style.cursor = 'default';
            }

            /*if we have a handle action*/
            if (mousePressed == true && lastMove != null && HandleManager.handleGetSelected() != null) {
                Log.info("onMouseMove() + STATE_CONNECTOR_SELECTED - trigger a handler action");
                var handle = HandleManager.handleGetSelected();
                //                alert('Handle action');

                /*We need completely new copies of the turningPoints in order to restore them,
                *this is simpler than keeping track of the handle used, the direction in which the handle edits
                *and the turningPoints it edits*/

                //store old turning points
                var turns = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId).turningPoints;
                var oldTurns = [turns.length];
                for (var i = 0; i < turns.length; i++) {
                    oldTurns[i] = turns[i].clone();
                }


                //DO the handle action
                handle.action(lastMove, x, y);

                //store new turning points
                turns = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId).turningPoints;
                var newTurns = [turns.length];
                for (var i = 0; i < turns.length; i++) {
                    newTurns[i] = turns[i].clone();
                }


                //see if old turning points are the same as the new turning points
                var difference = false;
                for (var k = 0; k < newTurns.length; k++) {
                    if (!newTurns[k].equals(oldTurns[k])) {
                        difference = true;
                    }
                }
                redraw = true;
            } else if (mousePressed == true && lastMove != null) {
                canvas.style.cursor = 'move';
                var con = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId);
                var deltaX = x - lastMove[0];
                var deltaY = y - lastMove[1];
                start.point.x = start.point.x + deltaX;
                end.point.x = end.point.x + deltaX;
                start.point.y = start.point.y + deltaY;
                end.point.y = end.point.y + deltaY;
                con.turningPoints[0] = start.point;
                con.turningPoints[1] = end.point;

                redraw = true;
            }
            break;
        case STATE_CONNECTOR_MOVE_POINT:
            /**
            *Description: 
            *Adjust on real time - WYSIWYG
            *-compute the solution
            *-update connector shape
            *-update glues
            *TODO: add description*/
            Log.info("Easy easy easy....it's fragile");
            if (mousePressed) { //only if we are dragging

                /*update connector - but not unglue/glue it (Unglue and glue is handle in onMouseUp)
                *as we want the glue-unglue to produce only when mouse is released*/
                connectorMovePoint(selectedConnectionPointId, x, y, ev);

                redraw = true;
            }
            break;
    }


    lastMove = [x, y];

    if (redraw) {
        draw();
    }
    return false;
}


/**Treats the mouse double click event
*@param {Event} ev - the event generated when key is clicked twice
*@author Artyom, Alex
**/
function onDblClick(ev) {
    var coords = getCanvasXY(ev);
    var x = coords[0];
    var y = coords[1];
    lastClick = [x, y];
    //user cancel
    return false;
    // store clicked figure or connector
    var shape = null;

    // store id value (from Stack) of clicked text primitive
    var textPrimitiveId = -1;

    //find Connector at (x,y)
    var cId = CONNECTOR_MANAGER.connectorGetByXY(x, y);
    var connector = null;

    // check if we clicked a connector
    if (cId != -1) {
        connector = CONNECTOR_MANAGER.connectorGetById(cId);
        shape = connector;
        textPrimitiveId = -1; // (0 by default)
    } else {
        cId = CONNECTOR_MANAGER.connectorGetByTextXY(x, y);

        // check if we clicked a text of connector
        if (cId != -1) {
            connector = CONNECTOR_MANAGER.connectorGetById(cId);
            shape = connector;
            textPrimitiveId = -1; // (0 by default)
        } else {
            //find Figure at (x,y)
            var fId = STACK.figureGetByXY(x, y);

            // check if we clicked a figure
            if (fId != -1) {
                var figure = STACK.figureGetById(fId);
                var tId = STACK.textGetByFigureXY(fId, x, y);

                // if we clicked text primitive inside of figure
                if (tId !== -1) {
                    shape = figure;
                    textPrimitiveId = tId;
                }
            } else {
                //find Container at (x,y)
                var contId = STACK.containerGetByXY(x, y);

                // check if we clicked a container
                if (contId !== -1) {
                    var container = STACK.containerGetById(contId);
                    var tId = STACK.textGetByContainerXY(contId, x, y);

                    // if we clicked text primitive inside of figure
                    if (tId !== -1) {
                        shape = container;
                        textPrimitiveId = tId;
                    }
                }
            }
        }
    }

    // check if we clicked a text primitive inside of shape
    if (textPrimitiveId != -1) {
        // if group selected
        if (state == STATE_GROUP_SELECTED) {
            var selectedGroup = STACK.groupGetById(selectedGroupId);

            // if group is temporary then destroy it
            if (!selectedGroup.permanent) {
                STACK.groupDestroy(selectedGroupId);
            }

            //deselect current group
            selectedGroupId = -1;
        }

        // deselect current figure
        selectedFigureId = -1;

        // deselect current container
        selectedContainerId = -1;

        // deselect current connector
        selectedConnectorId = -1;

        // set current state
        state = STATE_TEXT_EDITING;

        // set up text editor
        setUpTextEditorPopup(shape, textPrimitiveId);
        redraw = true;
    }

    draw();

    return false;
}


/**Pick the first connector we can get at (x,y) position
*@param {Number} x - the x position 
*@param {Number} y - the y position 
*@param {Event} ev - the event triggered
*@author Alex, Artyom
**/
function connectorPickFirst(x, y, ev) {
    Log.group("connectorPickFirst");
    //create connector
    var conId = CONNECTOR_MANAGER.connectorCreate(new Point(x, y), new Point(x + 10, y + 10) /*fake cp*/, connectorType);
    selectedConnectorId = conId;
    var con = CONNECTOR_MANAGER.connectorGetById(conId);


    //TRY TO GLUE IT
    //1.get CP of the connector
    var conCps = CONNECTOR_MANAGER.connectionPointGetAllByParent(conId);

    //get Figure's id if over it
    var fOverId = STACK.figureGetByXY(x, y);
    //get the ConnectionPoint's id if we are over it (and belonging to a figure)
    var fCpOverId = CONNECTOR_MANAGER.connectionPointGetByXY(x, y, ConnectionPoint.TYPE_FIGURE); //find figure's CP

    //see if we can snap to a figure
    if (fCpOverId != -1) { //Are we over a ConnectionPoint from a Figure?
        var fCp = CONNECTOR_MANAGER.connectionPointGetById(fCpOverId);

        //update connector' cp
        conCps[0].point.x = fCp.point.x;
        conCps[0].point.y = fCp.point.y;

        //update connector's turning point
        con.turningPoints[0].x = fCp.point.x;
        con.turningPoints[0].y = fCp.point.y;

        var g = CONNECTOR_MANAGER.glueCreate(fCp.id, conCps[0].id, false);
        Log.info("First glue created : " + g);
        //alert('First glue ' + g);
    } else if (fOverId !== -1) { //Are we, at least, over the {Figure}?

        /*As we are over a {Figure} but not over a {ConnectionPoint} we will switch
        * to automatic connection*/
        var point = new Point(x, y);
        var candidate = CONNECTOR_MANAGER.getClosestPointsOfConnection(
            true,    // automatic start
            true,    // automatic end 
            fOverId, //start figure's id
            point,   //start point 
            fOverId, //end figure's id
            point    //end point
        );

        var connectionPoint = candidate[0];

        //update connector' cp
        conCps[0].point.x = conCps[1].point.x = connectionPoint.x;
        conCps[0].point.y = conCps[1].point.y = connectionPoint.y;

        //update connector's turning point
        con.turningPoints[0].x = con.turningPoints[1].x = connectionPoint.x;
        con.turningPoints[0].y = con.turningPoints[1].y = connectionPoint.y;

        var g = CONNECTOR_MANAGER.glueCreate(candidate[2], conCps[0].id, true);
        Log.info("First glue created : " + g);
    }
    state = STATE_CONNECTOR_PICK_SECOND;
    Log.groupEnd();
}


/**Pick the second {ConnectorPoint}  we can get at (x,y) position
*@param {Number} x - the x position 
*@param {Number} y - the y position 
*@param {Event} ev - the event triggered
**/
function connectorPickSecond(x, y, ev) {
    Log.group("main: connectorPickSecond");

    //current connector
    var con = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId); //it should be the last one
    var cps = CONNECTOR_MANAGER.connectionPointGetAllByParent(con.id);

    //get the ConnectionPoint's id if we are over it (and belonging to a figure)
    var fCpOverId = CONNECTOR_MANAGER.connectionPointGetByXY(x, y, ConnectionPoint.TYPE_FIGURE); //find figure's CP
    //get Figure's id if over it
    var fOverId = STACK.figureGetByXY(x, y);

    //TODO: remove 
    //play with algorithm
    {
        //We will try to find the startFigure, endFigure, startPoint, endPoint, etc
        //start point
        var rStartPoint = con.turningPoints[0].clone();
        var rStartGlues = CONNECTOR_MANAGER.glueGetBySecondConnectionPointId(cps[0].id);
        var rStartFigure = STACK.figureGetAsFirstFigureForConnector(con.id);
        if (rStartFigure) {
            Log.info(":) WE HAVE A START FIGURE id = " + rStartFigure.id);
        }
        else {
            Log.info(":( WE DO NOT HAVE A START FIGURE");
        }

        //end point
        var rEndPoint = new Point(x, y);
        var rEndFigure = null;

        if (fCpOverId != -1) { //Are we over a ConnectionPoint from a Figure?
            var r_figureConnectionPoint = CONNECTOR_MANAGER.connectionPointGetById(fCpOverId);
            Log.info("End Figure's ConnectionPoint present id = " + fCpOverId);

            //As we found the connection point by a vicinity (so not exactly x,y match) we will adjust the end point too
            rEndPoint = r_figureConnectionPoint.point.clone();

            rEndFigure = STACK.figureGetById(r_figureConnectionPoint.parentId);
            Log.info(":) WE HAVE AN END FIGURE id = " + rEndFigure.id);
        } else if (fOverId != -1) {  //Are we, at least, over a Figure?
            Log.info("End Figure connected as automatic");

            rEndPoint = new Point(x, y);

            rEndFigure = STACK.figureGetById(fOverId);
            Log.info(":) WE HAVE AN END FIGURE id = " + rEndFigure.id);
        } else {
            Log.info(":( WE DO NOT HAVE AN END FIGURE ");
        }

        var rStartBounds = rStartFigure ? rStartFigure.getBounds() : null;
        var rEndBounds = rEndFigure ? rEndFigure.getBounds() : null;


        // if start point has automatic glue => connection has automatic start
        var automaticStart = rStartGlues.length > 0 && rStartGlues[0].automatic;

        // if end point is over a {Figure}'s {ConnectionPoint} => connection is not automatic
        // else if end point is over a {Figure} -> connection has automatic end
        //      else -> connection has no automatic end
        var automaticEnd = fCpOverId != -1 ? false : fOverId != -1;

        var xSpan = rStartPoint.x - rEndPoint.x;
        var ySpan = rStartPoint.y - rEndPoint.y;
        if (Math.abs(xSpan) > Math.abs(ySpan)) {
            rEndPoint.y = rStartPoint.y;
        } else { //竖线
            rEndPoint.x = rStartPoint.x;
        }

        var candidate = CONNECTOR_MANAGER.getClosestPointsOfConnection(
            automaticStart, //start automatic
            automaticEnd, //end automatic
            rStartFigure ? rStartFigure.id : -1, //start figure's id
            rStartPoint, //start figure's point
            rEndFigure ? rEndFigure.id : -1, //end figure's id
            rEndPoint //end figure's point
            );

        DIAGRAMO.debugSolutions = CONNECTOR_MANAGER.connector2Points(
            con.type,
            candidate[0], /*Start point*/
            candidate[1], /*End point*/
            rStartBounds,
            rEndBounds
        );

    }

    //end remove block

    //COLOR MANAGEMENT FOR {ConnectionPoint}
    //Find any {ConnectionPoint} from a figure at (x,y). Change FCP (figure connection points) color
    if (fCpOverId != -1 || fOverId != -1) { //Are we over a ConnectionPoint from a Figure or over a Figure?
        cps[1].color = ConnectionPoint.OVER_COLOR;
    } else {
        cps[1].color = ConnectionPoint.NORMAL_COLOR;
    }


    var firstConPoint = CONNECTOR_MANAGER.connectionPointGetFirstForConnector(selectedConnectorId);
    var secConPoint = CONNECTOR_MANAGER.connectionPointGetSecondForConnector(selectedConnectorId);
    //adjust connector
    Log.info("connectorPickSecond() -> Solution: " + DIAGRAMO.debugSolutions[0][2]);

    con.turningPoints = Point.cloneArray(DIAGRAMO.debugSolutions[0][2]);
    //CONNECTOR_MANAGER.connectionPointGetFirstForConnector(selectedConnectorId).point = con.turningPoints[0].clone();
    firstConPoint.point = con.turningPoints[0].clone();
    secConPoint.point = con.turningPoints[con.turningPoints.length - 1].clone();

    // MANAGE TEXT
    // update position of connector's text
    con.updateMiddleText();

    // before defining of {ConnectionPoint}'s position we reset currentCloud
    currentCloud = [];

    Log.groupEnd();
}


/**
*Alter the {Connector}  in real time
*@param {Number} connectionPointId - the id of the current dragged {ConnectionPoint} 
*@param {Number} x - the x position 
*@param {Number} y - the y position 
*@param {Event} ev - the event triggered
**/
function connectorMovePoint(connectionPointId, x, y, ev) {
    //current connector
    var con = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId);
    var cps = CONNECTOR_MANAGER.connectionPointGetAllByParent(con.id);

    // MANAGE TEXT
    // update position of connector's text
    con.updateMiddleText();

    //MANAGE COLOR
    //canvas.style.cursor = 'move';
    if (cps[0].id == selectedConnectionPointId) {
        cps[0].color = ConnectionPoint.OVER_COLOR;
    }
    else {
        cps[1].color = ConnectionPoint.NORMAL_COLOR;
    }

    /*Variables used in finding solution. As we only know the ConnectionPoint's id
    * (connectionPointId) and the location of event (x,y) we need to find
    * who is the start Figure, end Figure, starting Glue, ending Glue, etc*/
    var rStartPoint = con.turningPoints[0].clone();
    var rStartFigure = null; //starting figure (it can be null - as no Figure)
    var rEndPoint = con.turningPoints[con.turningPoints.length - 1].clone();
    var rEndFigure = null; //ending figure (it can be null - as no Figure)
    var rStartGlues = CONNECTOR_MANAGER.glueGetBySecondConnectionPointId(cps[0].id);
    var rEndGlues = CONNECTOR_MANAGER.glueGetBySecondConnectionPointId(cps[1].id);

    // before solution we reset currentCloud
    currentCloud = [];

    if (cps[0].id == connectionPointId) { //FIRST POINT
        rStartPoint = new Point(x, y);
        //end figure
        rEndFigure = STACK.figureGetAsSecondFigureForConnector(con.id);

        var rStartBounds = rStartFigure ? rStartFigure.getBounds() : null;
        var rEndBounds = rEndFigure ? rEndFigure.getBounds() : null;

        /** define connection type **/
        // if end point has automatic glue -> connection has automatic end
        var automaticEnd = rEndGlues.length && rEndGlues[0].automatic;

        // if start point is over figure's connection point -> connection has no automatic start
        // else if start point is over figure -> connection has automatic start
        //      else -> connection has no automatic start
        var automaticStart = false;

        var xSpan = rStartPoint.x - rEndPoint.x;
        var ySpan = rStartPoint.y - rEndPoint.y;
        if (Math.abs(xSpan) > Math.abs(ySpan)) {
            rStartPoint.y = rEndPoint.y;
        } else { //竖线
            rStartPoint.x = rEndPoint.x;
        }

        var candidate = CONNECTOR_MANAGER.getClosestPointsOfConnection(
            automaticStart, //start automatic
            automaticEnd, //end automatic
            rStartFigure ? rStartFigure.id : -1, //start figure's id
            rStartPoint, //start figure's point
            rEndFigure ? rEndFigure.id : -1, //end figure's id
            rEndPoint //end figure's point
        );

        //solutions
        DIAGRAMO.debugSolutions = CONNECTOR_MANAGER.connector2Points(con.type, candidate[0], candidate[1], rStartBounds, rEndBounds);


        //UPDATE CONNECTOR 
        var firstConPoint = CONNECTOR_MANAGER.connectionPointGetFirstForConnector(selectedConnectorId);
        var secondConPoint = CONNECTOR_MANAGER.connectionPointGetSecondForConnector(selectedConnectorId);
        //adjust connector
        Log.info("connectorMovePoint() -> Solution: " + DIAGRAMO.debugSolutions[0][2]);

        con.turningPoints = Point.cloneArray(DIAGRAMO.debugSolutions[0][2]);

        firstConPoint.point = con.turningPoints[0].clone();
        secondConPoint.point = con.turningPoints[con.turningPoints.length - 1].clone();
    }
    else if (cps[1].id == connectionPointId) { //SECOND POINT
        rEndPoint = new Point(x, y);
        //start figure
        rStartFigure = STACK.figureGetAsFirstFigureForConnector(con.id);

        var rStartBounds = rStartFigure ? rStartFigure.getBounds() : null;
        var rEndBounds = rEndFigure ? rEndFigure.getBounds() : null;

        /** define connection type **/
        // if start point has automatic glue -> connection has automatic start
        var automaticStart = rStartGlues.length && rStartGlues[0].automatic;

        // if end point is over figure's connection point -> connection has no automatic end
        // else if end point is over figure -> connection has automatic end
        //      else -> connection has no automatic end
        var automaticEnd = false;

        var xSpan = rStartPoint.x - rEndPoint.x;
        var ySpan = rStartPoint.y - rEndPoint.y;
        if (Math.abs(xSpan) > Math.abs(ySpan)) {
            rEndPoint.y = rStartPoint.y;
        } else { //竖线
            rEndPoint.x = rStartPoint.x;
        }

        var candidate = CONNECTOR_MANAGER.getClosestPointsOfConnection(
            automaticStart, //start automatic
            automaticEnd, //end automatic
            rStartFigure ? rStartFigure.id : -1, //start figure's id
            rStartPoint, //start figure's point
            rEndFigure ? rEndFigure.id : -1, //end figure's id
            rEndPoint //end figure point
        );

        //solutions
        DIAGRAMO.debugSolutions = CONNECTOR_MANAGER.connector2Points(con.type, candidate[0], candidate[1], rStartBounds, rEndBounds);


        //UPDATE CONNECTOR
        var firstConPoint = CONNECTOR_MANAGER.connectionPointGetFirstForConnector(selectedConnectorId);
        var secondConPoint = CONNECTOR_MANAGER.connectionPointGetSecondForConnector(selectedConnectorId);

        //adjust connector
        con.turningPoints = Point.cloneArray(DIAGRAMO.debugSolutions[0][2]);
        firstConPoint.point = con.turningPoints[0].clone();
        secondConPoint.point = con.turningPoints[con.turningPoints.length - 1].clone();
    } else {
        throw "main:connectorMovePoint() - this should never happen";
    }
    Log.groupEnd();
}


/** Creates a moving matrix taking into consideration the snapTo option
* The strange stuff is that Dia (http://projects.gnome.org/dia/) is using a top/left align
* but OpenOffice's Draw is using something similar to Diagramo
* @return {Matrix} - translation matrix
* @param {Object} fig - could be a figure, or a Connector
* @param {Number} x - mouse position
* @param {Number} y - mouse position
* @author Zack Newsham
* @author Alex Gheorghiu
*/
function generateMoveMatrix(fig, x, y) {
    //    Log.group("generateMoveMatrix");
    if (typeof x === 'undefined') {
        throw "Exception in generateMoveMatrix, x is undefined";
    }

    if (typeof y === 'undefined') {
        throw "Exception in generateMoveMatrix,  is undefined";
    }

    Log.info("main.js --> generateMoveMatrix x:" + x + ' y:' + y + ' lastMove=[' + lastMove + ']');
    var dx = x - lastMove[0];
    var dy = y - lastMove[1];
    //    Log.info("generateMoveMatrix() - delta  " + dx + ',  ' + dy);

    var moveMatrix = null;

    if (snapTo) { //snap effect
        moveMatrix = [
        [1, 0, 0],
        [0, 1, 0],
        [0, 0, 1]
        ];


        snapMonitor[0] += dx;
        snapMonitor[1] += dy;
        var jump = GRIDWIDTH / 2; //the figure will jump half of grid cell width

        //HORIZONTAL
        if (dx != 0) { //dragged to right

            /*Idea:
            *As you move the shape to right it might snap to next snap line  
            *regarding figure's start bounds (startNextGridX)
            *or snap to the snap line regarding figure's end bounds (endNextGridX)
            *We just need to see to which snap line will actually snap (the one that is closer)
            **/

            var startGridX = (Math.floor((fig.getBounds()[0] + snapMonitor[0]) / jump) + 1) * jump;
            var deltaStart = startGridX - fig.getBounds()[0];
            var endGridX = (Math.floor((fig.getBounds()[2] + snapMonitor[0]) / jump) + 1) * jump;
            var deltaEnd = endGridX - fig.getBounds()[2];

            if (deltaStart < deltaEnd) {
                if (fig.getBounds()[0] + snapMonitor[0] >= startGridX - SNAP_DISTANCE) {
                    moveMatrix[0][2] = deltaStart;
                    snapMonitor[0] -= deltaStart;
                }
                else if (fig.getBounds()[2] + snapMonitor[0] >= endGridX - SNAP_DISTANCE) {
                    moveMatrix[0][2] = deltaEnd;
                    snapMonitor[0] -= deltaEnd;
                }
            }
            else {
                if (fig.getBounds()[2] + snapMonitor[0] >= endGridX - SNAP_DISTANCE) {
                    moveMatrix[0][2] = deltaEnd;
                    snapMonitor[0] -= deltaEnd;
                }
                else if (fig.getBounds()[0] + snapMonitor[0] >= startGridX - SNAP_DISTANCE) {
                    moveMatrix[0][2] = deltaStart;
                    snapMonitor[0] -= deltaStart;
                }
            }
        }


        //VERTICAL
        if (dy != 0) { //dragged to bottom
            var upperGridY = (Math.floor((fig.getBounds()[1] + snapMonitor[1]) / jump) + 1) * jump;
            var deltaUpper = upperGridY - fig.getBounds()[1];
            var lowerGridY = (Math.floor((fig.getBounds()[3] + snapMonitor[1]) / jump) + 1) * jump;
            var deltaLower = lowerGridY - fig.getBounds()[3];

            if (deltaUpper < deltaLower) {
                if (fig.getBounds()[1] + snapMonitor[1] >= upperGridY - SNAP_DISTANCE) {
                    moveMatrix[1][2] = deltaUpper;
                    snapMonitor[1] -= deltaUpper;
                }
                else if (fig.getBounds()[3] + snapMonitor[1] >= lowerGridY - SNAP_DISTANCE) {
                    moveMatrix[1][2] = deltaLower;
                    snapMonitor[1] -= deltaLower;
                }
            }
            else {
                if (fig.getBounds()[3] + snapMonitor[1] >= lowerGridY - SNAP_DISTANCE) {
                    moveMatrix[1][2] = deltaLower;
                    snapMonitor[1] -= deltaLower;
                }
                else if (fig.getBounds()[1] + snapMonitor[1] >= upperGridY - SNAP_DISTANCE) {
                    moveMatrix[1][2] = deltaUpper;
                    snapMonitor[1] -= deltaUpper;
                }
            }
        }
    } else { //normal move
        moveMatrix = [
        [1, 0, dx],
        [0, 1, dy],
        [0, 0, 1]
        ];
    }
    Log.groupEnd();
    return moveMatrix;
}


/**Computes the bounds of the canvas
**@return {Array} of {Integer} - [xStart, yStart, xEnd, yEnd]
**/
function getCanvasBounds() {
    var canvasMinX = $("#a").offset().left;
    var canvasMaxX = canvasMinX + $("#a").width();

    var canvasMinY = $("#a").offset().top;
    var canvasMaxY = canvasMinY + $("#a").height();

    return [canvasMinX, canvasMinY, canvasMaxX, canvasMaxY];
}

/**Computes the (x,y) coordinates of an event in page
*@param {Event} ev - the event
**/
function getBodyXY(ev) {
    return [ev.pageX, ev.pageY]; //TODO: add scroll
}


/**
*Extracts the X and Y from an event (for canvas)
*@param {Event} ev - the event
*@return {Array} of {Integer} - or null if event not inside the canvas
**/
function getCanvasXY(ev) {
    var position = null;
    var canvasBounds = getCanvasBounds();
    //    Log.group("main.js->getCanvasXY()");
    Log.debug("Canvas bounds: [" + canvasBounds + ']');

    var tempPageX = null;
    var tempPageY = null;

    if (ev.touches) { //iPad 
        if (ev.touches.length > 0) {
            tempPageX = ev.touches[0].pageX;
            tempPageY = ev.touches[0].pageY;
        }
    }
    else { //normal Desktop
        tempPageX = ev.pageX; //Retrieves the x-coordinate of the mouse pointer relative to the top-left corner of the document.
        tempPageY = ev.pageY; //Retrieves the y-coordinate of the mouse pointer relative to the top-left corner of the document.          
        Log.debug("ev.pageX:" + ev.pageX + " ev.pageY:" + ev.pageY);
    }

    if (canvasBounds[0] <= tempPageX && tempPageX <= canvasBounds[2]
        && canvasBounds[1] <= tempPageY && tempPageY <= canvasBounds[3]) {
        //        Log.info('Inside canvas');
        position = [tempPageX - $("#a").offset().left, tempPageY - $("#a").offset().top];
        //alert("getXT : " + position);
    }
    //    Log.groupEnd();

    return position;
}

/**Buffered image for background of canvas
* It is set to null in CanvasProps::sync() method*/
var backgroundImage = null;

/**Adds a background to a canvas
* @param {HTMLCanvasElement} canvasElement the <canvas> DOM element
* */
function addBackground(canvasElement) {
    Log.info("addBackground: called");

    var ctx = canvasElement.getContext('2d');

    // set background image if backgroundImage is null
    if (!backgroundImage) {

        // fill canvas with fill color
        ctx.rect(0, 0, canvasElement.width, canvasElement.height);
        ctx.fillStyle = canvasProps.getFillColor();
        ctx.fill();

        // draw grid if it's visible
        if (gridVisible) {
            var columns = Math.floor(canvasElement.width / GRIDWIDTH) + 1;
            //console.info("Columns: " + columns );

            var rows = Math.floor(canvasElement.height / GRIDWIDTH) + 1;
            //console.info("Rows: " + rows );

            for (var i = 0; i < rows; i++) {
                for (var j = 0; j < columns; j++) {
                    ctx.beginPath();
                    ctx.strokeStyle = '#C0C0C0';

                    //big cross
                    ctx.moveTo(j * GRIDWIDTH - 1, i * GRIDWIDTH);
                    ctx.lineTo(j * GRIDWIDTH + 1, i * GRIDWIDTH);

                    ctx.moveTo(j * GRIDWIDTH, i * GRIDWIDTH - 1);
                    ctx.lineTo(j * GRIDWIDTH, i * GRIDWIDTH + 1);


                    //small dot
                    //ctx.moveTo(j * GRIDWIDTH + GRIDWIDTH/2 - 1, i * GRIDWIDTH +  GRIDWIDTH/2);
                    //ctx.lineTo(j * GRIDWIDTH + GRIDWIDTH/2 + 1, i * GRIDWIDTH +  GRIDWIDTH/2);

                    ctx.stroke();
                }
            }
        }

        // set new buffered background image
        backgroundImage = new Image();
        backgroundImage.src = canvasElement.toDataURL();

    } else {
        //	backgroundImage.onload = function(e){
        //		ctx.drawImage(this, 0, 0);
        //	} //end onload

        // draw buffered background image on canvas
        ctx.drawImage(backgroundImage, 0, 0);
    }

} //end function



/**Cleans up the canvas. It also add a white background to avoid "transparent"
*background issue in PNG
*@param {HTMLCanvasElement} canvas - the canvas to reset
*@author Alex
**/
function reset(canvas) {
    var ctx = canvas.getContext('2d');
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = '#FFFFFF';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
}


/**Draws all the stuff on the canvas*/
function draw() {

    var ctx = getContext();

    //    Log.group("A draw started");
    //alert('Paint 1')
    reset(getCanvas());

    //if grid visible paint it
    //	if(gridVisible){ //paint grid
    addBackground(getCanvas());
    //	}

    //alert('Paint 2')
    STACK.paint(ctx);

    minimap.updateMinimap();
    //    Log.groupEnd();
    //alert('Paint 3')
}


/**
*Returns the canvas data but without the selections and grid.
*@return {DOMString} - the result of a toDataURL() call on the temporary canvas
*@author Alex
*@author Artyom
**/
function renderedCanvas() {
    var canvas = getCanvas();

    //render the canvas without the selection and stuff
    var tempCanvas = document.getElementById('tempCanvas');
    if (tempCanvas === null) {
        tempCanvas = document.createElement('canvas');
        tempCanvas.setAttribute('id', 'tempCanvas');
        tempCanvas.style.display = 'none';

        //it seems that there is no need to actually add it to the dom tree to be able to render (tested: IE9, FF9, Chrome 19)
        //canvas.parentNode.appendChild(tempCanvas);
    }

    //adjust temp canvas size to main canvas (as it migh have been changed)
    tempCanvas.setAttribute('width', canvas.width);
    tempCanvas.setAttribute('height', canvas.height);
    reset(tempCanvas);
    addBackground(tempCanvas);
    STACK.paint(tempCanvas.getContext('2d'), true);
    //end render

    return tempCanvas.toDataURL();
}

/*Returns a text containing all the URL in a diagram */
function linkMap() {
    var csvBounds = '';
    var first = true;
    for (f in STACK.figures) {
        var figure = STACK.figures[f];
        if (figure.url != '') {
            var bounds = figure.getBounds();
            if (first) {
                first = false;
            }
            else {
                csvBounds += "\n";
            }

            csvBounds += bounds[0] + ',' + bounds[1] + ',' + bounds[2] + ',' + bounds[3] + ',' + figure.url;
        }
    }
    Log.info("editor.php->linkMap()->csv bounds: " + csvBounds);

    return csvBounds;
}

/** Save current diagram
* Save can be triggered in 3 cases:
*  1 - from menu
*  2 - from quick toolbar
*  3 - from shortcut Ctrl-S (onKeyDown)
**/
function Save(showInfo) {

    if (state == STATE_TEXT_EDITING) {
        currentTextEditor.destroy();
        currentTextEditor = null;
        state = STATE_NONE;
    }

    var dataURL = null;
    try {
        dataURL = renderedCanvas();
    }
    catch (e) {
        if (e.name === 'SecurityError' && e.code === 18) {
            /*This is usually happening as we load an image from another host than the one Diagramo is hosted*/
            alert("A figure contains an image loaded from another host. \
                \n\nHint: \
                \nPlease make sure that the browser's URL (current location) is the same as the one saved in the DB.");
        }
    }

    var diagram = { c: canvasProps, s: STACK, m: CONNECTOR_MANAGER, p: CONTAINER_MANAGER, v: DIAGRAMO.fileVersion };
    //var serializedDiagram = JSON.stringify(diagram,  Util.operaReplacer);
    var serializedDiagram = JSON.stringify(diagram);

    //var svgDiagram = toSVG();
    //save the URLs of figures as a CSV 
    var lMap = linkMap();

    var handler = new HttpHandler("BP.WF.HttpHandler.WF_Admin_CCFormDesigner");
    handler.AddPara("diagram", serializedDiagram);
    handler.AddPara("png", dataURL);
    handler.AddPara("linkMap", lMap);
    handler.AddPara("svg", "");
    handler.AddPara("FK_MapData", CCForm_FK_MapData);
    var data = handler.DoMethodReturnString("SaveForm");
    //    $.post( Handler, {
    //        action: 'SaveForm',
    //        diagram: serializedDiagram,
    //        png: dataURL,
    //        linkMap: lMap,
    //        svg: "",
    //        FK_MapData: CCForm_FK_MapData
    //    }, function (data) {

    if (data.indexOf('err@') != -1) {
        alert(data);
        return;
    }

    Designer_ShowMsg(data);

    //    });
}

/** Print current diagram
* Print can be triggered in 3 cases only after diagram was saved:
*  1 - from menu
*  2 - from quick toolbar
*  3 - from Ctrl + P shortcut
*
*  Copy link to saved diagram's png file to src of image,
*  add it to iframe and call print of last.
*
*  @author Artyom Pokatilov <artyom.pokatilov@gmail.com>
**/
function print_diagram() {
    var printFrameId = "printFrame";

    var iframe = document.getElementById(printFrameId);

    // if iframe isn't created
    if (iframe == null) {
        iframe = document.createElement("IFRAME");
        iframe.id = printFrameId;

        document.body.appendChild(iframe);
    }

    // get DOM of iframe
    var frameDoc = iframe.contentDocument;

    var diagramImages = frameDoc.getElementsByTagName('img');
    var diagramImage;
    if (diagramImages.length > 0) {     // if image is already added
        diagramImage = diagramImages[0];

        // set source of image to png of saved diagram
        diagramImage.setAttribute('src', "data/diagrams/" + currentDiagramId + ".png");

    } else {                        // if image isn't created yet
        diagramImage = frameDoc.createElement('img');

        // set source of image to png of saved diagram
        diagramImage.setAttribute('src', "data/diagrams/" + currentDiagramId + ".png");

        if (frameDoc.body !== null) {
            frameDoc.body.appendChild(diagramImage);
        } else {
            // IE case for more details
            // @see http://stackoverflow.com/questions/8298320/correct-access-of-dynamic-iframe-in-ie
            // create body of iframe
            frameDoc.src = "javascript:'<body></body>'";
            // append image through html of <img>
            frameDoc.write(diagramImage.outerHTML);
            frameDoc.close();
        }
    }

    // adjust iframe size to main canvas (as it might have been changed)
    iframe.setAttribute('width', canvasProps.getWidth());
    iframe.setAttribute('height', canvasProps.getHeight());

    // print iframe
    iframe.contentWindow.print();
}

/**Exports current canvas as SVG*/
function exportCanvas() {
    //export canvas as SVG
    var v = '<svg width="300" height="200" xmlns="http://www.w3.org/2000/svg" version="1.1">\
            <rect x="0" y="0" height="200" width="300" style="stroke:#000000; fill: #FFFFFF"/>\
                    <path d="M100,100 C200,200 100,50 300,100" style="stroke:#FFAAFF;fill:none;stroke-width:3;"  />\
                    <rect x="50" y="50" height="50" width="50"\
                      style="stroke:#ff0000; fill: #ccccdf" />\
            </svg>';



    //get svg
    var canvas = getCanvas();

    var v2 = '<svg width="' + canvas.width + '" height="' + canvas.height + '" xmlns="http://www.w3.org/2000/svg" version="1.1">';
    v2 += STACK.toSVG();
    v2 += CONNECTOR_MANAGER.toSVG();
    v2 += '</svg>';
    alert(v2);

    //save SVG into session
    //see: http://api.jquery.com/jQuery.post/
    $.post("../common/controller.php", { action: 'saveSvg', svg: escape(v2) },
        function (data) {
            if (data == 'svg_ok') {
                //alert('SVG was save into session');
            }
            else if (data == 'svg_failed') {
                Log.info('SVG was NOT save into session');
            }
        }
    );

    //open a new window that will display the SVG
    window.open('./svg.php', 'SVG', 'left=20,top=20,width=500,height=500,toolbar=1,resizable=0');
}

/**Loads a saved diagram
*@param {String} tempDiagramName - the name of temporary diagram
**/
function LoadTempDiagram(frmID) {
    //将v1版本表单元素转换为v2 杨玉慧  silverlight 自由表单转化为H5表单
    Conver_CCForm_V1ToV2();
    /*
    $.post(Handler, { action: 'FormDesigner_Loadform', FK_MapData: frmID },

    function (data) {

    if (data.indexOf('err@') != -1) {
    alert(data);
    return;
    }

    try {

    // 装载表单入口.
    if (data == "" || data == "" || data == null) {

                    
    return;
    }

    action(data);

    var obj = eval('(' + data + ')');

    //alert(obj);
    //console.log(obj);

    if (!('v' in obj) || obj.v != DIAGRAMO.fileVersion) {
    Importer.importDiagram(obj); //import 1st version of Diagramo files
    }

    STACK = Stack.load(obj['s']);
    canvasProps = CanvasProps.load(obj['c']);
    canvasProps.sync();
    setUpEditPanel(canvasProps);

    CONNECTOR_MANAGER = ConnectorManager.load(obj['m']);
    CONTAINER_MANAGER = ContainerFigureManager.load(obj['p']);

    draw();

    //alert("loaded");
    } catch (error) {
    alert("main.js:load() 装载表单异常 Exception: " + error);
    }
    }
    );
    */
}


/**Saves a diagram. Actually send the serialized version of diagram
*for saving
**/
function saveAs() {
    var dataURL = renderedCanvas();

    //                var $diagram = {c:canvas.save(), s:STACK, m:CONNECTOR_MANAGER};
    var $diagram = { c: canvasProps, s: STACK, m: CONNECTOR_MANAGER, p: CONTAINER_MANAGER, v: DIAGRAMO.fileVersion };
    var $serializedDiagram = JSON.stringify($diagram);
    //   $serializedDiagram = JSON.stringify($diagram,  Util.operaReplacer);
    var svgDiagram = toSVG();

    //save the URLs of figures as a CSV 
    var lMap = linkMap();

    //alert($serializedDiagram);

    //see: http://api.jquery.com/jQuery.post/
    $.post("./common/controller.php", { action: 'saveAs', diagram: $serializedDiagram, png: dataURL, linkMap: lMap, svg: svgDiagram },
       function (data) {
           if (data == 'noaccount') {
               Log.info('You must have an account to use that feature');
               //window.location = '../register.php';
           }
           else if (data == 'step1Ok') {
               Log.info('Save as...');
               window.location = './saveDiagram.php';
           }
       }
   );
}


/**Add listeners to elements on the page*/
// TODO: set dblclick handler for mobile (touches)
function addListeners() {
    var canvas = getCanvas();

    //add event handlers for Document
    document.addEventListener("keypress", onKeyPress, false);
    document.addEventListener("keydown", onKeyDown, false);
    document.addEventListener("keyup", onKeyUp, false);
    document.addEventListener("selectstart", stopselection, false);

    //add event handlers for Canvas
    canvas.addEventListener("mousemove", onMouseMove, false);
    canvas.addEventListener("mousedown", onMouseDown, false);
    canvas.addEventListener("mouseup", onMouseUp, false);
    canvas.addEventListener("dblclick", onDblClick, false);


    if (false) {
        //add listeners for iPad/iPhone
        //As this was only an experiment (for now) it is not well supported nor optimized
        ontouchstart = "touchStart(event);"
        ontouchmove = "touchMove(event);"
        ontouchend = "touchEnd(event);"
        ontouchcancel = "touchCancel(event);"
    }

}

/**Minimap section*/
var minimap; //stores a refence to minimap object (see minimap.js)

//TODO: remove reference to JQuery and add a normal listener (for "onmouseup" event)
$(document).mouseup(
    function () {
        minimap.selected = false;
    }
);

//TODO: convert to a normal listener (for "onresize" event)
window.onresize = function () {
    minimap.initMinimap()
};

var currentDiagramId = null;

/**Initialize the page
* @param {Integer} diagramId (optional) the diagram Id to load
* */
function Init_Panel(diagramId) {

    var canvas = getCanvas();

    minimap = new Minimap(canvas, document.getElementById("minimap"), 115);

    minimap.updateMinimap();

    //Canvas properties (width and height)
    if (canvasProps == null) {//only create a new one if we have not already loaded one
        canvasProps = new CanvasProps(CanvasProps.DEFAULT_WIDTH, CanvasProps.DEFAULT_HEIGHT, CanvasProps.DEFAULT_FILL_COLOR);
    }
    //lets make sure that our canvas is set to the correct values
    canvasProps.setWidth(canvasProps.getWidth());
    //  canvasProps.setHeight(canvasProps.getHeight());

    // alert( canvasProps.getHeight() );

    canvasProps.setHeight(2000);


    //Browser support and warnings
    if (isBrowserReady() == 0) { //no support at all
        modal();
    }

    //Edit panel
    setUpEditPanel(canvasProps);

    // loads diagram
    LoadTempDiagram(diagramId);

    // close layer when click-out
    addListeners();

    window.addEventListener("mousedown", documentOnMouseDown, false);
    window.addEventListener("mousemove", documentOnMouseMove, false);
    window.addEventListener("mouseup", documentOnMouseUp, false);
}

/**Flag to inform if to drew or not the diagram. Similar to "Dirty pattern" */
var redraw = false;

/**
*Dispatch actions. Detect the action needed and trigger it.
*@param {String} action - the action name
**/
function action(action) {
    redraw = false;
    switch (action) {

        case 'undo':
            Log.info("main.js->action()->Undo. Nr of actions in the STACK: " + History.COMMANDS.length);
            History.undo();
            redraw = true;
            break;

        //        case 'redo':     
        //            Log.info("main.js->action()->Redo. Nr of actions in the STACK: " + History.COMMANDS.length);     
        //            History.redo();     
        //            redraw = true;     
        //            break;     

        case 'group':
            /*After we pressed Ctrl-G any temporary group will became permanent*/
            if (selectedGroupId != -1) {
                var group = STACK.groupGetById(selectedGroupId);
                if (!group.permanent) { //group only temporary groups
                    var cmdGroup = new GroupCreateCommand(selectedGroupId);
                    cmdGroup.execute();
                    History.addUndo(cmdGroup);
                    Log.info("main.js->action()->Group. New group made permanent. Group id = " + selectedGroupId);
                }
                else {
                    Log.info("main.js->action()->Group. Group ALREADY permanent.  Group id = " + selectedGroupId);
                }

            }
            redraw = true;
            break;

        case 'container':
            Log.info("main.js->action()->container. Nr of actions in the STACK: " + History.COMMANDS.length);

            if (state == STATE_TEXT_EDITING) {
                currentTextEditor.destroy();
                currentTextEditor = null;
            }

            //creates a container
            var cmdContainerCreate = new ContainerCreateCommand(300, 300);
            cmdContainerCreate.execute();
            History.addUndo(cmdContainerCreate);

            redraw = true;

            break;

        case 'insertImage':
            Log.info("main.js->action()->insertImage. Nr of actions in the STACK: " + History.COMMANDS.length);

            if (state == STATE_TEXT_EDITING) {
                currentTextEditor.destroy();
                currentTextEditor = null;
            }

            //creates a container
            var cmdFigureCreate = new InsertedImageFigureCreateCommand(insertedImageFileName, 100, 100);
            cmdFigureCreate.execute();
            History.addUndo(cmdFigureCreate);

            redraw = true;

            break;

        case 'ungroup':
            if (selectedGroupId != -1) {
                var group = STACK.groupGetById(selectedGroupId);
                if (group.permanent) { //split only permanent groups
                    var cmdUngroup = new GroupDestroyCommand(selectedGroupId);
                    cmdUngroup.execute();
                    History.addUndo(cmdUngroup);
                    Log.info("main.js->action()->Ungroup. New group made permanent. Group id = " + selectedGroupId);
                }
                else {
                    Log.info("main.js->action()->Ungroup. Ignore. Group is not permanent.  Group id = " + selectedGroupId);
                }

                redraw = true;
            }
            break;

        case 'connector-jagged':
            if (state == STATE_TEXT_EDITING) {
                currentTextEditor.destroy();
                currentTextEditor = null;
            }
            selectedFigureId = -1;
            state = STATE_CONNECTOR_PICK_FIRST;
            connectorType = Connector.TYPE_JAGGED;
            redraw = true;
            break;

        case 'connector-straight':
            if (state == STATE_TEXT_EDITING) {
                currentTextEditor.destroy();
                currentTextEditor = null;
            }
            selectedFigureId = -1;
            state = STATE_CONNECTOR_PICK_FIRST;
            connectorType = Connector.TYPE_STRAIGHT;
            redraw = true;
            break;

        case 'connector-organic':
            if (state == STATE_TEXT_EDITING) {
                currentTextEditor.destroy();
                currentTextEditor = null;
            }
            selectedFigureId = -1;
            state = STATE_CONNECTOR_PICK_FIRST;
            connectorType = Connector.TYPE_ORGANIC;
            redraw = true;
            break;

        case 'rotate90':
        case 'rotate90A':
            if (selectedFigureId) {
                //alert("Selected figure index: " + STACK.figureSelectedIndex);
                var figure = STACK.figureGetById(selectedFigureId);
                var bounds = figure.getBounds();
                var dx = bounds[0] + (bounds[2] - bounds[0]) / 2
                var dy = bounds[1] + (bounds[3] - bounds[1]) / 2
                //alert(dx + '  ' + dy);
                //alert("Selected figure is: " + figure);
                var TRANSLATE = [
                [1, 0, dx * -1],
                [0, 1, dy * -1],
                [0, 0, 1]
                ]
                /*
                var dx = bounds[0] + (bounds[3] - bounds[1]) / 2
                var dy = bounds[1] + (bounds[2] - bounds[0]) / 2
                */
                //TODO: figure out why we have to -1 off the dx, we have to do this regardless of rotation angle
                var TRANSLATEBACK = [
                [1, 0, dx],
                [0, 1, dy],
                [0, 0, 1]
                ]
                figure.transform(TRANSLATE);
                if (action == "rotate90") {
                    figure.transform(R90);
                }
                else {
                    figure.transform(R90A);
                }
                figure.transform(TRANSLATEBACK);

                redraw = true;
            }
            break;

        case 'up': //decrease Y                                   
            switch (state) {
                case STATE_FIGURE_SELECTED:
                    var cmdFigUp = new FigureTranslateCommand(selectedFigureId, Matrix.UP);
                    History.addUndo(cmdFigUp);
                    cmdFigUp.execute();
                    redraw = true;
                    break;

                case STATE_GROUP_SELECTED:
                    var cmdGrpUp = new GroupTranslateCommand(selectedGroupId, Matrix.UP);
                    History.addUndo(cmdGrpUp);
                    cmdGrpUp.execute();
                    redraw = true;
                    break;

                case STATE_CONTAINER_SELECTED:
                    var cmdContUp = new ContainerTranslateCommand(selectedContainerId, Matrix.UP);
                    History.addUndo(cmdContUp);
                    cmdContUp.execute();
                    redraw = true;
                    break;
                case STATE_CONNECTOR_SELECTED:
                    var con = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId);
                    con.transform(Matrix.UP);
                    redraw = true;
                    break;
            }

            break;

        case 'down': //increase Y
            switch (state) {
                case STATE_FIGURE_SELECTED:
                    var cmdFigDown = new FigureTranslateCommand(selectedFigureId, Matrix.DOWN);
                    History.addUndo(cmdFigDown);
                    cmdFigDown.execute();
                    redraw = true;
                    break;

                case STATE_GROUP_SELECTED:
                    var cmdGrpDown = new GroupTranslateCommand(selectedGroupId, Matrix.DOWN);
                    History.addUndo(cmdGrpDown);
                    cmdGrpDown.execute();
                    redraw = true;
                    break;

                case STATE_CONTAINER_SELECTED:
                    var cmdContDown = new ContainerTranslateCommand(selectedContainerId, Matrix.DOWN);
                    History.addUndo(cmdContDown);
                    cmdContDown.execute();
                    redraw = true;
                    break;
                case STATE_CONNECTOR_SELECTED:
                    var con = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId);
                    con.transform(Matrix.DOWN);
                    redraw = true;
                    break;
            }
            break;

        case 'right': //increase X
            switch (state) {
                case STATE_FIGURE_SELECTED:
                    var cmdFigRight = new FigureTranslateCommand(selectedFigureId, Matrix.RIGHT);
                    History.addUndo(cmdFigRight);
                    cmdFigRight.execute();
                    redraw = true;
                    break;

                case STATE_GROUP_SELECTED:
                    var cmdGrpRight = new GroupTranslateCommand(selectedGroupId, Matrix.RIGHT);
                    History.addUndo(cmdGrpRight);
                    cmdGrpRight.execute();
                    redraw = true;
                    break;

                case STATE_CONTAINER_SELECTED:
                    var cmdContRight = new ContainerTranslateCommand(selectedContainerId, Matrix.RIGHT);
                    History.addUndo(cmdContRight);
                    cmdContRight.execute();
                    redraw = true;
                    break;
                case STATE_CONNECTOR_SELECTED:
                    var con = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId);
                    con.transform(Matrix.RIGHT);
                    redraw = true;
                    break;
            }
            break;

        case 'left': //decrease X
            switch (state) {
                case STATE_FIGURE_SELECTED:
                    var cmdFigLeft = new FigureTranslateCommand(selectedFigureId, Matrix.LEFT);
                    History.addUndo(cmdFigLeft);
                    cmdFigLeft.execute();
                    redraw = true;
                    break;

                case STATE_GROUP_SELECTED:
                    var cmdGrpLeft = new GroupTranslateCommand(selectedGroupId, Matrix.LEFT);
                    History.addUndo(cmdGrpLeft);
                    cmdGrpLeft.execute();
                    redraw = true;
                    break;

                case STATE_CONTAINER_SELECTED:
                    var cmdContLeft = new ContainerTranslateCommand(selectedContainerId, Matrix.LEFT);
                    History.addUndo(cmdContLeft);
                    cmdContLeft.execute();
                    redraw = true;
                    break;
                case STATE_CONNECTOR_SELECTED:
                    var con = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId);
                    con.transform(Matrix.LEFT);
                    redraw = true;
                    break;
            }
            break;

        case 'grow':
            if (selectedFigureId != -1) {
                //alert("Selected figure index: " + STACK.figureSelectedIndex);
                var figure = STACK.figureGetById(selectedFigureId);
                var bounds = figure.getBounds();
                var dx = bounds[0] + (bounds[2] - bounds[0]) / 2
                var dy = bounds[1] + (bounds[3] - bounds[1]) / 2
                //alert(dx + '  ' + dy);
                //alert("Selected figure is: " + figure);
                var TRANSLATE = [
                [1, 0, dx * -1],
                [0, 1, dy * -1],
                [0, 0, 1]
                ]
                /*
                var dx = bounds[0] + (bounds[3] - bounds[1]) / 2
                var dy = bounds[1] + (bounds[2] - bounds[0]) / 2
                */
                var TRANSLATEBACK = [
                [1, 0, dx],
                [0, 1, dy],
                [0, 0, 1]
                ]

                var GROW = [
                [1 + 0.2, 0, 0],
                [0, 1 + 0.2, 0],
                [0, 0, 1]
                ]
                figure.transform(TRANSLATE);
                figure.transform(GROW);
                figure.transform(TRANSLATEBACK);
                redraw = true;
            }
            break;

        case 'shrink':
            if (selectedFigureId != -1) {
                //alert("Selected figure index: " + STACK.figureSelectedIndex);
                var figure = STACK.figureGetById(selectedFigureId);
                var bounds = figure.getBounds();
                var dx = bounds[0] + (bounds[2] - bounds[0]) / 2
                var dy = bounds[1] + (bounds[3] - bounds[1]) / 2
                //alert(dx + '  ' + dy);
                //alert("Selected figure is: " + figure);
                var TRANSLATE = [
                [1, 0, dx * -1],
                [0, 1, dy * -1],
                [0, 0, 1]
                ]
                /*
                var dx = bounds[0] + (bounds[3] - bounds[1]) / 2
                var dy = bounds[1] + (bounds[2] - bounds[0]) / 2
                */
                var TRANSLATEBACK = [
                [1, 0, dx],
                [0, 1, dy],
                [0, 0, 1]
                ]
                var SHRINK = [
                [1 - 0.2, 0, 0],
                [0, 1 - 0.2, 0],
                [0, 0, 1]
                ]
                figure.transform(TRANSLATE);
                figure.transform(SHRINK);
                figure.transform(TRANSLATEBACK);
                redraw = true;
            }
            break;

        case 'duplicate':
            //TODO: From Janis: Connectors are not cloned

            if (selectedFigureId != -1) { //duplicate one figure
                var cmdDupl = new FigureCloneCommand(selectedFigureId);
                cmdDupl.execute();
                History.addUndo(cmdDupl);
            }

            if (selectedGroupId != -1) { //copies a group or multiple figures
                var cmdDupl = new GroupCloneCommand(selectedGroupId);
                cmdDupl.execute();
                History.addUndo(cmdDupl);
            }

            if (selectedConnectorId != -1) {
                var cmdConn = new ConnectorCloneCommand(selectedConnectorId);
                cmdConn.execute();
                History.addUndo(cmdConn);
            }
            getCanvas().style.cursor = "default";
            redraw = true;
            break;

        case 'back':
            if (selectedFigureId != -1) {
                var cmdBack = new FigureZOrderCommand(selectedFigureId, 0);
                cmdBack.execute();
                History.addUndo(cmdBack);
                //STACK.setPosition(selectedFigureId, 0);
                redraw = true;
            }
            break;

        case 'front':
            if (selectedFigureId != -1) {
                var cmdFront = new FigureZOrderCommand(selectedFigureId, STACK.figures.length - 1);
                cmdFront.execute();
                History.addUndo(cmdFront);
                redraw = true;
            }
            break;

        case 'moveback':
            if (selectedFigureId != -1) {
                var cmdMoveBack = new FigureZOrderCommand(selectedFigureId, STACK.idToIndex[selectedFigureId] - 1);
                cmdMoveBack.execute();
                History.addUndo(cmdMoveBack);
                redraw = true;
            }
            break;

        case 'moveforward':
            if (selectedFigureId != -1) {
                var cmdMoveForward = new FigureZOrderCommand(selectedFigureId, STACK.idToIndex[selectedFigureId] + 1);
                cmdMoveForward.execute();
                History.addUndo(cmdMoveForward);
                redraw = true;
            }
            break;

    } //end switch

    if (redraw) {
        draw();
    }
}

/**Stores last mouse position. Null initially.*/
var lastMousePosition = null;

function documentOnMouseDown(evt) {
    //Log.info("documentOnMouseDown");
    //evt.preventDefault();
}

var draggingFigure = null;
function documentOnMouseMove(evt) {
    //Log.info("documentOnMouseMove");

    switch (state) {
        case STATE_FIGURE_CREATE:
            //Log.info("documentOnMouseMove: trying to draw the D'n'D figure");

            if (!draggingFigure) {
                draggingFigure = document.createElement('img');
                draggingFigure.setAttribute('id', 'draggingThumb');
                draggingFigure.style.position = 'absolute';
                draggingFigure.style.zIndex = 3;  // set it in front of editor
                body.appendChild(draggingFigure);
            }


            //Log.info("editor.php>documentOnMouseMove>STATE_FIGURE_CREATE: selectedFigureThumb=" + selectedFigureThumb);
            draggingFigure.setAttribute('src', selectedFigureThumb);
            draggingFigure.style.width = '100px';
            draggingFigure.style.height = '25px';
            draggingFigure.style.left = (evt.pageX - 20) + 'px';
            draggingFigure.style.top = (evt.pageY - 15) + 'px';
            //draggingFigure.style.backgroundColor  = 'red';
            draggingFigure.style.display = 'block';

            draggingFigure.addEventListener('mousedown', function (event) {
                //Log.info("documentOnMouseMove: How stupid. Mouse down on dragging figure");
            }, false);

            draggingFigure.addEventListener('mouseup', function (ev) {
                var coords = getCanvasXY(ev);

                if (coords == null) {
                    return;
                }

                var x = coords[0];
                var y = coords[1];
                switch (state) {
                    case STATE_FIGURE_CREATE:
                        Log.info("draggingFigure>onMouseUp() + STATE_FIGURE_CREATE");

                        snapMonitor = [0, 0];

                        //treat new figure
                        //do we need to create a figure on the canvas?
                        if (window.createFigureFunction) {
                            //Log.info("draggingFigure>onMouseUp() + STATE_FIGURE_CREATE--> new state STATE_FIGURE_SELECTED + createFigureFunction = " + window.createFigureFunction);

                            var cmdCreateFig = new FigureCreateCommand(window.createFigureFunction, x, y);
                            cmdCreateFig.execute();
                            History.addUndo(cmdCreateFig);

                            //HTMLCanvas.style.cursor = 'default';

                            selectedConnectorId = -1;
                            createFigureFunction = null;
                            mousePressed = false;
                            redraw = true;

                            draw();

                            //TODO: a way around to hide this dragging DIV
                            document.getElementById('draggingThumb').style.display = 'none';

                            //TODO: the horror 
                            //body.removeChild(document.getElementById('draggingThumb'));

                        }
                        else {
                            Log.info("draggingFigure>onMouseUp() + STATE_FIGURE_CREATE--> but no 'createFigureFunction'");
                        }
                        break;
                }

                //stop canvas from gettting this event
                evt.stopPropagation();
            }, false);
            break;

        case STATE_NONE:
            //document.removeChild(document.getElementById('draggingThumb'));
            break;
    }
}


function documentOnMouseUp(evt) {
    Log.info("documentOnMouseUp");

    switch (state) {
        case STATE_FIGURE_CREATE:
            var eClicked = document.elementFromPoint(evt.clientX, evt.clientY);
            if (eClicked.id != 'a') {
                if (draggingFigure) {
                    //draggingFigure.style.display  = 'none';
                    draggingFigure.parentNode.removeChild(draggingFigure);
                    state = STATE_NONE;
                    draggingFigure = null;
                    //evt.stopPropagation();
                }
            }
            break;
    }
}


/*Returns a text containing all the URL in a diagram */
function linkMap() {
    var csvBounds = '';
    var first = true;
    for (var f in STACK.figures) {
        var figure = STACK.figures[f];
        if (figure.url != '') {
            var bounds = figure.getBounds();
            if (first) {
                first = false;
            }
            else {
                csvBounds += "\n";
            }

            csvBounds += bounds[0] + ',' + bounds[1] + ',' + bounds[2] + ',' + bounds[3] + ',' + figure.url;
        }
    }
    Log.info("editor.php->linkMap()->csv bounds: " + csvBounds);

    return csvBounds;
}
/*======================APPLE=====================================*/
/**Triggered when an touch is initiated (iPad/iPhone).
*Simply forward to onMouseDown
*@param {Event} event - the event triggered
**/
function touchStart(event) {
    event.preventDefault();

    onMouseDown(event);
}


/**Triggered while touching and moving is in progress (iPad/iPhone).
*Simply forward to onMouseMove
*@param {Event} event - the event triggered
**/
function touchMove(event) {
    event.preventDefault();

    onMouseMove(event);
}


/**Triggered when touch ends (iPad/iPhone).
*Simply forward to onMouseUp
*@param {Event} event - the event triggered
**/
function touchEnd(event) {
    onMouseUp(event);
}

/**Triggered when touch is canceled (iPad/iPhone).
*@param {Event} event - the event triggered
**/
function touchCancel(event) {
    //nothing
}
/*======================END APPLE=================================*/

