devexpress-richedit
Version:
DevExpress Rich Text Editor is an advanced word-processing tool designed for working with rich text documents.
225 lines (224 loc) • 13 kB
JavaScript
import { MapCreator } from '../../utils/map-creator';
import { HeaderFooterInvalidatorHelper } from '../../layout-formatter/invalidator/header-footer-invalidator-helper';
import { LayoutPageFlags } from '../../layout/main-structures/layout-page';
import { SubDocumentInfoType } from '../../model/enums';
import { AnchorObjectHorizontalPositionType } from '../../model/floating-objects/enums';
import { ChangeFooterIndexHistoryItem, ChangeHeaderIndexHistoryItem } from '../../model/history/items/header-footer-history-items';
import { ModelIterator } from '../../model/model-iterator';
import { AnchoredPictureRun } from '../../model/runs/anchored-picture-run';
import { AnchoredTextBoxRun } from '../../model/runs/anchored-text-box-run';
import { RunType } from '../../model/runs/run-type';
import { SectionHeadersFooters } from '../../model/section/header-footer';
import { Section } from '../../model/section/section';
import { SubDocument } from '../../model/sub-document';
import { SelectionIntervalsInfo } from '../../selection/selection-intervals-info';
import { Errors } from '@devexpress/utils/lib/errors';
import { FixedInterval } from '@devexpress/utils/lib/intervals/fixed';
import { isEven } from '@devexpress/utils/lib/utils/common';
import { ListUtils } from '@devexpress/utils/lib/utils/list';
import { MathUtils } from '@devexpress/utils/lib/utils/math';
import { ReadOnlyMode } from '../../interfaces/i-rich-edit-core';
import { ScrollState } from '../../scroll/model-states';
import { RichEditClientCommand } from '../client-command';
import { CommandBase, CommandOptions, CommandSimpleOptions } from '../command-base';
import { SimpleCommandState } from '../command-states';
import { HeaderFooterCommandBase } from '../header-footer/header-footer-command-base';
class FindPageIndexHelper {
getPageIndex(control, subDoc) {
let pageIndex = 0;
for (let page; page = control.layoutFormatterManager.forceFormatPage(pageIndex); pageIndex++)
if (ListUtils.elementBy(this.getPageAreas(page.getLayoutOtherPageAreasInfo()), (pa) => pa && pa.subDocument.id == subDoc.id))
break;
return MathUtils.restrictValue(pageIndex, 0, control.selection.layout.pages.length - 1);
}
}
class FindHeaderPageIndexHelper extends FindPageIndexHelper {
getPageAreas(layoutPageHeaderFooterPageAreas) {
return [layoutPageHeaderFooterPageAreas.headerPageArea];
}
}
class FindFooterPageIndexHelper extends FindPageIndexHelper {
getPageAreas(layoutPageHeaderFooterPageAreas) {
return [layoutPageHeaderFooterPageAreas.footerPageArea];
}
}
class FindTextBoxPageIndexHelper extends FindPageIndexHelper {
getPageAreas(layoutPageHeaderFooterPageAreas) {
return layoutPageHeaderFooterPageAreas.textBoxesPageAreas;
}
}
export class ChangeActiveSubDocumentToHeaderFooterByPageIndexCommandParameters extends CommandOptions {
constructor(control, pageIndex, isHeader) {
super(control);
this.pageIndex = pageIndex;
this.isHeader = isHeader;
}
}
export class ChangeActiveSubDocumentCommandBase extends CommandBase {
getState() {
return new SimpleCommandState(this.isEnabled());
}
finishChanges(newPageIndex, newActiveSubDoc, startSelection) {
const selection = this.selection;
if (!this.setNewSelectionProps(selection, this.control.modelManager.model, newPageIndex, newActiveSubDoc))
return false;
startSelection = this.validateSelectionPosition(newActiveSubDoc, startSelection);
selection.changeState((newState) => newState.setPosition(startSelection).resetKeepX().setEndOfLine(false).setSubDocument(newActiveSubDoc).setPageIndex(newPageIndex));
if (this.isNeedScrollAfter())
selection.scrollManager.setScroll(new ScrollState().byModelPosition(selection).setModelPosition(startSelection).useStdRelativePosition().useStdOffset());
return true;
}
canSetSelectionBeforeRun(run) {
return !((run instanceof AnchoredPictureRun || run instanceof AnchoredTextBoxRun) &&
run.anchorInfo.horizontalPositionType != AnchorObjectHorizontalPositionType.Character);
}
validateSelectionPosition(subDocument, position) {
const iterator = new ModelIterator(subDocument, true);
iterator.setPosition(position);
do
if (iterator.charOffset > 0 || this.canSetSelectionBeforeRun(iterator.run))
break;
while (iterator.moveToNextRun());
return iterator.getAbsolutePosition();
}
invalidatePages(targetPageIndex, currActiveSubDocInfo) {
if (currActiveSubDocInfo.isHeaderFooter) {
const headerFooterInvalidatorHelper = new HeaderFooterInvalidatorHelper(this.control.modelManager.model, this.control.layout, currActiveSubDocInfo.headerFooterType);
headerFooterInvalidatorHelper.initByPageIndex(targetPageIndex);
this.control.layoutFormatterManager.invalidator.onPagesChanged(headerFooterInvalidatorHelper.startPageIndex, headerFooterInvalidatorHelper.endPageIndex);
}
else
this.control.layoutFormatterManager.invalidator.onPagesChanged(targetPageIndex, targetPageIndex + 1);
}
setNewSelectionProps(selection, model, newPageIndex, newSubDocument) {
if (selection.pageIndex == newPageIndex && newSubDocument == selection.activeSubDocument)
return false;
if (newSubDocument.isTextBox() && (!selection.specialRunInfo.isSelectedAnchorObject ||
selection.specialRunInfo.getTextBoxInnerSubDocumentId() != newSubDocument.id)) {
const parentSubDoc = model.subDocuments[newSubDocument.info.parentSubDocumentId];
const iterator = new ModelIterator(parentSubDoc, true);
iterator.setPosition(0);
do
if (iterator.run.getType() == RunType.AnchoredTextBoxRun && iterator.run.subDocId == newSubDocument.id)
break;
while (iterator.moveToNextRun());
selection.specialRunInfo.init(SelectionIntervalsInfo.fromInterval(parentSubDoc, new FixedInterval(iterator.getAbsolutePosition(), 1)), this.modelManipulator.model);
}
return true;
}
isNeedScrollAfter() {
return false;
}
isEnabledInReadOnlyMode() {
return true;
}
}
export class ChangeActiveSubDocumentToMainCommand extends ChangeActiveSubDocumentCommandBase {
executeCore(_state, options) {
const selection = this.selection;
const targetPageIndex = selection.pageIndex;
if (targetPageIndex < 0)
return false;
this.invalidatePages(targetPageIndex, options.subDocument.info);
let targetLayoutPage = this.control.layoutFormatterManager.forceFormatPage(targetPageIndex);
if (!targetLayoutPage)
targetLayoutPage = selection.layout.getLastValidPage();
return this.finishChanges(-1, this.control.modelManager.model.mainSubDocument, targetLayoutPage ? targetLayoutPage.getPosition() : 0);
}
}
export class ChangeActiveSubDocumentToHeaderFooterByPageIndexCommand extends ChangeActiveSubDocumentCommandBase {
executeCore(_state, params) {
const targetPageIndex = params.pageIndex;
if (targetPageIndex >= this.control.layout.pages.length)
return false;
this.invalidatePages(targetPageIndex, this.selection.activeSubDocument.info);
const targetLayoutPage = this.control.layoutFormatterManager.forceFormatPage(targetPageIndex);
if (!targetLayoutPage)
return false;
this.history.beginTransaction();
const newSubDocument = this.getNewSubDocument(targetLayoutPage, params.isHeader);
if (!newSubDocument)
return false;
const res = this.finishChanges(targetPageIndex, newSubDocument, 0);
this.history.endTransaction();
return res;
}
getNewSubDocument(layoutPage, isHeader) {
const pageAreas = layoutPage.getLayoutOtherPageAreasInfo();
const pageArea = isHeader ? pageAreas.headerPageArea : pageAreas.footerPageArea;
if (pageArea)
return pageArea.subDocument;
if (this.control.modelManager.model.isDocumentProtectionEnabled || this.control.readOnly == ReadOnlyMode.Persistent)
return null;
const sections = this.control.modelManager.model.sections;
const sectionIndex = Section.getPageSectionIndex(this.control.layoutFormatterManager.forceFormatPage(layoutPage.index), sections);
const headerFooterType = SectionHeadersFooters.getActualObjectType(sections[sectionIndex], layoutPage.flags.get(LayoutPageFlags.IsFirstPageOfSection), isEven(layoutPage.layoutPageIndex));
return this.insertSubDocument(isHeader, sectionIndex, headerFooterType);
}
insertSubDocument(isHeader, sectionIndex, headerFooterType) {
const newObjectIndex = this.createHeaderFooter(isHeader, sectionIndex, headerFooterType);
return HeaderFooterCommandBase.getObjectsCache(isHeader, this.control)[newObjectIndex].getSubDocument(this.control.modelManager.model);
}
createHeaderFooter(isHeader, sectionIndex, type) {
const objectIndex = this.control.modelManager.modelManipulator.header.getHeaderFooterManipulator(isHeader).createObject(type);
this.changeHeaderFooterObjectIndex(isHeader, sectionIndex, type, objectIndex);
return objectIndex;
}
changeHeaderFooterObjectIndex(isHeader, sectionIndex, headerFooterType, newIndex) {
this.history.addAndRedo(new (isHeader ? ChangeHeaderIndexHistoryItem : ChangeFooterIndexHistoryItem)(this.modelManipulator, sectionIndex, headerFooterType, newIndex, (oldIndex) => {
if (this.control.selection.activeSubDocument.isHeaderFooter() && oldIndex == -1)
this.control.commandManager.getCommand(RichEditClientCommand.ChangeActiveSubDocumentToMain)
.execute(this.control.commandManager.isPublicApiCall);
}));
}
isNeedScrollAfter() {
return true;
}
isEnabled() {
return super.isEnabled() && this.control.innerClientProperties.viewsSettings.isPrintLayoutView;
}
}
export class ChangeActiveSubDocumentToHeaderFooterOrTextBoxBySubDocumentCommand extends ChangeActiveSubDocumentCommandBase {
executeCore(_state, options) {
const newActiveSubDoc = options.param;
let targetPageIndex;
if (this.selection.specialRunInfo.isTextBoxSelected() &&
this.selection.specialRunInfo.getTextBoxInnerSubDocumentId() == newActiveSubDoc.id &&
this.selection.pageIndex >= 0)
targetPageIndex = this.selection.pageIndex;
else
targetPageIndex = newActiveSubDoc.info.getType() != SubDocumentInfoType.Main ? ChangeActiveSubDocumentToHeaderFooterBySubDocumentCommand.MapTypeToPageIndexHelper[newActiveSubDoc.info.getType()]
.getPageIndex(this.control, newActiveSubDoc) : this.selection.pageIndex;
this.invalidatePages(targetPageIndex, this.selection.activeSubDocument.info);
return this.control.layoutFormatterManager.forceFormatPage(targetPageIndex) && this.finishChanges(targetPageIndex, newActiveSubDoc, 0);
}
isNeedScrollAfter() {
return true;
}
}
ChangeActiveSubDocumentToHeaderFooterOrTextBoxBySubDocumentCommand.MapTypeToPageIndexHelper = new MapCreator()
.add(SubDocumentInfoType.Header, new FindHeaderPageIndexHelper())
.add(SubDocumentInfoType.Footer, new FindFooterPageIndexHelper())
.add(SubDocumentInfoType.TextBox, new FindTextBoxPageIndexHelper())
.get();
export class ChangeActiveSubDocumentToHeaderFooterBySubDocumentCommand extends ChangeActiveSubDocumentToHeaderFooterOrTextBoxBySubDocumentCommand {
isEnabled() {
return super.isEnabled() && this.control.innerClientProperties.viewsSettings.isPrintLayoutView;
}
}
export class ChangeActiveSubDocumentToTextBoxCommand extends ChangeActiveSubDocumentToHeaderFooterOrTextBoxBySubDocumentCommand {
getState() {
if (!this.isEnabled())
return new SimpleCommandState(false);
const specInfo = this.selection.specialRunInfo;
if (!specInfo.isTextBoxSelected())
return new SimpleCommandState(false);
const run = this.selection.activeSubDocument.getRunByPosition(specInfo.getTextBoxPosition());
return new SimpleCommandState(run.getType() == RunType.AnchoredTextBoxRun, this.control.modelManager.model.subDocuments[run.subDocId]);
}
executeCore(state, _options) {
if (state.value instanceof SubDocument)
return super.executeCore(state, new CommandSimpleOptions(this.control, state.value));
throw new Error(Errors.InternalException);
}
}