devexpress-richedit
Version:
DevExpress Rich Text Editor is an advanced word-processing tool designed for working with rich text documents.
238 lines (237 loc) • 14.9 kB
JavaScript
import { LayoutPositionCreatorConflictFlags, LayoutPositionMainSubDocumentCreator, LayoutPositionOtherSubDocumentCreator } from '../../layout-engine/layout-position-creator';
import { DocumentLayoutDetailsLevel } from '../../layout/document-layout-details-level';
import { TableCellIterator } from '../../layout/table/table-cell-iterator';
import { Table } from '../../model/tables/main-structures/table';
import { TableCellUtils } from '../../model/tables/table-utils';
import { Errors } from '@devexpress/utils/lib/errors';
import { ListUtils } from '@devexpress/utils/lib/utils/list';
import { SearchUtils } from '@devexpress/utils/lib/utils/search';
import { ScrollState } from '../../scroll/model-states';
import { RichEditClientCommand } from '../client-command';
import { SelectionCommandBase, TableNavDirection } from './selection-command-base';
export class GoToLineVerticallyCommandBase extends SelectionCommandBase {
getDirection() {
return this.isForwardDirection() ? TableNavDirection.Down : TableNavDirection.Up;
}
getOppositeDirection() {
return this.isForwardDirection() ? TableNavDirection.Up : TableNavDirection.Down;
}
shouldCollapseTableSelectionToCaret() {
return this.extendSelection() &&
this.shouldCollapseSingleCellTableSelection(this.getOppositeDirection(), this.getDirection());
}
extendTableCellsSelection() {
const currentLayoutPosition = this.getInitialLayoutPosition();
if (!currentLayoutPosition)
return false;
const cellIterator = new TableCellIterator(currentLayoutPosition, this.control.layout, this.control.measurer);
const selectedCells = this.getSelectedCells();
const areCellsSelectedInSeries = this.selection.tableInfo.extendedData.areCellsSelectedInSeries;
const canGoToNextTableRow = this.canGoToNextTableRow(cellIterator);
if (this.extendSelection() && this.selection.tableSelectionAnchorCellPosition < 0 && canGoToNextTableRow && areCellsSelectedInSeries) {
const activeCell = this.selection.getTableSelectionActiveCell();
const currentCell = activeCell || (selectedCells.length > 0 && selectedCells[0].length > 0 ? selectedCells[0][0] : null);
if (currentCell) {
return this.selectTableCellsRange({
firstCell: currentCell,
lastCell: currentCell,
singleCellEntryDirection: this.getDirection(),
});
}
}
const hasSingleCellTableSelection = this.selection.tableSelectionAnchorCellPosition >= 0 &&
this.selection.tableSelectionAnchorCellPosition === this.selection.tableSelectionActiveCellPosition;
if (this.extendSelection() && (selectedCells.length > 1 || canGoToNextTableRow || hasSingleCellTableSelection) && areCellsSelectedInSeries) {
const table = selectedCells[0][0].parentRow.parentTable;
const anchorCell = this.selection.getTableSelectionAnchorCell();
const activeCell = this.selection.getTableSelectionActiveCell();
let isForward = (this.selection.isCollapsed() || hasSingleCellTableSelection)
? this.isForwardDirection()
: this.selection.forwardDirection;
let currentRow = activeCell ? activeCell.parentRow : this.getCurrentTableRow(selectedCells);
let currentRowIndex = SearchUtils.binaryIndexOf(table.rows, (row) => row.getStartPosition() - currentRow.getStartPosition());
if (this.checkTableRowIndex(currentRowIndex, table.rows.length)) {
let nextRow = this.getNextTableRow(table, currentRowIndex);
return this.performTableCellsSelection(nextRow, anchorCell, activeCell);
}
this.selection.saveVerticalTableReentrySnapshot(table, selectedCells);
this.performSelectionOnTheLastTableRow(isForward, selectedCells);
}
return false;
}
getSelectedCells() {
let selectedCells = ListUtils.map(this.selection.tableInfo.rawData.rows, (rowInfo) => ListUtils.map(rowInfo.cells, (cellInfo) => cellInfo.cell));
let position = LayoutPositionMainSubDocumentCreator.createLightLayoutPosition(this.control.layout, this.selection.activeSubDocument, this.getInitialSelectionEndPosition(), this.selection.pageIndex, DocumentLayoutDetailsLevel.Character, true, false);
if (!selectedCells.length && position.row.tableCellInfo) {
let modelTable = position.row.tableCellInfo.parentRow.parentTable.logicInfo.grid.table;
let modelRowIndex = position.row.tableCellInfo.parentRow.rowIndex;
selectedCells = [];
for (let i = 0; i <= modelRowIndex; i++)
selectedCells.push(modelTable.rows[i].cells);
}
return selectedCells;
}
getLogPositionByX(layoutPosition, x) {
var newLayoutPosition = layoutPosition.clone();
var xOffsetBoxLevel = Math.max(0, x - (newLayoutPosition.page.x + newLayoutPosition.pageArea.x + newLayoutPosition.column.x + newLayoutPosition.row.x));
var boxIndex = SearchUtils.normedInterpolationIndexOf(newLayoutPosition.row.boxes, (b) => b.x, xOffsetBoxLevel);
if (boxIndex < 0)
boxIndex = 0;
newLayoutPosition.box = newLayoutPosition.row.boxes[boxIndex];
var isNoVisibleBoxesInRow = false;
if (!newLayoutPosition.box.isVisible()) {
var lastVisibleBoxIndexInRow = newLayoutPosition.row.getLastVisibleBoxIndex();
if (lastVisibleBoxIndexInRow < 0) {
lastVisibleBoxIndexInRow = 0;
isNoVisibleBoxesInRow = true;
}
newLayoutPosition.boxIndex = lastVisibleBoxIndexInRow;
newLayoutPosition.box = newLayoutPosition.row.boxes[lastVisibleBoxIndexInRow];
}
var xOffsetCharLevel = isNoVisibleBoxesInRow ? 0 : xOffsetBoxLevel - newLayoutPosition.box.x;
return newLayoutPosition.getLogPosition(DocumentLayoutDetailsLevel.Box) + newLayoutPosition.box.calculateCharOffsetByPointX(this.control.measurer, xOffsetCharLevel);
}
isEndOfLine(layoutPosition, position) {
return position === layoutPosition.getLogPosition(DocumentLayoutDetailsLevel.Row) + layoutPosition.row.getLastBoxEndPositionInRow();
}
selectTableCellsRange(parameters) {
return this.control.commandManager.getCommand(RichEditClientCommand.SelectTableCellsRange).execute(this.control.commandManager.isPublicApiCall, parameters);
}
executeCore(_state, _options) {
if (this.shouldCollapseTableSelectionToCaret()) {
this.collapseSingleCellTableSelectionToCaret();
return true;
}
if (!this.extendSelection() && this.collapseTableCellSelectionSpatialFocus(this.isForwardDirection() ? TableNavDirection.Down : TableNavDirection.Up))
return true;
if (this.extendTableCellsSelection())
return true;
var selection = this.selection;
var layout = this.control.layout;
var subDocument = this.selection.activeSubDocument;
let initialLayoutPosition = this.getInitialLayoutPosition();
if (!initialLayoutPosition)
return false;
var keepX = selection.keepX;
if (keepX < 0)
keepX = initialLayoutPosition.page.x + initialLayoutPosition.pageArea.x + initialLayoutPosition.column.x + initialLayoutPosition.row.x + initialLayoutPosition.box.x +
initialLayoutPosition.box.getCharOffsetXInPixels(this.control.measurer, initialLayoutPosition.charOffset);
var newLayoutPosition;
var cellIterator = new TableCellIterator(initialLayoutPosition, layout, this.control.measurer);
if (this.canAdvanceToNextRow(cellIterator))
newLayoutPosition = cellIterator.getModifyPosition();
else {
var currentPosition = initialLayoutPosition.getLogPosition(DocumentLayoutDetailsLevel.Character);
newLayoutPosition = this.getNewLayoutPositionRowLevel(initialLayoutPosition);
if (newLayoutPosition && this.extendSelection()) {
var isEdgeDocumentPosition = currentPosition == 0 || currentPosition == subDocument.getDocumentEndPosition();
var needKeepCurrentLine = isEdgeDocumentPosition && currentPosition != this.getLogPositionByX(initialLayoutPosition, keepX);
if (needKeepCurrentLine)
newLayoutPosition = initialLayoutPosition.clone();
}
else if (!newLayoutPosition) {
var edgeLinePosition = this.getPositionForEdgeLine(initialLayoutPosition);
if (this.extendSelection() && currentPosition != edgeLinePosition) {
this.selection.changeState((newState) => newState.extendLastInterval(edgeLinePosition).setKeepX(keepX)
.setEndOfLine(this.isEndOfLine(initialLayoutPosition, edgeLinePosition)));
return true;
}
else if (!this.extendSelection()) {
var lastInterval = selection.lastSelectedInterval;
if (lastInterval && lastInterval.length > 0)
selection.deprecatedSetSelection(edgeLinePosition, edgeLinePosition, this.isEndOfLine(initialLayoutPosition, edgeLinePosition), keepX, true);
}
return false;
}
}
var newLogPosition = this.getLogPositionByX(newLayoutPosition, keepX);
const hasExplicitTableCellSelection = selection.hasExplicitTableCellSelection;
if (this.extendSelection() && !hasExplicitTableCellSelection) {
let tableAtNewPosition = Table.getTableByPosition(subDocument.tables, newLogPosition, true);
const snapshotTable = selection.getTableForSelectionRestore(subDocument);
if ((!tableAtNewPosition || (snapshotTable && tableAtNewPosition.getStartPosition() !== snapshotTable.getStartPosition())) && snapshotTable) {
const tableStart = snapshotTable.getStartPosition();
const tableEnd = snapshotTable.getEndPosition();
if (newLogPosition >= tableStart - 1 && newLogPosition <= tableEnd) {
const clampedPos = Math.max(tableStart, Math.min(newLogPosition, tableEnd - 1));
const probeTable = Table.getTableByPosition(subDocument.tables, clampedPos, true);
if (probeTable && probeTable.getStartPosition() === tableStart)
tableAtNewPosition = probeTable;
}
}
const snapshot = tableAtNewPosition ? selection.getVerticalTableReentrySnapshot(tableAtNewPosition) : null;
if (snapshot) {
const firstCell = Table.getTableCellByPositionFromTable(tableAtNewPosition, snapshot.firstCellPosition);
const lastCell = Table.getTableCellByPositionFromTable(tableAtNewPosition, snapshot.lastCellPosition);
if (firstCell && lastCell) {
const isRestored = this.selectTableCellsRange({
firstCell,
lastCell,
textSelectionAnchorPosition: snapshot.textSelectionAnchorPosition,
textSelectionActivePosition: snapshot.textSelectionActivePosition,
textSelectionEndOfLine: snapshot.textSelectionEndOfLine,
});
if (isRestored) {
selection.clearVerticalTableReentrySnapshot();
return true;
}
}
selection.clearVerticalTableReentrySnapshot();
}
}
if (newLogPosition == initialLayoutPosition.getLogPosition())
return false;
var endOfLine = this.isEndOfLine(newLayoutPosition, newLogPosition);
if (this.extendSelection()) {
this.selection.changeState((newState) => newState.extendLastInterval(newLogPosition).setKeepX(keepX).setEndOfLine(endOfLine));
this.selection.scrollManager.setScroll(new ScrollState()
.byModelPosition(this.selection)
.setModelPosition(newLogPosition)
.useStdRelativePosition()
.useStdOffset());
}
else
selection.deprecatedSetSelection(newLogPosition, newLogPosition, endOfLine, keepX, true);
return true;
}
getInitialLayoutPosition() {
const selection = this.selection;
const subDocument = selection.activeSubDocument;
const initialSelectionEndPosition = this.getInitialSelectionEndPosition();
const useBoxEndPos = selection.endOfLine;
return (subDocument.isMain()
? new LayoutPositionMainSubDocumentCreator(this.control.layout, subDocument, initialSelectionEndPosition, DocumentLayoutDetailsLevel.Character)
: new LayoutPositionOtherSubDocumentCreator(this.control.layout, subDocument, initialSelectionEndPosition, selection.pageIndex, DocumentLayoutDetailsLevel.Character))
.create(new LayoutPositionCreatorConflictFlags().setDefault(useBoxEndPos), new LayoutPositionCreatorConflictFlags().setDefault(false));
}
canGoToNextTableRow(_cellInterator) {
return false;
}
checkTableRowIndex(_rowIndex, _length) {
throw new Error(Errors.NotImplemented);
}
getCurrentTableRow(_selectedCells) {
throw new Error(Errors.NotImplemented);
}
getNextTableRow(_table, _rowIndex) {
throw new Error(Errors.NotImplemented);
}
performTableCellsSelection(nextRow, anchorCell, activeCell) {
const activeCellIndex = TableCellUtils.getCellIndexByColumnIndex(nextRow, TableCellUtils.getStartColumnIndex(activeCell));
const nextActiveCell = nextRow.cells[activeCellIndex];
const isShrinkToSingle = !!anchorCell && !!nextActiveCell &&
anchorCell.startParagraphPosition.value === nextActiveCell.startParagraphPosition.value &&
this.selection.tableSelectionAnchorCellPosition !== this.selection.tableSelectionActiveCellPosition;
return this.selectTableCellsRange({
firstCell: anchorCell,
lastCell: nextActiveCell,
singleCellShrinkDirection: isShrinkToSingle ? this.getDirection() : TableNavDirection.None,
});
}
performSelectionOnTheLastTableRow(_isForward, _selectedCells) {
throw new Error(Errors.NotImplemented);
}
isForwardDirection() {
throw new Error(Errors.NotImplemented);
}
}