QPlainTextEdit: Fix crash on complex undo w/full width selection

Say you have a document of two blocks of text.

When you select a block of text in the document and then replace this
with a new empty block (by pressing enter) and then subsequently
undo this action, the following three steps are performed as a
chain of undo commands:

1. Remove the empty block at the beginning of the document
2. Insert a new empty block at the beginning of the document
3. Insert the text back into the first block

Since a block is removed and inserted in the same go, both blocks
require a relayout, since the accumulated change spans both blocks.
However, in QPlainTextDocumentLayout we would only look at the max
of either removed chars or added chars. This would match the text
length of the first block at this point, so we would only relayout
that block. However, since we are also removing characters, the
actual accumulated change to the document is larger. We should
relayout any block touched by the sum of the added and removed
character counts.

Missing this, the paint event would later query
block.layout()->lineForTextPosition(0) which would give an invalid
line despite the fact that the block.length() > 0. This caused
a crash in the paint event when the full width selection was
turned on.

Note that the logic here was only recently updated to include the
removed characters at all in the logic, by the SHA1:
2983cb9531.

[ChangeLog][QPlainTextEdit] Fixed a crash when using full width
selections and issuing a complex undo command chain which removes
and inserts an empty block in one go.

Task-number: QTBUG-36415
Change-Id: Iafe8a69e455e0c713a48714f10f0cace69c84f51
Reviewed-by: Axel Rasmussen <axel.rasmussen1@gmail.com>
Reviewed-by: Konstantin Ritt <ritt.ks@gmail.com>
Reviewed-by: Simon Hausmann <simon.hausmann@digia.com>
This commit is contained in:
Eskil Abrahamsen Blomfeldt 2014-09-22 15:50:29 +02:00
parent ee6e9cbf36
commit 27bc4c4ce5
2 changed files with 26 additions and 1 deletions

View File

@ -289,7 +289,7 @@ void QPlainTextDocumentLayout::documentChanged(int from, int charsRemoved, int c
Q_D(QPlainTextDocumentLayout);
QTextDocument *doc = document();
int newBlockCount = doc->blockCount();
int charsChanged = qMax(charsRemoved, charsAdded);
int charsChanged = charsRemoved + charsAdded;
QTextBlock changeStartBlock = doc->findBlock(from);
QTextBlock changeEndBlock = doc->findBlock(qMax(0, from + charsChanged - 1));

View File

@ -155,6 +155,7 @@ private slots:
void findWithRegExpReturnsFalseIfNoMoreResults();
#endif
void layoutAfterMultiLineRemove();
void undoCommandRemovesAndReinsertsBlock();
private:
void createSelection();
@ -1612,5 +1613,29 @@ void tst_QPlainTextEdit::layoutAfterMultiLineRemove()
QCOMPARE(curs.blockNumber(), 3);
}
void tst_QPlainTextEdit::undoCommandRemovesAndReinsertsBlock()
{
ed->setVisible(true);
ed->setPlainText(QStringLiteral("line1\nline2"));
QCOMPARE(ed->document()->blockCount(), 2);
QTextCursor cursor = ed->textCursor();
cursor.movePosition(QTextCursor::Start);
cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor);
cursor.insertText(QStringLiteral("\n"));
QCOMPARE(ed->document()->blockCount(), 2);
ed->undo();
QCOMPARE(ed->document()->blockCount(), 2);
QTextBlock block;
for (block = ed->document()->begin(); block != ed->document()->end(); block = block.next()) {
QVERIFY(block.isValid());
QCOMPARE(block.length(), 6);
QVERIFY(block.layout()->lineForTextPosition(0).isValid());
}
}
QTEST_MAIN(tst_QPlainTextEdit)
#include "tst_qplaintextedit.moc"