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"