Merge branch 'master' into refactor
authorGunnar Sletta <gunnar.sletta@nokia.com>
Thu, 25 Aug 2011 09:47:24 +0000 (11:47 +0200)
committerGunnar Sletta <gunnar.sletta@nokia.com>
Thu, 25 Aug 2011 10:52:15 +0000 (12:52 +0200)
Conflicts:
src/declarative/items/qsgcanvas.cpp
src/declarative/items/qsgitem.cpp
src/declarative/items/qsgtextnode.cpp
tests/auto/declarative/examples/examples.pro
tools/qmlviewer/qmlviewer.pro

Change-Id: Icbb0ef5dc79b658c62fd2b2c25a66c9bb3cbeb10

31 files changed:
1  2 
src/declarative/items/qsgborderimage.cpp
src/declarative/items/qsgcanvas.cpp
src/declarative/items/qsgcanvas_p.h
src/declarative/items/qsgflickable.cpp
src/declarative/items/qsgimage.cpp
src/declarative/items/qsgitem.cpp
src/declarative/items/qsgitem.h
src/declarative/items/qsgmousearea.cpp
src/declarative/items/qsgpathview.cpp
src/declarative/items/qsgpincharea.cpp
src/declarative/items/qsgshadereffectsource.cpp
src/declarative/items/qsgtext.cpp
src/declarative/items/qsgtextedit.cpp
src/declarative/items/qsgtextinput.cpp
src/declarative/items/qsgtextnode.cpp
src/declarative/particles/qsgcustomparticle.cpp
src/declarative/qml/qdeclarativeengine.cpp
src/declarative/scenegraph/coreapi/qsgnode.cpp
src/declarative/scenegraph/coreapi/qsgrenderer.cpp
src/declarative/util/qdeclarativestateoperations.cpp
src/qtquick1/graphicsitems/qdeclarativeborderimage.cpp
src/qtquick1/graphicsitems/qdeclarativeflipable.cpp
src/qtquick1/graphicsitems/qdeclarativeitem.cpp
src/qtquick1/graphicsitems/qdeclarativerectangle.cpp
src/qtquick1/util/qdeclarativestateoperations.cpp
tests/auto/declarative/examples/examples.pro
tests/auto/declarative/examples/tst_examples.cpp
tests/auto/declarative/qjsvalue/tst_qjsvalue.cpp
tests/auto/declarative/qsglistview/tst_qsglistview.cpp
tools/qmlscene/main.cpp
tools/qmlviewer/qmlviewer.pro

@@@ -332,9 -422,10 +332,8 @@@ QSGCanvasPrivate::QSGCanvasPrivate(
      : rootItem(0)
      , activeFocusItem(0)
      , mouseGrabberItem(0)
-     , hoverItem(0)
      , dirtyItemList(0)
      , context(0)
 -    , contextFailed(false)
 -    , threadedRendering(false)
      , animationRunning(false)
      , renderThreadAwakened(false)
      , vsyncAnimations(false)
@@@ -813,17 -952,19 +811,19 @@@ QSGItem *QSGCanvas::mouseGrabberItem() 
  }
  
  
void QSGCanvasPrivate::clearHover()
bool QSGCanvasPrivate::clearHover()
  {
      Q_Q(QSGCanvas);
-     if (!hoverItem)
-         return;
+     if (hoverItems.isEmpty())
+         return false;
  
 -    QPointF pos = q->mapFromGlobal(QCursor::pos());
 +    QPointF pos = QCursor::pos(); // ### refactor: q->mapFromGlobal(QCursor::pos());
  
-     QSGItem *item = hoverItem;
-     hoverItem = 0;
-     sendHoverEvent(QEvent::HoverLeave, item, pos, pos, QApplication::keyboardModifiers(), true);
+     bool accepted = false;
+     foreach (QSGItem* item, hoverItems)
+         accepted = sendHoverEvent(QEvent::HoverLeave, item, pos, pos, QApplication::keyboardModifiers(), true) || accepted;
+     hoverItems.clear();
+     return accepted;
  }
  
  
