Make QSGTextNode back-end for QML's TextInput and TextEdit
authorEskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@nokia.com>
Wed, 1 Jun 2011 07:45:55 +0000 (09:45 +0200)
committerQt by Nokia <qt-info@nokia.com>
Fri, 19 Aug 2011 13:15:56 +0000 (15:15 +0200)
Use the general QSGTextNode class as back-end for all text
elements in QML to make all text elements look the same and
use the same text rasterization back-end. This requires a
few rewrites in the text node to support e.g. selections.

Crashes seen with threaded renderer in TextEdit and TextInput on
Mac are also fixed by this.

Reviewed-by: Jiang Jiang
Task-number: QTBUG-18019, QTBUG-20017
Change-Id: I4207faf180c83422e5f8b726741321af395bd724
Reviewed-on: http://codereview.qt.nokia.com/2865
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@nokia.com>
16 files changed:
src/declarative/items/qsgtext.cpp
src/declarative/items/qsgtext_p_p.h
src/declarative/items/qsgtextedit.cpp
src/declarative/items/qsgtextedit_p.h
src/declarative/items/qsgtextedit_p_p.h
src/declarative/items/qsgtextinput.cpp
src/declarative/items/qsgtextinput_p.h
src/declarative/items/qsgtextinput_p_p.h
src/declarative/items/qsgtextnode.cpp
src/declarative/items/qsgtextnode_p.h
src/declarative/scenegraph/qsgadaptationlayer_p.h
src/declarative/scenegraph/qsgdistancefieldglyphnode.cpp
tests/auto/declarative/qsgtext/tst_qsgtext.cpp
tests/auto/declarative/qsgtextedit/tst_qsgtextedit.cpp
tests/auto/declarative/qsgtextinput/data/positionAt.qml
tests/auto/declarative/qsgtextinput/tst_qsgtextinput.cpp

index d323e3b..7e8cf2d 100644 (file)
@@ -107,7 +107,12 @@ QSGTextPrivate::QSGTextPrivate()
   richText(false), singleline(false), cacheAllTextAsImage(true), internalWidthUpdate(false),
   requireImplicitWidth(false), truncated(false), hAlignImplicit(true), rightToLeftText(false),
   layoutTextElided(false), richTextAsImage(false), textureImageCacheDirty(false), naturalWidth(0),
-  doc(0), layoutThread(0), nodeType(NodeIsNull)
+  doc(0), nodeType(NodeIsNull)
+
+#if defined(Q_OS_MAC)
+  , layoutThread(0)
+#endif
+
 {
     cacheAllTextAsImage = enableImageCache();
 }
@@ -291,7 +296,9 @@ void QSGTextPrivate::updateSize()
     int dy = q->height();
     QSize size(0, 0);
 
+#if defined(Q_OS_MAC)
     layoutThread = QThread::currentThread();
+#endif
 
     //setup instance of QTextLayout for all cases other than richtext
     if (!richText) {
@@ -313,6 +320,8 @@ void QSGTextPrivate::updateSize()
         QTextOption option;
         option.setAlignment((Qt::Alignment)int(horizontalAlignment | vAlign));
         option.setWrapMode(QTextOption::WrapMode(wrapMode));
+        if (!cacheAllTextAsImage && !richTextAsImage && !qmlDisableDistanceField())
+            option.setUseDesignMetrics(true);
         doc->setDefaultTextOption(option);
         if (requireImplicitWidth && q->widthValid()) {
             doc->setTextWidth(-1);
@@ -376,6 +385,8 @@ QRect QSGTextPrivate::setupTextLayout()
     QTextOption textOption = layout.textOption();
     textOption.setAlignment(Qt::Alignment(q->effectiveHAlign()));
     textOption.setWrapMode(QTextOption::WrapMode(wrapMode));
+    if (!cacheAllTextAsImage && !richTextAsImage && !qmlDisableDistanceField())
+        textOption.setUseDesignMetrics(true);
     layout.setTextOption(textOption);
 
     bool elideText = false;
@@ -907,8 +918,6 @@ void QSGText::setFont(const QFont &font)
     d->sourceFont = font;
     QFont oldFont = d->font;
     d->font = font;
-    if (!qmlDisableDistanceField())
-        d->font.setHintingPreference(QFont::PreferNoHinting);
 
     if (d->font.pointSizeF() != -1) {
         // 0.5pt resolution
@@ -1480,8 +1489,10 @@ QSGNode *QSGText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
     QRectF bounds = boundingRect();
 
     // We need to make sure the layout is done in the current thread
+#if defined(Q_OS_MAC)
     if (d->layoutThread != QThread::currentThread())
         d->updateLayout();
+#endif
 
     // XXX todo - some styled text can be done by the QSGTextNode
     if (d->richTextAsImage || d->cacheAllTextAsImage || (qmlDisableDistanceField() && d->style != Normal)) {
index 050e398..40c9861 100644 (file)
@@ -134,7 +134,6 @@ public:
     QPixmap textLayoutImage(bool drawStyle);
     void drawTextLayout(QPainter *p, const QPointF &pos, bool drawStyle);
     QTextLayout layout;
-    QThread *layoutThread;
 
     static QPixmap drawOutline(const QPixmap &source, const QPixmap &styleSource);
     static QPixmap drawOutline(const QPixmap &source, const QPixmap &styleSource, int yOffset);
@@ -146,9 +145,13 @@ public:
     enum NodeType {
         NodeIsNull,
         NodeIsTexture,
-        NodeIsText,
+        NodeIsText
     };
     NodeType nodeType;
+
+#if defined(Q_OS_MAC)
+    QThread *layoutThread;
+#endif
 };
 
 QT_END_NAMESPACE
index 8e76a8b..eeeaa20 100644 (file)
@@ -43,6 +43,8 @@
 #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 <private/qtextcontrol_p.h>
 #include <private/qtextengine_p.h>
 #include <private/qwidget_p.h>
+#include <private/qsgdistancefieldglyphcache_p.h>
+#include <private/qsgtexture_p.h>
+#include <private/qsgadaptationlayer_p.h>
 
 QT_BEGIN_NAMESPACE
 
+DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD)
+
 QWidgetPrivate *qt_widget_private(QWidget *widget);
 /*!
     \qmlclass TextEdit QSGTextEdit
@@ -111,7 +118,7 @@ TextEdit {
     \a link string provides access to the particular link.
 */
 QSGTextEdit::QSGTextEdit(QSGItem *parent)
-: QSGImplicitSizePaintedItem(*(new QSGTextEditPrivate), parent)
+: QSGImplicitSizeItem(*(new QSGTextEditPrivate), parent)
 {
     Q_D(QSGTextEdit);
     d->init();
@@ -255,6 +262,7 @@ void QSGTextEdit::setText(const QString &text)
 #else
         d->control->setPlainText(text);
 #endif
+        d->isComplexRichText = QSGTextNode::isComplexRichText(d->document);
     } else {
         d->control->setPlainText(text);
     }
@@ -324,6 +332,7 @@ void QSGTextEdit::setTextFormat(TextFormat format)
         d->control->setPlainText(d->text);
 #endif
         updateSize();
+        d->isComplexRichText = QSGTextNode::isComplexRichText(d->document);
     }
     d->format = format;
     d->control->setAcceptRichText(d->format != PlainText);
@@ -358,7 +367,7 @@ void QSGTextEdit::setFont(const QFont &font)
             moveCursorDelegate();
         }
         updateSize();
-        update();
+        updateDocument();
     }
     emit fontChanged(d->sourceFont);
 }
@@ -394,7 +403,7 @@ void QSGTextEdit::setColor(const QColor &color)
     QPalette pal = d->control->palette();
     pal.setColor(QPalette::Text, color);
     d->control->setPalette(pal);
-    update();
+    updateDocument();
     emit colorChanged(d->color);
 }
 
@@ -419,7 +428,7 @@ void QSGTextEdit::setSelectionColor(const QColor &color)
     QPalette pal = d->control->palette();
     pal.setColor(QPalette::Highlight, color);
     d->control->setPalette(pal);
-    update();
+    updateDocument();
     emit selectionColorChanged(d->selectionColor);
 }
 
@@ -444,7 +453,7 @@ void QSGTextEdit::setSelectedTextColor(const QColor &color)
     QPalette pal = d->control->palette();
     pal.setColor(QPalette::HighlightedText, color);
     d->control->setPalette(pal);
-    update();
+    updateDocument();
     emit selectedTextColorChanged(d->selectedTextColor);
 }
 
