From 1cc58fdf174656a52603af00cb40478066c5abd4 Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Wed, 1 Jun 2011 09:45:55 +0200 Subject: [PATCH] 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 --- src/declarative/items/qsgtext.cpp | 17 +- src/declarative/items/qsgtext_p_p.h | 7 +- src/declarative/items/qsgtextedit.cpp | 226 ++++-- src/declarative/items/qsgtextedit_p.h | 9 +- src/declarative/items/qsgtextedit_p_p.h | 22 +- src/declarative/items/qsgtextinput.cpp | 142 +++- src/declarative/items/qsgtextinput_p.h | 4 +- src/declarative/items/qsgtextinput_p_p.h | 39 +- src/declarative/items/qsgtextnode.cpp | 762 +++++++++++++++++---- src/declarative/items/qsgtextnode_p.h | 38 +- src/declarative/scenegraph/qsgadaptationlayer_p.h | 3 +- .../scenegraph/qsgdistancefieldglyphnode.cpp | 7 +- tests/auto/declarative/qsgtext/tst_qsgtext.cpp | 37 +- .../declarative/qsgtextedit/tst_qsgtextedit.cpp | 62 +- .../declarative/qsgtextinput/data/positionAt.qml | 2 +- .../declarative/qsgtextinput/tst_qsgtextinput.cpp | 99 ++- 16 files changed, 1216 insertions(+), 260 deletions(-) diff --git a/src/declarative/items/qsgtext.cpp b/src/declarative/items/qsgtext.cpp index d323e3b..7e8cf2d 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 050e398..40c9861 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 8e76a8b..eeeaa20 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 #include @@ -55,9 +57,14 @@ #include #include #include +#include +#include +#include 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(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(oldNode); } + + qobject_cast(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(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(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 115b250..fa61f03 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 c080857..e4f74c0 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 4b9aea1..222a900 100644 --- a/src/declarative/items/qsgtextinput.cpp +++ b/src/declarative/items/qsgtextinput.cpp @@ -45,15 +45,20 @@ #include #include +#include #include #include #include #include #include +#include +#include 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(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 770afa8..f5ac50e 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 f257229..0d1f21b 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 cursorComponent; QPointer 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 c09a53c..ef0f98b 100644 --- a/src/declarative/items/qsgtextnode.cpp +++ b/src/declarative/items/qsgtextnode.cpp @@ -47,6 +47,7 @@ #include +#include #include #include #include @@ -57,6 +58,7 @@ #include #include #include +#include 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 *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 &binaryTree, + QVarLengthArray *sortedIndexes, + int currentIndex = 0) + { + Q_ASSERT(currentIndex < binaryTree.size()); + + const BinaryTreeNode *node = binaryTree.data() + currentIndex; + if (node->leftChildIndex >= 0) + inOrder(binaryTree, sortedIndexes, node->leftChildIndex); + + sortedIndexes->append(currentIndex); + + if (node->rightChildIndex >= 0) + inOrder(binaryTree, sortedIndexes, node->rightChildIndex); + } + }; + + // Engine that takes glyph runs as input, and produces a set of glyph nodes, clip nodes, + // and rectangle nodes to represent the text, decorations and selection. Will try to minimize + // number of nodes, and join decorations in neighbouring items + class SelectionEngine + { + public: + SelectionEngine() : m_hasSelection(false) {} + + QTextLine currentLine() const { return m_currentLine; } + + void setCurrentLine(const QTextLine ¤tLine) + { + if (m_currentLine.isValid()) + processCurrentLine(); + + m_currentLine = currentLine; + } + + void 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 &textDecorations, + qreal offset, qreal thickness); + + QColor m_selectionColor; + QColor m_textColor; + QColor m_selectedTextColor; + QPointF m_position; + + QTextLine m_currentLine; + bool m_hasSelection; + + QList m_selectionRects; + QVarLengthArray m_currentLineTree; + + QList m_lines; + QVector m_processedNodes; + }; + + void SelectionEngine::addTextDecorations(const QVarLengthArray &textDecorations, + qreal offset, qreal thickness) + { + for (int i=0; i sortedIndexes; // Indexes in tree sorted by x position + BinaryTreeNode::inOrder(m_currentLineTree, &sortedIndexes); + + Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size()); + + BinaryTreeNode::SelectionState currentSelectionState = BinaryTreeNode::Unselected; + QRectF currentRect; + + 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 pendingUnderlines; + QVarLengthArray pendingOverlines; + QVarLengthArray pendingStrikeOuts; + if (!sortedIndexes.isEmpty()) { + QSGClipNode *currentClipNode = m_hasSelection ? new QSGClipNode : 0; + bool currentClipNodeUsed = false; + for (int i=0; i<=sortedIndexes.size(); ++i) { + BinaryTreeNode *node = 0; + if (i < sortedIndexes.size()) { + int sortedIndex = sortedIndexes.at(i); + Q_ASSERT(sortedIndex < m_currentLineTree.size()); + + node = m_currentLineTree.data() + sortedIndex; + } + + if (i == 0) + currentSelectionState = node->selectionState; + + // Update decorations + if (currentDecorations != 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; isetGlyphs(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 > > KeyType; + QHash map; + for (int i=0; iupdate(); + 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 otherGlyphIndexes = otherGlyphRun.glyphIndexes(); + QVector otherGlyphPositions = otherGlyphRun.positions(); + + otherGlyphIndexes += glyphRun.glyphIndexes(); + + QVector glyphPositions = glyphRun.positions(); + for (int j=0; jposition - otherNode->position); + } + + otherGlyphRun.setGlyphIndexes(otherGlyphIndexes); + otherGlyphRun.setPositions(otherGlyphPositions); + + } else { + map.insert(key, node); + } + } + + // ...and add clip nodes and glyphs to tree. + QHash::const_iterator it = map.constBegin(); + while (it != map.constEnd()) { + + BinaryTreeNode *node = it.value(); + + QSGClipNode *clipNode = node->clipNode; + if (clipNode != 0 && clipNode->parent() == 0 ) + parentNode->appendChildNode(clipNode); + + QColor color = node->selectionState == BinaryTreeNode::Selected + ? m_selectedTextColor + : node->color; + + parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode); + + ++it; + } + + // Finally, add decorations for each node to the tree. + for (int i=0; iappendChildNode(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 glyphRuns = fragment.glyphRuns(blockRelativePosition, len); + for (int j=0; j glyphRuns = fragment.glyphRuns(blockRelativePosition, + unselectedPartLength); + for (int j=0; j textPos) { + int currentSelectionStart = qMax(selectionStart, textPos); + int currentSelectionLength = qMin(selectionEnd - currentSelectionStart, + currentStepEnd - currentSelectionStart); + int selectionRelativeBlockPosition = currentSelectionStart - block.position(); + + QList glyphRuns = fragment.glyphRuns(selectionRelativeBlockPosition, + currentSelectionLength); + for (int j=0; j= textPos && selectionEnd < currentStepEnd) { + QList glyphRuns = fragment.glyphRuns(selectionEnd - block.position(), + currentStepEnd - selectionEnd); + for (int j=0; j 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 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; ilineCount(); ++i) { + QTextLine line = textLayout->lineAt(i); + + engine.setCurrentLine(line); + + int lineEnd = line.textStart() + line.textLength(); + if (selectionStart > lineEnd || selectionEnd < line.textStart()) { + QList glyphRuns = line.glyphRuns(); + for (int j=0; j glyphRuns = line.glyphRuns(line.textStart(), + qMin(selectionStart - line.textStart(), + line.textLength())); + + for (int j=0; j= selectionStart && selectionStart >= line.textStart()) { + QList glyphRuns = line.glyphRuns(selectionStart, selectionEnd - selectionStart + 1); + + for (int j=0; j= line.textStart() && selectionEnd < lineEnd) { + QList glyphRuns = line.glyphRuns(selectionEnd + 1, lineEnd - selectionEnd); + for (int j=0; jboundingRect().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 glyphsList = fragment.glyphRuns(); - for (int i=0; ibaseLine(); - 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 7ff3199..4c5199a 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 820650e..f2c0cbe 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 - #include #include #include @@ -53,6 +52,8 @@ #include #include +#include + QT_BEGIN_HEADER QT_BEGIN_NAMESPACE diff --git a/src/declarative/scenegraph/qsgdistancefieldglyphnode.cpp b/src/declarative/scenegraph/qsgdistancefieldglyphnode.cpp index beeda1e..cb3dab9 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 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 48d679b..747ea51 100644 --- a/tests/auto/declarative/qsgtext/tst_qsgtext.cpp +++ b/tests/auto/declarative/qsgtext/tst_qsgtext.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -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 3492ac2..2a94e20 100644 --- a/tests/auto/declarative/qsgtextedit/tst_qsgtextedit.cpp +++ b/tests/auto/declarative/qsgtextedit/tst_qsgtextedit.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -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("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 7611376..1840462 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 93e8ad2..744717c 100644 --- a/tests/auto/declarative/qsgtextinput/tst_qsgtextinput.cpp +++ b/tests/auto/declarative/qsgtextinput/tst_qsgtextinput.cpp @@ -51,7 +51,9 @@ #include #include #include +#include #include +#include #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("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(); -- 2.7.4