blob: 42fdbea58defdcb07728a2406c5a8d9ac67c959e [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <qtest.h>
#include <QtTest/QSignalSpy>
#include <QTextDocument>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQml/qjsvalue.h>
#include <QtQuick/private/qquicktext_p.h>
#include <QtQuick/private/qquickmousearea_p.h>
#include <QtQuickTest/QtQuickTest>
#include <private/qquicktext_p_p.h>
#include <private/qquicktextdocument_p.h>
#include <private/qquickvaluetypes_p.h>
#include <QFontMetrics>
#include <qmath.h>
#include <QtQuick/QQuickView>
#include <QtQuick/qquickitemgrabresult.h>
#include <private/qguiapplication_p.h>
#include <limits.h>
#include <QtGui/QMouseEvent>
#include "../../shared/util.h"
#include "testhttpserver.h"
DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD)
Q_DECLARE_METATYPE(QQuickText::TextFormat)
QT_BEGIN_NAMESPACE
extern void qt_setQtEnableTestFont(bool value);
QT_END_NAMESPACE
class tst_qquicktext : public QQmlDataTest
{
Q_OBJECT
public:
tst_qquicktext();
private slots:
void cleanup();
void text();
void width();
void wrap();
void elide();
void elideParentChanged();
void multilineElide_data();
void multilineElide();
void implicitElide_data();
void implicitElide();
void textFormat();
void baseUrl();
void embeddedImages_data();
void embeddedImages();
void lineCount();
void lineHeight();
// ### these tests may be trivial
void horizontalAlignment();
void horizontalAlignment_RightToLeft();
void verticalAlignment();
void hAlignImplicitWidth();
void font();
void style();
void color();
void smooth();
void renderType();
void antialiasing();
// QQuickFontValueType
void weight();
void underline();
void overline();
void strikeout();
void capitalization();
void letterSpacing();
void wordSpacing();
void linkInteraction_data();
void linkInteraction();
void implicitSize_data();
void implicitSize();
void implicitSizeChangeRewrap();
void dependentImplicitSizes();
void contentSize();
void implicitSizeBinding_data();
void implicitSizeBinding();
void geometryChanged();
void boundingRect_data();
void boundingRect();
void clipRect();
void lineLaidOut();
void lineLaidOutRelayout();
void lineLaidOutHAlign();
void lineLaidOutImplicitWidth();
void imgTagsBaseUrl_data();
void imgTagsBaseUrl();
void imgTagsAlign_data();
void imgTagsAlign();
void imgTagsMultipleImages();
void imgTagsElide();
void imgTagsUpdates();
void imgTagsError();
void fontSizeMode_data();
void fontSizeMode();
void fontSizeModeMultiline_data();
void fontSizeModeMultiline();
void multilengthStrings_data();
void multilengthStrings();
void fontFormatSizes_data();
void fontFormatSizes();
void baselineOffset_data();
void baselineOffset();
void htmlLists();
void htmlLists_data();
void elideBeforeMaximumLineCount();
void hover();
void growFromZeroWidth();
void padding();
void hintingPreference();
void zeroWidthAndElidedDoesntRender();
void hAlignWidthDependsOnImplicitWidth_data();
void hAlignWidthDependsOnImplicitWidth();
void fontInfo();
void initialContentHeight();
void verticallyAlignedImageInTable();
void transparentBackground();
void displaySuperscriptedTag();
private:
QStringList standard;
QStringList richText;
QStringList horizontalAlignmentmentStrings;
QStringList verticalAlignmentmentStrings;
QList<Qt::Alignment> verticalAlignmentments;
QList<Qt::Alignment> horizontalAlignmentments;
QStringList styleStrings;
QList<QQuickText::TextStyle> styles;
QStringList colorStrings;
QQmlEngine engine;
QQuickView *createView(const QString &filename);
int numberOfNonWhitePixels(int fromX, int toX, const QImage &image);
};
void tst_qquicktext::cleanup()
{
QVERIFY(QGuiApplication::topLevelWindows().isEmpty());
}
tst_qquicktext::tst_qquicktext()
{
standard << "the quick brown fox jumped over the lazy dog"
<< "the quick brown fox\n jumped over the lazy dog";
richText << "<i>the <b>quick</b> brown <a href=\\\"http://www.google.com\\\">fox</a> jumped over the <b>lazy</b> dog</i>"
<< "<i>the <b>quick</b> brown <a href=\\\"http://www.google.com\\\">fox</a><br>jumped over the <b>lazy</b> dog</i>";
horizontalAlignmentmentStrings << "AlignLeft"
<< "AlignRight"
<< "AlignHCenter";
verticalAlignmentmentStrings << "AlignTop"
<< "AlignBottom"
<< "AlignVCenter";
horizontalAlignmentments << Qt::AlignLeft
<< Qt::AlignRight
<< Qt::AlignHCenter;
verticalAlignmentments << Qt::AlignTop
<< Qt::AlignBottom
<< Qt::AlignVCenter;
styleStrings << "Normal"
<< "Outline"
<< "Raised"
<< "Sunken";
styles << QQuickText::Normal
<< QQuickText::Outline
<< QQuickText::Raised
<< QQuickText::Sunken;
colorStrings << "aliceblue"
<< "antiquewhite"
<< "aqua"
<< "darkkhaki"
<< "darkolivegreen"
<< "dimgray"
<< "palevioletred"
<< "lightsteelblue"
<< "#000000"
<< "#AAAAAA"
<< "#FFFFFF"
<< "#2AC05F";
//
// need a different test to do alpha channel test
// << "#AA0011DD"
// << "#00F16B11";
//
qt_setQtEnableTestFont(true);
}
QQuickView *tst_qquicktext::createView(const QString &filename)
{
QQuickView *window = new QQuickView(nullptr);
window->setSource(QUrl::fromLocalFile(filename));
return window;
}
void tst_qquicktext::text()
{
{
QQmlComponent textComponent(&engine);
textComponent.setData("import QtQuick 2.0\nText { text: \"\" }", QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->text(), QString(""));
QCOMPARE(textObject->width(), qreal(0));
delete textObject;
}
for (int i = 0; i < standard.size(); i++)
{
QString componentStr = "import QtQuick 2.0\nText { text: \"" + standard.at(i) + "\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->text(), standard.at(i));
QVERIFY(textObject->width() > 0);
delete textObject;
}
for (int i = 0; i < richText.size(); i++)
{
QString componentStr = "import QtQuick 2.0\nText { text: \"" + richText.at(i) + "\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QString expected = richText.at(i);
QCOMPARE(textObject->text(), expected.replace("\\\"", "\""));
QVERIFY(textObject->width() > 0);
delete textObject;
}
}
void tst_qquicktext::width()
{
// uses Font metrics to find the width for standard and document to find the width for rich
{
QQmlComponent textComponent(&engine);
textComponent.setData("import QtQuick 2.0\nText { text: \"\" }", QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->width(), 0.);
delete textObject;
}
bool requiresUnhintedMetrics = !qmlDisableDistanceField();
for (int i = 0; i < standard.size(); i++)
{
QVERIFY(!Qt::mightBeRichText(standard.at(i))); // self-test
QFont f;
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 = layout.boundingRect().width();
} else {
QFontMetricsF fm(f);
metricWidth = fm.size(Qt::TextExpandTabs | Qt::TextShowMnemonic, standard.at(i)).width();
}
QString componentStr = "import QtQuick 2.0\nText { text: \"" + standard.at(i) + "\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QVERIFY(textObject->boundingRect().width() > 0);
QCOMPARE(textObject->width(), qreal(metricWidth));
QVERIFY(textObject->textFormat() == QQuickText::AutoText); // setting text doesn't change format
delete textObject;
}
for (int i = 0; i < richText.size(); i++)
{
QVERIFY(Qt::mightBeRichText(richText.at(i))); // self-test
QString componentStr = "import QtQuick 2.0\nText { text: \"" + richText.at(i) + "\"; textFormat: Text.RichText }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(textObject);
QVERIFY(textPrivate != nullptr);
QVERIFY(textPrivate->extra.isAllocated());
QTextDocument *doc = textPrivate->extra->doc;
QVERIFY(doc != nullptr);
QCOMPARE(int(textObject->width()), int(doc->idealWidth()));
QCOMPARE(textObject->textFormat(), QQuickText::RichText);
delete textObject;
}
}
void tst_qquicktext::wrap()
{
int textHeight = 0;
// for specified width and wrap set true
{
QQmlComponent textComponent(&engine);
textComponent.setData("import QtQuick 2.0\nText { text: \"Hello\"; wrapMode: Text.WordWrap; width: 300 }", QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
textHeight = textObject->height();
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->wrapMode(), QQuickText::WordWrap);
QCOMPARE(textObject->width(), 300.);
delete textObject;
}
for (int i = 0; i < standard.size(); i++)
{
QString componentStr = "import QtQuick 2.0\nText { wrapMode: Text.WordWrap; width: 30; text: \"" + standard.at(i) + "\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->width(), 30.);
QVERIFY(textObject->height() > textHeight);
int oldHeight = textObject->height();
textObject->setWidth(100);
QVERIFY(textObject->height() < oldHeight);
delete textObject;
}
for (int i = 0; i < richText.size(); i++)
{
QString componentStr = "import QtQuick 2.0\nText { wrapMode: Text.WordWrap; width: 30; text: \"" + richText.at(i) + "\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->width(), 30.);
QVERIFY(textObject->height() > textHeight);
qreal oldHeight = textObject->height();
textObject->setWidth(100);
QVERIFY(textObject->height() < oldHeight);
delete textObject;
}
// Check that increasing width from idealWidth will cause a relayout
for (int i = 0; i < richText.size(); i++)
{
QString componentStr = "import QtQuick 2.0\nText { wrapMode: Text.WordWrap; textFormat: Text.RichText; width: 30; text: \"" + richText.at(i) + "\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->width(), 30.);
QVERIFY(textObject->height() > textHeight);
QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(textObject);
QVERIFY(textPrivate != nullptr);
QVERIFY(textPrivate->extra.isAllocated());
QTextDocument *doc = textPrivate->extra->doc;
QVERIFY(doc != nullptr);
textObject->setWidth(doc->idealWidth());
QCOMPARE(textObject->width(), doc->idealWidth());
QVERIFY(textObject->height() > textHeight);
qreal oldHeight = textObject->height();
textObject->setWidth(100);
QVERIFY(textObject->height() < oldHeight);
delete textObject;
}
// richtext again with a fixed height
for (int i = 0; i < richText.size(); i++)
{
QString componentStr = "import QtQuick 2.0\nText { wrapMode: Text.WordWrap; width: 30; height: 50; text: \"" + richText.at(i) + "\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->width(), 30.);
QVERIFY(textObject->implicitHeight() > textHeight);
qreal oldHeight = textObject->implicitHeight();
textObject->setWidth(100);
QVERIFY(textObject->implicitHeight() < oldHeight);
delete textObject;
}
{
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0\n Text {}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickText *textObject = qobject_cast<QQuickText *>(object.data());
QVERIFY(textObject);
QSignalSpy spy(textObject, SIGNAL(wrapModeChanged()));
QCOMPARE(textObject->wrapMode(), QQuickText::NoWrap);
textObject->setWrapMode(QQuickText::Wrap);
QCOMPARE(textObject->wrapMode(), QQuickText::Wrap);
QCOMPARE(spy.count(), 1);
textObject->setWrapMode(QQuickText::Wrap);
QCOMPARE(spy.count(), 1);
textObject->setWrapMode(QQuickText::NoWrap);
QCOMPARE(textObject->wrapMode(), QQuickText::NoWrap);
QCOMPARE(spy.count(), 2);
}
}
void tst_qquicktext::elide()
{
for (QQuickText::TextElideMode m = QQuickText::ElideLeft; m<=QQuickText::ElideNone; m=QQuickText::TextElideMode(int(m)+1)) {
const char* elidename[]={"ElideLeft", "ElideRight", "ElideMiddle", "ElideNone"};
QString elide = "elide: Text." + QString(elidename[int(m)]) + ";";
// XXX Poor coverage.
{
QQmlComponent textComponent(&engine);
textComponent.setData(("import QtQuick 2.0\nText { text: \"\"; "+elide+" width: 100 }").toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE(textObject->elideMode(), m);
QCOMPARE(textObject->width(), 100.);
delete textObject;
}
for (int i = 0; i < standard.size(); i++)
{
QString componentStr = "import QtQuick 2.0\nText { "+elide+" width: 100; text: \"" + standard.at(i) + "\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE(textObject->elideMode(), m);
QCOMPARE(textObject->width(), 100.);
if (m != QQuickText::ElideNone && !standard.at(i).contains('\n'))
QVERIFY(textObject->contentWidth() <= textObject->width());
delete textObject;
}
for (int i = 0; i < richText.size(); i++)
{
QString componentStr = "import QtQuick 2.0\nText { "+elide+" width: 100; text: \"" + richText.at(i) + "\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE(textObject->elideMode(), m);
QCOMPARE(textObject->width(), 100.);
if (m != QQuickText::ElideNone && standard.at(i).contains("<br>"))
QVERIFY(textObject->contentWidth() <= textObject->width());
delete textObject;
}
}
}
// QTBUG-60328
// Tests that text with elide set is rendered after
// having its parent cleared and then set again.
void tst_qquicktext::elideParentChanged()
{
QQuickView window;
window.setSource(testFileUrl("elideParentChanged.qml"));
QTRY_COMPARE(window.status(), QQuickView::Ready);
window.show();
QVERIFY(QTest::qWaitForWindowExposed(&window));
QQuickItem *root = window.rootObject();
QVERIFY(root);
QCOMPARE(root->childItems().size(), 1);
// Store a snapshot of the scene so that we can compare it later.
QSharedPointer<QQuickItemGrabResult> grabResult = root->grabToImage();
QTRY_VERIFY(!grabResult->image().isNull());
const QImage expectedItemImageGrab(grabResult->image());
// Clear the text's parent. It shouldn't render anything.
QQuickItem *text = root->childItems().first();
text->setParentItem(nullptr);
QCOMPARE(text->width(), 0.0);
QCOMPARE(text->height(), 0.0);
// Set the parent back to what it was. The text should
// be rendered identically to how it was before.
text->setParentItem(root);
QCOMPARE(text->width(), 100.0);
QCOMPARE(text->height(), 30.0);
grabResult = root->grabToImage();
QTRY_VERIFY(!grabResult->image().isNull());
const QImage actualItemImageGrab(grabResult->image());
QCOMPARE(actualItemImageGrab, expectedItemImageGrab);
}
void tst_qquicktext::multilineElide_data()
{
QTest::addColumn<QQuickText::TextFormat>("format");
QTest::newRow("plain") << QQuickText::PlainText;
QTest::newRow("styled") << QQuickText::StyledText;
}
void tst_qquicktext::multilineElide()
{
QFETCH(QQuickText::TextFormat, format);
QScopedPointer<QQuickView> window(createView(testFile("multilineelide.qml")));
QQuickText *myText = qobject_cast<QQuickText*>(window->rootObject());
QVERIFY(myText != nullptr);
myText->setTextFormat(format);
QCOMPARE(myText->lineCount(), 3);
QCOMPARE(myText->truncated(), true);
qreal lineHeight = myText->contentHeight() / 3.;
// Set a valid height greater than the truncated content height and ensure the line count is
// unchanged.
myText->setHeight(200);
QCOMPARE(myText->lineCount(), 3);
QCOMPARE(myText->truncated(), true);
// reduce size and ensure fewer lines are drawn
myText->setHeight(lineHeight * 2);
QCOMPARE(myText->lineCount(), 2);
myText->setHeight(lineHeight);
QCOMPARE(myText->lineCount(), 1);
myText->setHeight(5);
QCOMPARE(myText->lineCount(), 1);
myText->setHeight(lineHeight * 3);
QCOMPARE(myText->lineCount(), 3);
// remove max count and show all lines.
myText->setHeight(1000);
myText->resetMaximumLineCount();
QCOMPARE(myText->truncated(), false);
// reduce size again
myText->setHeight(lineHeight * 2);
QCOMPARE(myText->lineCount(), 2);
QCOMPARE(myText->truncated(), true);
// change line height
myText->setLineHeight(1.1);
QCOMPARE(myText->lineCount(), 1);
}
void tst_qquicktext::implicitElide_data()
{
QTest::addColumn<QString>("width");
QTest::addColumn<QString>("initialText");
QTest::addColumn<QString>("text");
QTest::newRow("maximum width, empty")
<< "Math.min(implicitWidth, 100)"
<< "";
QTest::newRow("maximum width, short")
<< "Math.min(implicitWidth, 100)"
<< "the";
QTest::newRow("maximum width, long")
<< "Math.min(implicitWidth, 100)"
<< "the quick brown fox jumped over the lazy dog";
QTest::newRow("reset width, empty")
<< "implicitWidth > 100 ? 100 : undefined"
<< "";
QTest::newRow("reset width, short")
<< "implicitWidth > 100 ? 100 : undefined"
<< "the";
QTest::newRow("reset width, long")
<< "implicitWidth > 100 ? 100 : undefined"
<< "the quick brown fox jumped over the lazy dog";
}
void tst_qquicktext::implicitElide()
{
QFETCH(QString, width);
QFETCH(QString, initialText);
QString componentStr =
"import QtQuick 2.0\n"
"Text {\n"
"width: " + width + "\n"
"text: \"" + initialText + "\"\n"
"elide: Text.ElideRight\n"
"}";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject->contentWidth() <= textObject->width());
textObject->setText("the quick brown fox jumped over");
QVERIFY(textObject->contentWidth() > 0);
QVERIFY(textObject->contentWidth() <= textObject->width());
}
void tst_qquicktext::textFormat()
{
{
QQmlComponent textComponent(&engine);
textComponent.setData("import QtQuick 2.0\nText { text: \"Hello\"; textFormat: Text.RichText }", QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->textFormat(), QQuickText::RichText);
QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(textObject);
QVERIFY(textPrivate != nullptr);
QVERIFY(textPrivate->richText);
delete textObject;
}
{
QQmlComponent textComponent(&engine);
textComponent.setData("import QtQuick 2.0\nText { text: \"<b>Hello</b>\" }", QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->textFormat(), QQuickText::AutoText);
QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(textObject);
QVERIFY(textPrivate != nullptr);
QVERIFY(textPrivate->styledText);
delete textObject;
}
{
QQmlComponent textComponent(&engine);
textComponent.setData("import QtQuick 2.0\nText { text: \"<b>Hello</b>\"; textFormat: Text.PlainText }", QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->textFormat(), QQuickText::PlainText);
delete textObject;
}
{
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0\n Text {}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickText *text = qobject_cast<QQuickText *>(object.data());
QVERIFY(text);
QSignalSpy spy(text, &QQuickText::textFormatChanged);
QCOMPARE(text->textFormat(), QQuickText::AutoText);
text->setTextFormat(QQuickText::StyledText);
QCOMPARE(text->textFormat(), QQuickText::StyledText);
QCOMPARE(spy.count(), 1);
text->setTextFormat(QQuickText::StyledText);
QCOMPARE(spy.count(), 1);
text->setTextFormat(QQuickText::AutoText);
QCOMPARE(text->textFormat(), QQuickText::AutoText);
QCOMPARE(spy.count(), 2);
}
{
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0\n Text { text: \"<b>Hello</b>\" }", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickText *text = qobject_cast<QQuickText *>(object.data());
QVERIFY(text);
QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(text);
QVERIFY(textPrivate);
QCOMPARE(text->textFormat(), QQuickText::AutoText);
QVERIFY(!textPrivate->layout.formats().isEmpty());
text->setTextFormat(QQuickText::StyledText);
QVERIFY(!textPrivate->layout.formats().isEmpty());
text->setTextFormat(QQuickText::PlainText);
QVERIFY(textPrivate->layout.formats().isEmpty());
text->setTextFormat(QQuickText::AutoText);
QVERIFY(!textPrivate->layout.formats().isEmpty());
}
{
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0\nText { text: \"Hello\"; elide: Text.ElideRight }", QUrl::fromLocalFile(""));
QScopedPointer<QObject> object(component.create());
QQuickText *text = qobject_cast<QQuickText *>(object.data());
QVERIFY(text);
QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(text);
QVERIFY(textPrivate);
// underline a mnemonic
QVector<QTextLayout::FormatRange> formats;
QTextLayout::FormatRange range;
range.start = 0;
range.length = 1;
range.format.setFontUnderline(true);
formats << range;
// the mnemonic format should be retained
textPrivate->layout.setFormats(formats);
text->forceLayout();
QCOMPARE(textPrivate->layout.formats(), formats);
// and carried over to the elide layout
text->setWidth(text->implicitWidth() - 1);
QVERIFY(textPrivate->elideLayout);
QCOMPARE(textPrivate->elideLayout->formats(), formats);
// but cleared when the text changes
text->setText("Changed");
QVERIFY(textPrivate->elideLayout);
QVERIFY(textPrivate->layout.formats().isEmpty());
}
}
//the alignment tests may be trivial o.oa
void tst_qquicktext::horizontalAlignment()
{
//test one align each, and then test if two align fails.
for (int i = 0; i < standard.size(); i++)
{
for (int j=0; j < horizontalAlignmentmentStrings.size(); j++)
{
QString componentStr = "import QtQuick 2.0\nText { horizontalAlignment: \"" + horizontalAlignmentmentStrings.at(j) + "\"; text: \"" + standard.at(i) + "\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE((int)textObject->hAlign(), (int)horizontalAlignmentments.at(j));
delete textObject;
}
}
for (int i = 0; i < richText.size(); i++)
{
for (int j=0; j < horizontalAlignmentmentStrings.size(); j++)
{
QString componentStr = "import QtQuick 2.0\nText { horizontalAlignment: \"" + horizontalAlignmentmentStrings.at(j) + "\"; text: \"" + richText.at(i) + "\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE((int)textObject->hAlign(), (int)horizontalAlignmentments.at(j));
delete textObject;
}
}
}
void tst_qquicktext::horizontalAlignment_RightToLeft()
{
QScopedPointer<QQuickView> window(createView(testFile("horizontalAlignment_RightToLeft.qml")));
QQuickText *text = window->rootObject()->findChild<QQuickText*>("text");
QVERIFY(text != nullptr);
window->showNormal();
QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(text);
QVERIFY(textPrivate != nullptr);
QTRY_VERIFY(textPrivate->layout.lineCount());
// implicit alignment should follow the reading direction of RTL text
QCOMPARE(text->hAlign(), QQuickText::AlignRight);
QCOMPARE(text->effectiveHAlign(), text->hAlign());
QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() > window->width()/2);
// explicitly left aligned text
text->setHAlign(QQuickText::AlignLeft);
QCOMPARE(text->hAlign(), QQuickText::AlignLeft);
QCOMPARE(text->effectiveHAlign(), text->hAlign());
QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() < window->width()/2);
// explicitly right aligned text
text->setHAlign(QQuickText::AlignRight);
QCOMPARE(text->hAlign(), QQuickText::AlignRight);
QCOMPARE(text->effectiveHAlign(), text->hAlign());
QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() > window->width()/2);
// change to rich text
QString textString = text->text();
text->setText(QString("<i>") + textString + QString("</i>"));
text->setTextFormat(QQuickText::RichText);
text->resetHAlign();
// implicitly aligned rich text should follow the reading direction of text
QCOMPARE(text->hAlign(), QQuickText::AlignRight);
QCOMPARE(text->effectiveHAlign(), text->hAlign());
QVERIFY(textPrivate->extra.isAllocated());
QVERIFY(textPrivate->extra->doc->defaultTextOption().alignment() & Qt::AlignLeft);
// explicitly left aligned rich text
text->setHAlign(QQuickText::AlignLeft);
QCOMPARE(text->hAlign(), QQuickText::AlignLeft);
QCOMPARE(text->effectiveHAlign(), text->hAlign());
QVERIFY(textPrivate->extra->doc->defaultTextOption().alignment() & Qt::AlignRight);
// explicitly right aligned rich text
text->setHAlign(QQuickText::AlignRight);
QCOMPARE(text->hAlign(), QQuickText::AlignRight);
QCOMPARE(text->effectiveHAlign(), text->hAlign());
QVERIFY(textPrivate->extra->doc->defaultTextOption().alignment() & Qt::AlignLeft);
text->setText(textString);
text->setTextFormat(QQuickText::PlainText);
// explicitly center aligned
text->setHAlign(QQuickText::AlignHCenter);
QCOMPARE(text->hAlign(), QQuickText::AlignHCenter);
QCOMPARE(text->effectiveHAlign(), text->hAlign());
QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() < window->width()/2);
QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().right() > window->width()/2);
// reseted alignment should go back to following the text reading direction
text->resetHAlign();
QCOMPARE(text->hAlign(), QQuickText::AlignRight);
QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() > window->width()/2);
// mirror the text item
QQuickItemPrivate::get(text)->setLayoutMirror(true);
// mirrored implicit alignment should continue to follow the reading direction of the text
QCOMPARE(text->hAlign(), QQuickText::AlignRight);
QCOMPARE(text->effectiveHAlign(), QQuickText::AlignRight);
QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() > window->width()/2);
// mirrored explicitly right aligned behaves as left aligned
text->setHAlign(QQuickText::AlignRight);
QCOMPARE(text->hAlign(), QQuickText::AlignRight);
QCOMPARE(text->effectiveHAlign(), QQuickText::AlignLeft);
QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() < window->width()/2);
// mirrored explicitly left aligned behaves as right aligned
text->setHAlign(QQuickText::AlignLeft);
QCOMPARE(text->hAlign(), QQuickText::AlignLeft);
QCOMPARE(text->effectiveHAlign(), QQuickText::AlignRight);
QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() > window->width()/2);
// disable mirroring
QQuickItemPrivate::get(text)->setLayoutMirror(false);
text->resetHAlign();
// English text should be implicitly left aligned
text->setText("Hello world!");
QCOMPARE(text->hAlign(), QQuickText::AlignLeft);
QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() < window->width()/2);
// empty text with implicit alignment follows the system locale-based
// keyboard input direction from QInputMethod::inputDirection()
text->setText("");
QCOMPARE(text->hAlign(), qApp->inputMethod()->inputDirection() == Qt::LeftToRight ?
QQuickText::AlignLeft : QQuickText::AlignRight);
text->setHAlign(QQuickText::AlignRight);
QCOMPARE(text->hAlign(), QQuickText::AlignRight);
window.reset();
// alignment of Text with no text set to it
QString componentStr = "import QtQuick 2.0\nText {}";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE(textObject->hAlign(), qApp->inputMethod()->inputDirection() == Qt::LeftToRight ?
QQuickText::AlignLeft : QQuickText::AlignRight);
delete textObject;
}
int tst_qquicktext::numberOfNonWhitePixels(int fromX, int toX, const QImage &image)
{
int pixels = 0;
for (int x = fromX; x < toX; ++x) {
for (int y = 0; y < image.height(); ++y) {
if (image.pixel(x, y) != qRgb(255, 255, 255))
pixels++;
}
}
return pixels;
}
static inline QByteArray msgNotGreaterThan(int n1, int n2)
{
return QByteArray::number(n1) + QByteArrayLiteral(" is not greater than ") + QByteArray::number(n2);
}
static inline QByteArray msgNotLessThan(int n1, int n2)
{
return QByteArray::number(n1) + QByteArrayLiteral(" is not less than ") + QByteArray::number(n2);
}
void tst_qquicktext::hAlignImplicitWidth()
{
#ifdef Q_OS_MACOS
QSKIP("this test currently crashes on MacOS. See QTBUG-68047");
#endif
QQuickView view(testFileUrl("hAlignImplicitWidth.qml"));
view.setFlags(view.flags() | Qt::WindowStaysOnTopHint); // Prevent being obscured by other windows.
view.show();
view.requestActivate();
QVERIFY(QTest::qWaitForWindowExposed(&view));
QQuickText *text = view.rootObject()->findChild<QQuickText*>("textItem");
QVERIFY(text != nullptr);
// Try to check whether alignment works by checking the number of black
// pixels in the thirds of the grabbed image.
// QQuickWindow::grabWindow() scales the returned image by the devicePixelRatio of the screen.
const qreal devicePixelRatio = view.screen()->devicePixelRatio();
const int windowWidth = 220 * devicePixelRatio;
const int textWidth = qCeil(text->implicitWidth()) * devicePixelRatio;
QVERIFY2(textWidth < windowWidth, "System font too large.");
const int sectionWidth = textWidth / 3;
const int centeredSection1 = (windowWidth - textWidth) / 2;
const int centeredSection2 = centeredSection1 + sectionWidth;
const int centeredSection3 = centeredSection2 + sectionWidth;
const int centeredSection3End = centeredSection3 + sectionWidth;
{
if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
|| (QGuiApplication::platformName() == QLatin1String("minimal")))
QEXPECT_FAIL("", "Failure due to grabWindow not functional on offscreen/minimal platforms", Abort);
// Left Align
QImage image = view.grabWindow();
const int left = numberOfNonWhitePixels(centeredSection1, centeredSection2, image);
const int mid = numberOfNonWhitePixels(centeredSection2, centeredSection3, image);
const int right = numberOfNonWhitePixels(centeredSection3, centeredSection3End, image);
QVERIFY2(left > mid, msgNotGreaterThan(left, mid).constData());
QVERIFY2(mid > right, msgNotGreaterThan(mid, right).constData());
}
{
// HCenter Align
text->setHAlign(QQuickText::AlignHCenter);
text->setText("Reset"); // set dummy string to force relayout once original text is set again
text->setText("AA\nBBBBBBB\nCCCCCCCCCCCCCCCC");
QImage image = view.grabWindow();
const int left = numberOfNonWhitePixels(centeredSection1, centeredSection2, image);
const int mid = numberOfNonWhitePixels(centeredSection2, centeredSection3, image);
const int right = numberOfNonWhitePixels(centeredSection3, centeredSection3End, image);
QVERIFY2(left < mid, msgNotLessThan(left, mid).constData());
QVERIFY2(mid > right, msgNotGreaterThan(mid, right).constData());
}
{
// Right Align
text->setHAlign(QQuickText::AlignRight);
text->setText("Reset"); // set dummy string to force relayout once original text is set again
text->setText("AA\nBBBBBBB\nCCCCCCCCCCCCCCCC");
QImage image = view.grabWindow();
const int left = numberOfNonWhitePixels(centeredSection1, centeredSection2, image);
const int mid = numberOfNonWhitePixels(centeredSection2, centeredSection3, image);
const int right = numberOfNonWhitePixels(centeredSection3, centeredSection3End, image);
QVERIFY2(left < mid, msgNotLessThan(left, mid).constData());
QVERIFY2(mid < right, msgNotLessThan(mid, right).constData());
}
}
void tst_qquicktext::verticalAlignment()
{
//test one align each, and then test if two align fails.
for (int i = 0; i < standard.size(); i++)
{
for (int j=0; j < verticalAlignmentmentStrings.size(); j++)
{
QString componentStr = "import QtQuick 2.0\nText { verticalAlignment: \"" + verticalAlignmentmentStrings.at(j) + "\"; text: \"" + standard.at(i) + "\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE((int)textObject->vAlign(), (int)verticalAlignmentments.at(j));
delete textObject;
}
}
for (int i = 0; i < richText.size(); i++)
{
for (int j=0; j < verticalAlignmentmentStrings.size(); j++)
{
QString componentStr = "import QtQuick 2.0\nText { verticalAlignment: \"" + verticalAlignmentmentStrings.at(j) + "\"; text: \"" + richText.at(i) + "\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE((int)textObject->vAlign(), (int)verticalAlignmentments.at(j));
delete textObject;
}
}
}
void tst_qquicktext::font()
{
//test size, then bold, then italic, then family
{
QString componentStr = "import QtQuick 2.0\nText { font.pointSize: 40; text: \"Hello World\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE(textObject->font().pointSize(), 40);
QCOMPARE(textObject->font().bold(), false);
QCOMPARE(textObject->font().italic(), false);
delete textObject;
}
{
QString componentStr = "import QtQuick 2.0\nText { font.pixelSize: 40; text: \"Hello World\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE(textObject->font().pixelSize(), 40);
QCOMPARE(textObject->font().bold(), false);
QCOMPARE(textObject->font().italic(), false);
delete textObject;
}
{
QString componentStr = "import QtQuick 2.0\nText { font.bold: true; text: \"Hello World\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE(textObject->font().bold(), true);
QCOMPARE(textObject->font().italic(), false);
delete textObject;
}
{
QString componentStr = "import QtQuick 2.0\nText { font.italic: true; text: \"Hello World\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE(textObject->font().italic(), true);
QCOMPARE(textObject->font().bold(), false);
delete textObject;
}
{
QString componentStr = "import QtQuick 2.0\nText { font.family: \"Helvetica\"; text: \"Hello World\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE(textObject->font().family(), QString("Helvetica"));
QCOMPARE(textObject->font().bold(), false);
QCOMPARE(textObject->font().italic(), false);
delete textObject;
}
{
QString componentStr = "import QtQuick 2.0\nText { font.family: \"\"; text: \"Hello World\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE(textObject->font().family(), QString(""));
delete textObject;
}
}
void tst_qquicktext::style()
{
//test style
for (int i = 0; i < styles.size(); i++)
{
QString componentStr = "import QtQuick 2.0\nText { style: \"" + styleStrings.at(i) + "\"; styleColor: \"white\"; text: \"Hello World\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE((int)textObject->style(), (int)styles.at(i));
QCOMPARE(textObject->styleColor(), QColor("white"));
delete textObject;
}
QString componentStr = "import QtQuick 2.0\nText { text: \"Hello World\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QRectF brPre = textObject->boundingRect();
textObject->setStyle(QQuickText::Outline);
QRectF brPost = textObject->boundingRect();
QVERIFY(brPre.width() < brPost.width());
QVERIFY(brPre.height() < brPost.height());
delete textObject;
}
void tst_qquicktext::color()
{
//test style
for (int i = 0; i < colorStrings.size(); i++)
{
QString componentStr = "import QtQuick 2.0\nText { color: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE(textObject->color(), QColor(colorStrings.at(i)));
QCOMPARE(textObject->styleColor(), QColor("black"));
QCOMPARE(textObject->linkColor(), QColor("blue"));
delete textObject;
}
for (int i = 0; i < colorStrings.size(); i++)
{
QString componentStr = "import QtQuick 2.0\nText { styleColor: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE(textObject->styleColor(), QColor(colorStrings.at(i)));
// default color to black?
QCOMPARE(textObject->color(), QColor("black"));
QCOMPARE(textObject->linkColor(), QColor("blue"));
QSignalSpy colorSpy(textObject, SIGNAL(colorChanged()));
QSignalSpy linkColorSpy(textObject, SIGNAL(linkColorChanged()));
textObject->setColor(QColor("white"));
QCOMPARE(textObject->color(), QColor("white"));
QCOMPARE(colorSpy.count(), 1);
textObject->setLinkColor(QColor("black"));
QCOMPARE(textObject->linkColor(), QColor("black"));
QCOMPARE(linkColorSpy.count(), 1);
textObject->setColor(QColor("white"));
QCOMPARE(colorSpy.count(), 1);
textObject->setLinkColor(QColor("black"));
QCOMPARE(linkColorSpy.count(), 1);
textObject->setColor(QColor("black"));
QCOMPARE(textObject->color(), QColor("black"));
QCOMPARE(colorSpy.count(), 2);
textObject->setLinkColor(QColor("blue"));
QCOMPARE(textObject->linkColor(), QColor("blue"));
QCOMPARE(linkColorSpy.count(), 2);
delete textObject;
}
for (int i = 0; i < colorStrings.size(); i++)
{
QString componentStr = "import QtQuick 2.0\nText { linkColor: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE(textObject->styleColor(), QColor("black"));
QCOMPARE(textObject->color(), QColor("black"));
QCOMPARE(textObject->linkColor(), QColor(colorStrings.at(i)));
delete textObject;
}
for (int i = 0; i < colorStrings.size(); i++)
{
for (int j = 0; j < colorStrings.size(); j++)
{
QString componentStr = "import QtQuick 2.0\nText { "
"color: \"" + colorStrings.at(i) + "\"; "
"styleColor: \"" + colorStrings.at(j) + "\"; "
"linkColor: \"" + colorStrings.at(j) + "\"; "
"text: \"Hello World\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE(textObject->color(), QColor(colorStrings.at(i)));
QCOMPARE(textObject->styleColor(), QColor(colorStrings.at(j)));
QCOMPARE(textObject->linkColor(), QColor(colorStrings.at(j)));
delete textObject;
}
}
{
QString colorStr = "#AA001234";
QColor testColor("#001234");
testColor.setAlpha(170);
QString componentStr = "import QtQuick 2.0\nText { color: \"" + colorStr + "\"; text: \"Hello World\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE(textObject->color(), testColor);
delete textObject;
} {
QString colorStr = "#001234";
QColor testColor(colorStr);
QString componentStr = "import QtQuick 2.0\nText { color: \"" + colorStr + "\"; text: \"Hello World\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QScopedPointer<QObject> object(textComponent.create());
QQuickText *textObject = qobject_cast<QQuickText*>(object.data());
QSignalSpy spy(textObject, SIGNAL(colorChanged()));
QCOMPARE(textObject->color(), testColor);
textObject->setColor(testColor);
QCOMPARE(textObject->color(), testColor);
QCOMPARE(spy.count(), 0);
testColor = QColor("black");
textObject->setColor(testColor);
QCOMPARE(textObject->color(), testColor);
QCOMPARE(spy.count(), 1);
} {
QString colorStr = "#001234";
QColor testColor(colorStr);
QString componentStr = "import QtQuick 2.0\nText { styleColor: \"" + colorStr + "\"; text: \"Hello World\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QScopedPointer<QObject> object(textComponent.create());
QQuickText *textObject = qobject_cast<QQuickText*>(object.data());
QSignalSpy spy(textObject, SIGNAL(styleColorChanged()));
QCOMPARE(textObject->styleColor(), testColor);
textObject->setStyleColor(testColor);
QCOMPARE(textObject->styleColor(), testColor);
QCOMPARE(spy.count(), 0);
testColor = QColor("black");
textObject->setStyleColor(testColor);
QCOMPARE(textObject->styleColor(), testColor);
QCOMPARE(spy.count(), 1);
} {
QString colorStr = "#001234";
QColor testColor(colorStr);
QString componentStr = "import QtQuick 2.0\nText { linkColor: \"" + colorStr + "\"; text: \"Hello World\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QScopedPointer<QObject> object(textComponent.create());
QQuickText *textObject = qobject_cast<QQuickText*>(object.data());
QSignalSpy spy(textObject, SIGNAL(linkColorChanged()));
QCOMPARE(textObject->linkColor(), testColor);
textObject->setLinkColor(testColor);
QCOMPARE(textObject->linkColor(), testColor);
QCOMPARE(spy.count(), 0);
testColor = QColor("black");
textObject->setLinkColor(testColor);
QCOMPARE(textObject->linkColor(), testColor);
QCOMPARE(spy.count(), 1);
}
}
void tst_qquicktext::smooth()
{
for (int i = 0; i < standard.size(); i++)
{
{
QString componentStr = "import QtQuick 2.0\nText { smooth: false; text: \"" + standard.at(i) + "\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE(textObject->smooth(), false);
delete textObject;
}
{
QString componentStr = "import QtQuick 2.0\nText { text: \"" + standard.at(i) + "\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE(textObject->smooth(), true);
delete textObject;
}
}
for (int i = 0; i < richText.size(); i++)
{
{
QString componentStr = "import QtQuick 2.0\nText { smooth: false; text: \"" + richText.at(i) + "\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE(textObject->smooth(), false);
delete textObject;
}
{
QString componentStr = "import QtQuick 2.0\nText { text: \"" + richText.at(i) + "\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QCOMPARE(textObject->smooth(), true);
delete textObject;
}
}
}
void tst_qquicktext::renderType()
{
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0\n Text {}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickText *text = qobject_cast<QQuickText *>(object.data());
QVERIFY(text);
QSignalSpy spy(text, SIGNAL(renderTypeChanged()));
QCOMPARE(text->renderType(), QQuickText::QtRendering);
text->setRenderType(QQuickText::NativeRendering);
QCOMPARE(text->renderType(), QQuickText::NativeRendering);
QCOMPARE(spy.count(), 1);
text->setRenderType(QQuickText::NativeRendering);
QCOMPARE(spy.count(), 1);
text->setRenderType(QQuickText::QtRendering);
QCOMPARE(text->renderType(), QQuickText::QtRendering);
QCOMPARE(spy.count(), 2);
}
void tst_qquicktext::antialiasing()
{
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0\n Text {}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickText *text = qobject_cast<QQuickText *>(object.data());
QVERIFY(text);
QSignalSpy spy(text, SIGNAL(antialiasingChanged(bool)));
QCOMPARE(text->antialiasing(), true);
text->setAntialiasing(false);
QCOMPARE(text->antialiasing(), false);
QCOMPARE(spy.count(), 1);
text->setAntialiasing(false);
QCOMPARE(spy.count(), 1);
text->resetAntialiasing();
QCOMPARE(text->antialiasing(), true);
QCOMPARE(spy.count(), 2);
// QTBUG-39047
component.setData("import QtQuick 2.0\n Text { antialiasing: true }", QUrl());
object.reset(component.create());
text = qobject_cast<QQuickText *>(object.data());
QVERIFY(text);
QCOMPARE(text->antialiasing(), true);
}
void tst_qquicktext::weight()
{
{
QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE((int)textObject->font().weight(), (int)QQuickFontValueType::Normal);
delete textObject;
}
{
QString componentStr = "import QtQuick 2.0\nText { font.weight: \"Bold\"; text: \"Hello world!\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE((int)textObject->font().weight(), (int)QQuickFontValueType::Bold);
delete textObject;
}
}
void tst_qquicktext::underline()
{
QQuickView view(testFileUrl("underline.qml"));
view.show();
view.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&view));
QQuickText *textObject = view.rootObject()->findChild<QQuickText*>("myText");
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->font().overline(), false);
QCOMPARE(textObject->font().underline(), true);
QCOMPARE(textObject->font().strikeOut(), false);
}
void tst_qquicktext::overline()
{
QQuickView view(testFileUrl("overline.qml"));
view.show();
view.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&view));
QQuickText *textObject = view.rootObject()->findChild<QQuickText*>("myText");
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->font().overline(), true);
QCOMPARE(textObject->font().underline(), false);
QCOMPARE(textObject->font().strikeOut(), false);
}
void tst_qquicktext::strikeout()
{
QQuickView view(testFileUrl("strikeout.qml"));
view.show();
view.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&view));
QQuickText *textObject = view.rootObject()->findChild<QQuickText*>("myText");
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->font().overline(), false);
QCOMPARE(textObject->font().underline(), false);
QCOMPARE(textObject->font().strikeOut(), true);
}
void tst_qquicktext::capitalization()
{
{
QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE((int)textObject->font().capitalization(), (int)QQuickFontValueType::MixedCase);
delete textObject;
}
{
QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.capitalization: \"AllUppercase\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE((int)textObject->font().capitalization(), (int)QQuickFontValueType::AllUppercase);
delete textObject;
}
{
QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.capitalization: \"AllLowercase\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE((int)textObject->font().capitalization(), (int)QQuickFontValueType::AllLowercase);
delete textObject;
}
{
QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.capitalization: \"SmallCaps\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE((int)textObject->font().capitalization(), (int)QQuickFontValueType::SmallCaps);
delete textObject;
}
{
QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.capitalization: \"Capitalize\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE((int)textObject->font().capitalization(), (int)QQuickFontValueType::Capitalize);
delete textObject;
}
}
void tst_qquicktext::letterSpacing()
{
{
QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->font().letterSpacing(), 0.0);
delete textObject;
}
{
QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.letterSpacing: -2 }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->font().letterSpacing(), -2.);
delete textObject;
}
{
QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.letterSpacing: 3 }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->font().letterSpacing(), 3.);
delete textObject;
}
}
void tst_qquicktext::wordSpacing()
{
{
QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->font().wordSpacing(), 0.0);
delete textObject;
}
{
QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.wordSpacing: -50 }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->font().wordSpacing(), -50.);
delete textObject;
}
{
QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.wordSpacing: 200 }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->font().wordSpacing(), 200.);
delete textObject;
}
}
class EventSender : public QQuickItem
{
public:
void sendEvent(QEvent *event) {
switch (event->type()) {
case QEvent::MouseButtonPress:
mousePressEvent(static_cast<QMouseEvent *>(event));
break;
case QEvent::MouseButtonRelease:
mouseReleaseEvent(static_cast<QMouseEvent *>(event));
break;
case QEvent::MouseMove:
mouseMoveEvent(static_cast<QMouseEvent *>(event));
break;
case QEvent::HoverEnter:
hoverEnterEvent(static_cast<QHoverEvent *>(event));
break;
case QEvent::HoverLeave:
hoverLeaveEvent(static_cast<QHoverEvent *>(event));
break;
case QEvent::HoverMove:
hoverMoveEvent(static_cast<QHoverEvent *>(event));
break;
default:
qWarning() << "Trying to send unsupported event type";
break;
}
}
};
class LinkTest : public QObject
{
Q_OBJECT
public:
LinkTest() {}
QString clickedLink;
QString hoveredLink;
public slots:
void linkClicked(QString l) { clickedLink = l; }
void linkHovered(QString l) { hoveredLink = l; }
};
class TextMetrics
{
public:
TextMetrics(const QString &text, Qt::TextElideMode elideMode = Qt::ElideNone)
{
QString adjustedText = text;
adjustedText.replace(QLatin1Char('\n'), QChar(QChar::LineSeparator));
if (elideMode == Qt::ElideLeft)
adjustedText = QChar(0x2026) + adjustedText;
else if (elideMode == Qt::ElideRight)
adjustedText = adjustedText + QChar(0x2026);
layout.setText(adjustedText);
QTextOption option;
option.setUseDesignMetrics(true);
layout.setTextOption(option);
layout.beginLayout();
qreal height = 0;
QTextLine line = layout.createLine();
while (line.isValid()) {
line.setLineWidth(FLT_MAX);
line.setPosition(QPointF(0, height));
height += line.height();
line = layout.createLine();
}
layout.endLayout();
}
qreal width() const { return layout.maximumWidth(); }
QRectF characterRectangle(
int position,
int hAlign = Qt::AlignLeft,
int vAlign = Qt::AlignTop,
const QSizeF &bounds = QSizeF(240, 320)) const
{
qreal dy = 0;
switch (vAlign) {
case Qt::AlignBottom:
dy = bounds.height() - layout.boundingRect().height();
break;
case Qt::AlignVCenter:
dy = (bounds.height() - layout.boundingRect().height()) / 2;
break;
default:
break;
}
for (int i = 0; i < layout.lineCount(); ++i) {
QTextLine line = layout.lineAt(i);
if (position >= line.textStart() + line.textLength())
continue;
qreal dx = 0;
switch (hAlign) {
case Qt::AlignRight:
dx = bounds.width() - line.naturalTextWidth();
break;
case Qt::AlignHCenter:
dx = (bounds.width() - line.naturalTextWidth()) / 2;
break;
default:
break;
}
QRectF rect;
rect.setLeft(dx + line.cursorToX(position, QTextLine::Leading));
rect.setRight(dx + line.cursorToX(position, QTextLine::Trailing));
rect.setTop(dy + line.y());
rect.setBottom(dy + line.y() + line.height());
return rect;
}
return QRectF();
}
QTextLayout layout;
};
typedef QVector<QPointF> PointVector;
Q_DECLARE_METATYPE(PointVector);
void tst_qquicktext::linkInteraction_data()
{
QTest::addColumn<QString>("text");
QTest::addColumn<qreal>("width");
QTest::addColumn<QString>("bindings");
QTest::addColumn<PointVector>("mousePositions");
QTest::addColumn<QString>("clickedLink");
QTest::addColumn<QString>("hoverEnterLink");
QTest::addColumn<QString>("hoverMoveLink");
const QString singleLineText = "this text has a <a href=\\\"http://qt-project.org/single\\\">link</a> in it";
const QString singleLineLink = "http://qt-project.org/single";
const QString multipleLineText = "this text<br/>has <a href=\\\"http://qt-project.org/multiple\\\">multiple<br/>lines</a> in it";
const QString multipleLineLink = "http://qt-project.org/multiple";
const QString nestedText = "this text has a <a href=\\\"http://qt-project.org/outer\\\">nested <a href=\\\"http://qt-project.org/inner\\\">link</a> in it</a>";
const QString outerLink = "http://qt-project.org/outer";
const QString innerLink = "http://qt-project.org/inner";
{
const TextMetrics metrics("this text has a link in it");
QTest::newRow("click on link")
<< singleLineText << 240.
<< ""
<< (PointVector() << metrics.characterRectangle(18).center())
<< singleLineLink
<< singleLineLink << singleLineLink;
QTest::newRow("click on text")
<< singleLineText << 240.
<< ""
<< (PointVector() << metrics.characterRectangle(13).center())
<< QString()
<< QString() << QString();
QTest::newRow("drag within link")
<< singleLineText << 240.
<< ""
<< (PointVector()
<< metrics.characterRectangle(17).center()
<< metrics.characterRectangle(19).center())
<< singleLineLink
<< singleLineLink << singleLineLink;
QTest::newRow("drag away from link")
<< singleLineText << 240.
<< ""
<< (PointVector()
<< metrics.characterRectangle(18).center()
<< metrics.characterRectangle(13).center())
<< QString()
<< singleLineLink << QString();
QTest::newRow("drag on to link")
<< singleLineText << 240.
<< ""
<< (PointVector()
<< metrics.characterRectangle(13).center()
<< metrics.characterRectangle(18).center())
<< QString()
<< QString() << singleLineLink;
QTest::newRow("click on bottom right aligned link")
<< singleLineText << 240.
<< "horizontalAlignment: Text.AlignRight; verticalAlignment: Text.AlignBottom"
<< (PointVector() << metrics.characterRectangle(18, Qt::AlignRight, Qt::AlignBottom).center())
<< singleLineLink
<< singleLineLink << singleLineLink;
QTest::newRow("click on mirrored link")
<< singleLineText << 240.
<< "horizontalAlignment: Text.AlignLeft; LayoutMirroring.enabled: true"
<< (PointVector() << metrics.characterRectangle(18, Qt::AlignRight, Qt::AlignTop).center())
<< singleLineLink
<< singleLineLink << singleLineLink;
QTest::newRow("click on center aligned link")
<< singleLineText << 240.
<< "horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter"
<< (PointVector() << metrics.characterRectangle(18, Qt::AlignHCenter, Qt::AlignVCenter).center())
<< singleLineLink
<< singleLineLink << singleLineLink;
QTest::newRow("click on rich text link")
<< singleLineText << 240.
<< "textFormat: Text.RichText"
<< (PointVector() << metrics.characterRectangle(18).center())
<< singleLineLink
<< singleLineLink << singleLineLink;
QTest::newRow("click on rich text")
<< singleLineText << 240.
<< "textFormat: Text.RichText"
<< (PointVector() << metrics.characterRectangle(13).center())
<< QString()
<< QString() << QString();
QTest::newRow("click on bottom right aligned rich text link")
<< singleLineText << 240.
<< "textFormat: Text.RichText; horizontalAlignment: Text.AlignRight; verticalAlignment: Text.AlignBottom"
<< (PointVector() << metrics.characterRectangle(18, Qt::AlignRight, Qt::AlignBottom).center())
<< singleLineLink
<< singleLineLink << singleLineLink;
QTest::newRow("click on center aligned rich text link")
<< singleLineText << 240.
<< "textFormat: Text.RichText; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter"
<< (PointVector() << metrics.characterRectangle(18, Qt::AlignHCenter, Qt::AlignVCenter).center())
<< singleLineLink
<< singleLineLink << singleLineLink;
} {
const TextMetrics metrics("this text has a li", Qt::ElideRight);
QTest::newRow("click on right elided link")
<< singleLineText << metrics.width() + 2
<< "elide: Text.ElideRight"
<< (PointVector() << metrics.characterRectangle(17).center())
<< singleLineLink
<< singleLineLink << singleLineLink;
} {
const TextMetrics metrics("ink in it", Qt::ElideLeft);
QTest::newRow("click on left elided link")
<< singleLineText << metrics.width() + 2
<< "elide: Text.ElideLeft"
<< (PointVector() << metrics.characterRectangle(2).center())
<< singleLineLink
<< singleLineLink << singleLineLink;
} {
const TextMetrics metrics("this text\nhas multiple\nlines in it");
QTest::newRow("click on second line")
<< multipleLineText << 240.
<< ""
<< (PointVector() << metrics.characterRectangle(18).center())
<< multipleLineLink
<< multipleLineLink << multipleLineLink;
QTest::newRow("click on third line")
<< multipleLineText << 240.
<< ""
<< (PointVector() << metrics.characterRectangle(25).center())
<< multipleLineLink
<< multipleLineLink << multipleLineLink;
QTest::newRow("drag from second line to third")
<< multipleLineText << 240.
<< ""
<< (PointVector()
<< metrics.characterRectangle(18).center()
<< metrics.characterRectangle(25).center())
<< multipleLineLink
<< multipleLineLink << multipleLineLink;
QTest::newRow("click on rich text second line")
<< multipleLineText << 240.
<< "textFormat: Text.RichText"
<< (PointVector() << metrics.characterRectangle(18).center())
<< multipleLineLink
<< multipleLineLink << multipleLineLink;
QTest::newRow("click on rich text third line")
<< multipleLineText << 240.
<< "textFormat: Text.RichText"
<< (PointVector() << metrics.characterRectangle(25).center())
<< multipleLineLink
<< multipleLineLink << multipleLineLink;
QTest::newRow("drag rich text from second line to third")
<< multipleLineText << 240.
<< "textFormat: Text.RichText"
<< (PointVector()
<< metrics.characterRectangle(18).center()
<< metrics.characterRectangle(25).center())
<< multipleLineLink
<< multipleLineLink << multipleLineLink;
} {
const TextMetrics metrics("this text has a nested link in it");
QTest::newRow("click on left outer link")
<< nestedText << 240.
<< ""
<< (PointVector() << metrics.characterRectangle(22).center())
<< outerLink
<< outerLink << outerLink;
QTest::newRow("click on right outer link")
<< nestedText << 240.
<< ""
<< (PointVector() << metrics.characterRectangle(27).center())
<< outerLink
<< outerLink << outerLink;
QTest::newRow("click on inner link left")
<< nestedText << 240.
<< ""
<< (PointVector() << metrics.characterRectangle(23).center())
<< innerLink
<< innerLink << innerLink;
QTest::newRow("click on inner link right")
<< nestedText << 240.
<< ""
<< (PointVector() << metrics.characterRectangle(26).center())
<< innerLink
<< innerLink << innerLink;
QTest::newRow("drag from inner to outer link")
<< nestedText << 240.
<< ""
<< (PointVector()
<< metrics.characterRectangle(25).center()
<< metrics.characterRectangle(30).center())
<< QString()
<< innerLink << outerLink;
QTest::newRow("drag from outer to inner link")
<< nestedText << 240.
<< ""
<< (PointVector()
<< metrics.characterRectangle(30).center()
<< metrics.characterRectangle(25).center())
<< QString()
<< outerLink << innerLink;
QTest::newRow("click on left outer rich text link")
<< nestedText << 240.
<< "textFormat: Text.RichText"
<< (PointVector() << metrics.characterRectangle(22).center())
<< outerLink
<< outerLink << outerLink;
QTest::newRow("click on right outer rich text link")
<< nestedText << 240.
<< "textFormat: Text.RichText"
<< (PointVector() << metrics.characterRectangle(27).center())
<< outerLink
<< outerLink << outerLink;
QTest::newRow("click on inner rich text link left")
<< nestedText << 240.
<< "textFormat: Text.RichText"
<< (PointVector() << metrics.characterRectangle(23).center())
<< innerLink
<< innerLink << innerLink;
QTest::newRow("click on inner rich text link right")
<< nestedText << 240.
<< "textFormat: Text.RichText"
<< (PointVector() << metrics.characterRectangle(26).center())
<< innerLink
<< innerLink << innerLink;
QTest::newRow("drag from inner to outer rich text link")
<< nestedText << 240.
<< "textFormat: Text.RichText"
<< (PointVector()
<< metrics.characterRectangle(25).center()
<< metrics.characterRectangle(30).center())
<< QString()
<< innerLink << outerLink;
QTest::newRow("drag from outer to inner rich text link")
<< nestedText << 240.
<< "textFormat: Text.RichText"
<< (PointVector()
<< metrics.characterRectangle(30).center()
<< metrics.characterRectangle(25).center())
<< QString()
<< outerLink << innerLink;
}
}
void tst_qquicktext::linkInteraction()
{
QFETCH(QString, text);
QFETCH(qreal, width);
QFETCH(QString, bindings);
QFETCH(PointVector, mousePositions);
QFETCH(QString, clickedLink);
QFETCH(QString, hoverEnterLink);
QFETCH(QString, hoverMoveLink);
QString componentStr =
"import QtQuick 2.2\nText {\n"
"width: " + QString::number(width) + "\n"
"height: 320\n"
"text: \"" + text + "\"\n"
"" + bindings + "\n"
"}";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
LinkTest test;
QObject::connect(textObject, SIGNAL(linkActivated(QString)), &test, SLOT(linkClicked(QString)));
QObject::connect(textObject, SIGNAL(linkHovered(QString)), &test, SLOT(linkHovered(QString)));
QVERIFY(mousePositions.count() > 0);
QPointF mousePosition = mousePositions.first();
{
QHoverEvent he(QEvent::HoverEnter, mousePosition, QPointF());
static_cast<EventSender*>(static_cast<QQuickItem*>(textObject))->sendEvent(&he);
QMouseEvent me(QEvent::MouseButtonPress, mousePosition, Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
static_cast<EventSender*>(static_cast<QQuickItem*>(textObject))->sendEvent(&me);
}
QCOMPARE(test.hoveredLink, hoverEnterLink);
QCOMPARE(textObject->hoveredLink(), hoverEnterLink);
QCOMPARE(textObject->linkAt(mousePosition.x(), mousePosition.y()), hoverEnterLink);
for (int i = 1; i < mousePositions.count(); ++i) {
mousePosition = mousePositions.at(i);
QHoverEvent he(QEvent::HoverMove, mousePosition, QPointF());
static_cast<EventSender*>(static_cast<QQuickItem*>(textObject))->sendEvent(&he);
QMouseEvent me(QEvent::MouseMove, mousePosition, Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
static_cast<EventSender*>(static_cast<QQuickItem*>(textObject))->sendEvent(&me);
}
QCOMPARE(test.hoveredLink, hoverMoveLink);
QCOMPARE(textObject->hoveredLink(), hoverMoveLink);
QCOMPARE(textObject->linkAt(mousePosition.x(), mousePosition.y()), hoverMoveLink);
{
QHoverEvent he(QEvent::HoverLeave, mousePosition, QPointF());
static_cast<EventSender*>(static_cast<QQuickItem*>(textObject))->sendEvent(&he);
QMouseEvent me(QEvent::MouseButtonRelease, mousePosition, Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
static_cast<EventSender*>(static_cast<QQuickItem*>(textObject))->sendEvent(&me);
}
QCOMPARE(test.clickedLink, clickedLink);
QCOMPARE(test.hoveredLink, QString());
QCOMPARE(textObject->hoveredLink(), QString());
QCOMPARE(textObject->linkAt(-1, -1), QString());
delete textObject;
}
void tst_qquicktext::baseUrl()
{
QUrl localUrl("file:///tests/text.qml");
QUrl remoteUrl("http://www.qt-project.org/test.qml");
QQmlComponent textComponent(&engine);
textComponent.setData("import QtQuick 2.0\n Text {}", localUrl);
QQuickText *textObject = qobject_cast<QQuickText *>(textComponent.create());
QCOMPARE(textObject->baseUrl(), localUrl);
QSignalSpy spy(textObject, SIGNAL(baseUrlChanged()));
textObject->setBaseUrl(localUrl);
QCOMPARE(textObject->baseUrl(), localUrl);
QCOMPARE(spy.count(), 0);
textObject->setBaseUrl(remoteUrl);
QCOMPARE(textObject->baseUrl(), remoteUrl);
QCOMPARE(spy.count(), 1);
textObject->resetBaseUrl();
QCOMPARE(textObject->baseUrl(), localUrl);
QCOMPARE(spy.count(), 2);
}
void tst_qquicktext::embeddedImages_data()
{
QTest::addColumn<QUrl>("qmlfile");
QTest::addColumn<QString>("error");
QTest::newRow("local") << testFileUrl("embeddedImagesLocal.qml") << "";
QTest::newRow("local-error") << testFileUrl("embeddedImagesLocalError.qml")
<< testFileUrl("embeddedImagesLocalError.qml").toString()+":3:1: QML Text: Cannot open: " + testFileUrl("http/notexists.png").toString();
QTest::newRow("local") << testFileUrl("embeddedImagesLocalRelative.qml") << "";
QTest::newRow("remote") << testFileUrl("embeddedImagesRemote.qml") << "";
QTest::newRow("remote-error") << testFileUrl("embeddedImagesRemoteError.qml")
<< testFileUrl("embeddedImagesRemoteError.qml").toString()+":3:1: QML Text: Error transferring {{ServerBaseUrl}}/notexists.png - server replied: Not found";
QTest::newRow("remote-relative") << testFileUrl("embeddedImagesRemoteRelative.qml") << "";
}
void tst_qquicktext::embeddedImages()
{
// Tests QTBUG-9900
QFETCH(QUrl, qmlfile);
QFETCH(QString, error);
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
if (qstrcmp(QTest::currentDataTag(), "remote") == 0
|| qstrcmp(QTest::currentDataTag(), "remote-error") == 0
|| qstrcmp(QTest::currentDataTag(), "remote-relative") == 0) {
QSKIP("Remote tests cause occasional hangs in the CI system -- QTBUG-45655");
}
#endif
TestHTTPServer server;
QVERIFY2(server.listen(), qPrintable(server.errorString()));
server.serveDirectory(testFile("http"));
error.replace(QStringLiteral("{{ServerBaseUrl}}"), server.baseUrl().toString());
if (!error.isEmpty())
QTest::ignoreMessage(QtWarningMsg, error.toLatin1());
QQuickView *view = new QQuickView;
view->rootContext()->setContextProperty(QStringLiteral("serverBaseUrl"), server.baseUrl());
view->setSource(qmlfile);
view->show();
view->requestActivate();
QVERIFY(QTest::qWaitForWindowActive(view));
QQuickText *textObject = qobject_cast<QQuickText*>(view->rootObject());
QVERIFY(textObject != nullptr);
QTRY_COMPARE(textObject->resourcesLoading(), 0);
QPixmap pm(testFile("http/exists.png"));
if (error.isEmpty()) {
QCOMPARE(textObject->width(), double(pm.width()));
QCOMPARE(textObject->height(), double(pm.height()));
} else {
QVERIFY(16 != pm.width()); // check test is effective
QCOMPARE(textObject->width(), 16.0); // default size of QTextDocument broken image icon
QCOMPARE(textObject->height(), 16.0);
}
delete view;
}
void tst_qquicktext::lineCount()
{
QScopedPointer<QQuickView> window(createView(testFile("lineCount.qml")));
QQuickText *myText = window->rootObject()->findChild<QQuickText*>("myText");
QVERIFY(myText != nullptr);
QVERIFY(myText->lineCount() > 1);
QVERIFY(!myText->truncated());
QCOMPARE(myText->maximumLineCount(), INT_MAX);
myText->setMaximumLineCount(2);
QCOMPARE(myText->lineCount(), 2);
QCOMPARE(myText->truncated(), true);
QCOMPARE(myText->maximumLineCount(), 2);
myText->resetMaximumLineCount();
QCOMPARE(myText->maximumLineCount(), INT_MAX);
QCOMPARE(myText->truncated(), false);
myText->setElideMode(QQuickText::ElideRight);
myText->setMaximumLineCount(2);
QCOMPARE(myText->lineCount(), 2);
QCOMPARE(myText->truncated(), true);
QCOMPARE(myText->maximumLineCount(), 2);
}
void tst_qquicktext::lineHeight()
{
QScopedPointer<QQuickView> window(createView(testFile("lineHeight.qml")));
QQuickText *myText = window->rootObject()->findChild<QQuickText*>("myText");
QVERIFY(myText != nullptr);
QCOMPARE(myText->lineHeight(), qreal(1));
QCOMPARE(myText->lineHeightMode(), QQuickText::ProportionalHeight);
qreal h = myText->height();
myText->setLineHeight(1.5);
QCOMPARE(myText->height(), qreal(qCeil(h)) * 1.5);
myText->setLineHeightMode(QQuickText::FixedHeight);
myText->setLineHeight(20);
QCOMPARE(myText->height(), myText->lineCount() * 20.0);
myText->setText("Lorem ipsum sit <b>amet</b>, consectetur adipiscing elit. Integer felis nisl, varius in pretium nec, venenatis non erat. Proin lobortis interdum dictum.");
myText->setLineHeightMode(QQuickText::ProportionalHeight);
myText->setLineHeight(1.0);
qreal h2 = myText->height();
myText->setLineHeight(2.0);
QVERIFY(myText->height() == h2 * 2.0);
myText->setLineHeightMode(QQuickText::FixedHeight);
myText->setLineHeight(10);
QCOMPARE(myText->height(), myText->lineCount() * 10.0);
}
void tst_qquicktext::implicitSize_data()
{
QTest::addColumn<QString>("text");
QTest::addColumn<QString>("width");
QTest::addColumn<QString>("wrap");
QTest::addColumn<QString>("elide");
QTest::addColumn<QString>("format");
QTest::newRow("plain") << "The quick red fox jumped over the lazy brown dog" << "50" << "Text.NoWrap" << "Text.ElideNone" << "Text.PlainText";
QTest::newRow("richtext") << "<b>The quick red fox jumped over the lazy brown dog</b>" <<" 50" << "Text.NoWrap" << "Text.ElideNone" << "Text.RichText";
QTest::newRow("styledtext") << "<b>The quick red fox jumped over the lazy brown dog</b>" <<" 50" << "Text.NoWrap" << "Text.ElideNone" << "Text.StyledText";
QTest::newRow("plain, 0 width") << "The quick red fox jumped over the lazy brown dog" << "0" << "Text.NoWrap" << "Text.ElideNone" << "Text.PlainText";
QTest::newRow("plain, elide") << "The quick red fox jumped over the lazy brown dog" << "50" << "Text.NoWrap" << "Text.ElideRight" << "Text.PlainText";
QTest::newRow("plain, 0 width, elide") << "The quick red fox jumped over the lazy brown dog" << "0" << "Text.NoWrap" << "Text.ElideRight" << "Text.PlainText";
QTest::newRow("richtext, 0 width") << "<b>The quick red fox jumped over the lazy brown dog</b>" <<" 0" << "Text.NoWrap" << "Text.ElideNone" << "Text.RichText";
QTest::newRow("styledtext, 0 width") << "<b>The quick red fox jumped over the lazy brown dog</b>" <<" 0" << "Text.NoWrap" << "Text.ElideNone" << "Text.StyledText";
QTest::newRow("plain_wrap") << "The quick red fox jumped over the lazy brown dog" << "50" << "Text.Wrap" << "Text.ElideNone" << "Text.PlainText";
QTest::newRow("richtext_wrap") << "<b>The quick red fox jumped over the lazy brown dog</b>" << "50" << "Text.Wrap" << "Text.ElideNone" << "Text.RichText";
QTest::newRow("styledtext_wrap") << "<b>The quick red fox jumped over the lazy brown dog</b>" << "50" << "Text.Wrap" << "Text.ElideNone" << "Text.StyledText";
QTest::newRow("plain_wrap, 0 width") << "The quick red fox jumped over the lazy brown dog" << "0" << "Text.Wrap" << "Text.ElideNone" << "Text.PlainText";
QTest::newRow("plain_wrap, elide") << "The quick red fox jumped over the lazy brown dog" << "50" << "Text.Wrap" << "Text.ElideRight" << "Text.PlainText";
QTest::newRow("plain_wrap, 0 width, elide") << "The quick red fox jumped over the lazy brown dog" << "0" << "Text.Wrap" << "Text.ElideRight" << "Text.PlainText";
QTest::newRow("richtext_wrap, 0 width") << "<b>The quick red fox jumped over the lazy brown dog</b>" << "0" << "Text.Wrap" << "Text.ElideNone" << "Text.RichText";
QTest::newRow("styledtext_wrap, 0 width") << "<b>The quick red fox jumped over the lazy brown dog</b>" << "0" << "Text.Wrap" << "Text.ElideNone" << "Text.StyledText";
}
void tst_qquicktext::implicitSize()
{
QFETCH(QString, text);
QFETCH(QString, width);
QFETCH(QString, format);
QFETCH(QString, wrap);
QFETCH(QString, elide);
QString componentStr = "import QtQuick 2.0\nText { "
"property real iWidth: implicitWidth; "
"text: \"" + text + "\"; "
"width: " + width + "; "
"textFormat: " + format + "; "
"wrapMode: " + wrap + "; "
"elide: " + elide + "; "
"maximumLineCount: 2 }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject->width() < textObject->implicitWidth());
QCOMPARE(textObject->height(), textObject->implicitHeight());
QCOMPARE(textObject->property("iWidth").toReal(), textObject->implicitWidth());
textObject->resetWidth();
QCOMPARE(textObject->width(), textObject->implicitWidth());
QCOMPARE(textObject->height(), textObject->implicitHeight());
delete textObject;
}
void tst_qquicktext::dependentImplicitSizes()
{
QQmlComponent component(&engine, testFile("implicitSizes.qml"));
QScopedPointer<QObject> object(component.create());
QVERIFY(object.data());
QQuickText *reference = object->findChild<QQuickText *>("reference");
QQuickText *fixedWidthAndHeight = object->findChild<QQuickText *>("fixedWidthAndHeight");
QQuickText *implicitWidthFixedHeight = object->findChild<QQuickText *>("implicitWidthFixedHeight");
QQuickText *fixedWidthImplicitHeight = object->findChild<QQuickText *>("fixedWidthImplicitHeight");
QQuickText *cappedWidthAndHeight = object->findChild<QQuickText *>("cappedWidthAndHeight");
QQuickText *cappedWidthFixedHeight = object->findChild<QQuickText *>("cappedWidthFixedHeight");
QQuickText *fixedWidthCappedHeight = object->findChild<QQuickText *>("fixedWidthCappedHeight");
QVERIFY(reference);
QVERIFY(fixedWidthAndHeight);
QVERIFY(implicitWidthFixedHeight);
QVERIFY(fixedWidthImplicitHeight);
QVERIFY(cappedWidthAndHeight);
QVERIFY(cappedWidthFixedHeight);
QVERIFY(fixedWidthCappedHeight);
QCOMPARE(reference->width(), reference->implicitWidth());
QCOMPARE(reference->height(), reference->implicitHeight());
QVERIFY(fixedWidthAndHeight->width() < fixedWidthAndHeight->implicitWidth());
QVERIFY(fixedWidthAndHeight->height() < fixedWidthAndHeight->implicitHeight());
QCOMPARE(fixedWidthAndHeight->implicitWidth(), reference->implicitWidth());
QVERIFY(fixedWidthAndHeight->implicitHeight() > reference->implicitHeight());
QCOMPARE(implicitWidthFixedHeight->width(), implicitWidthFixedHeight->implicitWidth());
QVERIFY(implicitWidthFixedHeight->height() < implicitWidthFixedHeight->implicitHeight());
QCOMPARE(implicitWidthFixedHeight->implicitWidth(), reference->implicitWidth());
QCOMPARE(implicitWidthFixedHeight->implicitHeight(), reference->implicitHeight());
QVERIFY(fixedWidthImplicitHeight->width() < fixedWidthImplicitHeight->implicitWidth());
QCOMPARE(fixedWidthImplicitHeight->height(), fixedWidthImplicitHeight->implicitHeight());
QCOMPARE(fixedWidthImplicitHeight->implicitWidth(), reference->implicitWidth());
QCOMPARE(fixedWidthImplicitHeight->implicitHeight(), fixedWidthAndHeight->implicitHeight());
QVERIFY(cappedWidthAndHeight->width() < cappedWidthAndHeight->implicitWidth());
QVERIFY(cappedWidthAndHeight->height() < cappedWidthAndHeight->implicitHeight());
QCOMPARE(cappedWidthAndHeight->implicitWidth(), reference->implicitWidth());
QCOMPARE(cappedWidthAndHeight->implicitHeight(), fixedWidthAndHeight->implicitHeight());
QVERIFY(cappedWidthFixedHeight->width() < cappedWidthAndHeight->implicitWidth());
QVERIFY(cappedWidthFixedHeight->height() < cappedWidthFixedHeight->implicitHeight());
QCOMPARE(cappedWidthFixedHeight->implicitWidth(), reference->implicitWidth());
QCOMPARE(cappedWidthFixedHeight->implicitHeight(), fixedWidthAndHeight->implicitHeight());
QVERIFY(fixedWidthCappedHeight->width() < fixedWidthCappedHeight->implicitWidth());
QVERIFY(fixedWidthCappedHeight->height() < fixedWidthCappedHeight->implicitHeight());
QCOMPARE(fixedWidthCappedHeight->implicitWidth(), reference->implicitWidth());
QCOMPARE(fixedWidthCappedHeight->implicitHeight(), fixedWidthAndHeight->implicitHeight());
}
void tst_qquicktext::contentSize()
{
QString componentStr = "import QtQuick 2.0\nText { width: 75; height: 16; font.pixelSize: 10 }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QScopedPointer<QObject> object(textComponent.create());
QQuickText *textObject = qobject_cast<QQuickText *>(object.data());
QSignalSpy spySize(textObject, SIGNAL(contentSizeChanged()));
QSignalSpy spyWidth(textObject, SIGNAL(contentWidthChanged(qreal)));
QSignalSpy spyHeight(textObject, SIGNAL(contentHeightChanged(qreal)));
textObject->setText("The quick red fox jumped over the lazy brown dog");
QVERIFY(textObject->contentWidth() > textObject->width());
QVERIFY(textObject->contentHeight() < textObject->height());
QCOMPARE(spySize.count(), 1);
QCOMPARE(spyWidth.count(), 1);
QCOMPARE(spyHeight.count(), 0);
textObject->setWrapMode(QQuickText::WordWrap);
QVERIFY(textObject->contentWidth() <= textObject->width());
QVERIFY(textObject->contentHeight() > textObject->height());
QCOMPARE(spySize.count(), 2);
QCOMPARE(spyWidth.count(), 2);
QCOMPARE(spyHeight.count(), 1);
textObject->setElideMode(QQuickText::ElideRight);
QVERIFY(textObject->contentWidth() <= textObject->width());
QVERIFY(textObject->contentHeight() < textObject->height());
QCOMPARE(spySize.count(), 3);
QCOMPARE(spyWidth.count(), 3);
QCOMPARE(spyHeight.count(), 2);
int spyCount = 3;
qreal elidedWidth = textObject->contentWidth();
textObject->setText("The quickredfoxjumpedoverthe lazy brown dog");
QVERIFY(textObject->contentWidth() <= textObject->width());
QVERIFY(textObject->contentHeight() < textObject->height());
// this text probably won't have the same elided width, but it's not guaranteed.
if (textObject->contentWidth() != elidedWidth)
QCOMPARE(spySize.count(), ++spyCount);
else
QCOMPARE(spySize.count(), spyCount);
textObject->setElideMode(QQuickText::ElideNone);
QVERIFY(textObject->contentWidth() > textObject->width());
QVERIFY(textObject->contentHeight() > textObject->height());
QCOMPARE(spySize.count(), ++spyCount);
QCOMPARE(spyWidth.count(), spyCount);
QCOMPARE(spyHeight.count(), 3);
}
void tst_qquicktext::geometryChanged()
{
// Test that text is re-laid out when the geometry of the item by verifying changes in content
// size. Implicit width is also tested as that in combination with item geometry provides a
// reference for expected content sizes.
QString componentStr = "import QtQuick 2.0\nText { font.family: \"__Qt__Box__Engine__\"; font.pixelSize: 10 }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QScopedPointer<QObject> object(textComponent.create());
QQuickText *textObject = qobject_cast<QQuickText *>(object.data());
const qreal implicitHeight = textObject->implicitHeight();
const qreal widths[] = { 100, 2000, 3000, -100, 100 };
const qreal heights[] = { implicitHeight, 2000, 3000, -implicitHeight, implicitHeight };
QCOMPARE(textObject->implicitWidth(), 0.);
QVERIFY(implicitHeight > 0.);
QCOMPARE(textObject->width(), textObject->implicitWidth());
QCOMPARE(textObject->height(), implicitHeight);
QCOMPARE(textObject->contentWidth(), textObject->implicitWidth());
QCOMPARE(textObject->contentHeight(), implicitHeight);
textObject->setText("The quick red fox jumped over the lazy brown dog");
const qreal implicitWidth = textObject->implicitWidth();
QVERIFY(implicitWidth > 0.);
QCOMPARE(textObject->implicitHeight(), implicitHeight);
QCOMPARE(textObject->width(), textObject->implicitWidth());
QCOMPARE(textObject->height(), textObject->implicitHeight());
QCOMPARE(textObject->contentWidth(), textObject->implicitWidth());
QCOMPARE(textObject->contentHeight(), textObject->implicitHeight());
// Changing the geometry with no eliding, or wrapping doesn't change the content size.
for (int i = 0; i < 5; ++i) {
textObject->setWidth(widths[i]);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), implicitHeight);
QCOMPARE(textObject->width(), widths[i]);
QCOMPARE(textObject->height(), implicitHeight);
QCOMPARE(textObject->contentWidth(), implicitWidth);
QCOMPARE(textObject->contentHeight(), implicitHeight);
}
// With eliding enabled the content width is bounded to the item width, but is never
// larger than the implicit width.
textObject->setElideMode(QQuickText::ElideRight);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), implicitHeight);
QCOMPARE(textObject->width(), 100.);
QCOMPARE(textObject->height(), implicitHeight);
QVERIFY(textObject->contentWidth() <= 100.);
QCOMPARE(textObject->contentHeight(), implicitHeight);
textObject->setWidth(2000.);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), implicitHeight);
QCOMPARE(textObject->width(), 2000.);
QCOMPARE(textObject->height(), implicitHeight);
QCOMPARE(textObject->contentWidth(), implicitWidth);
QCOMPARE(textObject->contentHeight(), implicitHeight);
textObject->setWidth(3000.);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), implicitHeight);
QCOMPARE(textObject->width(), 3000.);
QCOMPARE(textObject->height(), implicitHeight);
QCOMPARE(textObject->contentWidth(), implicitWidth);
QCOMPARE(textObject->contentHeight(), implicitHeight);
textObject->setWidth(-100);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), implicitHeight);
QCOMPARE(textObject->width(), -100.);
QCOMPARE(textObject->height(), implicitHeight);
QCOMPARE(textObject->contentWidth(), 0.);
QCOMPARE(textObject->contentHeight(), implicitHeight);
textObject->setWidth(100.);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), implicitHeight);
QCOMPARE(textObject->width(), 100.);
QCOMPARE(textObject->height(), implicitHeight);
QVERIFY(textObject->contentWidth() <= 100.);
QCOMPARE(textObject->contentHeight(), implicitHeight);
// With wrapping enabled the implicit height changes with the width.
textObject->setElideMode(QQuickText::ElideNone);
textObject->setWrapMode(QQuickText::Wrap);
const qreal wrappedImplicitHeight = textObject->implicitHeight();
QVERIFY(wrappedImplicitHeight > implicitHeight);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->width(), 100.);
QCOMPARE(textObject->height(), wrappedImplicitHeight);
QVERIFY(textObject->contentWidth() <= 100.);
QCOMPARE(textObject->contentHeight(), wrappedImplicitHeight);
textObject->setWidth(2000.);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), implicitHeight);
QCOMPARE(textObject->width(), 2000.);
QCOMPARE(textObject->height(), implicitHeight);
QCOMPARE(textObject->contentWidth(), implicitWidth);
QCOMPARE(textObject->contentHeight(), implicitHeight);
textObject->setWidth(3000.);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), implicitHeight);
QCOMPARE(textObject->width(), 3000.);
QCOMPARE(textObject->height(), implicitHeight);
QCOMPARE(textObject->contentWidth(), implicitWidth);
QCOMPARE(textObject->contentHeight(), implicitHeight);
textObject->setWidth(-100);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), implicitHeight);
QCOMPARE(textObject->width(), -100.);
QCOMPARE(textObject->height(), implicitHeight);
QCOMPARE(textObject->contentWidth(), implicitWidth); // 0 or negative width item won't wrap.
QCOMPARE(textObject->contentHeight(), implicitHeight);
textObject->setWidth(100.);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight);
QCOMPARE(textObject->width(), 100.);
QCOMPARE(textObject->height(), wrappedImplicitHeight);
QVERIFY(textObject->contentWidth() <= 100.);
QCOMPARE(textObject->contentHeight(), wrappedImplicitHeight);
// With no eliding or maximum line count the content height is the same as the implicit height.
for (int i = 0; i < 5; ++i) {
textObject->setHeight(heights[i]);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight);
QCOMPARE(textObject->width(), 100.);
QCOMPARE(textObject->height(), heights[i]);
QVERIFY(textObject->contentWidth() <= 100.);
QCOMPARE(textObject->contentHeight(), wrappedImplicitHeight);
}
// The implicit height is unaffected by eliding but the content height will change.
textObject->setElideMode(QQuickText::ElideRight);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight);
QCOMPARE(textObject->width(), 100.);
QCOMPARE(textObject->height(), implicitHeight);
QVERIFY(textObject->contentWidth() <= 100.);
QCOMPARE(textObject->contentHeight(), implicitHeight);
textObject->setHeight(2000);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight);
QCOMPARE(textObject->width(), 100.);
QCOMPARE(textObject->height(), 2000.);
QVERIFY(textObject->contentWidth() <= 100.);
QCOMPARE(textObject->contentHeight(), wrappedImplicitHeight);
textObject->setHeight(3000);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight);
QCOMPARE(textObject->width(), 100.);
QCOMPARE(textObject->height(), 3000.);
QVERIFY(textObject->contentWidth() <= 100.);
QCOMPARE(textObject->contentHeight(), wrappedImplicitHeight);
textObject->setHeight(-implicitHeight);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight);
QCOMPARE(textObject->width(), 100.);
QCOMPARE(textObject->height(), -implicitHeight);
QVERIFY(textObject->contentWidth() <= 0.);
QCOMPARE(textObject->contentHeight(), implicitHeight); // content height is never less than font height. seems a little odd in this instance.
textObject->setHeight(implicitHeight);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight);
QCOMPARE(textObject->width(), 100.);
QCOMPARE(textObject->height(), implicitHeight);
QVERIFY(textObject->contentWidth() <= 100.);
QCOMPARE(textObject->contentHeight(), implicitHeight);
// Varying the height with a maximum line count but no eliding won't affect the content height.
textObject->setElideMode(QQuickText::ElideNone);
textObject->setMaximumLineCount(2);
textObject->resetHeight();
const qreal maxLineCountImplicitHeight = textObject->implicitHeight();
QVERIFY(maxLineCountImplicitHeight > implicitHeight);
QVERIFY(maxLineCountImplicitHeight < wrappedImplicitHeight);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->width(), 100.);
QCOMPARE(textObject->height(), maxLineCountImplicitHeight);
QVERIFY(textObject->contentWidth() <= 100.);
QCOMPARE(textObject->contentHeight(), maxLineCountImplicitHeight);
for (int i = 0; i < 5; ++i) {
textObject->setHeight(heights[i]);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), maxLineCountImplicitHeight);
QCOMPARE(textObject->width(), 100.);
QCOMPARE(textObject->height(), heights[i]);
QVERIFY(textObject->contentWidth() <= 100.);
QCOMPARE(textObject->contentHeight(), maxLineCountImplicitHeight);
}
// Varying the width with a maximum line count won't increase the implicit height beyond the
// height of the maximum number of lines.
textObject->setWidth(2000.);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), implicitHeight);
QCOMPARE(textObject->width(), 2000.);
QCOMPARE(textObject->height(), implicitHeight);
QCOMPARE(textObject->contentWidth(), implicitWidth);
QCOMPARE(textObject->contentHeight(), implicitHeight);
textObject->setWidth(3000.);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), implicitHeight);
QCOMPARE(textObject->width(), 3000.);
QCOMPARE(textObject->height(), implicitHeight);
QCOMPARE(textObject->contentWidth(), implicitWidth);
QCOMPARE(textObject->contentHeight(), implicitHeight);
textObject->setWidth(-100);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), implicitHeight);
QCOMPARE(textObject->width(), -100.);
QCOMPARE(textObject->height(), implicitHeight);
QCOMPARE(textObject->contentWidth(), implicitWidth); // 0 or negative width item won't wrap.
QCOMPARE(textObject->contentHeight(), implicitHeight);
textObject->setWidth(50.);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), maxLineCountImplicitHeight);
QCOMPARE(textObject->width(), 50.);
QCOMPARE(textObject->height(), implicitHeight);
QVERIFY(textObject->contentWidth() <= 50.);
QCOMPARE(textObject->contentHeight(), maxLineCountImplicitHeight);
textObject->setWidth(100.);
QCOMPARE(textObject->implicitWidth(), implicitWidth);
QCOMPARE(textObject->implicitHeight(), maxLineCountImplicitHeight);
QCOMPARE(textObject->width(), 100.);
QCOMPARE(textObject->height(), implicitHeight);
QVERIFY(textObject->contentWidth() <= 100.);
QCOMPARE(textObject->contentHeight(), maxLineCountImplicitHeight);
}
void tst_qquicktext::implicitSizeBinding_data()
{
implicitSize_data();
}
void tst_qquicktext::implicitSizeBinding()
{
QFETCH(QString, text);
QFETCH(QString, wrap);
QFETCH(QString, format);
QString componentStr = "import QtQuick 2.0\nText { text: \"" + text + "\"; width: implicitWidth; height: implicitHeight; wrapMode: " + wrap + "; textFormat: " + format + " }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QScopedPointer<QObject> object(textComponent.create());
QQuickText *textObject = qobject_cast<QQuickText *>(object.data());
QCOMPARE(textObject->width(), textObject->implicitWidth());
QCOMPARE(textObject->height(), textObject->implicitHeight());
textObject->resetWidth();
QCOMPARE(textObject->width(), textObject->implicitWidth());
QCOMPARE(textObject->height(), textObject->implicitHeight());
textObject->resetHeight();
QCOMPARE(textObject->width(), textObject->implicitWidth());
QCOMPARE(textObject->height(), textObject->implicitHeight());
}
void tst_qquicktext::boundingRect_data()
{
QTest::addColumn<QString>("format");
QTest::newRow("PlainText") << "Text.PlainText";
QTest::newRow("StyledText") << "Text.StyledText";
QTest::newRow("RichText") << "Text.RichText";
}
void tst_qquicktext::boundingRect()
{
QFETCH(QString, format);
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0\n Text { textFormat:" + format.toUtf8() + "}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickText *text = qobject_cast<QQuickText *>(object.data());
QVERIFY(text);
QCOMPARE(text->boundingRect().x(), qreal(0));
QCOMPARE(text->boundingRect().y(), qreal(0));
QCOMPARE(text->boundingRect().width(), qreal(0));
QCOMPARE(text->boundingRect().height(), qreal(qCeil(QFontMetricsF(text->font()).height())));
text->setText("Hello World");
QTextLayout layout(text->text());
layout.setFont(text->font());
if (!qmlDisableDistanceField()) {
QTextOption option;
option.setUseDesignMetrics(true);
layout.setTextOption(option);
}
layout.beginLayout();
QTextLine line = layout.createLine();
layout.endLayout();
QCOMPARE(text->boundingRect().x(), qreal(0));
QCOMPARE(text->boundingRect().y(), qreal(0));
QCOMPARE(text->boundingRect().width(), line.naturalTextWidth());
QCOMPARE(text->boundingRect().height(), line.height());
// the size of the bounding rect shouldn't be bounded by the size of item.
text->setWidth(text->width() / 2);
QCOMPARE(text->boundingRect().x(), qreal(0));
QCOMPARE(text->boundingRect().y(), qreal(0));
QCOMPARE(text->boundingRect().width(), line.naturalTextWidth());
QCOMPARE(text->boundingRect().height(), line.height());
text->setHeight(text->height() * 2);
QCOMPARE(text->boundingRect().x(), qreal(0));
QCOMPARE(text->boundingRect().y(), qreal(0));
QCOMPARE(text->boundingRect().width(), line.naturalTextWidth());
QCOMPARE(text->boundingRect().height(), line.height());
text->setHAlign(QQuickText::AlignRight);
QCOMPARE(text->boundingRect().x(), text->width() - line.naturalTextWidth());
QCOMPARE(text->boundingRect().y(), qreal(0));
QCOMPARE(text->boundingRect().width(), line.naturalTextWidth());
QCOMPARE(text->boundingRect().height(), line.height());
QQuickItemPrivate::get(text)->setLayoutMirror(true);
QCOMPARE(text->boundingRect().x(), qreal(0));
QCOMPARE(text->boundingRect().y(), qreal(0));
QCOMPARE(text->boundingRect().width(), line.naturalTextWidth());
QCOMPARE(text->boundingRect().height(), line.height());
text->setHAlign(QQuickText::AlignLeft);
QCOMPARE(text->boundingRect().x(), text->width() - line.naturalTextWidth());
QCOMPARE(text->boundingRect().y(), qreal(0));
QCOMPARE(text->boundingRect().width(), line.naturalTextWidth());
QCOMPARE(text->boundingRect().height(), line.height());
text->setWrapMode(QQuickText::Wrap);
QCOMPARE(text->boundingRect().right(), text->width());
QCOMPARE(text->boundingRect().y(), qreal(0));
QVERIFY(text->boundingRect().width() < line.naturalTextWidth());
QVERIFY(text->boundingRect().height() > line.height());
text->setVAlign(QQuickText::AlignBottom);
QCOMPARE(text->boundingRect().right(), text->width());
QCOMPARE(text->boundingRect().bottom(), text->height());
QVERIFY(text->boundingRect().width() < line.naturalTextWidth());
QVERIFY(text->boundingRect().height() > line.height());
}
void tst_qquicktext::clipRect()
{
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0\n Text {}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickText *text = qobject_cast<QQuickText *>(object.data());
QVERIFY(text);
QTextLayout layout;
layout.setFont(text->font());
QCOMPARE(text->clipRect().x(), qreal(0));
QCOMPARE(text->clipRect().y(), qreal(0));
QCOMPARE(text->clipRect().width(), text->width());
QCOMPARE(text->clipRect().height(), text->height());
text->setText("Hello World");
QCOMPARE(text->clipRect().x(), qreal(0));
QCOMPARE(text->clipRect().y(), qreal(0));
QCOMPARE(text->clipRect().width(), text->width());
QCOMPARE(text->clipRect().height(), text->height());
// Clip rect follows the item not content dimensions.
text->setWidth(text->width() / 2);
QCOMPARE(text->clipRect().x(), qreal(0));
QCOMPARE(text->clipRect().y(), qreal(0));
QCOMPARE(text->clipRect().width(), text->width());
QCOMPARE(text->clipRect().height(), text->height());
text->setHeight(text->height() * 2);
QCOMPARE(text->clipRect().x(), qreal(0));
QCOMPARE(text->clipRect().y(), qreal(0));
QCOMPARE(text->clipRect().width(), text->width());
QCOMPARE(text->clipRect().height(), text->height());
// Setting a style adds a small amount of padding to the clip rect.
text->setStyle(QQuickText::Outline);
QCOMPARE(text->clipRect().x(), qreal(-1));
QCOMPARE(text->clipRect().y(), qreal(0));
QCOMPARE(text->clipRect().width(), text->width() + 2);
QCOMPARE(text->clipRect().height(), text->height() + 2);
}
void tst_qquicktext::lineLaidOut()
{
QScopedPointer<QQuickView> window(createView(testFile("lineLayout.qml")));
QQuickText *myText = window->rootObject()->findChild<QQuickText*>("myText");
QVERIFY(myText != nullptr);
QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(myText);
QVERIFY(textPrivate != nullptr);
QVERIFY(!textPrivate->extra.isAllocated());
for (int i = 0; i < textPrivate->layout.lineCount(); ++i) {
QRectF r = textPrivate->layout.lineAt(i).rect();
QVERIFY(r.width() == i * 15);
if (i >= 30)
QVERIFY(r.x() == r.width() + 30);
if (i >= 60) {
QVERIFY(r.x() == r.width() * 2 + 60);
QCOMPARE(r.height(), qreal(20));
}
}
// Ensure that isLast was correctly emitted
int lastLineNumber = myText->property("lastLineNumber").toInt();
QCOMPARE(lastLineNumber, myText->lineCount() - 1);
// Ensure that only one line was considered last (after changing its width)
bool receivedMultipleLastLines = myText->property("receivedMultipleLastLines").toBool();
QVERIFY(!receivedMultipleLastLines);
}
void tst_qquicktext::lineLaidOutRelayout()
{
QScopedPointer<QQuickView> window(createView(testFile("lineLayoutRelayout.qml")));
window->show();
window->requestActivate();
QVERIFY(QTest::qWaitForWindowActive(window.data()));
QQuickText *myText = window->rootObject()->findChild<QQuickText*>("myText");
QVERIFY(myText != nullptr);
QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(myText);
QVERIFY(textPrivate != nullptr);
QVERIFY(!textPrivate->extra.isAllocated());
qreal y = 0.0;
for (int i = 0; i < textPrivate->layout.lineCount(); ++i) {
QTextLine line = textPrivate->layout.lineAt(i);
const QRectF r = line.rect();
if (r.x() == 0) {
QCOMPARE(r.y(), y);
} else {
if (qFuzzyIsNull(r.y()))
y = 0.0;
QCOMPARE(r.x(), myText->width() / 2);
QCOMPARE(r.y(), y);
}
y += line.height();
}
}
void tst_qquicktext::lineLaidOutHAlign()
{
QScopedPointer<QQuickView> window(createView(testFile("lineLayoutHAlign.qml")));
QQuickText *myText = window->rootObject()->findChild<QQuickText*>("myText");
QVERIFY(myText != nullptr);
QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(myText);
QVERIFY(textPrivate != nullptr);
QCOMPARE(textPrivate->layout.lineCount(), 1);
QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().x() < 0.0);
}
void tst_qquicktext::imgTagsBaseUrl_data()
{
QTest::addColumn<QUrl>("src");
QTest::addColumn<QUrl>("baseUrl");
QTest::addColumn<QUrl>("contextUrl");
QTest::addColumn<qreal>("imgHeight");
QTest::newRow("absolute local")
<< testFileUrl("images/heart200.png")
<< QUrl()
<< QUrl()
<< 181.;
QTest::newRow("relative local context 1")
<< QUrl("images/heart200.png")
<< QUrl()
<< testFileUrl("/app.qml")
<< 181.;
QTest::newRow("relative local context 2")
<< QUrl("heart200.png")
<< QUrl()
<< testFileUrl("images/app.qml")
<< 181.;
QTest::newRow("relative local base 1")
<< QUrl("images/heart200.png")
<< testFileUrl("")
<< testFileUrl("nonexistant/app.qml")
<< 181.;
QTest::newRow("relative local base 2")
<< QUrl("heart200.png")
<< testFileUrl("images/")
<< testFileUrl("nonexistant/app.qml")
<< 181.;
QTest::newRow("base relative to local context")
<< QUrl("heart200.png")
<< testFileUrl("images/")
<< testFileUrl("/app.qml")
<< 181.;
QTest::newRow("absolute remote")
<< QUrl("http://testserver/images/heart200.png")
<< QUrl()
<< QUrl()
<< 181.;
QTest::newRow("relative remote base 1")
<< QUrl("images/heart200.png")
<< QUrl("http://testserver/")
<< testFileUrl("nonexistant/app.qml")
<< 181.;
QTest::newRow("relative remote base 2")
<< QUrl("heart200.png")
<< QUrl("http://testserver/images/")
<< testFileUrl("nonexistant/app.qml")
<< 181.;
}
void tst_qquicktext::lineLaidOutImplicitWidth()
{
QScopedPointer<QQuickView> window(createView(testFile("lineLayoutImplicitWidth.qml")));
QQuickText *myText = window->rootObject()->findChild<QQuickText*>("myText");
QVERIFY(myText != nullptr);
QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(myText);
QVERIFY(textPrivate != nullptr);
// Retrieve the saved implicitWidth values of each rendered line
QVariant widthsProperty = myText->property("lineImplicitWidths");
QVERIFY(!widthsProperty.isNull());
QVERIFY(widthsProperty.isValid());
QVERIFY(widthsProperty.canConvert<QJSValue>());
QJSValue widthsValue = widthsProperty.value<QJSValue>();
QVERIFY(widthsValue.isArray());
int lineCount = widthsValue.property("length").toInt();
QVERIFY(lineCount > 0);
// Create the same text layout by hand
// Note that this approach needs additional processing for styled text,
// so we only use it for plain text here.
QTextLayout layout;
layout.setCacheEnabled(true);
layout.setText(myText->text());
layout.setTextOption(textPrivate->layout.textOption());
layout.setFont(myText->font());
layout.beginLayout();
for (QTextLine line = layout.createLine(); line.isValid(); line = layout.createLine()) {
line.setLineWidth(myText->width());
}
layout.endLayout();
// Line count of the just created layout should match the rendered text
QCOMPARE(lineCount, layout.lineCount());
// Go through each line and verify that the values emitted by lineLaidOut are correct
for (int i = 0; i < layout.lineCount(); ++i) {
qreal implicitWidth = widthsValue.property(i).toNumber();
QVERIFY(implicitWidth > 0);
QTextLine line = layout.lineAt(i);
QCOMPARE(implicitWidth, line.naturalTextWidth());
}
}
static QUrl substituteTestServerUrl(const QUrl &serverUrl, const QUrl &testUrl)
{
QUrl result = testUrl;
if (result.host() == QStringLiteral("testserver")) {
result.setScheme(serverUrl.scheme());
result.setHost(serverUrl.host());
result.setPort(serverUrl.port());
}
return result;
}
void tst_qquicktext::imgTagsBaseUrl()
{
QFETCH(QUrl, src);
QFETCH(QUrl, baseUrl);
QFETCH(QUrl, contextUrl);
QFETCH(qreal, imgHeight);
TestHTTPServer server;
QVERIFY2(server.listen(), qPrintable(server.errorString()));
server.serveDirectory(testFile(""));
src = substituteTestServerUrl(server.baseUrl(), src);
baseUrl = substituteTestServerUrl(server.baseUrl(), baseUrl);
contextUrl = substituteTestServerUrl(server.baseUrl(), contextUrl);
QByteArray baseUrlFragment;
if (!baseUrl.isEmpty())
baseUrlFragment = "; baseUrl: \"" + baseUrl.toEncoded() + "\"";
QByteArray componentStr = "import QtQuick 2.0\nText { text: \"This is a test <img src=\\\"" + src.toEncoded() + "\\\">\"" + baseUrlFragment + " }";
QQmlComponent component(&engine);
component.setData(componentStr, contextUrl);
QScopedPointer<QObject> object(component.create());
QQuickText *textObject = qobject_cast<QQuickText *>(object.data());
QVERIFY(textObject);
QCoreApplication::processEvents();
QTRY_COMPARE(textObject->height(), imgHeight);
}
void tst_qquicktext::imgTagsAlign_data()
{
QTest::addColumn<QString>("src");
QTest::addColumn<int>("imgHeight");
QTest::addColumn<QString>("align");
QTest::newRow("heart-bottom") << "data/images/heart200.png" << 181 << "bottom";
QTest::newRow("heart-middle") << "data/images/heart200.png" << 181 << "middle";
QTest::newRow("heart-top") << "data/images/heart200.png" << 181 << "top";
QTest::newRow("starfish-bottom") << "data/images/starfish_2.png" << 217 << "bottom";
QTest::newRow("starfish-middle") << "data/images/starfish_2.png" << 217 << "middle";
QTest::newRow("starfish-top") << "data/images/starfish_2.png" << 217 << "top";
}
void tst_qquicktext::imgTagsAlign()
{
QFETCH(QString, src);
QFETCH(int, imgHeight);
QFETCH(QString, align);
QString componentStr = "import QtQuick 2.0\nText { text: \"This is a test <img src=\\\"" + src + "\\\" align=\\\"" + align + "\\\"> of image.\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("."));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->height(), qreal(imgHeight));
QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(textObject);
QVERIFY(textPrivate != nullptr);
QRectF br = textPrivate->layout.boundingRect();
if (align == "bottom")
QVERIFY(br.y() == imgHeight - br.height());
else if (align == "middle")
QVERIFY(br.y() == imgHeight / 2.0 - br.height() / 2.0);
else if (align == "top")
QCOMPARE(br.y(), qreal(0));
delete textObject;
}
void tst_qquicktext::imgTagsMultipleImages()
{
QString componentStr = "import QtQuick 2.0\nText { text: \"This is a starfish<img src=\\\"data/images/starfish_2.png\\\" width=\\\"60\\\" height=\\\"60\\\" > and another one<img src=\\\"data/images/heart200.png\\\" width=\\\"85\\\" height=\\\"85\\\">.\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("."));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->height(), qreal(85));
QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(textObject);
QVERIFY(textPrivate != nullptr);
QCOMPARE(textPrivate->extra->visibleImgTags.count(), 2);
delete textObject;
}
void tst_qquicktext::imgTagsElide()
{
QScopedPointer<QQuickView> window(createView(testFile("imgTagsElide.qml")));
QQuickText *myText = window->rootObject()->findChild<QQuickText*>("myText");
QVERIFY(myText != nullptr);
QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(myText);
QVERIFY(textPrivate != nullptr);
QCOMPARE(textPrivate->extra->visibleImgTags.count(), 0);
myText->setMaximumLineCount(20);
QTRY_COMPARE(textPrivate->extra->visibleImgTags.count(), 1);
delete myText;
}
void tst_qquicktext::imgTagsUpdates()
{
QScopedPointer<QQuickView> window(createView(testFile("imgTagsUpdates.qml")));
QQuickText *myText = window->rootObject()->findChild<QQuickText*>("myText");
QVERIFY(myText != nullptr);
QSignalSpy spy(myText, SIGNAL(contentSizeChanged()));
QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(myText);
QVERIFY(textPrivate != nullptr);
myText->setText("This is a heart<img src=\"images/heart200.png\">.");
QCOMPARE(textPrivate->extra->visibleImgTags.count(), 1);
QCOMPARE(spy.count(), 1);
myText->setMaximumLineCount(2);
myText->setText("This is another heart<img src=\"images/heart200.png\">.");
QTRY_COMPARE(textPrivate->extra->visibleImgTags.count(), 1);
// if maximumLineCount is set and the img tag doesn't have an explicit size
// we relayout twice.
QCOMPARE(spy.count(), 3);
delete myText;
}
void tst_qquicktext::imgTagsError()
{
QString componentStr = "import QtQuick 2.0\nText { text: \"This is a starfish<img src=\\\"data/images/starfish_2.pn\\\" width=\\\"60\\\" height=\\\"60\\\">.\" }";
QQmlComponent textComponent(&engine);
QTest::ignoreMessage(QtWarningMsg, "<Unknown File>:2:1: QML Text: Cannot open: file:data/images/starfish_2.pn");
textComponent.setData(componentStr.toLatin1(), QUrl("file:"));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
delete textObject;
}
void tst_qquicktext::fontSizeMode_data()
{
QTest::addColumn<QString>("text");
QTest::newRow("plain") << "The quick red fox jumped over the lazy brown dog";
QTest::newRow("styled") << "<b>The quick red fox jumped over the lazy brown dog</b>";
}
void tst_qquicktext::fontSizeMode()
{
QFETCH(QString, text);
QScopedPointer<QQuickView> window(createView(testFile("fontSizeMode.qml")));
window->show();
QQuickText *myText = window->rootObject()->findChild<QQuickText*>("myText");
QVERIFY(myText != nullptr);
myText->setText(text);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
qreal originalWidth = myText->contentWidth();
qreal originalHeight = myText->contentHeight();
// The original text unwrapped should exceed the width of the item.
QVERIFY(originalWidth > myText->width());
QVERIFY(originalHeight < myText->height());
QFont font = myText->font();
font.setPixelSize(64);
myText->setFont(font);
myText->setFontSizeMode(QQuickText::HorizontalFit);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
// Font size reduced to fit within the width of the item.
qreal horizontalFitWidth = myText->contentWidth();
qreal horizontalFitHeight = myText->contentHeight();
QVERIFY(horizontalFitWidth <= myText->width() + 2); // rounding
QVERIFY(horizontalFitHeight <= myText->height() + 2);
// Elide won't affect the size with HorizontalFit.
myText->setElideMode(QQuickText::ElideRight);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), horizontalFitWidth);
QCOMPARE(myText->contentHeight(), horizontalFitHeight);
myText->setElideMode(QQuickText::ElideLeft);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), horizontalFitWidth);
QCOMPARE(myText->contentHeight(), horizontalFitHeight);
myText->setElideMode(QQuickText::ElideMiddle);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), horizontalFitWidth);
QCOMPARE(myText->contentHeight(), horizontalFitHeight);
myText->setElideMode(QQuickText::ElideNone);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
myText->setFontSizeMode(QQuickText::VerticalFit);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
// Font size increased to fill the height of the item.
qreal verticalFitHeight = myText->contentHeight();
QVERIFY(myText->contentWidth() > myText->width());
QVERIFY(verticalFitHeight <= myText->height() + 2);
QVERIFY(verticalFitHeight > originalHeight);
// Elide won't affect the height of a single line with VerticalFit but will crop the width.
myText->setElideMode(QQuickText::ElideRight);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(myText->truncated());
QVERIFY(myText->contentWidth() <= myText->width() + 2);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
myText->setElideMode(QQuickText::ElideLeft);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(myText->truncated());
QVERIFY(myText->contentWidth() <= myText->width() + 2);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
myText->setElideMode(QQuickText::ElideMiddle);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(myText->truncated());
QVERIFY(myText->contentWidth() <= myText->width() + 2);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
myText->setElideMode(QQuickText::ElideNone);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
myText->setFontSizeMode(QQuickText::Fit);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
// Should be the same as HorizontalFit with no wrapping.
QCOMPARE(myText->contentWidth(), horizontalFitWidth);
QCOMPARE(myText->contentHeight(), horizontalFitHeight);
// Elide won't affect the size with Fit.
myText->setElideMode(QQuickText::ElideRight);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), horizontalFitWidth);
QCOMPARE(myText->contentHeight(), horizontalFitHeight);
myText->setElideMode(QQuickText::ElideLeft);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), horizontalFitWidth);
QCOMPARE(myText->contentHeight(), horizontalFitHeight);
myText->setElideMode(QQuickText::ElideMiddle);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), horizontalFitWidth);
QCOMPARE(myText->contentHeight(), horizontalFitHeight);
myText->setElideMode(QQuickText::ElideNone);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
myText->setFontSizeMode(QQuickText::FixedSize);
myText->setWrapMode(QQuickText::Wrap);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
originalWidth = myText->contentWidth();
originalHeight = myText->contentHeight();
// The original text wrapped should exceed the height of the item.
QVERIFY(originalWidth <= myText->width() + 2);
QVERIFY(originalHeight > myText->height());
myText->setFontSizeMode(QQuickText::HorizontalFit);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
// HorizontalFit should reduce the font size to minimize wrapping, which brings it back to the
// same size as without text wrapping.
QCOMPARE(myText->contentWidth(), horizontalFitWidth);
QCOMPARE(myText->contentHeight(), horizontalFitHeight);
// Elide won't affect the size with HorizontalFit.
myText->setElideMode(QQuickText::ElideRight);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), horizontalFitWidth);
QCOMPARE(myText->contentHeight(), horizontalFitHeight);
myText->setElideMode(QQuickText::ElideNone);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
myText->setFontSizeMode(QQuickText::VerticalFit);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
// VerticalFit should reduce the size to the wrapped text within the vertical height.
verticalFitHeight = myText->contentHeight();
qreal verticalFitWidth = myText->contentWidth();
QVERIFY(myText->contentWidth() <= myText->width() + 2);
QVERIFY(verticalFitHeight <= myText->height() + 2);
QVERIFY(verticalFitHeight < originalHeight);
// Elide won't affect the height or width of a wrapped text with VerticalFit.
myText->setElideMode(QQuickText::ElideRight);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), verticalFitWidth);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
myText->setElideMode(QQuickText::ElideNone);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
myText->setFontSizeMode(QQuickText::Fit);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
// Should be the same as VerticalFit with wrapping.
QCOMPARE(myText->contentWidth(), verticalFitWidth);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
// Elide won't affect the size with Fit.
myText->setElideMode(QQuickText::ElideRight);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), verticalFitWidth);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
myText->setElideMode(QQuickText::ElideNone);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
myText->setFontSizeMode(QQuickText::FixedSize);
myText->setMaximumLineCount(2);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
// The original text wrapped should exceed the height of the item.
QVERIFY(originalWidth <= myText->width() + 2);
QVERIFY(originalHeight > myText->height());
myText->setFontSizeMode(QQuickText::HorizontalFit);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
// HorizontalFit should reduce the font size to minimize wrapping, which brings it back to the
// same size as without text wrapping.
QCOMPARE(myText->contentWidth(), horizontalFitWidth);
QCOMPARE(myText->contentHeight(), horizontalFitHeight);
// Elide won't affect the size with HorizontalFit.
myText->setElideMode(QQuickText::ElideRight);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), horizontalFitWidth);
QCOMPARE(myText->contentHeight(), horizontalFitHeight);
myText->setElideMode(QQuickText::ElideNone);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
myText->setFontSizeMode(QQuickText::VerticalFit);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
// VerticalFit should reduce the size to the wrapped text within the vertical height.
verticalFitHeight = myText->contentHeight();
verticalFitWidth = myText->contentWidth();
QVERIFY(myText->contentWidth() <= myText->width() + 2);
QVERIFY(verticalFitHeight <= myText->height() + 2);
QVERIFY(verticalFitHeight < originalHeight);
// Elide won't affect the height or width of a wrapped text with VerticalFit.
myText->setElideMode(QQuickText::ElideRight);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), verticalFitWidth);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
myText->setElideMode(QQuickText::ElideNone);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
myText->setFontSizeMode(QQuickText::Fit);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
// Should be the same as VerticalFit with wrapping.
QCOMPARE(myText->contentWidth(), verticalFitWidth);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
// Elide won't affect the size with Fit.
myText->setElideMode(QQuickText::ElideRight);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), verticalFitWidth);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
myText->setElideMode(QQuickText::ElideNone);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
}
void tst_qquicktext::fontSizeModeMultiline_data()
{
QTest::addColumn<QString>("text");
QTest::newRow("plain") << "The quick red fox jumped\n over the lazy brown dog";
QTest::newRow("styledtext") << "<b>The quick red fox jumped<br/> over the lazy brown dog</b>";
}
void tst_qquicktext::fontSizeModeMultiline()
{
QFETCH(QString, text);
QScopedPointer<QQuickView> window(createView(testFile("fontSizeMode.qml")));
window->show();
QQuickText *myText = window->rootObject()->findChild<QQuickText*>("myText");
QVERIFY(myText != nullptr);
myText->setText(text);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
qreal originalWidth = myText->contentWidth();
qreal originalHeight = myText->contentHeight();
QCOMPARE(myText->lineCount(), 2);
// The original text unwrapped should exceed the width and height of the item.
QVERIFY(originalWidth > myText->width());
QVERIFY(originalHeight > myText->height());
QFont font = myText->font();
font.setPixelSize(64);
myText->setFont(font);
myText->setFontSizeMode(QQuickText::HorizontalFit);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
// Font size reduced to fit within the width of the item.
QCOMPARE(myText->lineCount(), 2);
qreal horizontalFitWidth = myText->contentWidth();
qreal horizontalFitHeight = myText->contentHeight();
QVERIFY(horizontalFitWidth <= myText->width() + 2); // rounding
QVERIFY(horizontalFitHeight > myText->height());
// Right eliding will remove the last line
myText->setElideMode(QQuickText::ElideRight);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(myText->truncated());
QCOMPARE(myText->lineCount(), 1);
QVERIFY(myText->contentWidth() <= myText->width() + 2);
QVERIFY(myText->contentHeight() <= myText->height() + 2);
// Left or middle eliding wont have any effect.
myText->setElideMode(QQuickText::ElideLeft);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), horizontalFitWidth);
QCOMPARE(myText->contentHeight(), horizontalFitHeight);
myText->setElideMode(QQuickText::ElideMiddle);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), horizontalFitWidth);
QCOMPARE(myText->contentHeight(), horizontalFitHeight);
myText->setElideMode(QQuickText::ElideNone);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
myText->setFontSizeMode(QQuickText::VerticalFit);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
// Font size reduced to fit within the height of the item.
qreal verticalFitWidth = myText->contentWidth();
qreal verticalFitHeight = myText->contentHeight();
QVERIFY(verticalFitWidth <= myText->width() + 2);
QVERIFY(verticalFitHeight <= myText->height() + 2);
// Elide will have no effect.
myText->setElideMode(QQuickText::ElideRight);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QVERIFY(myText->contentWidth() <= myText->width() + 2);
QCOMPARE(myText->contentWidth(), verticalFitWidth);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
myText->setElideMode(QQuickText::ElideLeft);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), verticalFitWidth);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
myText->setElideMode(QQuickText::ElideMiddle);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), verticalFitWidth);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
myText->setElideMode(QQuickText::ElideNone);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
myText->setFontSizeMode(QQuickText::Fit);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
// Should be the same as VerticalFit with no wrapping.
QCOMPARE(myText->contentWidth(), verticalFitWidth);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
// Elide won't affect the size with Fit.
myText->setElideMode(QQuickText::ElideRight);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), verticalFitWidth);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
myText->setElideMode(QQuickText::ElideLeft);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), verticalFitWidth);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
myText->setElideMode(QQuickText::ElideMiddle);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), verticalFitWidth);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
myText->setElideMode(QQuickText::ElideNone);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
myText->setFontSizeMode(QQuickText::FixedSize);
myText->setWrapMode(QQuickText::Wrap);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
originalWidth = myText->contentWidth();
originalHeight = myText->contentHeight();
// The original text wrapped should exceed the height of the item.
QVERIFY(originalWidth <= myText->width() + 2);
QVERIFY(originalHeight > myText->height());
myText->setFontSizeMode(QQuickText::HorizontalFit);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
// HorizontalFit should reduce the font size to minimize wrapping, which brings it back to the
// same size as without text wrapping.
QCOMPARE(myText->contentWidth(), horizontalFitWidth);
QCOMPARE(myText->contentHeight(), horizontalFitHeight);
// Text will be elided vertically with HorizontalFit
myText->setElideMode(QQuickText::ElideRight);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(myText->truncated());
QVERIFY(myText->contentWidth() <= myText->width() + 2);
QVERIFY(myText->contentHeight() <= myText->height() + 2);
myText->setElideMode(QQuickText::ElideNone);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
myText->setFontSizeMode(QQuickText::VerticalFit);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
// VerticalFit should reduce the size to the wrapped text within the vertical height.
verticalFitHeight = myText->contentHeight();
verticalFitWidth = myText->contentWidth();
QVERIFY(myText->contentWidth() <= myText->width() + 2);
QVERIFY(verticalFitHeight <= myText->height() + 2);
QVERIFY(verticalFitHeight < originalHeight);
// Elide won't affect the height or width of a wrapped text with VerticalFit.
myText->setElideMode(QQuickText::ElideRight);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), verticalFitWidth);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
myText->setElideMode(QQuickText::ElideNone);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
myText->setFontSizeMode(QQuickText::Fit);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
// Should be the same as VerticalFit with wrapping.
QCOMPARE(myText->contentWidth(), verticalFitWidth);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
// Elide won't affect the size with Fit.
myText->setElideMode(QQuickText::ElideRight);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), verticalFitWidth);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
myText->setElideMode(QQuickText::ElideNone);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
myText->setFontSizeMode(QQuickText::FixedSize);
myText->setMaximumLineCount(2);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
// The original text wrapped should exceed the height of the item.
QVERIFY(originalWidth <= myText->width() + 2);
QVERIFY(originalHeight > myText->height());
myText->setFontSizeMode(QQuickText::HorizontalFit);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
// HorizontalFit should reduce the font size to minimize wrapping, which brings it back to the
// same size as without text wrapping.
QCOMPARE(myText->contentWidth(), horizontalFitWidth);
QCOMPARE(myText->contentHeight(), horizontalFitHeight);
// Elide won't affect the size with HorizontalFit.
myText->setElideMode(QQuickText::ElideRight);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(myText->truncated());
QVERIFY(myText->contentWidth() <= myText->width() + 2);
QVERIFY(myText->contentHeight() <= myText->height() + 2);
myText->setElideMode(QQuickText::ElideNone);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
myText->setFontSizeMode(QQuickText::VerticalFit);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
// VerticalFit should reduce the size to the wrapped text within the vertical height.
verticalFitHeight = myText->contentHeight();
verticalFitWidth = myText->contentWidth();
QVERIFY(myText->contentWidth() <= myText->width() + 2);
QVERIFY(verticalFitHeight <= myText->height() + 2);
QVERIFY(verticalFitHeight < originalHeight);
// Elide won't affect the height or width of a wrapped text with VerticalFit.
myText->setElideMode(QQuickText::ElideRight);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), verticalFitWidth);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
myText->setElideMode(QQuickText::ElideNone);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
myText->setFontSizeMode(QQuickText::Fit);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
// Should be the same as VerticalFit with wrapping.
QCOMPARE(myText->contentWidth(), verticalFitWidth);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
// Elide won't affect the size with Fit.
myText->setElideMode(QQuickText::ElideRight);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QVERIFY(!myText->truncated());
QCOMPARE(myText->contentWidth(), verticalFitWidth);
QCOMPARE(myText->contentHeight(), verticalFitHeight);
myText->setElideMode(QQuickText::ElideNone);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
}
void tst_qquicktext::multilengthStrings_data()
{
QTest::addColumn<QString>("source");
QTest::newRow("No Wrap") << testFile("multilengthStrings.qml");
QTest::newRow("Wrap") << testFile("multilengthStringsWrapped.qml");
}
void tst_qquicktext::multilengthStrings()
{
QFETCH(QString, source);
QScopedPointer<QQuickView> window(createView(source));
window->show();
QQuickText *myText = window->rootObject()->findChild<QQuickText*>("myText");
QVERIFY(myText != nullptr);
const QString longText = "the quick brown fox jumped over the lazy dog";
const QString mediumText = "the brown fox jumped over the dog";
const QString shortText = "fox jumped dog";
myText->setText(longText);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
const qreal longWidth = myText->contentWidth();
const qreal longHeight = myText->contentHeight();
myText->setText(mediumText);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
const qreal mediumWidth = myText->contentWidth();
const qreal mediumHeight = myText->contentHeight();
myText->setText(shortText);
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
const qreal shortWidth = myText->contentWidth();
const qreal shortHeight = myText->contentHeight();
myText->setElideMode(QQuickText::ElideRight);
myText->setText(longText + QLatin1Char('\x9c') + mediumText + QLatin1Char('\x9c') + shortText);
myText->setSize(QSizeF(longWidth, longHeight));
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QCOMPARE(myText->contentWidth(), longWidth);
QCOMPARE(myText->contentHeight(), longHeight);
QCOMPARE(myText->truncated(), false);
myText->setSize(QSizeF(mediumWidth, mediumHeight));
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QCOMPARE(myText->contentWidth(), mediumWidth);
QCOMPARE(myText->contentHeight(), mediumHeight);
QCOMPARE(myText->truncated(), true);
myText->setSize(QSizeF(shortWidth, shortHeight));
QVERIFY(QQuickTest::qWaitForItemPolished(myText));
QCOMPARE(myText->contentWidth(), shortWidth);
QCOMPARE(myText->contentHeight(), shortHeight);
QCOMPARE(myText->truncated(), true);
}
void tst_qquicktext::fontFormatSizes_data()
{
QTest::addColumn<QString>("text");
QTest::addColumn<QString>("textWithTag");
QTest::addColumn<bool>("fontIsBigger");
QTest::newRow("fs1") << "Hello world!" << "Hello <font size=\"1\">world</font>!" << false;
QTest::newRow("fs2") << "Hello world!" << "Hello <font size=\"2\">world</font>!" << false;
QTest::newRow("fs3") << "Hello world!" << "Hello <font size=\"3\">world</font>!" << false;
QTest::newRow("fs4") << "Hello world!" << "Hello <font size=\"4\">world</font>!" << true;
QTest::newRow("fs5") << "Hello world!" << "Hello <font size=\"5\">world</font>!" << true;
QTest::newRow("fs6") << "Hello world!" << "Hello <font size=\"6\">world</font>!" << true;
QTest::newRow("fs7") << "Hello world!" << "Hello <font size=\"7\">world</font>!" << true;
QTest::newRow("h1") << "This is<br/>a font<br/> size test." << "This is <h1>a font</h1> size test." << true;
QTest::newRow("h2") << "This is<br/>a font<br/> size test." << "This is <h2>a font</h2> size test." << true;
QTest::newRow("h3") << "This is<br/>a font<br/> size test." << "This is <h3>a font</h3> size test." << true;
QTest::newRow("h4") << "This is<br/>a font<br/> size test." << "This is <h4>a font</h4> size test." << true;
QTest::newRow("h5") << "This is<br/>a font<br/> size test." << "This is <h5>a font</h5> size test." << false;
QTest::newRow("h6") << "This is<br/>a font<br/> size test." << "This is <h6>a font</h6> size test." << false;
}
void tst_qquicktext::fontFormatSizes()
{
QFETCH(QString, text);
QFETCH(QString, textWithTag);
QFETCH(bool, fontIsBigger);
QQuickView *view = new QQuickView;
{
view->setSource(testFileUrl("pointFontSizes.qml"));
view->show();
QQuickText *qtext = view->rootObject()->findChild<QQuickText*>("text");
QQuickText *qtextWithTag = view->rootObject()->findChild<QQuickText*>("textWithTag");
QVERIFY(qtext != nullptr);
QVERIFY(qtextWithTag != nullptr);
qtext->setText(text);
qtextWithTag->setText(textWithTag);
for (int size = 6; size < 100; size += 4) {
view->rootObject()->setProperty("pointSize", size);
if (fontIsBigger)
QVERIFY(qtext->height() <= qtextWithTag->height());
else
QVERIFY(qtext->height() >= qtextWithTag->height());
}
}
{
view->setSource(testFileUrl("pixelFontSizes.qml"));
QQuickText *qtext = view->rootObject()->findChild<QQuickText*>("text");
QQuickText *qtextWithTag = view->rootObject()->findChild<QQuickText*>("textWithTag");
QVERIFY(qtext != nullptr);
QVERIFY(qtextWithTag != nullptr);
qtext->setText(text);
qtextWithTag->setText(textWithTag);
for (int size = 6; size < 100; size += 4) {
view->rootObject()->setProperty("pixelSize", size);
if (fontIsBigger)
QVERIFY(qtext->height() <= qtextWithTag->height());
else
QVERIFY(qtext->height() >= qtextWithTag->height());
}
}
delete view;
}
typedef qreal (*ExpectedBaseline)(QQuickText *item);
Q_DECLARE_METATYPE(ExpectedBaseline)
static qreal expectedBaselineTop(QQuickText *item)
{
QFontMetricsF fm(item->font());
return fm.ascent() + item->topPadding();
}
static qreal expectedBaselineBottom(QQuickText *item)
{
QFontMetricsF fm(item->font());
return item->height() - item->contentHeight() - item->bottomPadding() + fm.ascent();
}
static qreal expectedBaselineCenter(QQuickText *item)
{
QFontMetricsF fm(item->font());
return ((item->height() - item->contentHeight() - item->topPadding() - item->bottomPadding()) / 2) + fm.ascent() + item->topPadding();
}
static qreal expectedBaselineBold(QQuickText *item)
{
QFont font = item->font();
font.setBold(true);
QFontMetricsF fm(font);
return fm.ascent() + item->topPadding();
}
static qreal expectedBaselineImage(QQuickText *item)
{
QFontMetricsF fm(item->font());
// The line is positioned so the bottom of the line is aligned with the bottom of the image,
// or image height - line height and the baseline is line position + ascent. Because
// QTextLine's height is rounded up this can give slightly different results to image height
// - descent.
return 181 - qCeil(fm.height()) + fm.ascent() + item->topPadding();
}
static qreal expectedBaselineCustom(QQuickText *item)
{
QFontMetricsF fm(item->font());
return 16 + fm.ascent() + item->topPadding();
}
static qreal expectedBaselineScaled(QQuickText *item)
{
QFont font = item->font();
QTextLayout layout(item->text().replace(QLatin1Char('\n'), QChar::LineSeparator));
do {
layout.setFont(font);
qreal width = 0;
layout.beginLayout();
for (QTextLine line = layout.createLine(); line.isValid(); line = layout.createLine()) {
line.setLineWidth(FLT_MAX);
width = qMax(line.naturalTextWidth(), width);
}
layout.endLayout();
if (width < item->width()) {
QFontMetricsF fm(layout.font());
return fm.ascent() + item->topPadding();
}
font.setPointSize(font.pointSize() - 1);
} while (font.pointSize() > 0);
return item->topPadding();
}
static qreal expectedBaselineFixedBottom(QQuickText *item)
{
QFontMetricsF fm(item->font());
qreal dy = item->text().contains(QLatin1Char('\n'))
? 160
: 180;
return dy + fm.ascent() - item->bottomPadding();
}
static qreal expectedBaselineProportionalBottom(QQuickText *item)
{
QFontMetricsF fm(item->font());
qreal dy = item->text().contains(QLatin1Char('\n'))
? 200 - (qCeil(fm.height()) * 3)
: 200 - (qCeil(fm.height()) * 1.5);
return dy + fm.ascent() - item->bottomPadding();
}
void tst_qquicktext::baselineOffset_data()
{
qRegisterMetaType<ExpectedBaseline>();
QTest::addColumn<QString>("text");
QTest::addColumn<QString>("wrappedText");
QTest::addColumn<QByteArray>("bindings");
QTest::addColumn<ExpectedBaseline>("expectedBaseline");
QTest::addColumn<ExpectedBaseline>("expectedBaselineEmpty");
QTest::newRow("top align")
<< "hello world"
<< "hello\nworld"
<< QByteArray("height: 200; verticalAlignment: Text.AlignTop")
<< &expectedBaselineTop
<< &expectedBaselineTop;
QTest::newRow("bottom align")
<< "hello world"
<< "hello\nworld"
<< QByteArray("height: 200; verticalAlignment: Text.AlignBottom")
<< &expectedBaselineBottom
<< &expectedBaselineBottom;
QTest::newRow("center align")
<< "hello world"
<< "hello\nworld"
<< QByteArray("height: 200; verticalAlignment: Text.AlignVCenter")
<< &expectedBaselineCenter
<< &expectedBaselineCenter;
QTest::newRow("bold")
<< "<b>hello world</b>"
<< "<b>hello<br/>world</b>"
<< QByteArray("height: 200")
<< &expectedBaselineBold
<< &expectedBaselineTop;
QTest::newRow("richText")
<< "<b>hello world</b>"
<< "<b>hello<br/>world</b>"
<< QByteArray("height: 200; textFormat: Text.RichText")
<< &expectedBaselineTop
<< &expectedBaselineTop;
QTest::newRow("elided")
<< "hello world"
<< "hello\nworld"
<< QByteArray("width: 20; height: 8; elide: Text.ElideRight")
<< &expectedBaselineTop
<< &expectedBaselineTop;
QTest::newRow("elided bottom align")
<< "hello world"
<< "hello\nworld!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
<< QByteArray("width: 200; height: 200; elide: Text.ElideRight; verticalAlignment: Text.AlignBottom")
<< &expectedBaselineBottom
<< &expectedBaselineBottom;
QTest::newRow("image")
<< "hello <img src=\"images/heart200.png\" /> world"
<< "hello <img src=\"images/heart200.png\" /><br/>world"
<< QByteArray("height: 200\n; baseUrl: \"") + testFileUrl("reference").toEncoded() + QByteArray("\"")
<< &expectedBaselineImage
<< &expectedBaselineTop;
QTest::newRow("customLine")
<< "hello world"
<< "hello\nworld"
<< QByteArray("height: 200; onLineLaidOut: line.y += 16")
<< &expectedBaselineCustom
<< &expectedBaselineCustom;
QTest::newRow("scaled font")
<< "hello world"
<< "hello\nworld"
<< QByteArray("width: 200; minimumPointSize: 1; font.pointSize: 64; fontSizeMode: Text.HorizontalFit")
<< &expectedBaselineScaled
<< &expectedBaselineTop;
QTest::newRow("fixed line height top align")
<< "hello world"
<< "hello\nworld"
<< QByteArray("height: 200; lineHeightMode: Text.FixedHeight; lineHeight: 20; verticalAlignment: Text.AlignTop")
<< &expectedBaselineTop
<< &expectedBaselineTop;
QTest::newRow("fixed line height bottom align")
<< "hello world"
<< "hello\nworld"
<< QByteArray("height: 200; lineHeightMode: Text.FixedHeight; lineHeight: 20; verticalAlignment: Text.AlignBottom")
<< &expectedBaselineFixedBottom
<< &expectedBaselineFixedBottom;
QTest::newRow("proportional line height top align")
<< "hello world"
<< "hello\nworld"
<< QByteArray("height: 200; lineHeightMode: Text.ProportionalHeight; lineHeight: 1.5; verticalAlignment: Text.AlignTop")
<< &expectedBaselineTop
<< &expectedBaselineTop;
QTest::newRow("proportional line height bottom align")
<< "hello world"
<< "hello\nworld"
<< QByteArray("height: 200; lineHeightMode: Text.ProportionalHeight; lineHeight: 1.5; verticalAlignment: Text.AlignBottom")
<< &expectedBaselineProportionalBottom
<< &expectedBaselineProportionalBottom;
QTest::newRow("top align with padding")
<< "hello world"
<< "hello\nworld"
<< QByteArray("height: 200; topPadding: 10; bottomPadding: 20; verticalAlignment: Text.AlignTop")
<< &expectedBaselineTop
<< &expectedBaselineTop;
QTest::newRow("bottom align with padding")
<< "hello world"
<< "hello\nworld"
<< QByteArray("height: 200; topPadding: 10; bottomPadding: 20; verticalAlignment: Text.AlignBottom")
<< &expectedBaselineBottom
<< &expectedBaselineBottom;
QTest::newRow("center align with padding")
<< "hello world"
<< "hello\nworld"
<< QByteArray("height: 200; topPadding: 10; bottomPadding: 20; verticalAlignment: Text.AlignVCenter")
<< &expectedBaselineCenter
<< &expectedBaselineCenter;
QTest::newRow("bold width padding")
<< "<b>hello world</b>"
<< "<b>hello<br/>world</b>"
<< QByteArray("height: 200; topPadding: 10; bottomPadding: 20")
<< &expectedBaselineBold
<< &expectedBaselineTop;
QTest::newRow("richText with padding")
<< "<b>hello world</b>"
<< "<b>hello<br/>world</b>"
<< QByteArray("height: 200; topPadding: 10; bottomPadding: 20; textFormat: Text.RichText")
<< &expectedBaselineTop
<< &expectedBaselineTop;
QTest::newRow("elided with padding")
<< "hello world"
<< "hello\nworld"
<< QByteArray("width: 20; height: 8; topPadding: 10; bottomPadding: 20; elide: Text.ElideRight")
<< &expectedBaselineTop
<< &expectedBaselineTop;
QTest::newRow("elided bottom align with padding")
<< "hello world"
<< "hello\nworld!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
<< QByteArray("width: 200; height: 200; topPadding: 10; bottomPadding: 20; elide: Text.ElideRight; verticalAlignment: Text.AlignBottom")
<< &expectedBaselineBottom
<< &expectedBaselineBottom;
QTest::newRow("image with padding")
<< "hello <img src=\"images/heart200.png\" /> world"
<< "hello <img src=\"images/heart200.png\" /><br/>world"
<< QByteArray("height: 200\n; topPadding: 10; bottomPadding: 20; baseUrl: \"") + testFileUrl("reference").toEncoded() + QByteArray("\"")
<< &expectedBaselineImage
<< &expectedBaselineTop;
QTest::newRow("customLine with padding")
<< "hello world"
<< "hello\nworld"
<< QByteArray("height: 200; topPadding: 10; bottomPadding: 20; onLineLaidOut: line.y += 16")
<< &expectedBaselineCustom
<< &expectedBaselineCustom;
QTest::newRow("scaled font with padding")
<< "hello world"
<< "hello\nworld"
<< QByteArray("width: 200; topPadding: 10; bottomPadding: 20; minimumPointSize: 1; font.pointSize: 64; fontSizeMode: Text.HorizontalFit")
<< &expectedBaselineScaled
<< &expectedBaselineTop;
QTest::newRow("fixed line height top align with padding")
<< "hello world"
<< "hello\nworld"
<< QByteArray("height: 200; topPadding: 10; bottomPadding: 20; lineHeightMode: Text.FixedHeight; lineHeight: 20; verticalAlignment: Text.AlignTop")
<< &expectedBaselineTop
<< &expectedBaselineTop;
QTest::newRow("fixed line height bottom align with padding")
<< "hello world"
<< "hello\nworld"
<< QByteArray("height: 200; topPadding: 10; bottomPadding: 20; lineHeightMode: Text.FixedHeight; lineHeight: 20; verticalAlignment: Text.AlignBottom")
<< &expectedBaselineFixedBottom
<< &expectedBaselineFixedBottom;
QTest::newRow("proportional line height top align with padding")
<< "hello world"
<< "hello\nworld"
<< QByteArray("height: 200; topPadding: 10; bottomPadding: 20; lineHeightMode: Text.ProportionalHeight; lineHeight: 1.5; verticalAlignment: Text.AlignTop")
<< &expectedBaselineTop
<< &expectedBaselineTop;
QTest::newRow("proportional line height bottom align with padding")
<< "hello world"
<< "hello\nworld"
<< QByteArray("height: 200; topPadding: 10; bottomPadding: 20; lineHeightMode: Text.ProportionalHeight; lineHeight: 1.5; verticalAlignment: Text.AlignBottom")
<< &expectedBaselineProportionalBottom
<< &expectedBaselineProportionalBottom;
}
void tst_qquicktext::baselineOffset()
{
QFETCH(QString, text);
QFETCH(QString, wrappedText);
QFETCH(QByteArray, bindings);
QFETCH(ExpectedBaseline, expectedBaseline);
QFETCH(ExpectedBaseline, expectedBaselineEmpty);
QQmlComponent component(&engine);
component.setData(
"import QtQuick 2.6\n"
"Text {\n"
+ bindings + "\n"
"}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickText *item = qobject_cast<QQuickText *>(object.data());
QVERIFY(item);
{
qreal baseline = expectedBaselineEmpty(item);
QCOMPARE(item->baselineOffset(), baseline);
item->setText(text);
if (expectedBaseline != expectedBaselineEmpty)
baseline = expectedBaseline(item);
QCOMPARE(item->baselineOffset(), baseline);
item->setText(wrappedText);
QCOMPARE(item->baselineOffset(), expectedBaseline(item));
}
QFont font = item->font();
font.setPointSize(font.pointSize() + 8);
{
QCOMPARE(item->baselineOffset(), expectedBaseline(item));
item->setText(text);
qreal baseline = expectedBaseline(item);
QCOMPARE(item->baselineOffset(), baseline);
item->setText(QString());
if (expectedBaselineEmpty != expectedBaseline)
baseline = expectedBaselineEmpty(item);
QCOMPARE(item->baselineOffset(), baseline);
}
}
void tst_qquicktext::htmlLists()
{
QFETCH(QString, text);
QFETCH(int, nbLines);
QQuickView *view = createView(testFile("htmlLists.qml"));
QQuickText *textObject = view->rootObject()->findChild<QQuickText*>("myText");
QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(textObject);
QVERIFY(textPrivate != nullptr);
QVERIFY(textPrivate->extra.isAllocated());
QVERIFY(textObject != nullptr);
textObject->setText(text);
view->show();
view->requestActivate();
QVERIFY(QTest::qWaitForWindowActive(view));
QCOMPARE(textPrivate->extra->doc->lineCount(), nbLines);
delete view;
}
void tst_qquicktext::htmlLists_data()
{
QTest::addColumn<QString>("text");
QTest::addColumn<int>("nbLines");
QTest::newRow("ordered list") << "<ol><li>one<li>two<li>three" << 3;
QTest::newRow("ordered list closed") << "<ol><li>one</li></ol>" << 1;
QTest::newRow("ordered list alpha") << "<ol type=\"a\"><li>one</li><li>two</li></ol>" << 2;
QTest::newRow("ordered list upper alpha") << "<ol type=\"A\"><li>one</li><li>two</li></ol>" << 2;
QTest::newRow("ordered list roman") << "<ol type=\"i\"><li>one</li><li>two</li></ol>" << 2;
QTest::newRow("ordered list upper roman") << "<ol type=\"I\"><li>one</li><li>two</li></ol>" << 2;
QTest::newRow("ordered list bad") << "<ol type=\"z\"><li>one</li><li>two</li></ol>" << 2;
QTest::newRow("unordered list") << "<ul><li>one<li>two" << 2;
QTest::newRow("unordered list closed") << "<ul><li>one</li><li>two</li></ul>" << 2;
QTest::newRow("unordered list disc") << "<ul type=\"disc\"><li>one</li><li>two</li></ul>" << 2;
QTest::newRow("unordered list square") << "<ul type=\"square\"><li>one</li><li>two</li></ul>" << 2;
QTest::newRow("unordered list bad") << "<ul type=\"bad\"><li>one</li><li>two</li></ul>" << 2;
}
void tst_qquicktext::elideBeforeMaximumLineCount()
{ // QTBUG-31471
QQmlComponent component(&engine, testFile("elideBeforeMaximumLineCount.qml"));
QScopedPointer<QObject> object(component.create());
QQuickText *item = qobject_cast<QQuickText *>(object.data());
QVERIFY(item);
QCOMPARE(item->lineCount(), 2);
}
void tst_qquicktext::hover()
{ // QTBUG-33842
QQmlComponent component(&engine, testFile("hover.qml"));
QScopedPointer<QObject> object(component.create());
QQuickWindow *window = qobject_cast<QQuickWindow *>(object.data());
QVERIFY(window);
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window));
QQuickMouseArea *mouseArea = window->property("mouseArea").value<QQuickMouseArea *>();
QVERIFY(mouseArea);
QQuickText *textItem = window->property("textItem").value<QQuickText *>();
QVERIFY(textItem);
QVERIFY(!mouseArea->property("wasHovered").toBool());
QPoint center(window->width() / 2, window->height() / 2);
QPoint delta(window->width() / 10, window->height() / 10);
QTest::mouseMove(window, center - delta);
QTest::mouseMove(window, center + delta);
QVERIFY(mouseArea->property("wasHovered").toBool());
}
void tst_qquicktext::growFromZeroWidth()
{
QQmlComponent component(&engine, testFile("growFromZeroWidth.qml"));
QScopedPointer<QObject> object(component.create());
QQuickText *text = qobject_cast<QQuickText *>(object.data());
QVERIFY(text);
QCOMPARE(text->lineCount(), 3);
text->setWidth(80);
// the new width should force our contents to wrap
QVERIFY(text->lineCount() > 3);
}
void tst_qquicktext::padding()
{
QScopedPointer<QQuickView> window(new QQuickView);
window->setSource(testFileUrl("padding.qml"));
QTRY_COMPARE(window->status(), QQuickView::Ready);
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
QQuickItem *root = window->rootObject();
QVERIFY(root);
QQuickText *obj = qobject_cast<QQuickText*>(root);
QVERIFY(obj != nullptr);
qreal cw = obj->contentWidth();
qreal ch = obj->contentHeight();
QVERIFY(cw > 0);
QVERIFY(ch > 0);
QCOMPARE(obj->topPadding(), 20.0);
QCOMPARE(obj->leftPadding(), 30.0);
QCOMPARE(obj->rightPadding(), 40.0);
QCOMPARE(obj->bottomPadding(), 50.0);
QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding());
QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding());
obj->setTopPadding(2.25);
QCOMPARE(obj->topPadding(), 2.25);
QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding());
obj->setLeftPadding(3.75);
QCOMPARE(obj->leftPadding(), 3.75);
QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding());
obj->setRightPadding(4.4);
QCOMPARE(obj->rightPadding(), 4.4);
QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding());
obj->setBottomPadding(1.11);
QCOMPARE(obj->bottomPadding(), 1.11);
QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding());
obj->setWidth(cw / 2);
obj->setElideMode(QQuickText::ElideRight);
QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding());
QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding());
obj->setElideMode(QQuickText::ElideNone);
obj->resetWidth();
obj->setWrapMode(QQuickText::WordWrap);
QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding());
QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding());
obj->setWrapMode(QQuickText::NoWrap);
obj->setText("Qt");
QVERIFY(obj->contentWidth() < cw);
QCOMPARE(obj->contentHeight(), ch);
cw = obj->contentWidth();
QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding());
QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding());
obj->setFont(QFont("Courier", 96));
QVERIFY(obj->contentWidth() > cw);
QVERIFY(obj->contentHeight() > ch);
cw = obj->contentWidth();
ch = obj->contentHeight();
QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding());
QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding());
obj->resetTopPadding();
QCOMPARE(obj->topPadding(), 10.0);
obj->resetLeftPadding();
QCOMPARE(obj->leftPadding(), 10.0);
obj->resetRightPadding();
QCOMPARE(obj->rightPadding(), 10.0);
obj->resetBottomPadding();
QCOMPARE(obj->bottomPadding(), 10.0);
obj->resetPadding();
QCOMPARE(obj->padding(), 0.0);
QCOMPARE(obj->topPadding(), 0.0);
QCOMPARE(obj->leftPadding(), 0.0);
QCOMPARE(obj->rightPadding(), 0.0);
QCOMPARE(obj->bottomPadding(), 0.0);
delete root;
}
void tst_qquicktext::hintingPreference()
{
{
QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE((int)textObject->font().hintingPreference(), (int)QFont::PreferDefaultHinting);
delete textObject;
}
{
QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.hintingPreference: Font.PreferNoHinting }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE((int)textObject->font().hintingPreference(), (int)QFont::PreferNoHinting);
delete textObject;
}
}
void tst_qquicktext::zeroWidthAndElidedDoesntRender()
{
// Tests QTBUG-34990
QQmlComponent component(&engine, testFile("ellipsisText.qml"));
QScopedPointer<QObject> object(component.create());
QQuickText *text = qobject_cast<QQuickText *>(object.data());
QVERIFY(text);
QCOMPARE(text->contentWidth(), 0.0);
QQuickText *reference = text->findChild<QQuickText *>("elidedRef");
QVERIFY(reference);
text->setWidth(10);
QCOMPARE(text->contentWidth(), reference->contentWidth());
}
void tst_qquicktext::hAlignWidthDependsOnImplicitWidth_data()
{
QTest::addColumn<QQuickText::HAlignment>("horizontalAlignment");
QTest::addColumn<QQuickText::TextElideMode>("elide");
QTest::addColumn<int>("extraWidth");
QTest::newRow("AlignHCenter, ElideNone, 0 extraWidth") << QQuickText::AlignHCenter << QQuickText::ElideNone << 0;
QTest::newRow("AlignRight, ElideNone, 0 extraWidth") << QQuickText::AlignRight << QQuickText::ElideNone << 0;
QTest::newRow("AlignHCenter, ElideRight, 0 extraWidth") << QQuickText::AlignHCenter << QQuickText::ElideRight << 0;
QTest::newRow("AlignRight, ElideRight, 0 extraWidth") << QQuickText::AlignRight << QQuickText::ElideRight << 0;
QTest::newRow("AlignHCenter, ElideNone, 20 extraWidth") << QQuickText::AlignHCenter << QQuickText::ElideNone << 20;
QTest::newRow("AlignRight, ElideNone, 20 extraWidth") << QQuickText::AlignRight << QQuickText::ElideNone << 20;
QTest::newRow("AlignHCenter, ElideRight, 20 extraWidth") << QQuickText::AlignHCenter << QQuickText::ElideRight << 20;
QTest::newRow("AlignRight, ElideRight, 20 extraWidth") << QQuickText::AlignRight << QQuickText::ElideRight << 20;
}
void tst_qquicktext::hAlignWidthDependsOnImplicitWidth()
{
QFETCH(QQuickText::HAlignment, horizontalAlignment);
QFETCH(QQuickText::TextElideMode, elide);
QFETCH(int, extraWidth);
QScopedPointer<QQuickView> window(new QQuickView);
window->setSource(testFileUrl("hAlignWidthDependsOnImplicitWidth.qml"));
QTRY_COMPARE(window->status(), QQuickView::Ready);
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
QQuickItem *rect = window->rootObject();
QVERIFY(rect);
QVERIFY(rect->setProperty("horizontalAlignment", horizontalAlignment));
QVERIFY(rect->setProperty("elide", elide));
QVERIFY(rect->setProperty("extraWidth", extraWidth));
QImage image = window->grabWindow();
const int rectX = 100 * window->screen()->devicePixelRatio();
QCOMPARE(numberOfNonWhitePixels(0, rectX - 1, image), 0);
QVERIFY(rect->setProperty("text", "this is mis-aligned"));
image = window->grabWindow();
QCOMPARE(numberOfNonWhitePixels(0, rectX - 1, image), 0);
}
void tst_qquicktext::fontInfo()
{
QQmlComponent component(&engine, testFile("fontInfo.qml"));
QScopedPointer<QObject> object(component.create());
QObject *root = object.data();
QQuickText *main = root->findChild<QQuickText *>("main");
QVERIFY(main);
QCOMPARE(main->font().pixelSize(), 1000);
QQuickText *copy = root->findChild<QQuickText *>("copy");
QVERIFY(copy);
QCOMPARE(copy->font().family(), QFontInfo(QFont()).family());
QVERIFY(copy->font().pixelSize() < 1000);
}
void tst_qquicktext::initialContentHeight()
{
QQmlComponent component(&engine, testFile("contentHeight.qml"));
QVERIFY(component.isReady());
QScopedPointer<QObject> object(component.create());
QObject *root = object.data();
QVERIFY(root);
QQuickText *text = qobject_cast<QQuickText *>(root);
QVERIFY(text);
QCOMPARE(text->height(), text->contentHeight());
}
void tst_qquicktext::implicitSizeChangeRewrap()
{
QScopedPointer<QQuickView> window(new QQuickView);
window->setSource(testFileUrl("implicitSizeChangeRewrap.qml"));
QTRY_COMPARE(window->status(), QQuickView::Ready);
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
QObject *root = window->rootObject();
QQuickText *text = root->findChild<QQuickText *>("text");
QVERIFY(text != nullptr);
QVERIFY(text->contentWidth() < window->width());
}
void tst_qquicktext::verticallyAlignedImageInTable()
{
QScopedPointer<QQuickView> window(new QQuickView);
window->setSource(testFileUrl("verticallyAlignedImageInTable.qml"));
QTRY_COMPARE(window->status(), QQuickView::Ready);
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
// Don't crash
}
void tst_qquicktext::transparentBackground()
{
if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
|| (QGuiApplication::platformName() == QLatin1String("minimal")))
QSKIP("Skipping due to grabToImage not functional on offscreen/minimal platforms");
QScopedPointer<QQuickView> window(new QQuickView);
window->setSource(testFileUrl("transparentBackground.qml"));
QTRY_COMPARE(window->status(), QQuickView::Ready);
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
QImage img = window->grabWindow();
QCOMPARE(img.isNull(), false);
QColor color = img.pixelColor(0, 0);
QCOMPARE(color.red(), 255);
QCOMPARE(color.blue(), 255);
QCOMPARE(color.green(), 255);
}
void tst_qquicktext::displaySuperscriptedTag()
{
if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
|| (QGuiApplication::platformName() == QLatin1String("minimal")))
QSKIP("Skipping due to grabToImage not functional on offscreen/minimal platforms");
QScopedPointer<QQuickView> window(new QQuickView);
window->setSource(testFileUrl("displaySuperscriptedTag.qml"));
QTRY_COMPARE(window->status(), QQuickView::Ready);
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
QQuickText *text = window->findChild<QQuickText *>("text");
QVERIFY(text);
QImage img = window->grabWindow();
QCOMPARE(img.isNull(), false);
QColor color = img.pixelColor(1, static_cast<int>(text->contentHeight()) / 4 * 3);
QCOMPARE(color.red(), 255);
QCOMPARE(color.blue(), 255);
QCOMPARE(color.green(), 255);
}
QTEST_MAIN(tst_qquicktext)
#include "tst_qquicktext.moc"