Skip to content

Commit

Permalink
Add support for more sequence types
Browse files Browse the repository at this point in the history
This commit adds support for more sequence types by adding a sequence
wrapper.  This class enables conversion between v8::Array and C++
sequences of various types (currently just QList<int>, QList<qreal>,
QList<bool>, QList<QString>, QList<QUrl> and QStringList), but more
types can be added later if required).

When a JavaScript object is created from such a sequence, its
prototype object is set to the v8::Array prototype object.  The
indexed setter, indexed getter, length and toString methods are
implemented directly or in terms of the underlying sequence resource.

Note that currently, sequences of ValueTypes are NOT supported, due to
the fact that operations like:
     someObj.someValueTypeSequence[i].x = 5;
would not behave as required.

Task-number: QTBUG-20826
Task-number: QTBUG-21770
Change-Id: I36deb448fb0e87a32084a900e70a2604ff369309
Reviewed-by: Chris Adams <christopher.adams@nokia.com>
  • Loading branch information
Chris Adams authored and Qt by Nokia committed Nov 2, 2011
1 parent 9dd6d4e commit c177691
Show file tree
Hide file tree
Showing 24 changed files with 1,915 additions and 21 deletions.
30 changes: 30 additions & 0 deletions doc/src/declarative/extending.qdoc
Expand Up @@ -236,6 +236,36 @@ The \c guest property declaration looks like this:
\l {Extending QML - Object and List Property Types Example} shows the complete
code used to create the \c BirthdayParty type.

\section1 Sequence Types

Certain C++ sequence types are supported transparently in QML as JavaScript Array types.
In particular, QML currently supports:
\list
\o \c {QList<int>}
\o \c {QList<qreal>}
\o \c {QList<bool>}
\o \c {QList<QString>}
\o \c {QList<QUrl>}
\endlist

These sequence types are implemented directly in terms of the underlying C++ sequence.
There are two ways in which such sequences can be exposed to QML: as a Q_PROPERTY of
the given sequence type; or as the return type of a Q_INVOKABLE method. There are some
differences in the way these are implemented, which are important to note.

If the sequence is exposed as a Q_PROPERTY, accessing any value in the sequence by index
will cause the sequence data to be read from the QObject's property, then a read to occur.
Similarly, modifying any value in the sequence will cause the sequence data to be read,
and then the modification will be performed and the modified sequence will be written back
to the QObject's property.

If the sequence is returned from a Q_INVOKABLE function, access and mutation is much cheaper,
as no QObject property read or write occurs; instead, the C++ sequence data is accessed and
modified directly.

Other sequence types are not supported transparently, and instead an instance of any other
sequence type will be passed between QML and C++ as an opaque QVariantList.

\section1 Inheritance and Coercion

\snippet examples/declarative/cppextensions/referenceexamples/coercion/example.qml 0
Expand Down
2 changes: 1 addition & 1 deletion src/declarative/qml/qdeclarativevaluetype.cpp
Expand Up @@ -92,7 +92,7 @@ QDeclarativeValueTypeFactory::~QDeclarativeValueTypeFactory()

bool QDeclarativeValueTypeFactory::isValueType(int idx)
{
if ((uint)idx < QVariant::UserType)
if ((uint)idx < QVariant::UserType && (uint)idx != QVariant::StringList)
return true;
return false;
}
Expand Down
55 changes: 42 additions & 13 deletions src/declarative/qml/v8/qv8engine.cpp
Expand Up @@ -45,6 +45,7 @@
#include "qv8contextwrapper_p.h"
#include "qv8valuetypewrapper_p.h"
#include "qv8gccallback_p.h"
#include "qv8sequencewrapper_p.h"
#include "qv8include_p.h"
#include "../../../3rdparty/javascriptcore/DateMath.h"

