/**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 ''; v2 += "\n" + ''; INDENTATION++; v2 += STACK.toSVG(); v2 += CONNECTOR_MANAGER.toSVG(); INDENTATION--; v2 += "\n" + ''; 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 tag * @author Alex Gheorghiu **/ 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 **/ 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 **/ 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 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
inside editor page var textEditorTools = document.getElementById('text-editor-tools'); //a
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 **/ 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 **/ 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 **/ 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 http://www.quirksmode.org/js/keys.html **/ 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 http://www.quirksmode.org/js/keys.html **/ 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 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 **/ 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:''"; // append image through html of 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 = '\ \ \ \ '; //get svg var canvas = getCanvas(); var v2 = ''; v2 += STACK.toSVG(); v2 += CONNECTOR_MANAGER.toSVG(); v2 += ''; 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 = cceval('(' + 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=================================*/