fin-hypergrid
Version:
Canvas-based high-performance grid
418 lines (364 loc) • 12.9 kB
JavaScript
'use strict';
var Feature = require('./Feature');
/**
* @constructor
* @extends Feature
*/
var CellSelection = Feature.extend('CellSelection', {
/**
* The pixel location of the mouse pointer during a drag operation.
* @type {Point}
* @memberOf CellSelection.prototype
*/
currentDrag: null,
/**
* the cell coordinates of the where the mouse pointer is during a drag operation
* @type {Object}
* @memberOf CellSelection.prototype
*/
lastDragCell: null,
/**
* a millisecond value representing the previous time an autoscroll started
* @type {number}
* @default 0
* @memberOf CellSelection.prototype
*/
sbLastAuto: 0,
/**
* a millisecond value representing the time the current autoscroll started
* @type {number}
* @default 0
* @memberOf CellSelection.prototype
*/
sbAutoStart: 0,
/**
* @memberOf CellSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleMouseUp: function(grid, event) {
if (this.dragging) {
this.dragging = false;
}
if (this.next) {
this.next.handleMouseUp(grid, event);
}
},
/**
* @memberOf CellSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleMouseDown: function(grid, event) {
var dx = event.gridCell.x,
dy = event.dataCell.y,
isSelectable = grid.behavior.getCellProperty(event.dataCell.x, event.gridCell.y, 'cellSelection');
if (isSelectable && event.isDataCell && !event.primitiveEvent.detail.isRightClick) {
var dCell = grid.newPoint(dx, dy),
primEvent = event.primitiveEvent,
keys = primEvent.detail.keys;
this.dragging = true;
this.extendSelection(grid, dCell, keys);
} else if (this.next) {
this.next.handleMouseDown(grid, event);
}
},
/**
* @memberOf CellSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleMouseDrag: function(grid, event) {
if (this.dragging && grid.properties.cellSelection && !event.primitiveEvent.detail.isRightClick) {
this.currentDrag = event.primitiveEvent.detail.mouse;
this.lastDragCell = grid.newPoint(event.gridCell.x, event.dataCell.y);
this.checkDragScroll(grid, this.currentDrag);
this.handleMouseDragCellSelection(grid, this.lastDragCell, event.primitiveEvent.detail.keys);
} else if (this.next) {
this.next.handleMouseDrag(grid, event);
}
},
/**
* @memberOf CellSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleKeyDown: function(grid, event) {
var detail = event.detail,
cellEvent = grid.getGridCellFromLastSelection(true),
navKey = cellEvent && (
cellEvent.properties.mappedNavKey(detail.char, detail.ctrl) ||
cellEvent.properties.navKey(detail.char, detail.ctrl)
),
handler = this['handle' + navKey];
// STEP 1: Move the selection
if (handler) {
handler.call(this, grid, detail);
// STEP 2: Open the cell editor at the new position if `editable` AND edited cell had `editOnNextCell`
if (cellEvent.properties.editOnNextCell) {
grid.renderer.computeCellsBounds(true); // moving selection may have auto-scrolled
cellEvent = grid.getGridCellFromLastSelection(); // new cell
grid.editAt(cellEvent); // succeeds only if `editable`
}
// STEP 3: If editor not opened on new cell, take focus
if (!grid.cellEditor) {
grid.takeFocus();
}
} else if (this.next) {
this.next.handleKeyDown(grid, event);
}
},
/**
* @memberOf CellSelection.prototype
* @desc Handle a mousedrag selection.
* @param {Hypergrid} grid
* @param {Object} mouse - the event details
* @param {Array} keys - array of the keys that are currently pressed down
*/
handleMouseDragCellSelection: function(grid, gridCell, keys) {
var x = Math.max(0, gridCell.x),
y = Math.max(0, gridCell.y),
previousDragExtent = grid.getDragExtent(),
mouseDown = grid.getMouseDown(),
newX = x - mouseDown.x,
newY = y - mouseDown.y;
if (previousDragExtent.x === newX && previousDragExtent.y === newY) {
return;
}
grid.clearMostRecentSelection();
grid.select(mouseDown.x, mouseDown.y, newX, newY);
grid.setDragExtent(grid.newPoint(newX, newY));
grid.repaint();
},
/**
* @memberOf CellSelection.prototype
* @desc this checks while were dragging if we go outside the visible bounds, if so, kick off the external autoscroll check function (above)
* @param {Hypergrid} grid
* @param {Object} mouse - the event details
*/
checkDragScroll: function(grid, mouse) {
if (!grid.properties.scrollingEnabled) {
return;
}
var b = grid.getDataBounds();
var inside = b.contains(mouse);
if (inside) {
if (grid.isScrollingNow()) {
grid.setScrollingNow(false);
}
} else if (!grid.isScrollingNow()) {
grid.setScrollingNow(true);
this.scrollDrag(grid);
}
},
/**
* @memberOf CellSelection.prototype
* @desc this function makes sure that while we are dragging outside of the grid visible bounds, we srcroll accordingly
* @param {Hypergrid} grid
*/
scrollDrag: function(grid) {
if (!grid.isScrollingNow()) {
return;
}
var dragStartedInHeaderArea = grid.isMouseDownInHeaderArea(),
lastDragCell = this.lastDragCell,
b = grid.getDataBounds(),
xOffset = 0,
yOffset = 0,
numFixedColumns = grid.getFixedColumnCount(),
numFixedRows = grid.getFixedRowCount(),
dragEndInFixedAreaX = lastDragCell.x < numFixedColumns,
dragEndInFixedAreaY = lastDragCell.y < numFixedRows;
if (!dragStartedInHeaderArea) {
if (this.currentDrag.x < b.origin.x) {
xOffset = -1;
}
if (this.currentDrag.y < b.origin.y) {
yOffset = -1;
}
}
if (this.currentDrag.x > b.origin.x + b.extent.x) {
xOffset = 1;
}
if (this.currentDrag.y > b.origin.y + b.extent.y) {
yOffset = 1;
}
var dragCellOffsetX = xOffset;
var dragCellOffsetY = yOffset;
if (dragEndInFixedAreaX) {
dragCellOffsetX = 0;
}
if (dragEndInFixedAreaY) {
dragCellOffsetY = 0;
}
this.lastDragCell = lastDragCell.plusXY(dragCellOffsetX, dragCellOffsetY);
grid.scrollBy(xOffset, yOffset);
this.handleMouseDragCellSelection(grid, lastDragCell, []); // update the selection
grid.repaint();
setTimeout(this.scrollDrag.bind(this, grid), 25);
},
/**
* @memberOf CellSelection.prototype
* @desc extend a selection or create one if there isnt yet
* @param {Hypergrid} grid
* @param {Object} gridCell - the event details
* @param {Array} keys - array of the keys that are currently pressed down
*/
extendSelection: function(grid, gridCell, keys) {
var hasCTRL = keys.indexOf('CTRL') >= 0,
hasSHIFT = keys.indexOf('SHIFT') >= 0,
mousePoint = grid.getMouseDown(),
x = gridCell.x, // - numFixedColumns + scrollLeft;
y = gridCell.y; // - numFixedRows + scrollTop;
//were outside of the grid do nothing
if (x < 0 || y < 0) {
return;
}
//we have repeated a click in the same spot deslect the value from last time
if (
hasCTRL &&
x === mousePoint.x &&
y === mousePoint.y
) {
grid.clearMostRecentSelection();
grid.popMouseDown();
grid.repaint();
return;
}
if (!hasCTRL && !hasSHIFT) {
grid.clearSelections();
}
if (hasSHIFT) {
grid.clearMostRecentSelection();
grid.select(mousePoint.x, mousePoint.y, x - mousePoint.x, y - mousePoint.y);
grid.setDragExtent(grid.newPoint(x - mousePoint.x, y - mousePoint.y));
} else {
grid.select(x, y, 0, 0);
grid.setMouseDown(grid.newPoint(x, y));
grid.setDragExtent(grid.newPoint(0, 0));
}
grid.repaint();
},
/**
* @memberOf CellSelection.prototype
* @param {Hypergrid} grid
*/
handleDOWNSHIFT: function(grid) {
this.moveShiftSelect(grid, 0, 1);
},
/**
* @memberOf CellSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleUPSHIFT: function(grid) {
this.moveShiftSelect(grid, 0, -1);
},
/**
* @memberOf CellSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleLEFTSHIFT: function(grid) {
this.moveShiftSelect(grid, -1, 0);
},
/**
* @memberOf CellSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleRIGHTSHIFT: function(grid) {
this.moveShiftSelect(grid, 1, 0);
},
/**
* @memberOf CellSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleDOWN: function(grid, event) {
//keep the browser viewport from auto scrolling on key event
event.primitiveEvent.preventDefault();
var count = this.getAutoScrollAcceleration();
grid.moveSingleSelect(0, count);
},
/**
* @memberOf CellSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleUP: function(grid, event) {
//keep the browser viewport from auto scrolling on key event
event.primitiveEvent.preventDefault();
var count = this.getAutoScrollAcceleration();
grid.moveSingleSelect(0, -count);
},
/**
* @memberOf CellSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleLEFT: function(grid) {
grid.moveSingleSelect(-1, 0);
},
/**
* @memberOf CellSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleRIGHT: function(grid) {
grid.moveSingleSelect(1, 0);
},
/**
* @memberOf CellSelection.prototype
* @desc If we are holding down the same navigation key, accelerate the increment we scroll
* #### returns: integer
*/
getAutoScrollAcceleration: function() {
var count = 1;
var elapsed = this.getAutoScrollDuration() / 2000;
count = Math.max(1, Math.floor(elapsed * elapsed * elapsed * elapsed));
return count;
},
/**
* @memberOf CellSelection.prototype
* @desc set the start time to right now when we initiate an auto scroll
*/
setAutoScrollStartTime: function() {
this.sbAutoStart = Date.now();
},
/**
* @memberOf CellSelection.prototype
* @desc update the autoscroll start time if we haven't autoscrolled within the last 500ms otherwise update the current autoscroll time
*/
pingAutoScroll: function() {
var now = Date.now();
if (now - this.sbLastAuto > 500) {
this.setAutoScrollStartTime();
}
this.sbLastAuto = Date.now();
},
/**
* @memberOf CellSelection.prototype
* @desc answer how long we have been auto scrolling
* #### returns: integer
*/
getAutoScrollDuration: function() {
if (Date.now() - this.sbLastAuto > 500) {
return 0;
}
return Date.now() - this.sbAutoStart;
},
/**
* @memberOf CellSelection.prototype
* @desc Augment the most recent selection extent by (offsetX,offsetY) and scroll if necessary.
* @param {Hypergrid} grid
* @param {number} offsetX - x coordinate to start at
* @param {number} offsetY - y coordinate to start at
*/
moveShiftSelect: function(grid, offsetX, offsetY) {
if (grid.extendSelect(offsetX, offsetY)) {
this.pingAutoScroll();
}
}
});
module.exports = CellSelection;