diff options
8 files changed, 270 insertions, 63 deletions
diff --git a/src/quick/items/qquicktextnodeengine.cpp b/src/quick/items/qquicktextnodeengine.cpp index 359d7256f2..f746fe8b1b 100644 --- a/src/quick/items/qquicktextnodeengine.cpp +++ b/src/quick/items/qquicktextnodeengine.cpp @@ -51,7 +51,7 @@ QT_BEGIN_NAMESPACE void QQuickTextNodeEngine::BinaryTreeNode::insert(QVarLengthArray<BinaryTreeNode, 16> *binaryTree, const QGlyphRun &glyphRun, SelectionState selectionState, QQuickTextNode::Decorations decorations, const QColor &textColor, - const QColor &backgroundColor, const QPointF &position) + const QColor &backgroundColor, const QPointF &position, int rangeStart, int rangeEnd) { QRectF searchRect = glyphRun.boundingRect(); searchRect.translate(position); @@ -66,7 +66,7 @@ void QQuickTextNodeEngine::BinaryTreeNode::insert(QVarLengthArray<BinaryTreeNode qreal ascent = glyphRun.rawFont().ascent(); insert(binaryTree, BinaryTreeNode(glyphRun, selectionState, searchRect, decorations, - textColor, backgroundColor, position, ascent)); + textColor, backgroundColor, position, ascent, rangeStart, rangeEnd)); } void QQuickTextNodeEngine::BinaryTreeNode::insert(QVarLengthArray<BinaryTreeNode, 16> *binaryTree, const BinaryTreeNode &binaryTreeNode) @@ -247,12 +247,9 @@ void QQuickTextNodeEngine::processCurrentLine() // selection rect to the graph, and we add decoration every time the // selection state changes, because that means the text color changes if (node == 0 || node->selectionState != currentSelectionState) { - if (node != 0) - currentRect.setRight(node->boundingRect.left()); currentRect.setY(m_position.y() + m_currentLine.y()); currentRect.setHeight(m_currentLine.height()); - // Draw selection all the way up to the left edge of the unselected item if (currentSelectionState == Selected) m_selectionRects.append(currentRect); @@ -288,8 +285,10 @@ void QQuickTextNodeEngine::processCurrentLine() } if (node != 0) { - node->clipNode = currentClipNode; - currentClipNodeUsed = true; + if (node->selectionState == Selected) { + node->clipNode = currentClipNode; + currentClipNodeUsed = true; + } decorationRect = node->boundingRect; @@ -442,17 +441,19 @@ void QQuickTextNodeEngine::addTextObject(const QPointF &position, const QTextCha } } -void QQuickTextNodeEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun) +void QQuickTextNodeEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun, int rangeStart, int rangeEnd) { BinaryTreeNode::insert(&m_currentLineTree, glyphRun, Unselected, - QQuickTextNode::NoDecoration, m_textColor, m_backgroundColor, m_position); + QQuickTextNode::NoDecoration, m_textColor, m_backgroundColor, m_position, + rangeStart, rangeEnd); } -void QQuickTextNodeEngine::addSelectedGlyphs(const QGlyphRun &glyphRun) +void QQuickTextNodeEngine::addSelectedGlyphs(const QGlyphRun &glyphRun, int rangeStart, int rangeEnd) { int currentSize = m_currentLineTree.size(); BinaryTreeNode::insert(&m_currentLineTree, glyphRun, Selected, - QQuickTextNode::NoDecoration, m_textColor, m_backgroundColor, m_position); + QQuickTextNode::NoDecoration, m_textColor, m_backgroundColor, m_position, + rangeStart, rangeEnd); m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize; } @@ -525,17 +526,15 @@ void QQuickTextNodeEngine::addGlyphsInRange(int rangeStart, int rangeLength, QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart, rangeLength); for (int j=0; j<glyphRuns.size(); ++j) { const QGlyphRun &glyphRun = glyphRuns.at(j); - addUnselectedGlyphs(glyphRun); + addUnselectedGlyphs(glyphRun, rangeStart, rangeEnd - 1); } } else { if (rangeStart < selectionStart) { - QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart, - qMin(selectionStart - rangeStart, - rangeLength)); - + int length = qMin(selectionStart - rangeStart, rangeLength); + QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart, length); for (int j=0; j<glyphRuns.size(); ++j) { const QGlyphRun &glyphRun = glyphRuns.at(j); - addUnselectedGlyphs(glyphRun); + addUnselectedGlyphs(glyphRun, rangeStart, rangeStart + length - 1); } } @@ -546,15 +545,18 @@ void QQuickTextNodeEngine::addGlyphsInRange(int rangeStart, int rangeLength, for (int j=0; j<glyphRuns.size(); ++j) { const QGlyphRun &glyphRun = glyphRuns.at(j); - addSelectedGlyphs(glyphRun); + addSelectedGlyphs(glyphRun, start, start + length - 1); + addUnselectedGlyphs(glyphRun, start, start + length - 1); } } if (selectionEnd >= rangeStart && selectionEnd < rangeEnd) { - QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionEnd + 1, rangeEnd - selectionEnd - 1); + int start = selectionEnd + 1; + int length = rangeEnd - selectionEnd - 1; + QList<QGlyphRun> glyphRuns = line.glyphRuns(start, length); for (int j=0; j<glyphRuns.size(); ++j) { const QGlyphRun &glyphRun = glyphRuns.at(j); - addUnselectedGlyphs(glyphRun); + addUnselectedGlyphs(glyphRun, start, start + length - 1); } } } @@ -633,37 +635,12 @@ void QQuickTextNodeEngine::addToSceneGraph(QQuickTextNode *parentNode, if (m_currentLine.isValid()) processCurrentLine(); - - for (int i=0; i<m_backgrounds.size(); ++i) { - const QRectF &rect = m_backgrounds.at(i).first; - const QColor &color = m_backgrounds.at(i).second; - - parentNode->addRectangleNode(rect, color); - } - - // First, prepend all selection rectangles to the tree - for (int i=0; i<m_selectionRects.size(); ++i) { - const QRectF &rect = m_selectionRects.at(i); - - parentNode->addRectangleNode(rect, m_selectionColor); - } - - // Finally, add decorations for each node to the tree. - for (int i=0; i<m_lines.size(); ++i) { - const TextDecoration &textDecoration = m_lines.at(i); - - QColor color = textDecoration.selectionState == Selected - ? m_selectedTextColor - : textDecoration.color; - - parentNode->addRectangleNode(textDecoration.rect, color); - } - // Then, go through all the nodes for all lines and combine all QGlyphRuns with a common // font, selection state and clip node. typedef QPair<QFontEngine *, QPair<QQuickDefaultClipNode *, QPair<QRgb, int> > > KeyType; QHash<KeyType, BinaryTreeNode *> map; QList<BinaryTreeNode *> nodes; + QList<BinaryTreeNode *> imageNodes; for (int i = 0; i < m_processedNodes.size(); ++i) { BinaryTreeNode *node = m_processedNodes.data() + i; @@ -696,31 +673,137 @@ void QQuickTextNodeEngine::addToSceneGraph(QQuickTextNode *parentNode, otherGlyphRun.setGlyphIndexes(otherGlyphIndexes); otherGlyphRun.setPositions(otherGlyphPositions); + otherNode->ranges += node->ranges; + } else { map.insert(key, node); nodes.append(node); } } else { - parentNode->addImage(node->boundingRect, node->image); - if (node->selectionState == Selected) { - QColor color = m_selectionColor; - color.setAlpha(128); - parentNode->addRectangleNode(node->boundingRect, color); - } + imageNodes.append(node); } } - foreach (const BinaryTreeNode *node, nodes) { + for (int i = 0; i < m_backgrounds.size(); ++i) { + const QRectF &rect = m_backgrounds.at(i).first; + const QColor &color = m_backgrounds.at(i).second; + parentNode->addRectangleNode(rect, color); + } + + // Add all unselected text first + for (int i = 0; i < nodes.size(); ++i) { + const BinaryTreeNode *node = nodes.at(i); + if (node->selectionState == Unselected) + parentNode->addGlyphs(node->position, node->glyphRun, node->color, style, styleColor, 0); + } + + for (int i = 0; i < imageNodes.size(); ++i) { + const BinaryTreeNode *node = imageNodes.at(i); + if (node->selectionState == Unselected) + parentNode->addImage(node->boundingRect, node->image); + } + + // Then, prepend all selection rectangles to the tree + for (int i = 0; i < m_selectionRects.size(); ++i) { + const QRectF &rect = m_selectionRects.at(i); + + parentNode->addRectangleNode(rect, m_selectionColor); + } + + // Add decorations for each node to the tree. + for (int i = 0; i < m_lines.size(); ++i) { + const TextDecoration &textDecoration = m_lines.at(i); + + QColor color = textDecoration.selectionState == Selected + ? m_selectedTextColor + : textDecoration.color; + + parentNode->addRectangleNode(textDecoration.rect, color); + } + + // Finally add the selected text on top of everything + for (int i = 0; i < nodes.size(); ++i) { + const BinaryTreeNode *node = nodes.at(i); QQuickDefaultClipNode *clipNode = node->clipNode; - if (clipNode != 0 && clipNode->parent() == 0 ) + if (clipNode != 0 && clipNode->parent() == 0) parentNode->appendChildNode(clipNode); - QColor color = node->selectionState == Selected - ? m_selectedTextColor - : node->color; + if (node->selectionState == Selected) { + QColor color = m_selectedTextColor; + int previousNodeIndex = i - 1; + int nextNodeIndex = i + 1; + const BinaryTreeNode *previousNode = previousNodeIndex < 0 ? 0 : nodes.at(previousNodeIndex); + while (previousNode != 0 && qFuzzyCompare(previousNode->boundingRect.left(), node->boundingRect.left())) + previousNode = --previousNodeIndex < 0 ? 0 : nodes.at(previousNodeIndex); + + const BinaryTreeNode *nextNode = nextNodeIndex == nodes.size() ? 0 : nodes.at(nextNodeIndex); + + if (previousNode != 0 && previousNode->selectionState == Unselected) + parentNode->addGlyphs(previousNode->position, previousNode->glyphRun, color, style, styleColor, clipNode); + + if (nextNode != 0 && nextNode->selectionState == Unselected) + parentNode->addGlyphs(nextNode->position, nextNode->glyphRun, color, style, styleColor, clipNode); + + // If the previous or next node completely overlaps this one, then we have already drawn the glyphs of + // this node + bool drawCurrent = false; + if (previousNode != 0 || nextNode != 0) { + for (int i = 0; i < node->ranges.size(); ++i) { + const QPair<int, int> &range = node->ranges.at(i); + + int rangeLength = range.second - range.first + 1; + if (previousNode != 0) { + for (int j = 0; j < previousNode->ranges.size(); ++j) { + const QPair<int, int> &otherRange = previousNode->ranges.at(j); + if (range.first <= otherRange.second && range.second >= otherRange.first) { + int start = qMax(range.first, otherRange.first); + int end = qMin(range.second, otherRange.second); + rangeLength -= end - start + 1; + if (rangeLength == 0) + break; + } + } + } + + if (nextNode != 0 && rangeLength > 0) { + for (int j = 0; j < nextNode->ranges.size(); ++j) { + const QPair<int, int> &otherRange = nextNode->ranges.at(j); + + if (range.first <= otherRange.second && range.second >= otherRange.first) { + int start = qMax(range.first, otherRange.first); + int end = qMin(range.second, otherRange.second); + rangeLength -= end - start + 1; + if (rangeLength == 0) + break; + } + } + } + + if (rangeLength > 0) { + drawCurrent = true; + break; + } + } + } else { + drawCurrent = true; + } + + if (drawCurrent) + parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode); + } + } - parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode); + for (int i = 0; i < imageNodes.size(); ++i) { + const BinaryTreeNode *node = imageNodes.at(i); + if (node->selectionState == Selected) { + parentNode->addImage(node->boundingRect, node->image); + if (node->selectionState == Selected) { + QColor color = m_selectionColor; + color.setAlpha(128); + parentNode->addRectangleNode(node->boundingRect, color); + } + } } } @@ -844,7 +927,7 @@ void QQuickTextNodeEngine::addTextBlock(QTextDocument *textDocument, const QText QList<QGlyphRun> glyphRuns = layout.glyphRuns(); for (int i=0; i<glyphRuns.size(); ++i) - addUnselectedGlyphs(glyphRuns.at(i)); + addUnselectedGlyphs(glyphRuns.at(i), 0, layout.text().length() - 1); } } diff --git a/src/quick/items/qquicktextnodeengine_p.h b/src/quick/items/qquicktextnodeengine_p.h index 9a64853dd3..142375b5a3 100644 --- a/src/quick/items/qquicktextnodeengine_p.h +++ b/src/quick/items/qquicktextnodeengine_p.h @@ -75,10 +75,11 @@ public: BinaryTreeNode(const QGlyphRun &g, SelectionState selState, const QRectF &brect, const QQuickTextNode::Decorations &decs, const QColor &c, const QColor &bc, - const QPointF &pos, qreal a) + const QPointF &pos, qreal a, int rangeStart, int rangeEnd) : glyphRun(g), boundingRect(brect), selectionState(selState), clipNode(0), decorations(decs) , color(c), backgroundColor(bc), position(pos), ascent(a), leftChildIndex(-1), rightChildIndex(-1) { + ranges.append(qMakePair(rangeStart, rangeEnd)); } QGlyphRun glyphRun; @@ -95,11 +96,14 @@ public: int leftChildIndex; int rightChildIndex; + QList<QPair<int, int> > ranges; + static void insert(QVarLengthArray<BinaryTreeNode, 16> *binaryTree, const QRectF &rect, const QImage &image, qreal ascent, SelectionState selectionState) { insert(binaryTree, BinaryTreeNode(rect, image, selectionState, ascent)); } static void insert(QVarLengthArray<BinaryTreeNode, 16> *binaryTree, const QGlyphRun &glyphRun, SelectionState selectionState, - QQuickTextNode::Decorations decorations, const QColor &textColor, const QColor &backgroundColor, const QPointF &position); + QQuickTextNode::Decorations decorations, const QColor &textColor, const QColor &backgroundColor, const QPointF &position, + int rangeStart, int rangeEnd); static void insert(QVarLengthArray<BinaryTreeNode, 16> *binaryTree, const BinaryTreeNode &binaryTreeNode); static void inOrder(const QVarLengthArray<BinaryTreeNode, 16> &binaryTree, QVarLengthArray<int> *sortedIndexes, int currentIndex = 0); }; @@ -134,8 +138,8 @@ public: SelectionState selectionState, QTextDocument *textDocument, int pos, QTextFrameFormat::Position layoutPosition = QTextFrameFormat::InFlow); - void addSelectedGlyphs(const QGlyphRun &glyphRun); - void addUnselectedGlyphs(const QGlyphRun &glyphRun); + void addSelectedGlyphs(const QGlyphRun &glyphRun, int rangeStart, int rangeEnd); + void addUnselectedGlyphs(const QGlyphRun &glyphRun, int rangeStart, int rangeEnd); void addGlyphsInRange(int rangeStart, int rangeEnd, const QColor &color, const QColor &backgroundColor, int selectionStart, int selectionEnd); diff --git a/tests/manual/scenegraph_lancelot/data/text/textedit_bidi_selected_first.qml b/tests/manual/scenegraph_lancelot/data/text/textedit_bidi_selected_first.qml new file mode 100644 index 0000000000..78451c0570 --- /dev/null +++ b/tests/manual/scenegraph_lancelot/data/text/textedit_bidi_selected_first.qml @@ -0,0 +1,17 @@ +import QtQuick 2.0 + +Item { + width: 320 + height: 480 + TextEdit { + anchors.centerIn: parent + id: textEdit + font.family: "Arial" + font.pixelSize: 14 + text: "Lorem ipsum لمّ استبدال dolor sit." + + Component.onCompleted: { + textEdit.select(0, 1) + } + } +} diff --git a/tests/manual/scenegraph_lancelot/data/text/textedit_multiline_selected_first_line.qml b/tests/manual/scenegraph_lancelot/data/text/textedit_multiline_selected_first_line.qml new file mode 100644 index 0000000000..b2eacee344 --- /dev/null +++ b/tests/manual/scenegraph_lancelot/data/text/textedit_multiline_selected_first_line.qml @@ -0,0 +1,22 @@ +import QtQuick 2.0 + +Item { + width: 320 + height: 480 + + TextEdit { + id: textEdit + anchors.centerIn: parent + font.family: "Arial" + font.pixelSize: 16 + width: 200 + textFormat: TextEdit.RichText + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + text: "Lorem<br /> ipsum dolor sit amet, consectetur adipiscing elit. In id diam vitae enim fringilla vestibulum. Pellentesque non leo justo, quis vestibulum augue" + + Component.onCompleted: { + textEdit.select(0, 5) + } + } + +} diff --git a/tests/manual/scenegraph_lancelot/data/text/textedit_multiline_selected_two_first_lines.qml b/tests/manual/scenegraph_lancelot/data/text/textedit_multiline_selected_two_first_lines.qml new file mode 100644 index 0000000000..4df83b0203 --- /dev/null +++ b/tests/manual/scenegraph_lancelot/data/text/textedit_multiline_selected_two_first_lines.qml @@ -0,0 +1,22 @@ +import QtQuick 2.0 + +Item { + width: 320 + height: 480 + + TextEdit { + id: textEdit + anchors.centerIn: parent + font.family: "Arial" + font.pixelSize: 16 + width: 200 + textFormat: TextEdit.RichText + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + text: "Lorem<br />ipsum<br />dolor sit amet, consectetur adipiscing elit. In id diam vitae enim fringilla vestibulum. Pellentesque non leo justo, quis vestibulum augue" + + Component.onCompleted: { + textEdit.select(0, 11) + } + } + +} diff --git a/tests/manual/scenegraph_lancelot/data/text/textedit_selected_inline_image_selection_outside.qml b/tests/manual/scenegraph_lancelot/data/text/textedit_selected_inline_image_selection_outside.qml new file mode 100644 index 0000000000..e6e54cb487 --- /dev/null +++ b/tests/manual/scenegraph_lancelot/data/text/textedit_selected_inline_image_selection_outside.qml @@ -0,0 +1,20 @@ +import QtQuick 2.0 + +Item { + width: 320 + height: 480 + + TextEdit { + id: textEdit + anchors.centerIn: parent + font.pixelSize: 16 + width: parent.width + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + textFormat: Text.RichText + text: "This is selected from here and <img width=16 height=16 src=\"data/logo.png\" /> to here but not further" + + Component.onCompleted: { + textEdit.select(2, 3) + } + } +} diff --git a/tests/manual/scenegraph_lancelot/data/text/textedit_selection_color_separate_item.qml b/tests/manual/scenegraph_lancelot/data/text/textedit_selection_color_separate_item.qml new file mode 100644 index 0000000000..69679ed882 --- /dev/null +++ b/tests/manual/scenegraph_lancelot/data/text/textedit_selection_color_separate_item.qml @@ -0,0 +1,22 @@ +import QtQuick 2.0 + +Item { + width: 320 + height: 480 + + TextEdit { + id: textEdit + anchors.centerIn: parent + width: parent.width + font.pixelSize: 14 + text: "Lorem ipsum dolor sit amet, э consectetur adipiscing elit. Maecenas nibh" + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + + selectionColor: "red" + selectedTextColor: "blue" + + Component.onCompleted: { + textEdit.select(28, 29) + } + } +} diff --git a/tests/manual/scenegraph_lancelot/data/text/textinput_italic_selected.qml b/tests/manual/scenegraph_lancelot/data/text/textinput_italic_selected.qml new file mode 100644 index 0000000000..1df07e6269 --- /dev/null +++ b/tests/manual/scenegraph_lancelot/data/text/textinput_italic_selected.qml @@ -0,0 +1,17 @@ +import QtQuick 2.0 + +Item { + width: 320 + height: 480 + TextInput { + anchors.centerIn: parent + id: textInput + font.pixelSize: 44 + font.italic: true + text: "777" + + Component.onCompleted: { + textInput.select(1, 2) + } + } +} |