aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJ-P Nurmi <jpnurmi@qt.io>2016-04-22 00:18:25 +0200
committerJ-P Nurmi <jpnurmi@qt.io>2016-04-26 13:31:06 +0000
commit67f3da65d7d11af025a9bdd54d08ca0bf764c3ce (patch)
tree9fc5e79e31a82d39d25f8a00835a1115b55b08a7
parent1780a817fce77f4875f001c2b95b41cd2cd06f2b (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>
-rw-r--r--src/imports/controls/doc/images/qtquickcontrols2-textarea-flickable.pngbin0 -> 1942 bytes
-rw-r--r--src/imports/controls/doc/images/qtquickcontrols2-textarea.pngbin2113 -> 2238 bytes
-rw-r--r--src/imports/controls/doc/snippets/qtquickcontrols2-textarea-flickable.qml50
-rw-r--r--src/imports/controls/doc/snippets/qtquickcontrols2-textarea.qml2
-rw-r--r--src/imports/controls/material/TextArea.qml3
-rw-r--r--src/imports/templates/qtquicktemplates2plugin.cpp1
-rw-r--r--src/quicktemplates2/qquicktextarea.cpp224
-rw-r--r--src/quicktemplates2/qquicktextarea_p.h26
-rw-r--r--src/quicktemplates2/qquicktextarea_p_p.h13
-rw-r--r--tests/auto/controls/data/tst_textarea.qml33
10 files changed, 345 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
new file mode 100644
index 00000000..39578f71
--- /dev/null
+++ b/src/imports/controls/doc/images/qtquickcontrols2-textarea-flickable.png
Binary files differ
diff --git a/src/imports/controls/doc/images/qtquickcontrols2-textarea.png b/src/imports/controls/doc/images/qtquickcontrols2-textarea.png
index f468bfd4..924f6681 100644
--- a/src/imports/controls/doc/images/qtquickcontrols2-textarea.png
+++ b/src/imports/controls/doc/images/qtquickcontrols2-textarea.png
Binary files differ
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
diff --git a/tests/auto/controls/data/tst_textarea.qml b/tests/auto/controls/data/tst_textarea.qml
index 4e49c8fc..f09304b0 100644
--- a/tests/auto/controls/data/tst_textarea.qml
+++ b/tests/auto/controls/data/tst_textarea.qml
@@ -56,6 +56,15 @@ TestCase {
}
Component {
+ id: flickable
+ Flickable {
+ width: 200
+ height: 200
+ TextArea.flickable: TextArea { }
+ }
+ }
+
+ Component {
id: signalSpy
SignalSpy { }
}
@@ -137,4 +146,28 @@ TestCase {
control.destroy()
}
+
+ function test_flickable() {
+ var control = flickable.createObject(testCase, {text:"line0"})
+ verify(control)
+
+ var textArea = control.TextArea.flickable
+ verify(textArea)
+
+ for (var i = 1; i <= 100; ++i)
+ textArea.text += "line\n" + i
+
+ verify(textArea.contentWidth > 0)
+ verify(textArea.contentHeight > 200)
+
+ compare(control.contentWidth, textArea.contentWidth + textArea.leftPadding + textArea.rightPadding)
+ compare(control.contentHeight, textArea.contentHeight + textArea.topPadding + textArea.bottomPadding)
+
+ control.destroy()
+ }
+
+ function test_warning() {
+ ignoreWarning(Qt.resolvedUrl("tst_textarea.qml") + ":45:1: QML TestCase: TextArea must be attached to a Flickable")
+ testCase.TextArea.flickable = null
+ }
}