@@ -861,7 +870,7 @@ void QSGTextEdit::setCursorDelegate(QDeclarativeComponent* c)
     if(d->cursorComponent){
         if(d->cursor){
             d->control->setCursorWidth(-1);
-            update(cursorRectangle());
+            updateCursor();
             delete d->cursor;
             d->cursor = 0;
         }
@@ -886,7 +895,7 @@ void QSGTextEdit::loadCursorDelegate()
     d->cursor = qobject_cast<QSGItem*>(d->cursorComponent->create(qmlContext(this)));
     if(d->cursor){
         d->control->setCursorWidth(0);
-        update(cursorRectangle());
+        updateCursor();
         QDeclarative_setParent_noEvent(d->cursor, this);
         d->cursor->setParentItem(this);
         d->cursor->setHeight(QFontMetrics(d->font).height());
@@ -1016,7 +1025,7 @@ void QSGTextEdit::geometryChanged(const QRectF &newGeometry,
 {
     if (newGeometry.width() != oldGeometry.width())
         updateSize();
-    QSGPaintedItem::geometryChanged(newGeometry, oldGeometry);
+    QSGImplicitSizeItem::geometryChanged(newGeometry, oldGeometry);
 }
 
 /*!
@@ -1026,13 +1035,19 @@ void QSGTextEdit::geometryChanged(const QRectF &newGeometry,
 void QSGTextEdit::componentComplete()
 {
     Q_D(QSGTextEdit);
-    QSGPaintedItem::componentComplete();
+    QSGImplicitSizeItem::componentComplete();
+
+    if (d->richText) {
+        d->isComplexRichText = QSGTextNode::isComplexRichText(d->document);
+    }
+
     if (d->dirty) {
         d->determineHorizontalAlignment();
         d->updateDefaultTextOption();
         updateSize();
         d->dirty = false;
     }
+
 }
 /*!
     \qmlproperty bool QtQuick2::TextEdit::selectByMouse
@@ -1164,7 +1179,7 @@ bool QSGTextEdit::event(QEvent *event)
         d->control->processEvent(event, QPointF(0, -d->yoff));
         return event->isAccepted();
     }
-    return QSGPaintedItem::event(event);
+    return QSGImplicitSizeItem::event(event);
 }
 
 /*!
@@ -1176,7 +1191,7 @@ void QSGTextEdit::keyPressEvent(QKeyEvent *event)
     Q_D(QSGTextEdit);
     d->control->processEvent(event, QPointF(0, -d->yoff));
     if (!event->isAccepted())
-        QSGPaintedItem::keyPressEvent(event);
+        QSGImplicitSizeItem::keyPressEvent(event);
 }
 
 /*!
@@ -1188,7 +1203,7 @@ void QSGTextEdit::keyReleaseEvent(QKeyEvent *event)
     Q_D(QSGTextEdit);
     d->control->processEvent(event, QPointF(0, -d->yoff));
     if (!event->isAccepted())
-        QSGPaintedItem::keyReleaseEvent(event);
+        QSGImplicitSizeItem::keyReleaseEvent(event);
 }
 
 /*!
@@ -1332,7 +1347,7 @@ void QSGTextEdit::mousePressEvent(QGraphicsSceneMouseEvent *event)
     }
     d->control->processEvent(event, QPointF(0, -d->yoff));
     if (!event->isAccepted())
-        QSGPaintedItem::mousePressEvent(event);
+        QSGImplicitSizeItem::mousePressEvent(event);
 }
 
 /*!
@@ -1353,7 +1368,7 @@ void QSGTextEdit::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
     d->clickCausedFocus = false;
 
     if (!event->isAccepted())
-        QSGPaintedItem::mouseReleaseEvent(event);
+        QSGImplicitSizeItem::mouseReleaseEvent(event);
 }
 
 /*!
@@ -1365,7 +1380,7 @@ void QSGTextEdit::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
     Q_D(QSGTextEdit);
     d->control->processEvent(event, QPointF(0, -d->yoff));
     if (!event->isAccepted())
-        QSGPaintedItem::mouseDoubleClickEvent(event);
+        QSGImplicitSizeItem::mouseDoubleClickEvent(event);
 }
 
 /*!
@@ -1377,7 +1392,7 @@ void QSGTextEdit::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
     Q_D(QSGTextEdit);
     d->control->processEvent(event, QPointF(0, -d->yoff));
     if (!event->isAccepted())
-        QSGPaintedItem::mouseMoveEvent(event);
+        QSGImplicitSizeItem::mouseMoveEvent(event);
 }
 
 /*!
@@ -1412,41 +1427,122 @@ QVariant QSGTextEdit::inputMethodQuery(Qt::InputMethodQuery property) const
     return d->control->inputMethodQuery(property);
 }
 
-/*!
-Draws the contents of the text edit using the given \a painter within
-the given \a bounds.
-*/
-void QSGTextEdit::paint(QPainter *painter)
+void QSGTextEdit::updateImageCache(const QRectF &)
 {
-    // XXX todo
-    QRect bounds(0, 0, width(), height());
     Q_D(QSGTextEdit);
 
-    painter->setRenderHint(QPainter::TextAntialiasing, true);
-    painter->translate(0,d->yoff);
+    // Do we really need the image cache?
+    if (!d->richText || !d->isComplexRichText) {
+        if (!d->pixmapCache.isNull())
+            d->pixmapCache = QPixmap();
+        return;
+    }
+
+    if (width() != d->pixmapCache.width() || height() != d->pixmapCache.height())
+        d->pixmapCache = QPixmap(width(), height());
+
+    if (d->pixmapCache.isNull())
+        return;
+
+    // ### Use supplied rect, clear area and update only this part (for cursor updates)
+    QRectF bounds = QRectF(0, 0, width(), height());
+    d->pixmapCache.fill(Qt::transparent);
+    {
+        QPainter painter(&d->pixmapCache);
 
-    d->control->drawContents(painter, bounds.translated(0,-d->yoff));
+        painter.setRenderHint(QPainter::TextAntialiasing);
+        painter.translate(0, d->yoff);
+
+        d->control->drawContents(&painter, bounds);
+    }
 
-    painter->translate(0,-d->yoff);
 }
 
-void QSGTextEdit::updateImgCache(const QRectF &rf)
+QSGNode *QSGTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
 {
-    Q_D(const QSGTextEdit);
-    QRect r;
-    if (!rf.isValid()) {
-        r = QRect(0,0,INT_MAX,INT_MAX);
-    } else {
-        r = rf.toRect();
-        if (r.height() > INT_MAX/2) {
-            // Take care of overflow when translating "everything"
-            r.setTop(r.y() + d->yoff);
-            r.setBottom(INT_MAX/2);
+    Q_UNUSED(updatePaintNodeData);
+    Q_D(QSGTextEdit);
+
+    QSGNode *currentNode = oldNode;
+    if (d->richText && d->isComplexRichText) {
+        QSGImageNode *node = 0;
+        if (oldNode == 0 || d->nodeType != QSGTextEditPrivate::NodeIsTexture) {
+            delete oldNode;
+            node = QSGItemPrivate::get(this)->sceneGraphContext()->createImageNode();
+            d->texture = new QSGPlainTexture();
+            d->nodeType = QSGTextEditPrivate::NodeIsTexture;
+            currentNode = node;
         } else {
-            r = r.translated(0,d->yoff);
+            node = static_cast<QSGImageNode *>(oldNode);
         }
+
+        qobject_cast<QSGPlainTexture *>(d->texture)->setImage(d->pixmapCache.toImage());
+        node->setTexture(0);
+        node->setTexture(d->texture);
+
+        node->setTargetRect(QRectF(0, 0, d->pixmapCache.width(), d->pixmapCache.height()));
+        node->setSourceRect(QRectF(0, 0, 1, 1));
+        node->setHorizontalWrapMode(QSGTexture::ClampToEdge);
+        node->setVerticalWrapMode(QSGTexture::ClampToEdge);
+        node->setFiltering(QSGTexture::Linear); // Nonsmooth text just ugly, so don't do that..
+        node->update();
+
+    } else if (oldNode == 0 || d->documentDirty) {
+        d->documentDirty = false;
+
+#if defined(Q_WS_MAC)
+        // Make sure document is relayouted in the paint node on Mac
+        // to avoid crashes due to the font engines created in the
+        // shaping process
+        d->document->markContentsDirty(0, d->document->characterCount());
+#endif
+
+        QSGTextNode *node = 0;
+        if (oldNode == 0 || d->nodeType != QSGTextEditPrivate::NodeIsText) {
+            delete oldNode;
+            node = new QSGTextNode(QSGItemPrivate::get(this)->sceneGraphContext());
+            d->nodeType = QSGTextEditPrivate::NodeIsText;
+            currentNode = node;
+        } else {
+            node = static_cast<QSGTextNode *>(oldNode);
+        }
+
+        node->deleteContent();
+        node->setMatrix(QMatrix4x4());
+
+        QRectF bounds = boundingRect();
+
+        QColor selectionColor = d->control->palette().color(QPalette::Highlight);
+        QColor selectedTextColor = d->control->palette().color(QPalette::HighlightedText);
+        node->addTextDocument(bounds.topLeft(), d->document, d->color, QSGText::Normal, QColor(),
+                              selectionColor, selectedTextColor, selectionStart(),
+                              selectionEnd());
+
+#if defined(Q_WS_MAC)
+        // We also need to make sure the document layout is redone when
+        // control is returned to the main thread, as all the font engines
+        // are now owned by the rendering thread
+        d->document->markContentsDirty(0, d->document->characterCount());
+#endif
+    }
+
+    if (d->nodeType == QSGTextEditPrivate::NodeIsText && d->cursorComponent == 0 && !isReadOnly()) {
+        QSGTextNode *node = static_cast<QSGTextNode *>(currentNode);
+
+        QColor color = (!d->cursorVisible || !d->control->cursorOn())
+                ? QColor(0, 0, 0, 0)
+                : d->color;
+
+        if (node->cursorNode() == 0) {
+            node->setCursor(cursorRectangle(), color);
+        } else {
+            node->cursorNode()->setRect(cursorRectangle());
+            node->cursorNode()->setColor(color);
+        }
+
     }
-    update(r);
+
+    return currentNode;
 }
 
 /*!
@@ -1502,12 +1598,22 @@ void QSGTextEditPrivate::init()
     q->setSmooth(smooth);
     q->setAcceptedMouseButtons(Qt::LeftButton);
     q->setFlag(QSGItem::ItemAcceptsInputMethod);
+    q->setFlag(QSGItem::ItemHasContents);
 
     control = new QTextControl(q);
     control->setIgnoreUnusedNavigationEvents(true);
     control->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::TextSelectableByKeyboard | Qt::TextEditable);
     control->setDragEnabled(false);
 
+    // By default, QTextControl will issue both a updateCursorRequest() and an updateRequest()
+    // when the cursor needs to be repainted. We need the signals to be separate to be able to
+    // distinguish the cursor updates so that we can avoid updating the whole subtree when the
+    // cursor blinks.
+    if (!QObject::disconnect(control, SIGNAL(updateCursorRequest(QRectF)),
+                             control, SIGNAL(updateRequest(QRectF)))) {
+        qWarning("QSGTextEditPrivate::init: Failed to disconnect updateCursorRequest and updateRequest");
+    }
+
     // QTextControl follows the default text color
     // defined by the platform, declarative text
     // should be black by default
@@ -1517,8 +1623,8 @@ void QSGTextEditPrivate::init()
         control->setPalette(pal);
     }
 
-    QObject::connect(control, SIGNAL(updateRequest(QRectF)), q, SLOT(updateImgCache(QRectF)));
-
+    QObject::connect(control, SIGNAL(updateRequest(QRectF)), q, SLOT(updateDocument()));
+    QObject::connect(control, SIGNAL(updateCursorRequest()), q, SLOT(updateCursor()));
     QObject::connect(control, SIGNAL(textChanged()), q, SLOT(q_textChanged()));
     QObject::connect(control, SIGNAL(selectionChanged()), q, SIGNAL(selectionChanged()));
     QObject::connect(control, SIGNAL(selectionChanged()), q, SLOT(updateSelectionMarkers()));
@@ -1597,7 +1703,7 @@ void QSGTextEdit::updateSelectionMarkers()
 QRectF QSGTextEdit::boundingRect() const
 {
     Q_D(const QSGTextEdit);
-    QRectF r = QSGPaintedItem::boundingRect();
+    QRectF r = QSGImplicitSizeItem::boundingRect();
     int cursorWidth = 1;
     if(d->cursor)
         cursorWidth = d->cursor->width();
@@ -1679,12 +1785,31 @@ void QSGTextEdit::updateSize()
         setImplicitHeight(newHeight);
 
         d->paintedSize = QSize(newWidth, newHeight);
-        setContentsSize(d->paintedSize);
         emit paintedSizeChanged();
     } else {
         d->dirty = true;
     }
-    update();
+    updateDocument();
+}
+
+void QSGTextEdit::updateDocument()
+{
+    Q_D(QSGTextEdit);
+    d->documentDirty = true;
+
+    if (isComponentComplete()) {
+        updateImageCache();
+        update();
+    }
+}
+
+void QSGTextEdit::updateCursor()
+{
+    Q_D(QSGTextEdit);
+    if (isComponentComplete()) {
+        updateImageCache(d->control->cursorRect());
+        update();
+    }
 }
 
 void QSGTextEdit::updateTotalLines()
@@ -1725,8 +1850,15 @@ void QSGTextEditPrivate::updateDefaultTextOption()
     QTextOption::WrapMode oldWrapMode = opt.wrapMode();
     opt.setWrapMode(QTextOption::WrapMode(wrapMode));
 
-    if (oldWrapMode == opt.wrapMode() && oldAlignment == opt.alignment())
+    bool oldUseDesignMetrics = opt.useDesignMetrics();
+    bool useDesignMetrics = !qmlDisableDistanceField();
+    opt.setUseDesignMetrics(useDesignMetrics);
+
+    if (oldWrapMode == opt.wrapMode()
+            && oldAlignment == opt.alignment()
+            && oldUseDesignMetrics == useDesignMetrics) {
         return;
+    }
     document->setDefaultTextOption(opt);
 }
 
@@ -1838,7 +1970,7 @@ void QSGTextEdit::focusInEvent(QFocusEvent *event)
             openSoftwareInputPanel();
         }
     }
-    QSGPaintedItem::focusInEvent(event);
+    QSGImplicitSizeItem::focusInEvent(event);
 }
 
 void QSGTextEdit::q_canPasteChanged()
index 115b250..fa61f03 100644 (file)
@@ -54,7 +54,7 @@ QT_BEGIN_NAMESPACE
 QT_MODULE(Declarative)
 
 class QSGTextEditPrivate;
-class Q_AUTOTEST_EXPORT QSGTextEdit : public QSGImplicitSizePaintedItem
+class Q_AUTOTEST_EXPORT QSGTextEdit : public QSGImplicitSizeItem
 {
     Q_OBJECT
     Q_ENUMS(VAlignment)
@@ -260,16 +260,18 @@ public Q_SLOTS:
 #endif
 
 private Q_SLOTS:
-    void updateImgCache(const QRectF &rect);
     void q_textChanged();
     void updateSelectionMarkers();
     void moveCursorDelegate();
     void loadCursorDelegate();
     void q_canPasteChanged();
+    void updateDocument();
+    void updateCursor();
 
 private:
     void updateSize();
     void updateTotalLines();
+    void updateImageCache(const QRectF &rect = QRectF());
 
 protected:
     virtual void geometryChanged(const QRectF &newGeometry, 
@@ -288,7 +290,8 @@ protected:
     void inputMethodEvent(QInputMethodEvent *e);
     virtual void itemChange(ItemChange, const ItemChangeData &);
 
-    void paint(QPainter *);
+    QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData);
+
 private:
     Q_DISABLE_COPY(QSGTextEdit)
     Q_DECLARE_PRIVATE(QSGTextEdit)
index c080857..e4f74c0 100644 (file)
@@ -63,21 +63,21 @@ QT_BEGIN_NAMESPACE
 class QTextLayout;
 class QTextDocument;
 class QTextControl;
-class QSGTextEditPrivate : public QSGImplicitSizePaintedItemPrivate
+class QSGTextEditPrivate : public QSGImplicitSizeItemPrivate
 {
     Q_DECLARE_PUBLIC(QSGTextEdit)
 
 public:
     QSGTextEditPrivate()
       : color("black"), hAlign(QSGTextEdit::AlignLeft), vAlign(QSGTextEdit::AlignTop),
-      imgDirty(true), dirty(false), richText(false), cursorVisible(false), focusOnPress(true),
+      documentDirty(true), dirty(false), richText(false), cursorVisible(false), focusOnPress(true),
       showInputPanelOnFocus(true), clickCausedFocus(false), persistentSelection(true),
       requireImplicitWidth(false), selectByMouse(false), canPaste(false),
-      hAlignImplicit(true), rightToLeftText(false),
+      hAlignImplicit(true), rightToLeftText(false), isComplexRichText(false),
       textMargin(0.0), lastSelectionStart(0), lastSelectionEnd(0), cursorComponent(0), cursor(0),
       format(QSGTextEdit::AutoText), document(0), wrapMode(QSGTextEdit::NoWrap),
       mouseSelectionMode(QSGTextEdit::SelectCharacters),
-      yoff(0)
+      yoff(0), nodeType(NodeIsNull), texture(0)
     {
 #ifdef Q_OS_SYMBIAN
         if (QSysInfo::symbianVersion() == QSysInfo::SV_SF_1 || QSysInfo::symbianVersion() == QSysInfo::SV_SF_3) {
@@ -104,12 +104,10 @@ public:
     QColor  selectedTextColor;
     QString style;
     QColor  styleColor;
-    QPixmap imgCache;
-    QPixmap imgStyleCache;
     QSGTextEdit::HAlignment hAlign;
     QSGTextEdit::VAlignment vAlign;
 
-    bool imgDirty : 1;
+    bool documentDirty : 1;
     bool dirty : 1;
     bool richText : 1;
     bool cursorVisible : 1;
@@ -122,6 +120,7 @@ public:
     bool canPaste:1;
     bool hAlignImplicit:1;
     bool rightToLeftText:1;
+    bool isComplexRichText:1;
 
     qreal textMargin;
     int lastSelectionStart;
@@ -136,6 +135,15 @@ public:
     int lineCount;
     int yoff;
     QSize paintedSize;
+
+    enum NodeType {
+        NodeIsNull,
+        NodeIsTexture,
+        NodeIsText
+    };
+    NodeType nodeType;
+    QSGTexture *texture;
+    QPixmap pixmapCache;
 };
 
 QT_END_NAMESPACE
index 4b9aea1..222a900 100644 (file)
 
 #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 <QTextBoundaryFinder>
 #include <qstyle.h>
+#include <qsgtextnode_p.h>
+#include <qsgsimplerectnode.h>
 
 QT_BEGIN_NAMESPACE
 
+DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD)
+
 QWidgetPrivate *qt_widget_private(QWidget *widget);
 
 /*!
@@ -76,7 +81,7 @@ QWidgetPrivate *qt_widget_private(QWidget *widget);
     \sa TextEdit, Text, {declarative/text/textselection}{Text Selection example}
 */
 QSGTextInput::QSGTextInput(QSGItem* parent)
-: QSGImplicitSizePaintedItem(*(new QSGTextInputPrivate), parent)
+: QSGImplicitSizeItem(*(new QSGTextInputPrivate), parent)
 {
     Q_D(QSGTextInput);
     d->init();
@@ -1055,7 +1060,7 @@ void QSGTextInput::keyPressEvent(QKeyEvent* ev)
         d->control->processKeyEvent(ev);
     }
     if (!ev->isAccepted())
-        QSGPaintedItem::keyPressEvent(ev);
+        QSGImplicitSizeItem::keyPressEvent(ev);
 }
 
 void QSGTextInput::inputMethodEvent(QInputMethodEvent *ev)
@@ -1068,7 +1073,7 @@ void QSGTextInput::inputMethodEvent(QInputMethodEvent *ev)
         d->control->processInputMethodEvent(ev);
     }
     if (!ev->isAccepted())
-        QSGPaintedItem::inputMethodEvent(ev);
+        QSGImplicitSizeItem::inputMethodEvent(ev);
 
     if (wasComposing != (d->control->preeditAreaText().length() > 0))
         emit inputMethodComposingChanged();
@@ -1084,7 +1089,7 @@ void QSGTextInput::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
         d->control->selectWordAtPos(cursor);
         event->setAccepted(true);
     } else {
-        QSGPaintedItem::mouseDoubleClickEvent(event);
+        QSGImplicitSizeItem::mouseDoubleClickEvent(event);
     }
 }
 
@@ -1129,7 +1134,7 @@ void QSGTextInput::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
         moveCursorSelection(d->xToPos(event->pos().x()), d->mouseSelectionMode);
         event->setAccepted(true);
     } else {
-        QSGPaintedItem::mouseMoveEvent(event);
+        QSGImplicitSizeItem::mouseMoveEvent(event);
     }
 }
 
