| 
 | |
| var SelectionMixin = { | |
| 
 | |
|   /** | |
|    * This function can be called from the _doInAllSectors function | |
|    * | |
|    * @param object | |
|    * @param overlappingNodes | |
|    * @private | |
|    */ | |
|   _getNodesOverlappingWith : function(object, overlappingNodes) { | |
|     var nodes = this.nodes; | |
|     for (var nodeId in nodes) { | |
|       if (nodes.hasOwnProperty(nodeId)) { | |
|         if (nodes[nodeId].isOverlappingWith(object)) { | |
|           overlappingNodes.push(nodeId); | |
|         } | |
|       } | |
|     } | |
|   }, | |
| 
 | |
|   /** | |
|    * retrieve all nodes overlapping with given object | |
|    * @param {Object} object  An object with parameters left, top, right, bottom | |
|    * @return {Number[]}   An array with id's of the overlapping nodes | |
|    * @private | |
|    */ | |
|   _getAllNodesOverlappingWith : function (object) { | |
|     var overlappingNodes = []; | |
|     this._doInAllActiveSectors("_getNodesOverlappingWith",object,overlappingNodes); | |
|     return overlappingNodes; | |
|   }, | |
| 
 | |
| 
 | |
|   /** | |
|    * Return a position object in canvasspace from a single point in screenspace | |
|    * | |
|    * @param pointer | |
|    * @returns {{left: number, top: number, right: number, bottom: number}} | |
|    * @private | |
|    */ | |
|   _pointerToPositionObject : function(pointer) { | |
|     var x = this._canvasToX(pointer.x); | |
|     var y = this._canvasToY(pointer.y); | |
| 
 | |
|     return {left:   x, | |
|             top:    y, | |
|             right:  x, | |
|             bottom: y}; | |
|   }, | |
| 
 | |
| 
 | |
|   /** | |
|    * Get the top node at the a specific point (like a click) | |
|    * | |
|    * @param {{x: Number, y: Number}} pointer | |
|    * @return {Node | null} node | |
|    * @private | |
|    */ | |
|   _getNodeAt : function (pointer) { | |
|     // we first check if this is an navigation controls element | |
|     var positionObject = this._pointerToPositionObject(pointer); | |
|     var overlappingNodes = this._getAllNodesOverlappingWith(positionObject); | |
| 
 | |
|     // if there are overlapping nodes, select the last one, this is the | |
|     // one which is drawn on top of the others | |
|     if (overlappingNodes.length > 0) { | |
|        return this.nodes[overlappingNodes[overlappingNodes.length - 1]]; | |
|     } | |
|     else { | |
|       return null; | |
|     } | |
|   }, | |
| 
 | |
| 
 | |
|   /** | |
|    * retrieve all edges overlapping with given object, selector is around center | |
|    * @param {Object} object  An object with parameters left, top, right, bottom | |
|    * @return {Number[]}   An array with id's of the overlapping nodes | |
|    * @private | |
|    */ | |
|   _getEdgesOverlappingWith : function (object, overlappingEdges) { | |
|     var edges = this.edges; | |
|     for (var edgeId in edges) { | |
|       if (edges.hasOwnProperty(edgeId)) { | |
|         if (edges[edgeId].isOverlappingWith(object)) { | |
|           overlappingEdges.push(edgeId); | |
|         } | |
|       } | |
|     } | |
|   }, | |
| 
 | |
| 
 | |
|   /** | |
|    * retrieve all nodes overlapping with given object | |
|    * @param {Object} object  An object with parameters left, top, right, bottom | |
|    * @return {Number[]}   An array with id's of the overlapping nodes | |
|    * @private | |
|    */ | |
|   _getAllEdgesOverlappingWith : function (object) { | |
|     var overlappingEdges = []; | |
|     this._doInAllActiveSectors("_getEdgesOverlappingWith",object,overlappingEdges); | |
|     return overlappingEdges; | |
|   }, | |
| 
 | |
|   /** | |
|    * Place holder. To implement change the _getNodeAt to a _getObjectAt. Have the _getObjectAt call | |
|    * _getNodeAt and _getEdgesAt, then priortize the selection to user preferences. | |
|    * | |
|    * @param pointer | |
|    * @returns {null} | |
|    * @private | |
|    */ | |
|   _getEdgeAt : function(pointer) { | |
|     var positionObject = this._pointerToPositionObject(pointer); | |
|     var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject); | |
| 
 | |
|     if (overlappingEdges.length > 0) { | |
|       return this.edges[overlappingEdges[overlappingEdges.length - 1]]; | |
|     } | |
|     else { | |
|       return null; | |
|     } | |
|   }, | |
| 
 | |
| 
 | |
