From b419ca92bef624893fa46559186b207905bc45e0 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 2 Feb 2015 17:51:16 +0100 Subject: TextInput: add support for padding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes it possible for TextField to inherit TextInput, reserve space for the decoration, and set the desired property defaults without having to create dozens of property aliases. [ChangeLog][QtQuick][TextInput] Added padding, leftPadding, topPadding, rightPadding and bottomPadding properties. Task-number: QTBUG-41559 Change-Id: Iaa7697a10a6f66685c7cae454edf4c1984d411bc Reviewed-by: Jan Arve Sæther Reviewed-by: Alan Alpert --- src/quick/items/qquickitemsmodule.cpp | 1 + src/quick/items/qquicktextinput.cpp | 201 +++++++++++++++++++-- src/quick/items/qquicktextinput_p.h | 31 ++++ src/quick/items/qquicktextinput_p_p.h | 24 +++ tests/auto/quick/qquicktextinput/data/padding.qml | 12 ++ .../quick/qquicktextinput/tst_qquicktextinput.cpp | 125 ++++++++++++- 6 files changed, 373 insertions(+), 21 deletions(-) create mode 100644 tests/auto/quick/qquicktextinput/data/padding.qml diff --git a/src/quick/items/qquickitemsmodule.cpp b/src/quick/items/qquickitemsmodule.cpp index 94bfd9ba4e..7d905a23ef 100644 --- a/src/quick/items/qquickitemsmodule.cpp +++ b/src/quick/items/qquickitemsmodule.cpp @@ -269,6 +269,7 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor) qmlRegisterType(uri, 2, 5,"PinchArea"); qmlRegisterType(uri, 2, 6, "TextEdit"); + qmlRegisterType(uri, 2, 6, "TextInput"); } static void initResources() diff --git a/src/quick/items/qquicktextinput.cpp b/src/quick/items/qquicktextinput.cpp index 1d86b4b000..91e6bf9615 100644 --- a/src/quick/items/qquicktextinput.cpp +++ b/src/quick/items/qquicktextinput.cpp @@ -776,8 +776,8 @@ QRectF QQuickTextInput::cursorRectangle() const QTextLine l = d->m_textLayout.lineForTextPosition(c); if (!l.isValid()) return QRectF(); - qreal x = l.cursorToX(c) - d->hscroll; - qreal y = l.y() - d->vscroll; + qreal x = l.cursorToX(c) - d->hscroll + leftPadding(); + qreal y = l.y() - d->vscroll + topPadding(); return QRectF(x, y, 1, l.height()); } @@ -1479,8 +1479,9 @@ void QQuickTextInput::positionAt(QQmlV4Function *args) const int QQuickTextInputPrivate::positionAt(qreal x, qreal y, QTextLine::CursorPosition position) const { - x += hscroll; - y += vscroll; + Q_Q(const QQuickTextInput); + x += hscroll - q->leftPadding(); + y += vscroll - q->topPadding(); QTextLine line = m_textLayout.lineAt(0); for (int i = 1; i < m_textLayout.lineCount(); ++i) { QTextLine nextLine = m_textLayout.lineAt(i); @@ -1741,7 +1742,7 @@ void QQuickTextInputPrivate::ensureVisible(int position, int preeditCursor, int { Q_Q(QQuickTextInput); QTextLine textLine = m_textLayout.lineForTextPosition(position + preeditCursor); - const qreal width = qMax(0, q->width()); + const qreal width = qMax(0, q->width() - q->leftPadding() - q->rightPadding()); qreal cix = 0; qreal widthUsed = 0; if (textLine.isValid()) { @@ -1804,7 +1805,7 @@ void QQuickTextInputPrivate::updateVerticalScroll() #ifndef QT_NO_IM const int preeditLength = m_textLayout.preeditAreaText().length(); #endif - const qreal height = qMax(0, q->height()); + const qreal height = qMax(0, q->height() - q->topPadding() - q->bottomPadding()); qreal heightUsed = contentSize.height(); qreal previousScroll = vscroll; @@ -1900,13 +1901,13 @@ QSGNode *QQuickTextInput::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData node->deleteContent(); node->setMatrix(QMatrix4x4()); - QPointF offset(0, 0); + QPointF offset(leftPadding(), topPadding()); if (d->autoScroll && d->m_textLayout.lineCount() > 0) { QFontMetricsF fm(d->font); // the y offset is there to keep the baseline constant in case we have script changes in the text. - offset = -QPointF(d->hscroll, d->vscroll + d->m_textLayout.lineAt(0).ascent() - fm.ascent()); + offset += -QPointF(d->hscroll, d->vscroll + d->m_textLayout.lineAt(0).ascent() - fm.ascent()); } else { - offset = -QPointF(d->hscroll, d->vscroll); + offset += -QPointF(d->hscroll, d->vscroll); } if (!d->m_textLayout.text().isEmpty() @@ -2861,7 +2862,7 @@ qreal QQuickTextInputPrivate::getImplicitWidth() const QTextLine line = layout.createLine(); line.setLineWidth(INT_MAX); - d->implicitWidth = qCeil(line.naturalTextWidth()); + d->implicitWidth = qCeil(line.naturalTextWidth()) + q->leftPadding() + q->rightPadding(); layout.endLayout(); } @@ -2869,6 +2870,54 @@ qreal QQuickTextInputPrivate::getImplicitWidth() const return implicitWidth; } +void QQuickTextInputPrivate::setTopPadding(qreal value, bool reset) +{ + Q_Q(QQuickTextInput); + qreal oldPadding = q->topPadding(); + topPadding = value; + explicitTopPadding = !reset; + if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding))) { + updateLayout(); + emit q->topPaddingChanged(); + } +} + +void QQuickTextInputPrivate::setLeftPadding(qreal value, bool reset) +{ + Q_Q(QQuickTextInput); + qreal oldPadding = q->leftPadding(); + leftPadding = value; + explicitLeftPadding = !reset; + if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding))) { + updateLayout(); + emit q->leftPaddingChanged(); + } +} + +void QQuickTextInputPrivate::setRightPadding(qreal value, bool reset) +{ + Q_Q(QQuickTextInput); + qreal oldPadding = q->rightPadding(); + rightPadding = value; + explicitRightPadding = !reset; + if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding))) { + updateLayout(); + emit q->rightPaddingChanged(); + } +} + +void QQuickTextInputPrivate::setBottomPadding(qreal value, bool reset) +{ + Q_Q(QQuickTextInput); + qreal oldPadding = q->bottomPadding(); + bottomPadding = value; + explicitBottomPadding = !reset; + if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding))) { + updateLayout(); + emit q->bottomPaddingChanged(); + } +} + void QQuickTextInputPrivate::updateLayout() { Q_Q(QQuickTextInput); @@ -2894,12 +2943,12 @@ void QQuickTextInputPrivate::updateLayout() line.setLineWidth(INT_MAX); const bool wasInLayout = inLayout; inLayout = true; - q->setImplicitWidth(qCeil(line.naturalTextWidth())); + q->setImplicitWidth(qCeil(line.naturalTextWidth()) + q->leftPadding() + q->rightPadding()); inLayout = wasInLayout; if (inLayout) // probably the result of a binding loop, but by letting it return; // get this far we'll get a warning to that effect. } - qreal lineWidth = q->widthValid() ? q->width() : INT_MAX; + qreal lineWidth = q->widthValid() ? q->width() - q->leftPadding() - q->rightPadding() : INT_MAX; qreal height = 0; qreal width = 0; do { @@ -2926,9 +2975,9 @@ void QQuickTextInputPrivate::updateLayout() q->update(); if (!requireImplicitWidth && !q->widthValid()) - q->setImplicitSize(width, height); + q->setImplicitSize(width + q->leftPadding() + q->rightPadding(), height + q->topPadding() + q->bottomPadding()); else - q->setImplicitHeight(height); + q->setImplicitHeight(height + q->topPadding() + q->bottomPadding()); updateBaselineOffset(); @@ -2950,13 +2999,13 @@ void QQuickTextInputPrivate::updateBaselineOffset() QFontMetricsF fm(font); qreal yoff = 0; if (q->heightValid()) { - const qreal surplusHeight = q->height() - contentSize.height(); + const qreal surplusHeight = q->height() - contentSize.height() - q->topPadding() - q->bottomPadding(); if (vAlign == QQuickTextInput::AlignBottom) yoff = surplusHeight; else if (vAlign == QQuickTextInput::AlignVCenter) yoff = surplusHeight/2; } - q->setBaselineOffset(fm.ascent() + yoff); + q->setBaselineOffset(fm.ascent() + yoff + q->topPadding()); } #ifndef QT_NO_CLIPBOARD @@ -4487,5 +4536,125 @@ void QQuickTextInput::ensureVisible(int position) updateCursorRectangle(false); } +/*! + \since 5.6 + \qmlproperty real QtQuick::TextInput::padding + \qmlproperty real QtQuick::TextInput::topPadding + \qmlproperty real QtQuick::TextInput::leftPadding + \qmlproperty real QtQuick::TextInput::bottomPadding + \qmlproperty real QtQuick::TextInput::rightPadding + + These properties hold the padding around the content. This space is reserved + in addition to the contentWidth and contentHeight. +*/ +qreal QQuickTextInput::padding() const +{ + Q_D(const QQuickTextInput); + return d->padding; +} + +void QQuickTextInput::setPadding(qreal padding) +{ + Q_D(QQuickTextInput); + if (qFuzzyCompare(d->padding, padding)) + return; + d->padding = padding; + d->updateLayout(); + emit paddingChanged(); + if (!d->explicitTopPadding) + emit topPaddingChanged(); + if (!d->explicitLeftPadding) + emit leftPaddingChanged(); + if (!d->explicitRightPadding) + emit rightPaddingChanged(); + if (!d->explicitBottomPadding) + emit bottomPaddingChanged(); +} + +void QQuickTextInput::resetPadding() +{ + setPadding(0); +} + +qreal QQuickTextInput::topPadding() const +{ + Q_D(const QQuickTextInput); + if (d->explicitTopPadding) + return d->topPadding; + return d->padding; +} + +void QQuickTextInput::setTopPadding(qreal padding) +{ + Q_D(QQuickTextInput); + d->setTopPadding(padding); +} + +void QQuickTextInput::resetTopPadding() +{ + Q_D(QQuickTextInput); + d->setTopPadding(0, true); +} + +qreal QQuickTextInput::leftPadding() const +{ + Q_D(const QQuickTextInput); + if (d->explicitLeftPadding) + return d->leftPadding; + return d->padding; +} + +void QQuickTextInput::setLeftPadding(qreal padding) +{ + Q_D(QQuickTextInput); + d->setLeftPadding(padding); +} + +void QQuickTextInput::resetLeftPadding() +{ + Q_D(QQuickTextInput); + d->setLeftPadding(0, true); +} + +qreal QQuickTextInput::rightPadding() const +{ + Q_D(const QQuickTextInput); + if (d->explicitRightPadding) + return d->rightPadding; + return d->padding; +} + +void QQuickTextInput::setRightPadding(qreal padding) +{ + Q_D(QQuickTextInput); + d->setRightPadding(padding); +} + +void QQuickTextInput::resetRightPadding() +{ + Q_D(QQuickTextInput); + d->setRightPadding(0, true); +} + +qreal QQuickTextInput::bottomPadding() const +{ + Q_D(const QQuickTextInput); + if (d->explicitBottomPadding) + return d->bottomPadding; + return d->padding; +} + +void QQuickTextInput::setBottomPadding(qreal padding) +{ + Q_D(QQuickTextInput); + d->setBottomPadding(padding); +} + +void QQuickTextInput::resetBottomPadding() +{ + Q_D(QQuickTextInput); + d->setBottomPadding(0, true); +} + QT_END_NAMESPACE diff --git a/src/quick/items/qquicktextinput_p.h b/src/quick/items/qquicktextinput_p.h index 78e6a6be7e..82a9cf9728 100644 --- a/src/quick/items/qquicktextinput_p.h +++ b/src/quick/items/qquicktextinput_p.h @@ -96,6 +96,12 @@ class Q_QUICK_PRIVATE_EXPORT QQuickTextInput : public QQuickImplicitSizeItem Q_PROPERTY(qreal contentHeight READ contentHeight NOTIFY contentSizeChanged) Q_PROPERTY(RenderType renderType READ renderType WRITE setRenderType NOTIFY renderTypeChanged) + Q_PROPERTY(qreal padding READ padding WRITE setPadding RESET resetPadding NOTIFY paddingChanged REVISION 6) + Q_PROPERTY(qreal topPadding READ topPadding WRITE setTopPadding RESET resetTopPadding NOTIFY topPaddingChanged REVISION 6) + Q_PROPERTY(qreal leftPadding READ leftPadding WRITE setLeftPadding RESET resetLeftPadding NOTIFY leftPaddingChanged REVISION 6) + Q_PROPERTY(qreal rightPadding READ rightPadding WRITE setRightPadding RESET resetRightPadding NOTIFY rightPaddingChanged REVISION 6) + Q_PROPERTY(qreal bottomPadding READ bottomPadding WRITE setBottomPadding RESET resetBottomPadding NOTIFY bottomPaddingChanged REVISION 6) + public: QQuickTextInput(QQuickItem * parent=0); ~QQuickTextInput(); @@ -260,6 +266,26 @@ public: qreal contentWidth() const; qreal contentHeight() const; + qreal padding() const; + void setPadding(qreal padding); + void resetPadding(); + + qreal topPadding() const; + void setTopPadding(qreal padding); + void resetTopPadding(); + + qreal leftPadding() const; + void setLeftPadding(qreal padding); + void resetLeftPadding(); + + qreal rightPadding() const; + void setRightPadding(qreal padding); + void resetRightPadding(); + + qreal bottomPadding() const; + void setBottomPadding(qreal padding); + void resetBottomPadding(); + Q_SIGNALS: void textChanged(); void cursorPositionChanged(); @@ -300,6 +326,11 @@ Q_SIGNALS: void contentSizeChanged(); void inputMethodHintsChanged(); void renderTypeChanged(); + Q_REVISION(6) void paddingChanged(); + Q_REVISION(6) void topPaddingChanged(); + Q_REVISION(6) void leftPaddingChanged(); + Q_REVISION(6) void rightPaddingChanged(); + Q_REVISION(6) void bottomPaddingChanged(); private: void invalidateFontCaches(); diff --git a/src/quick/items/qquicktextinput_p_p.h b/src/quick/items/qquicktextinput_p_p.h index 7596eae570..bb08c6226e 100644 --- a/src/quick/items/qquicktextinput_p_p.h +++ b/src/quick/items/qquicktextinput_p_p.h @@ -74,6 +74,15 @@ public: QQuickTextInputPrivate() : hscroll(0) , vscroll(0) + , padding(0) + , topPadding(0) + , leftPadding(0) + , rightPadding(0) + , bottomPadding(0) + , explicitTopPadding(false) + , explicitLeftPadding(false) + , explicitRightPadding(false) + , explicitBottomPadding(false) , cursorItem(0) , textNode(0) , m_maskData(0) @@ -189,6 +198,16 @@ public: qreal hscroll; qreal vscroll; + qreal padding; + qreal topPadding; + qreal leftPadding; + qreal rightPadding; + qreal bottomPadding; + bool explicitTopPadding; + bool explicitLeftPadding; + bool explicitRightPadding; + bool explicitBottomPadding; + QTextLayout m_textLayout; QString m_text; QString m_inputMask; @@ -420,6 +439,11 @@ public: qreal getImplicitWidth() const Q_DECL_OVERRIDE; + void setTopPadding(qreal value, bool reset = false); + void setLeftPadding(qreal value, bool reset = false); + void setRightPadding(qreal value, bool reset = false); + void setBottomPadding(qreal value, bool reset = false); + private: void removeSelectedText(); void internalSetText(const QString &txt, int pos = -1, bool edited = true); diff --git a/tests/auto/quick/qquicktextinput/data/padding.qml b/tests/auto/quick/qquicktextinput/data/padding.qml new file mode 100644 index 0000000000..23bfe20f22 --- /dev/null +++ b/tests/auto/quick/qquicktextinput/data/padding.qml @@ -0,0 +1,12 @@ +import QtQuick 2.6 + +TextInput { + width: 200; height: 200 + text: "Hello Qt" + + padding: 10 + topPadding: 20 + leftPadding: 30 + rightPadding: 40 + bottomPadding: 50 +} diff --git a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp index 74aba5b4c7..2c6003998e 100644 --- a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp +++ b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp @@ -225,6 +225,7 @@ private slots: void baselineOffset(); void ensureVisible(); + void padding(); private: void simulateKey(QWindow *, int key); @@ -6371,25 +6372,25 @@ Q_DECLARE_METATYPE(ExpectedBaseline) static qreal expectedBaselineTop(QQuickTextInput *item) { QFontMetricsF fm(item->font()); - return fm.ascent(); + return fm.ascent() + item->topPadding(); } static qreal expectedBaselineBottom(QQuickTextInput *item) { QFontMetricsF fm(item->font()); - return item->height() - item->contentHeight() + fm.ascent(); + return item->height() - item->contentHeight() - item->bottomPadding() + fm.ascent(); } static qreal expectedBaselineCenter(QQuickTextInput *item) { QFontMetricsF fm(item->font()); - return ((item->height() - item->contentHeight()) / 2) + fm.ascent(); + return ((item->height() - item->contentHeight() - item->topPadding() - item->bottomPadding()) / 2) + fm.ascent() + item->topPadding(); } static qreal expectedBaselineMultilineBottom(QQuickTextInput *item) { QFontMetricsF fm(item->font()); - return item->height() - item->contentHeight() + fm.ascent(); + return item->height() - item->contentHeight() - item->bottomPadding() + fm.ascent(); } void tst_qquicktextinput::baselineOffset_data() @@ -6434,6 +6435,41 @@ void tst_qquicktextinput::baselineOffset_data() << -1. << &expectedBaselineMultilineBottom << &expectedBaselineBottom; + + QTest::newRow("padding") + << "Typography" + << QByteArray("topPadding: 10; bottomPadding: 20") + << -1. + << &expectedBaselineTop + << &expectedBaselineTop; + + QTest::newRow("top align with padding") + << "Typography" + << QByteArray("height: 200; verticalAlignment: Text.AlignTop; topPadding: 10; bottomPadding: 20") + << -1. + << &expectedBaselineTop + << &expectedBaselineTop; + + QTest::newRow("bottom align with padding") + << "Typography" + << QByteArray("height: 200; verticalAlignment: Text.AlignBottom; topPadding: 10; bottomPadding: 20") + << 100. + << &expectedBaselineBottom + << &expectedBaselineBottom; + + QTest::newRow("center align with padding") + << "Typography" + << QByteArray("height: 200; verticalAlignment: Text.AlignVCenter; topPadding: 10; bottomPadding: 20") + << 100. + << &expectedBaselineCenter + << &expectedBaselineCenter; + + QTest::newRow("multiline bottom aligned with padding") + << "The quick brown fox jumps over the lazy dog" + << QByteArray("height: 200; width: 30; verticalAlignment: Text.AlignBottom; wrapMode: TextInput.WordWrap; topPadding: 10; bottomPadding: 20") + << -1. + << &expectedBaselineMultilineBottom + << &expectedBaselineBottom; } void tst_qquicktextinput::baselineOffset() @@ -6446,7 +6482,7 @@ void tst_qquicktextinput::baselineOffset() QQmlComponent component(&engine); component.setData( - "import QtQuick 2.0\n" + "import QtQuick 2.6\n" "TextInput {\n" + bindings + "\n" "}", QUrl()); @@ -6511,6 +6547,85 @@ void tst_qquicktextinput::ensureVisible() QCOMPARE(input->boundingRect().height(), line.height()); } +void tst_qquicktextinput::padding() +{ + QScopedPointer 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); + QQuickTextInput *obj = qobject_cast(root); + QVERIFY(obj != 0); + + qreal cw = obj->contentWidth(); + qreal ch = obj->contentHeight(); + + QVERIFY(cw > 0); + QVERIFY(ch > 0); + + QCOMPARE(obj->padding(), 10.0); + QCOMPARE(obj->topPadding(), 20.0); + QCOMPARE(obj->leftPadding(), 30.0); + QCOMPARE(obj->rightPadding(), 40.0); + QCOMPARE(obj->bottomPadding(), 50.0); + + QCOMPARE(obj->implicitWidth(), qCeil(cw) + obj->leftPadding() + obj->rightPadding()); + QCOMPARE(obj->implicitHeight(), qCeil(ch) + obj->topPadding() + obj->bottomPadding()); + + obj->setTopPadding(2.25); + QCOMPARE(obj->topPadding(), 2.25); + QCOMPARE(obj->implicitHeight(), qCeil(ch) + obj->topPadding() + obj->bottomPadding()); + + obj->setLeftPadding(3.75); + QCOMPARE(obj->leftPadding(), 3.75); + QCOMPARE(obj->implicitWidth(), qCeil(cw) + obj->leftPadding() + obj->rightPadding()); + + obj->setRightPadding(4.4); + QCOMPARE(obj->rightPadding(), 4.4); + QCOMPARE(obj->implicitWidth(), qCeil(cw) + obj->leftPadding() + obj->rightPadding()); + + obj->setBottomPadding(1.11); + QCOMPARE(obj->bottomPadding(), 1.11); + QCOMPARE(obj->implicitHeight(), qCeil(ch) + obj->topPadding() + obj->bottomPadding()); + + obj->setText("Qt"); + QVERIFY(obj->contentWidth() < cw); + QCOMPARE(obj->contentHeight(), ch); + cw = obj->contentWidth(); + + QCOMPARE(obj->implicitWidth(), qCeil(cw) + obj->leftPadding() + obj->rightPadding()); + QCOMPARE(obj->implicitHeight(), qCeil(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(), qCeil(cw) + obj->leftPadding() + obj->rightPadding()); + QCOMPARE(obj->implicitHeight(), qCeil(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; +} + QTEST_MAIN(tst_qquicktextinput) #include "tst_qquicktextinput.moc" -- cgit v1.2.3