@@ -1152,7 +1157,7 @@ void QSGTextInput::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
     d->clickCausedFocus = false;
     d->control->processEvent(event);
     if (!event->isAccepted())
-        QSGPaintedItem::mouseReleaseEvent(event);
+        QSGImplicitSizeItem::mouseReleaseEvent(event);
 }
 
 bool QSGTextInputPrivate::sendMouseEventToInputContext(
@@ -1218,7 +1223,7 @@ bool QSGTextInput::event(QEvent* ev)
             handled = d->control->processEvent(ev);
     }
     if(!handled)
-        handled = QSGPaintedItem::event(ev);
+        handled = QSGImplicitSizeItem::event(ev);
     return handled;
 }
 
@@ -1230,7 +1235,7 @@ void QSGTextInput::geometryChanged(const QRectF &newGeometry,
         updateSize();
         updateCursorRectangle();
     }
-    QSGPaintedItem::geometryChanged(newGeometry, oldGeometry);
+    QSGImplicitSizeItem::geometryChanged(newGeometry, oldGeometry);
 }
 
 int QSGTextInputPrivate::calculateTextWidth()
@@ -1284,25 +1289,73 @@ void QSGTextInputPrivate::updateHorizontalScroll()
     }
 }
 
-void QSGTextInput::paint(QPainter *p)
+QSGNode *QSGTextInput::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
 {
-    // XXX todo
-    QRect r = boundingRect().toRect();
-
+    Q_UNUSED(data);
     Q_D(QSGTextInput);
-    p->setRenderHint(QPainter::TextAntialiasing, true);
-    p->save();
-    p->setPen(QPen(d->color));
-    int flags = QLineControl::DrawText;
-    if(!isReadOnly() && d->cursorVisible && !d->cursorItem)
-        flags |= QLineControl::DrawCursor;
-    if (d->control->hasSelectedText())
-            flags |= QLineControl::DrawSelections;
-    QFontMetrics fm = QFontMetrics(d->font);
-    // the y offset is there to keep the baseline constant in case we have script changes in the text.
-    QPoint offset(-d->hscroll, fm.ascent() - d->control->ascent());
-    d->control->draw(p, offset, r, flags);
-    p->restore();
+
+    QSGTextNode *node = static_cast<QSGTextNode *>(oldNode);
+    if (node == 0)
+        node = new QSGTextNode(QSGItemPrivate::get(this)->sceneGraphContext());
+    d->textNode = node;
+
+    if (!d->textLayoutDirty) {
+        QSGSimpleRectNode *cursorNode = node->cursorNode();
+        if (cursorNode != 0 && !isReadOnly()) {
+            QFontMetrics fm = QFontMetrics(d->font);
+            // the y offset is there to keep the baseline constant in case we have script changes in the text.
+            QPoint offset(-d->hscroll, fm.ascent() - d->control->ascent());
+            offset.rx() += d->control->cursorToX();
+
+            QRect br(boundingRect().toRect());
+            cursorNode->setRect(QRectF(offset, QSizeF(d->control->cursorWidth(), br.height())));
+
+            if (!d->cursorVisible
+                    || (!d->control->cursorBlinkStatus() && d->control->cursorBlinkPeriod() > 0)) {
+                d->hideCursor();
+            } else {
+                d->showCursor();
+            }
+        }
+    } else {
+        node->deleteContent();
+        node->setMatrix(QMatrix4x4());
+
+        QPoint offset = QPoint(0,0);
+        QFontMetrics fm = QFontMetrics(d->font);
+        QRect br(boundingRect().toRect());
+        if (d->autoScroll) {
+            // the y offset is there to keep the baseline constant in case we have script changes in the text.
+            offset = br.topLeft() - QPoint(d->hscroll, d->control->ascent() - fm.ascent());
+        } else {
+            offset = QPoint(d->hscroll, 0);
+        }
+
+        QTextLayout *textLayout = d->control->textLayout();
+        if (!textLayout->text().isEmpty()) {
+            node->addTextLayout(offset, textLayout, d->color,
+                                QSGText::Normal, QColor(),
+                                d->selectionColor, d->selectedTextColor,
+                                d->control->selectionStart(),
+                                d->control->selectionEnd() - 1); // selectionEnd() returns first char after
+                                                                 // selection
+        }
+
+        if (!isReadOnly() && d->cursorItem == 0) {
+            offset.rx() += d->control->cursorToX();
+            node->setCursor(QRectF(offset, QSizeF(d->control->cursorWidth(), br.height())), d->color);
+            if (!d->cursorVisible
+                    || (!d->control->cursorBlinkStatus() && d->control->cursorBlinkPeriod() > 0)) {
+                d->hideCursor();
+            } else {
+                d->showCursor();
+            }
+        }
+
+        d->textLayoutDirty = false;
+    }
+
+    return node;
 }
 
 QVariant QSGTextInput::inputMethodQuery(Qt::InputMethodQuery property) const
