From 1037a7f7123dcf9f7b48a6a74118f746ddcbf3d5 Mon Sep 17 00:00:00 2001 From: J-P Nurmi <jpnurmi@digia.com> Date: Tue, 23 Apr 2013 15:29:56 +0200 Subject: [PATCH] Add QQuickText::hoveredLink Task-number: QTBUG-30804 Change-Id: I6c6993b152285f4bdf34d6e1aa04f25fa7ca41e0 Reviewed-by: Alan Alpert <aalpert@blackberry.com> --- src/quick/items/qquickitemsmodule.cpp | 1 + src/quick/items/qquicktext.cpp | 80 +++++++++ src/quick/items/qquicktext_p.h | 8 + src/quick/items/qquicktext_p_p.h | 4 + .../auto/quick/qquicktext/tst_qquicktext.cpp | 168 +++++++++++++----- 5 files changed, 212 insertions(+), 49 deletions(-) diff --git a/src/quick/items/qquickitemsmodule.cpp b/src/quick/items/qquickitemsmodule.cpp index 9b3f6ee362..6f1edc718a 100644 --- a/src/quick/items/qquickitemsmodule.cpp +++ b/src/quick/items/qquickitemsmodule.cpp @@ -236,6 +236,7 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor) qmlRegisterType<QQuickGridView, 1>(uri, 2, 1, "GridView"); qmlRegisterType<QQuickTextEdit, 1>(uri, 2, 1, "TextEdit"); + qmlRegisterType<QQuickText, 2>(uri, 2, 2, "Text"); qmlRegisterType<QQuickTextEdit, 2>(uri, 2, 2, "TextEdit"); } diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp index b46f2e5ab9..cc2cbb3cb3 100644 --- a/src/quick/items/qquicktext.cpp +++ b/src/quick/items/qquicktext.cpp @@ -107,6 +107,7 @@ void QQuickTextPrivate::init() Q_Q(QQuickText); q->setAcceptedMouseButtons(Qt::LeftButton); q->setFlag(QQuickItem::ItemHasContents); + q->setAcceptHoverEvents(true); } QQuickTextDocumentWithImageResources::QQuickTextDocumentWithImageResources(QQuickItem *parent) @@ -2581,4 +2582,83 @@ void QQuickText::mouseReleaseEvent(QMouseEvent *event) QQuickItem::mouseReleaseEvent(event); } +bool QQuickTextPrivate::isLinkHoveredConnected() +{ + Q_Q(QQuickText); + IS_SIGNAL_CONNECTED(q, QQuickText, linkHovered, (const QString &)); +} + +/*! + \qmlsignal QtQuick2::Text::onLinkHovered(string link) + \since QtQuick 2.2 + + This handler is called when the user hovers a link embedded in the + text. The link must be in rich text or HTML format and the \a link + string provides access to the particular link. + + \sa hoveredLink +*/ + +/*! + \qmlproperty string QtQuick2::Text::hoveredLink + \since QtQuick 2.2 + + This property contains the link string when user hovers a link + embedded in the text. The link must be in rich text or HTML format + and the \a hoveredLink string provides access to the particular link. + + \sa onLinkHovered +*/ + +QString QQuickText::hoveredLink() const +{ + Q_D(const QQuickText); + if (const_cast<QQuickTextPrivate *>(d)->isLinkHoveredConnected()) { + if (d->extra.isAllocated()) + return d->extra->hoveredLink; + } else { +#ifndef QT_NO_CURSOR + if (QQuickWindow *wnd = window()) { + QPointF pos = QCursor::pos(wnd->screen()) - wnd->position() - mapToScene(QPointF(0, 0)); + return d->anchorAt(pos); + } +#endif // QT_NO_CURSOR + } + return QString(); +} + +void QQuickTextPrivate::processHoverEvent(QHoverEvent *event) +{ + Q_Q(QQuickText); + QString link; + if (event->type() != QEvent::HoverLeave) + link = anchorAt(event->posF()); + + if ((!extra.isAllocated() && !link.isEmpty()) || (extra.isAllocated() && extra->hoveredLink != link)) { + extra.value().hoveredLink = link; + emit q->linkHovered(extra->hoveredLink); + } +} + +void QQuickText::hoverEnterEvent(QHoverEvent *event) +{ + Q_D(QQuickText); + if (d->isLinkHoveredConnected()) + d->processHoverEvent(event); +} + +void QQuickText::hoverMoveEvent(QHoverEvent *event) +{ + Q_D(QQuickText); + if (d->isLinkHoveredConnected()) + d->processHoverEvent(event); +} + +void QQuickText::hoverLeaveEvent(QHoverEvent *event) +{ + Q_D(QQuickText); + if (d->isLinkHoveredConnected()) + d->processHoverEvent(event); +} + QT_END_NAMESPACE diff --git a/src/quick/items/qquicktext_p.h b/src/quick/items/qquicktext_p.h index 03b436b3fb..f34cf17e5d 100644 --- a/src/quick/items/qquicktext_p.h +++ b/src/quick/items/qquicktext_p.h @@ -90,6 +90,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickText : public QQuickImplicitSizeItem Q_PROPERTY(int minimumPointSize READ minimumPointSize WRITE setMinimumPointSize NOTIFY minimumPointSizeChanged) Q_PROPERTY(FontSizeMode fontSizeMode READ fontSizeMode WRITE setFontSizeMode NOTIFY fontSizeModeChanged) Q_PROPERTY(RenderType renderType READ renderType WRITE setRenderType NOTIFY renderTypeChanged) + Q_PROPERTY(QString hoveredLink READ hoveredLink NOTIFY linkHovered REVISION 2) public: QQuickText(QQuickItem *parent=0); @@ -207,9 +208,12 @@ public: RenderType renderType() const; void setRenderType(RenderType renderType); + QString hoveredLink() const; + Q_SIGNALS: void textChanged(const QString &text); void linkActivated(const QString &link); + Q_REVISION(2) void linkHovered(const QString &link); void fontChanged(const QFont &font); void colorChanged(); void linkColorChanged(); @@ -243,6 +247,10 @@ protected: void updatePolish(); + void hoverEnterEvent(QHoverEvent *event); + void hoverMoveEvent(QHoverEvent *event); + void hoverLeaveEvent(QHoverEvent *event); + private Q_SLOTS: void q_imagesLoaded(); void triggerPreprocess(); diff --git a/src/quick/items/qquicktext_p_p.h b/src/quick/items/qquicktext_p_p.h index ff6b0f20be..7a31e77ae4 100644 --- a/src/quick/items/qquicktext_p_p.h +++ b/src/quick/items/qquicktext_p_p.h @@ -87,6 +87,8 @@ public: QString elidedText(qreal lineWidth, const QTextLine &line, QTextLine *nextLine = 0) const; void elideFormats(int start, int length, int offset, QList<QTextLayout::FormatRange> *elidedFormats); + void processHoverEvent(QHoverEvent *event); + QRectF layedOutTextRect; struct ExtraData { @@ -95,6 +97,7 @@ public: qreal lineHeight; QQuickTextDocumentWithImageResources *doc; QString activeLink; + QString hoveredLink; int minimumPixelSize; int minimumPointSize; int nbActiveDownloads; @@ -167,6 +170,7 @@ public: QRectF setupTextLayout(qreal * const baseline); void setupCustomLineGeometry(QTextLine &line, qreal &height, int lineOffset = 0); bool isLinkActivatedConnected(); + bool isLinkHoveredConnected(); static QString anchorAt(const QTextLayout *layout, const QPointF &mousePos); QString anchorAt(const QPointF &pos) const; diff --git a/tests/auto/quick/qquicktext/tst_qquicktext.cpp b/tests/auto/quick/qquicktext/tst_qquicktext.cpp index fb3b62b8d2..fdaa1d6617 100644 --- a/tests/auto/quick/qquicktext/tst_qquicktext.cpp +++ b/tests/auto/quick/qquicktext/tst_qquicktext.cpp @@ -110,8 +110,8 @@ private slots: void letterSpacing(); void wordSpacing(); - void clickLink_data(); - void clickLink(); + void linkInteraction_data(); + void linkInteraction(); void implicitSize_data(); void implicitSize(); @@ -1482,15 +1482,30 @@ void tst_qquicktext::wordSpacing() class EventSender : public QQuickItem { public: - void sendEvent(QMouseEvent *event) { - if (event->type() == QEvent::MouseButtonPress) - mousePressEvent(event); - else if (event->type() == QEvent::MouseButtonRelease) - mouseReleaseEvent(event); - else if (event->type() == QEvent::MouseMove) - mouseMoveEvent(event); - else + 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; + } } }; @@ -1500,10 +1515,12 @@ class LinkTest : public QObject public: LinkTest() {} - QString link; + QString clickedLink; + QString hoveredLink; public slots: - void linkClicked(QString l) { link = l; } + void linkClicked(QString l) { clickedLink = l; } + void linkHovered(QString l) { hoveredLink = l; } }; class TextMetrics @@ -1589,13 +1606,15 @@ public: typedef QVector<QPointF> PointVector; Q_DECLARE_METATYPE(PointVector); -void tst_qquicktext::clickLink_data() +void tst_qquicktext::linkInteraction_data() { QTest::addColumn<QString>("text"); QTest::addColumn<qreal>("width"); QTest::addColumn<QString>("bindings"); QTest::addColumn<PointVector>("mousePositions"); - QTest::addColumn<QString>("link"); + 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"; @@ -1612,196 +1631,229 @@ void tst_qquicktext::clickLink_data() << singleLineText << 240. << "" << (PointVector() << metrics.characterRectangle(18).center()) - << singleLineLink; + << singleLineLink + << singleLineLink << singleLineLink; QTest::newRow("click on text") << singleLineText << 240. << "" << (PointVector() << metrics.characterRectangle(13).center()) - << QString(); + << QString() + << QString() << QString(); QTest::newRow("drag within link") << singleLineText << 240. << "" << (PointVector() << metrics.characterRectangle(17).center() << metrics.characterRectangle(19).center()) - << singleLineLink; + << singleLineLink + << singleLineLink << singleLineLink; QTest::newRow("drag away from link") << singleLineText << 240. << "" << (PointVector() << metrics.characterRectangle(18).center() << metrics.characterRectangle(13).center()) - << QString(); + << QString() + << singleLineLink << QString(); QTest::newRow("drag on to link") << singleLineText << 240. << "" << (PointVector() << metrics.characterRectangle(13).center() << metrics.characterRectangle(18).center()) - << QString(); + << 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 << 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 << singleLineLink; QTest::newRow("click on rich text link") << singleLineText << 240. << "textFormat: Text.RichText" << (PointVector() << metrics.characterRectangle(18).center()) - << singleLineLink; + << singleLineLink + << singleLineLink << singleLineLink; QTest::newRow("click on rich text") << singleLineText << 240. << "textFormat: Text.RichText" << (PointVector() << metrics.characterRectangle(13).center()) - << QString(); + << 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 << 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 << 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 << 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 << 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 << multipleLineLink; QTest::newRow("click on third line") << multipleLineText << 240. << "" << (PointVector() << metrics.characterRectangle(25).center()) - << multipleLineLink; + << 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 << multipleLineLink; QTest::newRow("click on rich text second line") << multipleLineText << 240. << "textFormat: Text.RichText" << (PointVector() << metrics.characterRectangle(18).center()) - << multipleLineLink; + << multipleLineLink + << multipleLineLink << multipleLineLink; QTest::newRow("click on rich text third line") << multipleLineText << 240. << "textFormat: Text.RichText" << (PointVector() << metrics.characterRectangle(25).center()) - << multipleLineLink; + << 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 << 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 << outerLink; QTest::newRow("click on right outer link") << nestedText << 240. << "" << (PointVector() << metrics.characterRectangle(27).center()) - << outerLink; + << outerLink + << outerLink << outerLink; QTest::newRow("click on inner link left") << nestedText << 240. << "" << (PointVector() << metrics.characterRectangle(23).center()) - << innerLink; + << innerLink + << innerLink << innerLink; QTest::newRow("click on inner link right") << nestedText << 240. << "" << (PointVector() << metrics.characterRectangle(26).center()) - << innerLink; + << innerLink + << innerLink << innerLink; QTest::newRow("drag from inner to outer link") << nestedText << 240. << "" << (PointVector() << metrics.characterRectangle(25).center() << metrics.characterRectangle(30).center()) - << QString(); + << QString() + << innerLink << outerLink; QTest::newRow("drag from outer to inner link") << nestedText << 240. << "" << (PointVector() << metrics.characterRectangle(30).center() << metrics.characterRectangle(25).center()) - << QString(); + << 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 << outerLink; QTest::newRow("click on right outer rich text link") << nestedText << 240. << "textFormat: Text.RichText" << (PointVector() << metrics.characterRectangle(27).center()) - << outerLink; + << 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 << innerLink; QTest::newRow("click on inner rich text link right") << nestedText << 240. << "textFormat: Text.RichText" << (PointVector() << metrics.characterRectangle(26).center()) - << innerLink; + << 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(); + << 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(); + << QString() + << outerLink << innerLink; } } -void tst_qquicktext::clickLink() +void tst_qquicktext::linkInteraction() { QFETCH(QString, text); QFETCH(qreal, width); QFETCH(QString, bindings); QFETCH(PointVector, mousePositions); - QFETCH(QString, link); + QFETCH(QString, clickedLink); + QFETCH(QString, hoverEnterLink); + QFETCH(QString, hoverMoveLink); QString componentStr = - "import QtQuick 2.0\nText {\n" + "import QtQuick 2.2\nText {\n" "width: " + QString::number(width) + "\n" "height: 320\n" "text: \"" + text + "\"\n" @@ -1815,28 +1867,46 @@ void tst_qquicktext::clickLink() 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); + 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); + { + 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.link, link); + QCOMPARE(test.clickedLink, clickedLink); + QCOMPARE(test.hoveredLink, QString()); + QCOMPARE(textObject->hoveredLink(), QString()); delete textObject; } -- GitLab