aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2021-12-13 08:14:26 +0100
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2022-01-06 08:43:53 +0000
commit18ad8a7f17a6f49c0a93c0046c05578820732ccb (patch)
treec244c721b6a4bb75793f6535392a4b8e046ada3b
parentcb28c76c40a9492d1a00baf61f71ad52c834ac25 (diff)
TextEdit: deal with scrolling backwards in rich text
A bug was introduced in 9db23e0e04906cf9ea33e23fa41f34955e5e6fe0 : when scrolling backwards in some kinds of rich text, updatePaintNode() failed to re-populate the nodes that had been scrolled out above the viewport. That was because those nodes had been removed from textNodeMap, and then firstDirtyPos was set from the first node in textNodeMap. For some reason this didn't happen with the markdown document that I was testing (maybe because it had a table and an image near the beginning), but showed up when viewing an html document similar to the one we ship with the rich text example. So now we iterate backwards from textNodeMap.begin() when this happens, to find all nodes in the current frame that intersect the viewport as rendered. In the autotest we now use font.pixelSize in an attempt to make the rendered text ranges more consistent across platforms in CI; and we need to wait for the rendering to be redone after scrolling. Change-Id: I70ef54c8d8facc439b9a6f8b5cb8e3a4a1c37e16 Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io> (cherry picked from commit cd083920b3b4f3a1ed7f2297058cf0d110d7cf10) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--src/quick/items/qquicktextedit.cpp23
-rw-r--r--src/quick/items/qquicktextedit_p_p.h4
-rw-r--r--tests/auto/quick/qquicktextedit/data/viewport.qml2
-rw-r--r--tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp38
4 files changed, 52 insertions, 15 deletions
diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp
index 9ef041bbb1..d0614bbeff 100644
--- a/src/quick/items/qquicktextedit.cpp
+++ b/src/quick/items/qquicktextedit.cpp
@@ -2087,7 +2087,7 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
QQuickTextNodeEngine engine;
QQuickTextNodeEngine frameDecorationsEngine;
- if (!oldNode || nodeIterator < d->textNodeMap.end()) {
+ if (!oldNode || nodeIterator < d->textNodeMap.end() || d->textNodeMap.isEmpty()) {
if (!oldNode)
rootNode = new RootNode;
@@ -2184,9 +2184,10 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
QTextFrame::iterator it = textFrame->begin();
while (!it.atEnd()) {
QTextBlock block = it.currentBlock();
- ++it;
- if (block.position() < firstDirtyPos)
+ if (block.position() < firstDirtyPos) {
+ ++it;
continue;
+ }
if (!engine.hasContents())
nodeOffset = d->document->documentLayout()->blockBoundingRect(block).topLeft();
@@ -2199,6 +2200,21 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
inView = coveredRegion.bottom() > viewport.top();
}
if (d->firstBlockInViewport < 0 && inView) {
+ // During backward scrolling, we need to iterate backwards from textNodeMap.begin() to fill the top of the viewport.
+ if (coveredRegion.top() > viewport.top() + 1) {
+ qCDebug(lcVP) << "checking backwards from block" << block.blockNumber() << "@" << nodeOffset.y() << coveredRegion;
+ while (it != textFrame->begin() && it.currentBlock().layout() &&
+ it.currentBlock().layout()->boundingRect().top() + nodeOffset.y() > viewport.top())
+ --it;
+ if (!it.currentBlock().layout())
+ ++it;
+ if (Q_LIKELY(it.currentBlock().layout())) {
+ block = it.currentBlock();
+ coveredRegion = block.layout()->boundingRect().adjusted(nodeOffset.x(), nodeOffset.y(), nodeOffset.x(), nodeOffset.y());
+ } else {
+ qCWarning(lcVP) << "failed to find a text block with layout during back-scrolling";
+ }
+ }
qCDebug(lcVP) << "first block in viewport" << block.blockNumber() << "@" << nodeOffset.y() << coveredRegion;
d->firstBlockInViewport = block.blockNumber();
if (block.layout())
@@ -2240,6 +2256,7 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
resetEngine(&engine, d->color, d->selectedTextColor, d->selectionColor);
nodeStart = block.next().position();
}
+ ++it;
}
}
if (Q_LIKELY(node && !node->parent()))
diff --git a/src/quick/items/qquicktextedit_p_p.h b/src/quick/items/qquicktextedit_p_p.h
index b1937dab9e..2381b46a05 100644
--- a/src/quick/items/qquicktextedit_p_p.h
+++ b/src/quick/items/qquicktextedit_p_p.h
@@ -190,8 +190,8 @@ public:
int lastSelectionStart;
int lastSelectionEnd;
int lineCount;
- int firstBlockInViewport = -1;
- int firstBlockPastViewport = -1;
+ int firstBlockInViewport = -1; // only for the autotest; can be wrong after scrolling sometimes
+ int firstBlockPastViewport = -1; // only for the autotest
QRectF renderedRegion;
enum UpdateType {
diff --git a/tests/auto/quick/qquicktextedit/data/viewport.qml b/tests/auto/quick/qquicktextedit/data/viewport.qml
index c7c1dea526..4c7ffb88c0 100644
--- a/tests/auto/quick/qquicktextedit/data/viewport.qml
+++ b/tests/auto/quick/qquicktextedit/data/viewport.qml
@@ -10,7 +10,7 @@ Item {
border.color: "red"
TextEdit {
- font.pointSize: 10
+ font.pixelSize: 10
cursorDelegate: Rectangle {
border.color: "green"
border.width: 2
diff --git a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp
index 812b541cb0..ccdf3e6b92 100644
--- a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp
+++ b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp
@@ -3693,6 +3693,8 @@ void tst_qquicktextedit::largeTextObservesViewport_data()
QTest::addColumn<QQuickTextEdit::TextFormat>("textFormat");
QTest::addColumn<bool>("parentIsViewport");
QTest::addColumn<int>("cursorPos");
+ QTest::addColumn<int>("scrollDelta"); // non-zero to move TextEdit in viewport
+
QTest::addColumn<int>("expectedBlockTolerance");
QTest::addColumn<int>("expectedBlocksAboveViewport");
QTest::addColumn<int>("expectedBlocksPastViewport");
@@ -3715,10 +3717,12 @@ void tst_qquicktextedit::largeTextObservesViewport_data()
// by default, the root item acts as the viewport:
// QQuickTextEdit doesn't populate lines of text beyond the bottom of the window
// cursor position 1000 is on line 121
- QTest::newRow("default plain text") << text << QQuickTextEdit::PlainText << false << 1000 << 4 << 115 << 147 << 1700 << 2700;
+ QTest::newRow("default plain text") << text << QQuickTextEdit::PlainText << false << 1000 << 0
+ << 5 << 114 << 155 << 1200 << 2200;
// make the rectangle into a viewport item, and move the text upwards:
// QQuickTextEdit doesn't populate lines of text beyond the bottom of the viewport rectangle
- QTest::newRow("clipped plain text") << text << QQuickTextEdit::PlainText << true << 1000 << 4 << 123 << 141 << 1800 << 2600;
+ QTest::newRow("clipped plain text") << text << QQuickTextEdit::PlainText << true << 1000 << 0
+ << 5 << 123 << 147 << 1200 << 2100;
{
QStringList lines;
@@ -3741,12 +3745,21 @@ void tst_qquicktextedit::largeTextObservesViewport_data()
// by default, the root item acts as the viewport:
// QQuickTextEdit doesn't populate blocks beyond the bottom of the window
- QTest::newRow("default styled text") << text << QQuickTextEdit::RichText << false << 1000 << 4 << 123 << 141 << 3200 << 4100;
+ QTest::newRow("default styled text") << text << QQuickTextEdit::RichText << false << 1000 << 0
+ << 120 << 7 << 143 << 2700 << 3700;
// make the rectangle into a viewport item, and move the text upwards:
// QQuickTextEdit doesn't populate blocks that don't intersect the viewport rectangle
- QTest::newRow("clipped styled text") << text << QQuickTextEdit::RichText << true << 1000 << 4 << 127 << 138 << 3300 << 4100;
+ QTest::newRow("clipped styled text") << text << QQuickTextEdit::RichText << true << 1000 << 0
+ << 3 << 127 << 139 << 2800 << 3600;
// get the "chapter 2" heading into the viewport
- QTest::newRow("heading visible") << text << QQuickTextEdit::RichText << true << 780 << 4 << 102 << 112 << 2600 << 3300;
+ QTest::newRow("heading visible") << text << QQuickTextEdit::RichText << true << 800 << 0
+ << 3 << 105 << 116 << 2300 << 3000;
+ // get the "chapter 2" heading into the viewport, and then scroll backwards
+ QTest::newRow("scroll backwards") << text << QQuickTextEdit::RichText << true << 800 << 20
+ << 3 << 104 << 116 << 2200 << 3000;
+ // get the "chapter 2" heading into the viewport, and then scroll forwards
+ QTest::newRow("scroll forwards") << text << QQuickTextEdit::RichText << true << 800 << -50
+ << 3 << 107 << 119 << 2300 << 3100;
}
void tst_qquicktextedit::largeTextObservesViewport()
@@ -3759,6 +3772,7 @@ void tst_qquicktextedit::largeTextObservesViewport()
QFETCH(QQuickTextEdit::TextFormat, textFormat);
QFETCH(bool, parentIsViewport);
QFETCH(int, cursorPos);
+ QFETCH(int, scrollDelta);
QFETCH(int, expectedBlockTolerance);
QFETCH(int, expectedBlocksAboveViewport);
QFETCH(int, expectedBlocksPastViewport);
@@ -3784,6 +3798,13 @@ void tst_qquicktextedit::largeTextObservesViewport()
textItem->setCursorPosition(cursorPos);
auto cursorRect = textItem->cursorRectangle();
textItem->setY(-cursorRect.top());
+ if (lcTests().isDebugEnabled())
+ QTest::qWait(500);
+ if (scrollDelta) {
+ textItem->setY(textItem->y() + scrollDelta);
+ if (lcTests().isDebugEnabled())
+ QTest::qWait(500);
+ }
qCDebug(lcTests) << "text size" << textItem->text().size() << "lines" << textItem->lineCount() << "font" << textItem->font();
Q_ASSERT(textItem->text().size() > QQuickTextEditPrivate::largeTextSizeThreshold);
QVERIFY(textItem->flags().testFlag(QQuickItem::ItemObservesViewport)); // large text sets this flag automatically
@@ -3793,11 +3814,10 @@ void tst_qquicktextedit::largeTextObservesViewport()
<< "expected" << expectedBlocksAboveViewport
<< "first block past viewport" << textPriv->firstBlockPastViewport
<< "expected" << expectedBlocksPastViewport
- << "region" << textPriv->renderedRegion
+ << "region" << textPriv->renderedRegion << "bottom" << textPriv->renderedRegion.bottom()
<< "expected range" << expectedRenderedRegionMin << expectedRenderedRegionMax;
- if (lcTests().isDebugEnabled())
- QTest::qWait(1000);
- QVERIFY(qAbs(textPriv->firstBlockInViewport - expectedBlocksAboveViewport) < expectedBlockTolerance);
+ if (scrollDelta >= 0) // unfortunately firstBlockInViewport isn't always reliable after scrolling
+ QTRY_VERIFY(qAbs(textPriv->firstBlockInViewport - expectedBlocksAboveViewport) < expectedBlockTolerance);
QVERIFY(qAbs(textPriv->firstBlockPastViewport - expectedBlocksPastViewport) < expectedBlockTolerance);
QVERIFY(textPriv->renderedRegion.top() > expectedRenderedRegionMin);
QVERIFY(textPriv->renderedRegion.bottom() < expectedRenderedRegionMax);