webgme
Version:
Web-based Generic Modeling Environment
680 lines (530 loc) • 25.2 kB
JavaScript
/*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;
});