|   /** | |
|    * Add object to the selection array. | |
|    * | |
|    * @param obj | |
|    * @private | |
|    */ | |
|   _addToSelection : function(obj) { | |
|     this.selectionObj[obj.id] = obj; | |
|   }, | |
| 
 | |
| 
 | |
|   /** | |
|    * Remove a single option from selection. | |
|    * | |
|    * @param {Object} obj | |
|    * @private | |
|    */ | |
|   _removeFromSelection : function(obj) { | |
|     delete this.selectionObj[obj.id]; | |
|   }, | |
| 
 | |
| 
 | |
|   /** | |
|    * Unselect all. The selectionObj is useful for this. | |
|    * | |
|    * @param {Boolean} [doNotTrigger] | ignore trigger | |
|    * @private | |
|    */ | |
|   _unselectAll : function(doNotTrigger) { | |
|     if (doNotTrigger === undefined) { | |
|       doNotTrigger = false; | |
|     } | |
| 
 | |
|     for (var objectId in this.selectionObj) { | |
|       if (this.selectionObj.hasOwnProperty(objectId)) { | |
|         this.selectionObj[objectId].unselect(); | |
|       } | |
|     } | |
|     this.selectionObj = {}; | |
| 
 | |
|     if (doNotTrigger == false) { | |
|       this.emit('select', this.getSelection()); | |
|     } | |
|   }, | |
| 
 | |
|   /** | |
|    * Unselect all clusters. The selectionObj is useful for this. | |
|    * | |
|    * @param {Boolean} [doNotTrigger] | ignore trigger | |
|    * @private | |
|    */ | |
|   _unselectClusters : function(doNotTrigger) { | |
|     if (doNotTrigger === undefined) { | |
|       doNotTrigger = false; | |
|     } | |
| 
 | |
|     for (var objectId in this.selectionObj) { | |
|       if (this.selectionObj.hasOwnProperty(objectId)) { | |
|         if (this.selectionObj[objectId] instanceof Node) { | |
|           if (this.selectionObj[objectId].clusterSize > 1) { | |
|             this.selectionObj[objectId].unselect(); | |
|             this._removeFromSelection(this.selectionObj[objectId]); | |
|           } | |
|         } | |
|       } | |
|     } | |
| 
 | |
|     if (doNotTrigger == false) { | |
|       this.emit('select', this.getSelection()); | |
|     } | |
|   }, | |
| 
 | |
| 
 | |
|   /** | |
|    * return the number of selected nodes | |
|    * | |
|    * @returns {number} | |
|    * @private | |
|    */ | |
|   _getSelectedNodeCount : function() { | |
|     var count = 0; | |
|     for (var objectId in this.selectionObj) { | |
|       if (this.selectionObj.hasOwnProperty(objectId)) { | |
|         if (this.selectionObj[objectId] instanceof Node) { | |
|           count += 1; | |
|         } | |
|       } | |
|     } | |
|     return count; | |
|   }, | |
| 
 | |
|   /** | |
|    * return the number of selected nodes | |
|    * | |
|    * @returns {number} | |
|    * @private | |
|    */ | |
|   _getSelectedNode : function() { | |
|     for (var objectId in this.selectionObj) { | |
|       if (this.selectionObj.hasOwnProperty(objectId)) { | |
|         if (this.selectionObj[objectId] instanceof Node) { | |
|           return this.selectionObj[objectId]; | |
|         } | |
|       } | |
|     } | |
|     return null; | |
|   }, | |
| 
 | |
| 
 | |
|   /** | |
|    * return the number of selected edges | |
|    * | |
|    * @returns {number} | |
|    * @private | |
|    */ | |
|   _getSelectedEdgeCount : function() { | |
|     var count = 0; | |
|     for (var objectId in this.selectionObj) { | |
|       if (this.selectionObj.hasOwnProperty(objectId)) { | |
|         if (this.selectionObj[objectId] instanceof Edge) { | |
|           count += 1; | |
|         } | |
|       } | |
|     } | |
|     return count; | |
|   }, | |
| 
 | |
| 
 | |
|   /** | |
|    * return the number of selected objects. | |
|    * | |
|    * @returns {number} | |
|    * @private | |
|    */ | |
|   _getSelectedObjectCount : function() { | |
|     var count = 0; | |
|     for (var objectId in this.selectionObj) { | |
|       if (this.selectionObj.hasOwnProperty(objectId)) { | |
|         count += 1; | |
|       } | |
|     } | |
|     return count; | |
|   }, | |
| 
 | |
|   /** | |
|    * Check if anything is selected | |
|    * | |
|    * @returns {boolean} | |
|    * @private | |
|    */ | |
|   _selectionIsEmpty : function() { | |
|     for(var objectId in this.selectionObj) { | |
|       if(this.selectionObj.hasOwnProperty(objectId)) { | |
|         return false; | |
|       } | |
|     } | |
|     return true; | |
|   }, | |
| 
 | |
