// Drag select selection model gist taken from https://gist.github.com/skoon/5312536 // heavily modified import { mixin } from 'vs/base/common/objects'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { CellRangeSelector, ICellRangeSelector } from 'sql/base/browser/ui/table/plugins/cellRangeSelector'; export interface ICellSelectionModelOptions { cellRangeSelector?: any; selectActiveCell?: boolean; } const defaults: ICellSelectionModelOptions = { selectActiveCell: true }; export class CellSelectionModel implements Slick.SelectionModel> { private grid: Slick.Grid; private selector: ICellRangeSelector; private ranges: Array = []; private _handler = new Slick.EventHandler(); public onSelectedRangesChanged = new Slick.Event>(); constructor(private options: ICellSelectionModelOptions = defaults) { this.options = mixin(this.options, defaults, false); if (this.options.cellRangeSelector) { this.selector = this.options.cellRangeSelector; } else { // this is added by the noderequires above this.selector = new CellRangeSelector({ selectionCss: { 'border': '2px dashed grey' } }); } } public init(grid: Slick.Grid) { this.grid = grid; this._handler.subscribe(this.grid.onActiveCellChanged, (e: Event, args: Slick.OnActiveCellChangedEventArgs) => this.handleActiveCellChange(e, args)); this._handler.subscribe(this.grid.onKeyDown, (e: KeyboardEvent) => this.handleKeyDown(e)); this._handler.subscribe(this.grid.onHeaderClick, (e: MouseEvent, args: Slick.OnHeaderClickEventArgs) => this.handleHeaderClick(e, args)); this.grid.registerPlugin(this.selector); this._handler.subscribe(this.selector.onCellRangeSelected, (e: Event, range: Slick.Range) => this.handleCellRangeSelected(e, range)); this._handler.subscribe(this.selector.onBeforeCellRangeSelected, (e: Event, cell: Slick.Cell) => this.handleBeforeCellRangeSelected(e, cell)); } public destroy() { this._handler.unsubscribeAll(); this.grid.unregisterPlugin(this.selector); } private removeInvalidRanges(ranges: Array): Array { let result: Array = []; for (let i = 0; i < ranges.length; i++) { let r = ranges[i]; if (this.grid.canCellBeSelected(r.fromRow, r.fromCell) && this.grid.canCellBeSelected(r.toRow, r.toCell)) { result.push(r); } else if (this.grid.canCellBeSelected(r.fromRow, r.fromCell + 1) && this.grid.canCellBeSelected(r.toRow, r.toCell)) { // account for number row result.push(new Slick.Range(r.fromRow, r.fromCell + 1, r.toRow, r.toCell)); } } return result; } public setSelectedRanges(ranges: Array): void { // simple check for: empty selection didn't change, prevent firing onSelectedRangesChanged if ((!this.ranges || this.ranges.length === 0) && (!ranges || ranges.length === 0)) { return; } this.ranges = this.removeInvalidRanges(ranges); this.onSelectedRangesChanged.notify(this.ranges); } public getSelectedRanges() { return this.ranges; } private handleBeforeCellRangeSelected(e: Event, args: Slick.Cell) { if (this.grid.getEditorLock().isActive()) { e.stopPropagation(); return false; } return true; } private handleCellRangeSelected(e: Event, range: Slick.Range) { this.grid.setActiveCell(range.fromRow, range.fromCell, false, false, true); this.setSelectedRanges([range]); } private handleActiveCellChange(e: Event, args: Slick.OnActiveCellChangedEventArgs) { if (this.options.selectActiveCell && !isUndefinedOrNull(args.row) && !isUndefinedOrNull(args.cell)) { this.setSelectedRanges([new Slick.Range(args.row, args.cell)]); } else if (!this.options.selectActiveCell) { // clear the previous selection once the cell changes this.setSelectedRanges([]); } } private handleHeaderClick(e: MouseEvent, args: Slick.OnHeaderClickEventArgs) { if (!isUndefinedOrNull(args.column)) { let columnIndex = this.grid.getColumnIndex(args.column.id!); if (this.grid.canCellBeSelected(0, columnIndex)) { let ranges: Array; if (e.shiftKey) { ranges = this.getSelectedRanges(); ranges.push(new Slick.Range(0, columnIndex, this.grid.getDataLength() - 1, columnIndex)); } else { ranges = [new Slick.Range(0, columnIndex, this.grid.getDataLength() - 1, columnIndex)]; } this.grid.setActiveCell(0, columnIndex); this.setSelectedRanges(ranges); } } } private handleKeyDown(e: KeyboardEvent) { /*** * Кey codes * 37 left * 38 up * 39 right * 40 down */ let active = this.grid.getActiveCell(); let metaKey = e.ctrlKey || e.metaKey; if (active && e.shiftKey && !metaKey && !e.altKey && (e.which === 37 || e.which === 39 || e.which === 38 || e.which === 40)) { let ranges = this.getSelectedRanges(), last: Slick.Range; ranges = this.getSelectedRanges(); if (!ranges.length) { ranges.push(new Slick.Range(active.row, active.cell)); } // keyboard can work with last range only last = ranges.pop()!; // this is guarenteed since if ranges is empty we add one // can't handle selection out of active cell if (!last.contains(active.row, active.cell)) { last = new Slick.Range(active.row, active.cell); } let dRow = last.toRow - last.fromRow, dCell = last.toCell - last.fromCell, // walking direction dirRow = active.row === last.fromRow ? 1 : -1, dirCell = active.cell === last.fromCell ? 1 : -1; if (e.which === 37) { dCell -= dirCell; } else if (e.which === 39) { dCell += dirCell; } else if (e.which === 38) { dRow -= dirRow; } else if (e.which === 40) { dRow += dirRow; } // define new selection range let new_last = new Slick.Range(active.row, active.cell, active.row + dirRow * dRow, active.cell + dirCell * dCell); if (this.removeInvalidRanges([new_last]).length) { ranges.push(new_last); let viewRow = dirRow > 0 ? new_last.toRow : new_last.fromRow; let viewCell = dirCell > 0 ? new_last.toCell : new_last.fromCell; this.grid.scrollRowIntoView(viewRow, false); this.grid.scrollCellIntoView(viewRow, viewCell, false); } else { ranges.push(last); } this.setSelectedRanges(ranges); e.preventDefault(); e.stopPropagation(); } } }