diff --git a/libs/text/KoTextBlockData.h b/libs/text/KoTextBlockData.h --- a/libs/text/KoTextBlockData.h +++ b/libs/text/KoTextBlockData.h @@ -62,6 +62,8 @@ int lastChar; qreal startX; qreal endX; + int firstRebased; + int lastRebased; }; /** diff --git a/libs/text/KoTextBlockData.cpp b/libs/text/KoTextBlockData.cpp --- a/libs/text/KoTextBlockData.cpp +++ b/libs/text/KoTextBlockData.cpp @@ -81,19 +81,19 @@ void KoTextBlockData::appendMarkup(MarkupType type, int firstChar, int lastChar) { - if (type == KoTextBlockData::Grammar) { - Q_ASSERT(d->markupRangesMap[type].isEmpty() || d->markupRangesMap[type].last().lastChar < firstChar); - } else if (!d->markupRangesMap[type].isEmpty() && d->markupRangesMap[type].last().lastChar >= firstChar) { - // Character positions from spellchecker are not in sync with document. - // I have only seen this in connection with dropcaps, so could be caused by - // large amount to spellcheck combined with large changes to the document. - // Anyway, returning here just means markup will be incorrect/missing until next spellcheck is run. - return; - } + Q_ASSERT(d->markupRangesMap[type].isEmpty() || firstChar > d->markupRangesMap[type].last().lastChar + d->markupRangesMap[type].last().lastRebased); MarkupRange range; range.firstChar = firstChar; range.lastChar = lastChar; + range.firstRebased = 0; + range.lastRebased = 0; + if (!d->markupRangesMap[type].isEmpty()) { + // The document may have been changed (and thus markup has moved) while + // the plugin has done its job in the background + range.firstChar += d->markupRangesMap[type].last().firstRebased; + range.lastChar += d->markupRangesMap[type].last().lastRebased; + } d->layoutedMarkupRanges[type] = false; d->markupRangesMap[type].append(range); @@ -108,9 +108,9 @@ KoTextBlockData::MarkupRange KoTextBlockData::findMarkup(MarkupType type, int positionWithin) const { foreach (const MarkupRange &range, d->markupRangesMap[type]) { - if (positionWithin <= range.lastChar) { + if (positionWithin <= range.lastChar + range.lastRebased) { // possible hit - if (positionWithin >= range.firstChar) { + if (positionWithin >= range.firstChar + range.firstRebased) { return range; } else { return MarkupRange(); // we have passed it without finding @@ -128,10 +128,12 @@ if (fromPosition <= markIt->lastChar) { // we need to modify the end of this markIt->lastChar += delta; + markIt->lastRebased += delta; } if (fromPosition < markIt->firstChar) { // we need to modify the end of this markIt->firstChar += delta; + markIt->firstRebased += delta; } ++markIt; } diff --git a/plugins/textediting/spellcheck/SpellCheck.cpp b/plugins/textediting/spellcheck/SpellCheck.cpp --- a/plugins/textediting/spellcheck/SpellCheck.cpp +++ b/plugins/textediting/spellcheck/SpellCheck.cpp @@ -225,6 +225,9 @@ return m_speller.testAttribute(Speller::SkipRunTogether); } +// TODO: +// 1) When editing a misspelled word it should be spellchecked on the fly so the markup is removed when it is OK. +// 2) Deleting a character should be treated as a simple edit void SpellCheck::highlightMisspelled(const QString &word, int startPosition, bool misspelled) { if (!misspelled) @@ -243,27 +246,38 @@ void SpellCheck::documentChanged(int from, int charsRemoved, int charsAdded) { - Q_UNUSED(charsRemoved); QTextDocument *document = qobject_cast(sender()); if (document == 0) return; - QTextBlock block = document->findBlock(from); + // If a simple edit, we use the cursor position to determine where + // the change occured. This makes it possible to handle cases + // where formatting of a block has changed, eg. when dropcaps is used. + // QTextDocument then reports the change as if the whole block has changed. + // Ex: Having a 10 char line and you add a char at pos 7: + // from = block->postion() + // charsRemoved = 10 + // charsAdded = 11 + // m_cursorPosition = 7 + int pos = m_simpleEdit ? m_cursorPosition : from; + QTextBlock block = document->findBlock(pos); if (!block.isValid()) return; do { KoTextBlockData blockData(block); if (m_enableSpellCheck) { + // This block and all blocks after this must be relayouted blockData.setMarkupsLayoutValidity(KoTextBlockData::Misspell, false); - // We cannot safely handle combined remove/add in case it spans words. - // This should probably never be defined as a simple edit anyway, - // but atm lines with dropcaps trigger this behaviour. See KoTextLayoutArea. - if (m_simpleEdit && (charsRemoved == 0 || charsAdded == 0)) { - // If it's a simple edit we will wait until finishedWord before spellchecking - // but we need to adjust all markups behind the added/removed character(s) - blockData.rebaseMarkups(KoTextBlockData::Misspell, from - block.position(), charsAdded - charsRemoved); + // If it's a simple edit we will wait until finishedWord before spellchecking + // but we need to adjust all markups behind the added/removed character(s) + if (m_simpleEdit) { + // Since markups work on positions within each block only the edited block must be rebased + if (block.position() <= pos) { + blockData.rebaseMarkups(KoTextBlockData::Misspell, pos - block.position(), charsAdded - charsRemoved); + } } else { + // handle not so simple edits (like cut/paste etc) checkSection(document, block.position(), block.position() + block.length() - 1); } } else {