| 
 | |
|   /** | |
|    * check if one of the selected nodes is a cluster. | |
|    * | |
|    * @returns {boolean} | |
|    * @private | |
|    */ | |
|   _clusterInSelection : function() { | |
|     for(var objectId in this.selectionObj) { | |
|       if(this.selectionObj.hasOwnProperty(objectId)) { | |
|         if (this.selectionObj[objectId] instanceof Node) { | |
|           if (this.selectionObj[objectId].clusterSize > 1) { | |
|             return true; | |
|           } | |
|         } | |
|       } | |
|     } | |
|     return false; | |
|   }, | |
| 
 | |
|   /** | |
|    * select the edges connected to the node that is being selected | |
|    * | |
|    * @param {Node} node | |
|    * @private | |
|    */ | |
|   _selectConnectedEdges : function(node) { | |
|     for (var i = 0; i < node.dynamicEdges.length; i++) { | |
|       var edge = node.dynamicEdges[i]; | |
|       edge.select(); | |
|       this._addToSelection(edge); | |
|     } | |
|   }, | |
| 
 | |
| 
 | |
|   /** | |
|    * unselect the edges connected to the node that is being selected | |
|    * | |
|    * @param {Node} node | |
|    * @private | |
|    */ | |
|   _unselectConnectedEdges : function(node) { | |
|     for (var i = 0; i < node.dynamicEdges.length; i++) { | |
|       var edge = node.dynamicEdges[i]; | |
|       edge.unselect(); | |
|       this._removeFromSelection(edge); | |
|     } | |
|   }, | |
| 
 | |
| 
 | |
| 
 | |
|   /** | |
|    * This is called when someone clicks on a node. either select or deselect it. | |
|    * If there is an existing selection and we don't want to append to it, clear the existing selection | |
|    * | |
|    * @param {Node || Edge} object | |
|    * @param {Boolean} append | |
|    * @param {Boolean} [doNotTrigger] | ignore trigger | |
|    * @private | |
|    */ | |
|   _selectObject : function(object, append, doNotTrigger) { | |
|     if (doNotTrigger === undefined) { | |
|       doNotTrigger = false; | |
|     } | |
| 
 | |
|     if (this._selectionIsEmpty() == false && append == false && this.forceAppendSelection == false) { | |
|       this._unselectAll(true); | |
|     } | |
| 
 | |
|     if (object.selected == false) { | |
|       object.select(); | |
|       this._addToSelection(object); | |
|       if (object instanceof Node && this.blockConnectingEdgeSelection == false) { | |
|         this._selectConnectedEdges(object); | |
|       } | |
|     } | |
|     else { | |
|       object.unselect(); | |
|       this._removeFromSelection(object); | |
|     } | |
|     if (doNotTrigger == false) { | |
|       this.emit('select', this.getSelection()); | |
|     } | |
|   }, | |
| 
 | |
| 
 | |
|   /** | |
|    * handles the selection part of the touch, only for navigation controls elements; | |
|    * Touch is triggered before tap, also before hold. Hold triggers after a while. | |
|    * This is the most responsive solution | |
|    * | |
|    * @param {Object} pointer | |
|    * @private | |
|    */ | |
|   _handleTouch : function(pointer) { | |
| 
 | |
|   }, | |
| 
 | |
| 
 | |
|   /** | |
|    * handles the selection part of the tap; | |
|    * | |
|    * @param {Object} pointer | |
|    * @private | |
|    */ | |
|   _handleTap : function(pointer) { | |
|     var node = this._getNodeAt(pointer); | |
|     if (node != null) { | |
|       this._selectObject(node,false); | |
|       this.emit("clickNode", this.getSelection()); | |
|     } | |
|     else { | |
|       var edge = this._getEdgeAt(pointer); | |
|       if (edge != null) { | |
|         this._selectObject(edge,false); | |
|         this.emit("clickEdge", this.getSelection()); | |
|       } | |
|       else { | |
|         this._unselectAll(); | |
|       } | |
|     } | |
|     this.emit("click", this.getSelection()); | |
|     this._redraw(); | |
|   }, | |
| 
 | |
| 
 | |
|   /** | |
|    * handles the selection part of the double tap and opens a cluster if needed | |
|    * | |
|    * @param {Object} pointer | |
|    * @private | |
|    */ | |
|   _handleDoubleTap : function(pointer) { | |
|     var node = this._getNodeAt(pointer); | |
|     var selection = this.getSelection(); | |
|     if (node != null && node !== undefined) { | |
|       // we reset the areaCenter here so the opening of the node will occur | |
|       this.areaCenter =  {"x" : this._canvasToX(pointer.x), | |
|                           "y" : this._canvasToY(pointer.y)}; | |
|       this.openCluster(node); | |
|       this.emit("doubleClickNode", selection); | |
|     } | |
|     else { | |
|       if (this._getSelectedEdgeCount() == 1) { | |
|         this.emit("doubleClickEdge", selection); | |
|       } | |
|     } | |
|     this.emit("doubleClick", selection); | |
|   }, | |
| 
 | |