Expand Down Expand Up @@ -82,20 +83,28 @@ static bool ObjectComparisonCallback(v8::Local<v8::Object> lhs, v8::Local<v8::Ob

switch (lhst) {
case QV8ObjectResource::ValueTypeType:
// a value type might be equal to a variant or another value type
if (rhst == QV8ObjectResource::ValueTypeType) {
return lhsr->engine->valueTypeWrapper()->isEqual(lhsr, lhsr->engine->valueTypeWrapper()->toVariant(rhsr));
} else if (rhst == QV8ObjectResource::VariantType) {
return lhsr->engine->valueTypeWrapper()->isEqual(lhsr, lhsr->engine->variantWrapper()->toVariant(rhsr));
}
break;
case QV8ObjectResource::VariantType:
// a variant might be equal to a value type or other variant.
if (rhst == QV8ObjectResource::VariantType) {
return lhsr->engine->variantWrapper()->toVariant(lhsr) ==
lhsr->engine->variantWrapper()->toVariant(rhsr);
} else if (rhst == QV8ObjectResource::ValueTypeType) {
return rhsr->engine->valueTypeWrapper()->isEqual(rhsr, rhsr->engine->variantWrapper()->toVariant(lhsr));
}
break;
case QV8ObjectResource::SequenceType:
// a sequence might be equal to itself.
if (rhst == QV8ObjectResource::SequenceType) {
return lhsr->engine->sequenceWrapper()->isEqual(lhsr, rhsr);
}
break;
default:
break;
}
Expand Down Expand Up @@ -135,6 +144,7 @@ QV8Engine::QV8Engine(QJSEngine* qq, QJSEngine::ContextOwnership ownership)
m_listWrapper.init(this);
m_variantWrapper.init(this);
m_valueTypeWrapper.init(this);
m_sequenceWrapper.init(this);

QV8GCCallback::registerGcPrologueCallback();

Expand Down Expand Up @@ -164,6 +174,7 @@ QV8Engine::~QV8Engine()
invalidateAllValues();
clearExceptions();

m_sequenceWrapper.destroy();
m_valueTypeWrapper.destroy();
m_variantWrapper.destroy();
m_listWrapper.destroy();
Expand Down Expand Up @@ -226,25 +237,33 @@ QVariant QV8Engine::toVariant(v8::Handle<v8::Value> value, int typeHint)
return m_variantWrapper.toVariant(r);
case QV8ObjectResource::ValueTypeType:
return m_valueTypeWrapper.toVariant(r);
case QV8ObjectResource::SequenceType:
return m_sequenceWrapper.toVariant(r);
}
}
}

if (typeHint == qMetaTypeId<QList<QObject *> >() && value->IsArray()) {
if (value->IsArray()) {
v8::Handle<v8::Array> array = v8::Handle<v8::Array>::Cast(value);

QList<QObject *> list;
uint32_t length = array->Length();
for (uint32_t ii = 0; ii < length; ++ii) {
v8::Local<v8::Value> arrayItem = array->Get(ii);
if (arrayItem->IsObject()) {
list << toQObject(arrayItem->ToObject());
} else {
list << 0;
if (typeHint == qMetaTypeId<QList<QObject *> >()) {
QList<QObject *> list;
uint32_t length = array->Length();
for (uint32_t ii = 0; ii < length; ++ii) {
v8::Local<v8::Value> arrayItem = array->Get(ii);
if (arrayItem->IsObject()) {
list << toQObject(arrayItem->ToObject());
} else {
list << 0;
}
}

return qVariantFromValue<QList<QObject*> >(list);
}

return qVariantFromValue<QList<QObject*> >(list);
bool succeeded = false;
QVariant retn = m_sequenceWrapper.toVariant(array, typeHint, &succeeded);
if (succeeded)
return retn;
}

return toBasicVariant(value);
Expand Down Expand Up @@ -325,8 +344,14 @@ v8::Handle<v8::Value> QV8Engine::fromVariant(const QVariant &variant)
case QMetaType::QObjectStar:
case QMetaType::QWidgetStar:
return newQObject(*reinterpret_cast<QObject* const *>(ptr));
case QMetaType::QStringList:
case QMetaType::QStringList:
{
bool succeeded = false;
v8::Handle<v8::Value> retn = m_sequenceWrapper.fromVariant(variant, &succeeded);
if (succeeded)
return retn;
return arrayFromStringList(this, *reinterpret_cast<const QStringList *>(ptr));
}
case QMetaType::QVariantList:
return arrayFromVariantList(this, *reinterpret_cast<const QVariantList *>(ptr));
case QMetaType::QVariantMap:
Expand Down Expand Up @@ -369,6 +394,11 @@ v8::Handle<v8::Value> QV8Engine::fromVariant(const QVariant &variant)
QObject *obj = QDeclarativeMetaType::toQObject(variant, &objOk);
if (objOk)
return newQObject(obj);

bool succeeded = false;
v8::Handle<v8::Value> retn = m_sequenceWrapper.fromVariant(variant, &succeeded);
if (succeeded)
return retn;
}

// XXX TODO: To be compatible, we still need to handle:
Expand Down Expand Up @@ -459,7 +489,6 @@ QVariant QV8Engine::toBasicVariant(v8::Handle<v8::Value> value)
int length = array->Length();
for (int ii = 0; ii < length; ++ii)
rv << toVariant(array->Get(ii), -1);

return rv;
}
if (!value->IsFunction()) {
Expand Down
14 changes: 13 additions & 1 deletion src/declarative/qml/v8/qv8engine_p.h
Expand Up @@ -77,6 +77,7 @@
#include "qv8listwrapper_p.h"
#include "qv8variantwrapper_p.h"
#include "qv8valuetypewrapper_p.h"
#include "qv8sequencewrapper_p.h"

QT_BEGIN_NAMESPACE

Expand Down Expand Up @@ -136,7 +137,8 @@ class QV8ObjectResource : public v8::Object::ExternalResource
enum ResourceType { ContextType, QObjectType, TypeType, ListType, VariantType,
ValueTypeType, XMLHttpRequestType, DOMNodeType, SQLDatabaseType,
ListModelType, Context2DType, Context2DStyleType, Context2DPixelArrayType,
ParticleDataType, SignalHandlerType, IncubatorType, VisualDataItemType };
ParticleDataType, SignalHandlerType, IncubatorType, VisualDataItemType,
SequenceType };
virtual ResourceType resourceType() const = 0;

QV8Engine *engine;
Expand Down Expand Up @@ -279,6 +281,7 @@ class Q_DECLARATIVE_EXPORT QV8Engine
QV8ListWrapper *listWrapper() { return &m_listWrapper; }
QV8VariantWrapper *variantWrapper() { return &m_variantWrapper; }
QV8ValueTypeWrapper *valueTypeWrapper() { return &m_valueTypeWrapper; }
QV8SequenceWrapper *sequenceWrapper() { return &m_sequenceWrapper; }

void *xmlHttpRequestData() { return m_xmlHttpRequestData; }
void *sqlDatabaseData() { return m_sqlDatabaseData; }
Expand Down Expand Up @@ -326,6 +329,9 @@ class Q_DECLARATIVE_EXPORT QV8Engine
inline v8::Handle<v8::Value> newValueType(QObject *, int coreIndex, QDeclarativeValueType *);
inline v8::Handle<v8::Value> newValueType(const QVariant &, QDeclarativeValueType *);

// Create a new sequence type object
inline v8::Handle<v8::Value> newSequence(int sequenceType, QObject *, int coreIndex, bool *succeeded);

// Create a new QVariant object. This doesn't examine the type of the variant, but always returns
// a QVariant wrapper
inline v8::Handle<v8::Value> newQVariant(const QVariant &);
Expand Down Expand Up @@ -415,6 +421,7 @@ class Q_DECLARATIVE_EXPORT QV8Engine
QV8ListWrapper m_listWrapper;
QV8VariantWrapper m_variantWrapper;
QV8ValueTypeWrapper m_valueTypeWrapper;
QV8SequenceWrapper m_sequenceWrapper;

v8::Persistent<v8::Function> m_getOwnPropertyNames;
v8::Persistent<v8::Function> m_freezeObject;
Expand Down Expand Up @@ -545,6 +552,11 @@ v8::Handle<v8::Value> QV8Engine::newValueType(const QVariant &value, QDeclarativ
return m_valueTypeWrapper.newValueType(value, type);
}

v8::Handle<v8::Value> QV8Engine::newSequence(int sequenceType, QObject *object, int property, bool *succeeded)
{
return m_sequenceWrapper.newSequence(sequenceType, object, property, succeeded);
}

// XXX Can this be made more optimal? It is called prior to resolving each and every
// unqualified name in QV8ContextWrapper.
bool QV8Engine::startsWithUpper(v8::Handle<v8::String> string)
Expand Down
20 changes: 15 additions & 5 deletions src/declarative/qml/v8/qv8qobjectwrapper.cpp
Expand Up @@ -385,7 +385,13 @@ static v8::Handle<v8::Value> LoadProperty(QV8Engine *engine, QObject *object,
QDeclarativeValueType *valueType = ep->valueTypes[property.propType];
if (valueType)
return engine->newValueType(object, property.coreIndex, valueType);
}
} else {
// see if it's a sequence type
bool succeeded = false;
v8::Handle<v8::Value> retn = engine->newSequence(property.propType, object, property.coreIndex, &succeeded);
if (succeeded)
return retn;
}

QVariant var = object->metaObject()->property(property.coreIndex).read(object);
return engine->fromVariant(var);
Expand Down Expand Up @@ -425,13 +431,18 @@ static v8::Handle<v8::Value> LoadPropertyDirect(QV8Engine *engine, QObject *obje
void *args[] = { &handle, 0 };
object->qt_metacall(QMetaObject::ReadProperty, property.coreIndex, args);
return handle.toHandle();
} else if (QDeclarativeValueTypeFactory::isValueType((uint)property.propType)
&& engine->engine()) {
} else if (engine->engine() && QDeclarativeValueTypeFactory::isValueType((uint)property.propType)) {
QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(engine->engine());
QDeclarativeValueType *valueType = ep->valueTypes[property.propType];
if (valueType)
return engine->newValueType(object, property.coreIndex, valueType);
}
} else {
// see if it's a sequence type
bool success = false;
v8::Handle<v8::Value> retn = engine->newSequence(property.propType, object, property.coreIndex, &success);
if (success)
return retn;
}

QVariant var = object->metaObject()->property(property.coreIndex).read(object);
return engine->fromVariant(var);
Expand Down Expand Up @@ -601,7 +612,6 @@ static inline void StoreProperty(QV8Engine *engine, QObject *object, QDeclarativ
v = engine->toVariant(value, property->propType);

QDeclarativeContextData *context = engine->callingContext();

if (!QDeclarativePropertyPrivate::write(object, *property, v, context)) {
const char *valueType = 0;
if (v.userType() == QVariant::Invalid) valueType = "null";
Expand Down

0 comments on commit c177691

Please sign in to comment.