@@ -1754,7 +1807,7 @@ void QSGTextInput::focusInEvent(QFocusEvent *event)
             openSoftwareInputPanel();
         }
     }
-    QSGPaintedItem::focusInEvent(event);
+    QSGImplicitSizeItem::focusInEvent(event);
 }
 
 void QSGTextInput::itemChange(ItemChange change, const ItemChangeData &value)
@@ -1793,12 +1846,16 @@ bool QSGTextInput::isInputMethodComposing() const
 void QSGTextInputPrivate::init()
 {
     Q_Q(QSGTextInput);
+#if defined(Q_WS_MAC)
+    control->setThreadChecks(true);
+#endif
     control->setParent(q);//Now mandatory due to accessibility changes
     control->setCursorWidth(1);
     control->setPasswordCharacter(QLatin1Char('*'));
     q->setSmooth(smooth);
     q->setAcceptedMouseButtons(Qt::LeftButton);
     q->setFlag(QSGItem::ItemAcceptsInputMethod);
+    q->setFlag(QSGItem::ItemHasContents);
     q->connect(control, SIGNAL(cursorPositionChanged(int,int)),
                q, SLOT(cursorPosChanged()));
     q->connect(control, SIGNAL(selectionChanged()),
@@ -1828,6 +1885,12 @@ void QSGTextInputPrivate::init()
     selectedTextColor = p.color(QPalette::HighlightedText);
     selectionColor = p.color(QPalette::Highlight);
     determineHorizontalAlignment();
+
+    if (!qmlDisableDistanceField()) {
+        QTextOption option = control->textLayout()->textOption();
+        option.setUseDesignMetrics(true);
+        control->textLayout()->setTextOption(option);
+    }
 }
 
 void QSGTextInput::cursorPosChanged()
@@ -1898,19 +1961,35 @@ void QSGTextInput::q_textChanged()
     }
 }
 
