summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@nokia.com>2011-06-01 09:45:55 +0200
committerQt by Nokia <qt-info@nokia.com>2011-08-19 15:15:56 +0200
commit1cc58fdf174656a52603af00cb40478066c5abd4 (patch)
tree432054d60316a981b17655dc888805e4e5434966
parent7059861a8b8d48b5187eb46a2678f306ba288320 (diff)
Make QSGTextNode back-end for QML's TextInput and TextEdit
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>
-rw-r--r--src/declarative/items/qsgtext.cpp17
-rw-r--r--src/declarative/items/qsgtext_p_p.h7
-rw-r--r--src/declarative/items/qsgtextedit.cpp226
-rw-r--r--src/declarative/items/qsgtextedit_p.h9
-rw-r--r--src/declarative/items/qsgtextedit_p_p.h22
-rw-r--r--src/declarative/items/qsgtextinput.cpp142
-rw-r--r--src/declarative/items/qsgtextinput_p.h4
-rw-r--r--src/declarative/items/qsgtextinput_p_p.h39
-rw-r--r--src/declarative/items/qsgtextnode.cpp762
-rw-r--r--src/declarative/items/qsgtextnode_p.h38
-rw-r--r--src/declarative/scenegraph/qsgadaptationlayer_p.h3
-rw-r--r--src/declarative/scenegraph/qsgdistancefieldglyphnode.cpp7
-rw-r--r--tests/auto/declarative/qsgtext/tst_qsgtext.cpp37
-rw-r--r--tests/auto/declarative/qsgtextedit/tst_qsgtextedit.cpp62
-rw-r--r--tests/auto/declarative/qsgtextinput/data/positionAt.qml2
-rw-r--r--tests/auto/declarative/qsgtextinput/tst_qsgtextinput.cpp99
16 files changed, 1216 insertions, 260 deletions
diff --git a/src/declarative/items/qsgtext.cpp b/src/declarative/items/qsgtext.cpp
index d323e3bd54..7e8cf2d099 100644
--- a/src/declarative/items/qsgtext.cpp
+++ b/src/declarative/items/qsgtext.cpp
@@ -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)) {
diff --git a/src/declarative/items/qsgtext_p_p.h b/src/declarative/items/qsgtext_p_p.h
index 050e3984ab..40c986142c 100644
--- a/src/declarative/items/qsgtext_p_p.h
+++ b/src/declarative/items/qsgtext_p_p.h
@@ -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
diff --git a/src/declarative/items/qsgtextedit.cpp b/src/declarative/items/qsgtextedit.cpp
index 8e76a8bffd..eeeaa206db 100644
--- a/src/declarative/items/qsgtextedit.cpp
+++ b/src/declarative/items/qsgtextedit.cpp
@@ -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>
@@ -55,9 +57,14 @@
#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()
diff --git a/src/declarative/items/qsgtextedit_p.h b/src/declarative/items/qsgtextedit_p.h
index 115b25040d..fa61f03f7f 100644
--- a/src/declarative/items/qsgtextedit_p.h
+++ b/src/declarative/items/qsgtextedit_p.h
@@ -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)
diff --git a/src/declarative/items/qsgtextedit_p_p.h b/src/declarative/items/qsgtextedit_p_p.h
index c080857e46..e4f74c0ef5 100644
--- a/src/declarative/items/qsgtextedit_p_p.h
+++ b/src/declarative/items/qsgtextedit_p_p.h
@@ -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
diff --git a/src/declarative/items/qsgtextinput.cpp b/src/declarative/items/qsgtextinput.cpp
index 4b9aea1b21..222a9002e7 100644
--- a/src/declarative/items/qsgtextinput.cpp
+++ b/src/declarative/items/qsgtextinput.cpp
@@ -45,15 +45,20 @@
#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();
}
diff --git a/src/declarative/items/qsgtextinput_p.h b/src/declarative/items/qsgtextinput_p.h
index 770afa8283..f5ac50e8e1 100644
--- a/src/declarative/items/qsgtextinput_p.h
+++ b/src/declarative/items/qsgtextinput_p.h
@@ -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();
diff --git a/src/declarative/items/qsgtextinput_p_p.h b/src/declarative/items/qsgtextinput_p_p.h
index f25722972b..0d1f21b54c 100644
--- a/src/declarative/items/qsgtextinput_p_p.h
+++ b/src/declarative/items/qsgtextinput_p_p.h
@@ -65,18 +65,35 @@
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();
diff --git a/src/declarative/items/qsgtextnode.cpp b/src/declarative/items/qsgtextnode.cpp
index c09a53ca49..ef0f98bf1d 100644
--- a/src/declarative/items/qsgtextnode.cpp
+++ b/src/declarative/items/qsgtextnode.cpp
@@ -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
diff --git a/src/declarative/items/qsgtextnode_p.h b/src/declarative/items/qsgtextnode_p.h
index 7ff31998c6..4c5199aefa 100644
--- a/src/declarative/items/qsgtextnode_p.h
+++ b/src/declarative/items/qsgtextnode_p.h
@@ -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
diff --git a/src/declarative/scenegraph/qsgadaptationlayer_p.h b/src/declarative/scenegraph/qsgadaptationlayer_p.h
index 820650eb56..f2c0cbe9ef 100644
--- a/src/declarative/scenegraph/qsgadaptationlayer_p.h
+++ b/src/declarative/scenegraph/qsgadaptationlayer_p.h
@@ -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
diff --git a/src/declarative/scenegraph/qsgdistancefieldglyphnode.cpp b/src/declarative/scenegraph/qsgdistancefieldglyphnode.cpp
index beeda1e476..cb3dab959c 100644
--- a/src/declarative/scenegraph/qsgdistancefieldglyphnode.cpp
+++ b/src/declarative/scenegraph/qsgdistancefieldglyphnode.cpp
@@ -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);
diff --git a/tests/auto/declarative/qsgtext/tst_qsgtext.cpp b/tests/auto/declarative/qsgtext/tst_qsgtext.cpp
index 48d679bfa7..747ea51f41 100644
--- a/tests/auto/declarative/qsgtext/tst_qsgtext.cpp
+++ b/tests/auto/declarative/qsgtext/tst_qsgtext.cpp
@@ -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);
diff --git a/tests/auto/declarative/qsgtextedit/tst_qsgtextedit.cpp b/tests/auto/declarative/qsgtextedit/tst_qsgtextedit.cpp
index 3492ac2138..2a94e20d3f 100644
--- a/tests/auto/declarative/qsgtextedit/tst_qsgtextedit.cpp
+++ b/tests/auto/declarative/qsgtextedit/tst_qsgtextedit.cpp
@@ -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
diff --git a/tests/auto/declarative/qsgtextinput/data/positionAt.qml b/tests/auto/declarative/qsgtextinput/data/positionAt.qml
index 7611376e13..1840462c87 100644
--- a/tests/auto/declarative/qsgtextinput/data/positionAt.qml
+++ b/tests/auto/declarative/qsgtextinput/data/positionAt.qml
@@ -4,5 +4,5 @@ TextInput{
focus: true
objectName: "myInput"
width: 50
- text: "This is a long piece of text"
+ text: "AAAAAAAAAAAAAAAAAAAAAAAAAAAA"
}
diff --git a/tests/auto/declarative/qsgtextinput/tst_qsgtextinput.cpp b/tests/auto/declarative/qsgtextinput/tst_qsgtextinput.cpp
index 93e8ad21fd..744717cd80 100644
--- a/tests/auto/declarative/qsgtextinput/tst_qsgtextinput.cpp
+++ b/tests/auto/declarative/qsgtextinput/tst_qsgtextinput.cpp
@@ -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();