aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJ-P Nurmi <jpnurmi@digia.com>2013-03-22 16:31:39 +0100
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-03-22 17:07:22 +0100
commit4390f9322e2601b0219c984168fe7b4ec340a498 (patch)
tree8b1a8e6dada17f01047cfab045e9ef8cfb7e5081
parentd2395a2237a48565b5ea50856e0fdeade703f206 (diff)
parent5ac2ce5f4810092cd2135663160017e81d6c96a4 (diff)
Merge "Merge remote-tracking branch 'origin/dev' into stable" into refs/staging/stable
-rw-r--r--src/qml/qml/qqmlengine.cpp2
-rw-r--r--src/qml/types/qqmlinstantiator.cpp459
-rw-r--r--src/qml/types/qqmlinstantiator_p.h109
-rw-r--r--src/qml/types/qqmlinstantiator_p_p.h90
-rw-r--r--src/qml/types/types.pri8
-rw-r--r--src/quick/items/items.pri2
-rw-r--r--src/quick/items/qquicktextcontrol.cpp1
-rw-r--r--src/quick/items/qquicktextcontrol_p.h1
-rw-r--r--src/quick/items/qquicktextcontrol_p_p.h4
-rw-r--r--src/quick/items/qquicktextedit.cpp349
-rw-r--r--src/quick/items/qquicktextedit_p.h6
-rw-r--r--src/quick/items/qquicktextedit_p_p.h29
-rw-r--r--src/quick/items/qquicktextnode.cpp1129
-rw-r--r--src/quick/items/qquicktextnode_p.h9
-rw-r--r--src/quick/items/qquicktextnodeengine.cpp935
-rw-r--r--src/quick/items/qquicktextnodeengine_p.h234
-rw-r--r--tests/auto/qml/qml.pro3
-rw-r--r--tests/auto/qml/qqmlinstantiator/data/createMultiple.qml9
-rw-r--r--tests/auto/qml/qqmlinstantiator/data/createNone.qml12
-rw-r--r--tests/auto/qml/qqmlinstantiator/data/createSingle.qml8
-rw-r--r--tests/auto/qml/qqmlinstantiator/data/inactive.qml9
-rw-r--r--tests/auto/qml/qqmlinstantiator/data/stringModel.qml9
-rw-r--r--tests/auto/qml/qqmlinstantiator/qqmlinstantiator.pro12
-rw-r--r--tests/auto/qml/qqmlinstantiator/tst_qqmlinstantiator.cpp198
24 files changed, 2430 insertions, 1197 deletions
diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp
index 96d01920d9..25ef080cb5 100644
--- a/src/qml/qml/qqmlengine.cpp
+++ b/src/qml/qml/qqmlengine.cpp
@@ -96,6 +96,7 @@
#include <private/qqmldelegatemodel_p.h>
#include <private/qqmlobjectmodel_p.h>
#include <private/qquickworkerscript_p.h>
+#include <private/qqmlinstantiator_p.h>
#ifdef Q_OS_WIN // for %APPDATA%
#include <qt_windows.h>
@@ -183,6 +184,7 @@ void QQmlEnginePrivate::registerBaseTypes(const char *uri, int versionMajor, int
qmlRegisterType<QQmlBind>(uri, versionMajor, versionMinor,"Binding");
qmlRegisterType<QQmlConnections>(uri, versionMajor, versionMinor,"Connections");
qmlRegisterType<QQmlTimer>(uri, versionMajor, versionMinor,"Timer");
+ qmlRegisterType<QQmlInstantiator>(uri, versionMajor, (versionMinor < 1 ? 1 : versionMinor), "Instantiator"); //Only available in >=2.1
qmlRegisterCustomType<QQmlConnections>(uri, versionMajor, versionMinor,"Connections", new QQmlConnectionsParser);
qmlRegisterType<QQmlInstanceModel>();
}
diff --git a/src/qml/types/qqmlinstantiator.cpp b/src/qml/types/qqmlinstantiator.cpp
new file mode 100644
index 0000000000..a2a1fa23ad
--- /dev/null
+++ b/src/qml/types/qqmlinstantiator.cpp
@@ -0,0 +1,459 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Research In Motion.
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qqmlinstantiator_p.h"
+#include "qqmlinstantiator_p_p.h"
+#include <QtQml/QQmlContext>
+#include <QtQml/QQmlComponent>
+#include <QtQml/QQmlInfo>
+#include <QtQml/QQmlError>
+#include <QtQml/private/qqmlobjectmodel_p.h>
+#include <QtQml/private/qqmldelegatemodel_p.h>
+
+QT_BEGIN_NAMESPACE
+
+QQmlInstantiatorPrivate::QQmlInstantiatorPrivate()
+ : componentComplete(true)
+ , active(true)
+ , async(false)
+ , ownModel(false)
+ , model(QVariant(1))
+ , instanceModel(0)
+ , delegate(0)
+{
+}
+
+QQmlInstantiatorPrivate::~QQmlInstantiatorPrivate()
+{
+ qDeleteAll(objects);
+}
+
+void QQmlInstantiatorPrivate::clear()
+{
+ Q_Q(QQmlInstantiator);
+ if (!instanceModel)
+ return;
+ if (!objects.count())
+ return;
+
+ for (int i=0; i < objects.count(); i++) {
+ q->objectRemoved(i, objects[i]);
+ instanceModel->release(objects[i]);
+ }
+ objects.clear();
+ q->objectChanged();
+}
+
+void QQmlInstantiatorPrivate::regenerate()
+{
+ Q_Q(QQmlInstantiator);
+ if (!componentComplete)
+ return;
+
+ int prevCount = q->count();
+
+ clear();
+
+ if (!active || !instanceModel || !instanceModel->count() || !instanceModel->isValid()) {
+ if (prevCount)
+ q->countChanged();
+ return;
+ }
+
+ for (int i = 0; i < instanceModel->count(); i++) {
+ QObject *object = instanceModel->object(i, async);
+ // If the item was already created we won't get a createdItem
+ if (object)
+ _q_createdItem(i, object);
+ }
+ if (q->count() != prevCount)
+ q->countChanged();
+}
+
+void QQmlInstantiatorPrivate::_q_createdItem(int idx, QObject* item)
+{
+ Q_Q(QQmlInstantiator);
+ if (objects.contains(item)) //Case when it was created synchronously in regenerate
+ return;
+ item->setParent(q);
+ objects << item;
+ if (objects.count() == 1)
+ q->objectChanged();
+ q->objectAdded(idx, item);
+}
+
+void QQmlInstantiatorPrivate::_q_modelUpdated(const QQmlChangeSet &changeSet, bool reset)
+{
+ Q_Q(QQmlInstantiator);
+
+ if (componentComplete)
+ return;
+
+ if (reset) {
+ regenerate();
+ if (changeSet.difference() != 0)
+ q->countChanged();
+ return;
+ }
+
+ int difference = 0;
+ QHash<int, QVector<QPointer<QObject> > > moved;
+ foreach (const QQmlChangeSet::Remove &remove, changeSet.removes()) {
+ int index = qMin(remove.index, objects.count());
+ int count = qMin(remove.index + remove.count, objects.count()) - index;
+ if (remove.isMove()) {
+ moved.insert(remove.moveId, objects.mid(index, count));
+ objects.erase(
+ objects.begin() + index,
+ objects.begin() + index + count);
+ } else while (count--) {
+ QObject *obj = objects.at(index);
+ objects.remove(index);
+ q->objectRemoved(index, obj);
+ if (obj)
+ instanceModel->release(obj);
+ }
+
+ difference -= remove.count;
+ }
+
+ foreach (const QQmlChangeSet::Insert &insert, changeSet.inserts()) {
+ int index = qMin(insert.index, objects.count());
+ if (insert.isMove()) {
+ QVector<QPointer<QObject> > movedObjects = moved.value(insert.moveId);
+ objects = objects.mid(0, index) + movedObjects + objects.mid(index);
+ } else for (int i = 0; i < insert.count; ++i) {
+ int modelIndex = index + i;
+ QObject* obj = instanceModel->object(i, async);
+ if (obj)
+ _q_createdItem(modelIndex, obj);
+ }
+ difference += insert.count;
+ }
+
+ if (difference != 0)
+ q->countChanged();
+}
+
+void QQmlInstantiatorPrivate::makeModel()
+{
+ Q_Q(QQmlInstantiator);
+ QQmlDelegateModel* delegateModel = new QQmlDelegateModel(qmlContext(q));
+ instanceModel = delegateModel;
+ ownModel = true;
+ delegateModel->setDelegate(delegate);
+ delegateModel->classBegin(); //Pretend it was made in QML
+ if (componentComplete)
+ delegateModel->componentComplete();
+}
+
+
+/*!
+ \qmltype Instantiator
+ \instantiates QQmlInstantiator
+ \inqmlmodule QtQml 2
+ \brief Dynamically creates objects
+
+ A Instantiator can be used to control the dynamic creation of objects, or to dynamically
+ create multiple objects from a template.
+
+ The Instantiator element will manage the objects it creates. Those objects are parented to the
+ Instantiator and can also be deleted by the Instantiator if the Instantiator's properties change. Objects
+ can also be destroyed dynamically through other means, and the Instantiator will not recreate
+ them unless the properties of the Instantiator change.
+
+*/
+QQmlInstantiator::QQmlInstantiator(QObject *parent)
+ : QObject(*(new QQmlInstantiatorPrivate), parent)
+{
+}
+
+QQmlInstantiator::~QQmlInstantiator()
+{
+}
+
+/*!
+ \qmlsignal QtQml2::Instantiator::onObjectAdded(int index, QtObject object)
+
+ This handler is called when an object is added to the Instantiator. The \a index
+ parameter holds the index which the object has been given, and the \a object
+ parameter holds the \l QtObject that has been added.
+*/
+
+/*!
+ \qmlsignal QtQml2::Instantiator::onObjectRemoved(int index, QtObject object)
+
+ This handler is called when an object is added to the Instantiator. The \a index
+ parameter holds the index which the object had been given, and the \a object
+ parameter holds the \l QtObject that has been removed.
+
+ Do not keep a reference to \a object if it was created by this Instantiator, as
+ in these cases it will be deleted shortly after the handler is called.
+*/
+/*!
+ \qmlproperty bool QtQml2::Instantiator::active
+
+ When active is true, and the delegate component is ready, the Instantiator will
+ create objects according to the model. When active is false, no objects
+ will be created and any previously created objects will be destroyed.
+
+ Default is true.
+*/
+bool QQmlInstantiator::isActive() const
+{
+ Q_D(const QQmlInstantiator);
+ return d->active;
+}
+
+void QQmlInstantiator::setActive(bool newVal)
+{
+ Q_D(QQmlInstantiator);
+ if (newVal == d->active)
+ return;
+ d->active = newVal;
+ emit activeChanged();
+ d->regenerate();
+}
+
+/*!
+ \qmlproperty bool QtQml2::Instantiator::asynchronous
+
+ When asynchronous is true the Instantiator will attempt to create objects
+ asynchronously. This means that objects may not be available immediately,
+ even if active is set to true.
+
+ You can use the objectAdded signal to respond to items being created.
+
+ Default is false.
+*/
+bool QQmlInstantiator::isAsync() const
+{
+ Q_D(const QQmlInstantiator);
+ return d->async;
+}
+
+void QQmlInstantiator::setAsync(bool newVal)
+{
+ Q_D(QQmlInstantiator);
+ if (newVal == d->async)
+ return;
+ d->async = newVal;
+ emit asynchronousChanged();
+}
+
+
+/*!
+ \qmlproperty int QtQml2::Instantiator::count
+
+ The number of objects the Instantiator is currently managing.
+*/
+
+int QQmlInstantiator::count() const
+{
+ Q_D(const QQmlInstantiator);
+ return d->objects.count();
+}
+
+/*!
+ \qmlproperty QtQml2::Component QtQml2::Instantiator::delegate
+ \default
+
+ The component used to create all objects.
+
+ Note that an extra variable, index, will be available inside instances of the
+ delegate. This variable refers to the index of the instance inside the Instantiator,
+ and can be used to obtain the object through the itemAt method of the Instantiator.
+
+ If this property is changed, all instances using the old delegate will be destroyed
+ and new instances will be created using the new delegate.
+*/
+QQmlComponent* QQmlInstantiator::delegate()
+{
+ Q_D(QQmlInstantiator);
+ return d->delegate;
+}
+
+void QQmlInstantiator::setDelegate(QQmlComponent* c)
+{
+ Q_D(QQmlInstantiator);
+ if (c == d->delegate)
+ return;
+
+ d->delegate = c;
+ emit delegateChanged();
+
+ if (!d->ownModel)
+ return;
+
+ if (QQmlDelegateModel *dModel = qobject_cast<QQmlDelegateModel*>(d->instanceModel))
+ dModel->setDelegate(c);
+ if (d->componentComplete)
+ d->regenerate();
+}
+
+/*!
+ \qmlproperty variant QtQml2::Instantiator::model
+
+ This property can be set to any of the supported \l {qml-data-models}{data models}:
+
+ \list
+ \li A number that indicates the number of delegates to be created by the repeater
+ \li A model (e.g. a ListModel item, or a QAbstractItemModel subclass)
+ \li A string list
+ \li An object list
+ \endlist
+
+ The type of model affects the properties that are exposed to the \l delegate.
+
+ Default value is 1, which creates a single delegate instance.
+
+ \sa {qml-data-models}{Data Models}
+*/
+
+QVariant QQmlInstantiator::model() const
+{
+ Q_D(const QQmlInstantiator);
+ return d->model;
+}
+
+void QQmlInstantiator::setModel(const QVariant &v)
+{
+ Q_D(QQmlInstantiator);
+ if (d->model == v)
+ return;
+
+ d->model = v;
+ //Don't actually set model until componentComplete in case it wants to create its delegates immediately
+ if (!d->componentComplete)
+ return;
+
+ QQmlInstanceModel *prevModel = d->instanceModel;
+ QObject *object = qvariant_cast<QObject*>(v);
+ QQmlInstanceModel *vim = 0;
+ if (object && (vim = qobject_cast<QQmlInstanceModel *>(object))) {
+ if (d->ownModel) {
+ delete d->instanceModel;
+ d->ownModel = false;
+ }
+ d->instanceModel = vim;
+ } else if (v != QVariant(0)){
+ if (!d->ownModel)
+ d->makeModel();
+
+ if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel *>(d->instanceModel))
+ dataModel->setModel(v);
+ }
+
+ if (d->instanceModel != prevModel) {
+ if (prevModel) {
+ disconnect(prevModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
+ this, SLOT(_q_modelUpdated(QQmlChangeSet,bool)));
+ disconnect(prevModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(_q_createdItem(int,QObject*)));
+ //disconnect(prevModel, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*)));
+ }
+
+ connect(d->instanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
+ this, SLOT(_q_modelUpdated(QQmlChangeSet,bool)));
+ connect(d->instanceModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(_q_createdItem(int,QObject*)));
+ //connect(d->instanceModel, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*)));
+ }
+
+ d->regenerate();
+ emit modelChanged();
+}
+
+/*!
+ \qmlproperty QtQml2::QtObject QtQml2::Instantiator::object
+
+ This is a reference to the first created object, intended as a convenience
+ for the case where only one object has been created.
+*/
+QObject *QQmlInstantiator::object() const
+{
+ Q_D(const QQmlInstantiator);
+ if (d->objects.count())
+ return d->objects[0];
+ return 0;
+}
+
+/*!
+ \qmlmethod QtQml2::QtObject QtQml2::Instantiator::objectAt
+
+ Returns a reference to the object with the given \a index.
+*/
+QObject *QQmlInstantiator::objectAt(int index) const
+{
+ Q_D(const QQmlInstantiator);
+ if (index >= 0 && index < d->objects.count())
+ return d->objects[index];
+ return 0;
+}
+
+/*!
+ \internal
+*/
+void QQmlInstantiator::classBegin()
+{
+ Q_D(QQmlInstantiator);
+ d->componentComplete = false;
+}
+
+/*!
+ \internal
+*/
+void QQmlInstantiator::componentComplete()
+{
+ Q_D(QQmlInstantiator);
+ d->componentComplete = true;
+ if (d->ownModel) {
+ static_cast<QQmlDelegateModel*>(d->instanceModel)->componentComplete();
+ d->regenerate();
+ } else {
+ QVariant realModel = d->model;
+ d->model = QVariant(0);
+ setModel(realModel); //If realModel == d->model this won't do anything, but that's fine since the model's 0
+ //setModel calls regenerate
+ }
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqmlinstantiator_p.cpp"
diff --git a/src/qml/types/qqmlinstantiator_p.h b/src/qml/types/qqmlinstantiator_p.h
new file mode 100644
index 0000000000..d4eb793357
--- /dev/null
+++ b/src/qml/types/qqmlinstantiator_p.h
@@ -0,0 +1,109 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Research In Motion.
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQMLINSTANTIATOR_P_H
+#define QQMLINSTANTIATOR_P_H
+
+#include <QtQml/qqmlcomponent.h>
+#include <QtQml/qqmlparserstatus.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQmlInstantiatorPrivate;
+class Q_AUTOTEST_EXPORT QQmlInstantiator : public QObject, public QQmlParserStatus
+{
+ Q_OBJECT
+ Q_INTERFACES(QQmlParserStatus)
+
+ Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged)
+ Q_PROPERTY(bool asynchronous READ isAsync WRITE setAsync NOTIFY asynchronousChanged)
+ Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged)
+ Q_PROPERTY(int count READ count NOTIFY countChanged)
+ Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
+ Q_PROPERTY(QObject *object READ object NOTIFY objectChanged)
+ Q_CLASSINFO("DefaultProperty", "delegate")
+
+public:
+ QQmlInstantiator(QObject *parent = 0);
+ virtual ~QQmlInstantiator();
+
+ bool isActive() const;
+ void setActive(bool newVal);
+
+ bool isAsync() const;
+ void setAsync(bool newVal);
+
+ int count() const;
+
+ QQmlComponent* delegate();
+ void setDelegate(QQmlComponent* c);
+
+ QVariant model() const;
+ void setModel(const QVariant &v);
+
+ QObject *object() const;
+
+ Q_INVOKABLE QObject *objectAt(int index) const;
+
+ void classBegin();
+ void componentComplete();
+
+Q_SIGNALS:
+ void modelChanged();
+ void delegateChanged();
+ void countChanged();
+ void objectChanged();
+ void activeChanged();
+ void asynchronousChanged();
+
+ void objectAdded(int index, QObject* object);
+ void objectRemoved(int index, QObject* object);
+
+private:
+ Q_DISABLE_COPY(QQmlInstantiator)
+ Q_DECLARE_PRIVATE(QQmlInstantiator)
+ Q_PRIVATE_SLOT(d_func(), void _q_createdItem(int, QObject *))
+ Q_PRIVATE_SLOT(d_func(), void _q_modelUpdated(const QQmlChangeSet &, bool))
+};
+
+QT_END_NAMESPACE
+
+#endif // QQMLCREATOR_P_H
diff --git a/src/qml/types/qqmlinstantiator_p_p.h b/src/qml/types/qqmlinstantiator_p_p.h
new file mode 100644
index 0000000000..79459299dc
--- /dev/null
+++ b/src/qml/types/qqmlinstantiator_p_p.h
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Research In Motion.
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQMLINSTANTIATOR_P_P_H
+#define QQMLINSTANTIATOR_P_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qqmlinstantiator_p.h"
+#include <QObject>
+#include <private/qobject_p.h>
+#include <private/qqmlchangeset_p.h>
+#include <private/qqmlobjectmodel_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQmlInstantiatorPrivate : public QObjectPrivate
+{
+ Q_DECLARE_PUBLIC(QQmlInstantiator)
+
+public:
+ QQmlInstantiatorPrivate();
+ ~QQmlInstantiatorPrivate();
+
+ void clear();
+ void regenerate();
+ void makeModel();
+ void _q_createdItem(int, QObject *);
+ void _q_modelUpdated(const QQmlChangeSet &, bool);
+
+ bool componentComplete;
+ bool active;
+ bool async;
+ bool ownModel;
+ QVariant model;
+ QQmlInstanceModel *instanceModel;
+ QQmlComponent *delegate;
+ QVector<QPointer<QObject> > objects;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQMLCREATOR_P_P_H
diff --git a/src/qml/types/types.pri b/src/qml/types/types.pri
index 22e62ea8de..3e6153759d 100644
--- a/src/qml/types/types.pri
+++ b/src/qml/types/types.pri
@@ -8,7 +8,8 @@ SOURCES += \
$$PWD/qqmlobjectmodel.cpp \
$$PWD/qqmltimer.cpp \
$$PWD/qquickpackage.cpp \
- $$PWD/qquickworkerscript.cpp
+ $$PWD/qquickworkerscript.cpp \
+ $$PWD/qqmlinstantiator.cpp
HEADERS += \
$$PWD/qqmlbind_p.h \
@@ -22,5 +23,6 @@ HEADERS += \
$$PWD/qqmlobjectmodel_p.h \
$$PWD/qqmltimer_p.h \
$$PWD/qquickpackage_p.h \
- $$PWD/qquickworkerscript_p.h
-
+ $$PWD/qquickworkerscript_p.h \
+ $$PWD/qqmlinstantiator_p.h \
+ $$PWD/qqmlinstantiator_p_p.h
diff --git a/src/quick/items/items.pri b/src/quick/items/items.pri
index fe406ecaac..5aaf7d3ac6 100644
--- a/src/quick/items/items.pri
+++ b/src/quick/items/items.pri
@@ -17,6 +17,7 @@ HEADERS += \
$$PWD/qquicktext_p.h \
$$PWD/qquicktext_p_p.h \
$$PWD/qquicktextnode_p.h \
+ $$PWD/qquicktextnodeengine_p.h \
$$PWD/qquicktextinput_p.h \
$$PWD/qquicktextinput_p_p.h \
$$PWD/qquicktextcontrol_p.h \
@@ -84,6 +85,7 @@ SOURCES += \
$$PWD/qquickpainteditem.cpp \
$$PWD/qquicktext.cpp \
$$PWD/qquicktextnode.cpp \
+ $$PWD/qquicktextnodeengine.cpp \
$$PWD/qquicktextinput.cpp \
$$PWD/qquicktextcontrol.cpp \
$$PWD/qquicktextdocument.cpp \
diff --git a/src/quick/items/qquicktextcontrol.cpp b/src/quick/items/qquicktextcontrol.cpp
index 6cce3cc25a..9625aa5d5e 100644
--- a/src/quick/items/qquicktextcontrol.cpp
+++ b/src/quick/items/qquicktextcontrol.cpp
@@ -586,6 +586,7 @@ QQuickTextControl::QQuickTextControl(QTextDocument *doc, QObject *parent)
qmlobject_connect(doc, QTextDocument, SIGNAL(contentsChanged()), this, QQuickTextControl, SIGNAL(textChanged()));
qmlobject_connect(doc, QTextDocument, SIGNAL(contentsChanged()), this, QQuickTextControl, SLOT(_q_updateCurrentCharFormatAndSelection()));
qmlobject_connect(doc, QTextDocument, SIGNAL(cursorPositionChanged(QTextCursor)), this, QQuickTextControl, SLOT(_q_emitCursorPosChanged(QTextCursor)));
+ connect(doc, &QTextDocument::contentsChange, this, &QQuickTextControl::contentsChange);
layout->setProperty("cursorWidth", textCursorWidth);
diff --git a/src/quick/items/qquicktextcontrol_p.h b/src/quick/items/qquicktextcontrol_p.h
index 94eae81f4e..7ec8a68b4c 100644
--- a/src/quick/items/qquicktextcontrol_p.h
+++ b/src/quick/items/qquicktextcontrol_p.h
@@ -138,6 +138,7 @@ public Q_SLOTS:
Q_SIGNALS:
void textChanged();
+ void contentsChange(int from, int charsRemoved, int charsAdded);
void undoAvailable(bool b);
void redoAvailable(bool b);
void currentCharFormatChanged(const QTextCharFormat &format);
diff --git a/src/quick/items/qquicktextcontrol_p_p.h b/src/quick/items/qquicktextcontrol_p_p.h
index a0727c7a71..fbb88bd255 100644
--- a/src/quick/items/qquicktextcontrol_p_p.h
+++ b/src/quick/items/qquicktextcontrol_p_p.h
@@ -39,8 +39,8 @@
**
****************************************************************************/
-#ifndef QTEXTCONTROL_P_P_H
-#define QTEXTCONTROL_P_P_H
+#ifndef QQUICKTEXTCONTROL_P_P_H
+#define QQUICKTEXTCONTROL_P_P_H
//
// W A R N I N G
diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp
index feabbba864..cf20b1d4e7 100644
--- a/src/quick/items/qquicktextedit.cpp
+++ b/src/quick/items/qquicktextedit.cpp
@@ -46,6 +46,7 @@
#include "qquickevents_p_p.h"
#include "qquickwindow.h"
#include "qquicktextnode_p.h"
+#include "qquicktextnodeengine_p.h"
#include "qquicktextutil_p.h"
#include <QtQuick/qsgsimplerectnode.h>
@@ -55,6 +56,7 @@
#include <QtGui/qpainter.h>
#include <QtGui/qtextobject.h>
#include <QtCore/qmath.h>
+#include <QtCore/qalgorithms.h>
#include <private/qqmlglobal_p.h>
#include <private/qqmlproperty_p.h>
@@ -118,6 +120,11 @@ TextEdit {
The link must be in rich text or HTML format and the
\a link string provides access to the particular link.
*/
+
+// This is a pretty arbitrary figure. The idea is that we don't want to break down the document
+// into text nodes corresponding to a text block each so that the glyph node grouping doesn't become pointless.
+static const int nodeBreakingSize = 300;
+
QQuickTextEdit::QQuickTextEdit(QQuickItem *parent)
: QQuickImplicitSizeItem(*(new QQuickTextEditPrivate), parent)
{
@@ -410,7 +417,7 @@ void QQuickTextEdit::setFont(const QFont &font)
moveCursorDelegate();
}
updateSize();
- updateDocument();
+ updateWholeDocument();
#ifndef QT_NO_IM
updateInputMethod(Qt::ImCursorRectangle | Qt::ImFont);
#endif
@@ -446,7 +453,7 @@ void QQuickTextEdit::setColor(const QColor &color)
return;
d->color = color;
- updateDocument();
+ updateWholeDocument();
emit colorChanged(d->color);
}
@@ -468,7 +475,7 @@ void QQuickTextEdit::setSelectionColor(const QColor &color)
return;
d->selectionColor = color;
- updateDocument();
+ updateWholeDocument();
emit selectionColorChanged(d->selectionColor);
}
@@ -490,7 +497,7 @@ void QQuickTextEdit::setSelectedTextColor(const QColor &color)
return;
d->selectedTextColor = color;
- updateDocument();
+ updateWholeDocument();
emit selectedTextColorChanged(d->selectedTextColor);
}
@@ -1205,6 +1212,7 @@ void QQuickTextEdit::geometryChanged(const QRectF &newGeometry,
Q_D(QQuickTextEdit);
if (newGeometry.width() != oldGeometry.width() && widthValid() && !d->inLayout) {
updateSize();
+ updateWholeDocument();
moveCursorDelegate();
}
QQuickImplicitSizeItem::geometryChanged(newGeometry, oldGeometry);
@@ -1492,7 +1500,7 @@ void QQuickTextEdit::select(int start, int end)
d->control->setTextCursor(cursor);
// QTBUG-11100
- updateSelectionMarkers();
+ updateSelection();
}
/*!
@@ -1679,6 +1687,15 @@ void QQuickTextEdit::triggerPreprocess()
update();
}
+typedef QQuickTextEditPrivate::Node TextNode;
+typedef QList<TextNode*>::iterator TextNodeIterator;
+
+
+static bool comesBefore(TextNode* n1, TextNode* n2)
+{
+ return n1->startPos() < n2->startPos();
+}
+
QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
{
Q_UNUSED(updatePaintNodeData);
@@ -1692,45 +1709,144 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
d->updateType = QQuickTextEditPrivate::UpdateNone;
- QSGNode *currentNode = oldNode;
- if (oldNode == 0 || d->documentDirty) {
- d->documentDirty = false;
+ QSGTransformNode *rootNode = static_cast<QSGTransformNode *>(oldNode);
+ TextNodeIterator nodeIterator = d->textNodeMap.begin();
+ while (nodeIterator != d->textNodeMap.end() && !(*nodeIterator)->dirty())
+ ++nodeIterator;
- QQuickTextNode *node = 0;
- if (oldNode == 0) {
- node = new QQuickTextNode(QQuickItemPrivate::get(this)->sceneGraphContext(), this);
- currentNode = node;
- } else {
- node = static_cast<QQuickTextNode *>(oldNode);
+
+ if (!oldNode || nodeIterator < d->textNodeMap.end()) {
+
+ if (!oldNode)
+ rootNode = new QSGTransformNode;
+
+ int firstDirtyPos = 0;
+ if (nodeIterator != d->textNodeMap.end()) {
+ firstDirtyPos = (*nodeIterator)->startPos();
+ do {
+ rootNode->removeChildNode((*nodeIterator)->textNode());
+ delete (*nodeIterator)->textNode();
+ delete *nodeIterator;
+ nodeIterator = d->textNodeMap.erase(nodeIterator);
+ } while (nodeIterator != d->textNodeMap.end() && (*nodeIterator)->dirty());
}
+ // FIXME: the text decorations could probably be handled separately (only updated for affected textFrames)
+ if (d->frameDecorationsNode) {
+ rootNode->removeChildNode(d->frameDecorationsNode);
+ delete d->frameDecorationsNode;
+ }
+ d->frameDecorationsNode = new QQuickTextNode(QQuickItemPrivate::get(this)->sceneGraphContext(), this);
+ d->frameDecorationsNode->initEngine(QColor(), QColor(), QColor());
+
+
+ QQuickTextNode *node = new QQuickTextNode(QQuickItemPrivate::get(this)->sceneGraphContext(), this);
node->setUseNativeRenderer(d->renderType == NativeRendering);
- node->deleteContent();
- node->setMatrix(QMatrix4x4());
+ node->initEngine(d->color, d->selectedTextColor, d->selectionColor);
+
+
+ int sizeCounter = 0;
+ int prevBlockStart = firstDirtyPos;
+ QPointF basePosition(d->xoff, d->yoff);
+ QPointF nodeOffset;
+ TextNode *firstCleanNode = (nodeIterator != d->textNodeMap.end()) ? *nodeIterator : 0;
+
+ QList<QTextFrame *> frames;
+ frames.append(d->document->rootFrame());
+
+ while (!frames.isEmpty()) {
+ QTextFrame *textFrame = frames.takeFirst();
+ frames.append(textFrame->childFrames());
+ d->frameDecorationsNode->m_engine->addFrameDecorations(d->document, textFrame);
+
+ if (textFrame->firstPosition() > textFrame->lastPosition()
+ && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
+ QRectF rect = d->document->documentLayout()->frameBoundingRect(textFrame);
+
+ if (!node->m_engine->hasContents()) {
+ nodeOffset = rect.topLeft();
+ QMatrix4x4 transformMatrix;
+ transformMatrix.translate(nodeOffset.x(), nodeOffset.y());
+ node->setMatrix(transformMatrix);
+ }
+ const int pos = textFrame->firstPosition() - 1;
+ QTextBlock block = textFrame->firstCursorPosition().block();
+ node->m_engine->setCurrentLine(block.layout()->lineForTextPosition(pos - block.position()));
+ node->m_engine->addTextObject(QPointF(0, 0), block.charFormat(), QQuickTextNodeEngine::Unselected, d->document,
+ pos, textFrame->frameFormat().position());
+ } else {
+
+ QTextFrame::iterator it = textFrame->begin();
+
+ while (!it.atEnd()) {
+ QTextBlock block = it.currentBlock();
+ ++it;
+ if (block.position() < firstDirtyPos)
+ continue;
+
+ if (!node->m_engine->hasContents()) {
+ nodeOffset = d->document->documentLayout()->blockBoundingRect(block).topLeft();
+ QMatrix4x4 transformMatrix;
+ transformMatrix.translate(nodeOffset.x(), nodeOffset.y());
+ node->setMatrix(transformMatrix);
+ }
+
+ node->m_engine->addTextBlock(d->document, block, basePosition - nodeOffset, d->color, QColor(), selectionStart(), selectionEnd() - 1);
+ sizeCounter += block.length();
+
+ if ((it.atEnd() && frames.isEmpty()) || (firstCleanNode && block.next().position() >= firstCleanNode->startPos())) // last node that needed replacing or last block of the last frame
+ break;
+
+ if (sizeCounter > nodeBreakingSize) {
+ sizeCounter = 0;
+ node->m_engine->addToSceneGraph(node, QQuickText::Normal, QColor());
+ nodeIterator = d->textNodeMap.insert(nodeIterator, new TextNode(prevBlockStart, node));
+ ++nodeIterator;
+ rootNode->appendChildNode(node);
+ prevBlockStart = block.next().position();
+ node = new QQuickTextNode(QQuickItemPrivate::get(this)->sceneGraphContext(), this);
+ node->setUseNativeRenderer(d->renderType == NativeRendering);
+ node->initEngine(d->color, d->selectedTextColor, d->selectionColor);
+ }
+ }
+ }
+ }
+ node->m_engine->addToSceneGraph(node, QQuickText::Normal, QColor());
+ nodeIterator = d->textNodeMap.insert(nodeIterator, new TextNode(prevBlockStart, node));
+ ++nodeIterator;
+ rootNode->appendChildNode(node);
+ d->frameDecorationsNode->m_engine->addToSceneGraph(d->frameDecorationsNode, QQuickText::Normal, QColor());
+ // Now prepend the frame decorations since we want them rendered first, with the text nodes and cursor in front.
+ rootNode->prependChildNode(d->frameDecorationsNode);
+
+ Q_ASSERT(nodeIterator == d->textNodeMap.end() || (*nodeIterator) == firstCleanNode);
+ // Update the position of the subsequent text blocks.
+ if (firstCleanNode) {
+ QPointF oldOffset = firstCleanNode->textNode()->matrix().map(QPointF(0,0));
+ QPointF currentOffset = d->document->documentLayout()->blockBoundingRect(d->document->findBlock(firstCleanNode->startPos())).topLeft();
+ QPointF delta = currentOffset - oldOffset;
+ while (nodeIterator != d->textNodeMap.end()) {
+ QMatrix4x4 transformMatrix = (*nodeIterator)->textNode()->matrix();
+ transformMatrix.translate(delta.x(), delta.y());
+ (*nodeIterator)->textNode()->setMatrix(transformMatrix);
+ ++nodeIterator;
+ }
- node->addTextDocument(QPointF(d->xoff, d->yoff), d->document, d->color, QQuickText::Normal, QColor(),
- QColor(), d->selectionColor, d->selectedTextColor, selectionStart(),
- selectionEnd() - 1); // selectionEnd() returns first char after
- // selection
+ }
}
if (d->cursorComponent == 0 && !isReadOnly()) {
- QQuickTextNode *node = static_cast<QQuickTextNode *>(currentNode);
-
QColor color = (!d->cursorVisible || !d->control->cursorOn())
? QColor(0, 0, 0, 0)
: d->color;
-
- if (node->cursorNode() == 0) {
- node->setCursor(cursorRectangle(), color);
- } else {
- node->cursorNode()->setRect(cursorRectangle());
- node->cursorNode()->setColor(color);
- }
-
+ if (d->cursorNode)
+ rootNode->removeChildNode(d->cursorNode);
+ delete d->cursorNode;
+ d->cursorNode = new QSGSimpleRectNode(cursorRectangle(), color);
+ rootNode->appendChildNode(d->cursorNode);
}
- return currentNode;
+ return rootNode;
}
/*!
@@ -1818,27 +1934,28 @@ void QQuickTextEditPrivate::init()
control->setAcceptRichText(false);
control->setCursorIsFocusIndicator(true);
- qmlobject_connect(control, QQuickTextControl, SIGNAL(updateRequest()), q, QQuickTextEdit, SLOT(updateDocument()));
qmlobject_connect(control, QQuickTextControl, SIGNAL(updateCursorRequest()), q, QQuickTextEdit, SLOT(updateCursor()));
- qmlobject_connect(control, QQuickTextControl, SIGNAL(textChanged()), q, QQuickTextEdit, SLOT(q_textChanged()));
qmlobject_connect(control, QQuickTextControl, SIGNAL(selectionChanged()), q, QQuickTextEdit, SIGNAL(selectedTextChanged()));
- qmlobject_connect(control, QQuickTextControl, SIGNAL(selectionChanged()), q, QQuickTextEdit, SLOT(updateSelectionMarkers()));
- qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorPositionChanged()), q, QQuickTextEdit, SLOT(updateSelectionMarkers()));
+ qmlobject_connect(control, QQuickTextControl, SIGNAL(selectionChanged()), q, QQuickTextEdit, SLOT(updateSelection()));
+ qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorPositionChanged()), q, QQuickTextEdit, SLOT(updateSelection()));
qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorPositionChanged()), q, QQuickTextEdit, SIGNAL(cursorPositionChanged()));
qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorRectangleChanged()), q, QQuickTextEdit, SLOT(moveCursorDelegate()));
qmlobject_connect(control, QQuickTextControl, SIGNAL(linkActivated(QString)), q, QQuickTextEdit, SIGNAL(linkActivated(QString)));
+ qmlobject_connect(control, QQuickTextControl, SIGNAL(textChanged()), q, QQuickTextEdit, SLOT(q_textChanged()));
#ifndef QT_NO_CLIPBOARD
qmlobject_connect(QGuiApplication::clipboard(), QClipboard, SIGNAL(dataChanged()), q, QQuickTextEdit, SLOT(q_canPasteChanged()));
#endif
qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(undoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canUndoChanged()));
qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(redoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canRedoChanged()));
qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(imagesLoaded()), q, QQuickTextEdit, SLOT(updateSize()));
+ QObject::connect(document, &QQuickTextDocumentWithImageResources::contentsChange, q, &QQuickTextEdit::q_contentsChange);
document->setDefaultFont(font);
document->setDocumentMargin(textMargin);
document->setUndoRedoEnabled(false); // flush undo buffer.
document->setUndoRedoEnabled(true);
updateDefaultTextOption();
+ q->updateSize();
}
void QQuickTextEdit::q_textChanged()
@@ -1857,6 +1974,44 @@ void QQuickTextEdit::q_textChanged()
emit textChanged();
}
+void QQuickTextEdit::markDirtyNodesForRange(int start, int end, int charDelta)
+{
+ Q_D(QQuickTextEdit);
+ if (start == end)
+ return;
+ TextNode dummyNode(start, 0);
+ TextNodeIterator it = qLowerBound(d->textNodeMap.begin(), d->textNodeMap.end(), &dummyNode, &comesBefore);
+ // qLowerBound gives us the first node past the start of the affected portion, rewind by one if we can.
+ if (it != d->textNodeMap.begin())
+ --it;
+
+ // mark the affected nodes as dirty
+ while (it != d->textNodeMap.constEnd()) {
+ if ((*it)->startPos() <= end)
+ (*it)->setDirty();
+ else if (charDelta)
+ (*it)->moveStartPos(charDelta);
+ else
+ return;
+ ++it;
+ }
+}
+
+void QQuickTextEdit::q_contentsChange(int pos, int charsRemoved, int charsAdded)
+{
+ Q_D(QQuickTextEdit);
+
+ const int editRange = pos + qMax(charsAdded, charsRemoved);
+ const int delta = charsAdded - charsRemoved;
+
+ markDirtyNodesForRange(pos, editRange, delta);
+
+ if (isComponentComplete()) {
+ d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
+ update();
+ }
+}
+
void QQuickTextEdit::moveCursorDelegate()
{
Q_D(QQuickTextEdit);
@@ -1871,9 +2026,21 @@ void QQuickTextEdit::moveCursorDelegate()
d->cursorItem->setY(cursorRect.y());
}
-void QQuickTextEdit::updateSelectionMarkers()
+void QQuickTextEdit::updateSelection()
{
Q_D(QQuickTextEdit);
+
+ // No need for node updates when we go from an empty selection to another empty selection
+ if (d->control->textCursor().hasSelection() || d->hadSelection) {
+ markDirtyNodesForRange(qMin(d->lastSelectionStart, d->control->textCursor().selectionStart()), qMax(d->control->textCursor().selectionEnd(), d->lastSelectionEnd), 0);
+ if (isComponentComplete()) {
+ d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
+ update();
+ }
+ }
+
+ d->hadSelection = d->control->textCursor().hasSelection();
+
if (d->lastSelectionStart != d->control->textCursor().selectionStart()) {
d->lastSelectionStart = d->control->textCursor().selectionStart();
emit selectionStartChanged();
@@ -1938,70 +2105,70 @@ qreal QQuickTextEditPrivate::getImplicitWidth() const
void QQuickTextEdit::updateSize()
{
Q_D(QQuickTextEdit);
- if (isComponentComplete()) {
- qreal naturalWidth = d->implicitWidth;
- // ### assumes that if the width is set, the text will fill to edges
- // ### (unless wrap is false, then clipping will occur)
- if (widthValid()) {
- if (!d->requireImplicitWidth) {
- emit implicitWidthChanged();
- // if the implicitWidth is used, then updateSize() has already been called (recursively)
- if (d->requireImplicitWidth)
- return;
- }
- if (d->requireImplicitWidth) {
- d->document->setTextWidth(-1);
- naturalWidth = d->document->idealWidth();
-
- const bool wasInLayout = d->inLayout;
- d->inLayout = true;
- setImplicitWidth(naturalWidth);
- d->inLayout = wasInLayout;
- if (d->inLayout) // probably the result of a binding loop, but by letting it
- return; // get this far we'll get a warning to that effect.
- }
- if (d->document->textWidth() != width())
- d->document->setTextWidth(width());
- } else {
+ if (!isComponentComplete()) {
+ d->dirty = true;
+ return;
+ }
+
+ qreal naturalWidth = d->implicitWidth;
+
+ qreal newWidth = d->document->idealWidth();
+ // ### assumes that if the width is set, the text will fill to edges
+ // ### (unless wrap is false, then clipping will occur)
+ if (widthValid()) {
+ if (!d->requireImplicitWidth) {
+ emit implicitWidthChanged();
+ // if the implicitWidth is used, then updateSize() has already been called (recursively)
+ if (d->requireImplicitWidth)
+ return;
+ }
+ if (d->requireImplicitWidth) {
d->document->setTextWidth(-1);
+ naturalWidth = d->document->idealWidth();
+
+ const bool wasInLayout = d->inLayout;
+ d->inLayout = true;
+ setImplicitWidth(naturalWidth);
+ d->inLayout = wasInLayout;
+ if (d->inLayout) // probably the result of a binding loop, but by letting it
+ return; // get this far we'll get a warning to that effect.
}
-
+ if (d->document->textWidth() != width())
+ d->document->setTextWidth(width());
//### need to confirm cost of always setting these
- qreal newWidth = d->document->idealWidth();
- if ((!widthValid() || d->wrapMode == NoWrap) && d->document->textWidth() != newWidth)
- d->document->setTextWidth(newWidth); // ### Text does not align if width is not set or the idealWidth exceeds the textWidth (QTextDoc bug)
- // ### Setting the implicitWidth triggers another updateSize(), and unless there are bindings nothing has changed.
- qreal iWidth = -1;
- if (!widthValid() && !d->requireImplicitWidth)
- iWidth = newWidth;
-
- QFontMetricsF fm(d->font);
- qreal newHeight = d->document->isEmpty() ? qCeil(fm.height()) : d->document->size().height();
-
- if (iWidth > -1)
- setImplicitSize(iWidth, newHeight);
- else
- setImplicitHeight(newHeight);
+ } else if (d->wrapMode == NoWrap && d->document->textWidth() != newWidth) {
+ d->document->setTextWidth(newWidth); // ### Text does not align if width is not set or the idealWidth exceeds the textWidth (QTextDoc bug)
+ } else {
+ d->document->setTextWidth(-1);
+ }
- d->xoff = QQuickTextUtil::alignedX(d->document->size().width(), width(), effectiveHAlign());
- d->yoff = QQuickTextUtil::alignedY(d->document->size().height(), height(), d->vAlign);
- setBaselineOffset(fm.ascent() + d->yoff + d->textMargin);
+ QFontMetricsF fm(d->font);
+ qreal newHeight = d->document->isEmpty() ? qCeil(fm.height()) : d->document->size().height();
- QSizeF size(newWidth, newHeight);
- if (d->contentSize != size) {
- d->contentSize = size;
- emit contentSizeChanged();
- }
- } else {
- d->dirty = true;
+ // ### Setting the implicitWidth triggers another updateSize(), and unless there are bindings nothing has changed.
+ if (!widthValid() && !d->requireImplicitWidth)
+ setImplicitSize(newWidth, newHeight);
+ else
+ setImplicitHeight(newHeight);
+
+ d->xoff = qMax(qreal(0), QQuickTextUtil::alignedX(d->document->size().width(), width(), effectiveHAlign()));
+ d->yoff = QQuickTextUtil::alignedY(d->document->size().height(), height(), d->vAlign);
+ setBaselineOffset(fm.ascent() + d->yoff + d->textMargin);
+
+ QSizeF size(newWidth, newHeight);
+ if (d->contentSize != size) {
+ d->contentSize = size;
+ emit contentSizeChanged();
}
- updateDocument();
}
-void QQuickTextEdit::updateDocument()
+void QQuickTextEdit::updateWholeDocument()
{
Q_D(QQuickTextEdit);
- d->documentDirty = true;
+ if (!d->textNodeMap.isEmpty()) {
+ Q_FOREACH (TextNode* node, d->textNodeMap)
+ node->setDirty();
+ }
if (isComponentComplete()) {
d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
@@ -2023,7 +2190,7 @@ void QQuickTextEdit::q_updateAlignment()
Q_D(QQuickTextEdit);
if (d->determineHorizontalAlignment()) {
d->updateDefaultTextOption();
- d->xoff = QQuickTextUtil::alignedX(d->document->size().width(), width(), effectiveHAlign());
+ d->xoff = qMax(qreal(0), QQuickTextUtil::alignedX(d->document->size().width(), width(), effectiveHAlign()));
moveCursorDelegate();
}
}
diff --git a/src/quick/items/qquicktextedit_p.h b/src/quick/items/qquicktextedit_p.h
index 8a2d9b1e92..4e09eafcac 100644
--- a/src/quick/items/qquicktextedit_p.h
+++ b/src/quick/items/qquicktextedit_p.h
@@ -313,17 +313,19 @@ public Q_SLOTS:
private Q_SLOTS:
void q_textChanged();
- void updateSelectionMarkers();
+ void q_contentsChange(int, int, int);
+ void updateSelection();
void moveCursorDelegate();
void createCursor();
void q_canPasteChanged();
- void updateDocument();
+ void updateWholeDocument();
void updateCursor();
void q_updateAlignment();
void updateSize();
void triggerPreprocess();
private:
+ void markDirtyNodesForRange(int start, int end, int charDelta);
void updateTotalLines();
protected:
diff --git a/src/quick/items/qquicktextedit_p_p.h b/src/quick/items/qquicktextedit_p_p.h
index dd0f76f8d9..feb7e98873 100644
--- a/src/quick/items/qquicktextedit_p_p.h
+++ b/src/quick/items/qquicktextedit_p_p.h
@@ -58,11 +58,14 @@
#include "qquicktextcontrol_p.h"
#include <QtQml/qqml.h>
+#include <QtCore/qlist.h>
QT_BEGIN_NAMESPACE
class QTextLayout;
class QQuickTextDocumentWithImageResources;
class QQuickTextControl;
+class QQuickTextNode;
+class QSGSimpleRectNode;
class QQuickTextEditPrivate : public QQuickImplicitSizeItemPrivate
{
public:
@@ -70,10 +73,26 @@ public:
typedef QQuickTextEdit Public;
+ struct Node {
+ explicit Node(int startPos, QQuickTextNode* node)
+ : m_startPos(startPos), m_node(node), m_dirty(false) { }
+ QQuickTextNode* textNode() const { return m_node; }
+ void moveStartPos(int delta) { Q_ASSERT(m_startPos + delta > 0); m_startPos += delta; }
+ int startPos() const { return m_startPos; }
+ void setDirty() { m_dirty = true; }
+ bool dirty() const { return m_dirty; }
+
+ private:
+ int m_startPos;
+ QQuickTextNode* m_node;
+ bool m_dirty;
+ };
+
+
QQuickTextEditPrivate()
: color(QRgb(0xFF000000)), selectionColor(QRgb(0xFF000080)), selectedTextColor(QRgb(0xFFFFFFFF))
, textMargin(0.0), xoff(0), yoff(0), font(sourceFont), cursorComponent(0), cursorItem(0), document(0), control(0)
- , quickDocument(0)
+ , quickDocument(0), frameDecorationsNode(0), cursorNode(0)
, lastSelectionStart(0), lastSelectionEnd(0), lineCount(0)
, hAlign(QQuickTextEdit::AlignLeft), vAlign(QQuickTextEdit::AlignTop)
, format(QQuickTextEdit::PlainText), wrapMode(QQuickTextEdit::NoWrap)
@@ -84,10 +103,11 @@ public:
, inputMethodHints(Qt::ImhNone)
#endif
, updateType(UpdatePaintNode)
- , documentDirty(true), dirty(false), richText(false), cursorVisible(false), cursorPending(false)
+ , dirty(false), richText(false), cursorVisible(false), cursorPending(false)
, focusOnPress(true), persistentSelection(false), requireImplicitWidth(false)
, selectByMouse(false), canPaste(false), canPasteValid(false), hAlignImplicit(true)
, textCached(true), inLayout(false), selectByKeyboard(false), selectByKeyboardSet(false)
+ , hadSelection(false)
{
}
@@ -131,6 +151,9 @@ public:
QQuickTextDocumentWithImageResources *document;
QQuickTextControl *control;
QQuickTextDocument *quickDocument;
+ QList<Node*> textNodeMap;
+ QQuickTextNode *frameDecorationsNode;
+ QSGSimpleRectNode *cursorNode;
int lastSelectionStart;
int lastSelectionEnd;
@@ -154,7 +177,6 @@ public:
#endif
UpdateType updateType;
- bool documentDirty : 1;
bool dirty : 1;
bool richText : 1;
bool cursorVisible : 1;
@@ -170,6 +192,7 @@ public:
bool inLayout:1;
bool selectByKeyboard:1;
bool selectByKeyboardSet:1;
+ bool hadSelection : 1;
};
QT_END_NAMESPACE
diff --git a/src/quick/items/qquicktextnode.cpp b/src/quick/items/qquicktextnode.cpp
index 5e1b20c032..d46959464a 100644
--- a/src/quick/items/qquicktextnode.cpp
+++ b/src/quick/items/qquicktextnode.cpp
@@ -41,6 +41,8 @@
#include "qquicktextnode_p.h"
+#include "qquicktextnodeengine_p.h"
+
#include <QtQuick/qsgsimplerectnode.h>
#include <private/qsgadaptationlayer_p.h>
#include <private/qsgdistancefieldglyphnode_p.h>
@@ -53,20 +55,28 @@
#include <qtextlayout.h>
#include <qabstracttextdocumentlayout.h>
#include <qxmlstream.h>
-#include <qrawfont.h>
-#include <qtexttable.h>
-#include <qtextlist.h>
#include <private/qquickstyledtext_p.h>
-#include <private/qquicktext_p_p.h>
#include <private/qfont_p.h>
#include <private/qfontengine_p.h>
-#include <private/qrawfont_p.h>
-#include <private/qtextimagehandler_p.h>
+
#include <private/qtextdocumentlayout_p.h>
#include <qhash.h>
QT_BEGIN_NAMESPACE
+namespace {
+
+ class ProtectedLayoutAccessor: public QAbstractTextDocumentLayout
+ {
+ public:
+ inline QTextCharFormat formatAccessor(int pos)
+ {
+ return format(pos);
+ }
+ };
+
+}
+
/*!
Creates an empty QQuickTextNode
*/
@@ -166,937 +176,14 @@ void QQuickTextNode::setCursor(const QRectF &rect, const QColor &color)
appendChildNode(m_cursorNode);
}
-namespace {
-
- struct BinaryTreeNode {
- enum SelectionState {
- Unselected,
- Selected
- };
-
- BinaryTreeNode()
- : selectionState(Unselected)
- , clipNode(0)
- , decorations(QQuickTextNode::NoDecoration)
- , ascent(0.0)
- , leftChildIndex(-1)
- , rightChildIndex(-1)
- {
-
- }
-
- BinaryTreeNode(const QRectF &brect, const QImage &i, SelectionState selState, qreal a)
- : boundingRect(brect)
- , selectionState(selState)
- , clipNode(0)
- , decorations(QQuickTextNode::NoDecoration)
- , image(i)
- , ascent(a)
- , leftChildIndex(-1)
- , rightChildIndex(-1)
- {
- }
-
- BinaryTreeNode(const QGlyphRun &g, SelectionState selState, const QRectF &brect,
- const QQuickTextNode::Decorations &decs, const QColor &c, const QColor &bc,
- const QPointF &pos, qreal a)
- : glyphRun(g)
- , boundingRect(brect)
- , selectionState(selState)
- , clipNode(0)
- , decorations(decs)
- , color(c)
- , backgroundColor(bc)
- , position(pos)
- , ascent(a)
- , leftChildIndex(-1)
- , rightChildIndex(-1)
- {
- }
-
- QGlyphRun glyphRun;
- QRectF boundingRect;
- SelectionState selectionState;
- QQuickDefaultClipNode *clipNode;
- QQuickTextNode::Decorations decorations;
- QColor color;
- QColor backgroundColor;
- QPointF position;
- QImage image;
- qreal ascent;
-
- int leftChildIndex;
- int rightChildIndex;
-
- static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
- const QRectF &rect,
- const QImage &image,
- qreal ascent,
- SelectionState selectionState)
- {
- insert(binaryTree, BinaryTreeNode(rect, image, selectionState, ascent));
- }
-
- static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
- const QGlyphRun &glyphRun,
- SelectionState selectionState,
- QQuickTextNode::Decorations decorations,
- const QColor &textColor,
- const QColor &backgroundColor,
- const QPointF &position)
- {
- QRectF searchRect = glyphRun.boundingRect();
- searchRect.translate(position);
-
- if (qFuzzyIsNull(searchRect.width()) || qFuzzyIsNull(searchRect.height()))
- return;
-
- decorations |= (glyphRun.underline() ? QQuickTextNode::Underline : QQuickTextNode::NoDecoration);
- decorations |= (glyphRun.overline() ? QQuickTextNode::Overline : QQuickTextNode::NoDecoration);
- decorations |= (glyphRun.strikeOut() ? QQuickTextNode::StrikeOut : QQuickTextNode::NoDecoration);
- decorations |= (backgroundColor.isValid() ? QQuickTextNode::Background : QQuickTextNode::NoDecoration);
-
- qreal ascent = glyphRun.rawFont().ascent();
- insert(binaryTree, BinaryTreeNode(glyphRun, selectionState, searchRect, decorations,
- textColor, backgroundColor, position, ascent));
- }
-
- static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
- const BinaryTreeNode &binaryTreeNode)
- {
- int newIndex = binaryTree->size();
- binaryTree->append(binaryTreeNode);
- if (newIndex == 0)
- return;
-
- int searchIndex = 0;
- forever {
- BinaryTreeNode *node = binaryTree->data() + searchIndex;
- if (binaryTreeNode.boundingRect.left() < node->boundingRect.left()) {
- if (node->leftChildIndex < 0) {
- node->leftChildIndex = newIndex;
- break;
- } else {
- searchIndex = node->leftChildIndex;
- }
- } else {
- if (node->rightChildIndex < 0) {
- node->rightChildIndex = newIndex;
- break;
- } else {
- searchIndex = node->rightChildIndex;
- }
- }
- }
- }
-
- static void inOrder(const QVarLengthArray<BinaryTreeNode> &binaryTree,
- QVarLengthArray<int> *sortedIndexes,
- int currentIndex = 0)
- {
- Q_ASSERT(currentIndex < binaryTree.size());
-
- const BinaryTreeNode *node = binaryTree.data() + currentIndex;
- if (node->leftChildIndex >= 0)
- inOrder(binaryTree, sortedIndexes, node->leftChildIndex);
-
- sortedIndexes->append(currentIndex);
-
- if (node->rightChildIndex >= 0)
- inOrder(binaryTree, sortedIndexes, node->rightChildIndex);
- }
- };
-
- // Engine that takes glyph runs as input, and produces a set of glyph nodes, clip nodes,
- // and rectangle nodes to represent the text, decorations and selection. Will try to minimize
- // number of nodes, and join decorations in neighbouring items
- class SelectionEngine
- {
- public:
- SelectionEngine() : m_hasSelection(false) {}
-
- QTextLine currentLine() const { return m_currentLine; }
-
- void setCurrentLine(const QTextLine &currentLine)
- {
- if (m_currentLine.isValid())
- processCurrentLine();
-
- m_currentLine = currentLine;
- }
-
- void addBorder(const QRectF &rect, qreal border, QTextFrameFormat::BorderStyle borderStyle,
- const QBrush &borderBrush);
- void addFrameDecorations(QTextDocument *document, QTextFrame *frame);
- void addImage(const QRectF &rect, const QImage &image, qreal ascent,
- BinaryTreeNode::SelectionState selectionState,
- QTextFrameFormat::Position layoutPosition);
- int addText(const QTextBlock &block,
- const QTextCharFormat &charFormat,
- const QColor &textColor,
- const QVarLengthArray<QTextLayout::FormatRange> &colorChanges,
- int textPos, int fragmentEnd,
- int selectionStart, int selectionEnd);
- void addTextObject(const QPointF &position, const QTextCharFormat &format,
- BinaryTreeNode::SelectionState selectionState,
- QTextDocument *textDocument, int pos,
- QTextFrameFormat::Position layoutPosition = QTextFrameFormat::InFlow);
- void addSelectedGlyphs(const QGlyphRun &glyphRun);
- void addUnselectedGlyphs(const QGlyphRun &glyphRun);
- void addGlyphsInRange(int rangeStart, int rangeEnd,
- const QColor &color, const QColor &backgroundColor,
- int selectionStart, int selectionEnd);
- void addGlyphsForRanges(const QVarLengthArray<QTextLayout::FormatRange> &ranges,
- int start, int end,
- int selectionStart, int selectionEnd);
-
- void addToSceneGraph(QQuickTextNode *parent,
- QQuickText::TextStyle style = QQuickText::Normal,
- const QColor &styleColor = QColor());
-
- void setSelectionColor(const QColor &selectionColor)
- {
- m_selectionColor = selectionColor;
- }
-
- void setSelectedTextColor(const QColor &selectedTextColor)
- {
- m_selectedTextColor = selectedTextColor;
- }
-
- void setTextColor(const QColor &textColor)
- {
- m_textColor = textColor;
- }
-
- void setAnchorColor(const QColor &anchorColor)
- {
- m_anchorColor = anchorColor;
- }
-
- void setPosition(const QPointF &position)
- {
- m_position = position;
- }
-
- private:
- struct TextDecoration
- {
- TextDecoration() : selectionState(BinaryTreeNode::Unselected) {}
- TextDecoration(const BinaryTreeNode::SelectionState &s,
- const QRectF &r,
- const QColor &c)
- : selectionState(s)
- , rect(r)
- , color(c)
- {
- }
-
- BinaryTreeNode::SelectionState selectionState;
- QRectF rect;
- QColor color;
- };
-
- void processCurrentLine();
- void addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
- qreal offset, qreal thickness);
-
- QColor m_selectionColor;
- QColor m_textColor;
- QColor m_backgroundColor;
- QColor m_selectedTextColor;
- QColor m_anchorColor;
- QPointF m_position;
-
- QTextLine m_currentLine;
- bool m_hasSelection;
-
- QList<QPair<QRectF, QColor> > m_backgrounds;
- QList<QRectF> m_selectionRects;
- QVarLengthArray<BinaryTreeNode> m_currentLineTree;
-
- QList<TextDecoration> m_lines;
- QVector<BinaryTreeNode> m_processedNodes;
-
- QList<QPair<QRectF, QImage> > m_images;
- };
-
- int SelectionEngine::addText(const QTextBlock &block,
- const QTextCharFormat &charFormat,
- const QColor &textColor,
- const QVarLengthArray<QTextLayout::FormatRange> &colorChanges,
- int textPos, int fragmentEnd,
- int selectionStart, int selectionEnd)
- {
- if (charFormat.foreground().style() != Qt::NoBrush)
- setTextColor(charFormat.foreground().color());
- else
- setTextColor(textColor);
-
- while (textPos < fragmentEnd) {
- int blockRelativePosition = textPos - block.position();
- QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
- if (!currentLine().isValid()
- || line.lineNumber() != currentLine().lineNumber()) {
- setCurrentLine(line);
- }
-
- Q_ASSERT(line.textLength() > 0);
- int lineEnd = line.textStart() + block.position() + line.textLength();
-
- int len = qMin(lineEnd - textPos, fragmentEnd - textPos);
- Q_ASSERT(len > 0);
-
- int currentStepEnd = textPos + len;
-
- addGlyphsForRanges(colorChanges,
- textPos - block.position(),
- currentStepEnd - block.position(),
- selectionStart - block.position(),
- selectionEnd - block.position());
-
- textPos = currentStepEnd;
- }
- return textPos;
- }
-
- void SelectionEngine::addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
- qreal offset, qreal thickness)
- {
- for (int i=0; i<textDecorations.size(); ++i) {
- TextDecoration textDecoration = textDecorations.at(i);
-
- {
- QRectF &rect = textDecoration.rect;
- rect.setY(qRound(rect.y() + m_currentLine.ascent() + offset));
- rect.setHeight(thickness);
- }
-
- m_lines.append(textDecoration);
- }
- }
-
- void SelectionEngine::processCurrentLine()
- {
- // No glyphs, do nothing
- if (m_currentLineTree.isEmpty())
- return;
-
- // 1. Go through current line and get correct decoration position for each node based on
- // neighbouring decorations. Add decoration to global list
- // 2. Create clip nodes for all selected text. Try to merge as many as possible within
- // the line.
- // 3. Add QRects to a list of selection rects.
- // 4. Add all nodes to a global processed list
- QVarLengthArray<int> sortedIndexes; // Indexes in tree sorted by x position
- BinaryTreeNode::inOrder(m_currentLineTree, &sortedIndexes);
-
- Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size());
-
- BinaryTreeNode::SelectionState currentSelectionState = BinaryTreeNode::Unselected;
- QRectF currentRect;
-
- QQuickTextNode::Decorations currentDecorations = QQuickTextNode::NoDecoration;
- qreal underlineOffset = 0.0;
- qreal underlineThickness = 0.0;
-
- qreal overlineOffset = 0.0;
- qreal overlineThickness = 0.0;
-
- qreal strikeOutOffset = 0.0;
- qreal strikeOutThickness = 0.0;
-
- QRectF decorationRect = currentRect;
-
- QColor lastColor;
- QColor lastBackgroundColor;
-
- QVarLengthArray<TextDecoration> pendingUnderlines;
- QVarLengthArray<TextDecoration> pendingOverlines;
- QVarLengthArray<TextDecoration> pendingStrikeOuts;
- if (!sortedIndexes.isEmpty()) {
- QQuickDefaultClipNode *currentClipNode = m_hasSelection ? new QQuickDefaultClipNode(QRectF()) : 0;
- bool currentClipNodeUsed = false;
- for (int i=0; i<=sortedIndexes.size(); ++i) {
- BinaryTreeNode *node = 0;
- if (i < sortedIndexes.size()) {
- int sortedIndex = sortedIndexes.at(i);
- Q_ASSERT(sortedIndex < m_currentLineTree.size());
-
- node = m_currentLineTree.data() + sortedIndex;
- }
-
- if (i == 0)
- currentSelectionState = node->selectionState;
-
- // Update decorations
- if (currentDecorations != QQuickTextNode::NoDecoration) {
- decorationRect.setY(m_position.y() + m_currentLine.y());
- decorationRect.setHeight(m_currentLine.height());
-
- if (node != 0)
- decorationRect.setRight(node->boundingRect.left());
-
- TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor);
- if (currentDecorations & QQuickTextNode::Underline)
- pendingUnderlines.append(textDecoration);
-
- if (currentDecorations & QQuickTextNode::Overline)
- pendingOverlines.append(textDecoration);
-
- if (currentDecorations & QQuickTextNode::StrikeOut)
- pendingStrikeOuts.append(textDecoration);
-
- if (currentDecorations & QQuickTextNode::Background)
- m_backgrounds.append(qMakePair(decorationRect, lastBackgroundColor));
- }
-
- // If we've reached an unselected node from a selected node, we add the
- // selection rect to the graph, and we add decoration every time the
- // selection state changes, because that means the text color changes
- if (node == 0 || node->selectionState != currentSelectionState) {
- if (node != 0)
- currentRect.setRight(node->boundingRect.left());
- currentRect.setY(m_position.y() + m_currentLine.y());
- currentRect.setHeight(m_currentLine.height());
-
- // Draw selection all the way up to the left edge of the unselected item
- if (currentSelectionState == BinaryTreeNode::Selected)
- m_selectionRects.append(currentRect);
-
- if (currentClipNode != 0) {
- if (!currentClipNodeUsed) {
- delete currentClipNode;
- } else {
- currentClipNode->setIsRectangular(true);
- currentClipNode->setRect(currentRect);
- currentClipNode->update();
- }
- }
-
- if (node != 0 && m_hasSelection)
- currentClipNode = new QQuickDefaultClipNode(QRectF());
- else
- currentClipNode = 0;
- currentClipNodeUsed = false;
-
- if (node != 0) {
- currentSelectionState = node->selectionState;
- currentRect = node->boundingRect;
-
- // Make sure currentRect is valid, otherwise the unite won't work
- if (currentRect.isNull())
- currentRect.setSize(QSizeF(1, 1));
- }
- } else {
- if (currentRect.isNull())
- currentRect = node->boundingRect;
- else
- currentRect = currentRect.united(node->boundingRect);
- }
-
- if (node != 0) {
- node->clipNode = currentClipNode;
- currentClipNodeUsed = true;
-
- decorationRect = node->boundingRect;
-
- // If previous item(s) had underline and current does not, then we add the
- // pending lines to the lists and likewise for overlines and strikeouts
- if (!pendingUnderlines.isEmpty()
- && !(node->decorations & QQuickTextNode::Underline)) {
- addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
-
- pendingUnderlines.clear();
-
- underlineOffset = 0.0;
- underlineThickness = 0.0;
- }
-
- // ### Add pending when overlineOffset/thickness changes to minimize number of
- // nodes
- if (!pendingOverlines.isEmpty()) {
- addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
-
- pendingOverlines.clear();
-
- overlineOffset = 0.0;
- overlineThickness = 0.0;
- }
-
- // ### Add pending when overlineOffset/thickness changes to minimize number of
- // nodes
- if (!pendingStrikeOuts.isEmpty()) {
- addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
-
- pendingStrikeOuts.clear();
-
- strikeOutOffset = 0.0;
- strikeOutThickness = 0.0;
- }
-
- // Merge current values with previous. Prefer greatest thickness
- QRawFont rawFont = node->glyphRun.rawFont();
- if (node->decorations & QQuickTextNode::Underline) {
- if (rawFont.lineThickness() > underlineThickness) {
- underlineThickness = rawFont.lineThickness();
- underlineOffset = rawFont.underlinePosition();
- }
- }
-
- if (node->decorations & QQuickTextNode::Overline) {
- overlineOffset = -rawFont.ascent();
- overlineThickness = rawFont.lineThickness();
- }
-
- if (node->decorations & QQuickTextNode::StrikeOut) {
- strikeOutThickness = rawFont.lineThickness();
- strikeOutOffset = rawFont.ascent() / -3.0;
- }
-
- currentDecorations = node->decorations;
- lastColor = node->color;
- lastBackgroundColor = node->backgroundColor;
- m_processedNodes.append(*node);
- }
- }
-
- if (!pendingUnderlines.isEmpty())
- addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
-
- if (!pendingOverlines.isEmpty())
- addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
-
- if (!pendingStrikeOuts.isEmpty())
- addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
- }
-
- m_currentLineTree.clear();
- m_currentLine = QTextLine();
- m_hasSelection = false;
- }
-
- void SelectionEngine::addImage(const QRectF &rect, const QImage &image, qreal ascent,
- BinaryTreeNode::SelectionState selectionState,
- QTextFrameFormat::Position layoutPosition)
- {
- QRectF searchRect = rect;
- if (layoutPosition == QTextFrameFormat::InFlow) {
- if (m_currentLineTree.isEmpty()) {
- searchRect.moveTopLeft(m_position + m_currentLine.position());
- } else {
- const BinaryTreeNode *lastNode = m_currentLineTree.data() + m_currentLineTree.size() - 1;
- if (lastNode->glyphRun.isRightToLeft()) {
- QPointF lastPos = lastNode->boundingRect.topLeft();
- searchRect.moveTopRight(lastPos - QPointF(0, ascent - lastNode->ascent));
- } else {
- QPointF lastPos = lastNode->boundingRect.topRight();
- searchRect.moveTopLeft(lastPos - QPointF(0, ascent - lastNode->ascent));
- }
- }
- }
-
- BinaryTreeNode::insert(&m_currentLineTree, searchRect, image, ascent, selectionState);
- }
-
- void SelectionEngine::addTextObject(const QPointF &position, const QTextCharFormat &format,
- BinaryTreeNode::SelectionState selectionState,
- QTextDocument *textDocument, int pos,
- QTextFrameFormat::Position layoutPosition)
- {
- QTextObjectInterface *handler = textDocument->documentLayout()->handlerForObject(format.objectType());
- if (handler != 0) {
- QImage image;
- QSizeF size = handler->intrinsicSize(textDocument, pos, format);
-
- if (format.objectType() == QTextFormat::ImageObject) {
- QTextImageFormat imageFormat = format.toImageFormat();
- if (QQuickTextDocumentWithImageResources *imageDoc = qobject_cast<QQuickTextDocumentWithImageResources *>(textDocument)) {
- image = imageDoc->image(imageFormat);
-
- if (image.isNull())
- return;
- } else {
- QTextImageHandler *imageHandler = static_cast<QTextImageHandler *>(handler);
- image = imageHandler->image(textDocument, imageFormat);
- }
- }
-
- if (image.isNull()) {
- image = QImage(size.toSize(), QImage::Format_ARGB32_Premultiplied);
- image.fill(Qt::transparent);
- {
- QPainter painter(&image);
- handler->drawObject(&painter, image.rect(), textDocument, pos, format);
- }
- }
-
- qreal ascent;
- QFontMetrics m(format.font());
- switch (format.verticalAlignment())
- {
- case QTextCharFormat::AlignMiddle:
- ascent = size.height() / 2 - 1;
- break;
- case QTextCharFormat::AlignBaseline:
- ascent = size.height() - m.descent() - 1;
- break;
- default:
- ascent = size.height() - 1;
- }
-
- addImage(QRectF(position, size), image, ascent, selectionState, layoutPosition);
- }
- }
-
- void SelectionEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun)
- {
- BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Unselected,
- QQuickTextNode::NoDecoration, m_textColor, m_backgroundColor, m_position);
- }
-
- void SelectionEngine::addSelectedGlyphs(const QGlyphRun &glyphRun)
- {
- int currentSize = m_currentLineTree.size();
- BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Selected,
- QQuickTextNode::NoDecoration, m_textColor, m_backgroundColor, m_position);
- m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize;
- }
-
- void SelectionEngine::addGlyphsForRanges(const QVarLengthArray<QTextLayout::FormatRange> &ranges,
- int start, int end,
- int selectionStart, int selectionEnd)
- {
- int currentPosition = start;
- int remainingLength = end - start;
- for (int j=0; j<ranges.size(); ++j) {
- const QTextLayout::FormatRange &range = ranges.at(j);
- if (range.start + range.length >= currentPosition
- && range.start < currentPosition + remainingLength) {
-
- if (range.start > currentPosition) {
- addGlyphsInRange(currentPosition, range.start - currentPosition,
- QColor(), QColor(), selectionStart, selectionEnd);
- }
- int rangeEnd = qMin(range.start + range.length, currentPosition + remainingLength);
- QColor rangeColor;
- if (range.format.hasProperty(QTextFormat::ForegroundBrush))
- rangeColor = range.format.foreground().color();
- else if (range.format.isAnchor())
- rangeColor = m_anchorColor;
- QColor rangeBackgroundColor = range.format.hasProperty(QTextFormat::BackgroundBrush)
- ? range.format.background().color()
- : QColor();
-
- addGlyphsInRange(range.start, rangeEnd - range.start,
- rangeColor, rangeBackgroundColor,
- selectionStart, selectionEnd);
-
- currentPosition = range.start + range.length;
- remainingLength = end - currentPosition;
-
- } else if (range.start > currentPosition + remainingLength || remainingLength <= 0) {
- break;
- }
- }
-
- if (remainingLength > 0) {
- addGlyphsInRange(currentPosition, remainingLength, QColor(), QColor(),
- selectionStart, selectionEnd);
- }
-
- }
-
- void SelectionEngine::addGlyphsInRange(int rangeStart, int rangeLength,
- const QColor &color, const QColor &backgroundColor,
- int selectionStart, int selectionEnd)
- {
- QColor oldColor;
- if (color.isValid()) {
- oldColor = m_textColor;
- m_textColor = color;
- }
-
- QColor oldBackgroundColor = m_backgroundColor;
- if (backgroundColor.isValid()) {
- oldBackgroundColor = m_backgroundColor;
- m_backgroundColor = backgroundColor;
- }
-
- bool hasSelection = selectionEnd >= 0
- && selectionStart <= selectionEnd;
-
- QTextLine &line = m_currentLine;
- int rangeEnd = rangeStart + rangeLength;
- if (!hasSelection || (selectionStart > rangeEnd || selectionEnd < rangeStart)) {
- QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart, rangeLength);
- for (int j=0; j<glyphRuns.size(); ++j) {
- const QGlyphRun &glyphRun = glyphRuns.at(j);
- addUnselectedGlyphs(glyphRun);
- }
- } else {
- if (rangeStart < selectionStart) {
- QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart,
- qMin(selectionStart - rangeStart,
- rangeLength));
-
- for (int j=0; j<glyphRuns.size(); ++j) {
- const QGlyphRun &glyphRun = glyphRuns.at(j);
- addUnselectedGlyphs(glyphRun);
- }
- }
-
- if (rangeEnd > selectionStart) {
- int start = qMax(selectionStart, rangeStart);
- int length = qMin(selectionEnd - start + 1, rangeEnd - start);
- QList<QGlyphRun> glyphRuns = line.glyphRuns(start, length);
-
- for (int j=0; j<glyphRuns.size(); ++j) {
- const QGlyphRun &glyphRun = glyphRuns.at(j);
- addSelectedGlyphs(glyphRun);
- }
- }
-
- if (selectionEnd >= rangeStart && selectionEnd < rangeEnd) {
- QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionEnd + 1, rangeEnd - selectionEnd - 1);
- for (int j=0; j<glyphRuns.size(); ++j) {
- const QGlyphRun &glyphRun = glyphRuns.at(j);
- addUnselectedGlyphs(glyphRun);
- }
- }
- }
-
- if (backgroundColor.isValid())
- m_backgroundColor = oldBackgroundColor;
-
- if (oldColor.isValid())
- m_textColor = oldColor;
- }
-
- void SelectionEngine::addBorder(const QRectF &rect, qreal border,
- QTextFrameFormat::BorderStyle borderStyle,
- const QBrush &borderBrush)
- {
- QColor color = borderBrush.color();
-
- // Currently we don't support other styles than solid
- Q_UNUSED(borderStyle);
-
- m_backgrounds.append(qMakePair(QRectF(rect.left(), rect.top(), border, rect.height() + border), color));
- m_backgrounds.append(qMakePair(QRectF(rect.left() + border, rect.top(), rect.width(), border), color));
- m_backgrounds.append(qMakePair(QRectF(rect.right(), rect.top() + border, border, rect.height() - border), color));
- m_backgrounds.append(qMakePair(QRectF(rect.left() + border, rect.bottom(), rect.width(), border), color));
- }
-
- void SelectionEngine::addFrameDecorations(QTextDocument *document, QTextFrame *frame)
- {
- QTextDocumentLayout *documentLayout = qobject_cast<QTextDocumentLayout *>(document->documentLayout());
- QTextFrameFormat frameFormat = frame->format().toFrameFormat();
-
- QTextTable *table = qobject_cast<QTextTable *>(frame);
- QRectF boundingRect = table == 0
- ? documentLayout->frameBoundingRect(frame)
- : documentLayout->tableBoundingRect(table);
-
- QBrush bg = frame->frameFormat().background();
- if (bg.style() != Qt::NoBrush)
- m_backgrounds.append(qMakePair(boundingRect, bg.color()));
-
- if (!frameFormat.hasProperty(QTextFormat::FrameBorder))
- return;
-
- qreal borderWidth = frameFormat.border();
- if (qFuzzyIsNull(borderWidth))
- return;
-
- QBrush borderBrush = frameFormat.borderBrush();
- QTextFrameFormat::BorderStyle borderStyle = frameFormat.borderStyle();
- if (borderStyle == QTextFrameFormat::BorderStyle_None)
- return;
-
- addBorder(boundingRect.adjusted(frameFormat.leftMargin(), frameFormat.topMargin(),
- -frameFormat.rightMargin(), -frameFormat.bottomMargin()),
- borderWidth, borderStyle, borderBrush);
- if (table != 0) {
- int rows = table->rows();
- int columns = table->columns();
-
- for (int row=0; row<rows; ++row) {
- for (int column=0; column<columns; ++column) {
- QTextTableCell cell = table->cellAt(row, column);
-
- QRectF cellRect = documentLayout->tableCellBoundingRect(table, cell);
- addBorder(cellRect.adjusted(-borderWidth, -borderWidth, 0, 0), borderWidth,
- borderStyle, borderBrush);
- }
- }
- }
- }
-
- void SelectionEngine::addToSceneGraph(QQuickTextNode *parentNode,
- QQuickText::TextStyle style,
- const QColor &styleColor)
- {
- if (m_currentLine.isValid())
- processCurrentLine();
-
-
- for (int i=0; i<m_backgrounds.size(); ++i) {
- const QRectF &rect = m_backgrounds.at(i).first;
- const QColor &color = m_backgrounds.at(i).second;
-
- parentNode->appendChildNode(new QSGSimpleRectNode(rect, color));
- }
-
- // First, prepend all selection rectangles to the tree
- for (int i=0; i<m_selectionRects.size(); ++i) {
- const QRectF &rect = m_selectionRects.at(i);
-
- parentNode->appendChildNode(new QSGSimpleRectNode(rect, m_selectionColor));
- }
-
- // Finally, add decorations for each node to the tree.
- for (int i=0; i<m_lines.size(); ++i) {
- const TextDecoration &textDecoration = m_lines.at(i);
-
- QColor color = textDecoration.selectionState == BinaryTreeNode::Selected
- ? m_selectedTextColor
- : textDecoration.color;
-
- parentNode->appendChildNode(new QSGSimpleRectNode(textDecoration.rect, color));
- }
-
- // Then, go through all the nodes for all lines and combine all QGlyphRuns with a common
- // font, selection state and clip node.
- typedef QPair<QFontEngine *, QPair<QQuickDefaultClipNode *, QPair<QRgb, int> > > KeyType;
- QHash<KeyType, BinaryTreeNode *> map;
- QList<BinaryTreeNode *> nodes;
- for (int i=0; i<m_processedNodes.size(); ++i) {
- BinaryTreeNode *node = m_processedNodes.data() + i;
-
- if (node->image.isNull()) {
- QGlyphRun glyphRun = node->glyphRun;
- QRawFont rawFont = glyphRun.rawFont();
- QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
-
- QFontEngine *fontEngine = rawFontD->fontEngine;
-
- KeyType key(qMakePair(fontEngine,
- qMakePair(node->clipNode,
- qMakePair(node->color.rgba(), int(node->selectionState)))));
-
- BinaryTreeNode *otherNode = map.value(key, 0);
- if (otherNode != 0) {
- QGlyphRun &otherGlyphRun = otherNode->glyphRun;
-
- QVector<quint32> otherGlyphIndexes = otherGlyphRun.glyphIndexes();
- QVector<QPointF> otherGlyphPositions = otherGlyphRun.positions();
-
- otherGlyphIndexes += glyphRun.glyphIndexes();
-
- QVector<QPointF> glyphPositions = glyphRun.positions();
- for (int j=0; j<glyphPositions.size(); ++j) {
- otherGlyphPositions += glyphPositions.at(j) + (node->position - otherNode->position);
- }
-
- otherGlyphRun.setGlyphIndexes(otherGlyphIndexes);
- otherGlyphRun.setPositions(otherGlyphPositions);
-
- } else {
- map.insert(key, node);
- nodes.append(node);
- }
- } else {
- parentNode->addImage(node->boundingRect, node->image);
- if (node->selectionState == BinaryTreeNode::Selected) {
- QColor color = m_selectionColor;
- color.setAlpha(128);
- parentNode->appendChildNode(new QSGSimpleRectNode(node->boundingRect, color));
- }
- }
- }
-
- // ...and add clip nodes and glyphs to tree.
- foreach (const BinaryTreeNode *node, nodes) {
-
- QQuickDefaultClipNode *clipNode = node->clipNode;
- if (clipNode != 0 && clipNode->parent() == 0 )
- parentNode->appendChildNode(clipNode);
-
- QColor color = node->selectionState == BinaryTreeNode::Selected
- ? m_selectedTextColor
- : node->color;
-
- parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode);
- }
- }
-}
-
-void QQuickTextNode::mergeFormats(QTextLayout *textLayout,
- QVarLengthArray<QTextLayout::FormatRange> *mergedFormats)
+void QQuickTextNode::initEngine(const QColor& textColor, const QColor& selectedTextColor, const QColor& selectionColor, const QColor& anchorColor)
{
- Q_ASSERT(mergedFormats != 0);
- if (textLayout == 0)
- return;
-
- QList<QTextLayout::FormatRange> additionalFormats = textLayout->additionalFormats();
- for (int i=0; i<additionalFormats.size(); ++i) {
- QTextLayout::FormatRange additionalFormat = additionalFormats.at(i);
- if (additionalFormat.format.hasProperty(QTextFormat::ForegroundBrush)
- || additionalFormat.format.hasProperty(QTextFormat::BackgroundBrush)
- || additionalFormat.format.isAnchor()) {
- // Merge overlapping formats
- if (!mergedFormats->isEmpty()) {
- QTextLayout::FormatRange *lastFormat = mergedFormats->data() + mergedFormats->size() - 1;
-
- if (additionalFormat.start < lastFormat->start + lastFormat->length) {
- QTextLayout::FormatRange *mergedRange = 0;
-
- int length = additionalFormat.length;
- if (additionalFormat.start > lastFormat->start) {
- lastFormat->length = additionalFormat.start - lastFormat->start;
- length -= lastFormat->length;
-
- mergedFormats->append(QTextLayout::FormatRange());
- mergedRange = mergedFormats->data() + mergedFormats->size() - 1;
- lastFormat = mergedFormats->data() + mergedFormats->size() - 2;
- } else {
- mergedRange = lastFormat;
- }
-
- mergedRange->format = lastFormat->format;
- mergedRange->format.merge(additionalFormat.format);
- mergedRange->start = additionalFormat.start;
-
- int end = qMin(additionalFormat.start + additionalFormat.length,
- lastFormat->start + lastFormat->length);
-
- mergedRange->length = end - mergedRange->start;
- length -= mergedRange->length;
-
- additionalFormat.start = end;
- additionalFormat.length = length;
- }
- }
-
- if (additionalFormat.length > 0)
- mergedFormats->append(additionalFormat);
- }
- }
-
-}
-
-namespace {
-
- class ProtectedLayoutAccessor: public QAbstractTextDocumentLayout
- {
- public:
- inline QTextCharFormat formatAccessor(int pos)
- {
- return format(pos);
- }
- };
-
+ m_engine.reset(new QQuickTextNodeEngine);
+ m_engine->m_hasContents = false;
+ m_engine->setTextColor(textColor);
+ m_engine->setSelectedTextColor(selectedTextColor);
+ m_engine->setSelectionColor(selectionColor);
+ m_engine->setAnchorColor(anchorColor);
}
void QQuickTextNode::addImage(const QRectF &rect, const QImage &image)
@@ -1118,11 +205,7 @@ void QQuickTextNode::addTextDocument(const QPointF &position, QTextDocument *tex
const QColor &selectionColor, const QColor &selectedTextColor,
int selectionStart, int selectionEnd)
{
- SelectionEngine engine;
- engine.setTextColor(textColor);
- engine.setSelectedTextColor(selectedTextColor);
- engine.setSelectionColor(selectionColor);
- engine.setAnchorColor(anchorColor);
+ initEngine(textColor, selectedTextColor, selectionColor, anchorColor);
QList<QTextFrame *> frames;
frames.append(textDocument->rootFrame());
@@ -1130,7 +213,7 @@ void QQuickTextNode::addTextDocument(const QPointF &position, QTextDocument *tex
QTextFrame *textFrame = frames.takeFirst();
frames.append(textFrame->childFrames());
- engine.addFrameDecorations(textDocument, textFrame);
+ m_engine->addFrameDecorations(textDocument, textFrame);
if (textFrame->firstPosition() > textFrame->lastPosition()
&& textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
@@ -1140,160 +223,23 @@ void QQuickTextNode::addTextDocument(const QPointF &position, QTextDocument *tex
QRectF rect = a->frameBoundingRect(textFrame);
QTextBlock block = textFrame->firstCursorPosition().block();
- engine.setCurrentLine(block.layout()->lineForTextPosition(pos - block.position()));
- engine.addTextObject(rect.topLeft(), format, BinaryTreeNode::Unselected, textDocument,
+ m_engine->setCurrentLine(block.layout()->lineForTextPosition(pos - block.position()));
+ m_engine->addTextObject(rect.topLeft(), format, QQuickTextNodeEngine::Unselected, textDocument,
pos, textFrame->frameFormat().position());
} else {
QTextFrame::iterator it = textFrame->begin();
while (!it.atEnd()) {
- Q_ASSERT(!engine.currentLine().isValid());
+ Q_ASSERT(!m_engine->currentLine().isValid());
QTextBlock block = it.currentBlock();
-#ifndef QT_NO_IM
- int preeditLength = block.isValid() ? block.layout()->preeditAreaText().length() : 0;
- int preeditPosition = block.isValid() ? block.layout()->preeditAreaPosition() : -1;
-#endif
-
- QVarLengthArray<QTextLayout::FormatRange> colorChanges;
- mergeFormats(block.layout(), &colorChanges);
-
- QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft() + position;
- if (QTextList *textList = block.textList()) {
- QPointF pos = blockPosition;
- QTextLayout *layout = block.layout();
- if (layout->lineCount() > 0) {
- QTextLine firstLine = layout->lineAt(0);
- Q_ASSERT(firstLine.isValid());
-
- engine.setCurrentLine(firstLine);
-
- QRectF textRect = firstLine.naturalTextRect();
- pos += textRect.topLeft();
- if (block.textDirection() == Qt::RightToLeft)
- pos.rx() += textRect.width();
-
- const QTextCharFormat charFormat = block.charFormat();
- QFont font(charFormat.font());
- QFontMetricsF fontMetrics(font);
- QTextListFormat listFormat = textList->format();
-
- QString listItemBullet;
- switch (listFormat.style()) {
- case QTextListFormat::ListCircle:
- listItemBullet = QChar(0x25E6); // White bullet
- break;
- case QTextListFormat::ListSquare:
- listItemBullet = QChar(0x25AA); // Black small square
- break;
- case QTextListFormat::ListDecimal:
- case QTextListFormat::ListLowerAlpha:
- case QTextListFormat::ListUpperAlpha:
- case QTextListFormat::ListLowerRoman:
- case QTextListFormat::ListUpperRoman:
- listItemBullet = textList->itemText(block);
- break;
- default:
- listItemBullet = QChar(0x2022); // Black bullet
- break;
- };
-
- QSizeF size(fontMetrics.width(listItemBullet), fontMetrics.height());
- qreal xoff = fontMetrics.width(QLatin1Char(' '));
- if (block.textDirection() == Qt::LeftToRight)
- xoff = -xoff - size.width();
- engine.setPosition(pos + QPointF(xoff, 0));
-
- QTextLayout layout;
- layout.setFont(font);
- layout.setText(listItemBullet); // Bullet
- layout.beginLayout();
- QTextLine line = layout.createLine();
- line.setPosition(QPointF(0, 0));
- layout.endLayout();
-
- QList<QGlyphRun> glyphRuns = layout.glyphRuns();
- for (int i=0; i<glyphRuns.size(); ++i)
- engine.addUnselectedGlyphs(glyphRuns.at(i));
- }
- }
-
- int textPos = block.position();
- QTextBlock::iterator blockIterator = block.begin();
-
- while (!blockIterator.atEnd()) {
- QTextFragment fragment = blockIterator.fragment();
- QString text = fragment.text();
- if (text.isEmpty())
- continue;
-
- QTextCharFormat charFormat = fragment.charFormat();
- engine.setPosition(blockPosition);
- if (text.contains(QChar::ObjectReplacementCharacter)) {
- QTextFrame *frame = qobject_cast<QTextFrame *>(textDocument->objectForFormat(charFormat));
- if (frame && frame->frameFormat().position() == QTextFrameFormat::InFlow) {
- int blockRelativePosition = textPos - block.position();
- QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
- if (!engine.currentLine().isValid()
- || line.lineNumber() != engine.currentLine().lineNumber()) {
- engine.setCurrentLine(line);
- }
-
- BinaryTreeNode::SelectionState selectionState =
- (selectionStart < textPos + text.length()
- && selectionEnd >= textPos)
- ? BinaryTreeNode::Selected
- : BinaryTreeNode::Unselected;
-
- engine.addTextObject(QPointF(), charFormat, selectionState, textDocument, textPos);
- }
- textPos += text.length();
- } else {
- if (charFormat.foreground().style() != Qt::NoBrush)
- engine.setTextColor(charFormat.foreground().color());
- else if (charFormat.isAnchor())
- engine.setTextColor(anchorColor);
- else
- engine.setTextColor(textColor);
-
- int fragmentEnd = textPos + fragment.length();
-#ifndef QT_NO_IM
- if (preeditPosition >= 0
- && preeditPosition >= textPos
- && preeditPosition <= fragmentEnd) {
- fragmentEnd += preeditLength;
- }
-#endif
-
- textPos = engine.addText(block, charFormat, textColor, colorChanges, textPos, fragmentEnd,
- selectionStart, selectionEnd);
- }
-
- ++blockIterator;
- }
-
-#ifndef QT_NO_IM
- if (preeditLength >= 0 && textPos <= block.position() + preeditPosition) {
- engine.setPosition(blockPosition);
- textPos = block.position() + preeditPosition;
- QTextLine line = block.layout()->lineForTextPosition(preeditPosition);
- if (!engine.currentLine().isValid()
- || line.lineNumber() != engine.currentLine().lineNumber()) {
- engine.setCurrentLine(line);
- }
- textPos = engine.addText(block, block.charFormat(), textColor, colorChanges,
- textPos, textPos + preeditLength,
- selectionStart, selectionEnd);
- }
-#endif
-
- engine.setCurrentLine(QTextLine()); // Reset current line because the text layout changed
+ m_engine->addTextBlock(textDocument, block, position, textColor, anchorColor, selectionStart, selectionEnd);
++it;
}
}
}
- engine.addToSceneGraph(this, style, styleColor);
+ m_engine->addToSceneGraph(this, style, styleColor);
}
void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color,
@@ -1303,12 +249,7 @@ void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLay
int selectionStart, int selectionEnd,
int lineStart, int lineCount)
{
- SelectionEngine engine;
- engine.setTextColor(color);
- engine.setSelectedTextColor(selectedTextColor);
- engine.setSelectionColor(selectionColor);
- engine.setAnchorColor(anchorColor);
- engine.setPosition(position);
+ initEngine(color, selectedTextColor, selectionColor, anchorColor);
#ifndef QT_NO_IM
int preeditLength = textLayout->preeditAreaText().length();
@@ -1316,7 +257,7 @@ void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLay
#endif
QVarLengthArray<QTextLayout::FormatRange> colorChanges;
- mergeFormats(textLayout, &colorChanges);
+ m_engine->mergeFormats(textLayout, &colorChanges);
lineCount = lineCount >= 0
? qMin(lineStart + lineCount, textLayout->lineCount())
@@ -1337,11 +278,11 @@ void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLay
}
#endif
- engine.setCurrentLine(line);
- engine.addGlyphsForRanges(colorChanges, start, end, selectionStart, selectionEnd);
+ m_engine->setCurrentLine(line);
+ m_engine->addGlyphsForRanges(colorChanges, start, end, selectionStart, selectionEnd);
}
- engine.addToSceneGraph(this, style, styleColor);
+ m_engine->addToSceneGraph(this, style, styleColor);
}
void QQuickTextNode::deleteContent()
diff --git a/src/quick/items/qquicktextnode_p.h b/src/quick/items/qquicktextnode_p.h
index dcc4ebe22f..16da3ce685 100644
--- a/src/quick/items/qquicktextnode_p.h
+++ b/src/quick/items/qquicktextnode_p.h
@@ -49,6 +49,7 @@
#include <QtGui/qcolor.h>
#include <QtGui/qtextlayout.h>
#include <QtCore/qvarlengtharray.h>
+#include <QtCore/qscopedpointer.h>
QT_BEGIN_NAMESPACE
@@ -62,6 +63,8 @@ class QSGSimpleRectNode;
class QSGClipNode;
class QSGTexture;
+class QQuickTextNodeEngine;
+
class QQuickTextNode : public QSGTransformNode
{
public:
@@ -104,13 +107,17 @@ public:
void setUseNativeRenderer(bool on) { m_useNativeRenderer = on; }
private:
- void mergeFormats(QTextLayout *textLayout, QVarLengthArray<QTextLayout::FormatRange> *mergedFormats);
+ void initEngine(const QColor &textColor, const QColor &selectedTextColor, const QColor &selectionColor, const QColor& anchorColor = QColor());
+
QSGContext *m_context;
QSGSimpleRectNode *m_cursorNode;
QList<QSGTexture *> m_textures;
QQuickItem *m_ownerElement;
bool m_useNativeRenderer;
+ QScopedPointer<QQuickTextNodeEngine> m_engine;
+
+ friend class QQuickTextEdit;
};
QT_END_NAMESPACE
diff --git a/src/quick/items/qquicktextnodeengine.cpp b/src/quick/items/qquicktextnodeengine.cpp
new file mode 100644
index 0000000000..7bbfd1bcda
--- /dev/null
+++ b/src/quick/items/qquicktextnodeengine.cpp
@@ -0,0 +1,935 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qquicktextnodeengine_p.h"
+
+#include <QtCore/qpoint.h>
+#include <QtGui/qabstracttextdocumentlayout.h>
+#include <QtGui/qrawfont.h>
+#include <QtGui/qtextdocument.h>
+#include <QtGui/qtextlayout.h>
+#include <QtGui/qtextobject.h>
+#include <QtGui/qtexttable.h>
+#include <QtGui/qtextlist.h>
+#include <QtQuick/qsgsimplerectnode.h>
+
+#include <private/qquicktext_p_p.h>
+#include <private/qtextdocumentlayout_p.h>
+#include <private/qtextimagehandler_p.h>
+#include <private/qrawfont_p.h>
+
+QT_BEGIN_NAMESPACE
+
+void QQuickTextNodeEngine::BinaryTreeNode::insert(QVarLengthArray<BinaryTreeNode> *binaryTree, const QGlyphRun &glyphRun, SelectionState selectionState,
+ QQuickTextNode::Decorations decorations, const QColor &textColor,
+ const QColor &backgroundColor, const QPointF &position)
+{
+ QRectF searchRect = glyphRun.boundingRect();
+ searchRect.translate(position);
+
+ if (qFuzzyIsNull(searchRect.width()) || qFuzzyIsNull(searchRect.height()))
+ return;
+
+ decorations |= (glyphRun.underline() ? QQuickTextNode::Underline : QQuickTextNode::NoDecoration);
+ decorations |= (glyphRun.overline() ? QQuickTextNode::Overline : QQuickTextNode::NoDecoration);
+ decorations |= (glyphRun.strikeOut() ? QQuickTextNode::StrikeOut : QQuickTextNode::NoDecoration);
+ decorations |= (backgroundColor.isValid() ? QQuickTextNode::Background : QQuickTextNode::NoDecoration);
+
+ qreal ascent = glyphRun.rawFont().ascent();
+ insert(binaryTree, BinaryTreeNode(glyphRun, selectionState, searchRect, decorations,
+ textColor, backgroundColor, position, ascent));
+}
+
+void QQuickTextNodeEngine::BinaryTreeNode::insert(QVarLengthArray<BinaryTreeNode> *binaryTree, const BinaryTreeNode &binaryTreeNode)
+{
+ int newIndex = binaryTree->size();
+ binaryTree->append(binaryTreeNode);
+ if (newIndex == 0)
+ return;
+
+ int searchIndex = 0;
+ forever {
+ BinaryTreeNode *node = binaryTree->data() + searchIndex;
+ if (binaryTreeNode.boundingRect.left() < node->boundingRect.left()) {
+ if (node->leftChildIndex < 0) {
+ node->leftChildIndex = newIndex;
+ break;
+ } else {
+ searchIndex = node->leftChildIndex;
+ }
+ } else {
+ if (node->rightChildIndex < 0) {
+ node->rightChildIndex = newIndex;
+ break;
+ } else {
+ searchIndex = node->rightChildIndex;
+ }
+ }
+ }
+}
+
+void QQuickTextNodeEngine::BinaryTreeNode::inOrder(const QVarLengthArray<BinaryTreeNode> &binaryTree,
+ QVarLengthArray<int> *sortedIndexes, int currentIndex)
+{
+ Q_ASSERT(currentIndex < binaryTree.size());
+
+ const BinaryTreeNode *node = binaryTree.data() + currentIndex;
+ if (node->leftChildIndex >= 0)
+ inOrder(binaryTree, sortedIndexes, node->leftChildIndex);
+
+ sortedIndexes->append(currentIndex);
+
+ if (node->rightChildIndex >= 0)
+ inOrder(binaryTree, sortedIndexes, node->rightChildIndex);
+}
+
+
+int QQuickTextNodeEngine::addText(const QTextBlock &block,
+ const QTextCharFormat &charFormat,
+ const QColor &textColor,
+ const QVarLengthArray<QTextLayout::FormatRange> &colorChanges,
+ int textPos, int fragmentEnd,
+ int selectionStart, int selectionEnd)
+{
+ if (charFormat.foreground().style() != Qt::NoBrush)
+ setTextColor(charFormat.foreground().color());
+ else
+ setTextColor(textColor);
+
+ while (textPos < fragmentEnd) {
+ int blockRelativePosition = textPos - block.position();
+ QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
+ if (!currentLine().isValid()
+ || line.lineNumber() != currentLine().lineNumber()) {
+ setCurrentLine(line);
+ }
+
+ Q_ASSERT(line.textLength() > 0);
+ int lineEnd = line.textStart() + block.position() + line.textLength();
+
+ int len = qMin(lineEnd - textPos, fragmentEnd - textPos);
+ Q_ASSERT(len > 0);
+
+ int currentStepEnd = textPos + len;
+
+ addGlyphsForRanges(colorChanges,
+ textPos - block.position(),
+ currentStepEnd - block.position(),
+ selectionStart - block.position(),
+ selectionEnd - block.position());
+
+ textPos = currentStepEnd;
+ }
+ return textPos;
+}
+
+void QQuickTextNodeEngine::addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
+ qreal offset, qreal thickness)
+{
+ for (int i=0; i<textDecorations.size(); ++i) {
+ TextDecoration textDecoration = textDecorations.at(i);
+
+ {
+ QRectF &rect = textDecoration.rect;
+ rect.setY(qRound(rect.y() + m_currentLine.ascent() + offset));
+ rect.setHeight(thickness);
+ }
+
+ m_lines.append(textDecoration);
+ }
+}
+
+void QQuickTextNodeEngine::processCurrentLine()
+{
+ // No glyphs, do nothing
+ if (m_currentLineTree.isEmpty())
+ return;
+
+ // 1. Go through current line and get correct decoration position for each node based on
+ // neighbouring decorations. Add decoration to global list
+ // 2. Create clip nodes for all selected text. Try to merge as many as possible within
+ // the line.
+ // 3. Add QRects to a list of selection rects.
+ // 4. Add all nodes to a global processed list
+ QVarLengthArray<int> sortedIndexes; // Indexes in tree sorted by x position
+ BinaryTreeNode::inOrder(m_currentLineTree, &sortedIndexes);
+
+ Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size());
+
+ SelectionState currentSelectionState = Unselected;
+ QRectF currentRect;
+
+ QQuickTextNode::Decorations currentDecorations = QQuickTextNode::NoDecoration;
+ qreal underlineOffset = 0.0;
+ qreal underlineThickness = 0.0;
+
+ qreal overlineOffset = 0.0;
+ qreal overlineThickness = 0.0;
+
+ qreal strikeOutOffset = 0.0;
+ qreal strikeOutThickness = 0.0;
+
+ QRectF decorationRect = currentRect;
+
+ QColor lastColor;
+ QColor lastBackgroundColor;
+
+ QVarLengthArray<TextDecoration> pendingUnderlines;
+ QVarLengthArray<TextDecoration> pendingOverlines;
+ QVarLengthArray<TextDecoration> pendingStrikeOuts;
+ if (!sortedIndexes.isEmpty()) {
+ QQuickDefaultClipNode *currentClipNode = m_hasSelection ? new QQuickDefaultClipNode(QRectF()) : 0;
+ bool currentClipNodeUsed = false;
+ for (int i=0; i<=sortedIndexes.size(); ++i) {
+ BinaryTreeNode *node = 0;
+ if (i < sortedIndexes.size()) {
+ int sortedIndex = sortedIndexes.at(i);
+ Q_ASSERT(sortedIndex < m_currentLineTree.size());
+
+ node = m_currentLineTree.data() + sortedIndex;
+ }
+
+ if (i == 0)
+ currentSelectionState = node->selectionState;
+
+ // Update decorations
+ if (currentDecorations != QQuickTextNode::NoDecoration) {
+ decorationRect.setY(m_position.y() + m_currentLine.y());
+ decorationRect.setHeight(m_currentLine.height());
+
+ if (node != 0)
+ decorationRect.setRight(node->boundingRect.left());
+
+ TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor);
+ if (currentDecorations & QQuickTextNode::Underline)
+ pendingUnderlines.append(textDecoration);
+
+ if (currentDecorations & QQuickTextNode::Overline)
+ pendingOverlines.append(textDecoration);
+
+ if (currentDecorations & QQuickTextNode::StrikeOut)
+ pendingStrikeOuts.append(textDecoration);
+
+ if (currentDecorations & QQuickTextNode::Background)
+ m_backgrounds.append(qMakePair(decorationRect, lastBackgroundColor));
+ }
+
+ // If we've reached an unselected node from a selected node, we add the
+ // selection rect to the graph, and we add decoration every time the
+ // selection state changes, because that means the text color changes
+ if (node == 0 || node->selectionState != currentSelectionState) {
+ if (node != 0)
+ currentRect.setRight(node->boundingRect.left());
+ currentRect.setY(m_position.y() + m_currentLine.y());
+ currentRect.setHeight(m_currentLine.height());
+
+ // Draw selection all the way up to the left edge of the unselected item
+ if (currentSelectionState == Selected)
+ m_selectionRects.append(currentRect);
+
+ if (currentClipNode != 0) {
+ if (!currentClipNodeUsed) {
+ delete currentClipNode;
+ } else {
+ currentClipNode->setIsRectangular(true);
+ currentClipNode->setRect(currentRect);
+ currentClipNode->update();
+ }
+ }
+
+ if (node != 0 && m_hasSelection)
+ currentClipNode = new QQuickDefaultClipNode(QRectF());
+ else
+ currentClipNode = 0;
+ currentClipNodeUsed = false;
+
+ if (node != 0) {
+ currentSelectionState = node->selectionState;
+ currentRect = node->boundingRect;
+
+ // Make sure currentRect is valid, otherwise the unite won't work
+ if (currentRect.isNull())
+ currentRect.setSize(QSizeF(1, 1));
+ }
+ } else {
+ if (currentRect.isNull())
+ currentRect = node->boundingRect;
+ else
+ currentRect = currentRect.united(node->boundingRect);
+ }
+
+ if (node != 0) {
+ node->clipNode = currentClipNode;
+ currentClipNodeUsed = true;
+
+ decorationRect = node->boundingRect;
+
+ // If previous item(s) had underline and current does not, then we add the
+ // pending lines to the lists and likewise for overlines and strikeouts
+ if (!pendingUnderlines.isEmpty()
+ && !(node->decorations & QQuickTextNode::Underline)) {
+ addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
+
+ pendingUnderlines.clear();
+
+ underlineOffset = 0.0;
+ underlineThickness = 0.0;
+ }
+
+ // ### Add pending when overlineOffset/thickness changes to minimize number of
+ // nodes
+ if (!pendingOverlines.isEmpty()) {
+ addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
+
+ pendingOverlines.clear();
+
+ overlineOffset = 0.0;
+ overlineThickness = 0.0;
+ }
+
+ // ### Add pending when overlineOffset/thickness changes to minimize number of
+ // nodes
+ if (!pendingStrikeOuts.isEmpty()) {
+ addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
+
+ pendingStrikeOuts.clear();
+
+ strikeOutOffset = 0.0;
+ strikeOutThickness = 0.0;
+ }
+
+ // Merge current values with previous. Prefer greatest thickness
+ QRawFont rawFont = node->glyphRun.rawFont();
+ if (node->decorations & QQuickTextNode::Underline) {
+ if (rawFont.lineThickness() > underlineThickness) {
+ underlineThickness = rawFont.lineThickness();
+ underlineOffset = rawFont.underlinePosition();
+ }
+ }
+
+ if (node->decorations & QQuickTextNode::Overline) {
+ overlineOffset = -rawFont.ascent();
+ overlineThickness = rawFont.lineThickness();
+ }
+
+ if (node->decorations & QQuickTextNode::StrikeOut) {
+ strikeOutThickness = rawFont.lineThickness();
+ strikeOutOffset = rawFont.ascent() / -3.0;
+ }
+
+ currentDecorations = node->decorations;
+ lastColor = node->color;
+ lastBackgroundColor = node->backgroundColor;
+ m_processedNodes.append(*node);
+ }
+ }
+
+ if (!pendingUnderlines.isEmpty())
+ addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
+
+ if (!pendingOverlines.isEmpty())
+ addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
+
+ if (!pendingStrikeOuts.isEmpty())
+ addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
+ }
+
+ m_currentLineTree.clear();
+ m_currentLine = QTextLine();
+ m_hasSelection = false;
+}
+
+void QQuickTextNodeEngine::addImage(const QRectF &rect, const QImage &image, qreal ascent,
+ SelectionState selectionState,
+ QTextFrameFormat::Position layoutPosition)
+{
+ QRectF searchRect = rect;
+ if (layoutPosition == QTextFrameFormat::InFlow) {
+ if (m_currentLineTree.isEmpty()) {
+ searchRect.moveTopLeft(m_position + m_currentLine.position());
+ } else {
+ const BinaryTreeNode *lastNode = m_currentLineTree.data() + m_currentLineTree.size() - 1;
+ if (lastNode->glyphRun.isRightToLeft()) {
+ QPointF lastPos = lastNode->boundingRect.topLeft();
+ searchRect.moveTopRight(lastPos - QPointF(0, ascent - lastNode->ascent));
+ } else {
+ QPointF lastPos = lastNode->boundingRect.topRight();
+ searchRect.moveTopLeft(lastPos - QPointF(0, ascent - lastNode->ascent));
+ }
+ }
+ }
+
+ BinaryTreeNode::insert(&m_currentLineTree, searchRect, image, ascent, selectionState);
+ m_hasContents = true;
+}
+
+void QQuickTextNodeEngine::addTextObject(const QPointF &position, const QTextCharFormat &format,
+ SelectionState selectionState,
+ QTextDocument *textDocument, int pos,
+ QTextFrameFormat::Position layoutPosition)
+{
+ QTextObjectInterface *handler = textDocument->documentLayout()->handlerForObject(format.objectType());
+ if (handler != 0) {
+ QImage image;
+ QSizeF size = handler->intrinsicSize(textDocument, pos, format);
+
+ if (format.objectType() == QTextFormat::ImageObject) {
+ QTextImageFormat imageFormat = format.toImageFormat();
+ if (QQuickTextDocumentWithImageResources *imageDoc = qobject_cast<QQuickTextDocumentWithImageResources *>(textDocument)) {
+ image = imageDoc->image(imageFormat);
+
+ if (image.isNull())
+ return;
+ } else {
+ QTextImageHandler *imageHandler = static_cast<QTextImageHandler *>(handler);
+ image = imageHandler->image(textDocument, imageFormat);
+ }
+ }
+
+ if (image.isNull()) {
+ image = QImage(size.toSize(), QImage::Format_ARGB32_Premultiplied);
+ image.fill(Qt::transparent);
+ {
+ QPainter painter(&image);
+ handler->drawObject(&painter, image.rect(), textDocument, pos, format);
+ }
+ }
+
+ qreal ascent;
+ QFontMetrics m(format.font());
+ switch (format.verticalAlignment())
+ {
+ case QTextCharFormat::AlignMiddle:
+ ascent = size.height() / 2 - 1;
+ break;
+ case QTextCharFormat::AlignBaseline:
+ ascent = size.height() - m.descent() - 1;
+ break;
+ default:
+ ascent = size.height() - 1;
+ }
+
+ addImage(QRectF(position, size), image, ascent, selectionState, layoutPosition);
+ }
+}
+
+void QQuickTextNodeEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun)
+{
+ BinaryTreeNode::insert(&m_currentLineTree, glyphRun, Unselected,
+ QQuickTextNode::NoDecoration, m_textColor, m_backgroundColor, m_position);
+}
+
+void QQuickTextNodeEngine::addSelectedGlyphs(const QGlyphRun &glyphRun)
+{
+ int currentSize = m_currentLineTree.size();
+ BinaryTreeNode::insert(&m_currentLineTree, glyphRun, Selected,
+ QQuickTextNode::NoDecoration, m_textColor, m_backgroundColor, m_position);
+ m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize;
+}
+
+void QQuickTextNodeEngine::addGlyphsForRanges(const QVarLengthArray<QTextLayout::FormatRange> &ranges,
+ int start, int end,
+ int selectionStart, int selectionEnd)
+{
+ int currentPosition = start;
+ int remainingLength = end - start;
+ for (int j=0; j<ranges.size(); ++j) {
+ const QTextLayout::FormatRange &range = ranges.at(j);
+ if (range.start + range.length >= currentPosition
+ && range.start < currentPosition + remainingLength) {
+
+ if (range.start > currentPosition) {
+ addGlyphsInRange(currentPosition, range.start - currentPosition,
+ QColor(), QColor(), selectionStart, selectionEnd);
+ }
+ int rangeEnd = qMin(range.start + range.length, currentPosition + remainingLength);
+ QColor rangeColor;
+ if (range.format.hasProperty(QTextFormat::ForegroundBrush))
+ rangeColor = range.format.foreground().color();
+ else if (range.format.isAnchor())
+ rangeColor = m_anchorColor;
+ QColor rangeBackgroundColor = range.format.hasProperty(QTextFormat::BackgroundBrush)
+ ? range.format.background().color()
+ : QColor();
+
+ addGlyphsInRange(range.start, rangeEnd - range.start,
+ rangeColor, rangeBackgroundColor,
+ selectionStart, selectionEnd);
+
+ currentPosition = range.start + range.length;
+ remainingLength = end - currentPosition;
+
+ } else if (range.start > currentPosition + remainingLength || remainingLength <= 0) {
+ break;
+ }
+ }
+
+ if (remainingLength > 0) {
+ addGlyphsInRange(currentPosition, remainingLength, QColor(), QColor(),
+ selectionStart, selectionEnd);
+ }
+
+}
+
+void QQuickTextNodeEngine::addGlyphsInRange(int rangeStart, int rangeLength,
+ const QColor &color, const QColor &backgroundColor,
+ int selectionStart, int selectionEnd)
+{
+ QColor oldColor;
+ if (color.isValid()) {
+ oldColor = m_textColor;
+ m_textColor = color;
+ }
+
+ QColor oldBackgroundColor = m_backgroundColor;
+ if (backgroundColor.isValid()) {
+ oldBackgroundColor = m_backgroundColor;
+ m_backgroundColor = backgroundColor;
+ }
+
+ bool hasSelection = selectionEnd >= 0
+ && selectionStart <= selectionEnd;
+
+ QTextLine &line = m_currentLine;
+ int rangeEnd = rangeStart + rangeLength;
+ if (!hasSelection || (selectionStart > rangeEnd || selectionEnd < rangeStart)) {
+ QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart, rangeLength);
+ for (int j=0; j<glyphRuns.size(); ++j) {
+ const QGlyphRun &glyphRun = glyphRuns.at(j);
+ addUnselectedGlyphs(glyphRun);
+ }
+ } else {
+ if (rangeStart < selectionStart) {
+ QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart,
+ qMin(selectionStart - rangeStart,
+ rangeLength));
+
+ for (int j=0; j<glyphRuns.size(); ++j) {
+ const QGlyphRun &glyphRun = glyphRuns.at(j);
+ addUnselectedGlyphs(glyphRun);
+ }
+ }
+
+ if (rangeEnd > selectionStart) {
+ int start = qMax(selectionStart, rangeStart);
+ int length = qMin(selectionEnd - start + 1, rangeEnd - start);
+ QList<QGlyphRun> glyphRuns = line.glyphRuns(start, length);
+
+ for (int j=0; j<glyphRuns.size(); ++j) {
+ const QGlyphRun &glyphRun = glyphRuns.at(j);
+ addSelectedGlyphs(glyphRun);
+ }
+ }
+
+ if (selectionEnd >= rangeStart && selectionEnd < rangeEnd) {
+ QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionEnd + 1, rangeEnd - selectionEnd - 1);
+ for (int j=0; j<glyphRuns.size(); ++j) {
+ const QGlyphRun &glyphRun = glyphRuns.at(j);
+ addUnselectedGlyphs(glyphRun);
+ }
+ }
+ }
+
+ if (backgroundColor.isValid())
+ m_backgroundColor = oldBackgroundColor;
+
+ if (oldColor.isValid())
+ m_textColor = oldColor;
+}
+
+void QQuickTextNodeEngine::addBorder(const QRectF &rect, qreal border,
+ QTextFrameFormat::BorderStyle borderStyle,
+ const QBrush &borderBrush)
+{
+ QColor color = borderBrush.color();
+
+ // Currently we don't support other styles than solid
+ Q_UNUSED(borderStyle);
+
+ m_backgrounds.append(qMakePair(QRectF(rect.left(), rect.top(), border, rect.height() + border), color));
+ m_backgrounds.append(qMakePair(QRectF(rect.left() + border, rect.top(), rect.width(), border), color));
+ m_backgrounds.append(qMakePair(QRectF(rect.right(), rect.top() + border, border, rect.height() - border), color));
+ m_backgrounds.append(qMakePair(QRectF(rect.left() + border, rect.bottom(), rect.width(), border), color));
+}
+
+void QQuickTextNodeEngine::addFrameDecorations(QTextDocument *document, QTextFrame *frame)
+{
+ QTextDocumentLayout *documentLayout = qobject_cast<QTextDocumentLayout *>(document->documentLayout());
+ QTextFrameFormat frameFormat = frame->format().toFrameFormat();
+
+ QTextTable *table = qobject_cast<QTextTable *>(frame);
+ QRectF boundingRect = table == 0
+ ? documentLayout->frameBoundingRect(frame)
+ : documentLayout->tableBoundingRect(table);
+
+ QBrush bg = frame->frameFormat().background();
+ if (bg.style() != Qt::NoBrush)
+ m_backgrounds.append(qMakePair(boundingRect, bg.color()));
+
+ if (!frameFormat.hasProperty(QTextFormat::FrameBorder))
+ return;
+
+ qreal borderWidth = frameFormat.border();
+ if (qFuzzyIsNull(borderWidth))
+ return;
+
+ QBrush borderBrush = frameFormat.borderBrush();
+ QTextFrameFormat::BorderStyle borderStyle = frameFormat.borderStyle();
+ if (borderStyle == QTextFrameFormat::BorderStyle_None)
+ return;
+
+ addBorder(boundingRect.adjusted(frameFormat.leftMargin(), frameFormat.topMargin(),
+ -frameFormat.rightMargin(), -frameFormat.bottomMargin()),
+ borderWidth, borderStyle, borderBrush);
+ if (table != 0) {
+ int rows = table->rows();
+ int columns = table->columns();
+
+ for (int row=0; row<rows; ++row) {
+ for (int column=0; column<columns; ++column) {
+ QTextTableCell cell = table->cellAt(row, column);
+
+ QRectF cellRect = documentLayout->tableCellBoundingRect(table, cell);
+ addBorder(cellRect.adjusted(-borderWidth, -borderWidth, 0, 0), borderWidth,
+ borderStyle, borderBrush);
+ }
+ }
+ }
+}
+
+void QQuickTextNodeEngine::addToSceneGraph(QQuickTextNode *parentNode,
+ QQuickText::TextStyle style,
+ const QColor &styleColor)
+{
+ if (m_currentLine.isValid())
+ processCurrentLine();
+
+
+ for (int i=0; i<m_backgrounds.size(); ++i) {
+ const QRectF &rect = m_backgrounds.at(i).first;
+ const QColor &color = m_backgrounds.at(i).second;
+
+ parentNode->appendChildNode(new QSGSimpleRectNode(rect, color));
+ }
+
+ // First, prepend all selection rectangles to the tree
+ for (int i=0; i<m_selectionRects.size(); ++i) {
+ const QRectF &rect = m_selectionRects.at(i);
+
+ parentNode->appendChildNode(new QSGSimpleRectNode(rect, m_selectionColor));
+ }
+
+ // Finally, add decorations for each node to the tree.
+ for (int i=0; i<m_lines.size(); ++i) {
+ const TextDecoration &textDecoration = m_lines.at(i);
+
+ QColor color = textDecoration.selectionState == Selected
+ ? m_selectedTextColor
+ : textDecoration.color;
+
+ parentNode->appendChildNode(new QSGSimpleRectNode(textDecoration.rect, color));
+ }
+
+ // Then, go through all the nodes for all lines and combine all QGlyphRuns with a common
+ // font, selection state and clip node.
+ typedef QPair<QFontEngine *, QPair<QQuickDefaultClipNode *, QPair<QRgb, int> > > KeyType;
+ QHash<KeyType, BinaryTreeNode *> map;
+ QList<BinaryTreeNode *> nodes;
+ for (int i = 0; i < m_processedNodes.size(); ++i) {
+ BinaryTreeNode *node = m_processedNodes.data() + i;
+
+ if (node->image.isNull()) {
+ QGlyphRun glyphRun = node->glyphRun;
+ QRawFont rawFont = glyphRun.rawFont();
+ QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
+
+ QFontEngine *fontEngine = rawFontD->fontEngine;
+
+ KeyType key(qMakePair(fontEngine,
+ qMakePair(node->clipNode,
+ qMakePair(node->color.rgba(), int(node->selectionState)))));
+
+ BinaryTreeNode *otherNode = map.value(key, 0);
+ if (otherNode != 0) {
+ QGlyphRun &otherGlyphRun = otherNode->glyphRun;
+
+ QVector<quint32> otherGlyphIndexes = otherGlyphRun.glyphIndexes();
+ QVector<QPointF> otherGlyphPositions = otherGlyphRun.positions();
+
+ otherGlyphIndexes += glyphRun.glyphIndexes();
+
+ QVector<QPointF> glyphPositions = glyphRun.positions();
+ otherGlyphPositions.reserve(otherGlyphPositions.size() + glyphPositions.size());
+ for (int j = 0; j < glyphPositions.size(); ++j) {
+ otherGlyphPositions += glyphPositions.at(j) + (node->position - otherNode->position);
+ }
+
+ otherGlyphRun.setGlyphIndexes(otherGlyphIndexes);
+ otherGlyphRun.setPositions(otherGlyphPositions);
+
+ } else {
+ map.insert(key, node);
+ nodes.append(node);
+ }
+ } else {
+ parentNode->addImage(node->boundingRect, node->image);
+ if (node->selectionState == Selected) {
+ QColor color = m_selectionColor;
+ color.setAlpha(128);
+ parentNode->appendChildNode(new QSGSimpleRectNode(node->boundingRect, color));
+ }
+ }
+ }
+
+ foreach (const BinaryTreeNode *node, nodes) {
+
+ QQuickDefaultClipNode *clipNode = node->clipNode;
+ if (clipNode != 0 && clipNode->parent() == 0 )
+ parentNode->appendChildNode(clipNode);
+
+ QColor color = node->selectionState == Selected
+ ? m_selectedTextColor
+ : node->color;
+
+ parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode);
+ }
+}
+
+void QQuickTextNodeEngine::mergeFormats(QTextLayout *textLayout, QVarLengthArray<QTextLayout::FormatRange> *mergedFormats)
+{
+ Q_ASSERT(mergedFormats != 0);
+ if (textLayout == 0)
+ return;
+
+ QList<QTextLayout::FormatRange> additionalFormats = textLayout->additionalFormats();
+ for (int i=0; i<additionalFormats.size(); ++i) {
+ QTextLayout::FormatRange additionalFormat = additionalFormats.at(i);
+ if (additionalFormat.format.hasProperty(QTextFormat::ForegroundBrush)
+ || additionalFormat.format.hasProperty(QTextFormat::BackgroundBrush)
+ || additionalFormat.format.isAnchor()) {
+ // Merge overlapping formats
+ if (!mergedFormats->isEmpty()) {
+ QTextLayout::FormatRange *lastFormat = mergedFormats->data() + mergedFormats->size() - 1;
+
+ if (additionalFormat.start < lastFormat->start + lastFormat->length) {
+ QTextLayout::FormatRange *mergedRange = 0;
+
+ int length = additionalFormat.length;
+ if (additionalFormat.start > lastFormat->start) {
+ lastFormat->length = additionalFormat.start - lastFormat->start;
+ length -= lastFormat->length;
+
+ mergedFormats->append(QTextLayout::FormatRange());
+ mergedRange = mergedFormats->data() + mergedFormats->size() - 1;
+ lastFormat = mergedFormats->data() + mergedFormats->size() - 2;
+ } else {
+ mergedRange = lastFormat;
+ }
+
+ mergedRange->format = lastFormat->format;
+ mergedRange->format.merge(additionalFormat.format);
+ mergedRange->start = additionalFormat.start;
+
+ int end = qMin(additionalFormat.start + additionalFormat.length,
+ lastFormat->start + lastFormat->length);
+
+ mergedRange->length = end - mergedRange->start;
+ length -= mergedRange->length;
+
+ additionalFormat.start = end;
+ additionalFormat.length = length;
+ }
+ }
+
+ if (additionalFormat.length > 0)
+ mergedFormats->append(additionalFormat);
+ }
+ }
+
+}
+
+void QQuickTextNodeEngine::addTextBlock(QTextDocument *textDocument, const QTextBlock &block, const QPointF &position, const QColor &textColor, const QColor &anchorColor, int selectionStart, int selectionEnd)
+{
+ Q_ASSERT(textDocument);
+#ifndef QT_NO_IM
+ int preeditLength = block.isValid() ? block.layout()->preeditAreaText().length() : 0;
+ int preeditPosition = block.isValid() ? block.layout()->preeditAreaPosition() : -1;
+#endif
+
+ QVarLengthArray<QTextLayout::FormatRange> colorChanges;
+ mergeFormats(block.layout(), &colorChanges);
+
+ QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft() + position;
+ if (QTextList *textList = block.textList()) {
+ QPointF pos = blockPosition;
+ QTextLayout *layout = block.layout();
+ if (layout->lineCount() > 0) {
+ QTextLine firstLine = layout->lineAt(0);
+ Q_ASSERT(firstLine.isValid());
+
+ setCurrentLine(firstLine);
+
+ QRectF textRect = firstLine.naturalTextRect();
+ pos += textRect.topLeft();
+ if (block.textDirection() == Qt::RightToLeft)
+ pos.rx() += textRect.width();
+
+ const QTextCharFormat charFormat = block.charFormat();
+ QFont font(charFormat.font());
+ QFontMetricsF fontMetrics(font);
+ QTextListFormat listFormat = textList->format();
+
+ QString listItemBullet;
+ switch (listFormat.style()) {
+ case QTextListFormat::ListCircle:
+ listItemBullet = QChar(0x25E6); // White bullet
+ break;
+ case QTextListFormat::ListSquare:
+ listItemBullet = QChar(0x25AA); // Black small square
+ break;
+ case QTextListFormat::ListDecimal:
+ case QTextListFormat::ListLowerAlpha:
+ case QTextListFormat::ListUpperAlpha:
+ case QTextListFormat::ListLowerRoman:
+ case QTextListFormat::ListUpperRoman:
+ listItemBullet = textList->itemText(block);
+ break;
+ default:
+ listItemBullet = QChar(0x2022); // Black bullet
+ break;
+ };
+
+ QSizeF size(fontMetrics.width(listItemBullet), fontMetrics.height());
+ qreal xoff = fontMetrics.width(QLatin1Char(' '));
+ if (block.textDirection() == Qt::LeftToRight)
+ xoff = -xoff - size.width();
+ setPosition(pos + QPointF(xoff, 0));
+
+ QTextLayout layout;
+ layout.setFont(font);
+ layout.setText(listItemBullet); // Bullet
+ layout.beginLayout();
+ QTextLine line = layout.createLine();
+ line.setPosition(QPointF(0, 0));
+ layout.endLayout();
+
+ QList<QGlyphRun> glyphRuns = layout.glyphRuns();
+ for (int i=0; i<glyphRuns.size(); ++i)
+ addUnselectedGlyphs(glyphRuns.at(i));
+ }
+ }
+
+ int textPos = block.position();
+ QTextBlock::iterator blockIterator = block.begin();
+
+ while (!blockIterator.atEnd()) {
+ QTextFragment fragment = blockIterator.fragment();
+ QString text = fragment.text();
+ if (text.isEmpty())
+ continue;
+
+ QTextCharFormat charFormat = fragment.charFormat();
+ setPosition(blockPosition);
+ if (text.contains(QChar::ObjectReplacementCharacter)) {
+ QTextFrame *frame = qobject_cast<QTextFrame *>(textDocument->objectForFormat(charFormat));
+ if (frame && frame->frameFormat().position() == QTextFrameFormat::InFlow) {
+ int blockRelativePosition = textPos - block.position();
+ QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
+ if (!currentLine().isValid()
+ || line.lineNumber() != currentLine().lineNumber()) {
+ setCurrentLine(line);
+ }
+
+ QQuickTextNodeEngine::SelectionState selectionState =
+ (selectionStart < textPos + text.length()
+ && selectionEnd >= textPos)
+ ? QQuickTextNodeEngine::Selected
+ : QQuickTextNodeEngine::Unselected;
+
+ addTextObject(QPointF(), charFormat, selectionState, textDocument, textPos);
+ }
+ textPos += text.length();
+ } else {
+ if (charFormat.foreground().style() != Qt::NoBrush)
+ setTextColor(charFormat.foreground().color());
+ else if (charFormat.isAnchor())
+ setTextColor(anchorColor);
+ else
+ setTextColor(textColor);
+
+ int fragmentEnd = textPos + fragment.length();
+#ifndef QT_NO_IM
+ if (preeditPosition >= 0
+ && preeditPosition >= textPos
+ && preeditPosition <= fragmentEnd) {
+ fragmentEnd += preeditLength;
+ }
+#endif
+
+ textPos = addText(block, charFormat, textColor, colorChanges, textPos, fragmentEnd,
+ selectionStart, selectionEnd);
+ }
+
+ ++blockIterator;
+ }
+
+#ifndef QT_NO_IM
+ if (preeditLength >= 0 && textPos <= block.position() + preeditPosition) {
+ setPosition(blockPosition);
+ textPos = block.position() + preeditPosition;
+ QTextLine line = block.layout()->lineForTextPosition(preeditPosition);
+ if (!currentLine().isValid()
+ || line.lineNumber() != currentLine().lineNumber()) {
+ setCurrentLine(line);
+ }
+ textPos = addText(block, block.charFormat(), textColor, colorChanges,
+ textPos, textPos + preeditLength,
+ selectionStart, selectionEnd);
+ }
+#endif
+
+ setCurrentLine(QTextLine()); // Reset current line because the text layout changed
+ m_hasContents = true;
+}
+
+
+QT_END_NAMESPACE
+
diff --git a/src/quick/items/qquicktextnodeengine_p.h b/src/quick/items/qquicktextnodeengine_p.h
new file mode 100644
index 0000000000..6a98d6b470
--- /dev/null
+++ b/src/quick/items/qquicktextnodeengine_p.h
@@ -0,0 +1,234 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/qlist.h>
+#include <QtCore/qvarlengtharray.h>
+#include <QtGui/qcolor.h>
+#include <QtGui/qglyphrun.h>
+#include <QtGui/qimage.h>
+#include <QtGui/qtextdocument.h>
+#include <QtGui/qtextlayout.h>
+#include "qquickclipnode_p.h"
+#include "qquicktextnode_p.h"
+
+#ifndef QQUICKTEXTNODEENGINE_P_H
+#define QQUICKTEXTNODEENGINE_P_H
+
+QT_BEGIN_NAMESPACE
+
+// Engine that takes glyph runs as input, and produces a set of glyph nodes, clip nodes,
+// and rectangle nodes to represent the text, decorations and selection. Will try to minimize
+// number of nodes, and join decorations in neighbouring items
+
+class QQuickTextNodeEngine {
+
+public:
+
+ enum SelectionState {
+ Unselected,
+ Selected
+ };
+
+ struct BinaryTreeNode {
+
+ BinaryTreeNode()
+ : selectionState(Unselected), clipNode(0), decorations(QQuickTextNode::NoDecoration)
+ , ascent(0.0), leftChildIndex(-1), rightChildIndex(-1)
+ {
+ }
+
+ BinaryTreeNode(const QRectF &brect, const QImage &i, SelectionState selState, qreal a)
+ : boundingRect(brect), selectionState(selState), clipNode(0), decorations(QQuickTextNode::NoDecoration)
+ , image(i), ascent(a), leftChildIndex(-1), rightChildIndex(-1)
+ {
+ }
+
+ BinaryTreeNode(const QGlyphRun &g, SelectionState selState, const QRectF &brect,
+ const QQuickTextNode::Decorations &decs, const QColor &c, const QColor &bc,
+ const QPointF &pos, qreal a)
+ : glyphRun(g), boundingRect(brect), selectionState(selState), clipNode(0), decorations(decs)
+ , color(c), backgroundColor(bc), position(pos), ascent(a), leftChildIndex(-1), rightChildIndex(-1)
+ {
+ }
+
+ QGlyphRun glyphRun;
+ QRectF boundingRect;
+ SelectionState selectionState;
+ QQuickDefaultClipNode *clipNode;
+ QQuickTextNode::Decorations decorations;
+ QColor color;
+ QColor backgroundColor;
+ QPointF position;
+ QImage image;
+ qreal ascent;
+
+ int leftChildIndex;
+ int rightChildIndex;
+
+ static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree, const QRectF &rect, const QImage &image, qreal ascent, SelectionState selectionState)
+ { insert(binaryTree, BinaryTreeNode(rect, image, selectionState, ascent)); }
+
+ static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree, const QGlyphRun &glyphRun, SelectionState selectionState,
+ QQuickTextNode::Decorations decorations, const QColor &textColor, const QColor &backgroundColor, const QPointF &position);
+ static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree, const BinaryTreeNode &binaryTreeNode);
+ static void inOrder(const QVarLengthArray<BinaryTreeNode> &binaryTree, QVarLengthArray<int> *sortedIndexes, int currentIndex = 0);
+ };
+
+ QQuickTextNodeEngine() : m_hasSelection(false), m_hasContents(false) {}
+
+ bool hasContents() const { return m_hasContents; }
+ void addTextBlock(QTextDocument *, const QTextBlock &, const QPointF &position, const QColor &textColor, const QColor& anchorColor, int selectionStart, int selectionEnd);
+ QTextLine currentLine() const { return m_currentLine; }
+
+ void setCurrentLine(const QTextLine &currentLine)
+ {
+ if (m_currentLine.isValid())
+ processCurrentLine();
+
+ m_currentLine = currentLine;
+ }
+
+ void addBorder(const QRectF &rect, qreal border, QTextFrameFormat::BorderStyle borderStyle,
+ const QBrush &borderBrush);
+ void addFrameDecorations(QTextDocument *document, QTextFrame *frame);
+ void addImage(const QRectF &rect, const QImage &image, qreal ascent,
+ SelectionState selectionState,
+ QTextFrameFormat::Position layoutPosition);
+ int addText(const QTextBlock &block,
+ const QTextCharFormat &charFormat,
+ const QColor &textColor,
+ const QVarLengthArray<QTextLayout::FormatRange> &colorChanges,
+ int textPos, int fragmentEnd,
+ int selectionStart, int selectionEnd);
+ void addTextObject(const QPointF &position, const QTextCharFormat &format,
+ SelectionState selectionState,
+ QTextDocument *textDocument, int pos,
+ QTextFrameFormat::Position layoutPosition = QTextFrameFormat::InFlow);
+ void addSelectedGlyphs(const QGlyphRun &glyphRun);
+ void addUnselectedGlyphs(const QGlyphRun &glyphRun);
+ void addGlyphsInRange(int rangeStart, int rangeEnd,
+ const QColor &color, const QColor &backgroundColor,
+ int selectionStart, int selectionEnd);
+ void addGlyphsForRanges(const QVarLengthArray<QTextLayout::FormatRange> &ranges,
+ int start, int end,
+ int selectionStart, int selectionEnd);
+
+ void addToSceneGraph(QQuickTextNode *parent,
+ QQuickText::TextStyle style = QQuickText::Normal,
+ const QColor &styleColor = QColor());
+
+ void setSelectionColor(const QColor &selectionColor)
+ {
+ m_selectionColor = selectionColor;
+ }
+
+ void setSelectedTextColor(const QColor &selectedTextColor)
+ {
+ m_selectedTextColor = selectedTextColor;
+ }
+
+ void setTextColor(const QColor &textColor)
+ {
+ m_textColor = textColor;
+ }
+
+ void setAnchorColor(const QColor &anchorColor)
+ {
+ m_anchorColor = anchorColor;
+ }
+
+ void setPosition(const QPointF &position)
+ {
+ m_position = position;
+ }
+
+
+
+
+private:
+ struct TextDecoration
+ {
+ TextDecoration() : selectionState(Unselected) {}
+ TextDecoration(const SelectionState &s,
+ const QRectF &r,
+ const QColor &c)
+ : selectionState(s)
+ , rect(r)
+ , color(c)
+ {
+ }
+
+ SelectionState selectionState;
+ QRectF rect;
+ QColor color;
+ };
+
+ void processCurrentLine();
+ void addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations, qreal offset, qreal thickness);
+ void mergeFormats(QTextLayout *textLayout, QVarLengthArray<QTextLayout::FormatRange> *mergedFormats);
+
+ QColor m_selectionColor;
+ QColor m_textColor;
+ QColor m_backgroundColor;
+ QColor m_selectedTextColor;
+ QColor m_anchorColor;
+ QPointF m_position;
+
+ QTextLine m_currentLine;
+
+ QList<QPair<QRectF, QColor> > m_backgrounds;
+ QList<QRectF> m_selectionRects;
+ QVarLengthArray<BinaryTreeNode> m_currentLineTree;
+
+ QList<TextDecoration> m_lines;
+ QVector<BinaryTreeNode> m_processedNodes;
+
+ QList<QPair<QRectF, QImage> > m_images;
+
+ bool m_hasSelection : 1;
+ bool m_hasContents : 1;
+ friend class QQuickTextNode;
+
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKTEXTNODEENGINE_P_H
diff --git a/tests/auto/qml/qml.pro b/tests/auto/qml/qml.pro
index 584533c65b..b973d44012 100644
--- a/tests/auto/qml/qml.pro
+++ b/tests/auto/qml/qml.pro
@@ -53,7 +53,8 @@ PRIVATETESTS += \
qqmlbundle \
qrcqml \
v4 \
- qqmltimer
+ qqmltimer \
+ qqmlinstantiator
qtHaveModule(widgets) {
PUBLICTESTS += \
diff --git a/tests/auto/qml/qqmlinstantiator/data/createMultiple.qml b/tests/auto/qml/qqmlinstantiator/data/createMultiple.qml
new file mode 100644
index 0000000000..facfadfb50
--- /dev/null
+++ b/tests/auto/qml/qqmlinstantiator/data/createMultiple.qml
@@ -0,0 +1,9 @@
+import QtQml 2.1
+
+Instantiator {
+ model: 10
+ delegate: QtObject {
+ property bool success: true
+ property int idx: index
+ }
+}
diff --git a/tests/auto/qml/qqmlinstantiator/data/createNone.qml b/tests/auto/qml/qqmlinstantiator/data/createNone.qml
new file mode 100644
index 0000000000..275e27c4b6
--- /dev/null
+++ b/tests/auto/qml/qqmlinstantiator/data/createNone.qml
@@ -0,0 +1,12 @@
+import QtQml 2.1
+
+Instantiator {
+ model: 0
+ property bool success: true
+ QtObject {
+ property bool success: true
+ property int idx: index
+ }
+ onObjectChanged: success = false;//Don't create intermediate objects
+ onCountChanged: success = false;//Don't create intermediate objects
+}
diff --git a/tests/auto/qml/qqmlinstantiator/data/createSingle.qml b/tests/auto/qml/qqmlinstantiator/data/createSingle.qml
new file mode 100644
index 0000000000..b04e62eb48
--- /dev/null
+++ b/tests/auto/qml/qqmlinstantiator/data/createSingle.qml
@@ -0,0 +1,8 @@
+import QtQml 2.1
+
+Instantiator {
+ QtObject {
+ property bool success: true
+ property int idx: index
+ }
+}
diff --git a/tests/auto/qml/qqmlinstantiator/data/inactive.qml b/tests/auto/qml/qqmlinstantiator/data/inactive.qml
new file mode 100644
index 0000000000..8f5e21f990
--- /dev/null
+++ b/tests/auto/qml/qqmlinstantiator/data/inactive.qml
@@ -0,0 +1,9 @@
+import QtQml 2.1
+
+Instantiator {
+ active: false
+ QtObject {
+ property bool success: true
+ property int idx: index
+ }
+}
diff --git a/tests/auto/qml/qqmlinstantiator/data/stringModel.qml b/tests/auto/qml/qqmlinstantiator/data/stringModel.qml
new file mode 100644
index 0000000000..ede2705df7
--- /dev/null
+++ b/tests/auto/qml/qqmlinstantiator/data/stringModel.qml
@@ -0,0 +1,9 @@
+import QtQml 2.1
+
+Instantiator {
+ model: ["alpha", "beta", "gamma", "delta"]
+ delegate: QtObject {
+ property bool success: index == 1 ? datum.length == 4 : datum.length == 5
+ property string datum: modelData
+ }
+}
diff --git a/tests/auto/qml/qqmlinstantiator/qqmlinstantiator.pro b/tests/auto/qml/qqmlinstantiator/qqmlinstantiator.pro
new file mode 100644
index 0000000000..aa83da1509
--- /dev/null
+++ b/tests/auto/qml/qqmlinstantiator/qqmlinstantiator.pro
@@ -0,0 +1,12 @@
+CONFIG += testcase
+TARGET = tst_qqmlinstantiator
+macx:CONFIG -= app_bundle
+
+INCLUDEPATH += ../../shared/
+SOURCES += tst_qqmlinstantiator.cpp
+
+include (../../shared/util.pri)
+
+TESTDATA = data/*
+
+QT += core-private gui-private qml-private testlib
diff --git a/tests/auto/qml/qqmlinstantiator/tst_qqmlinstantiator.cpp b/tests/auto/qml/qqmlinstantiator/tst_qqmlinstantiator.cpp
new file mode 100644
index 0000000000..3e90eb2cbe
--- /dev/null
+++ b/tests/auto/qml/qqmlinstantiator/tst_qqmlinstantiator.cpp
@@ -0,0 +1,198 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Research In Motion.
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include <qtest.h>
+#include <QSignalSpy>
+#include <QDebug>
+
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlcomponent.h>
+#include <QtQml/private/qqmlinstantiator_p.h>
+#include "../../shared/util.h"
+
+class tst_qqmlinstantiator: public QQmlDataTest
+{
+ Q_OBJECT
+
+private slots:
+ void createNone();
+ void createSingle();
+ void createMultiple();
+ void stringModel();
+ void activeProperty();
+ void intModelChange();
+};
+
+void tst_qqmlinstantiator::createNone()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, testFileUrl("createNone.qml"));
+ QQmlInstantiator *instantiator = qobject_cast<QQmlInstantiator*>(component.create());
+ QVERIFY(instantiator != 0);
+ QCOMPARE(instantiator->isActive(), true);
+ QCOMPARE(instantiator->count(), 0);
+ QCOMPARE(instantiator->property("success").toBool(), true);
+ QVERIFY(instantiator->delegate()->isReady());
+}
+
+void tst_qqmlinstantiator::createSingle()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, testFileUrl("createSingle.qml"));
+ QQmlInstantiator *instantiator = qobject_cast<QQmlInstantiator*>(component.create());
+ QVERIFY(instantiator != 0);
+ QCOMPARE(instantiator->isActive(), true);
+ QCOMPARE(instantiator->count(), 1);
+ QVERIFY(instantiator->delegate()->isReady());
+
+ QObject *object = instantiator->object();
+ QVERIFY(object);
+ QCOMPARE(object->parent(), instantiator);
+ QCOMPARE(object->property("success").toBool(), true);
+ QCOMPARE(object->property("idx").toInt(), 0);
+}
+
+void tst_qqmlinstantiator::createMultiple()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, testFileUrl("createMultiple.qml"));
+ QQmlInstantiator *instantiator = qobject_cast<QQmlInstantiator*>(component.create());
+ QVERIFY(instantiator != 0);
+ QCOMPARE(instantiator->isActive(), true);
+ QCOMPARE(instantiator->count(), 10);
+
+ for (int i=0; i<10; i++) {
+ QObject *object = instantiator->objectAt(i);
+ QVERIFY(object);
+ QCOMPARE(object->parent(), instantiator);
+ QCOMPARE(object->property("success").toBool(), true);
+ QCOMPARE(object->property("idx").toInt(), i);
+ }
+}
+
+void tst_qqmlinstantiator::stringModel()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, testFileUrl("stringModel.qml"));
+ QQmlInstantiator *instantiator = qobject_cast<QQmlInstantiator*>(component.create());
+ QVERIFY(instantiator != 0);
+ QCOMPARE(instantiator->isActive(), true);
+ QCOMPARE(instantiator->count(), 4);
+
+ for (int i=0; i<4; i++) {
+ QObject *object = instantiator->objectAt(i);
+ QVERIFY(object);
+ QCOMPARE(object->parent(), instantiator);
+ QCOMPARE(object->property("success").toBool(), true);
+ }
+}
+
+void tst_qqmlinstantiator::activeProperty()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, testFileUrl("inactive.qml"));
+ QQmlInstantiator *instantiator = qobject_cast<QQmlInstantiator*>(component.create());
+ QVERIFY(instantiator != 0);
+ QSignalSpy activeSpy(instantiator, SIGNAL(activeChanged()));
+ QSignalSpy countSpy(instantiator, SIGNAL(countChanged()));
+ QSignalSpy objectSpy(instantiator, SIGNAL(objectChanged()));
+ QSignalSpy modelSpy(instantiator, SIGNAL(modelChanged()));
+ QCOMPARE(instantiator->isActive(), false);
+ QCOMPARE(instantiator->count(), 0);
+ QVERIFY(instantiator->delegate()->isReady());
+
+ QCOMPARE(activeSpy.count(), 0);
+ QCOMPARE(countSpy.count(), 0);
+ QCOMPARE(objectSpy.count(), 0);
+ QCOMPARE(modelSpy.count(), 0);
+
+ instantiator->setActive(true);
+ QCOMPARE(instantiator->isActive(), true);
+ QCOMPARE(instantiator->count(), 1);
+
+ QCOMPARE(activeSpy.count(), 1);
+ QCOMPARE(countSpy.count(), 1);
+ QCOMPARE(objectSpy.count(), 1);
+ QCOMPARE(modelSpy.count(), 0);
+
+ QObject *object = instantiator->object();
+ QVERIFY(object);
+ QCOMPARE(object->parent(), instantiator);
+ QCOMPARE(object->property("success").toBool(), true);
+ QCOMPARE(object->property("idx").toInt(), 0);
+}
+
+void tst_qqmlinstantiator::intModelChange()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, testFileUrl("createMultiple.qml"));
+ QQmlInstantiator *instantiator = qobject_cast<QQmlInstantiator*>(component.create());
+ QVERIFY(instantiator != 0);
+ QSignalSpy activeSpy(instantiator, SIGNAL(activeChanged()));
+ QSignalSpy countSpy(instantiator, SIGNAL(countChanged()));
+ QSignalSpy objectSpy(instantiator, SIGNAL(objectChanged()));
+ QSignalSpy modelSpy(instantiator, SIGNAL(modelChanged()));
+ QCOMPARE(instantiator->count(), 10);
+
+ QCOMPARE(activeSpy.count(), 0);
+ QCOMPARE(countSpy.count(), 0);
+ QCOMPARE(objectSpy.count(), 0);
+ QCOMPARE(modelSpy.count(), 0);
+
+ instantiator->setModel(QVariant(2));
+ QCOMPARE(instantiator->count(), 2);
+
+ QCOMPARE(activeSpy.count(), 0);
+ QCOMPARE(countSpy.count(), 1);
+ QCOMPARE(objectSpy.count(), 2);
+ QCOMPARE(modelSpy.count(), 1);
+
+ for (int i=0; i<2; i++) {
+ QObject *object = instantiator->objectAt(i);
+ QVERIFY(object);
+ QCOMPARE(object->parent(), instantiator);
+ QCOMPARE(object->property("success").toBool(), true);
+ QCOMPARE(object->property("idx").toInt(), i);
+ }
+}
+
+QTEST_MAIN(tst_qqmlinstantiator)
+
+#include "tst_qqmlinstantiator.moc"