Simple merge
Simple merge
@@@ -1164,10 -2043,12 +2042,9 @@@ void QSGItemPrivate::initCanvas(Initial
  
      canvas = c;
  
-     if (canvas && polishScheduled) 
+     if (canvas && polishScheduled)
          QSGCanvasPrivate::get(canvas)->itemsToPolish.insert(q);
  
-     // XXX todo - why aren't these added to the destroy list?
 -    if (canvas && hoverEnabled && !canvas->hasMouseTracking())
 -        canvas->setMouseTracking(true);
 -
      itemNodeInstance = 0;
      opacityNode = 0;
      clipNode = 0;
@@@ -2999,21 -4415,37 +4409,21 @@@ bool QSGItem::isUnderMouse() cons
          return false;
  
      QPoint cursorPos = QCursor::pos();
 -    if (QRectF(0, 0, width(), height()).contains(mapFromScene(d->canvas->mapFromGlobal(cursorPos))))
 +    if (QRectF(0, 0, width(), height()).contains(mapFromScene(cursorPos))) // ### refactor: d->canvas->mapFromGlobal(cursorPos))))
          return true;
-     return false; 
+     return false;
  }
  
- bool QSGItem::acceptHoverEvents() const 
- { 
+ bool QSGItem::acceptHoverEvents() const
+ {
      Q_D(const QSGItem);
      return d->hoverEnabled;
  }
  
- void QSGItem::setAcceptHoverEvents(bool enabled) 
- { 
+ void QSGItem::setAcceptHoverEvents(bool enabled)
+ {
      Q_D(QSGItem);
      d->hoverEnabled = enabled;
 -
 -    if (d->canvas){
 -        QSGCanvasPrivate *c = QSGCanvasPrivate::get(d->canvas);
 -        if (d->hoverEnabled){
 -            if (!d->canvas->hasMouseTracking())
 -                d->canvas->setMouseTracking(true);
 -            if (isUnderMouse())
 -                c->hoverItems.prepend(this);
 -                c->sendHoverEvent(QEvent::HoverEnter, this, c->lastMousePosition, c->lastMousePosition,
 -                        QApplication::keyboardModifiers(), true);
 -        } else {
 -            c->hoverItems.removeAll(this);
 -            c->sendHoverEvent(QEvent::HoverLeave, this, c->lastMousePosition, c->lastMousePosition,
 -                    QApplication::keyboardModifiers(), true);
 -        }
 -    }
  }
  
  void QSGItem::grabMouse() 
Simple merge
Simple merge
@@@ -810,17 -753,40 +814,17 @@@ static void get_wrap_mode(QSGShaderEffe
  }
  
  
 -QSGTexture *QSGShaderEffectSource::texture() const
 -{
 -    m_texture->setMipmapFiltering(m_mipmap ? QSGTexture::Linear : QSGTexture::None);
 -    m_texture->setFiltering(QSGItemPrivate::get(this)->smooth ? QSGTexture::Linear : QSGTexture::Nearest);
 -    QSGTexture::WrapMode h, v;
 -    get_wrap_mode(m_wrapMode, &h, &v);
 -    m_texture->setHorizontalWrapMode(h);
 -    m_texture->setVerticalWrapMode(v);
 -    return m_texture;
 -}
 -
  QSGNode *QSGShaderEffectSource::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
  {
-     if (!m_sourceItem) {
+     if (!m_sourceItem || m_sourceItem->width() == 0 || m_sourceItem->height() == 0) {
          delete oldNode;
          return 0;
      }
  
 -    QSGShaderEffectSourceNode *node = static_cast<QSGShaderEffectSourceNode *>(oldNode);
 -    if (!node) {
 -        node = new QSGShaderEffectSourceNode;
 -        node->setTexture(m_texture);
 -        connect(m_texture, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()), Qt::DirectConnection);
 -    }
 -
 -    // If live and recursive, update continuously.
 -    if (m_live && m_recursive)
 -        node->markDirty(QSGNode::DirtyMaterial);
 -
      QSGShaderEffectTexture *tex = qobject_cast<QSGShaderEffectTexture *>(m_texture);
 -
      tex->setLive(m_live);
      tex->setItem(QSGItemPrivate::get(m_sourceItem)->itemNode());
-     QRectF sourceRect = m_sourceRect.isNull()
+     QRectF sourceRect = m_sourceRect.width() == 0 || m_sourceRect.height() == 0
                        ? QRectF(0, 0, m_sourceItem->width(), m_sourceItem->height())
                        : m_sourceRect;
      tex->setRect(sourceRect);
Simple merge
  #include "qsgtextedit_p_p.h"
  #include "qsgevents_p_p.h"
  #include "qsgcanvas.h"
+ #include "qsgtextnode_p.h"
+ #include "qsgsimplerectnode.h"
  
  #include <QtDeclarative/qdeclarativeinfo.h>
 -#include <QtGui/qapplication.h>
 -#include <QtGui/qgraphicssceneevent.h>
 +#include <QtWidgets/qapplication.h>
 +#include <QtWidgets/qgraphicssceneevent.h>
  #include <QtGui/qpainter.h>
  #include <QtGui/qtextobject.h>
  #include <QtCore/qmath.h>
@@@ -1200,10 -1913,49 +1915,49 @@@ void QSGTextEdit::openSoftwareInputPane
      }
  }
  
+ /*!
+     \qmlmethod void QtQuick2::TextEdit::closeSoftwareInputPanel()
+     Closes a software input panel like a virtual keyboard shown on the screen, useful
+     for customizing when you want the input keyboard to be shown and hidden in
+     your application.
+     By default the opening of input panels follows the platform style. On Symbian^1 and
+     Symbian^3 -based devices the panels are opened by clicking TextEdit. On other platforms
+     the panels are automatically opened when TextEdit element gains active focus. Input panels are
+     always closed if no editor has active focus.
+     You can disable the automatic behavior by setting the property \c activeFocusOnPress to false
+     and use functions openSoftwareInputPanel() and closeSoftwareInputPanel() to implement
+     the behavior you want.
+     Only relevant on platforms, which provide virtual keyboards.
+     \code
+         import QtQuick 1.0
+         TextEdit {
+             id: textEdit
+             text: "Hello world!"
+             activeFocusOnPress: false
+             MouseArea {
+                 anchors.fill: parent
+                 onClicked: {
+                     if (!textEdit.activeFocus) {
+                         textEdit.forceActiveFocus();
+                         textEdit.openSoftwareInputPanel();
+                     } else {
+                         textEdit.focus = false;
+                     }
+                 }
+                 onPressAndHold: textEdit.closeSoftwareInputPanel();
+             }
+         }
+     \endcode
+ */
  void QSGTextEdit::closeSoftwareInputPanel()
 -{
 +{  
      if (qApp) {
 -        if (canvas() && canvas() == qApp->focusWidget()) {
 +        if (canvas()) {
              QEvent event(QEvent::CloseSoftwareInputPanel);
              QApplication::sendEvent(canvas(), &event);
          }
  
  #include <private/qdeclarativeglobal_p.h>
  #include <private/qwidget_p.h>
+ #include <private/qsgdistancefieldglyphcache_p.h>
  
  #include <QtDeclarative/qdeclarativeinfo.h>
 -#include <QtGui/qgraphicssceneevent.h>
 -#include <QtGui/qinputcontext.h>
 +#include <QtWidgets/qgraphicssceneevent.h>
 +#include <QtWidgets/qinputcontext.h>
  #include <QTextBoundaryFinder>
  #include <qstyle.h>
+ #include <qsgtextnode_p.h>
+ #include <qsgsimplerectnode.h>
  
  QT_BEGIN_NAMESPACE
  
@@@ -1054,10 -1700,50 +1701,49 @@@ void QSGTextInput::moveCursorSelection(
      }
  }
  
+ /*!
+     \qmlmethod void QtQuick2::TextInput::openSoftwareInputPanel()
+     Opens software input panels like virtual keyboards for typing, useful for
+     customizing when you want the input keyboard to be shown and hidden in
+     your application.
+     By default the opening of input panels follows the platform style. On Symbian^1 and
+     Symbian^3 -based devices the panels are opened by clicking TextInput. On other platforms
+     the panels are automatically opened when TextInput element gains active focus. Input panels are
+     always closed if no editor has active focus.
+   . You can disable the automatic behavior by setting the property \c activeFocusOnPress to false
+     and use functions openSoftwareInputPanel() and closeSoftwareInputPanel() to implement
+     the behavior you want.
+     Only relevant on platforms, which provide virtual keyboards.
+     \qml
+         import QtQuick 1.0
+         TextInput {
+             id: textInput
+             text: "Hello world!"
+             activeFocusOnPress: false
+             MouseArea {
+                 anchors.fill: parent
+                 onClicked: {
+                     if (!textInput.activeFocus) {
+                         textInput.forceActiveFocus()
+                         textInput.openSoftwareInputPanel();
+                     } else {
+                         textInput.focus = false;
+                     }
+                 }
+                 onPressAndHold: textInput.closeSoftwareInputPanel();
+             }
+         }
+     \endqml
+ */
  void QSGTextInput::openSoftwareInputPanel()
  {
 -    QEvent event(QEvent::RequestSoftwareInputPanel);
      if (qApp) {
 -        if (canvas() && canvas() == qApp->focusWidget()) {
 +        if (canvas()) {
              QEvent event(QEvent::RequestSoftwareInputPanel);
              QApplication::sendEvent(canvas(), &event);
          }
@@@ -119,76 -121,544 +121,553 @@@ void QSGTextNode::setStyleColor(const Q
  }
  #endif
  
void QSGTextNode::addTextDecorations(Decoration decorations, const QPointF &position,
-                                      const QColor &color, qreal width, qreal lineThickness,
-                                      qreal underlinePos, qreal ascent)
QSGGlyphNode *QSGTextNode::addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color,
+                                      QSGText::TextStyle style, const QColor &styleColor,
+                                      QSGNode *parentNode)
  {
-     QRectF line(position.x(), position.y() - lineThickness / 2.0, width, lineThickness);
+     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();
  
-     if (decorations & Underline) {
-         int underlinePosition = qCeil(underlinePos);
-         QRectF underline(line);
-         underline.translate(0.0, underlinePosition);
-         appendChildNode(new QSGSimpleRectNode(underline, color));
-     }
++    /* 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 (decorations & Overline) {
-         QRectF overline(line);
-         overline.translate(0.0, -ascent);
-         appendChildNode(new QSGSimpleRectNode(overline, color));
-     }
+     if (parentNode == 0)
+         parentNode = this;
+     parentNode->appendChildNode(node);
  
-     if (decorations & StrikeOut) {
-         QRectF strikeOut(line);
-         strikeOut.translate(0.0, ascent / -3.0);
-         appendChildNode(new QSGSimpleRectNode(strikeOut, color));
-     }
+     return node;
  }
  
- QSGGlyphNode *QSGTextNode::addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color,
-                                            QSGText::TextStyle style, const QColor &styleColor, QSGGlyphNode *prevNode)
+ void QSGTextNode::setCursor(const QRectF &rect, const QColor &color)
  {
-     QSGGlyphNode *node = prevNode;
+     if (m_cursorNode != 0)
+         delete m_cursorNode;
+     m_cursorNode = new QSGSimpleRectNode(rect, color);
+     appendChildNode(m_cursorNode);
+ }
+ namespace {
  
-     if (!node)
-         node = m_context->createGlyphNode();
+     struct BinaryTreeNode {
+         enum SelectionState {
+             Unselected,
+             Selected
+         };
  
-     node->setGlyphs(position, glyphs);
+         BinaryTreeNode()
+             : selectionState(Unselected)
+             , clipNode(0)
+             , decorations(QSGTextNode::NoDecoration)
+             , leftChildIndex(-1)
+             , rightChildIndex(-1)
+         {
  
-     if (node != prevNode) {
-         if (QSGDistanceFieldGlyphCache::distanceFieldEnabled()) {
-             QSGDistanceFieldGlyphNode *dfNode = static_cast<QSGDistanceFieldGlyphNode *>(node);
-             dfNode->setStyle(style);
-             dfNode->setStyleColor(styleColor);
          }
-         node->setColor(color);
+         BinaryTreeNode(const QGlyphRun &g, SelectionState selState, const QRectF &brect,
+                        const QSGTextNode::Decorations &decs, const QColor &c,
+                        const QPointF &pos)
+             : glyphRun(g)
+             , boundingRect(brect)
+             , selectionState(selState)
+             , clipNode(0)
+             , decorations(decs)
+             , color(c)
+             , position(pos)
+             , leftChildIndex(-1)
+             , rightChildIndex(-1)
+         {
+         }
+         QGlyphRun glyphRun;
+         QRectF boundingRect;
+         SelectionState selectionState;
+         QSGClipNode *clipNode;
+         QSGTextNode::Decorations decorations;
+         QColor color;
+         QPointF position;
+         int leftChildIndex;
+         int rightChildIndex;
+         static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
+                            const QGlyphRun &glyphRun,
+                            SelectionState selectionState,
+                            const QColor &textColor,
+                            const QPointF &position)
+         {
+             int newIndex = binaryTree->size();
+             QRectF searchRect = glyphRun.boundingRect();
+             if (qFuzzyIsNull(searchRect.width()) || qFuzzyIsNull(searchRect.height()))
+                 return;
+             QSGTextNode::Decorations decorations = QSGTextNode::NoDecoration;
+             decorations |= (glyphRun.underline() ? QSGTextNode::Underline : QSGTextNode::NoDecoration);
+             decorations |= (glyphRun.overline()  ? QSGTextNode::Overline  : QSGTextNode::NoDecoration);
+             decorations |= (glyphRun.strikeOut() ? QSGTextNode::StrikeOut : QSGTextNode::NoDecoration);
+             binaryTree->append(BinaryTreeNode(glyphRun, selectionState, searchRect, decorations,
+                                               textColor, position));
+             if (newIndex == 0)
+                 return;
+             int searchIndex = 0;
+             forever {
+                 BinaryTreeNode *node = binaryTree->data() + searchIndex;
+                 if (searchRect.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<BinaryTreeNode> &binaryTree,
+                             QVarLengthArray<int> *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 &currentLine)
+         {
+             if (m_currentLine.isValid())
+                 processCurrentLine();
+             m_currentLine = currentLine;
+         }
+         void addSelectedGlyphs(const QGlyphRun &glyphRun);
+         void addUnselectedGlyphs(const QGlyphRun &glyphRun);
+         void addToSceneGraph(QSGTextNode *parent,
+                              QSGText::TextStyle style = QSGText::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<TextDecoration> &textDecorations,
+                                 qreal offset, qreal thickness);
+         QColor m_selectionColor;
+         QColor m_textColor;
+         QColor m_selectedTextColor;
+         QPointF m_position;
+         QTextLine m_currentLine;
+         bool m_hasSelection;
+         QList<QRectF> m_selectionRects;
+         QVarLengthArray<BinaryTreeNode> m_currentLineTree;
+         QList<TextDecoration> m_lines;
+         QVector<BinaryTreeNode> m_processedNodes;
+     };
+     void SelectionEngine::addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
+                                              qreal offset, qreal thickness)
+     {
+         for (int i=0; i<textDecorations.size(); ++i) {
+             TextDecoration textDecoration = textDecorations.at(i);
+             {
+                 QRectF &rect = textDecoration.rect;
+                 rect.setY(qRound(rect.y() + m_currentLine.ascent() + offset));
+                 rect.setHeight(thickness);
+             }
+             m_lines.append(textDecoration);
+         }
      }
  
-     node->update();
+     void SelectionEngine::processCurrentLine()
+     {
+         // No glyphs, do nothing
+         if (m_currentLineTree.isEmpty())
+             return;
+         // 1. Go through current line and get correct decoration position for each node based on
+         // neighbouring decorations. Add decoration to global list
+         // 2. Create clip nodes for all selected text. Try to merge as many as possible within
+         // the line.
+         // 3. Add QRects to a list of selection rects.
+         // 4. Add all nodes to a global processed list
+         QVarLengthArray<int> 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;
+         QSGTextNode::Decorations currentDecorations = QSGTextNode::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;
+         QVarLengthArray<TextDecoration> pendingUnderlines;
+         QVarLengthArray<TextDecoration> pendingOverlines;
+         QVarLengthArray<TextDecoration> 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 != QSGTextNode::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 & QSGTextNode::Underline)
+                         pendingUnderlines.append(textDecoration);
+                     if (currentDecorations & QSGTextNode::Overline)
+                         pendingOverlines.append(textDecoration);
+                     if (currentDecorations & QSGTextNode::StrikeOut)
+                         pendingStrikeOuts.append(textDecoration);
+                 }
+                 // 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 & QSGTextNode::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 & QSGTextNode::Underline) {
+                         if (rawFont.lineThickness() > underlineThickness) {
+                             underlineThickness = rawFont.lineThickness();
+                             underlineOffset = rawFont.underlinePosition();
+                         }
+                     }
+                     if (node->decorations & QSGTextNode::Overline) {
+                         overlineOffset = -rawFont.ascent();
+                         overlineThickness = rawFont.lineThickness();
+                     }
+                     if (node->decorations & QSGTextNode::StrikeOut) {
+                         strikeOutThickness = rawFont.lineThickness();
+                         strikeOutOffset = rawFont.ascent() / -3.0;
+                     }
+                     currentDecorations = node->decorations;
+                     lastColor = node->color;
+                     m_processedNodes.append(*node);
+                 }
+             }
  
-     // A new node, add it to the graph.
-     if (node != prevNode) {
-         appendChildNode(node);
-         /* 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 there are pending decorations, we need to add them
+             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;
      }
  
-     return node;
+     void SelectionEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun)
+     {
+         BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Unselected,
+                                m_textColor, m_position);
+     }
+     void SelectionEngine::addSelectedGlyphs(const QGlyphRun &glyphRun)
+     {
+         int currentSize = m_currentLineTree.size();
+         BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Selected,
+                                m_textColor, m_position);
+         m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize;
+     }
+     void SelectionEngine::addToSceneGraph(QSGTextNode *parentNode,
+                                           QSGText::TextStyle style,
+                                           const QColor &styleColor)
+     {
+         if (m_currentLine.isValid())
+             processCurrentLine();
+         // 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->appendChildNode(new QSGSimpleRectNode(rect, m_selectionColor));
+         }
+         // 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<QSGClipNode *, QPair<QRgb, int> > > KeyType;
+         QHash<KeyType, BinaryTreeNode *> map;
+         for (int i=0; i<m_processedNodes.size(); ++i) {
+             BinaryTreeNode *node = m_processedNodes.data() + i;
+             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<quint32> otherGlyphIndexes = otherGlyphRun.glyphIndexes();
+                 QVector<QPointF> otherGlyphPositions = otherGlyphRun.positions();
+                 otherGlyphIndexes += glyphRun.glyphIndexes();
+                 QVector<QPointF> glyphPositions = glyphRun.positions();
+                 for (int j=0; j<glyphPositions.size(); ++j) {
+                     otherGlyphPositions += glyphPositions.at(j) + (node->position - otherNode->position);
+                 }
+                 otherGlyphRun.setGlyphIndexes(otherGlyphIndexes);
+                 otherGlyphRun.setPositions(otherGlyphPositions);
+             } else {
+                 map.insert(key, node);
+             }
+         }
+         // ...and add clip nodes and glyphs to tree.
+         QHash<KeyType, BinaryTreeNode *>::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;
+         }
+         // 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 == BinaryTreeNode::Selected
+                     ? m_selectedTextColor
+                     : textDecoration.color;
+             parentNode->appendChildNode(new QSGSimpleRectNode(textDecoration.rect, color));
+         }
+     }
  }
  
- void QSGTextNode::addTextDocument(const QPointF &position, QTextDocument *textDocument, const QColor &color,
-                                   QSGText::TextStyle style, const QColor &styleColor)
+ void QSGTextNode::addTextDocument(const QPointF &, QTextDocument *textDocument,
+                                   const QColor &textColor,
+                                   QSGText::TextStyle style, const QColor &styleColor,
+                                   const QColor &selectionColor, const QColor &selectedTextColor,
+                                   int selectionStart, int selectionEnd)
  {
-     Q_UNUSED(position)
      QTextFrame *textFrame = textDocument->rootFrame();
-     QPointF p = textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
+     QPointF position = textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
+     SelectionEngine engine;
+     engine.setTextColor(textColor);
+     engine.setSelectedTextColor(selectedTextColor);
+     engine.setSelectionColor(selectionColor);
+     bool hasSelection = selectionStart >= 0 && selectionEnd >= 0 && selectionStart != selectionEnd;
  
      QTextFrame::iterator it = textFrame->begin();
      while (!it.atEnd()) {
@@@ -18,4 -18,6 +18,6 @@@ symbian: 
  
  CONFIG += parallel_test
  
 -QT += core-private gui-private declarative-private qtquick1-private
 +QT += core-private gui-private declarative-private qtquick1-private widgets-private
+ qpa:CONFIG+=insignificant_test  # QTBUG-20990, aborts
  #include <QtDeclarative/qdeclarativecomponent.h>
  #include <QtQuick1/qdeclarativeview.h>
  #include <QtCore/qdir.h>
 -#include <QtGui/QFormLayout>
 -#include <QtGui/QComboBox>
 -#include <QtGui/QCheckBox>
 -#include <QtGui/QDialog>
 -#include <QtGui/QDialogButtonBox>
 -#include <QtGui/QFileDialog>
 -#include <QtGui/QGraphicsView>
 +#include <QtWidgets/QFormLayout>
 +#include <QtWidgets/QComboBox>
 +#include <QtWidgets/QCheckBox>
 +#include <QtWidgets/QDialog>
 +#include <QtWidgets/QDialogButtonBox>
 +#include <QtWidgets/QFileDialog>
 +#include <QtWidgets/QGraphicsView>
  
  #include <QtDeclarative/qdeclarativecontext.h>
- #include <private/qdeclarativedebughelper_p.h>
  
  // ### This should be private API
  #include <qsgitem.h>
@@@ -1,7 -1,7 +1,7 @@@
  TEMPLATE = app
- CONFIG += qt uic
- DESTDIR = ../../bin
- QT += declarative qtquick1 qtquick1-private widgets widgets-private 
+ CONFIG += qt uic declarative_debug
+ DESTDIR = $$QT.declarative.bins
 -QT += declarative qtquick1 qtquick1-private
++QT += declarative qtquick1 qtquick1-private widgets widgets-private
  
  include(qml.pri)