+void QSGTextInputPrivate::showCursor()
+{
+    if (textNode != 0 && textNode->cursorNode() != 0)
+        textNode->cursorNode()->setColor(color);
+}
+
+void QSGTextInputPrivate::hideCursor()
+{
+    if (textNode != 0 && textNode->cursorNode() != 0)
+        textNode->cursorNode()->setColor(QColor(0, 0, 0, 0));
+}
+
 void QSGTextInput::updateRect(const QRect &r)
 {
     Q_D(QSGTextInput);
-    if(r == QRect())
-        update();
-    else
-        update(QRect(r.x() - d->hscroll, r.y(), r.width(), r.height()));
+    if (!isComponentComplete())
+        return;
+
+    if (r.isEmpty()) {
+        d->textLayoutDirty = true;
+    }
+
+    update();
 }
 
 QRectF QSGTextInput::boundingRect() const
 {
     Q_D(const QSGTextInput);
-    QRectF r = QSGPaintedItem::boundingRect();
+    QRectF r = QSGImplicitSizeItem::boundingRect();
 
     int cursorWidth = d->cursorItem ? d->cursorItem->width() : d->control->cursorWidth();
 
@@ -1927,7 +2006,6 @@ void QSGTextInput::updateSize(bool needsRedraw)
     int h = height();
     setImplicitHeight(d->control->height()-1); // -1 to counter QLineControl's +1 which is not consistent with Text.
     setImplicitWidth(d->calculateTextWidth());
-    setContentsSize(boundingRect().size().toSize());
     if(w==width() && h==height() && needsRedraw)
         update();
 }
index 770afa8..f5ac50e 100644 (file)
@@ -54,7 +54,7 @@ QT_MODULE(Declarative)
 
 class QSGTextInputPrivate;
 class QValidator;
-class Q_AUTOTEST_EXPORT QSGTextInput : public QSGImplicitSizePaintedItem
+class Q_AUTOTEST_EXPORT QSGTextInput : public QSGImplicitSizeItem
 {
     Q_OBJECT
     Q_ENUMS(HAlignment)
@@ -204,7 +204,6 @@ public:
 
     bool hasAcceptableInput() const;
 
-    void paint(QPainter *p);
     QVariant inputMethodQuery(Qt::InputMethodQuery property) const;
 
     QRectF boundingRect() const;
@@ -261,6 +260,7 @@ protected:
     bool event(QEvent *e);
     void focusInEvent(QFocusEvent *event);
     virtual void itemChange(ItemChange, const ItemChangeData &);
+    QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data);
 
 public Q_SLOTS:
     void selectAll();
index f257229..0d1f21b 100644 (file)
 
 QT_BEGIN_NAMESPACE
 
-class Q_AUTOTEST_EXPORT QSGTextInputPrivate : public QSGImplicitSizePaintedItemPrivate
+class QSGTextNode;
+
+class Q_AUTOTEST_EXPORT QSGTextInputPrivate : public QSGImplicitSizeItemPrivate
 {
     Q_DECLARE_PUBLIC(QSGTextInput)
 public:
-    QSGTextInputPrivate() : control(new QLineControl(QString())),
-                 color((QRgb)0), style(QSGText::Normal),
-                 styleColor((QRgb)0), hAlign(QSGTextInput::AlignLeft),
-                 mouseSelectionMode(QSGTextInput::SelectCharacters), inputMethodHints(Qt::ImhNone),
-                 hscroll(0), oldScroll(0), oldValidity(false), focused(false), focusOnPress(true),
-                 showInputPanelOnFocus(true), clickCausedFocus(false), cursorVisible(false),
-                 autoScroll(true), selectByMouse(false), canPaste(false), hAlignImplicit(true),
-                 selectPressed(false)
+    QSGTextInputPrivate()
+                 : control(new QLineControl(QString()))
+                 , color((QRgb)0)
+                 , style(QSGText::Normal)
+                 , styleColor((QRgb)0)
+                 , hAlign(QSGTextInput::AlignLeft)
+                 , mouseSelectionMode(QSGTextInput::SelectCharacters)
+                 , inputMethodHints(Qt::ImhNone)
+                 , textNode(0)
+                 , hscroll(0)
+                 , oldScroll(0)
+                 , oldValidity(false)
+                 , focused(false)
+                 , focusOnPress(true)
+                 , showInputPanelOnFocus(true)
+                 , clickCausedFocus(false)
+                 , cursorVisible(false)
+                 , autoScroll(true)
+                 , selectByMouse(false)
+                 , canPaste(false)
+                 , hAlignImplicit(true)
+                 , selectPressed(false)
+                 , textLayoutDirty(true)
     {
 #ifdef Q_OS_SYMBIAN
         if (QSysInfo::symbianVersion() == QSysInfo::SV_SF_1 || QSysInfo::symbianVersion() == QSysInfo::SV_SF_3) {
@@ -106,6 +123,8 @@ public:
     int calculateTextWidth();
     bool sendMouseEventToInputContext(QGraphicsSceneMouseEvent *event, QEvent::Type eventType);
     void updateInputMethodHints();
+    void hideCursor();
+    void showCursor();
 
     QLineControl* control;
 
@@ -122,6 +141,7 @@ public:
     QPointer<QDeclarativeComponent> cursorComponent;
     QPointer<QSGItem> cursorItem;
     QPointF pressPos;
+    QSGTextNode *textNode;
 
     int lastSelectionStart;
     int lastSelectionEnd;
@@ -141,6 +161,7 @@ public:
     bool canPaste:1;
     bool hAlignImplicit:1;
     bool selectPressed:1;
+    bool textLayoutDirty:1;
 
     static inline QSGTextInputPrivate *get(QSGTextInput *t) {
         return t->d_func();
index c09a53c..ef0f98b 100644 (file)
@@ -47,6 +47,7 @@
 
 #include <private/qsgcontext_p.h>
 
+#include <QtCore/qpoint.h>
 #include <qmath.h>
 #include <qtextdocument.h>
 #include <qtextlayout.h>
@@ -57,6 +58,7 @@
 #include <private/qfont_p.h>
 #include <private/qfontengine_p.h>
 #include <private/qrawfont_p.h>
+#include <qhash.h>
 
 QT_BEGIN_NAMESPACE
 
@@ -64,7 +66,7 @@ QT_BEGIN_NAMESPACE
   Creates an empty QSGTextNode
 */
 QSGTextNode::QSGTextNode(QSGContext *context)
-: m_context(context)
+    : m_context(context), m_cursorNode(0)
 {
 #if defined(QML_RUNTIME_TESTING)
     description = QLatin1String("text");
@@ -119,117 +121,673 @@ void QSGTextNode::setStyleColor(const QColor &styleColor)
 }
 #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 (parentNode == 0)
+        parentNode = this;
+    parentNode->appendChildNode(node);
+
+    return node;
+}
+
+void QSGTextNode::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(QSGTextNode::NoDecoration)
+            , leftChildIndex(-1)
+            , rightChildIndex(-1)
+        {
+
+        }
+
+        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);
+        }
+    }
+
+    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);
 
-    if (decorations & Underline) {
-        int underlinePosition = qCeil(underlinePos);
-        QRectF underline(line);
-        underline.translate(0.0, underlinePosition);
-        appendChildNode(new QSGSimpleRectNode(underline, color));
+                        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);
+                }
+            }
+
+            // 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;
     }
 
-    if (decorations & Overline) {
-        QRectF overline(line);
-        overline.translate(0.0, -ascent);
-        appendChildNode(new QSGSimpleRectNode(overline, color));
+    void SelectionEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun)
+    {
+        BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Unselected,
+                               m_textColor, m_position);
     }
 
-    if (decorations & StrikeOut) {
-        QRectF strikeOut(line);
-        strikeOut.translate(0.0, ascent / -3.0);
-        appendChildNode(new QSGSimpleRectNode(strikeOut, color));
+    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;
     }
-}
 
