diff options
author | J-P Nurmi <jpnurmi@qt.io> | 2016-04-22 00:18:25 +0200 |
---|---|---|
committer | J-P Nurmi <jpnurmi@qt.io> | 2016-04-26 13:31:06 +0000 |
commit | 67f3da65d7d11af025a9bdd54d08ca0bf764c3ce (patch) | |
tree | 9fc5e79e31a82d39d25f8a00835a1115b55b08a7 /src | |
parent | 1780a817fce77f4875f001c2b95b41cd2cd06f2b (diff) |
Allow attaching TextArea to a Flickable
It is not always possible to let TextArea grow infinitely, but in
some cases it should be scrollable on its own. Even though it is not
a built-in feature of TextArea like in Qt Quick Controls 1, this change
makes it straight-forward to attach TextArea to a Flickable. This makes
TextArea behave like a standalone scrollable editor.
Change-Id: I8181e8ebe0edbcdfef2c30c9e102db7cbe4ff705
Task-number: QTBUG-51677
Task-number: QTBUG-52169
Reviewed-by: Mitch Curtis <mitch.curtis@theqtcompany.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/imports/controls/doc/images/qtquickcontrols2-textarea-flickable.png | bin | 0 -> 1942 bytes | |||
-rw-r--r-- | src/imports/controls/doc/images/qtquickcontrols2-textarea.png | bin | 2113 -> 2238 bytes | |||
-rw-r--r-- | src/imports/controls/doc/snippets/qtquickcontrols2-textarea-flickable.qml | 50 | ||||
-rw-r--r-- | src/imports/controls/doc/snippets/qtquickcontrols2-textarea.qml | 2 | ||||
-rw-r--r-- | src/imports/controls/material/TextArea.qml | 3 | ||||
-rw-r--r-- | src/imports/templates/qtquicktemplates2plugin.cpp | 1 | ||||
-rw-r--r-- | src/quicktemplates2/qquicktextarea.cpp | 224 | ||||
-rw-r--r-- | src/quicktemplates2/qquicktextarea_p.h | 26 | ||||
-rw-r--r-- | src/quicktemplates2/qquicktextarea_p_p.h | 13 |
9 files changed, 312 insertions, 7 deletions
diff --git a/src/imports/controls/doc/images/qtquickcontrols2-textarea-flickable.png b/src/imports/controls/doc/images/qtquickcontrols2-textarea-flickable.png Binary files differnew file mode 100644 index 00000000..39578f71 --- /dev/null +++ b/src/imports/controls/doc/images/qtquickcontrols2-textarea-flickable.png diff --git a/src/imports/controls/doc/images/qtquickcontrols2-textarea.png b/src/imports/controls/doc/images/qtquickcontrols2-textarea.png Binary files differindex f468bfd4..924f6681 100644 --- a/src/imports/controls/doc/images/qtquickcontrols2-textarea.png +++ b/src/imports/controls/doc/images/qtquickcontrols2-textarea.png diff --git a/src/imports/controls/doc/snippets/qtquickcontrols2-textarea-flickable.qml b/src/imports/controls/doc/snippets/qtquickcontrols2-textarea-flickable.qml new file mode 100644 index 00000000..e11750cb --- /dev/null +++ b/src/imports/controls/doc/snippets/qtquickcontrols2-textarea-flickable.qml @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtQuick.Controls 2.0 + +Item { + width: 100 + height: 100 + + Binding { target: flickable.ScrollBar.vertical; property: "active"; value: true } + + //! [1] + Flickable { + id: flickable + anchors.fill: parent + + TextArea.flickable: TextArea { + text: "TextArea\n...\n...\n...\n...\n...\n...\n" + wrapMode: TextArea.Wrap + } + + ScrollBar.vertical: ScrollBar { } + } + //! [1] +} diff --git a/src/imports/controls/doc/snippets/qtquickcontrols2-textarea.qml b/src/imports/controls/doc/snippets/qtquickcontrols2-textarea.qml index 84994008..52077f90 100644 --- a/src/imports/controls/doc/snippets/qtquickcontrols2-textarea.qml +++ b/src/imports/controls/doc/snippets/qtquickcontrols2-textarea.qml @@ -29,5 +29,5 @@ import QtQuick 2.0 import QtQuick.Controls 2.0 TextArea { - text: "Text\nArea..." + placeholderText: qsTr("Enter description") } diff --git a/src/imports/controls/material/TextArea.qml b/src/imports/controls/material/TextArea.qml index aa9cb12f..8098ac42 100644 --- a/src/imports/controls/material/TextArea.qml +++ b/src/imports/controls/material/TextArea.qml @@ -56,6 +56,7 @@ T.TextArea { selectedTextColor: Material.primaryHighlightedTextColor cursorDelegate: Rectangle { id: cursor + clip: true // TODO color: control.Material.accentColor width: 2 visible: control.activeFocus && control.selectionStart === control.selectionEnd @@ -99,7 +100,7 @@ T.TextArea { //! [background] background: Rectangle { - y: control.height - height - control.bottomPadding / 2 + y: parent.height - height - control.bottomPadding / 2 implicitWidth: 120 height: control.activeFocus ? 2 : 1 color: control.activeFocus ? control.Material.accentColor : control.Material.hintTextColor diff --git a/src/imports/templates/qtquicktemplates2plugin.cpp b/src/imports/templates/qtquicktemplates2plugin.cpp index 86adf973..ab46c7ab 100644 --- a/src/imports/templates/qtquicktemplates2plugin.cpp +++ b/src/imports/templates/qtquicktemplates2plugin.cpp @@ -148,6 +148,7 @@ void QtQuickTemplates2Plugin::registerTypes(const char *uri) qmlRegisterType<QQuickSwitchDelegate>(uri, 2, 0, "SwitchDelegate"); qmlRegisterType<QQuickTabBar>(uri, 2, 0, "TabBar"); qmlRegisterType<QQuickTabButton>(uri, 2, 0, "TabButton"); + qmlRegisterType<QQuickTextAreaAttached>(); qmlRegisterType<QQuickTextArea>(uri, 2, 0, "TextArea"); qmlRegisterType<QQuickTextField>(uri, 2, 0, "TextField"); qmlRegisterType<QQuickToolBar>(uri, 2, 0, "ToolBar"); diff --git a/src/quicktemplates2/qquicktextarea.cpp b/src/quicktemplates2/qquicktextarea.cpp index f7a281d7..92f5cf64 100644 --- a/src/quicktemplates2/qquicktextarea.cpp +++ b/src/quicktemplates2/qquicktextarea.cpp @@ -40,9 +40,11 @@ #include "qquickcontrol_p_p.h" #include <QtGui/qguiapplication.h> +#include <QtQml/qqmlinfo.h> #include <QtQuick/private/qquickitem_p.h> #include <QtQuick/private/qquicktext_p.h> #include <QtQuick/private/qquickclipnode_p.h> +#include <QtQuick/private/qquickflickable_p.h> #ifndef QT_NO_ACCESSIBILITY #include <QtQuick/private/qquickaccessibleattached_p.h> @@ -61,12 +63,37 @@ QT_BEGIN_NAMESPACE TextArea is a multi-line text editor. TextArea extends TextEdit with a \l {placeholderText}{placeholder text} functionality, and adds decoration. + \image qtquickcontrols2-textarea.png + \code TextArea { placeholderText: qsTr("Enter description") } \endcode + TextArea is not scrollable by itself. Especially on screen-size constrained + platforms, it is often preferable to make entire application pages scrollable. + On such a scrollable page, a non-scrollable TextArea might behave better than + nested scrollable controls. Notice, however, that in such a scenario, the background + decoration of the TextArea scrolls together with the rest of the scrollable + content. + + If you want to make a TextArea scrollable, for example, when it covers + an entire application page, attach it to a \l Flickable and combine with a + \l ScrollBar or \l ScrollIndicator. + + \image qtquickcontrols2-textarea-flickable.png + + \snippet qtquickcontrols2-textarea-flickable.qml 1 + + A TextArea that is attached to a Flickable does the following: + + \list + \li Sets the content size automatically + \li Ensures that the background decoration stays in place + \li Clips the content + \endlist + \sa TextField, {Customizing TextArea}, {Input Controls} */ @@ -79,7 +106,7 @@ QT_BEGIN_NAMESPACE */ QQuickTextAreaPrivate::QQuickTextAreaPrivate() - : background(nullptr), focusReason(Qt::OtherFocusReason), accessibleAttached(nullptr) + : background(nullptr), focusReason(Qt::OtherFocusReason), accessibleAttached(nullptr), flickable(nullptr) { #ifndef QT_NO_ACCESSIBILITY QAccessible::installActivationObserver(this); @@ -99,16 +126,133 @@ void QQuickTextAreaPrivate::resizeBackground() if (background) { QQuickItemPrivate *p = QQuickItemPrivate::get(background); if (!p->widthValid && qFuzzyIsNull(background->x())) { - background->setWidth(q->width()); + if (flickable) + background->setWidth(flickable->width()); + else + background->setWidth(q->width()); p->widthValid = false; } if (!p->heightValid && qFuzzyIsNull(background->y())) { - background->setHeight(q->height()); + if (flickable) + background->setHeight(flickable->height()); + else + background->setHeight(q->height()); p->heightValid = false; } } } +void QQuickTextAreaPrivate::attachFlickable(QQuickFlickable *item) +{ + Q_Q(QQuickTextArea); + flickable = item; + q->setParentItem(flickable->contentItem()); + + if (background) + background->setParentItem(flickable); + + QObjectPrivate::connect(q, &QQuickTextArea::contentSizeChanged, this, &QQuickTextAreaPrivate::resizeFlickableContent); + QObjectPrivate::connect(q, &QQuickTextEdit::cursorRectangleChanged, this, &QQuickTextAreaPrivate::ensureCursorVisible); + + QObject::connect(flickable, &QQuickFlickable::contentXChanged, q, &QQuickItem::update); + QObject::connect(flickable, &QQuickFlickable::contentYChanged, q, &QQuickItem::update); + + QQuickItemPrivate::get(flickable)->updateOrAddGeometryChangeListener(this, QQuickItemPrivate::SizeChange); + QObjectPrivate::connect(flickable, &QQuickFlickable::contentWidthChanged, this, &QQuickTextAreaPrivate::resizeFlickableControl); + QObjectPrivate::connect(flickable, &QQuickFlickable::contentHeightChanged, this, &QQuickTextAreaPrivate::resizeFlickableControl); + + resizeFlickableControl(); +} + +void QQuickTextAreaPrivate::detachFlickable() +{ + Q_Q(QQuickTextArea); + q->setParentItem(nullptr); + if (background && background->parentItem() == flickable) + background->setParentItem(q); + + QObjectPrivate::disconnect(q, &QQuickTextArea::contentSizeChanged, this, &QQuickTextAreaPrivate::resizeFlickableContent); + QObjectPrivate::disconnect(q, &QQuickTextEdit::cursorRectangleChanged, this, &QQuickTextAreaPrivate::ensureCursorVisible); + + QObject::disconnect(flickable, &QQuickFlickable::contentXChanged, q, &QQuickItem::update); + QObject::disconnect(flickable, &QQuickFlickable::contentYChanged, q, &QQuickItem::update); + + QQuickItemPrivate::get(flickable)->updateOrRemoveGeometryChangeListener(this, QQuickItemPrivate::SizeChange); + QObjectPrivate::disconnect(flickable, &QQuickFlickable::contentWidthChanged, this, &QQuickTextAreaPrivate::resizeFlickableControl); + QObjectPrivate::disconnect(flickable, &QQuickFlickable::contentHeightChanged, this, &QQuickTextAreaPrivate::resizeFlickableControl); + + flickable = nullptr; +} + +void QQuickTextAreaPrivate::ensureCursorVisible() +{ + Q_Q(QQuickTextArea); + if (!flickable) + return; + + const qreal cx = flickable->contentX(); + const qreal cy = flickable->contentY(); + const qreal w = flickable->width(); + const qreal h = flickable->height(); + + const qreal tp = q->topPadding(); + const qreal lp = q->leftPadding(); + const QRectF cr = q->cursorRectangle(); + + if (cr.left() <= cx + lp) { + flickable->setContentX(cr.left() - lp); + } else { + // calculate the rectangle of the next character and ensure that + // it's visible if it's on the same line with the cursor + const qreal rp = q->rightPadding(); + const QRectF nr = q->cursorPosition() < q->length() ? q->positionToRectangle(q->cursorPosition() + 1) : QRectF(); + if (qFuzzyCompare(nr.y(), cr.y()) && nr.right() >= cx + lp + w - rp) + flickable->setContentX(nr.right() - w + rp); + else if (cr.right() >= cx + lp + w - rp) + flickable->setContentX(cr.right() - w + rp); + } + + if (cr.top() <= cy + tp) { + flickable->setContentY(cr.top() - tp); + } else { + const qreal bp = q->bottomPadding(); + if (cr.bottom() >= cy + tp + h - bp) + flickable->setContentY(cr.bottom() - h + bp); + } +} + +void QQuickTextAreaPrivate::resizeFlickableControl() +{ + Q_Q(QQuickTextArea); + if (!flickable) + return; + + const qreal w = wrapMode == QQuickTextArea::NoWrap ? qMax(flickable->width(), flickable->contentWidth()) : flickable->width(); + const qreal h = qMax(flickable->height(), flickable->contentHeight()); + q->setSize(QSizeF(w, h)); + + resizeBackground(); +} + +void QQuickTextAreaPrivate::resizeFlickableContent() +{ + Q_Q(QQuickTextArea); + if (!flickable) + return; + + flickable->setContentWidth(q->contentWidth() + q->leftPadding() + q->rightPadding()); + flickable->setContentHeight(q->contentHeight() + q->topPadding() + q->bottomPadding()); +} + +void QQuickTextAreaPrivate::itemGeometryChanged(QQuickItem *item, const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_UNUSED(item); + Q_UNUSED(newGeometry); + Q_UNUSED(oldGeometry); + + resizeFlickableControl(); +} + qreal QQuickTextAreaPrivate::getImplicitWidth() const { return QQuickItemPrivate::getImplicitWidth(); @@ -148,6 +292,11 @@ QQuickTextArea::~QQuickTextArea() { } +QQuickTextAreaAttached *QQuickTextArea::qmlAttachedProperties(QObject *object) +{ + return new QQuickTextAreaAttached(object); +} + /*! \internal @@ -345,17 +494,29 @@ void QQuickTextArea::geometryChanged(const QRectF &newGeometry, const QRectF &ol QSGNode *QQuickTextArea::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) { + Q_D(QQuickTextArea); QQuickDefaultClipNode *clipNode = static_cast<QQuickDefaultClipNode *>(oldNode); if (!clipNode) clipNode = new QQuickDefaultClipNode(QRectF()); - clipNode->setRect(clipRect().adjusted(leftPadding(), topPadding(), -rightPadding(), -bottomPadding())); + QQuickItem *clipper = this; + if (d->flickable) + clipper = d->flickable; + + const QRectF cr = clipper->clipRect().adjusted(leftPadding(), topPadding(), -rightPadding(), -bottomPadding()); + clipNode->setRect(!d->flickable ? cr : cr.translated(d->flickable->contentX(), d->flickable->contentY())); clipNode->update(); QSGNode *textNode = QQuickTextEdit::updatePaintNode(clipNode->firstChild(), data); if (!textNode->parent()) clipNode->appendChildNode(textNode); + if (d->cursorItem) { + QQuickDefaultClipNode *cursorNode = QQuickItemPrivate::get(d->cursorItem)->clipNode(); + if (cursorNode) + cursorNode->setClipRect(d->cursorItem->mapRectFromItem(clipper, cr)); + } + return clipNode; } @@ -420,4 +581,59 @@ void QQuickTextArea::timerEvent(QTimerEvent *event) } } +class QQuickTextAreaAttachedPrivate : public QObjectPrivate +{ +public: + QQuickTextAreaAttachedPrivate() : control(nullptr) { } + + QQuickTextArea *control; +}; + +QQuickTextAreaAttached::QQuickTextAreaAttached(QObject *parent) : + QObject(*(new QQuickTextAreaAttachedPrivate), parent) +{ +} + +QQuickTextAreaAttached::~QQuickTextAreaAttached() +{ +} + +/*! + \qmlattachedproperty TextArea QtQuick.Controls::TextArea::flickable + + This property attaches a text area to a \l Flickable. + + \snippet qtquickcontrols2-textarea-flickable.qml 1 + + \sa ScrollBar, ScrollIndicator +*/ +QQuickTextArea *QQuickTextAreaAttached::flickable() const +{ + Q_D(const QQuickTextAreaAttached); + return d->control; +} + +void QQuickTextAreaAttached::setFlickable(QQuickTextArea *control) +{ + Q_D(QQuickTextAreaAttached); + QQuickFlickable *flickable = qobject_cast<QQuickFlickable *>(parent()); + if (!flickable) { + qmlInfo(parent()) << "TextArea must be attached to a Flickable"; + return; + } + + if (d->control == control) + return; + + if (d->control) + QQuickTextAreaPrivate::get(d->control)->detachFlickable(); + + d->control = control; + + if (control) + QQuickTextAreaPrivate::get(control)->attachFlickable(flickable); + + emit flickableChanged(); +} + QT_END_NAMESPACE diff --git a/src/quicktemplates2/qquicktextarea_p.h b/src/quicktemplates2/qquicktextarea_p.h index c62d35df..7c145a9a 100644 --- a/src/quicktemplates2/qquicktextarea_p.h +++ b/src/quicktemplates2/qquicktextarea_p.h @@ -55,6 +55,7 @@ QT_BEGIN_NAMESPACE class QQuickText; class QQuickTextAreaPrivate; +class QQuickTextAreaAttached; class QQuickMouseEvent; class Q_QUICKTEMPLATES2_EXPORT QQuickTextArea : public QQuickTextEdit @@ -71,6 +72,8 @@ public: explicit QQuickTextArea(QQuickItem *parent = nullptr); ~QQuickTextArea(); + static QQuickTextAreaAttached *qmlAttachedProperties(QObject *object); + QFont font() const; void setFont(const QFont &font); @@ -111,8 +114,31 @@ private: Q_DECLARE_PRIVATE(QQuickTextArea) }; +class QQuickTextAreaAttachedPrivate; + +class Q_QUICKTEMPLATES2_EXPORT QQuickTextAreaAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickTextArea *flickable READ flickable WRITE setFlickable NOTIFY flickableChanged FINAL) + +public: + explicit QQuickTextAreaAttached(QObject *parent); + ~QQuickTextAreaAttached(); + + QQuickTextArea *flickable() const; + void setFlickable(QQuickTextArea *control); + +Q_SIGNALS: + void flickableChanged(); + +private: + Q_DISABLE_COPY(QQuickTextAreaAttached) + Q_DECLARE_PRIVATE(QQuickTextAreaAttached) +}; + QT_END_NAMESPACE QML_DECLARE_TYPE(QQuickTextArea) +QML_DECLARE_TYPEINFO(QQuickTextArea, QML_HAS_ATTACHED_PROPERTIES) #endif // QQUICKTEXTAREA_P_H diff --git a/src/quicktemplates2/qquicktextarea_p_p.h b/src/quicktemplates2/qquicktextarea_p_p.h index 99ee7cdb..8af5d0d7 100644 --- a/src/quicktemplates2/qquicktextarea_p_p.h +++ b/src/quicktemplates2/qquicktextarea_p_p.h @@ -49,6 +49,7 @@ // #include <QtQuick/private/qquicktextedit_p_p.h> +#include <QtQuick/private/qquickitemchangelistener_p.h> #include <QtQuickTemplates2/private/qquickpresshandler_p_p.h> #include "qquicktextarea_p.h" @@ -59,9 +60,10 @@ QT_BEGIN_NAMESPACE +class QQuickFlickable; class QQuickAccessibleAttached; -class QQuickTextAreaPrivate : public QQuickTextEditPrivate +class QQuickTextAreaPrivate : public QQuickTextEditPrivate, public QQuickItemChangeListener #ifndef QT_NO_ACCESSIBILITY , public QAccessible::ActivationObserver #endif @@ -79,6 +81,14 @@ public: void resolveFont(); void inheritFont(const QFont &f); + void attachFlickable(QQuickFlickable *flickable); + void detachFlickable(); + void ensureCursorVisible(); + void resizeFlickableControl(); + void resizeFlickableContent(); + + void itemGeometryChanged(QQuickItem *item, const QRectF &newGeometry, const QRectF &oldGeometry) override; + qreal getImplicitWidth() const override; qreal getImplicitHeight() const override; @@ -98,6 +108,7 @@ public: Qt::FocusReason focusReason; QQuickPressHandler pressHandler; QQuickAccessibleAttached *accessibleAttached; + QQuickFlickable *flickable; }; QT_END_NAMESPACE |