Warning, file /office/calligra/libs/text/KoTextEditor.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /* This file is part of the KDE project
0002  * Copyright (C) 2009-2012 Pierre Stirnweiss <pstirnweiss@googlemail.com>
0003  * Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
0004  * Copyright (c) 2011 Boudewijn Rempt <boud@kogmbh.com>
0005  * Copyright (C) 2011-2015 C. Boemann <cbo@boemann.dk>
0006  * Copyright (C) 2014-2015 Denis Kuplyakov <dener.kup@gmail.com>
0007  * Copyright (C) 2015 Soma Schliszka <soma.schliszka@gmail.com>
0008  *
0009  * This library is free software; you can redistribute it and/or
0010  * modify it under the terms of the GNU Library General Public
0011  * License as published by the Free Software Foundation; either
0012  * version 2 of the License, or (at your option) any later version.
0013  *
0014  * This library is distributed in the hope that it will be useful,
0015  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0016  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0017  * Library General Public License for more details.
0018  *
0019  * You should have received a copy of the GNU Library General Public License
0020  * along with this library; see the file COPYING.LIB.  If not, write to
0021  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0022  * Boston, MA 02110-1301, USA.
0023  */
0024 
0025 #include "KoTextEditor.h"
0026 #include "KoTextEditor_p.h"
0027 
0028 #include "KoList.h"
0029 #include "KoBookmark.h"
0030 #include "KoAnnotation.h"
0031 #include "KoTextRangeManager.h"
0032 #include "KoInlineTextObjectManager.h"
0033 #include "KoInlineNote.h"
0034 #include "KoInlineCite.h"
0035 #include "BibliographyGenerator.h"
0036 #include <KoTextShapeDataBase.h>
0037 #include <KoSelection.h>
0038 #include <KoShapeController.h>
0039 #include <KoShapeManager.h>
0040 #include <KoCanvasBase.h>
0041 #include "KoShapeAnchor.h"
0042 #include "KoTextDocument.h"
0043 #include "KoTextLocator.h"
0044 #include "KoTableOfContentsGeneratorInfo.h"
0045 #include "KoBibliographyInfo.h"
0046 #include "changetracker/KoChangeTracker.h"
0047 #include "changetracker/KoChangeTrackerElement.h"
0048 #include "styles/KoCharacterStyle.h"
0049 #include "styles/KoParagraphStyle.h"
0050 #include "styles/KoStyleManager.h"
0051 #include "styles/KoTableCellStyle.h"
0052 #include "styles/KoTableStyle.h"
0053 #include "KoTableColumnAndRowStyleManager.h"
0054 #include "commands/DeleteTableRowCommand.h"
0055 #include "commands/DeleteTableColumnCommand.h"
0056 #include "commands/InsertTableRowCommand.h"
0057 #include "commands/InsertTableColumnCommand.h"
0058 #include "commands/ResizeTableCommand.h"
0059 #include "commands/TextPasteCommand.h"
0060 #include "commands/ListItemNumberingCommand.h"
0061 #include "commands/ChangeListCommand.h"
0062 #include "commands/InsertInlineObjectCommand.h"
0063 #include "commands/DeleteCommand.h"
0064 #include "commands/DeleteAnchorsCommand.h"
0065 #include "commands/DeleteAnnotationsCommand.h"
0066 #include "commands/InsertNoteCommand.h"
0067 #include "commands/AddTextRangeCommand.h"
0068 #include "commands/AddAnnotationCommand.h"
0069 #include "commands/RenameSectionCommand.h"
0070 #include "commands/NewSectionCommand.h"
0071 #include "commands/SplitSectionsCommand.h"
0072 
0073 #include <klocalizedstring.h>
0074 
0075 #include <QTextList>
0076 #include <QTextBlock>
0077 #include <QTextBlockFormat>
0078 #include <QTextCharFormat>
0079 #include <QTextDocument>
0080 #include <QTextDocumentFragment>
0081 #include <QTextFormat>
0082 #include <QTextTable>
0083 #include <QTextTableCell>
0084 #include <kundo2command.h>
0085 
0086 #include "TextDebug.h"
0087 #include "KoTextDebug.h"
0088 
0089 Q_DECLARE_METATYPE(QTextFrame*)
0090 
0091 /*Private*/
0092 
0093 KoTextEditor::Private::Private(KoTextEditor *qq, QTextDocument *document)
0094     : q(qq)
0095     , document (document)
0096     , addNewCommand(true)
0097     , dummyMacroAdded(false)
0098     , customCommandCount(0)
0099     , editProtectionCached(false)
0100 {
0101     caret = QTextCursor(document);
0102     editorState = NoOp;
0103 }
0104 
0105 void KoTextEditor::Private::emitTextFormatChanged()
0106 {
0107     emit q->textFormatChanged();
0108 }
0109 
0110 void KoTextEditor::Private::newLine(KUndo2Command *parent)
0111 {
0112     // Handle if this is the special block before a table
0113     bool hiddenTableHandling = caret.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable);
0114     if (hiddenTableHandling) {
0115         // Easy solution is to go back to the end of previous block and do the insertion from there.
0116         // However if there is no block before we have a problem. This may be the case if there is
0117         // a table before or we are at the beginning of a cell or a document.
0118         // So here is a better approach
0119         // 1) create block
0120         // 2) select the previous block so it get's deleted and replaced
0121         // 3) remove HiddenByTable from both new and previous block
0122         // 4) actually make new line replacing the block we just inserted
0123         // 5) set HiddenByTable on the block just before the table again
0124         caret.insertText("oops you should never see this");
0125         caret.insertBlock();
0126         caret.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
0127         caret.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
0128         QTextBlockFormat bf = caret.blockFormat();
0129         bf.clearProperty(KoParagraphStyle::HiddenByTable);
0130         caret.setBlockFormat(bf);
0131    }
0132 
0133 
0134     if (caret.hasSelection()) {
0135         q->deleteChar(false, parent);
0136     }
0137     KoTextDocument textDocument(document);
0138     KoStyleManager *styleManager = textDocument.styleManager();
0139     KoParagraphStyle *nextStyle = 0;
0140     KoParagraphStyle *currentStyle = 0;
0141     if (styleManager) {
0142         int id = caret.blockFormat().intProperty(KoParagraphStyle::StyleId);
0143         currentStyle = styleManager->paragraphStyle(id);
0144         if (currentStyle == 0) // not a style based parag.  Lets make the next one correct.
0145             nextStyle = styleManager->defaultParagraphStyle();
0146         else
0147             nextStyle = styleManager->paragraphStyle(currentStyle->nextStyle());
0148         Q_ASSERT(nextStyle);
0149         if (currentStyle == nextStyle)
0150             nextStyle = 0;
0151     }
0152 
0153     QTextCharFormat format = caret.charFormat();
0154     if (format.hasProperty(KoCharacterStyle::ChangeTrackerId)) {
0155         format.clearProperty(KoCharacterStyle::ChangeTrackerId);
0156     }
0157 
0158     // Build the block format and subtract the properties that are not inherited
0159     QTextBlockFormat bf = caret.blockFormat();
0160 
0161     bf.clearProperty(KoParagraphStyle::BreakBefore);
0162     bf.clearProperty(KoParagraphStyle::ListStartValue);
0163     bf.clearProperty(KoParagraphStyle::UnnumberedListItem);
0164     bf.clearProperty(KoParagraphStyle::IsListHeader);
0165     bf.clearProperty(KoParagraphStyle::MasterPageName);
0166     bf.clearProperty(KoParagraphStyle::OutlineLevel);
0167     bf.clearProperty(KoParagraphStyle::HiddenByTable);
0168 
0169     // We should stay in the same section so we can't start new one.
0170     bf.clearProperty(KoParagraphStyle::SectionStartings);
0171     // But we move all the current endings to the next paragraph.
0172     QTextBlockFormat origin = caret.blockFormat();
0173     origin.clearProperty(KoParagraphStyle::SectionEndings);
0174     caret.setBlockFormat(origin);
0175 
0176     // Build the block char format which is just a copy
0177     QTextCharFormat bcf = caret.blockCharFormat();
0178 
0179     // Actually insert the new paragraph char
0180     int startPosition = caret.position();
0181 
0182     caret.insertBlock(bf, bcf);
0183 
0184     int endPosition = caret.position();
0185 
0186     // Mark the CR as a tracked change
0187     QTextCursor changeCursor(document);
0188     changeCursor.beginEditBlock();
0189     changeCursor.setPosition(startPosition);
0190     changeCursor.setPosition(endPosition, QTextCursor::KeepAnchor);
0191     changeCursor.endEditBlock();
0192 
0193     q->registerTrackedChange(changeCursor, KoGenChange::InsertChange, kundo2_i18n("New Paragraph"), format, format, false);
0194 
0195     // possibly change the style if requested
0196     if (nextStyle) {
0197         QTextBlock block = caret.block();
0198         if (currentStyle)
0199             currentStyle->unapplyStyle(block);
0200         nextStyle->applyStyle(block);
0201         format = block.charFormat();
0202     }
0203 
0204     caret.setCharFormat(format);
0205 
0206     if (hiddenTableHandling) {
0207         // see code and comment above
0208         QTextBlockFormat bf = caret.blockFormat();
0209         bf.setProperty(KoParagraphStyle::HiddenByTable, true);
0210         caret.setBlockFormat(bf);
0211         caret.movePosition(QTextCursor::PreviousCharacter);
0212     }
0213 }
0214 
0215 /*KoTextEditor*/
0216 
0217 //TODO factor out the changeTracking charFormat setting from all individual slots to a public slot, which will be available for external commands (TextShape)
0218 
0219 //The BlockFormatVisitor and CharFormatVisitor are used when a property needs to be modified relative to its current value (which could be different over the selection). For example: increase indentation by 10pt.
0220 //The BlockFormatVisitor is also used for the change tracking of a blockFormat. The changeTracker stores the information about the changeId in the charFormat. The BlockFormatVisitor ensures that thd changeId is set on the whole block (even if only a part of the block is actually selected).
0221 //Should such mechanisms be later provided directly by Qt, we could dispose of these classes.
0222 
0223 
0224 KoTextEditor::KoTextEditor(QTextDocument *document)
0225     : QObject(document),
0226       d (new Private(this, document))
0227 {
0228     connect (d->document, SIGNAL(undoCommandAdded()), this, SLOT(documentCommandAdded()));
0229 }
0230 
0231 KoTextEditor::~KoTextEditor()
0232 {
0233     delete d;
0234 }
0235 
0236 KoTextEditor *KoTextEditor::getTextEditorFromCanvas(KoCanvasBase *canvas)
0237 {
0238     KoSelection *selection = canvas->shapeManager()->selection();
0239     if (selection) {
0240         foreach(KoShape *shape, selection->selectedShapes()) {
0241             if (KoTextShapeDataBase *textData = qobject_cast<KoTextShapeDataBase*>(shape->userData())) {
0242                 KoTextDocument doc(textData->document());
0243                 return doc.textEditor();
0244             }
0245         }
0246     }
0247     return 0;
0248 }
0249 
0250 QTextCursor* KoTextEditor::cursor()
0251 {
0252     return &(d->caret);
0253 }
0254 
0255 const QTextCursor KoTextEditor::constCursor() const
0256 {
0257     return QTextCursor(d->caret);
0258 }
0259 
0260 void KoTextEditor::registerTrackedChange(QTextCursor &selection, KoGenChange::Type changeType, const KUndo2MagicString &title, QTextFormat& format, QTextFormat& prevFormat, bool applyToWholeBlock)
0261 {
0262     KoChangeTracker *changeTracker = KoTextDocument(d->document).changeTracker();
0263     if (!changeTracker || !changeTracker->recordChanges()) {
0264         // clear the ChangeTrackerId from the passed in selection, without recursively registring
0265         // change tracking again  ;)
0266         int start = qMin(selection.position(), selection.anchor());
0267         int end = qMax(selection.position(), selection.anchor());
0268 
0269         QTextBlock block = selection.block();
0270         if (block.position() > start)
0271             block = block.document()->findBlock(start);
0272 
0273         while (block.isValid() && block.position() < end) {
0274             QTextBlock::iterator iter = block.begin();
0275             while (!iter.atEnd()) {
0276                 QTextFragment fragment = iter.fragment();
0277                 if (fragment.position() > end) {
0278                     break;
0279                 }
0280 
0281                 if (fragment.position() + fragment.length() <= start) {
0282                     ++iter;
0283                     continue;
0284                 }
0285 
0286                 QTextCursor cursor(block);
0287                 cursor.setPosition(fragment.position());
0288                 QTextCharFormat fm = fragment.charFormat();
0289 
0290                 if (fm.hasProperty(KoCharacterStyle::ChangeTrackerId)) {
0291                     fm.clearProperty(KoCharacterStyle::ChangeTrackerId);
0292                     int to = qMin(end, fragment.position() + fragment.length());
0293                     cursor.setPosition(to, QTextCursor::KeepAnchor);
0294                     cursor.setCharFormat(fm);
0295                     iter = block.begin();
0296                 } else {
0297                     ++iter;
0298                 }
0299             }
0300             block = block.next();
0301         }
0302     } else {
0303         if (changeType != KoGenChange::DeleteChange) {
0304             //first check if there already is an identical change registered just before or just after the selection. If so, merge appropriately.
0305             //TODO implement for format change. handle the prevFormat/newFormat check.
0306             QTextCursor checker = QTextCursor(selection);
0307             int idBefore = 0;
0308             int idAfter = 0;
0309             int changeId = 0;
0310             int selectionBegin = qMin(checker.anchor(), checker.position());
0311             int selectionEnd = qMax(checker.anchor(), checker.position());
0312 
0313             checker.setPosition(selectionBegin);
0314             if (!checker.atBlockStart()) {
0315                 int changeId = checker.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt();
0316                 if (changeId && changeTracker->elementById(changeId)->getChangeType() == changeType)
0317                     idBefore = changeId;
0318             } else {
0319                 if (!checker.currentTable()) {
0320                     int changeId = checker.blockFormat().intProperty(KoCharacterStyle::ChangeTrackerId);
0321                     if (changeId && changeTracker->elementById(changeId)->getChangeType() == changeType)
0322                         idBefore = changeId;
0323                 } else {
0324                     idBefore = checker.currentTable()->format().intProperty(KoCharacterStyle::ChangeTrackerId);
0325                     if (!idBefore) {
0326                         idBefore = checker.currentTable()->cellAt(checker).format().intProperty(KoCharacterStyle::ChangeTrackerId);
0327                     }
0328                 }
0329             }
0330 
0331             checker.setPosition(selectionEnd);
0332             if (!checker.atEnd()) {
0333                 checker.movePosition(QTextCursor::NextCharacter);
0334                 idAfter = changeTracker->mergeableId(changeType, title, checker.charFormat().property( KoCharacterStyle::ChangeTrackerId ).toInt());
0335             }
0336             changeId = (idBefore)?idBefore:idAfter;
0337 
0338             switch (changeType) {//TODO: this whole thing actually needs to be done like a visitor. If the selection contains several change regions, the parenting needs to be individualised.
0339             case KoGenChange::InsertChange:
0340                 if (!changeId)
0341                     changeId = changeTracker->getInsertChangeId(title, 0);
0342                 break;
0343             case KoGenChange::FormatChange:
0344                 if (!changeId)
0345                     changeId = changeTracker->getFormatChangeId(title, format, prevFormat, 0);
0346                 break;
0347             case KoGenChange::DeleteChange:
0348                 //this should never be the case
0349                 break;
0350             default:
0351                 ;// do nothing
0352             }
0353 
0354             if (applyToWholeBlock) {
0355                 selection.movePosition(QTextCursor::StartOfBlock);
0356                 selection.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
0357             }
0358 
0359             QTextCharFormat f;
0360             f.setProperty(KoCharacterStyle::ChangeTrackerId, changeId);
0361             selection.mergeCharFormat(f);
0362 
0363             QTextBlock startBlock = selection.document()->findBlock(selection.anchor());
0364             QTextBlock endBlock = selection.document()->findBlock(selection.position());
0365 
0366             while (startBlock.isValid() && startBlock != endBlock) {
0367                 startBlock = startBlock.next();
0368                 QTextCursor cursor(startBlock);
0369                 QTextBlockFormat blockFormat;
0370                 blockFormat.setProperty(KoCharacterStyle::ChangeTrackerId, changeId);
0371                 cursor.mergeBlockFormat(blockFormat);
0372 
0373                 QTextCharFormat blockCharFormat = cursor.blockCharFormat();
0374                 if (blockCharFormat.hasProperty(KoCharacterStyle::ChangeTrackerId)) {
0375                     blockCharFormat.clearProperty(KoCharacterStyle::ChangeTrackerId);
0376                     cursor.setBlockCharFormat(blockCharFormat);
0377                 }
0378             }
0379         }
0380     }
0381 }
0382 
0383 // To figure out if a the blocks of the selection are write protected we need to
0384 // traverse the entire document as sections build up the protectiveness recursively.
0385 void KoTextEditor::recursivelyVisitSelection(QTextFrame::iterator it, KoTextVisitor &visitor) const
0386 {
0387     do {
0388         if (visitor.abortVisiting())
0389             return;
0390 
0391         QTextBlock block = it.currentBlock();
0392         QTextTable *table = qobject_cast<QTextTable*>(it.currentFrame());
0393         QTextFrame *subFrame = it.currentFrame();
0394         if (table) {
0395             // There are 4 ways this table can be selected:
0396             //  - "before to mid"
0397             //  - "mid to after"
0398             //  - "complex mid to mid"
0399             //  - "simple mid to mid"
0400             // The 3 first are entire cells, the fourth is within a cell
0401 
0402             if (d->caret.selectionStart() <= table->lastPosition()
0403                     && d->caret.selectionEnd() >= table->firstPosition()) {
0404                 // We have a selection somewhere
0405                 QTextTableCell cell1 = table->cellAt(d->caret.selectionStart());
0406                 QTextTableCell cell2 = table->cellAt(d->caret.selectionEnd());
0407                 if (cell1 != cell2 || !cell1.isValid() || !cell2.isValid()) {
0408                     // And the selection is complex or entire table
0409                     int selectionRow;
0410                     int selectionColumn;
0411                     int selectionRowSpan;
0412                     int selectionColumnSpan;
0413                     if (!cell1.isValid() || !cell2.isValid()) {
0414                         // entire table
0415                         visitor.visitTable(table, KoTextVisitor::Entirely);
0416                         selectionRow = selectionColumn = 0;
0417                         selectionRowSpan = table->rows();
0418                         selectionColumnSpan = table->columns();
0419                     } else {
0420                         visitor.visitTable(table, KoTextVisitor::Partly);
0421                         d->caret.selectedTableCells(&selectionRow, &selectionRowSpan, &selectionColumn, &selectionColumnSpan);
0422                     }
0423 
0424                     for (int r = selectionRow; r < selectionRow + selectionRowSpan; r++) {
0425                         for (int c = selectionColumn; c < selectionColumn +
0426                              selectionColumnSpan; c++) {
0427                             QTextTableCell cell = table->cellAt(r,c);
0428                             if (!cell.format().boolProperty(KoTableCellStyle::CellIsProtected)) {
0429                                 visitor.visitTableCell(&cell, KoTextVisitor::Partly);
0430                                 recursivelyVisitSelection(cell.begin(), visitor);
0431                             } else {
0432                                 visitor.nonVisit();
0433                             }
0434 
0435                             if (visitor.abortVisiting())
0436                                 return;
0437                         }
0438                     }
0439                 } else {
0440                     visitor.visitTable(table, KoTextVisitor::Partly);
0441                     // And the selection is simple
0442                     if (!cell1.format().boolProperty(KoTableCellStyle::CellIsProtected)) {
0443                         visitor.visitTableCell(&cell1, KoTextVisitor::Entirely);
0444                         recursivelyVisitSelection(cell1.begin(), visitor);
0445                     } else {
0446                         visitor.nonVisit();
0447                     }
0448                     return;
0449                 }
0450             }
0451             if (d->caret.selectionEnd() <= table->lastPosition()) {
0452                 return;
0453             }
0454         } else if (subFrame) {
0455             recursivelyVisitSelection(subFrame->begin(), visitor);
0456         } else {
0457             // TODO build up the section stack
0458 
0459             if (d->caret.selectionStart() < block.position() + block.length()
0460                     && d->caret.selectionEnd() >= block.position()) {
0461                 // We have a selection somewhere
0462                 if (true) { // TODO don't change if block is protected by section
0463                     visitor.visitBlock(block, d->caret);
0464                 } else {
0465                     visitor.nonVisit();
0466                 }
0467             }
0468 
0469             // TODO tear down the section stack
0470 
0471             if (d->caret.selectionEnd() < block.position() + block.length()) {
0472                 return;
0473             }
0474         }
0475         if (!it.atEnd()) {
0476             ++it;
0477         }
0478     } while (!it.atEnd());
0479 }
0480 
0481 KoBookmark *KoTextEditor::addBookmark(const QString &name)
0482 {//TODO changeTracking
0483     KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Add Bookmark"));
0484 
0485     KoBookmark *bookmark = new KoBookmark(d->caret);
0486     bookmark->setName(name);
0487     bookmark->setManager(KoTextDocument(d->document).textRangeManager());
0488 
0489     addCommand(new AddTextRangeCommand(bookmark, topCommand));
0490 
0491     endEditBlock();
0492 
0493     return bookmark;
0494 }
0495 KoTextRangeManager *KoTextEditor::textRangeManager() const
0496 {
0497     return KoTextDocument(d->document).textRangeManager();
0498 }
0499 
0500 KoAnnotation *KoTextEditor::addAnnotation(KoShape *annotationShape)
0501 {
0502     KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Add Annotation"));
0503 
0504     KoAnnotation *annotation = new KoAnnotation(d->caret);
0505     KoTextRangeManager *textRangeManager = KoTextDocument(d->document).textRangeManager();
0506     annotation->setManager(textRangeManager);
0507     //FIXME: I need the name, a unique name, we can set selected text as annotation name or use createUniqueAnnotationName function
0508     // to do it for us.
0509     QString name = annotation->createUniqueAnnotationName(textRangeManager->annotationManager(), "", false);
0510     annotation->setName(name);
0511     annotation->setAnnotationShape(annotationShape);
0512 
0513     addCommand(new AddAnnotationCommand(annotation, topCommand));
0514 
0515     endEditBlock();
0516 
0517     return annotation;
0518 }
0519 
0520 KoInlineObject *KoTextEditor::insertIndexMarker()
0521 {//TODO changeTracking
0522     if (isEditProtected()) {
0523         return 0;
0524     }
0525 
0526     d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Insert Index"));
0527 
0528     if (d->caret.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) {
0529         d->newLine(0);
0530     }
0531 
0532     QTextBlock block = d->caret.block();
0533     if (d->caret.position() >= block.position() + block.length() - 1)
0534         return 0; // can't insert one at end of text
0535     if (block.text().at( d->caret.position() - block.position()).isSpace())
0536         return 0; // can't insert one on a whitespace as that does not indicate a word.
0537 
0538     KoTextLocator *tl = new KoTextLocator();
0539     KoTextDocument(d->document).inlineTextObjectManager()->insertInlineObject(d->caret, tl);
0540     d->updateState(KoTextEditor::Private::NoOp);
0541     return tl;
0542 }
0543 
0544 void KoTextEditor::insertInlineObject(KoInlineObject *inliner, KUndo2Command *cmd)
0545 {
0546     if (isEditProtected()) {
0547         return;
0548     }
0549 
0550     KUndo2Command *topCommand = cmd;
0551     if (!cmd) {
0552         topCommand = beginEditBlock(kundo2_i18n("Insert Variable"));
0553     }
0554 
0555     if (d->caret.hasSelection()) {
0556         deleteChar(false, topCommand);
0557     }
0558     d->caret.beginEditBlock();
0559 
0560 
0561     if (d->caret.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) {
0562         d->newLine(0);
0563     }
0564 
0565     QTextCharFormat format = d->caret.charFormat();
0566     if (format.hasProperty(KoCharacterStyle::ChangeTrackerId)) {
0567         format.clearProperty(KoCharacterStyle::ChangeTrackerId);
0568     }
0569 
0570     InsertInlineObjectCommand *insertInlineObjectCommand = new InsertInlineObjectCommand(inliner, d->document, topCommand);
0571     Q_UNUSED(insertInlineObjectCommand);
0572     d->caret.endEditBlock();
0573  
0574     if (!cmd) {
0575         addCommand(topCommand);
0576         endEditBlock();
0577     }
0578 
0579     emit cursorPositionChanged();
0580 }
0581 
0582 void KoTextEditor::updateInlineObjectPosition(int start, int end)
0583 {
0584     KoInlineTextObjectManager *inlineObjectManager = KoTextDocument(d->document).inlineTextObjectManager();
0585     // and, of course, every inline object after the current position has the wrong position
0586     QTextCursor cursor = d->document->find(QString(QChar::ObjectReplacementCharacter), start);
0587     while (!cursor.isNull() && (end > -1 && cursor.position() < end )) {
0588         QTextCharFormat fmt = cursor.charFormat();
0589         KoInlineObject *obj = inlineObjectManager->inlineTextObject(fmt);
0590         obj->updatePosition(d->document, cursor.position(), fmt);
0591         cursor = d->document->find(QString(QChar::ObjectReplacementCharacter), cursor.position());
0592     }
0593 
0594 }
0595 
0596 void KoTextEditor::removeAnchors(const QList<KoShapeAnchor*> &anchors, KUndo2Command *parent)
0597 {
0598     Q_ASSERT(parent);
0599     addCommand(new DeleteAnchorsCommand(anchors, d->document, parent));
0600 }
0601 
0602 void KoTextEditor::removeAnnotations(const QList<KoAnnotation *> &annotations, KUndo2Command *parent)
0603 {
0604     Q_ASSERT(parent);
0605     addCommand(new DeleteAnnotationsCommand(annotations, d->document, parent));
0606 }
0607 
0608 void KoTextEditor::insertFrameBreak()
0609 {
0610     if (isEditProtected()) {
0611         return;
0612     }
0613 
0614     QTextCursor curr(d->caret.block());
0615     if (dynamic_cast<QTextTable *> (curr.currentFrame())) {
0616         return;
0617     }
0618 
0619     d->updateState(KoTextEditor::Private::KeyPress, kundo2_i18n("Insert Break"));
0620     QTextBlock block = d->caret.block();
0621     if (d->caret.position() == block.position() && block.length() > 0) { // start of parag
0622         QTextBlockFormat bf = d->caret.blockFormat();
0623         bf.setProperty(KoParagraphStyle::BreakBefore, KoText::PageBreak);
0624         d->caret.insertBlock(bf);
0625         if (block.textList())
0626             block.textList()->remove(block);
0627     } else {
0628         QTextBlockFormat bf = d->caret.blockFormat();
0629         if (!d->caret.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) {
0630             newLine();
0631         }
0632         bf = d->caret.blockFormat();
0633         bf.setProperty(KoParagraphStyle::BreakBefore, KoText::PageBreak);
0634         d->caret.setBlockFormat(bf);
0635     }
0636     d->updateState(KoTextEditor::Private::NoOp);
0637     emit cursorPositionChanged();
0638 }
0639 
0640 void KoTextEditor::paste(KoCanvasBase *canvas, const QMimeData *mimeData, bool pasteAsText)
0641 {
0642     if (isEditProtected()) {
0643         return;
0644     }
0645 
0646     KoShapeController *shapeController = KoTextDocument(d->document).shapeController();
0647 
0648     addCommand(new TextPasteCommand(mimeData,
0649                                     d->document,
0650                                     shapeController,
0651                                     canvas, 0,
0652                                     pasteAsText));
0653 }
0654 
0655 void KoTextEditor::deleteChar(bool previous, KUndo2Command *parent)
0656 {
0657     if (isEditProtected()) {
0658         return;
0659     }
0660 
0661     KoShapeController *shapeController = KoTextDocument(d->document).shapeController();
0662 
0663     // Find out if we should track changes or not
0664 //    KoChangeTracker *changeTracker = KoTextDocument(d->document).changeTracker();
0665 //    bool trackChanges = false;
0666 //    if (changeTracker && changeTracker->recordChanges()) {
0667 //        trackChanges = true;
0668 //    }
0669 
0670     if (previous) {
0671         if (!d->caret.hasSelection() && d->caret.block().blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) {
0672             movePosition(QTextCursor::PreviousCharacter);
0673             if (d->caret.block().length() <= 1) {
0674                 movePosition(QTextCursor::NextCharacter);
0675             } else
0676                 return; // it becomes just a cursor movement;
0677         }
0678     } else {
0679         if (!d->caret.hasSelection() && d->caret.block().length() > 1) {
0680             QTextCursor tmpCursor = d->caret;
0681             tmpCursor.movePosition(QTextCursor::NextCharacter);
0682             if (tmpCursor.block().blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) {
0683                 movePosition(QTextCursor::NextCharacter);
0684                 return; // it becomes just a cursor movement;
0685             }
0686         }
0687     }
0688 
0689     if (previous) {
0690         addCommand(new DeleteCommand(DeleteCommand::PreviousChar,
0691                                         d->document,
0692                                         shapeController, parent));
0693     } else {
0694         addCommand(new DeleteCommand(DeleteCommand::NextChar,
0695                                         d->document,
0696                                         shapeController, parent));
0697     }
0698 }
0699 
0700 void KoTextEditor::toggleListNumbering(bool numberingEnabled)
0701 {
0702     if (isEditProtected()) {
0703         return;
0704     }
0705 
0706     addCommand(new ListItemNumberingCommand(block(), numberingEnabled));
0707     emit textFormatChanged();
0708 }
0709 
0710 void KoTextEditor::setListProperties(const KoListLevelProperties &llp,
0711                                      ChangeListFlags flags, KUndo2Command *parent)
0712 {
0713     if (isEditProtected()) {
0714         return;
0715     }
0716 
0717     if (flags & AutoListStyle && d->caret.block().textList() == 0) {
0718         flags = MergeWithAdjacentList;
0719     }
0720 
0721     if (KoList *list = KoTextDocument(d->document).list(d->caret.block().textList())) {
0722         KoListStyle *listStyle = list->style();
0723         if (KoStyleManager *styleManager = KoTextDocument(d->document).styleManager()) {
0724             QList<KoParagraphStyle *> paragraphStyles = styleManager->paragraphStyles();
0725             foreach (KoParagraphStyle *paragraphStyle, paragraphStyles) {
0726                 if (paragraphStyle->listStyle() == listStyle ||
0727                         (paragraphStyle->list() && paragraphStyle->list()->style() == listStyle)) {
0728                     flags = NoFlags;
0729                     break;
0730                 }
0731             }
0732         }
0733     }
0734 
0735     addCommand(new ChangeListCommand(d->caret, llp, flags, parent));
0736     emit textFormatChanged();
0737 }
0738 
0739 
0740 int KoTextEditor::anchor() const
0741 {
0742     return d->caret.anchor();
0743 }
0744 
0745 bool KoTextEditor::atBlockEnd() const
0746 {
0747     return d->caret.atBlockEnd();
0748 }
0749 
0750 bool KoTextEditor::atBlockStart() const
0751 {
0752     return d->caret.atBlockStart();
0753 }
0754 
0755 bool KoTextEditor::atEnd() const
0756 {
0757     QTextCursor cursor(d->caret.document()->rootFrame()->lastCursorPosition());
0758     cursor.movePosition(QTextCursor::PreviousCharacter);
0759     QTextFrame *auxFrame = cursor.currentFrame();
0760 
0761     if (auxFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) {
0762         //auxFrame really is the auxillary frame
0763         if (d->caret.position() == auxFrame->firstPosition() - 1) {
0764             return true;
0765         }
0766         return false;
0767     }
0768     return d->caret.atEnd();
0769 }
0770 
0771 bool KoTextEditor::atStart() const
0772 {
0773     return d->caret.atStart();
0774 }
0775 
0776 QTextBlock KoTextEditor::block() const
0777 {
0778     return d->caret.block();
0779 }
0780 
0781 int KoTextEditor::blockNumber() const
0782 {
0783     return d->caret.blockNumber();
0784 }
0785 
0786 void KoTextEditor::clearSelection()
0787 {
0788     d->caret.clearSelection();
0789 }
0790 
0791 int KoTextEditor::columnNumber() const
0792 {
0793     return d->caret.columnNumber();
0794 }
0795 
0796 void KoTextEditor::deleteChar()
0797 {
0798     if (isEditProtected()) {
0799         return;
0800     }
0801 
0802     if (!d->caret.hasSelection()) {
0803         if (d->caret.atEnd())
0804             return;
0805 
0806         // We alson need to refuse delete if we are at final pos in table cell
0807         if (QTextTable *table = d->caret.currentTable()) {
0808             QTextTableCell cell = table->cellAt(d->caret.position());
0809             if (d->caret.position() == cell.lastCursorPosition().position()) {
0810                 return;
0811             }
0812         }
0813 
0814         // We also need to refuse delete if it will delete a note frame
0815         QTextCursor after(d->caret);
0816         after.movePosition(QTextCursor::NextCharacter);
0817 
0818         QTextFrame *beforeFrame = d->caret.currentFrame();
0819         while (qobject_cast<QTextTable *>(beforeFrame)) {
0820             beforeFrame = beforeFrame->parentFrame();
0821         }
0822 
0823         QTextFrame *afterFrame = after.currentFrame();
0824         while (qobject_cast<QTextTable *>(afterFrame)) {
0825             afterFrame = afterFrame->parentFrame();
0826         }
0827         if (beforeFrame != afterFrame) {
0828             return;
0829         }
0830     }
0831 
0832 
0833     deleteChar(false);
0834 
0835     emit cursorPositionChanged();
0836 }
0837 
0838 void KoTextEditor::deletePreviousChar()
0839 {
0840     if (isEditProtected()) {
0841         return;
0842     }
0843 
0844     if (!d->caret.hasSelection()) {
0845         if (d->caret.atStart())
0846             return;
0847 
0848         // We also need to refuse delete if we are at first pos in table cell
0849         if (QTextTable *table = d->caret.currentTable()) {
0850             QTextTableCell cell = table->cellAt(d->caret.position());
0851             if (d->caret.position() == cell.firstCursorPosition().position()) {
0852                 return;
0853             }
0854         }
0855 
0856         // We also need to refuse delete if it will delete a note frame
0857         QTextCursor after(d->caret);
0858         after.movePosition(QTextCursor::PreviousCharacter);
0859 
0860         QTextFrame *beforeFrame = d->caret.currentFrame();
0861         while (qobject_cast<QTextTable *>(beforeFrame)) {
0862             beforeFrame = beforeFrame->parentFrame();
0863         }
0864 
0865         QTextFrame *afterFrame = after.currentFrame();
0866         while (qobject_cast<QTextTable *>(afterFrame)) {
0867             afterFrame = afterFrame->parentFrame();
0868         }
0869 
0870         if (beforeFrame != afterFrame) {
0871             return;
0872         }
0873     }
0874 
0875     deleteChar(true);
0876 
0877     emit cursorPositionChanged();
0878 }
0879 
0880 QTextDocument *KoTextEditor::document() const
0881 {
0882     return d->caret.document();
0883 }
0884 
0885 bool KoTextEditor::hasComplexSelection() const
0886 {
0887     return d->caret.hasComplexSelection();
0888 }
0889 
0890 bool KoTextEditor::hasSelection() const
0891 {
0892     return d->caret.hasSelection();
0893 }
0894 
0895 
0896 class ProtectionCheckVisitor : public KoTextVisitor
0897 {
0898 public:
0899     ProtectionCheckVisitor(const KoTextEditor *editor)
0900         : KoTextVisitor(const_cast<KoTextEditor *>(editor))
0901     {
0902     }
0903 
0904     // override super's implementation to not waste cpu cycles
0905     void visitBlock(QTextBlock&, const QTextCursor &) override
0906     {
0907     }
0908 
0909     void nonVisit() override
0910     {
0911         setAbortVisiting(true);
0912     }
0913 };
0914 
0915 bool KoTextEditor::isEditProtected(bool useCached) const
0916 {
0917     ProtectionCheckVisitor visitor(this);
0918 
0919     if (useCached) {
0920         if (! d->editProtectionCached) {
0921             recursivelyVisitSelection(d->document->rootFrame()->begin(), visitor);
0922             d->editProtected = visitor.abortVisiting();
0923             d->editProtectionCached = true;
0924         }
0925         return d->editProtected;
0926     }
0927     d->editProtectionCached = false;
0928     recursivelyVisitSelection(d->document->rootFrame()->begin(), visitor);
0929     return visitor.abortVisiting();
0930 }
0931 
0932 void KoTextEditor::insertTable(int rows, int columns)
0933 {
0934     if (isEditProtected() || rows <= 0 || columns <= 0) {
0935         return;
0936     }
0937 
0938     bool hasSelection = d->caret.hasSelection();
0939     if (!hasSelection) {
0940         d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Insert Table"));
0941     } else {
0942         KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Insert Table"));
0943         deleteChar(false, topCommand);
0944         d->caret.beginEditBlock();
0945     }
0946 
0947     QTextTableFormat tableFormat;
0948 
0949     tableFormat.setWidth(QTextLength(QTextLength::PercentageLength, 100));
0950     tableFormat.setProperty(KoTableStyle::CollapsingBorders, true);
0951     tableFormat.setMargin(5);
0952 
0953     KoChangeTracker *changeTracker = KoTextDocument(d->document).changeTracker();
0954     if (changeTracker && changeTracker->recordChanges()) {
0955         QTextCharFormat charFormat = d->caret.charFormat();
0956         QTextBlockFormat blockFormat = d->caret.blockFormat();
0957         KUndo2MagicString title = kundo2_i18n("Insert Table");
0958 
0959         int changeId;
0960         if (!d->caret.atBlockStart()) {
0961             changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, charFormat.intProperty(KoCharacterStyle::ChangeTrackerId));
0962         } else {
0963             changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, blockFormat.intProperty(KoCharacterStyle::ChangeTrackerId));
0964         }
0965 
0966         if (!changeId) {
0967             changeId = changeTracker->getInsertChangeId(title, 0);
0968         }
0969 
0970         tableFormat.setProperty(KoCharacterStyle::ChangeTrackerId, changeId);
0971     }
0972 
0973     QTextBlock currentBlock = d->caret.block();
0974     if (d->caret.position() != currentBlock.position()) {
0975         d->caret.insertBlock();
0976         currentBlock = d->caret.block();
0977     }
0978 
0979     QTextTable *table = d->caret.insertTable(rows, columns, tableFormat);
0980 
0981     // Get (and thus create) columnandrowstyle manager so it becomes part of undo
0982     // and not something that happens uncontrollably during layout
0983     KoTableColumnAndRowStyleManager::getManager(table);
0984 
0985     // 'Hide' the block before the table
0986     QTextBlockFormat blockFormat = currentBlock.blockFormat();
0987     QTextCursor cursor(currentBlock);
0988     blockFormat.setProperty(KoParagraphStyle::HiddenByTable, true);
0989     cursor.setBlockFormat(blockFormat);
0990 
0991     // Define the initial cell format
0992     QTextTableCellFormat format;
0993     KoTableCellStyle cellStyle;
0994     cellStyle.setEdge(KoBorder::TopBorder, KoBorder::BorderSolid, 2, QColor(Qt::black));
0995     cellStyle.setEdge(KoBorder::LeftBorder, KoBorder::BorderSolid, 2, QColor(Qt::black));
0996     cellStyle.setEdge(KoBorder::BottomBorder, KoBorder::BorderSolid, 2, QColor(Qt::black));
0997     cellStyle.setEdge(KoBorder::RightBorder, KoBorder::BorderSolid, 2, QColor(Qt::black));
0998     cellStyle.setPadding(5);
0999     cellStyle.applyStyle(format);
1000 
1001     // Apply formatting to all cells
1002     for (int row = 0; row < table->rows(); ++row) {
1003         for (int col = 0; col < table->columns(); ++col) {
1004             QTextTableCell cell = table->cellAt(row, col);
1005             cell.setFormat(format);
1006         }
1007     }
1008 
1009     if (hasSelection) {
1010         d->caret.endEditBlock();
1011         endEditBlock();
1012     } else {
1013         d->updateState(KoTextEditor::Private::NoOp);
1014     }
1015 
1016     emit cursorPositionChanged();
1017 }
1018 
1019 void KoTextEditor::insertTableRowAbove()
1020 {
1021     if (isEditProtected()) {
1022         return;
1023     }
1024 
1025     QTextTable *table = d->caret.currentTable();
1026     if (table) {
1027         addCommand(new InsertTableRowCommand(this, table, false));
1028     }
1029 }
1030 
1031 void KoTextEditor::insertTableRowBelow()
1032 {
1033     if (isEditProtected()) {
1034         return;
1035     }
1036 
1037     QTextTable *table = d->caret.currentTable();
1038     if (table) {
1039         addCommand(new InsertTableRowCommand(this, table, true));
1040     }
1041 }
1042 
1043 void KoTextEditor::insertTableColumnLeft()
1044 {
1045     if (isEditProtected()) {
1046         return;
1047     }
1048 
1049     QTextTable *table = d->caret.currentTable();
1050     if (table) {
1051         addCommand(new InsertTableColumnCommand(this, table, false));
1052     }
1053 }
1054 
1055 void KoTextEditor::insertTableColumnRight()
1056 {
1057     if (isEditProtected()) {
1058         return;
1059     }
1060 
1061     QTextTable *table = d->caret.currentTable();
1062     if (table) {
1063         addCommand(new InsertTableColumnCommand(this, table, true));
1064     }
1065 }
1066 
1067 void KoTextEditor::deleteTableColumn()
1068 {
1069     if (isEditProtected()) {
1070         return;
1071     }
1072 
1073     QTextTable *table = d->caret.currentTable();
1074     if (table) {
1075         addCommand(new DeleteTableColumnCommand(this, table));
1076     }
1077 }
1078 
1079 void KoTextEditor::deleteTableRow()
1080 {
1081     if (isEditProtected()) {
1082         return;
1083     }
1084 
1085     QTextTable *table = d->caret.currentTable();
1086     if (table) {
1087         addCommand(new DeleteTableRowCommand(this, table));
1088     }
1089 }
1090 
1091 void KoTextEditor::mergeTableCells()
1092 {
1093     if (isEditProtected()) {
1094         return;
1095     }
1096 
1097     d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Merge Cells"));
1098 
1099     QTextTable *table = d->caret.currentTable();
1100 
1101     if (table) {
1102         table->mergeCells(d->caret);
1103     }
1104 
1105     d->updateState(KoTextEditor::Private::NoOp);
1106 }
1107 
1108 void KoTextEditor::splitTableCells()
1109 {
1110     if (isEditProtected()) {
1111         return;
1112     }
1113 
1114     d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Split Cells"));
1115 
1116     QTextTable *table = d->caret.currentTable();
1117 
1118     if (table) {
1119         QTextTableCell cell = table->cellAt(d->caret);
1120         table->splitCell(cell.row(), cell.column(),  1, 1);
1121     }
1122 
1123     d->updateState(KoTextEditor::Private::NoOp);
1124 }
1125 
1126 void KoTextEditor::adjustTableColumnWidth(QTextTable *table, int column, qreal width, KUndo2Command *parentCommand)
1127 {
1128     ResizeTableCommand *cmd = new ResizeTableCommand(table, true, column, width, parentCommand);
1129 
1130     addCommand(cmd);
1131 }
1132 
1133 
1134 void KoTextEditor::adjustTableRowHeight(QTextTable *table, int column, qreal height, KUndo2Command *parentCommand)
1135 {
1136     ResizeTableCommand *cmd = new ResizeTableCommand(table, false, column, height, parentCommand);
1137 
1138     addCommand(cmd);
1139 }
1140 
1141 void KoTextEditor::adjustTableWidth(QTextTable *table, qreal dLeft, qreal dRight)
1142 {
1143     d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Adjust Table Width"));
1144     d->caret.beginEditBlock();
1145     QTextTableFormat fmt = table->format();
1146     if (dLeft) {
1147         fmt.setLeftMargin(fmt.leftMargin() + dLeft);
1148     }
1149     if (dRight) {
1150         fmt.setRightMargin(fmt.rightMargin() + dRight);
1151     }
1152     table->setFormat(fmt);
1153     d->caret.endEditBlock();
1154     d->updateState(KoTextEditor::Private::NoOp);
1155 }
1156 
1157 void KoTextEditor::setTableBorderData(QTextTable *table, int row, int column,
1158          KoBorder::BorderSide cellSide, const KoBorder::BorderData &data)
1159 {
1160     d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Change Border Formatting"));
1161     d->caret.beginEditBlock();
1162     QTextTableCell cell = table->cellAt(row, column);
1163     QTextCharFormat fmt = cell.format();
1164     KoBorder border = fmt.property(KoTableCellStyle::Borders).value<KoBorder>();
1165 
1166     border.setBorderData(cellSide, data);
1167     fmt.setProperty(KoTableCellStyle::Borders, QVariant::fromValue<KoBorder>(border));
1168     cell.setFormat(fmt);
1169     d->caret.endEditBlock();
1170     d->updateState(KoTextEditor::Private::NoOp);
1171 }
1172 
1173 KoInlineNote *KoTextEditor::insertFootNote()
1174 {
1175     if (isEditProtected()) {
1176         return 0;
1177     }
1178 
1179     InsertNoteCommand *cmd = new InsertNoteCommand(KoInlineNote::Footnote, d->document);
1180     addCommand(cmd);
1181 
1182     emit cursorPositionChanged();
1183     return cmd->m_inlineNote;
1184 }
1185 
1186 KoInlineNote *KoTextEditor::insertEndNote()
1187 {
1188     if (isEditProtected()) {
1189         return 0;
1190     }
1191 
1192     InsertNoteCommand *cmd = new InsertNoteCommand(KoInlineNote::Endnote, d->document);
1193     addCommand(cmd);
1194 
1195     emit cursorPositionChanged();
1196     return cmd->m_inlineNote;
1197 }
1198 
1199 void KoTextEditor::insertTableOfContents(KoTableOfContentsGeneratorInfo *info)
1200 {
1201     if (isEditProtected()) {
1202         return;
1203     }
1204 
1205     bool hasSelection = d->caret.hasSelection();
1206     if (!hasSelection) {
1207         d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Insert Table Of Contents"));
1208     } else {
1209         KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Insert Table Of Contents"));
1210         deleteChar(false, topCommand);
1211         d->caret.beginEditBlock();
1212     }
1213 
1214     QTextBlockFormat tocFormat;
1215     KoTableOfContentsGeneratorInfo *newToCInfo = info->clone();
1216     QTextDocument *tocDocument = new QTextDocument();
1217     tocFormat.setProperty(KoParagraphStyle::TableOfContentsData, QVariant::fromValue<KoTableOfContentsGeneratorInfo *>(newToCInfo) );
1218     tocFormat.setProperty(KoParagraphStyle::GeneratedDocument, QVariant::fromValue<QTextDocument*>(tocDocument));
1219 
1220     //make sure we set up the textrangemanager on the subdocument as well
1221     KoTextDocument(tocDocument).setTextRangeManager(new KoTextRangeManager);
1222 
1223     KoChangeTracker *changeTracker = KoTextDocument(d->document).changeTracker();
1224     if (changeTracker && changeTracker->recordChanges()) {
1225         QTextCharFormat charFormat = d->caret.charFormat();
1226         QTextBlockFormat blockFormat = d->caret.blockFormat();
1227         KUndo2MagicString title = kundo2_i18n("Insert Table Of Contents");
1228 
1229         int changeId;
1230         if (!d->caret.atBlockStart()) {
1231             changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, charFormat.intProperty(KoCharacterStyle::ChangeTrackerId));
1232         } else {
1233             changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, blockFormat.intProperty(KoCharacterStyle::ChangeTrackerId));
1234         }
1235 
1236         if (!changeId) {
1237             changeId = changeTracker->getInsertChangeId(title, 0);
1238         }
1239 
1240         tocFormat.setProperty(KoCharacterStyle::ChangeTrackerId, changeId);
1241     }
1242 
1243     d->caret.insertBlock();
1244     d->caret.movePosition(QTextCursor::Left);
1245     d->caret.insertBlock(tocFormat);
1246     d->caret.movePosition(QTextCursor::Right);
1247 
1248     if (hasSelection) {
1249         d->caret.endEditBlock();
1250         endEditBlock();
1251     } else {
1252         d->updateState(KoTextEditor::Private::NoOp);
1253     }
1254 
1255     emit cursorPositionChanged();
1256 }
1257 
1258 void KoTextEditor::setTableOfContentsConfig(KoTableOfContentsGeneratorInfo *info, const QTextBlock &block)
1259 {
1260     if (isEditProtected()) {
1261         return;
1262     }
1263 
1264     KoTableOfContentsGeneratorInfo *newToCInfo=info->clone();
1265 
1266     d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Modify Table Of Contents"));
1267 
1268     QTextCursor cursor(block);
1269     QTextBlockFormat tocBlockFormat=block.blockFormat();
1270 
1271     tocBlockFormat.setProperty(KoParagraphStyle::TableOfContentsData, QVariant::fromValue<KoTableOfContentsGeneratorInfo*>(newToCInfo) );
1272     cursor.setBlockFormat(tocBlockFormat);
1273 
1274     d->updateState(KoTextEditor::Private::NoOp);
1275     emit cursorPositionChanged();
1276     const_cast<QTextDocument *>(document())->markContentsDirty(document()->firstBlock().position(), 0);
1277 }
1278 
1279 void KoTextEditor::insertBibliography(KoBibliographyInfo *info)
1280 {
1281     bool hasSelection = d->caret.hasSelection();
1282     if (!hasSelection) {
1283         d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Insert Bibliography"));
1284     } else {
1285         KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Insert Bibliography"));
1286         deleteChar(false, topCommand);
1287         d->caret.beginEditBlock();
1288     }
1289 
1290     QTextBlockFormat bibFormat;
1291     KoBibliographyInfo *newBibInfo = info->clone();
1292     QTextDocument *bibDocument = new QTextDocument();
1293 
1294     bibFormat.setProperty( KoParagraphStyle::BibliographyData, QVariant::fromValue<KoBibliographyInfo*>(newBibInfo));
1295     bibFormat.setProperty( KoParagraphStyle::GeneratedDocument, QVariant::fromValue<QTextDocument*>(bibDocument));
1296 
1297     //make sure we set up the textrangemanager on the subdocument as well
1298     KoTextDocument(bibDocument).setTextRangeManager(new KoTextRangeManager);
1299 
1300     KoChangeTracker *changeTracker = KoTextDocument(d->document).changeTracker();
1301     if (changeTracker && changeTracker->recordChanges()) {
1302         QTextCharFormat charFormat = d->caret.charFormat();
1303         QTextBlockFormat blockFormat = d->caret.blockFormat();
1304         KUndo2MagicString title = kundo2_i18n("Insert Bibliography");
1305 
1306         int changeId;
1307         if (!d->caret.atBlockStart()) {
1308             changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, charFormat.intProperty(KoCharacterStyle::ChangeTrackerId));
1309         } else {
1310             changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, blockFormat.intProperty(KoCharacterStyle::ChangeTrackerId));
1311         }
1312 
1313         if (!changeId) {
1314             changeId = changeTracker->getInsertChangeId(title, 0);
1315         }
1316 
1317         bibFormat.setProperty(KoCharacterStyle::ChangeTrackerId, changeId);
1318     }
1319 
1320     d->caret.insertBlock();
1321     d->caret.movePosition(QTextCursor::Left);
1322     d->caret.insertBlock(bibFormat);
1323     d->caret.movePosition(QTextCursor::Right);
1324 
1325     new BibliographyGenerator(bibDocument, block(), newBibInfo);
1326 
1327     if (hasSelection) {
1328         d->caret.endEditBlock();
1329         endEditBlock();
1330     } else {
1331         d->updateState(KoTextEditor::Private::NoOp);
1332     }
1333 
1334     emit cursorPositionChanged();
1335 }
1336 
1337 KoInlineCite *KoTextEditor::insertCitation()
1338 {
1339     bool hasSelection = d->caret.hasSelection();
1340     if (!hasSelection) {
1341         d->updateState(KoTextEditor::Private::KeyPress, kundo2_i18n("Add Citation"));
1342     } else {
1343         KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Add Citation"));
1344         deleteChar(false, topCommand);
1345         d->caret.beginEditBlock();
1346     }
1347 
1348     KoInlineCite *cite = new KoInlineCite(KoInlineCite::Citation);
1349     KoInlineTextObjectManager *manager = KoTextDocument(d->document).inlineTextObjectManager();
1350     manager->insertInlineObject(d->caret,cite);
1351 
1352     if (hasSelection) {
1353         d->caret.endEditBlock();
1354         endEditBlock();
1355     } else {
1356         d->updateState(KoTextEditor::Private::NoOp);
1357     }
1358 
1359     return cite;
1360 }
1361 
1362 void KoTextEditor::insertText(const QString &text, const QString &hRef)
1363 {
1364     if (isEditProtected()) {
1365         return;
1366     }
1367 
1368     bool hasSelection = d->caret.hasSelection();
1369     if (!hasSelection) {
1370         d->updateState(KoTextEditor::Private::KeyPress, kundo2_i18n("Typing"));
1371     } else {
1372         KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Typing"));
1373         deleteChar(false, topCommand);
1374         d->caret.beginEditBlock();
1375     }
1376 
1377     //first we make sure that we clear the inlineObject charProperty, if we have no selection
1378     if (!hasSelection && d->caret.charFormat().hasProperty(KoCharacterStyle::InlineInstanceId))
1379         d->clearCharFormatProperty(KoCharacterStyle::InlineInstanceId);
1380 
1381     int startPosition = d->caret.position();
1382 
1383     if (d->caret.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) {
1384         d->newLine(0);
1385         startPosition = d->caret.position();
1386     }
1387 
1388     QTextCharFormat format = d->caret.charFormat();
1389     if (format.hasProperty(KoCharacterStyle::ChangeTrackerId)) {
1390         format.clearProperty(KoCharacterStyle::ChangeTrackerId);
1391     }
1392     static QRegExp urlScanner("\\S+://\\S+");
1393     if (!hRef.isEmpty()) {
1394         format.setAnchor(true);
1395         format.setProperty(KoCharacterStyle::AnchorType, KoCharacterStyle::Anchor);
1396         if ((urlScanner.indexIn(hRef)) == 0) {//web url
1397             format.setAnchorHref(hRef);
1398         } else {
1399             format.setAnchorHref("#"+hRef);
1400         }
1401     }
1402     d->caret.insertText(text, format);
1403 
1404     int endPosition = d->caret.position();
1405 
1406     //Mark the inserted text
1407     d->caret.setPosition(startPosition);
1408     d->caret.setPosition(endPosition, QTextCursor::KeepAnchor);
1409 
1410     registerTrackedChange(d->caret, KoGenChange::InsertChange, kundo2_i18n("Typing"), format, format, false);
1411 
1412     d->caret.clearSelection();
1413 
1414     if (hasSelection) {
1415         d->caret.endEditBlock();
1416         endEditBlock();
1417     }
1418     if (!hRef.isEmpty()) {
1419         format.setAnchor(false);
1420         format.clearProperty(KoCharacterStyle::Anchor);
1421         format.clearProperty(KoCharacterStyle::AnchorType);
1422         d->caret.setCharFormat(format);
1423     }
1424     emit cursorPositionChanged();
1425 }
1426 
1427 bool KoTextEditor::movePosition(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode, int n)
1428 {
1429     d->editProtectionCached = false;
1430 
1431     // We need protection against moving in and out of note areas
1432     QTextCursor after(d->caret);
1433     bool b = after.movePosition (operation, mode, n);
1434 
1435     QTextFrame *beforeFrame = d->caret.currentFrame();
1436     while (qobject_cast<QTextTable *>(beforeFrame)) {
1437         beforeFrame = beforeFrame->parentFrame();
1438     }
1439 
1440     QTextFrame *afterFrame = after.currentFrame();
1441     while (qobject_cast<QTextTable *>(afterFrame)) {
1442         afterFrame = afterFrame->parentFrame();
1443     }
1444 
1445     if (beforeFrame == afterFrame) {
1446         if (after.selectionEnd() == after.document()->characterCount() -1) {
1447             QTextCursor cursor(d->caret.document()->rootFrame()->lastCursorPosition());
1448             cursor.movePosition(QTextCursor::PreviousCharacter);
1449             QTextFrame *auxFrame = cursor.currentFrame();
1450 
1451             if (auxFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) {
1452                 if (operation == QTextCursor::End) {
1453                     d->caret.setPosition(auxFrame->firstPosition() - 1, mode);
1454                     emit cursorPositionChanged();
1455                     return true;
1456                 }
1457                 return false;
1458             }
1459         }
1460         d->caret = after;
1461         emit cursorPositionChanged();
1462         return b;
1463     }
1464     return false;
1465 }
1466 
1467 void KoTextEditor::newSection()
1468 {
1469     if (isEditProtected()) {
1470         return;
1471     }
1472 
1473     NewSectionCommand *cmd = new NewSectionCommand(d->document);
1474     addCommand(cmd);
1475     emit cursorPositionChanged();
1476 }
1477 
1478 void KoTextEditor::splitSectionsStartings(int sectionIdToInsertBefore)
1479 {
1480     if (isEditProtected()) {
1481         return;
1482     }
1483     addCommand(new SplitSectionsCommand(
1484         d->document,
1485         SplitSectionsCommand::Startings,
1486         sectionIdToInsertBefore));
1487     emit cursorPositionChanged();
1488 }
1489 
1490 void KoTextEditor::splitSectionsEndings(int sectionIdToInsertAfter)
1491 {
1492     if (isEditProtected()) {
1493         return;
1494     }
1495     addCommand(new SplitSectionsCommand(
1496         d->document,
1497         SplitSectionsCommand::Endings,
1498         sectionIdToInsertAfter));
1499     emit cursorPositionChanged();
1500 }
1501 
1502 void KoTextEditor::renameSection(KoSection* section, const QString &newName)
1503 {
1504     if (isEditProtected()) {
1505         return;
1506     }
1507     addCommand(new RenameSectionCommand(section, newName, document()));
1508 }
1509 
1510 void KoTextEditor::newLine()
1511 {
1512     if (isEditProtected()) {
1513         return;
1514     }
1515 
1516     bool hasSelection = d->caret.hasSelection();
1517     if (!hasSelection) {
1518         d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("New Paragraph"));
1519     } else {
1520         KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("New Paragraph"));
1521         deleteChar(false, topCommand);
1522     }
1523     d->caret.beginEditBlock();
1524 
1525     d->newLine(0);
1526 
1527     d->caret.endEditBlock();
1528 
1529     if (hasSelection) {
1530         endEditBlock();
1531     } else {
1532         d->updateState(KoTextEditor::Private::NoOp);
1533     }
1534 
1535     emit cursorPositionChanged();
1536 }
1537 
1538 class WithinSelectionVisitor : public KoTextVisitor
1539 {
1540 public:
1541     WithinSelectionVisitor(KoTextEditor *editor, int position)
1542         : KoTextVisitor(editor)
1543         , m_position(position)
1544         , m_returnValue(false)
1545     {
1546     }
1547 
1548     void visitBlock(QTextBlock &block, const QTextCursor &caret) override
1549     {
1550         if (m_position >= qMax(block.position(), caret.selectionStart())
1551                     && m_position <= qMin(block.position() + block.length(), caret.selectionEnd())) {
1552             m_returnValue = true;
1553             setAbortVisiting(true);
1554         }
1555     }
1556     int m_position; //the position we are searching for
1557     bool m_returnValue; //if position is within the selection
1558 };
1559 
1560 bool KoTextEditor::isWithinSelection(int position) const
1561 {
1562     // we know the visitor doesn't do anything with the texteditor so let's const cast
1563     // to have a more beautiful outer api
1564     WithinSelectionVisitor visitor(const_cast<KoTextEditor *>(this), position);
1565 
1566     recursivelyVisitSelection(d->document->rootFrame()->begin(), visitor);
1567     return visitor.m_returnValue;
1568 }
1569 
1570 int KoTextEditor::position() const
1571 {
1572     return d->caret.position();
1573 }
1574 
1575 void KoTextEditor::select(QTextCursor::SelectionType selection)
1576 {
1577     //TODO add selection of previous/next char, and option about hasSelection
1578     d->caret.select(selection);
1579 }
1580 
1581 QString KoTextEditor::selectedText() const
1582 {
1583     return d->caret.selectedText();
1584 }
1585 
1586 QTextDocumentFragment KoTextEditor::selection() const
1587 {
1588     return d->caret.selection();
1589 }
1590 
1591 int KoTextEditor::selectionEnd() const
1592 {
1593     return d->caret.selectionEnd();
1594 }
1595 
1596 int KoTextEditor::selectionStart() const
1597 {
1598     return d->caret.selectionStart();
1599 }
1600 
1601 void KoTextEditor::setPosition(int pos, QTextCursor::MoveMode mode)
1602 {
1603     d->editProtectionCached = false;
1604 
1605     if (pos == d->caret.document()->characterCount() -1) {
1606         QTextCursor cursor(d->caret.document()->rootFrame()->lastCursorPosition());
1607         cursor.movePosition(QTextCursor::PreviousCharacter);
1608         QTextFrame *auxFrame = cursor.currentFrame();
1609 
1610         if (auxFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) {
1611             return;
1612         }
1613     }
1614 
1615     if (mode == QTextCursor::MoveAnchor) {
1616         d->caret.setPosition (pos, mode);
1617         emit cursorPositionChanged();
1618     }
1619 
1620     // We need protection against moving in and out of note areas
1621     QTextCursor after(d->caret);
1622     after.setPosition (pos, mode);
1623 
1624     QTextFrame *beforeFrame = d->caret.currentFrame();
1625     while (qobject_cast<QTextTable *>(beforeFrame)) {
1626         beforeFrame = beforeFrame->parentFrame();
1627     }
1628 
1629     QTextFrame *afterFrame = after.currentFrame();
1630     while (qobject_cast<QTextTable *>(afterFrame)) {
1631         afterFrame = afterFrame->parentFrame();
1632     }
1633 
1634     if (beforeFrame == afterFrame) {
1635         d->caret = after;
1636         emit cursorPositionChanged();
1637     }
1638 }
1639 
1640 void KoTextEditor::setVisualNavigation(bool b)
1641 {
1642     d->caret.setVisualNavigation (b);
1643 }
1644 
1645 bool KoTextEditor::visualNavigation() const
1646 {
1647     return d->caret.visualNavigation();
1648 }
1649 
1650 const QTextFrame *KoTextEditor::currentFrame () const
1651 {
1652     return d->caret.currentFrame();
1653 }
1654 
1655 const QTextList *KoTextEditor::currentList () const
1656 {
1657     return d->caret.currentList();
1658 }
1659 
1660 const QTextTable *KoTextEditor::currentTable () const
1661 {
1662     return d->caret.currentTable();
1663 }
1664 
1665 //have to include this because of Q_PRIVATE_SLOT
1666 #include "moc_KoTextEditor.cpp"