-QSGGlyphNode *QSGTextNode::addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color,
-                                           QSGText::TextStyle style, const QColor &styleColor, QSGGlyphNode *prevNode)
-{
-    QSGGlyphNode *node = prevNode;
+    void SelectionEngine::addToSceneGraph(QSGTextNode *parentNode,
+                                          QSGText::TextStyle style,
+                                          const QColor &styleColor)
+    {
+        if (m_currentLine.isValid())
+            processCurrentLine();
 
-    if (!node)
-        node = m_context->createGlyphNode();
+        // First, prepend all selection rectangles to the tree
+        for (int i=0; i<m_selectionRects.size(); ++i) {
+            const QRectF &rect = m_selectionRects.at(i);
 
-    node->setGlyphs(position, glyphs);
+            parentNode->appendChildNode(new QSGSimpleRectNode(rect, m_selectionColor));
+        }
 
-    if (node != prevNode) {
-        node->setStyle(style);
-        node->setStyleColor(styleColor);
-        node->setColor(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<QSGClipNode *, QPair<QRgb, int> > > KeyType;
+        QHash<KeyType, BinaryTreeNode *> map;
+        for (int i=0; i<m_processedNodes.size(); ++i) {
+            BinaryTreeNode *node = m_processedNodes.data() + i;
 
-    node->update();
+            QGlyphRun glyphRun = node->glyphRun;
+            QRawFont rawFont = glyphRun.rawFont();
+            QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
 
-    if (node != prevNode)
-        appendChildNode(node);
+            QFontEngine *fontEngine = rawFontD->fontEngine;
 
-    return node;
+            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()) {
-        addTextBlock(p, textDocument, it.currentBlock(), color, style, styleColor);
+        Q_ASSERT(!engine.currentLine().isValid());
+
+        QTextBlock block = it.currentBlock();
+
+        QTextBlock::iterator blockIterator = block.begin();
+        while (!blockIterator.atEnd()) {
+            QTextFragment fragment = blockIterator.fragment();
+            if (fragment.text().isEmpty())
+                continue;
+
+            QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft();
+            engine.setPosition(position + blockPosition);
+
+            QTextCharFormat charFormat = fragment.charFormat();
+            if (!textColor.isValid())
+                engine.setTextColor(charFormat.foreground().color());
+
+            int fragmentEnd = fragment.position() + fragment.length();
+            int textPos = fragment.position();
+            while (textPos < fragmentEnd) {
+                int blockRelativePosition = textPos - block.position();
+                QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
+                Q_ASSERT(line.textLength() > 0);
+                if (!engine.currentLine().isValid() || line.lineNumber() != engine.currentLine().lineNumber())
+                    engine.setCurrentLine(line);
+
+                int lineEnd = line.textStart() + block.position() + line.textLength();
+
+                int len = qMin(lineEnd - textPos, fragmentEnd - textPos);
+                Q_ASSERT(len > 0);
+
+                int currentStepEnd = textPos + len;
+
+                if (!hasSelection || selectionStart > currentStepEnd || selectionEnd < textPos) {
+                    QList<QGlyphRun> glyphRuns = fragment.glyphRuns(blockRelativePosition, len);
+                    for (int j=0; j<glyphRuns.size(); ++j)
+                        engine.addUnselectedGlyphs(glyphRuns.at(j));
+                } else {
+                    if (textPos < selectionStart) {
+                        int unselectedPartLength = qMin(selectionStart - textPos, len);
+                        QList<QGlyphRun> glyphRuns = fragment.glyphRuns(blockRelativePosition,
+                                                                        unselectedPartLength);
+                        for (int j=0; j<glyphRuns.size(); ++j)
+                            engine.addUnselectedGlyphs(glyphRuns.at(j));
+                    }
+
+                    if (selectionStart < currentStepEnd && selectionEnd > textPos) {
+                        int currentSelectionStart = qMax(selectionStart, textPos);
+                        int currentSelectionLength = qMin(selectionEnd - currentSelectionStart,
+                                                          currentStepEnd - currentSelectionStart);
+                        int selectionRelativeBlockPosition = currentSelectionStart - block.position();
+
+                        QList<QGlyphRun> glyphRuns = fragment.glyphRuns(selectionRelativeBlockPosition,
+                                                                        currentSelectionLength);
+                        for (int j=0; j<glyphRuns.size(); ++j)
+                            engine.addSelectedGlyphs(glyphRuns.at(j));
+                    }
+
+                    if (selectionEnd >= textPos && selectionEnd < currentStepEnd) {
+                        QList<QGlyphRun> glyphRuns = fragment.glyphRuns(selectionEnd - block.position(),
+                                                                        currentStepEnd - selectionEnd);
+                        for (int j=0; j<glyphRuns.size(); ++j)
+                            engine.addUnselectedGlyphs(glyphRuns.at(j));
+                    }
+                }
+
+                textPos = currentStepEnd;
+            }
+
+            ++blockIterator;
+        }
+
+        engine.setCurrentLine(QTextLine()); // Reset current line because the text layout changed
         ++it;
     }
+
+    engine.addToSceneGraph(this, style, styleColor);
 }
 
 void QSGTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color,
-                                QSGText::TextStyle style, const QColor &styleColor)
+                                QSGText::TextStyle style, const QColor &styleColor,
+                                const QColor &selectionColor, const QColor &selectedTextColor,
+                                int selectionStart, int selectionEnd)
 {
-    QList<QGlyphRun> glyphsList(textLayout->glyphRuns());
-
-    QSGGlyphNode *prevNode = 0;
-
-    QFont font = textLayout->font();
-    qreal underlinePosition, ascent, lineThickness;
-    int decorations = NoDecoration;
-    decorations |= (font.underline() ? Underline : 0);
-    decorations |= (font.overline()  ? Overline  : 0);
-    decorations |= (font.strikeOut() ? StrikeOut : 0);
-
-    underlinePosition = ascent = lineThickness = 0;
-    for (int i=0; i<glyphsList.size(); ++i) {
-        QGlyphRun glyphs = glyphsList.at(i);
-        QRawFont rawfont = glyphs.rawFont();
-        prevNode = addGlyphs(position + QPointF(0, rawfont.ascent()), glyphs, color, style, styleColor);
-
-        if (decorations) {
-            qreal rawAscent = rawfont.ascent();
-            if (decorations & Underline) {
-                ascent = qMax(ascent, rawAscent);
-                qreal pos = rawfont.underlinePosition();
-                if (pos > underlinePosition) {
-                    underlinePosition = pos;
-                    // take line thickness from the rawfont with maximum underline
-                    // position in this case
-                    lineThickness = rawfont.lineThickness();
-                }
-            } else {
-                // otherwise it's strike out or overline, we take line thickness
-                // from the rawfont with maximum ascent
-                if (rawAscent > ascent) {
-                    ascent = rawAscent;
-                    lineThickness = rawfont.lineThickness();
-                }
+    SelectionEngine engine;
+    engine.setTextColor(color);
+    engine.setSelectedTextColor(selectedTextColor);
+    engine.setSelectionColor(selectionColor);
+    engine.setPosition(position);
+
+    for (int i=0; i<textLayout->lineCount(); ++i) {
+        QTextLine line = textLayout->lineAt(i);
+
+        engine.setCurrentLine(line);
+
+        int lineEnd = line.textStart() + line.textLength();
+        if (selectionStart > lineEnd || selectionEnd < line.textStart()) {
+            QList<QGlyphRun> glyphRuns = line.glyphRuns();
+            for (int j=0; j<glyphRuns.size(); ++j)
+                engine.addUnselectedGlyphs(glyphRuns.at(j));
+        } else {
+            if (line.textStart() < selectionStart) {
+                QList<QGlyphRun> glyphRuns = line.glyphRuns(line.textStart(),
+                                                            qMin(selectionStart - line.textStart(),
+                                                                 line.textLength()));
+
+                for (int j=0; j<glyphRuns.size(); ++j)
+                    engine.addUnselectedGlyphs(glyphRuns.at(j));
+            }
+
+            if (lineEnd >= selectionStart && selectionStart >= line.textStart()) {
+                QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionStart, selectionEnd - selectionStart + 1);
+
+                for (int j=0; j<glyphRuns.size(); ++j)
+                    engine.addSelectedGlyphs(glyphRuns.at(j));
+            }
+
+            if (selectionEnd >= line.textStart() && selectionEnd < lineEnd) {
+                QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionEnd + 1, lineEnd - selectionEnd);
+                for (int j=0; j<glyphRuns.size(); ++j)
+                    engine.addUnselectedGlyphs(glyphRuns.at(j));
             }
         }
     }
 
-    if (decorations) {
-        addTextDecorations(Decoration(decorations), position + QPointF(0, ascent), color,
-                           textLayout->boundingRect().width(),
-                           lineThickness, underlinePosition, ascent);
-    }
+    engine.addToSceneGraph(this, style, styleColor);
 }
 
 
@@ -369,49 +927,11 @@ bool QSGTextNode::isComplexRichText(QTextDocument *doc)
     return reader.hasError();
 }
 
-void QSGTextNode::addTextBlock(const QPointF &position, QTextDocument *textDocument, const QTextBlock &block,
-                               const QColor &overrideColor, QSGText::TextStyle style, const QColor &styleColor)
-{
-    if (!block.isValid())
-        return;
-
-    QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft();
-
-    QTextBlock::iterator it = block.begin();
-    while (!it.atEnd()) {
-        QTextFragment fragment = it.fragment();
-        if (!fragment.text().isEmpty()) {
-            QTextCharFormat charFormat = fragment.charFormat();
-            QColor color = overrideColor.isValid()
-                    ? overrideColor
-                    : charFormat.foreground().color();
-
-            QList<QGlyphRun> glyphsList = fragment.glyphRuns();
-            for (int i=0; i<glyphsList.size(); ++i) {
-                QGlyphRun glyphs = glyphsList.at(i);
-                QRawFont font = glyphs.rawFont();
-                QSGGlyphNode *glyphNode = addGlyphs(position + blockPosition + QPointF(0, font.ascent()),
-                                                    glyphs, color, style, styleColor);
-                int decorations = (glyphs.overline() ? Overline : 0) |
-                                  (glyphs.strikeOut() ? StrikeOut : 0) |
-                                  (glyphs.underline() ? Underline : 0);
-                if (decorations) {
-                    QPointF baseLine = glyphNode->baseLine();
-                    qreal width = glyphNode->boundingRect().width();
-                    addTextDecorations(Decoration(decorations), baseLine, color, width,
-                                       font.lineThickness(), font.underlinePosition(), font.ascent());
-                }
-            }
-        }
-
-        ++it;
-    }
-}
-
 void QSGTextNode::deleteContent()
 {
-    while (firstChild() > 0)
+    while (firstChild() != 0)
         delete firstChild();
+    m_cursorNode = 0;
 }
 
 #if 0
index 7ff3199..4c5199a 100644 (file)
@@ -54,10 +54,20 @@ class QColor;
 class QTextDocument;
 class QSGContext;
 class QRawFont;
+class QSGSimpleRectNode;
+class QSGClipNode;
 
 class QSGTextNode : public QSGTransformNode
 {
 public:
+    enum Decoration {
+        NoDecoration = 0x0,
+        Underline    = 0x1,
+        Overline     = 0x2,
+        StrikeOut    = 0x4
+    };
+    Q_DECLARE_FLAGS(Decorations, Decoration)
+
     QSGTextNode(QSGContext *);
     ~QSGTextNode();
 
@@ -65,26 +75,24 @@ public:
 
     void deleteContent();
     void addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color = QColor(),
-                       QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor());
+                       QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor(),
+                       const QColor &selectionColor = QColor(), const QColor &selectedTextColor = QColor(),
+                       int selectionStart = -1, int selectionEnd = -1);
     void addTextDocument(const QPointF &position, QTextDocument *textDocument, const QColor &color = QColor(),
-                         QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor());
+                         QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor(),
+                         const QColor &selectionColor = QColor(), const QColor &selectedTextColor = QColor(),
+                         int selectionStart = -1, int selectionEnd = -1);
 
