UNPKG

webgme

Version:

Web-based Generic Modeling Environment

680 lines (530 loc) 25.2 kB
/*globals define, _, $, WebGMEGlobal*/ /*jshint browser: true*/ /** * @author rkereskenyi / https://github.com/rkereskenyi */ define([ 'js/logger', './DragScroll', './DiagramDesignerWidget.Constants' ], function (Logger, DragScroll, DiagramDesignerWidgetConstants) { 'use strict'; var DragManager, MOVE_CURSOR = 'move', COPY_CURSOR = 'copy', MIN_DELTA_TO_DRAG = 10, EVENT_POSTFIX = 'DragManager', MOUSE_DOWN = 'mousedown.' + EVENT_POSTFIX, MOUSE_MOVE = 'mousemove.' + EVENT_POSTFIX, MOUSE_UP = 'mouseup.' + EVENT_POSTFIX; DragManager = function (options) { var loggerName = (options && options.loggerName) || 'gme:Widgets:DiagramDesigner:DragManager'; this.logger = (options && options.logger) || Logger.create(loggerName, WebGMEGlobal.gmeConfig.client.log); this._diagramDesigner = options ? options.diagramDesigner : null; if (this._diagramDesigner === undefined || this._diagramDesigner === null) { this.logger.error('Trying to initialize a DragManager without a canvas...'); throw ('DragManager can not be created'); } this.logger.debug('DragManager ctor finished'); }; DragManager.prototype.DRAGMODE_COPY = 'copy'; DragManager.prototype.DRAGMODE_MOVE = 'move'; DragManager.prototype.initialize = function (el) { var self = this; this._dragModes = {}; this.$el = el; this._dragModes[this.DRAGMODE_COPY] = true; this._dragModes[this.DRAGMODE_MOVE] = true; this._diagramDesigner.addEventListener(this._diagramDesigner.events.ITEM_POSITION_CHANGED, function (_canvas, event) { self._canvasItemPositionChanged(event); } ); this._dragScroll = new DragScroll(this.$el.parent()); this._diagramDesigner.addEventListener(this._diagramDesigner.events.ON_COMPONENT_DELETE, function (_canvas, componentId) { self._onComponentDelete(componentId); } ); }; DragManager.prototype.activate = function () { this._activateMouseListeners(); }; DragManager.prototype.deactivate = function () { this._deactivateMouseListeners(); }; DragManager.prototype._activateMouseListeners = function () { var self = this; this.$el.on(MOUSE_DOWN, 'div.' + DiagramDesignerWidgetConstants.DESIGNER_ITEM_CLASS, function (event) { self._onItemMouseDown(event); }); }; DragManager.prototype._deactivateMouseListeners = function () { this.$el.off(MOUSE_DOWN, 'div.' + DiagramDesignerWidgetConstants.DESIGNER_ITEM_CLASS); }; DragManager.prototype.enableMode = function (mode, enabled) { if (this._dragModes[mode]) { this._dragModes[mode] = enabled; } }; /******** ITEM MOUSE DOWN EVENT HANDLER ****************/ DragManager.prototype._onItemMouseDown = function (event) { var mousePos = this._diagramDesigner.getAdjustedMousePos(event), self = this, leftButton = event.which === 1, dragEnabled = !this._diagramDesigner.getIsReadOnlyMode(); if (dragEnabled && leftButton) { this.logger.debug('DragManager._onItemMouseDown at: ' + JSON.stringify(mousePos)); if (this._diagramDesigner.mode === this._diagramDesigner.OPERATING_MODES.DESIGN) { this._initDrag(mousePos.mX, mousePos.mY); //hook up MouseMove and MouseUp this._onBackgroundMouseMoveCallBack = function (event) { self._onBackgroundMouseMove(event); }; this._onBackgroundMouseUpCallBack = function (event) { self._onBackgroundMouseUp(event); }; $(document).on(MOUSE_MOVE, this._onBackgroundMouseMoveCallBack); $(document).on(MOUSE_UP, this._onBackgroundMouseUpCallBack); } event.stopPropagation(); event.preventDefault(); } }; DragManager.prototype._initDrag = function (mX, mY) { var dragEnabled = !this._diagramDesigner.getIsReadOnlyMode(); if (dragEnabled && this._diagramDesigner.mode === this._diagramDesigner.OPERATING_MODES.DESIGN) { //initialize drag descriptor this._dragDesc = { startX: mX, startY: mY, dX: 0, dY: 0, params: undefined, mode: undefined, dragging: false }; } }; /******** MOUSE MOVE AND MOUSE UP EVENT HANDLERS *********************/ DragManager.prototype._onBackgroundMouseMove = function (event) { var mousePos = this._diagramDesigner.getAdjustedMousePos(event), dx = mousePos.mX - this._dragDesc.startX, dy = mousePos.mY - this._dragDesc.startY; //touch device fix: not sure why but sometime during dragging negative coordinates are passed here if (mousePos.mX <= 0 && mousePos.mX <= 0) { return; } if (this._dragDesc.dragging === true) { //already dragging, handle dragged item reposition this.logger.debug('already dragging, handle dragged item reposition'); this._doDrag(event); } else { //not yet dragging //check if the mouse delta reached the minimum mouse delta at all to initiate the drag if (Math.abs(dx) >= MIN_DELTA_TO_DRAG || Math.abs(dy) >= MIN_DELTA_TO_DRAG) { //minimum delta is met, start the dragging this.logger.debug('Mouse delta reached the minimum to handle as a drag'); this._startDrag(event); this._dragScroll.start(); } } }; DragManager.prototype._onBackgroundMouseUp = function (event) { //unbind mousemove and mouseup handlers $(document).off(MOUSE_MOVE, this._onBackgroundMouseMoveCallBack); $(document).off(MOUSE_UP, this._onBackgroundMouseUpCallBack); //delete unnecessary instance members delete this._onBackgroundMouseMoveCallBack; delete this._onBackgroundMouseUpCallBack; this.logger.debug('DragManager._onBackgroundMouseUp'); //if dragging at all if (this._dragDesc.dragging === true) { this._endDrag(event); } //clear drag descriptor object this._dragDesc = undefined; }; /********* START DRAGGING *******************************/ DragManager.prototype._startDrag = function (event) { var ctrlKey = event && (event.ctrlKey || event.metaKey), shiftKey = event && event.shiftKey, currentDragMode = this.DRAGMODE_MOVE; //by default drag is treated as move //debug log this.logger.debug('_startDrag called'); //determine based on modifier keys what mode the DRAG should work //check modifiers to see what kind of drag-and-drop it will be //#1 //when CTRL key (META key on Mac) is pressed when drag starts, selected items will be copied //is this drag a 'COPY' if (ctrlKey && !shiftKey) { //if the copy mode is enabled at all? if (this._dragModes[this.DRAGMODE_COPY] === true) { currentDragMode = this.DRAGMODE_COPY; } } //when we have the selected drag-mode, initialize the parameters this._dragDesc.mode = currentDragMode; this._dragDesc.params = { draggedItemIDs: [], draggedConnectionIDs: [], minStartCoordinates: {x: undefined, y: undefined}, originalPositions: [], modeSpecificData: undefined }; if (this._dragDesc.mode === this.DRAGMODE_MOVE) { this._startDragModeMove(); } else if (this._dragDesc.mode === this.DRAGMODE_COPY) { this._startDragModeCopy(); } this._calculateMinStartCoordinates(); this._diagramDesigner.onDesignerItemDragStart(undefined, this._dragDesc.params.draggedItemIDs); //finally set the dragging initialized this._dragDesc.dragging = true; }; //initialize the drag descriptor for 'MOVE' operation DragManager.prototype._startDragModeMove = function () { var selectedItemIDs = this._diagramDesigner.selectionManager.getSelectedElements(), items = this._diagramDesigner.items, itemIDs = this._diagramDesigner.itemIds, i = selectedItemIDs.length, id, item; while (i--) { id = selectedItemIDs[i]; item = items[id]; //check if the currently checked item is DesignerItem or Connection if (itemIDs.indexOf(id) !== -1) { //if the item is draggable at all? if (this._diagramDesigner.onDragStartDesignerItemDraggable(id) === true) { //store we are dragging this guy this._dragDesc.params.draggedItemIDs.push(id); this._dragDesc.params.originalPositions.push({ x: item.positionX, y: item.positionY }); } } } //set cursor this.$el.css('cursor', MOVE_CURSOR); }; //initialize the drag descriptor for 'COPY' operation DragManager.prototype._startDragModeCopy = function () { var selectedItemIDs = this._diagramDesigner.selectionManager.getSelectedElements(), items = this._diagramDesigner.items, itemIDs = this._diagramDesigner.itemIds, connectionIDs = this._diagramDesigner.connectionIds, i = selectedItemIDs.length, id, srcItem, copiedItem, objDesc, copyData, newSelectionIDs = []; this._dragDesc.params.modeSpecificData = copyData = {}; this._diagramDesigner.beginUpdate(); //first copy the DesignerItems while (i--) { id = selectedItemIDs[i]; if (itemIDs.indexOf(id) !== -1) { //if the item is copy-able at all? if (this._diagramDesigner.onDragStartDesignerItemCopyable(id) === true) { objDesc = {}; srcItem = items[id]; objDesc.position = {x: srcItem.positionX, y: srcItem.positionY}; objDesc.decoratorClass = srcItem._decoratorClass; objDesc.control = srcItem._decoratorInstance.getControl(); objDesc.metaInfo = srcItem._decoratorInstance.getMetaInfo(); copiedItem = this._diagramDesigner.createDesignerItem(objDesc); //fix the DesignerCanvas' 'try-to-avoid-overlapping-auto-shift' feature copiedItem.moveTo(srcItem.positionX, srcItem.positionY); //store we are dragging this guy this._dragDesc.params.draggedItemIDs.push(copiedItem.id); this._dragDesc.params.originalPositions.push({ x: copiedItem.positionX, y: copiedItem.positionY }); copyData[id] = {copiedItemId: copiedItem.id}; newSelectionIDs.push(copiedItem.id); } } } i = selectedItemIDs.length; //then duplicate the connections that are selected while (i--) { id = selectedItemIDs[i]; if (connectionIDs.indexOf(id) !== -1) { if (this._diagramDesigner.onDragStartDesignerConnectionCopyable(id) === true) { var oConnection = items[id]; var srcObjId = this._diagramDesigner.connectionEndIDs[id].srcObjId; var srcSubCompId = this._diagramDesigner.connectionEndIDs[id].srcSubCompId; var dstObjId = this._diagramDesigner.connectionEndIDs[id].dstObjId; var dstSubCompId = this._diagramDesigner.connectionEndIDs[id].dstSubCompId; if (selectedItemIDs.indexOf(srcObjId) !== -1) { srcObjId = copyData[srcObjId].copiedItemId; } if (selectedItemIDs.indexOf(dstObjId) !== -1) { dstObjId = copyData[dstObjId].copiedItemId; } objDesc = _.extend({}, oConnection.getConnectionProps()); objDesc.srcObjId = srcObjId; objDesc.srcSubCompId = srcSubCompId; objDesc.dstObjId = dstObjId; objDesc.dstSubCompId = dstSubCompId; var copiedConnection = this._diagramDesigner.createConnection(objDesc); this._dragDesc.params.draggedConnectionIDs.push(copiedConnection.id); copyData[id] = {copiedConnectionId: copiedConnection.id}; newSelectionIDs.push(copiedConnection.id); } } } this._diagramDesigner.endUpdate(); this._diagramDesigner.selectionManager.setSelection(newSelectionIDs, false); //set cursor this.$el.css('cursor', COPY_CURSOR); }; /********* END OF --- START DRAGGING *******************************/ /********* DO DRAGGING *******************************/ DragManager.prototype._doDrag = function (event) { var mouseDelta = this._griddedMouseDelta(event); //position update for the dragged items this._updateDraggedItemPositions(mouseDelta.dX, mouseDelta.dY); }; /********* END OF --- DO DRAGGING *******************************/ /********* END DRAGGING *******************************/ DragManager.prototype._endDrag = function (event) { var mouseDelta = this._griddedMouseDelta(event); //final position update for the dragged items this._updateDraggedItemPositions(mouseDelta.dX, mouseDelta.dY); this._endDragAction(); }; DragManager.prototype._endDragAction = function () { var dItems = this._dragDesc.params.draggedItemIDs; if (this._dragDesc.mode === this.DRAGMODE_MOVE) { this._endDragModeMove(); } else if (this._dragDesc.mode === this.DRAGMODE_COPY) { this._endDragModeCopy(); } this._diagramDesigner.onDesignerItemDragStop(undefined, dItems); this.$el.css('cursor', ''); }; DragManager.prototype._endDragModeMove = function () { var draggedItemIDs = this._dragDesc.params.draggedItemIDs, i = draggedItemIDs.length, items = this._diagramDesigner.items, newPositions = {}, id, item; //if there is any displacement at all if (draggedItemIDs.length > 0) { if (this._dragDesc.dX !== 0 || this._dragDesc.dY !== 0) { while (i--) { id = draggedItemIDs[i]; item = items[id]; newPositions[id] = {x: item.positionX, y: item.positionY}; } //clear drag descriptor object this._dragDesc = undefined; this._diagramDesigner.onDesignerItemsMove(newPositions); } } }; DragManager.prototype._endDragModeCopy = function () { var draggedItemIDs = this._dragDesc.params.draggedItemIDs, copyData = this._dragDesc.params.modeSpecificData, i, copyDesc = { items: {}, connections: {} }, desc; //if drag&drop ended because of no more item being dragged if (draggedItemIDs.length > 0) { //create copy descriptor for (i in copyData) { if (copyData.hasOwnProperty(i)) { desc = copyData[i]; if (desc.hasOwnProperty('copiedItemId')) { //description of a box-copy copyDesc.items[desc.copiedItemId] = { oItemId: i, posX: this._diagramDesigner.items[desc.copiedItemId].positionX, posY: this._diagramDesigner.items[desc.copiedItemId].positionY }; } else if (desc.hasOwnProperty('copiedConnectionId')) { //description of a connection copy copyDesc.connections[desc.copiedConnectionId] = {oConnectionId: i}; } } } //clear drag descriptor object this._dragDesc = undefined; // FIXME: how to emit this event? //this._diagramDesigner.onDesignerItemsCopy(copyDesc); } }; /********* END OF --- END DRAGGING *******************************/ DragManager.prototype._updateDraggedItemPositions = function (dX, dY) { var gridSize = this._diagramDesigner.gridSize; if (dX !== this._dragDesc.dX || dY !== this._dragDesc.dY) { this._dragDesc.dX = dX; this._dragDesc.dY = dY; this.logger.debug('DragManager._updateDraggedItemPositions [dx,dy]: ' + dX + ',' + dY); if (this._dragDesc.params.minStartCoordinates.x + dX < gridSize) { this.logger.debug('dX cleared out otherwise item\'s position would be negative'); dX = gridSize - this._dragDesc.params.minStartCoordinates.x; } if (this._dragDesc.params.minStartCoordinates.y + dY < gridSize) { this.logger.debug('dY cleared out otherwise item\'s position would be negative'); dY = gridSize - this._dragDesc.params.minStartCoordinates.y; } this._moveDraggedComponentsBy(dX, dY); this._diagramDesigner.onDesignerItemDrag(undefined, this._dragDesc.params.draggedItemIDs); } }; DragManager.prototype._moveDraggedComponentsBy = function (dX, dY) { var i = this._dragDesc.params.draggedItemIDs.length, id, posX, posY; this._movingDraggedComponents = true; //move all the dragged items while (i--) { id = this._dragDesc.params.draggedItemIDs[i]; posX = this._dragDesc.params.originalPositions[i].x + dX; posY = this._dragDesc.params.originalPositions[i].y + dY; this._diagramDesigner.items[id].moveTo(posX, posY); } this._movingDraggedComponents = false; }; DragManager.prototype._griddedMouseDelta = function (event) { var mousePos = this._diagramDesigner.getAdjustedMousePos(event), dx = mousePos.mX - this._dragDesc.startX, dy = mousePos.mY - this._dragDesc.startY, gridSize = this._diagramDesigner.gridSize, dX = dx - dx % gridSize, dY = dy - dy % gridSize; return { dX: dX, dY: dY }; }; DragManager.prototype._calculateMinStartCoordinates = function () { var i; if (this._dragDesc) { i = this._dragDesc.params.originalPositions.length; this._dragDesc.params.minStartCoordinates.x = undefined; this._dragDesc.params.minStartCoordinates.y = undefined; //figure out minimum coordinates from originalPositions while (i--) { if (this._dragDesc.params.minStartCoordinates.x) { this._dragDesc.params.minStartCoordinates.x = Math.min(this._dragDesc.params.minStartCoordinates.x, this._dragDesc.params.originalPositions[i].x); } else { this._dragDesc.params.minStartCoordinates.x = this._dragDesc.params.originalPositions[i].x; } if (this._dragDesc.params.minStartCoordinates.y) { this._dragDesc.params.minStartCoordinates.y = Math.min(this._dragDesc.params.minStartCoordinates.y, this._dragDesc.params.originalPositions[i].y); } else { this._dragDesc.params.minStartCoordinates.y = this._dragDesc.params.originalPositions[i].y; } } } }; /***************** COMPONENT DELETED FROM CANVAS *****************/ DragManager.prototype._onComponentDelete = function (componentId) { var idx, copiedComponentId, modeSpecificData, itemDeleted = false; if (this._dragDesc) { modeSpecificData = this._dragDesc.params.modeSpecificData; //handle COPY / MOVE mode switch (this._dragDesc.mode) { case this.DRAGMODE_MOVE: idx = this._dragDesc.params.draggedItemIDs.indexOf(componentId); if (idx !== -1) { //one of the dragged items has been deleted this.logger.debug('One of the currently moved items is being deleted: ' + componentId); //remove the component's information from the drag list this._dragDesc.params.draggedItemIDs.splice(idx, 1); this._dragDesc.params.originalPositions.splice(idx, 1); itemDeleted = true; } break; case this.DRAGMODE_COPY: if (modeSpecificData.hasOwnProperty(componentId)) { //one of the dragged items has been deleted this.logger.debug('One of the currently copied items is being deleted: ' + componentId); copiedComponentId = modeSpecificData[componentId].copiedItemId || modeSpecificData[componentId].copiedConnectionId; //clean up delete modeSpecificData[componentId]; idx = this._dragDesc.params.draggedItemIDs.indexOf(copiedComponentId); if (idx !== -1) { this._dragDesc.params.draggedItemIDs.splice(idx, 1); this._dragDesc.params.originalPositions.splice(idx, 1); itemDeleted = true; } idx = this._dragDesc.params.draggedConnectionIDs.indexOf(copiedComponentId); if (idx !== -1) { this._dragDesc.params.draggedConnectionIDs.splice(idx, 1); } //delete from canvas this._diagramDesigner.deleteComponent(copiedComponentId); } break; default: break; } //after a component being deleted from the dragged group, minimal coordinates might have changed if (itemDeleted === true) { this._calculateMinStartCoordinates(); } //no more item is being dragged --> cancel dragging mode if (this._dragDesc && this._dragDesc.params && this._dragDesc.params.draggedItemIDs.length === 0) { this._cancelDrag(); } } }; DragManager.prototype._cancelDrag = function () { this.logger.debug('Cancelling drag artificially...'); this.$el.trigger('mouseup'); }; /************** END OF - COMPONENT DELETED FROM CANVAS ***********/ /************** EVENT HANDLER - CANVAS ITEM POSITION CHANGED *****/ DragManager.prototype._canvasItemPositionChanged = function (event) { var id = event.ID, pX = event.x, pY = event.y, idx, origPos; if (this._movingDraggedComponents === true) { return; } if (this._dragDesc && this._dragDesc.params && this._dragDesc.params.draggedItemIDs) { idx = this._dragDesc.params.draggedItemIDs.indexOf(id); if (idx !== -1) { origPos = this._dragDesc.params.originalPositions[idx]; if (pX !== origPos.x + this._dragDesc.dX || pY !== origPos.y + this._dragDesc.dY) { //moved outside of dragging, update original position info origPos.x = pX; origPos.y = pY; pX = origPos.x + this._dragDesc.dX; pY = origPos.y + this._dragDesc.dY; this._movingDraggedComponents = true; this._diagramDesigner.items[id].moveTo(pX, pY); this._movingDraggedComponents = false; } } } }; /******END OF - EVENT HANDLER - CANVAS ITEM POSITION CHANGED *****/ return DragManager; });