| 
 | |
|   /** | |
|    * Handle the onHold selection part | |
|    * | |
|    * @param pointer | |
|    * @private | |
|    */ | |
|   _handleOnHold : function(pointer) { | |
|     var node = this._getNodeAt(pointer); | |
|     if (node != null) { | |
|       this._selectObject(node,true); | |
|     } | |
|     else { | |
|       var edge = this._getEdgeAt(pointer); | |
|       if (edge != null) { | |
|         this._selectObject(edge,true); | |
|       } | |
|     } | |
|     this._redraw(); | |
|   }, | |
| 
 | |
| 
 | |
|   /** | |
|    * handle the onRelease event. These functions are here for the navigation controls module. | |
|    * | |
|     * @private | |
|    */ | |
|   _handleOnRelease : function(pointer) { | |
| 
 | |
|   }, | |
| 
 | |
| 
 | |
| 
 | |
|   /** | |
|    * | |
|    * retrieve the currently selected objects | |
|    * @return {Number[] | String[]} selection    An array with the ids of the | |
|    *                                            selected nodes. | |
|    */ | |
|   getSelection : function() { | |
|     var nodeIds = this.getSelectedNodes(); | |
|     var edgeIds = this.getSelectedEdges(); | |
|     return {nodes:nodeIds, edges:edgeIds}; | |
|   }, | |
| 
 | |
|   /** | |
|    * | |
|    * retrieve the currently selected nodes | |
|    * @return {String} selection    An array with the ids of the | |
|    *                                            selected nodes. | |
|    */ | |
|   getSelectedNodes : function() { | |
|     var idArray = []; | |
|     for(var objectId in this.selectionObj) { | |
|       if(this.selectionObj.hasOwnProperty(objectId)) { | |
|         if (this.selectionObj[objectId] instanceof Node) { | |
|           idArray.push(objectId); | |
|         } | |
|       } | |
|     } | |
|     return idArray | |
|   }, | |
| 
 | |
|   /** | |
|    * | |
|    * retrieve the currently selected edges | |
|    * @return {Array} selection    An array with the ids of the | |
|    *                                            selected nodes. | |
|    */ | |
|   getSelectedEdges : function() { | |
|     var idArray = []; | |
|     for(var objectId in this.selectionObj) { | |
|       if(this.selectionObj.hasOwnProperty(objectId)) { | |
|         if (this.selectionObj[objectId] instanceof Edge) { | |
|           idArray.push(objectId); | |
|         } | |
|       } | |
|     } | |
|     return idArray | |
|   }, | |
| 
 | |
| 
 | |
|   /** | |
|    * select zero or more nodes | |
|    * @param {Number[] | String[]} selection     An array with the ids of the | |
|    *                                            selected nodes. | |
|    */ | |
|   setSelection : function(selection) { | |
|     var i, iMax, id; | |
| 
 | |
|     if (!selection || (selection.length == undefined)) | |
|       throw 'Selection must be an array with ids'; | |
| 
 | |
|     // first unselect any selected node | |
|     this._unselectAll(true); | |
| 
 | |
|     for (i = 0, iMax = selection.length; i < iMax; i++) { | |
|       id = selection[i]; | |
| 
 | |
|       var node = this.nodes[id]; | |
|       if (!node) { | |
|         throw new RangeError('Node with id "' + id + '" not found'); | |
|       } | |
|       this._selectObject(node,true,true); | |
|     } | |
|     this.redraw(); | |
|   }, | |
| 
 | |
| 
 | |
|   /** | |
|    * Validate the selection: remove ids of nodes which no longer exist | |
|    * @private | |
|    */ | |
|   _updateSelection : function () { | |
|     for(var objectId in this.selectionObj) { | |
|       if(this.selectionObj.hasOwnProperty(objectId)) { | |
|         if (this.selectionObj[objectId] instanceof Node) { | |
|           if (!this.nodes.hasOwnProperty(objectId)) { | |
|             delete this.selectionObj[objectId]; | |
|           } | |
|         } | |
|         else { // assuming only edges and nodes are selected | |
|           if (!this.edges.hasOwnProperty(objectId)) { | |
|             delete this.selectionObj[objectId]; | |
|           } | |
|         } | |
|       } | |
|     } | |
|   } | |
| }; | |
| 
 | |
| 
 |