-private:
-    enum Decoration {
-        NoDecoration = 0x0,
-        Underline    = 0x1,
-        Overline     = 0x2,
-        StrikeOut    = 0x4
-    };
+    void setCursor(const QRectF &rect, const QColor &color);
+    QSGSimpleRectNode *cursorNode() const { return m_cursorNode; }
 
-    void addTextBlock(const QPointF &position, QTextDocument *textDocument, const QTextBlock &block,
-                      const QColor &overrideColor, QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor());
     QSGGlyphNode *addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color,
-                                  QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor(),
-                            QSGGlyphNode *node = 0);
-    void addTextDecorations(Decoration decorations, const QPointF &position, const QColor &color,
-                            qreal width, qreal lineThickness, qreal underlinePosition, qreal ascent);
+                            QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor(),
+                            QSGNode *parentNode = 0);
+
+private:
     QSGContext *m_context;
+    QSGSimpleRectNode *m_cursorNode;
 };
 
 QT_END_NAMESPACE
index 820650e..f2c0cbe 100644 (file)
@@ -45,7 +45,6 @@
 #include "qsgnode.h"
 #include "qsgtexture.h"
 #include <private/qsgtext_p.h>
-
 #include <QtCore/qobject.h>
 #include <QtCore/qrect.h>
 #include <QtGui/qcolor.h>
@@ -53,6 +52,8 @@
 #include <QtGui/qglyphrun.h>
 #include <QtCore/qurl.h>
 
+#include <private/qsgtext_p.h>
+
 QT_BEGIN_HEADER
 
 QT_BEGIN_NAMESPACE
index beeda1e..cb3dab9 100644 (file)
@@ -167,6 +167,7 @@ void QSGDistanceFieldGlyphNode::updateGeometry()
     QPointF margins(2, 2);
     QPointF texMargins = margins / m_glyph_cache->fontScale();
 
+    QVector<QPointF> glyphPositions = m_glyphs.positions();
     for (int i = 0; i < glyphIndexes.size(); ++i) {
         quint32 glyphIndex = glyphIndexes.at(i);
         QSGDistanceFieldGlyphCache::Metrics metrics = m_glyph_cache->glyphMetrics(glyphIndex);
@@ -183,9 +184,9 @@ void QSGDistanceFieldGlyphNode::updateGeometry()
                 c.height += texMargins.y() * 2;
         }
 
-        QPointF glyphPosition = m_glyphs.positions().at(i) + m_position;
-        qreal x = glyphPosition.x() + metrics.baselineX;
-        qreal y = glyphPosition.y() - metrics.baselineY;
+        const QPointF &glyphPosition = glyphPositions.at(i);
+        qreal x = glyphPosition.x() + metrics.baselineX + m_position.x();
+        qreal y = glyphPosition.y() - metrics.baselineY + m_position.y();
 
         boundingRect |= QRectF(x, y, metrics.width, metrics.height);
 
index 48d679b..747ea51 100644 (file)
@@ -45,6 +45,7 @@
 #include <private/qsgtext_p.h>
 #include <private/qsgtext_p_p.h>
 #include <private/qdeclarativevaluetype_p.h>
+#include <private/qsgdistancefieldglyphcache_p.h>
 #include <QFontMetrics>
 #include <QGraphicsSceneMouseEvent>
 #include <qmath.h>
@@ -61,6 +62,8 @@
 #define SRCDIR "."
 #endif
 
+DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD)
+
 class tst_qsgtext : public QObject
 
 {
@@ -261,14 +264,42 @@ void tst_qsgtext::width()
         delete textObject;
     }
 
+    bool requiresUnhintedMetrics = !qmlDisableDistanceField();
+
     for (int i = 0; i < standard.size(); i++)
     {
         QVERIFY(!Qt::mightBeRichText(standard.at(i))); // self-test
 
         QFont f;
-        QFontMetricsF fm(f);
-        qreal metricWidth = fm.size(Qt::TextExpandTabs && Qt::TextShowMnemonic, standard.at(i)).width();
-        metricWidth = qCeil(metricWidth);
+        qreal metricWidth = 0.0;
+
+        if (requiresUnhintedMetrics) {
+            QString s = standard.at(i);
+            s.replace(QLatin1Char('\n'), QChar::LineSeparator);
+
+            QTextLayout layout(s);
+            layout.setFlags(Qt::TextExpandTabs | Qt::TextShowMnemonic);
+            {
+                QTextOption option;
+                option.setUseDesignMetrics(true);
+                layout.setTextOption(option);
+            }
+
+            layout.beginLayout();
+            forever {
+                QTextLine line = layout.createLine();
+                if (!line.isValid())
+                    break;
+            }
+
+            layout.endLayout();
+
+            metricWidth = qCeil(layout.boundingRect().width());
+        } else {
+            QFontMetricsF fm(f);
+            qreal metricWidth = fm.size(Qt::TextExpandTabs && Qt::TextShowMnemonic, standard.at(i)).width();
+            metricWidth = qCeil(metricWidth);
+        }
 
         QString componentStr = "import QtQuick 2.0\nText { text: \"" + standard.at(i) + "\" }";
         QDeclarativeComponent textComponent(&engine);
index 3492ac2..2a94e20 100644 (file)
@@ -51,6 +51,7 @@
 #include <QtDeclarative/qdeclarativecomponent.h>
 #include <private/qsgtextedit_p.h>
 #include <private/qsgtextedit_p_p.h>
+#include <private/qsgdistancefieldglyphcache_p.h>
 #include <QFontMetrics>
 #include <QSGView>
 #include <QDir>
@@ -72,6 +73,7 @@
 #endif
 
 Q_DECLARE_METATYPE(QSGTextEdit::SelectionMode)
+DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD)
 
 QString createExpectedFileIfNotFound(const QString& filebasename, const QImage& actual)
 {
@@ -280,12 +282,40 @@ void tst_qsgtextedit::width()
         QCOMPARE(textEditObject->width(), 0.0);
     }
 
