Commit dfdc4ce8 authored by Pierre Rossi's avatar Pierre Rossi Committed by The Qt Project
Browse files

TextEdit: Better support of QTextTable and inline images


Fix some issues with incremental updates found while
playing with the Quick Controls textedit demo.

 -Grouping text blocks into a single text node is fine as
long as it doesn't cross the boundary of a child frame
(e.g. a table), otherwise all that node logic collapses.

 -Text tables are hard to split in a sensible way, ensure
we treat them as one text node for the sake of simplicity.

 -Inline images can cause several text nodes to have the
same apparent start position. Beef up the rewinding logic
in markDirtyNodesForRange.

Change-Id: Ib4518bcd9303035fa00d9f4b16da7ca6c88e2313
Reviewed-by: default avatarCaroline Chao <caroline.chao@digia.com>
Reviewed-by: default avatarEskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@digia.com>
parent a7cb4acd
dev 5.10 5.11 5.12 5.12.1 5.12.10 5.12.11 5.12.12 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.12.7 5.12.8 5.12.9 5.13 5.13.0 5.13.1 5.13.2 5.14 5.14.0 5.14.1 5.14.2 5.15 5.15.0 5.15.1 5.15.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 5.9.8 6.0 6.0.0 6.1 6.1.0 6.1.1 6.1.2 6.1.3 6.2 6.2.0 6.2.1 6.2.2 old/5.1 old/5.2 wip/cmake wip/dbus wip/gc wip/itemviews wip/nacl wip/new-backend wip/pointerhandler wip/propertycache-refactor wip/qquickdeliveryagent wip/scenegraphng wip/tizen wip/webassembly v5.15.0-alpha1 v5.14.1 v5.14.0 v5.14.0-rc2 v5.14.0-rc1 v5.14.0-beta3 v5.14.0-beta2 v5.14.0-beta1 v5.14.0-alpha1 v5.13.2 v5.13.1 v5.13.0 v5.13.0-rc3 v5.13.0-rc2 v5.13.0-rc1 v5.13.0-beta4 v5.13.0-beta3 v5.13.0-beta2 v5.13.0-beta1 v5.13.0-alpha1 v5.12.7 v5.12.6 v5.12.5 v5.12.4 v5.12.3 v5.12.2 v5.12.1 v5.12.0 v5.12.0-rc2 v5.12.0-rc1 v5.12.0-beta4 v5.12.0-beta3 v5.12.0-beta2 v5.12.0-beta1 v5.12.0-alpha1 v5.11.3 v5.11.2 v5.11.1 v5.11.0 v5.11.0-rc2 v5.11.0-rc1 v5.11.0-beta4 v5.11.0-beta3 v5.11.0-beta2 v5.11.0-beta1 v5.11.0-alpha1 v5.10.1 v5.10.0 v5.10.0-rc3 v5.10.0-rc2 v5.10.0-rc1 v5.10.0-beta4 v5.10.0-beta3 v5.10.0-beta2 v5.10.0-beta1 v5.10.0-alpha1 v5.9.9 v5.9.8 v5.9.7 v5.9.6 v5.9.5 v5.9.4 v5.9.3 v5.9.2 v5.9.1 v5.9.0 v5.9.0-rc2 v5.9.0-rc1 v5.9.0-beta4 v5.9.0-beta3 v5.9.0-beta2 v5.9.0-beta1 v5.9.0-alpha1 v5.8.0 v5.8.0-rc1 v5.8.0-beta1 v5.8.0-alpha1 v5.7.1 v5.7.0 v5.7.0-rc1 v5.7.0-beta1 v5.7.0-alpha1 v5.6.3 v5.6.2 v5.6.1 v5.6.1-1 v5.6.0 v5.6.0-rc1 v5.6.0-beta1 v5.6.0-alpha1 v5.5.1 v5.5.0 v5.5.0-rc1 v5.5.0-beta1 v5.5.0-alpha1 v5.4.2 v5.4.1 v5.4.0 v5.4.0-rc1 v5.4.0-beta1 v5.4.0-alpha1 v5.3.2 v5.3.1 v5.3.0 v5.3.0-rc1 v5.3.0-beta1 v5.3.0-alpha1 v5.2.1 v5.2.0 v5.2.0-rc1 v5.2.0-beta1 v5.2.0-alpha1 v5.1.1 v5.1.0 v5.1.0-rc2 v5.1.0-rc1 v5.1.0-beta1
No related merge requests found
Showing with 72 additions and 39 deletions
...@@ -55,6 +55,7 @@ ...@@ -55,6 +55,7 @@
#include <QtGui/qevent.h> #include <QtGui/qevent.h>
#include <QtGui/qpainter.h> #include <QtGui/qpainter.h>
#include <QtGui/qtextobject.h> #include <QtGui/qtextobject.h>
#include <QtGui/qtexttable.h>
#include <QtCore/qmath.h> #include <QtCore/qmath.h>
#include <QtCore/qalgorithms.h> #include <QtCore/qalgorithms.h>
...@@ -1710,6 +1711,13 @@ static bool comesBefore(TextNode* n1, TextNode* n2) ...@@ -1710,6 +1711,13 @@ static bool comesBefore(TextNode* n1, TextNode* n2)
return n1->startPos() < n2->startPos(); return n1->startPos() < n2->startPos();
} }
static inline void updateNodeTransform(QQuickTextNode* node, const QPointF &topLeft)
{
QMatrix4x4 transformMatrix;
transformMatrix.translate(topLeft.x(), topLeft.y());
node->setMatrix(transformMatrix);
}
QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
{ {
Q_UNUSED(updatePaintNodeData); Q_UNUSED(updatePaintNodeData);
...@@ -1750,17 +1758,12 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData * ...@@ -1750,17 +1758,12 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
rootNode->removeChildNode(d->frameDecorationsNode); rootNode->removeChildNode(d->frameDecorationsNode);
delete d->frameDecorationsNode; delete d->frameDecorationsNode;
} }
d->frameDecorationsNode = new QQuickTextNode(QQuickItemPrivate::get(this)->sceneGraphContext(), this); d->frameDecorationsNode = d->createTextNode();
d->frameDecorationsNode->initEngine(QColor(), QColor(), QColor());
QQuickTextNode *node = 0;
QQuickTextNode *node = new QQuickTextNode(QQuickItemPrivate::get(this)->sceneGraphContext(), this); int currentNodeSize = 0;
node->setUseNativeRenderer(d->renderType == NativeRendering && d->window->devicePixelRatio() <= 1); int nodeStart = firstDirtyPos;
node->initEngine(d->color, d->selectedTextColor, d->selectionColor);
int sizeCounter = 0;
int prevBlockStart = firstDirtyPos;
QPointF basePosition(d->xoff, d->yoff); QPointF basePosition(d->xoff, d->yoff);
QPointF nodeOffset; QPointF nodeOffset;
TextNode *firstCleanNode = (nodeIterator != d->textNodeMap.end()) ? *nodeIterator : 0; TextNode *firstCleanNode = (nodeIterator != d->textNodeMap.end()) ? *nodeIterator : 0;
...@@ -1773,16 +1776,14 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData * ...@@ -1773,16 +1776,14 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
frames.append(textFrame->childFrames()); frames.append(textFrame->childFrames());
d->frameDecorationsNode->m_engine->addFrameDecorations(d->document, textFrame); d->frameDecorationsNode->m_engine->addFrameDecorations(d->document, textFrame);
if (textFrame->lastPosition() < firstDirtyPos || (firstCleanNode && textFrame->firstPosition() >= firstCleanNode->startPos()))
continue;
node = d->createTextNode();
if (textFrame->firstPosition() > textFrame->lastPosition() if (textFrame->firstPosition() > textFrame->lastPosition()
&& textFrame->frameFormat().position() != QTextFrameFormat::InFlow) { && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
QRectF rect = d->document->documentLayout()->frameBoundingRect(textFrame); updateNodeTransform(node, d->document->documentLayout()->frameBoundingRect(textFrame).topLeft());
if (!node->m_engine->hasContents()) {
nodeOffset = rect.topLeft();
QMatrix4x4 transformMatrix;
transformMatrix.translate(nodeOffset.x(), nodeOffset.y());
node->setMatrix(transformMatrix);
}
const int pos = textFrame->firstPosition() - 1; const int pos = textFrame->firstPosition() - 1;
ProtectedLayoutAccessor *a = static_cast<ProtectedLayoutAccessor *>(d->document->documentLayout()); ProtectedLayoutAccessor *a = static_cast<ProtectedLayoutAccessor *>(d->document->documentLayout());
QTextCharFormat format = a->formatAccessor(pos); QTextCharFormat format = a->formatAccessor(pos);
...@@ -1790,10 +1791,23 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData * ...@@ -1790,10 +1791,23 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
node->m_engine->setCurrentLine(block.layout()->lineForTextPosition(pos - block.position())); node->m_engine->setCurrentLine(block.layout()->lineForTextPosition(pos - block.position()));
node->m_engine->addTextObject(QPointF(0, 0), format, QQuickTextNodeEngine::Unselected, d->document, node->m_engine->addTextObject(QPointF(0, 0), format, QQuickTextNodeEngine::Unselected, d->document,
pos, textFrame->frameFormat().position()); pos, textFrame->frameFormat().position());
nodeStart = pos;
} else if (qobject_cast<QTextTable*>(textFrame)) { // To keep things simple, map text tables as one text node
QTextFrame::iterator it = textFrame->begin();
nodeOffset = d->document->documentLayout()->frameBoundingRect(textFrame).topLeft();
updateNodeTransform(node, nodeOffset);
while (!it.atEnd())
node->m_engine->addTextBlock(d->document, (it++).currentBlock(), basePosition - nodeOffset, d->color, QColor(), selectionStart(), selectionEnd() - 1);
nodeStart = textFrame->firstPosition();
} else { } else {
// Having nodes spanning across frame boundaries will break the current bookkeeping mechanism. We need to prevent that.
QList<int> frameBoundaries;
frameBoundaries.reserve(frames.size());
Q_FOREACH (QTextFrame *frame, frames)
frameBoundaries.append(frame->firstPosition());
std::sort(frameBoundaries.begin(), frameBoundaries.end());
QTextFrame::iterator it = textFrame->begin(); QTextFrame::iterator it = textFrame->begin();
while (!it.atEnd()) { while (!it.atEnd()) {
QTextBlock block = it.currentBlock(); QTextBlock block = it.currentBlock();
++it; ++it;
...@@ -1802,34 +1816,27 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData * ...@@ -1802,34 +1816,27 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
if (!node->m_engine->hasContents()) { if (!node->m_engine->hasContents()) {
nodeOffset = d->document->documentLayout()->blockBoundingRect(block).topLeft(); nodeOffset = d->document->documentLayout()->blockBoundingRect(block).topLeft();
QMatrix4x4 transformMatrix; updateNodeTransform(node, nodeOffset);
transformMatrix.translate(nodeOffset.x(), nodeOffset.y()); nodeStart = block.position();
node->setMatrix(transformMatrix);
} }
node->m_engine->addTextBlock(d->document, block, basePosition - nodeOffset, d->color, QColor(), selectionStart(), selectionEnd() - 1); node->m_engine->addTextBlock(d->document, block, basePosition - nodeOffset, d->color, QColor(), selectionStart(), selectionEnd() - 1);
sizeCounter += block.length(); currentNodeSize += block.length();
if ((it.atEnd() && frames.isEmpty()) || (firstCleanNode && block.next().position() >= firstCleanNode->startPos())) // last node that needed replacing or last block of the last frame if ((it.atEnd()) || (firstCleanNode && block.next().position() >= firstCleanNode->startPos())) // last node that needed replacing or last block of the frame
break; break;
if (sizeCounter > nodeBreakingSize || it.atEnd()) { // text block grouping across text frames might not be a good idea, split it. QList<int>::const_iterator lowerBound = qLowerBound(frameBoundaries, block.next().position());
sizeCounter = 0; if (currentNodeSize > nodeBreakingSize || *lowerBound > nodeStart) {
node->m_engine->addToSceneGraph(node, QQuickText::Normal, QColor()); currentNodeSize = 0;
nodeIterator = d->textNodeMap.insert(nodeIterator, new TextNode(prevBlockStart, node)); d->addCurrentTextNodeToRoot(rootNode, node, nodeIterator, nodeStart);
++nodeIterator; node = d->createTextNode();
rootNode->appendChildNode(node); nodeStart = block.next().position();
prevBlockStart = block.next().position();
node = new QQuickTextNode(QQuickItemPrivate::get(this)->sceneGraphContext(), this);
node->setUseNativeRenderer(d->renderType == NativeRendering && d->window->devicePixelRatio() <= 1);
node->initEngine(d->color, d->selectedTextColor, d->selectionColor);
} }
} }
} }
d->addCurrentTextNodeToRoot(rootNode, node, nodeIterator, nodeStart);
} }
node->m_engine->addToSceneGraph(node, QQuickText::Normal, QColor());
nodeIterator = d->textNodeMap.insert(nodeIterator, new TextNode(prevBlockStart, node));
++nodeIterator;
rootNode->appendChildNode(node);
d->frameDecorationsNode->m_engine->addToSceneGraph(d->frameDecorationsNode, QQuickText::Normal, QColor()); d->frameDecorationsNode->m_engine->addToSceneGraph(d->frameDecorationsNode, QQuickText::Normal, QColor());
// Now prepend the frame decorations since we want them rendered first, with the text nodes and cursor in front. // Now prepend the frame decorations since we want them rendered first, with the text nodes and cursor in front.
rootNode->prependChildNode(d->frameDecorationsNode); rootNode->prependChildNode(d->frameDecorationsNode);
...@@ -1998,11 +2005,16 @@ void QQuickTextEdit::markDirtyNodesForRange(int start, int end, int charDelta) ...@@ -1998,11 +2005,16 @@ void QQuickTextEdit::markDirtyNodesForRange(int start, int end, int charDelta)
Q_D(QQuickTextEdit); Q_D(QQuickTextEdit);
if (start == end) if (start == end)
return; return;
TextNode dummyNode(start, 0); TextNode dummyNode(start, 0);
TextNodeIterator it = qLowerBound(d->textNodeMap.begin(), d->textNodeMap.end(), &dummyNode, &comesBefore); TextNodeIterator it = qLowerBound(d->textNodeMap.begin(), d->textNodeMap.end(), &dummyNode, &comesBefore);
// qLowerBound gives us the first node past the start of the affected portion, rewind by one if we can. // qLowerBound gives us the first node past the start of the affected portion, rewind to the first node
if (it != d->textNodeMap.begin()) // that starts at the last position before the edit position. (there might be several because of images)
if (it != d->textNodeMap.begin()) {
--it; --it;
TextNode otherDummy((*it)->startPos(), 0);
it = qLowerBound(d->textNodeMap.begin(), d->textNodeMap.end(), &otherDummy, &comesBefore);
}
// mark the affected nodes as dirty // mark the affected nodes as dirty
while (it != d->textNodeMap.constEnd()) { while (it != d->textNodeMap.constEnd()) {
...@@ -2309,6 +2321,23 @@ void QQuickTextEditPrivate::handleFocusEvent(QFocusEvent *event) ...@@ -2309,6 +2321,23 @@ void QQuickTextEditPrivate::handleFocusEvent(QFocusEvent *event)
} }
} }
void QQuickTextEditPrivate::addCurrentTextNodeToRoot(QSGTransformNode *root, QQuickTextNode *node, TextNodeIterator &it, int startPos)
{
node->m_engine->addToSceneGraph(node, QQuickText::Normal, QColor());
it = textNodeMap.insert(it, new TextNode(startPos, node));
++it;
root->appendChildNode(node);
}
QQuickTextNode *QQuickTextEditPrivate::createTextNode()
{
Q_Q(QQuickTextEdit);
QQuickTextNode* node = new QQuickTextNode(QQuickItemPrivate::get(q)->sceneGraphContext(), q);
node->setUseNativeRenderer(renderType == QQuickTextEdit::NativeRendering && window->devicePixelRatio() <= 1);
node->initEngine(color, selectedTextColor, selectionColor);
return node;
}
void QQuickTextEdit::q_canPasteChanged() void QQuickTextEdit::q_canPasteChanged()
{ {
Q_D(QQuickTextEdit); Q_D(QQuickTextEdit);
......
...@@ -87,6 +87,7 @@ public: ...@@ -87,6 +87,7 @@ public:
QQuickTextNode* m_node; QQuickTextNode* m_node;
bool m_dirty; bool m_dirty;
}; };
typedef QList<Node*>::iterator TextNodeIterator;
QQuickTextEditPrivate() QQuickTextEditPrivate()
...@@ -126,6 +127,8 @@ public: ...@@ -126,6 +127,8 @@ public:
void setNativeCursorEnabled(bool enabled) { control->setCursorWidth(enabled ? 1 : 0); } void setNativeCursorEnabled(bool enabled) { control->setCursorWidth(enabled ? 1 : 0); }
void handleFocusEvent(QFocusEvent *event); void handleFocusEvent(QFocusEvent *event);
void addCurrentTextNodeToRoot(QSGTransformNode *, QQuickTextNode*, TextNodeIterator&, int startPos);
QQuickTextNode* createTextNode();
#ifndef QT_NO_IM #ifndef QT_NO_IM
Qt::InputMethodHints effectiveInputMethodHints() const; Qt::InputMethodHints effectiveInputMethodHints() const;
......
...@@ -118,6 +118,7 @@ private: ...@@ -118,6 +118,7 @@ private:
QScopedPointer<QQuickTextNodeEngine> m_engine; QScopedPointer<QQuickTextNodeEngine> m_engine;
friend class QQuickTextEdit; friend class QQuickTextEdit;
friend class QQuickTextEditPrivate;
}; };
QT_END_NAMESPACE QT_END_NAMESPACE
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment