Improve documentation.
[profile/ivi/qtdeclarative.git] / src / qml / qml / qqmlboundsignal.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and Digia.  For licensing terms and
14 ** conditions see http://qt.digia.com/licensing.  For further information
15 ** use the contact form at http://qt.digia.com/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights.  These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file.  Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qqmlboundsignal_p.h"
43
44 #include <private/qmetaobject_p.h>
45 #include <private/qmetaobjectbuilder_p.h>
46 #include "qqmlengine_p.h"
47 #include "qqmlexpression_p.h"
48 #include "qqmlcontext_p.h"
49 #include "qqmlmetatype_p.h"
50 #include "qqml.h"
51 #include "qqmlcontext.h"
52 #include "qqmlglobal_p.h"
53 #include "qqmlrewrite_p.h"
54 #include <private/qqmlprofilerservice_p.h>
55 #include <private/qv8debugservice_p.h>
56 #include "qqmlinfo.h"
57
58 #include <QtCore/qstringbuilder.h>
59 #include <QtCore/qdebug.h>
60
61 Q_DECLARE_METATYPE(QQmlV8Handle)
62
63 QT_BEGIN_NAMESPACE
64
65 static QQmlJavaScriptExpression::VTable QQmlBoundSignalExpression_jsvtable = {
66     QQmlBoundSignalExpression::expressionIdentifier,
67     QQmlBoundSignalExpression::expressionChanged
68 };
69
70 QQmlBoundSignalExpression::QQmlBoundSignalExpression(QObject *target, int index,
71                                                      QQmlContextData *ctxt, QObject *scope, const QByteArray &expression,
72                                                      bool isRewritten, const QString &fileName, quint16 line, quint16 column)
73     : QQmlJavaScriptExpression(&QQmlBoundSignalExpression_jsvtable),
74       m_fileName(fileName),
75       m_line(line),
76       m_column(column),
77       m_parameterCountForJS(-1),
78       m_target(target),
79       m_index(index),
80       m_expressionFunctionValid(false),
81       m_expressionFunctionRewritten(isRewritten),
82       m_invalidParameterName(false)
83 {
84     init(ctxt, scope);
85     if (isRewritten)
86         m_expressionUtf8 = expression;
87     else
88         m_expression = QString::fromUtf8(expression);
89 }
90
91 QQmlBoundSignalExpression::QQmlBoundSignalExpression(QObject *target, int index,
92                                                      QQmlContextData *ctxt, QObject *scope, const QString &expression,
93                                                      bool isRewritten, const QString &fileName, quint16 line, quint16 column)
94     : QQmlJavaScriptExpression(&QQmlBoundSignalExpression_jsvtable),
95       m_fileName(fileName),
96       m_line(line),
97       m_column(column),
98       m_parameterCountForJS(-1),
99       m_target(target),
100       m_index(index),
101       m_expressionFunctionValid(false),
102       m_expressionFunctionRewritten(isRewritten),
103       m_invalidParameterName(false)
104 {
105     init(ctxt, scope);
106     if (isRewritten)
107         m_expressionUtf8 = expression.toUtf8();
108     else
109         m_expression = expression;
110 }
111
112 void QQmlBoundSignalExpression::init(QQmlContextData *ctxt, QObject *scope)
113 {
114     setNotifyOnValueChanged(false);
115     setContext(ctxt);
116     setScopeObject(scope);
117
118     Q_ASSERT(m_target && m_index > -1);
119     m_index = QQmlPropertyCache::originalClone(m_target, m_index);
120 }
121
122 QQmlBoundSignalExpression::~QQmlBoundSignalExpression()
123 {
124     qPersistentDispose(m_v8function);
125     qPersistentDispose(m_v8qmlscope);
126 }
127
128 QString QQmlBoundSignalExpression::expressionIdentifier(QQmlJavaScriptExpression *e)
129 {
130     QQmlBoundSignalExpression *This = static_cast<QQmlBoundSignalExpression *>(e);
131     return This->sourceFile() + QLatin1Char(':') + QString::number(This->lineNumber());
132 }
133
134 void QQmlBoundSignalExpression::expressionChanged(QQmlJavaScriptExpression *)
135 {
136     // bound signals do not notify on change.
137 }
138
139 QString QQmlBoundSignalExpression::expression() const
140 {
141     if (m_expressionFunctionValid) {
142         Q_ASSERT (context() && engine());
143         v8::HandleScope handle_scope;
144         v8::Context::Scope context_scope(QQmlEnginePrivate::get(engine())->v8engine()->context());
145         return QV8Engine::toStringStatic(m_v8function->ToString());
146     } else if (!m_expressionUtf8.isEmpty()) {
147         return QString::fromUtf8(m_expressionUtf8);
148     } else {
149         return m_expression;
150     }
151 }
152
153 // Parts of this function mirror code in QQmlExpressionPrivate::value() and v8value().
154 // Changes made here may need to be made there and vice versa.
155 void QQmlBoundSignalExpression::evaluate(void **a)
156 {
157     Q_ASSERT (context() && engine());
158
159     if (m_invalidParameterName)
160         return;
161
162     QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine());
163
164     ep->referenceScarceResources(); // "hold" scarce resources in memory during evaluation.
165     {
166         v8::HandleScope handle_scope;
167         v8::Context::Scope context_scope(ep->v8engine()->context());
168         if (!m_expressionFunctionValid) {
169
170             //TODO: look at using the property cache here (as in the compiler)
171             //      for further optimization
172             QMetaMethod signal = QMetaObjectPrivate::signal(m_target->metaObject(), m_index);
173             QQmlRewrite::RewriteSignalHandler rewriter;
174
175             QString expression;
176             bool ok = true;
177
178             if (m_expressionFunctionRewritten) {
179                 expression = QString::fromUtf8(m_expressionUtf8);
180
181                 //if we need parameters, and the rewrite doesn't include them,
182                 //create and insert the parameter string now
183                 if (m_parameterCountForJS == -1 && signal.parameterCount()) {
184                     const QString &parameters = rewriter.createParameterString(signal.parameterNames(),
185                                                                                ep->v8engine()->illegalNames());
186                     int index = expression.indexOf(QLatin1Char('('), 1);
187                     Q_ASSERT(index > -1);
188                     expression.insert(index + 1, parameters);
189
190                     setParameterCountForJS(rewriter.parameterCountForJS());
191                 }
192
193                 m_expressionUtf8.clear();
194             } else {
195                 //expression is still in its original form, so perform a full rewrite
196                 expression = rewriter(m_expression, QString()/*no name hint available*/, &ok,
197                                       signal.parameterNames(),
198                                       ep->v8engine()->illegalNames());
199                 m_expression.clear();
200             }
201
202             if (rewriter.hasParameterError()) {
203                 qmlInfo(scopeObject()) << rewriter.parameterError();
204                 m_invalidParameterName = true;
205                 ep->dereferenceScarceResources();
206                 return;
207             }
208
209             if (ok) {
210                 m_v8function = evalFunction(context(), scopeObject(), expression,
211                                             m_fileName, m_line, &m_v8qmlscope);
212             }
213
214             if (m_v8function.IsEmpty() || m_v8function->IsNull()) {
215                 ep->dereferenceScarceResources();
216                 return; // could not evaluate function.  Not valid.
217             }
218
219             setUseSharedContext(false);
220             m_expressionFunctionValid = true;
221         }
222
223         if (!hasParameterInfo()) {
224             QQmlJavaScriptExpression::evaluate(context(), m_v8function, 0);
225         } else {
226             QV8Engine *engine = ep->v8engine();
227             QVarLengthArray<int, 9> dummy;
228             //TODO: lookup via signal index rather than method index as an optimization
229             int methodIndex = QMetaObjectPrivate::signal(m_target->metaObject(), m_index).methodIndex();
230             int *argsTypes = QQmlPropertyCache::methodParameterTypes(m_target, methodIndex, dummy, 0);
231             int argCount = argsTypes ? m_parameterCountForJS : 0;
232
233             QVarLengthArray<v8::Handle<v8::Value>, 9> args(argCount);
234
235             for (int ii = 0; ii < argCount; ++ii) {
236                 int type = argsTypes[ii + 1];
237                 //### ideally we would use metaTypeToJS, however it currently gives different results
238                 //    for several cases (such as QVariant type and QObject-derived types)
239                 //args[ii] = engine->metaTypeToJS(type, a[ii + 1]);
240                 if (type == QMetaType::QVariant) {
241                     args[ii] = engine->fromVariant(*((QVariant *)a[ii + 1]));
242                 } else if (type == QMetaType::Int) {
243                     //### optimization. Can go away if we switch to metaTypeToJS, or be expanded otherwise
244                     args[ii] = v8::Integer::New(*reinterpret_cast<const int*>(a[ii + 1]));
245                 } else if (type == qMetaTypeId<QQmlV8Handle>()) {
246                     args[ii] = reinterpret_cast<QQmlV8Handle *>(a[ii + 1])->toHandle();
247                 } else if (ep->isQObject(type)) {
248                     if (!*reinterpret_cast<void* const *>(a[ii + 1]))
249                         args[ii] = v8::Null();
250                     else
251                         args[ii] = engine->newQObject(*reinterpret_cast<QObject* const *>(a[ii + 1]));
252                 } else {
253                     args[ii] = engine->fromVariant(QVariant(type, a[ii + 1]));
254                 }
255             }
256
257             QQmlJavaScriptExpression::evaluate(context(), m_v8function, argCount, args.data(), 0);
258         }
259     }
260     ep->dereferenceScarceResources(); // "release" scarce resources if top-level expression evaluation is complete.
261 }
262
263 ////////////////////////////////////////////////////////////////////////
264
265 QQmlAbstractBoundSignal::QQmlAbstractBoundSignal()
266 : m_prevSignal(0), m_nextSignal(0)
267 {
268 }
269
270 QQmlAbstractBoundSignal::~QQmlAbstractBoundSignal()
271 {
272     removeFromObject();
273 }
274
275 void QQmlAbstractBoundSignal::addToObject(QObject *obj)
276 {
277     Q_ASSERT(!m_prevSignal);
278     Q_ASSERT(obj);
279
280     QQmlData *data = QQmlData::get(obj, true);
281
282     m_nextSignal = data->signalHandlers;
283     if (m_nextSignal) m_nextSignal->m_prevSignal = &m_nextSignal;
284     m_prevSignal = &data->signalHandlers;
285     data->signalHandlers = this;
286 }
287
288 void QQmlAbstractBoundSignal::removeFromObject()
289 {
290     if (m_prevSignal) {
291         *m_prevSignal = m_nextSignal;
292         if (m_nextSignal) m_nextSignal->m_prevSignal = m_prevSignal;
293         m_prevSignal = 0;
294         m_nextSignal = 0;
295     }
296 }
297
298 /*! \internal
299     \a signal MUST be in the signal index range (see QObjectPrivate::signalIndex()).
300     This is different from QMetaMethod::methodIndex().
301 */
302 QQmlBoundSignal::QQmlBoundSignal(QObject *target, int signal, QObject *owner,
303                                  QQmlEngine *engine)
304 : m_expression(0), m_index(signal), m_isEvaluating(false)
305 {
306     addToObject(owner);
307     setCallback(QQmlNotifierEndpoint::QQmlBoundSignal);
308
309     /*
310         If this is a cloned method, connect to the 'original'. For example,
311         for the signal 'void aSignal(int parameter = 0)', if the method
312         index refers to 'aSignal()', get the index of 'aSignal(int)'.
313         This ensures that 'parameter' will be available from QML.
314     */
315     m_index = QQmlPropertyCache::originalClone(target, m_index);
316     QQmlNotifierEndpoint::connect(target, m_index, engine);
317 }
318
319 QQmlBoundSignal::~QQmlBoundSignal()
320 {
321     m_expression = 0;
322 }
323
324 /*!
325     Returns the signal index in the range returned by QObjectPrivate::signalIndex().
326     This is different from QMetaMethod::methodIndex().
327 */
328 int QQmlBoundSignal::index() const
329 {
330     return m_index;
331 }
332
333 /*!
334     Returns the signal expression.
335 */
336 QQmlBoundSignalExpression *QQmlBoundSignal::expression() const
337 {
338     return m_expression;
339 }
340
341 /*!
342     Sets the signal expression to \a e.  Returns the current signal expression,
343     or null if there is no signal expression.
344
345     The QQmlBoundSignal instance adds a reference to \a e.  The caller
346     assumes ownership of the returned QQmlBoundSignalExpression reference.
347 */
348 QQmlBoundSignalExpressionPointer QQmlBoundSignal::setExpression(QQmlBoundSignalExpression *e)
349 {
350     QQmlBoundSignalExpressionPointer rv = m_expression;
351     m_expression = e;
352     if (m_expression) m_expression->setNotifyOnValueChanged(false);
353     return rv;
354 }
355
356 /*!
357     Sets the signal expression to \a e.  Returns the current signal expression,
358     or null if there is no signal expression.
359
360     The QQmlBoundSignal instance takes ownership of \a e (and does not add a reference).  The caller
361     assumes ownership of the returned QQmlBoundSignalExpression reference.
362 */
363 QQmlBoundSignalExpressionPointer QQmlBoundSignal::takeExpression(QQmlBoundSignalExpression *e)
364 {
365     QQmlBoundSignalExpressionPointer rv = m_expression;
366     m_expression.take(e);
367     if (m_expression) m_expression->setNotifyOnValueChanged(false);
368     return rv;
369 }
370
371 void QQmlBoundSignal_callback(QQmlNotifierEndpoint *e, void **a)
372 {
373     QQmlBoundSignal *s = static_cast<QQmlBoundSignal*>(e);
374     if (!s->m_expression)
375         return;
376
377     if (QQmlDebugService::isDebuggingEnabled())
378         QV8DebugService::instance()->signalEmitted(QString::fromLatin1(QMetaObjectPrivate::signal(s->m_expression->target()->metaObject(), s->m_index).methodSignature()));
379
380     QQmlHandlingSignalProfiler prof(s->m_expression);
381
382     s->m_isEvaluating = true;
383
384     if (s->m_expression && s->m_expression->engine()) {
385         s->m_expression->evaluate(a);
386         if (s->m_expression && s->m_expression->hasError()) {
387             QQmlEngine *engine = s->m_expression->engine();
388             QQmlEnginePrivate::warning(engine, s->m_expression->error(engine));
389         }
390     }
391
392     s->m_isEvaluating = false;
393 }
394
395 ////////////////////////////////////////////////////////////////////////
396
397 QQmlBoundSignalExpressionPointer::QQmlBoundSignalExpressionPointer(QQmlBoundSignalExpression *o)
398 : o(o)
399 {
400     if (o) o->addref();
401 }
402
403 QQmlBoundSignalExpressionPointer::QQmlBoundSignalExpressionPointer(const QQmlBoundSignalExpressionPointer &other)
404 : o(other.o)
405 {
406     if (o) o->addref();
407 }
408
409 QQmlBoundSignalExpressionPointer::~QQmlBoundSignalExpressionPointer()
410 {
411     if (o) o->release();
412 }
413
414 QQmlBoundSignalExpressionPointer &QQmlBoundSignalExpressionPointer::operator=(const QQmlBoundSignalExpressionPointer &other)
415 {
416     if (other.o) other.o->addref();
417     if (o) o->release();
418     o = other.o;
419     return *this;
420 }
421
422 QQmlBoundSignalExpressionPointer &QQmlBoundSignalExpressionPointer::operator=(QQmlBoundSignalExpression *other)
423 {
424     if (other) other->addref();
425     if (o) o->release();
426     o = other;
427     return *this;
428 }
429
430 /*!
431 Takes ownership of \a other.  take() does *not* add a reference, as it assumes ownership
432 of the callers reference of other.
433 */
434 QQmlBoundSignalExpressionPointer &QQmlBoundSignalExpressionPointer::take(QQmlBoundSignalExpression *other)
435 {
436     if (o) o->release();
437     o = other;
438     return *this;
439 }
440
441 QT_END_NAMESPACE