+    bool requiresUnhintedMetrics = !qmlDisableDistanceField();
+
     for (int i = 0; i < standard.size(); i++)
     {
         QFont f;
-        QFontMetricsF fm(f);
-        qreal metricWidth = fm.size(Qt::TextExpandTabs && Qt::TextShowMnemonic, standard.at(i)).width();
-        metricWidth = ceil(metricWidth);
+        qreal metricWidth = 0.0;
+
+        if (requiresUnhintedMetrics) {
+            QString s = standard.at(i);
+            s.replace(QLatin1Char('\n'), QChar::LineSeparator);
+
+            QTextLayout layout(s);
+            layout.setFlags(Qt::TextExpandTabs | Qt::TextShowMnemonic);
+            {
+                QTextOption option;
+                option.setUseDesignMetrics(true);
+                layout.setTextOption(option);
+            }
+
+            layout.beginLayout();
+            forever {
+                QTextLine line = layout.createLine();
+                if (!line.isValid())
+                    break;
+            }
+
+            layout.endLayout();
+
+            metricWidth = ceil(layout.boundingRect().width());
+        } else {
+            QFontMetricsF fm(f);
+            metricWidth = fm.size(Qt::TextExpandTabs | Qt::TextShowMnemonic, standard.at(i)).width();
+            metricWidth = ceil(metricWidth);
+        }
 
         QString componentStr = "import QtQuick 2.0\nTextEdit { text: \"" + standard.at(i) + "\" }";
         QDeclarativeComponent texteditComponent(&engine);
@@ -301,6 +331,8 @@ void tst_qsgtextedit::width()
         QTextDocument document;
         document.setHtml(richText.at(i));
         document.setDocumentMargin(0);
+        if (requiresUnhintedMetrics)
+            document.setUseDesignMetrics(true);
 
         int documentWidth = ceil(document.idealWidth());
 
@@ -455,7 +487,6 @@ void tst_qsgtextedit::hAlign()
 
 void tst_qsgtextedit::hAlign_RightToLeft()
 {
-    QSKIP("QTBUG-20017", SkipAll);
     QSGView canvas(QUrl::fromLocalFile(SRCDIR "/data/horizontalAlignment_RightToLeft.qml"));
     QSGTextEdit *textEdit = canvas.rootObject()->findChild<QSGTextEdit*>("text");
     QVERIFY(textEdit != 0);
@@ -1471,7 +1502,28 @@ void tst_qsgtextedit::positionAt()
     const int y1 = fm.height() * 3 / 2;
 
     int pos = texteditObject->positionAt(texteditObject->width()/2, y0);
-    int diff = abs(int(fm.width(texteditObject->text().left(pos))-texteditObject->width()/2));
+    int width = 0;
+    if (!qmlDisableDistanceField()) {
+        QTextLayout layout(texteditObject->text().left(pos));
+
+        {
+            QTextOption option;
+            option.setUseDesignMetrics(true);
+            layout.setTextOption(option);
+        }
+
+        layout.beginLayout();
+        QTextLine line = layout.createLine();
+        layout.endLayout();
+
+        width = ceil(line.horizontalAdvance());
+
+    } else {
+        width = fm.width(texteditObject->text().left(pos));
+    }
+
+
+    int diff = abs(int(width-texteditObject->width()/2));
 
     // some tollerance for different fonts.
 #ifdef Q_OS_LINUX
index 7611376..1840462 100644 (file)
@@ -4,5 +4,5 @@ TextInput{
     focus: true
     objectName: "myInput"
     width: 50
-    text: "This is a long piece of text"
+    text: "AAAAAAAAAAAAAAAAAAAAAAAAAAAA"
 }
index 93e8ad2..744717c 100644 (file)
@@ -51,7 +51,9 @@
 #include <QStyle>
 #include <QInputContext>
 #include <private/qapplication_p.h>
+#include <private/qsgdistancefieldglyphcache_p.h>
 #include <QtOpenGL/QGLShaderProgram>
+#include <math.h>
 
 #ifdef Q_OS_SYMBIAN
 // In Symbian OS test data is located in applications private dir
@@ -59,6 +61,7 @@
 #endif
 
 Q_DECLARE_METATYPE(QSGTextInput::SelectionMode)
+DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD)
 
 QString createExpectedFileIfNotFound(const QString& filebasename, const QImage& actual)
 {
@@ -221,11 +224,38 @@ void tst_qsgtextinput::width()
         delete textinputObject;
     }
 
+    bool requiresUnhintedMetrics = !qmlDisableDistanceField();
+
     for (int i = 0; i < standard.size(); i++)
     {
         QFont f;
-        QFontMetricsF fm(f);
-        qreal metricWidth = fm.width(standard.at(i));
+        qreal metricWidth = 0.0;
+        if (requiresUnhintedMetrics) {
+            QString s = standard.at(i);
+            s.replace(QLatin1Char('\n'), QChar::LineSeparator);
+
+            QTextLayout layout(s);
+            layout.setFlags(Qt::TextExpandTabs | Qt::TextShowMnemonic);
+            {
+                QTextOption option;
+                option.setUseDesignMetrics(true);
+                layout.setTextOption(option);
+            }
+
+            layout.beginLayout();
+            forever {
+                QTextLine line = layout.createLine();
+                if (!line.isValid())
+                    break;
+            }
+
+            layout.endLayout();
+
+            metricWidth = ceil(layout.boundingRect().width());
+        } else {
+            QFontMetricsF fm(f);
+            metricWidth = fm.width(standard.at(i));
+        }
 
         QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + standard.at(i) + "\" }";
         QDeclarativeComponent textinputComponent(&engine);
@@ -1048,7 +1078,6 @@ void tst_qsgtextinput::horizontalAlignment()
 
 void tst_qsgtextinput::horizontalAlignment_RightToLeft()
 {
-    QSKIP("QTBUG-20017", SkipAll);
     QSGView canvas(QUrl::fromLocalFile(SRCDIR "/data/horizontalAlignment_RightToLeft.qml"));
     QSGTextInput *textInput = canvas.rootObject()->findChild<QSGTextInput*>("text");
     QVERIFY(textInput != 0);
@@ -1161,7 +1190,45 @@ void tst_qsgtextinput::positionAt()
     QFontMetrics fm(textinputObject->font());
 
     int pos = textinputObject->positionAt(textinputObject->width()/2);
-    int diff = abs(int(fm.width(textinputObject->text()) - (fm.width(textinputObject->text().left(pos))+textinputObject->width()/2)));
+    int textWidth = 0;
+    int textLeftWidth = 0;
+    if (!qmlDisableDistanceField()) {
+        {
+            QTextLayout layout(textinputObject->text().left(pos));
+
+            {
+                QTextOption option;
+                option.setUseDesignMetrics(true);
+                layout.setTextOption(option);
+            }
+
+            layout.beginLayout();
+            QTextLine line = layout.createLine();
+            layout.endLayout();
+
+            textLeftWidth = ceil(line.horizontalAdvance());
+        }
+        {
+            QTextLayout layout(textinputObject->text());
+
+            {
+                QTextOption option;
+                option.setUseDesignMetrics(true);
+                layout.setTextOption(option);
+            }
+
+            layout.beginLayout();
+            QTextLine line = layout.createLine();
+            layout.endLayout();
+
+            textWidth = ceil(line.horizontalAdvance());
+        }
+    } else {
+        textWidth = fm.width(textinputObject->text());
+        textLeftWidth = fm.width(textinputObject->text().left(pos));
+    }
+
+    int diff = abs(textWidth - (textLeftWidth+textinputObject->width()/2));
 
     // some tollerance for different fonts.
 #ifdef Q_OS_LINUX
@@ -1177,7 +1244,28 @@ void tst_qsgtextinput::positionAt()
     // Check without autoscroll...
     textinputObject->setAutoScroll(false);
     pos = textinputObject->positionAt(textinputObject->width()/2);
-    diff = abs(int(fm.width(textinputObject->text().left(pos))-textinputObject->width()/2));
+
+    if (!qmlDisableDistanceField()) {
+        {
+            QTextLayout layout(textinputObject->text().left(pos));
+
+            {
+                QTextOption option;
+                option.setUseDesignMetrics(true);
+                layout.setTextOption(option);
+            }
+
+            layout.beginLayout();
+            QTextLine line = layout.createLine();
+            layout.endLayout();
+
+            textLeftWidth = ceil(line.horizontalAdvance());
+        }
+    } else {
+        textLeftWidth = fm.width(textinputObject->text().left(pos));
+    }
+
+    diff = abs(int(textLeftWidth-textinputObject->width()/2));
 
     // some tollerance for different fonts.
 #ifdef Q_OS_LINUX
@@ -1442,7 +1530,6 @@ void tst_qsgtextinput::navigation()
 
 void tst_qsgtextinput::navigation_RTL()
 {
-    QSKIP("QTBUG-20017", SkipAll);
     QSGView canvas(QUrl::fromLocalFile(SRCDIR "/data/navigation.qml"));
     canvas.show();
     canvas.setFocus();