From bfb3c424abc7c839dc44de0e36024ede99ece736 Mon Sep 17 00:00:00 2001 From: Kent Hansen Date: Fri, 14 Oct 2011 10:51:42 +0200 Subject: Rename Qt Quick-specific classes to QQuick* The QSG (SceneGraph) prefix is too generic for Qt Quick(2)-specific classes. All the classes and files in the declarative/items directory have been renamed. In particular, for classes that are currently public, the renaming is as follows: QSGView --> QQuickView QSGCanvas --> QQuickCanvas QSGItem --> QQuickItem QSGPaintedItem --> QQuickPaintedItem The header files have been renamed accordingly (e.g. qsgview.h --> qquickview.h). Change-Id: Iac937fff81db20bb639486a793c3aeb5230b038c Reviewed-by: Kent Hansen --- src/declarative/items/qquicktextnode.cpp | 1343 ++++++++++++++++++++++++++++++ 1 file changed, 1343 insertions(+) create mode 100644 src/declarative/items/qquicktextnode.cpp (limited to 'src/declarative/items/qquicktextnode.cpp') diff --git a/src/declarative/items/qquicktextnode.cpp b/src/declarative/items/qquicktextnode.cpp new file mode 100644 index 0000000000..fab592ffdc --- /dev/null +++ b/src/declarative/items/qquicktextnode.cpp @@ -0,0 +1,1343 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquicktextnode_p.h" +#include "qsgsimplerectnode.h" +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + Creates an empty QQuickTextNode +*/ +QQuickTextNode::QQuickTextNode(QSGContext *context) + : m_context(context), m_cursorNode(0) +{ +#if defined(QML_RUNTIME_TESTING) + description = QLatin1String("text"); +#endif +} + +QQuickTextNode::~QQuickTextNode() +{ + qDeleteAll(m_textures); +} + +#if 0 +void QQuickTextNode::setColor(const QColor &color) +{ + if (m_usePixmapCache) { + setUpdateFlag(UpdateNodes); + } else { + for (QSGNode *childNode = firstChild(); childNode; childNode = childNode->nextSibling()) { + if (childNode->subType() == GlyphNodeSubType) { + QSGGlyphNode *glyphNode = static_cast(childNode); + if (glyphNode->color() == m_color) + glyphNode->setColor(color); + } else if (childNode->subType() == SolidRectNodeSubType) { + QSGSimpleRectNode *solidRectNode = static_cast(childNode); + if (solidRectNode->color() == m_color) + solidRectNode->setColor(color); + } + } + } + m_color = color; +} + +void QQuickTextNode::setStyleColor(const QColor &styleColor) +{ + if (m_textStyle != QQuickTextNode::NormalTextStyle) { + if (m_usePixmapCache) { + setUpdateFlag(UpdateNodes); + } else { + for (QSGNode *childNode = firstChild(); childNode; childNode = childNode->nextSibling()) { + if (childNode->subType() == GlyphNodeSubType) { + QSGGlyphNode *glyphNode = static_cast(childNode); + if (glyphNode->color() == m_styleColor) + glyphNode->setColor(styleColor); + } else if (childNode->subType() == SolidRectNodeSubType) { + QSGSimpleRectNode *solidRectNode = static_cast(childNode); + if (solidRectNode->color() == m_styleColor) + solidRectNode->setColor(styleColor); + } + } + } + } + m_styleColor = styleColor; +} +#endif + +QSGGlyphNode *QQuickTextNode::addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color, + QQuickText::TextStyle style, const QColor &styleColor, + QSGNode *parentNode) +{ + QSGGlyphNode *node = m_context->createGlyphNode(); + node->setGlyphs(position + QPointF(0, glyphs.rawFont().ascent()), glyphs); + node->setStyle(style); + node->setStyleColor(styleColor); + node->setColor(color); + node->update(); + + /* We flag the geometry as static, but we never call markVertexDataDirty + or markIndexDataDirty on them. This is because all text nodes are + discarded when a change occurs. If we start appending/removing from + existing geometry, then we also need to start marking the geometry as + dirty. + */ + node->geometry()->setIndexDataPattern(QSGGeometry::StaticPattern); + node->geometry()->setVertexDataPattern(QSGGeometry::StaticPattern); + + if (parentNode == 0) + parentNode = this; + parentNode->appendChildNode(node); + + return node; +} + +void QQuickTextNode::setCursor(const QRectF &rect, const QColor &color) +{ + if (m_cursorNode != 0) + delete m_cursorNode; + + m_cursorNode = new QSGSimpleRectNode(rect, color); + appendChildNode(m_cursorNode); +} + +namespace { + + struct BinaryTreeNode { + enum SelectionState { + Unselected, + Selected + }; + + BinaryTreeNode() + : selectionState(Unselected) + , clipNode(0) + , decorations(QQuickTextNode::NoDecoration) + , ascent(0.0) + , leftChildIndex(-1) + , rightChildIndex(-1) + { + + } + + BinaryTreeNode(const QRectF &brect, const QImage &i, SelectionState selState, qreal a) + : boundingRect(brect) + , selectionState(selState) + , clipNode(0) + , decorations(QQuickTextNode::NoDecoration) + , image(i) + , ascent(a) + , leftChildIndex(-1) + , rightChildIndex(-1) + { + } + + BinaryTreeNode(const QGlyphRun &g, SelectionState selState, const QRectF &brect, + const QQuickTextNode::Decorations &decs, const QColor &c, const QColor &bc, + const QPointF &pos, qreal a) + : glyphRun(g) + , boundingRect(brect) + , selectionState(selState) + , clipNode(0) + , decorations(decs) + , color(c) + , backgroundColor(bc) + , position(pos) + , ascent(a) + , leftChildIndex(-1) + , rightChildIndex(-1) + { + } + + QGlyphRun glyphRun; + QRectF boundingRect; + SelectionState selectionState; + QSGClipNode *clipNode; + QQuickTextNode::Decorations decorations; + QColor color; + QColor backgroundColor; + QPointF position; + QImage image; + qreal ascent; + + int leftChildIndex; + int rightChildIndex; + + static void insert(QVarLengthArray *binaryTree, + const QRectF &rect, + const QImage &image, + qreal ascent, + SelectionState selectionState) + { + insert(binaryTree, BinaryTreeNode(rect, image, selectionState, ascent)); + } + + static void insert(QVarLengthArray *binaryTree, + const QGlyphRun &glyphRun, + SelectionState selectionState, + const QColor &textColor, + const QColor &backgroundColor, + const QPointF &position) + { + QRectF searchRect = glyphRun.boundingRect(); + searchRect.translate(position); + + if (qFuzzyIsNull(searchRect.width()) || qFuzzyIsNull(searchRect.height())) + return; + + QQuickTextNode::Decorations decorations = QQuickTextNode::NoDecoration; + decorations |= (glyphRun.underline() ? QQuickTextNode::Underline : QQuickTextNode::NoDecoration); + decorations |= (glyphRun.overline() ? QQuickTextNode::Overline : QQuickTextNode::NoDecoration); + decorations |= (glyphRun.strikeOut() ? QQuickTextNode::StrikeOut : QQuickTextNode::NoDecoration); + decorations |= (backgroundColor.isValid() ? QQuickTextNode::Background : QQuickTextNode::NoDecoration); + + qreal ascent = glyphRun.rawFont().ascent(); + insert(binaryTree, BinaryTreeNode(glyphRun, selectionState, searchRect, decorations, + textColor, backgroundColor, position, ascent)); + } + + static void insert(QVarLengthArray *binaryTree, + const BinaryTreeNode &binaryTreeNode) + { + int newIndex = binaryTree->size(); + binaryTree->append(binaryTreeNode); + if (newIndex == 0) + return; + + int searchIndex = 0; + forever { + BinaryTreeNode *node = binaryTree->data() + searchIndex; + if (binaryTreeNode.boundingRect.left() < node->boundingRect.left()) { + if (node->leftChildIndex < 0) { + node->leftChildIndex = newIndex; + break; + } else { + searchIndex = node->leftChildIndex; + } + } else { + if (node->rightChildIndex < 0) { + node->rightChildIndex = newIndex; + break; + } else { + searchIndex = node->rightChildIndex; + } + } + } + } + + static void inOrder(const QVarLengthArray &binaryTree, + QVarLengthArray *sortedIndexes, + int currentIndex = 0) + { + Q_ASSERT(currentIndex < binaryTree.size()); + + const BinaryTreeNode *node = binaryTree.data() + currentIndex; + if (node->leftChildIndex >= 0) + inOrder(binaryTree, sortedIndexes, node->leftChildIndex); + + sortedIndexes->append(currentIndex); + + if (node->rightChildIndex >= 0) + inOrder(binaryTree, sortedIndexes, node->rightChildIndex); + } + }; + + // Engine that takes glyph runs as input, and produces a set of glyph nodes, clip nodes, + // and rectangle nodes to represent the text, decorations and selection. Will try to minimize + // number of nodes, and join decorations in neighbouring items + class SelectionEngine + { + public: + SelectionEngine() : m_hasSelection(false) {} + + QTextLine currentLine() const { return m_currentLine; } + + void setCurrentLine(const QTextLine ¤tLine) + { + if (m_currentLine.isValid()) + processCurrentLine(); + + m_currentLine = currentLine; + } + + void addBorder(const QRectF &rect, qreal border, QTextFrameFormat::BorderStyle borderStyle, + const QBrush &borderBrush); + void addFrameDecorations(QTextDocument *document, QTextFrame *frame); + void addImage(const QRectF &rect, const QImage &image, qreal ascent, + BinaryTreeNode::SelectionState selectionState, + QTextFrameFormat::Position layoutPosition); + void addTextObject(const QPointF &position, const QTextCharFormat &format, + BinaryTreeNode::SelectionState selectionState, + QTextDocument *textDocument, int pos, + QTextFrameFormat::Position layoutPosition = QTextFrameFormat::InFlow); + void addSelectedGlyphs(const QGlyphRun &glyphRun); + void addUnselectedGlyphs(const QGlyphRun &glyphRun); + void addGlyphsInRange(int rangeStart, int rangeEnd, + const QColor &color, const QColor &backgroundColor, + int selectionStart, int selectionEnd); + void addGlyphsForRanges(const QVarLengthArray &ranges, + int start, int end, + int selectionStart, int selectionEnd); + + void addToSceneGraph(QQuickTextNode *parent, + QQuickText::TextStyle style = QQuickText::Normal, + const QColor &styleColor = QColor()); + + void setSelectionColor(const QColor &selectionColor) + { + m_selectionColor = selectionColor; + } + + void setSelectedTextColor(const QColor &selectedTextColor) + { + m_selectedTextColor = selectedTextColor; + } + + void setTextColor(const QColor &textColor) + { + m_textColor = textColor; + } + + void setPosition(const QPointF &position) + { + m_position = position; + } + + private: + struct TextDecoration + { + TextDecoration() : selectionState(BinaryTreeNode::Unselected) {} + TextDecoration(const BinaryTreeNode::SelectionState &s, + const QRectF &r, + const QColor &c) + : selectionState(s) + , rect(r) + , color(c) + { + } + + BinaryTreeNode::SelectionState selectionState; + QRectF rect; + QColor color; + }; + + void processCurrentLine(); + void addTextDecorations(const QVarLengthArray &textDecorations, + qreal offset, qreal thickness); + + QColor m_selectionColor; + QColor m_textColor; + QColor m_backgroundColor; + QColor m_selectedTextColor; + QPointF m_position; + + QTextLine m_currentLine; + bool m_hasSelection; + + QList > m_backgrounds; + QList m_selectionRects; + QVarLengthArray m_currentLineTree; + + QList m_lines; + QVector m_processedNodes; + + QList > m_images; + }; + + void SelectionEngine::addTextDecorations(const QVarLengthArray &textDecorations, + qreal offset, qreal thickness) + { + for (int i=0; i sortedIndexes; // Indexes in tree sorted by x position + BinaryTreeNode::inOrder(m_currentLineTree, &sortedIndexes); + + Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size()); + + BinaryTreeNode::SelectionState currentSelectionState = BinaryTreeNode::Unselected; + QRectF currentRect; + + QQuickTextNode::Decorations currentDecorations = QQuickTextNode::NoDecoration; + qreal underlineOffset = 0.0; + qreal underlineThickness = 0.0; + + qreal overlineOffset = 0.0; + qreal overlineThickness = 0.0; + + qreal strikeOutOffset = 0.0; + qreal strikeOutThickness = 0.0; + + QRectF decorationRect = currentRect; + + QColor lastColor; + QColor lastBackgroundColor; + + QVarLengthArray pendingUnderlines; + QVarLengthArray pendingOverlines; + QVarLengthArray pendingStrikeOuts; + if (!sortedIndexes.isEmpty()) { + QSGClipNode *currentClipNode = m_hasSelection ? new QSGClipNode : 0; + bool currentClipNodeUsed = false; + for (int i=0; i<=sortedIndexes.size(); ++i) { + BinaryTreeNode *node = 0; + if (i < sortedIndexes.size()) { + int sortedIndex = sortedIndexes.at(i); + Q_ASSERT(sortedIndex < m_currentLineTree.size()); + + node = m_currentLineTree.data() + sortedIndex; + } + + if (i == 0) + currentSelectionState = node->selectionState; + + // Update decorations + if (currentDecorations != QQuickTextNode::NoDecoration) { + decorationRect.setY(m_position.y() + m_currentLine.y()); + decorationRect.setHeight(m_currentLine.height()); + + if (node != 0) + decorationRect.setRight(node->boundingRect.left()); + + TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor); + if (currentDecorations & QQuickTextNode::Underline) + pendingUnderlines.append(textDecoration); + + if (currentDecorations & QQuickTextNode::Overline) + pendingOverlines.append(textDecoration); + + if (currentDecorations & QQuickTextNode::StrikeOut) + pendingStrikeOuts.append(textDecoration); + + if (currentDecorations & QQuickTextNode::Background) + m_backgrounds.append(qMakePair(decorationRect, lastBackgroundColor)); + } + + // If we've reached an unselected node from a selected node, we add the + // 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 == BinaryTreeNode::Selected) + m_selectionRects.append(currentRect); + + if (currentClipNode != 0) { + if (!currentClipNodeUsed) { + delete currentClipNode; + } else { + currentClipNode->setIsRectangular(true); + currentClipNode->setClipRect(currentRect); + } + } + + if (node != 0 && m_hasSelection) + currentClipNode = new QSGClipNode; + else + currentClipNode = 0; + currentClipNodeUsed = false; + + if (node != 0) { + currentSelectionState = node->selectionState; + currentRect = node->boundingRect; + + // Make sure currentRect is valid, otherwise the unite won't work + if (currentRect.isNull()) + currentRect.setSize(QSizeF(1, 1)); + } + } else { + if (currentRect.isNull()) + currentRect = node->boundingRect; + else + currentRect = currentRect.united(node->boundingRect); + } + + if (node != 0) { + node->clipNode = currentClipNode; + currentClipNodeUsed = true; + + decorationRect = node->boundingRect; + + // If previous item(s) had underline and current does not, then we add the + // pending lines to the lists and likewise for overlines and strikeouts + if (!pendingUnderlines.isEmpty() + && !(node->decorations & QQuickTextNode::Underline)) { + addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness); + + pendingUnderlines.clear(); + + underlineOffset = 0.0; + underlineThickness = 0.0; + } + + // ### Add pending when overlineOffset/thickness changes to minimize number of + // nodes + if (!pendingOverlines.isEmpty()) { + addTextDecorations(pendingOverlines, overlineOffset, overlineThickness); + + pendingOverlines.clear(); + + overlineOffset = 0.0; + overlineThickness = 0.0; + } + + // ### Add pending when overlineOffset/thickness changes to minimize number of + // nodes + if (!pendingStrikeOuts.isEmpty()) { + addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness); + + pendingStrikeOuts.clear(); + + strikeOutOffset = 0.0; + strikeOutThickness = 0.0; + } + + // Merge current values with previous. Prefer greatest thickness + QRawFont rawFont = node->glyphRun.rawFont(); + if (node->decorations & QQuickTextNode::Underline) { + if (rawFont.lineThickness() > underlineThickness) { + underlineThickness = rawFont.lineThickness(); + underlineOffset = rawFont.underlinePosition(); + } + } + + if (node->decorations & QQuickTextNode::Overline) { + overlineOffset = -rawFont.ascent(); + overlineThickness = rawFont.lineThickness(); + } + + if (node->decorations & QQuickTextNode::StrikeOut) { + strikeOutThickness = rawFont.lineThickness(); + strikeOutOffset = rawFont.ascent() / -3.0; + } + + currentDecorations = node->decorations; + lastColor = node->color; + lastBackgroundColor = node->backgroundColor; + m_processedNodes.append(*node); + } + } + + if (!pendingUnderlines.isEmpty()) + addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness); + + if (!pendingOverlines.isEmpty()) + addTextDecorations(pendingOverlines, overlineOffset, overlineThickness); + + if (!pendingStrikeOuts.isEmpty()) + addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness); + } + + m_currentLineTree.clear(); + m_currentLine = QTextLine(); + m_hasSelection = false; + } + + void SelectionEngine::addImage(const QRectF &rect, const QImage &image, qreal ascent, + BinaryTreeNode::SelectionState selectionState, + QTextFrameFormat::Position layoutPosition) + { + QRectF searchRect = rect; + if (layoutPosition == QTextFrameFormat::InFlow) { + if (m_currentLineTree.isEmpty()) { + searchRect.moveTopLeft(m_position); + } else { + const BinaryTreeNode *lastNode = m_currentLineTree.data() + m_currentLineTree.size() - 1; + if (lastNode->glyphRun.isRightToLeft()) { + QPointF lastPos = lastNode->boundingRect.topLeft(); + searchRect.moveTopRight(lastPos - QPointF(0, ascent)); + } else { + QPointF lastPos = lastNode->boundingRect.topRight(); + searchRect.moveTopLeft(lastPos - QPointF(0, ascent)); + } + } + } + + BinaryTreeNode::insert(&m_currentLineTree, searchRect, image, ascent, selectionState); + } + + void SelectionEngine::addTextObject(const QPointF &position, const QTextCharFormat &format, + BinaryTreeNode::SelectionState selectionState, + QTextDocument *textDocument, int pos, + QTextFrameFormat::Position layoutPosition) + { + QTextObjectInterface *handler = textDocument->documentLayout()->handlerForObject(format.objectType()); + if (handler != 0) { + QImage image; + QSizeF size = handler->intrinsicSize(textDocument, pos, format); + + if (format.objectType() == QTextFormat::ImageObject) { + QTextImageFormat imageFormat = format.toImageFormat(); + QTextImageHandler *imageHandler = static_cast(handler); + image = imageHandler->image(textDocument, imageFormat); + } + + if (image.isNull()) { + image = QImage(size.toSize(), QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::transparent); + { + QPainter painter(&image); + handler->drawObject(&painter, image.rect(), textDocument, pos, format); + } + } + + qreal ascent; + QFontMetrics m(format.font()); + switch (format.verticalAlignment()) + { + case QTextCharFormat::AlignMiddle: + ascent = size.height() / 2 - 1; + break; + case QTextCharFormat::AlignBaseline: + ascent = size.height() - m.descent() - 1; + break; + default: + ascent = size.height() - 1; + } + + addImage(QRectF(position, size), image, ascent, selectionState, layoutPosition); + } + } + + void SelectionEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun) + { + BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Unselected, + m_textColor, m_backgroundColor, m_position); + } + + void SelectionEngine::addSelectedGlyphs(const QGlyphRun &glyphRun) + { + int currentSize = m_currentLineTree.size(); + BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Selected, + m_textColor, m_backgroundColor, m_position); + m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize; + } + + void SelectionEngine::addGlyphsForRanges(const QVarLengthArray &ranges, + int start, int end, + int selectionStart, int selectionEnd) + { + int currentPosition = start; + int remainingLength = end - start; + for (int j=0; j= currentPosition + && range.start < currentPosition + remainingLength) { + + if (range.start > currentPosition) { + addGlyphsInRange(currentPosition, range.start - currentPosition, + QColor(), QColor(), selectionStart, selectionEnd); + } + + int rangeEnd = qMin(range.start + range.length, currentPosition + remainingLength); + QColor rangeColor = range.format.hasProperty(QTextFormat::ForegroundBrush) + ? range.format.foreground().color() + : QColor(); + QColor rangeBackgroundColor = range.format.hasProperty(QTextFormat::BackgroundBrush) + ? range.format.background().color() + : QColor(); + + addGlyphsInRange(range.start, rangeEnd - range.start, + rangeColor, rangeBackgroundColor, + selectionStart, selectionEnd); + + currentPosition = range.start + range.length; + remainingLength = end - currentPosition; + + } else if (range.start > currentPosition + remainingLength || remainingLength <= 0) { + break; + } + } + + if (remainingLength > 0) { + addGlyphsInRange(currentPosition, remainingLength, QColor(), QColor(), + selectionStart, selectionEnd); + } + + } + + void SelectionEngine::addGlyphsInRange(int rangeStart, int rangeLength, + const QColor &color, const QColor &backgroundColor, + int selectionStart, int selectionEnd) + { + QColor oldColor; + if (color.isValid()) { + oldColor = m_textColor; + m_textColor = color; + } + + QColor oldBackgroundColor = m_backgroundColor; + if (backgroundColor.isValid()) { + oldBackgroundColor = m_backgroundColor; + m_backgroundColor = backgroundColor; + } + + bool hasSelection = selectionEnd >= 0 + && selectionStart <= selectionEnd; + + QTextLine &line = m_currentLine; + int rangeEnd = rangeStart + rangeLength; + if (!hasSelection || (selectionStart > rangeEnd || selectionEnd < rangeStart)) { + QList glyphRuns = line.glyphRuns(rangeStart, rangeLength); + for (int j=0; j glyphRuns = line.glyphRuns(rangeStart, + qMin(selectionStart - rangeStart, + rangeLength)); + + for (int j=0; j selectionStart) { + int start = qMax(selectionStart, rangeStart); + int length = qMin(selectionEnd - start + 1, rangeEnd - start); + QList glyphRuns = line.glyphRuns(start, length); + + for (int j=0; j= rangeStart && selectionEnd < rangeEnd) { + QList glyphRuns = line.glyphRuns(selectionEnd + 1, rangeEnd - selectionEnd - 1); + for (int j=0; j(document->documentLayout()); + QTextFrameFormat frameFormat = frame->format().toFrameFormat(); + + QTextTable *table = qobject_cast(frame); + QRectF boundingRect = table == 0 + ? documentLayout->frameBoundingRect(frame) + : documentLayout->tableBoundingRect(table); + + QBrush bg = frame->frameFormat().background(); + if (bg.style() != Qt::NoBrush) + m_backgrounds.append(qMakePair(boundingRect, bg.color())); + + if (!frameFormat.hasProperty(QTextFormat::FrameBorder)) + return; + + qreal borderWidth = frameFormat.border(); + if (qFuzzyIsNull(borderWidth)) + return; + + QBrush borderBrush = frameFormat.borderBrush(); + QTextFrameFormat::BorderStyle borderStyle = frameFormat.borderStyle(); + if (borderStyle == QTextFrameFormat::BorderStyle_None) + return; + + addBorder(boundingRect.adjusted(frameFormat.leftMargin(), frameFormat.topMargin(), + -frameFormat.rightMargin(), -frameFormat.bottomMargin()), + borderWidth, borderStyle, borderBrush); + if (table != 0) { + int rows = table->rows(); + int columns = table->columns(); + + for (int row=0; rowcellAt(row, column); + + QRectF cellRect = documentLayout->tableCellBoundingRect(table, cell); + addBorder(cellRect.adjusted(-borderWidth, -borderWidth, 0, 0), borderWidth, + borderStyle, borderBrush); + } + } + } + } + + void SelectionEngine::addToSceneGraph(QQuickTextNode *parentNode, + QQuickText::TextStyle style, + const QColor &styleColor) + { + if (m_currentLine.isValid()) + processCurrentLine(); + + + for (int i=0; iappendChildNode(new QSGSimpleRectNode(rect, color)); + } + + // First, prepend all selection rectangles to the tree + for (int i=0; iappendChildNode(new QSGSimpleRectNode(rect, m_selectionColor)); + } + + // Finally, add decorations for each node to the tree. + for (int i=0; iappendChildNode(new QSGSimpleRectNode(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 > > KeyType; + QHash map; + for (int i=0; iimage.isNull()) { + QGlyphRun glyphRun = node->glyphRun; + QRawFont rawFont = glyphRun.rawFont(); + QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont); + + QFontEngine *fontEngine = rawFontD->fontEngine; + + KeyType key(qMakePair(fontEngine, + qMakePair(node->clipNode, + qMakePair(node->color.rgba(), int(node->selectionState))))); + + BinaryTreeNode *otherNode = map.value(key, 0); + if (otherNode != 0) { + QGlyphRun &otherGlyphRun = otherNode->glyphRun; + + QVector otherGlyphIndexes = otherGlyphRun.glyphIndexes(); + QVector otherGlyphPositions = otherGlyphRun.positions(); + + otherGlyphIndexes += glyphRun.glyphIndexes(); + + QVector glyphPositions = glyphRun.positions(); + for (int j=0; jposition - otherNode->position); + } + + otherGlyphRun.setGlyphIndexes(otherGlyphIndexes); + otherGlyphRun.setPositions(otherGlyphPositions); + + } else { + map.insert(key, node); + } + } else { + parentNode->addImage(node->boundingRect, node->image); + if (node->selectionState == BinaryTreeNode::Selected) { + QColor color = m_selectionColor; + color.setAlpha(128); + parentNode->appendChildNode(new QSGSimpleRectNode(node->boundingRect, color)); + } + } + } + + // ...and add clip nodes and glyphs to tree. + QHash::const_iterator it = map.constBegin(); + while (it != map.constEnd()) { + + BinaryTreeNode *node = it.value(); + + QSGClipNode *clipNode = node->clipNode; + if (clipNode != 0 && clipNode->parent() == 0 ) + parentNode->appendChildNode(clipNode); + + QColor color = node->selectionState == BinaryTreeNode::Selected + ? m_selectedTextColor + : node->color; + + parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode); + + ++it; + } + } +} + +void QQuickTextNode::mergeFormats(QTextLayout *textLayout, + QVarLengthArray *mergedFormats) +{ + Q_ASSERT(mergedFormats != 0); + if (textLayout == 0) + return; + + QList additionalFormats = textLayout->additionalFormats(); + for (int i=0; iisEmpty()) { + QTextLayout::FormatRange *lastFormat = mergedFormats->data() + mergedFormats->size() - 1; + + if (additionalFormat.start < lastFormat->start + lastFormat->length) { + QTextLayout::FormatRange *mergedRange = 0; + + int length = additionalFormat.length; + if (additionalFormat.start > lastFormat->start) { + lastFormat->length = additionalFormat.start - lastFormat->start; + length -= lastFormat->length; + + mergedFormats->append(QTextLayout::FormatRange()); + mergedRange = mergedFormats->data() + mergedFormats->size() - 1; + lastFormat = mergedFormats->data() + mergedFormats->size() - 2; + } else { + mergedRange = lastFormat; + } + + mergedRange->format = lastFormat->format; + mergedRange->format.merge(additionalFormat.format); + mergedRange->start = additionalFormat.start; + + int end = qMin(additionalFormat.start + additionalFormat.length, + lastFormat->start + lastFormat->length); + + mergedRange->length = end - mergedRange->start; + length -= mergedRange->length; + + additionalFormat.start = end; + additionalFormat.length = length; + } + } + + if (additionalFormat.length > 0) + mergedFormats->append(additionalFormat); + } + } + +} + +namespace { + + class ProtectedLayoutAccessor: public QAbstractTextDocumentLayout + { + public: + inline QTextCharFormat formatAccessor(int pos) + { + return format(pos); + } + }; + +} + +void QQuickTextNode::addImage(const QRectF &rect, const QImage &image) +{ + QSGImageNode *node = m_context->createImageNode(); + QSGTexture *texture = m_context->createTexture(image); + m_textures.append(texture); + node->setTargetRect(rect); + node->setTexture(texture); + appendChildNode(node); + node->update(); +} + +void QQuickTextNode::addTextDocument(const QPointF &, QTextDocument *textDocument, + const QColor &textColor, + QQuickText::TextStyle style, const QColor &styleColor, + const QColor &selectionColor, const QColor &selectedTextColor, + int selectionStart, int selectionEnd) +{ + SelectionEngine engine; + engine.setTextColor(textColor); + engine.setSelectedTextColor(selectedTextColor); + engine.setSelectionColor(selectionColor); + + QList frames; + frames.append(textDocument->rootFrame()); + while (!frames.isEmpty()) { + QTextFrame *textFrame = frames.takeFirst(); + frames.append(textFrame->childFrames()); + + engine.addFrameDecorations(textDocument, textFrame); + + if (textFrame->firstPosition() > textFrame->lastPosition() + && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) { + const int pos = textFrame->firstPosition() - 1; + ProtectedLayoutAccessor *a = static_cast(textDocument->documentLayout()); + QTextCharFormat format = a->formatAccessor(pos); + QRectF rect = a->frameBoundingRect(textFrame); + + QTextBlock block = textFrame->firstCursorPosition().block(); + engine.setCurrentLine(block.layout()->lineForTextPosition(pos - block.position())); + engine.addTextObject(rect.topLeft(), format, BinaryTreeNode::Unselected, textDocument, + pos, textFrame->frameFormat().position()); + } else { + QTextFrame::iterator it = textFrame->begin(); + + while (!it.atEnd()) { + Q_ASSERT(!engine.currentLine().isValid()); + + QTextBlock block = it.currentBlock(); + int preeditLength = block.isValid() ? block.layout()->preeditAreaText().length() : 0; + int preeditPosition = block.isValid() ? block.layout()->preeditAreaPosition() : -1; + + QVarLengthArray colorChanges; + mergeFormats(block.layout(), &colorChanges); + + QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft(); + if (QTextList *textList = block.textList()) { + QPointF pos = blockPosition; + QTextLayout *layout = block.layout(); + if (layout->lineCount() > 0) { + QTextLine firstLine = layout->lineAt(0); + Q_ASSERT(firstLine.isValid()); + + engine.setCurrentLine(firstLine); + + QRectF textRect = firstLine.naturalTextRect(); + pos += textRect.topLeft(); + if (block.textDirection() == Qt::RightToLeft) + pos.rx() += textRect.width(); + + const QTextCharFormat charFormat = block.charFormat(); + QFont font(charFormat.font()); + QFontMetricsF fontMetrics(font); + QTextListFormat listFormat = textList->format(); + + QString listItemBullet; + switch (listFormat.style()) { + case QTextListFormat::ListCircle: + listItemBullet = QChar(0x25E6); // White bullet + break; + case QTextListFormat::ListSquare: + listItemBullet = QChar(0x25AA); // Black small square + break; + case QTextListFormat::ListDecimal: + case QTextListFormat::ListLowerAlpha: + case QTextListFormat::ListUpperAlpha: + case QTextListFormat::ListLowerRoman: + case QTextListFormat::ListUpperRoman: + listItemBullet = textList->itemText(block); + break; + default: + listItemBullet = QChar(0x2022); // Black bullet + break; + }; + + QSizeF size(fontMetrics.width(listItemBullet), fontMetrics.height()); + qreal xoff = fontMetrics.width(QLatin1Char(' ')); + if (block.textDirection() == Qt::LeftToRight) + xoff = -xoff - size.width(); + engine.setPosition(pos + QPointF(xoff, 0)); + + QTextLayout layout; + layout.setFont(font); + layout.setText(listItemBullet); // Bullet + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setPosition(QPointF(0, 0)); + layout.endLayout(); + + QList glyphRuns = layout.glyphRuns(); + for (int i=0; i(textDocument->objectForFormat(charFormat)); + if (frame && frame->frameFormat().position() == QTextFrameFormat::InFlow) { + BinaryTreeNode::SelectionState selectionState = + (selectionStart < textPos + text.length() + && selectionEnd >= textPos) + ? BinaryTreeNode::Selected + : BinaryTreeNode::Unselected; + + engine.addTextObject(QPointF(), charFormat, selectionState, textDocument, textPos); + } + textPos += text.length(); + } else { + if (!textColor.isValid()) + engine.setTextColor(charFormat.foreground().color()); + + int fragmentEnd = textPos + fragment.length(); + if (preeditPosition >= 0 + && preeditPosition >= textPos + && preeditPosition < fragmentEnd) { + fragmentEnd += preeditLength; + } + + while (textPos < fragmentEnd) { + int blockRelativePosition = textPos - block.position(); + QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition); + if (!engine.currentLine().isValid() + || line.lineNumber() != engine.currentLine().lineNumber()) { + engine.setCurrentLine(line); + } + + Q_ASSERT(line.textLength() > 0); + int lineEnd = line.textStart() + block.position() + line.textLength(); + + int len = qMin(lineEnd - textPos, fragmentEnd - textPos); + Q_ASSERT(len > 0); + + int currentStepEnd = textPos + len; + + engine.addGlyphsForRanges(colorChanges, + textPos - block.position(), + currentStepEnd - block.position(), + selectionStart - block.position(), + selectionEnd - block.position()); + + textPos = currentStepEnd; + } + } + + ++blockIterator; + } + + engine.setCurrentLine(QTextLine()); // Reset current line because the text layout changed + ++it; + } + } + } + + engine.addToSceneGraph(this, style, styleColor); +} + +void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color, + QQuickText::TextStyle style, const QColor &styleColor, + const QColor &selectionColor, const QColor &selectedTextColor, + int selectionStart, int selectionEnd) +{ + SelectionEngine engine; + engine.setTextColor(color); + engine.setSelectedTextColor(selectedTextColor); + engine.setSelectionColor(selectionColor); + engine.setPosition(position); + + int preeditLength = textLayout->preeditAreaText().length(); + int preeditPosition = textLayout->preeditAreaPosition(); + + QVarLengthArray colorChanges; + mergeFormats(textLayout, &colorChanges); + + for (int i=0; ilineCount(); ++i) { + QTextLine line = textLayout->lineAt(i); + + int start = line.textStart(); + int length = line.textLength(); + int end = start + length; + + if (preeditPosition >= 0 + && preeditPosition >= start + && preeditPosition < end) { + end += preeditLength; + } + + engine.setCurrentLine(line); + engine.addGlyphsForRanges(colorChanges, start, end, selectionStart, selectionEnd); + } + + engine.addToSceneGraph(this, style, styleColor); +} + +void QQuickTextNode::deleteContent() +{ + while (firstChild() != 0) + delete firstChild(); + m_cursorNode = 0; +} + +#if 0 +void QQuickTextNode::updateNodes() +{ + return; + deleteContent(); + if (m_text.isEmpty()) + return; + + if (m_usePixmapCache) { + // ### gunnar: port properly +// QPixmap pixmap = generatedPixmap(); +// if (pixmap.isNull()) +// return; + +// QSGImageNode *pixmapNode = m_context->createImageNode(); +// pixmapNode->setRect(pixmap.rect()); +// pixmapNode->setSourceRect(pixmap.rect()); +// pixmapNode->setOpacity(m_opacity); +// pixmapNode->setClampToEdge(true); +// pixmapNode->setLinearFiltering(m_linearFiltering); + +// appendChildNode(pixmapNode); + } else { + if (m_text.isEmpty()) + return; + + // Implement styling by drawing text several times at slight shifts. shiftForStyle + // contains the sequence of shifted positions at which to draw the text. All except + // the last will be drawn with styleColor. + QList shiftForStyle; + switch (m_textStyle) { + case OutlineTextStyle: + // ### Should be made faster by implementing outline material + shiftForStyle << QPointF(-1, 0); + shiftForStyle << QPointF(0, -1); + shiftForStyle << QPointF(1, 0); + shiftForStyle << QPointF(0, 1); + break; + case SunkenTextStyle: + shiftForStyle << QPointF(0, -1); + break; + case RaisedTextStyle: + shiftForStyle << QPointF(0, 1); + break; + default: + break; + } + + shiftForStyle << QPointF(0, 0); // Regular position + while (!shiftForStyle.isEmpty()) { + QPointF shift = shiftForStyle.takeFirst(); + + // Use styleColor for all but last shift + if (m_richText) { + QColor overrideColor = shiftForStyle.isEmpty() ? QColor() : m_styleColor; + + QTextFrame *textFrame = m_textDocument->rootFrame(); + QPointF p = m_textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft(); + + QTextFrame::iterator it = textFrame->begin(); + while (!it.atEnd()) { + addTextBlock(shift + p, it.currentBlock(), overrideColor); + ++it; + } + } else { + addTextLayout(shift, m_textLayout, shiftForStyle.isEmpty() + ? m_color + : m_styleColor); + } + } + } +} +#endif + +QT_END_NAMESPACE -- cgit v1.2.3