devexpress-richedit
Version:
DevExpress Rich Text Editor is an advanced word-processing tool designed for working with rich text documents.
259 lines (258 loc) • 11.5 kB
JavaScript
import { EvtUtils } from '@devexpress/utils/lib/utils/evt';
import { TabLeaderType } from '../../../../layout/main-structures/layout-boxes/layout-tab-space-box';
import { TabAlign } from '../../../../model/paragraph/paragraph';
import { TabInfo } from '../../../../model/paragraph/paragraph-style';
import { MinMaxNumber } from '@devexpress/utils/lib/class/min-max';
import { UnitConverter } from '@devexpress/utils/lib/class/unit-converter';
import { DomUtils } from '@devexpress/utils/lib/utils/dom';
import { ListUtils } from '@devexpress/utils/lib/utils/list';
import { DocumentRenderer } from '../../../../canvas/renderes/common/document-renderer';
import { RichEditClientCommand } from '../../../../commands/client-command';
import { RULLER_NUMBER_CORRECTION } from '../../settings';
import { RulerMultiControl } from '../owner';
import { RulerShadow } from '../shadow';
import { RulerLineDisplayType, SnapTo } from '../vertical-line';
import { RulerTabUtils } from './utils';
const RULLER_TABMARK_Y_POSITION = 6;
var TabAction;
(function (TabAction) {
TabAction[TabAction["None"] = 0] = "None";
TabAction[TabAction["Insert"] = 1] = "Insert";
TabAction[TabAction["Delete"] = 2] = "Delete";
TabAction[TabAction["Move"] = 3] = "Move";
})(TabAction || (TabAction = {}));
export class RulerTabsControlState {
constructor(tabs, enabled) {
this.tabs = tabs;
this.enabled = enabled;
}
clone() {
return new RulerTabsControlState(ListUtils.deepCopy(this.tabs), this.enabled);
}
}
export class RulerTabViewState {
constructor(position, align) {
this.position = position;
this.align = align;
}
equals(obj) {
return this.position == obj.position && this.align == obj.align;
}
clone() {
return new RulerTabViewState(this.position, this.align);
}
}
export class RulerTabsControl extends RulerMultiControl {
constructor() {
super(...arguments);
this.tabAction = TabAction.None;
this.newTab = null;
this.deleteTab = false;
}
getModelState() {
this.modelData.showTabs = this.modelData.commandManager.getCommand(RichEditClientCommand.InsertTabRuler).getState().enabled;
if (this.modelData.showTabs) {
const paragraph = this.modelData.selection.activeSubDocument.getParagraphByPosition(this.modelData.selection.intervalsInfo.interval.start);
const tabs = ListUtils.map(paragraph.getTabs().positions, tab => {
const newTab = tab.clone();
newTab.position = UnitConverter.twipsToPixelsF(tab.position);
return newTab;
});
return new RulerTabsControlState(tabs, true);
}
else
return new RulerTabsControlState([], false);
}
updateView() {
const offset = this.controls.paragraphLeftPosition;
this.viewState = ListUtils.map(this.currModelState.tabs, tab => new RulerTabViewState(offset + tab.position, tab.alignment));
this.setCount(this.viewState.length);
ListUtils.forEach2(this.subControls, this.viewState, (control, viewState) => control.setValue(viewState));
}
createSubControl() {
return new RulerTabControl(this.modelData, this.controls);
}
isTabMarkZone(evt) {
return EvtUtils.getEventY(evt) - DomUtils.getAbsolutePositionY(this.controls.ruler.rootElement) >= RULLER_TABMARK_Y_POSITION;
}
onMouseDown(source, evt) {
if (!this.currModelState.enabled || this.modelData.isReadOnly)
return false;
const exactHit = this.calculateExactHit(source);
if (exactHit)
return true;
if (this.tryInsertNewTab(evt))
return true;
return false;
}
calculateExactHit(source) {
let exactHit = ListUtils.unsafeAnyOf(this.subControls, (subControl, index) => {
if (subControl.canHandle(source)) {
this.handleControlIndex = index;
this.updatePresentation();
return true;
}
return false;
});
return exactHit;
}
updatePresentation() {
this.controls.lineControl.show(RulerLineDisplayType.Normal);
this.activeSubControl.lineControlSetPosition();
}
tryInsertNewTab(evt) {
const pos = EvtUtils.getEventX(evt) - DomUtils.getAbsolutePositionX(this.controls.ruler.rootElement) - RULLER_NUMBER_CORRECTION
- this.controls.paragraphLeftPosition;
if (this.isTabMarkZone(evt) && pos >= this.minPosition() && pos <= this.maxPosition()) {
this.tabAction = TabAction.Insert;
this.handleControlIndex = this.currModelState.tabs.length;
const tabPosition = this.controls.chooseClosestAnchorPosition(pos, [], new MinMaxNumber(this.minPosition(), this.maxPosition()));
this.newTab = new TabInfo(tabPosition, this.controls.tabTypeBox.align, TabLeaderType.None, false, false);
this.currModelState.tabs.push(this.newTab.clone());
this.updateView();
this.updatePresentation();
return true;
}
return false;
}
onMouseMove(distance, source) {
const deleteTab = !DomUtils.isItParent(this.controls.ruler.rootElement, source);
if (deleteTab != this.deleteTab) {
this.deleteTab = deleteTab;
this.activeSubControl.setVisible(!this.deleteTab);
}
super.onMouseMove(distance, source);
if (this.tabAction == TabAction.None) {
this.tabAction = TabAction.Move;
this.activeSubControl.showShadow();
}
}
onMouseUp() {
this.activeSubControl.setVisible(!this.deleteTab);
switch (this.tabAction) {
case TabAction.Insert: {
if (this.deleteTab) {
this.currModelState = this.prevModelState.clone();
this.updateView();
}
else {
const tab = this.currModelState.tabs[this.handleControlIndex];
this.modelData.commandManager.getCommand(RichEditClientCommand.InsertTabRuler)
.execute(this.modelData.commandManager.isPublicApiCall, { position: tab.position, align: tab.alignment });
}
break;
}
case TabAction.Move: {
if (this.deleteTab) {
this.modelData.commandManager.getCommand(RichEditClientCommand.DeleteTabRuler)
.execute(this.modelData.commandManager.isPublicApiCall, this.prevModelState.tabs[this.handleControlIndex].position);
}
else {
this.modelData.commandManager.getCommand(RichEditClientCommand.MoveTabRuler)
.execute(this.modelData.commandManager.isPublicApiCall, {
start: this.prevModelState.tabs[this.handleControlIndex].position,
end: this.currModelState.tabs[this.handleControlIndex].position
});
}
break;
}
}
this.finishHandle();
}
finishHandle() {
super.finishHandle();
this.tabAction = TabAction.None;
this.newTab = null;
this.deleteTab = false;
this.subControls.forEach(control => control.setVisible(true));
}
minPosition() {
return Math.min(this.controls.leftIndent.currModelState.modelValue, this.controls.firstLineIndent.currModelState.modelValue);
}
maxPosition() {
return this.controls.paragraphRightPosition - this.controls.rightIndent.currModelState.modelValue - this.controls.paragraphLeftPosition;
}
calculateNewModelState(distance) {
switch (this.tabAction) {
case TabAction.Insert: {
const startPos = this.newTab.position;
const currTab = this.currModelState.tabs[this.handleControlIndex];
currTab.position = this.controls.chooseClosestAnchorPosition(startPos + distance, [startPos], new MinMaxNumber(this.minPosition(), this.maxPosition()));
break;
}
case TabAction.Move: {
const startPos = this.prevModelState.tabs[this.handleControlIndex].position;
const currTab = this.currModelState.tabs[this.handleControlIndex];
currTab.position = this.controls.chooseClosestAnchorPosition(startPos + distance, [startPos], new MinMaxNumber(this.minPosition(), this.maxPosition()));
break;
}
}
}
}
class RulerTabControl {
constructor(modelData, controls) {
this.viewState = new RulerTabViewState(0, -1);
this.shadow = null;
this.controls = controls;
this.modelData = modelData;
this.rootElement = DocumentRenderer.renderContainer(modelData.styles.tab.className + " " + RulerTabUtils.getSpriteClassName(this.viewState.align, modelData.styles));
this.applyTemplate();
controls.ruler.rootElement.appendChild(this.rootElement);
this.rootElement.style.marginTop = (controls.divisions.height - this.rootElement.offsetHeight) + "px";
this.setCorrection();
}
dispose() {
this.hideShadow();
DomUtils.hideNode(this.rootElement);
this.rootElement = null;
}
setVisible(visible) {
this.rootElement.style.display = visible ? 'block' : 'none';
}
canHandle(source) { return source == this.rootElement && this.modelData.showTabs; }
showShadow() {
this.shadow = new RulerShadow(this.rootElement);
}
hideShadow() {
if (this.shadow) {
this.shadow.dispose();
this.shadow = null;
}
}
lineControlSetPosition() {
this.controls.lineControl.setPosition(this.viewState.position, SnapTo.LeftSide);
}
setValue(viewState) {
if (!this.viewState || !viewState.equals(this.viewState)) {
this.viewState = viewState.clone();
this.changeAlign();
this.rootElement.style.left = viewState.position - this.leftCorrection + RULLER_NUMBER_CORRECTION + "px";
}
}
setCorrection() {
switch (this.viewState.align) {
case TabAlign.Left:
this.leftCorrection = 0;
break;
case TabAlign.Right:
this.leftCorrection = this.rootElement.offsetWidth;
break;
case TabAlign.Center:
case TabAlign.Decimal:
this.leftCorrection = Math.round(this.rootElement.offsetWidth / 2);
break;
}
}
changeAlign() {
this.rootElement.className = this.modelData.styles.tab.className + " " +
RulerTabUtils.getSpriteClassName(this.viewState.align, this.modelData.styles);
this.applyTemplate();
this.rootElement.title = this.modelData.titles[RulerTabUtils.getTabTitlePropertyName(this.viewState.align)];
this.setCorrection();
}
applyTemplate() {
const template = RulerTabUtils.getTemplate(this.viewState.align);
if (template)
this.rootElement.innerHTML = template;
}
}