Merge lp:~mixxxdevelopers/mixxx/features_direct_bind into lp:~mixxxdevelopers/mixxx/trunk

Proposed by Phillip Whelan
Status: Merged
Merged at revision: 3214
Proposed branch: lp:~mixxxdevelopers/mixxx/features_direct_bind
Merge into: lp:~mixxxdevelopers/mixxx/trunk
Diff against target: 1162 lines (+598/-199)
8 files modified
mixxx/src/controllers/controller.cpp (+15/-14)
mixxx/src/controllers/controller.h (+2/-1)
mixxx/src/controllers/controllerengine.cpp (+321/-170)
mixxx/src/controllers/controllerengine.h (+61/-11)
mixxx/src/controllers/midi/midicontroller.cpp (+5/-2)
mixxx/src/controllers/mixxxcontrol.cpp (+7/-0)
mixxx/src/controllers/mixxxcontrol.h (+9/-0)
mixxx/src/test/controllerengine_test.cpp (+178/-1)
To merge this branch: bzr merge lp:~mixxxdevelopers/mixxx/features_direct_bind
Reviewer Review Type Date Requested Status
RJ Skerry-Ryan Needs Fixing
Review via email: mp+104529@code.launchpad.net

Description of the change

Call script functions directly as QScriptValues instead of parsing small javascript snippets for invoking MIDI Scripts.

To post a comment you must log in.
2875. By madjester <madjester@voidwalker>

Change declaration of ControllerEngine::timerEvent to match trunk.

Revision history for this message
Phillip Whelan (pwhelan) wrote :

The runtime evaluation option and file reloading are in the works for after this is merged.

Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

Thanks! -- here are my comments sofar:

* ControllerEngine::callFunctionOnObjects should check isFunction() before call()'ing the QScriptValue

* ControllerEngine::resolveFunction should check isFunction() in addition to isValid()

* In ControllerEngine::internalExecute(QScriptValue, QScriptValue) return false if !isFunction() to match execute(QScriptValue, QScriptValueList)

* In ControllerEngine you changed a bunch of debug outputs to say MidiScriptEngine instead of ControllerEngine -- please change those back :)

* In connectControl() in the first if(disconnect) path you set cb.key = key which seems redundant with a few lines above.

* ControllerEngineConnectionScriptValue::disconnect() shadows QObject::disconnect(). We probably shouldn't do that.

* connectControl() connects the valueChanged() signal from the ControlObject, not the ControlObjectThread which causes priority-inversion between the Engine and the Qt main-thread. This was a bug that was fixed in the control-system rewrite so please re-add that.

* MidiController::bindScriptFunction() calling preset() and casting the result -- that's for use by the superclass which doesn't know about MidiControllerPresets. You can just use m_preset. (though see my comment below)

* I think the m_scriptBindings cache in Controller/MidiController isn't really necessary. Instead of the cache living in MixxxControl/Controller and having to pre-cache every function we think we might want to call, why not put the cache in ControllerEngine::resolveFunction. Change ControllerEngine::resolveFunction(QString) to take a boolean shouldCache flag that tells it to first look in the QScriptValue cache and then just call resolveFunction() every time we want to call a method. I think this would be a lot cleaner and this branch would become almost totally contained to ControllerEngine and it would be just as fast as what is written now (and a little more robust to cache misses since it will cache on first use).

* thanks for the tests :)

review: Needs Fixing
Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

Any update Phil? If you're too hosed I can make these changes myself --
it'd be great to get these in 1.11.

On Fri, May 11, 2012 at 2:14 PM, RJ Ryan <email address hidden> wrote:

> Review: Needs Fixing
>
> Thanks! -- here are my comments sofar:
>
> * ControllerEngine::callFunctionOnObjects should check isFunction() before
> call()'ing the QScriptValue
>
> * ControllerEngine::resolveFunction should check isFunction() in addition
> to isValid()
>
> * In ControllerEngine::internalExecute(QScriptValue, QScriptValue) return
> false if !isFunction() to match execute(QScriptValue, QScriptValueList)
>
> * In ControllerEngine you changed a bunch of debug outputs to say
> MidiScriptEngine instead of ControllerEngine -- please change those back :)
>
> * In connectControl() in the first if(disconnect) path you set cb.key =
> key which seems redundant with a few lines above.
>
> * ControllerEngineConnectionScriptValue::disconnect() shadows
> QObject::disconnect(). We probably shouldn't do that.
>
> * connectControl() connects the valueChanged() signal from the
> ControlObject, not the ControlObjectThread which causes priority-inversion
> between the Engine and the Qt main-thread. This was a bug that was fixed in
> the control-system rewrite so please re-add that.
>
> * MidiController::bindScriptFunction() calling preset() and casting the
> result -- that's for use by the superclass which doesn't know about
> MidiControllerPresets. You can just use m_preset. (though see my comment
> below)
>
> * I think the m_scriptBindings cache in Controller/MidiController isn't
> really necessary. Instead of the cache living in MixxxControl/Controller
> and having to pre-cache every function we think we might want to call, why
> not put the cache in ControllerEngine::resolveFunction. Change
> ControllerEngine::resolveFunction(QString) to take a boolean shouldCache
> flag that tells it to first look in the QScriptValue cache and then just
> call resolveFunction() every time we want to call a method. I think this
> would be a lot cleaner and this branch would become almost totally
> contained to ControllerEngine and it would be just as fast as what is
> written now (and a little more robust to cache misses since it will cache
> on first use).
>
> * thanks for the tests :)
> --
>
> https://code.launchpad.net/~mixxxdevelopers/mixxx/features_direct_bind/+merge/104529
> You are reviewing the proposed merge of
> lp:~mixxxdevelopers/mixxx/features_direct_bind into lp:mixxx.
>

Revision history for this message
Phillip Whelan (pwhelan) wrote :

I'll see if I can get to them tomorrow. Been a bit busy lately with RL.

2876. By madjester <madjester@voidwalker>

merge from trunk.

2877. By madjester <madjester@voidwalker>

merge from trunk.

2878. By madjester <madjester@voidwalker>

merge from trunk.

2879. By madjester <madjester@voidwalker>

merge from trunk.

2880. By madjester <madjester@voidwalker>

merge from trunk.

2881. By madjester <madjester@voidwalker>

FIX: check isFunction in ControllerEngine::callFunctionOnObjects before calling a QScriptValue.

2882. By madjester <madjester@voidwalker>

FIX: check if object.isFunction in ControllerEngine::resolveFunction.

2883. By madjester <madjester@voidwalker>

FIX: return false in ControllerEngine::internalExecute(QScriptValue thisObject, QScriptValue functionObject) if functionObject is not an object to match the calling convention of the other overloads.

2884. By madjester <madjester@voidwalker>

Change occurances of MidiScriptEngine to ControllerEngine.

2885. By madjester <madjester@voidwalker>

Remove redundant assignment of cb.key in ControllerEngine::connectControl.

2886. By madjester <madjester@voidwalker>

Remove m_scriptBindings and bindScriptFunctions() from controllers and use ControllerEngine::resolveFunction to resolve functions (no more caching in the controllers themselves).

2887. By madjester <madjester@voidwalker>

FIX: only check if the object is a function right before returning it in ControllerEngine::resolveFunction.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'mixxx/src/controllers/controller.cpp'
2--- mixxx/src/controllers/controller.cpp 2012-04-29 02:08:20 +0000
3+++ mixxx/src/controllers/controller.cpp 2012-05-25 04:46:23 +0000
4@@ -70,14 +70,11 @@
5
6 // Load the script code into the engine
7 if (m_pEngine != NULL) {
8- const QStringList scriptFunctions = m_pEngine->getScriptFunctions();
9- if (scriptFunctions.isEmpty() && pPreset->scriptFileNames.isEmpty()) {
10+ if (pPreset->scriptFileNames.isEmpty()) {
11 qWarning() << "No script functions available! Did the XML file(s) load successfully? See above for any errors.";
12- } else {
13- if (scriptFunctions.isEmpty()) {
14- m_pEngine->loadScriptFiles(configPath,
15- pPreset->scriptFileNames);
16- }
17+ }
18+ else {
19+ m_pEngine->loadScriptFiles(configPath, pPreset->scriptFileNames);
20 m_pEngine->initializeScripts(pPreset->scriptFunctionPrefixes);
21 }
22 } else {
23@@ -131,13 +128,17 @@
24 qDebug() << message;
25 }
26
27- foreach (QString function, m_pEngine->getScriptFunctionPrefixes()) {
28- if (function == "") {
29- continue;
30- }
31- function.append(".incomingData");
32- if (!m_pEngine->execute(function, data)) {
33- qWarning() << "Controller: Invalid script function" << function;
34+ QListIterator<QString> prefixIt(m_pEngine->getScriptFunctionPrefixes());
35+ while (prefixIt.hasNext()) {
36+ QString function = prefixIt.next();
37+ if (function!="") {
38+ function.append(".incomingData");
39+
40+ QScriptValue incomingData = m_pEngine->resolveFunction(function);
41+ if (!m_pEngine->execute(incomingData, data)) {
42+ qWarning() << "Controller: Invalid script function" << function;
43+ }
44 }
45 }
46 }
47+
48
49=== modified file 'mixxx/src/controllers/controller.h'
50--- mixxx/src/controllers/controller.h 2012-04-29 01:40:37 +0000
51+++ mixxx/src/controllers/controller.h 2012-05-25 04:46:23 +0000
52@@ -18,6 +18,8 @@
53 #include "controllers/controllerpresetfilehandler.h"
54 #include "controllers/mixxxcontrol.h"
55
56+#include <QScriptValue>
57+
58 class Controller : public QObject, ControllerPresetVisitor {
59 Q_OBJECT
60 public:
61@@ -118,7 +120,6 @@
62 m_controlToLearn = control;
63 }
64
65-
66 private slots:
67 virtual int open() = 0;
68 virtual int close() = 0;
69
70=== modified file 'mixxx/src/controllers/controllerengine.cpp'
71--- mixxx/src/controllers/controllerengine.cpp 2012-05-01 21:42:24 +0000
72+++ mixxx/src/controllers/controllerengine.cpp 2012-05-25 04:46:23 +0000
73@@ -16,6 +16,10 @@
74
75 // #include <QScriptSyntaxCheckResult>
76
77+// Used for id's inside controlConnection objects
78+// (closure compatible version of connectControl)
79+#include <QUuid>
80+
81 #ifdef _MSC_VER
82 #include <float.h> // for _isnan() on VC++
83 #define isnan(x) _isnan(x) // VC++ uses _isnan() instead of isnan()
84@@ -66,6 +70,62 @@
85 m_pEngine = NULL;
86 engine->deleteLater();
87 }
88+
89+}
90+
91+/* -------- ------------------------------------------------------
92+Purpose: Calls the same method on a list of JS Objects
93+Input: -
94+Output: -
95+-------- ------------------------------------------------------ */
96+void ControllerEngine::callFunctionOnObjects(QList<QString> scriptFunctionPrefixes, QString function, QScriptValueList args)
97+{
98+ QListIterator<QString> prefixIt(scriptFunctionPrefixes);
99+ const QScriptValue global = m_pEngine->globalObject();
100+
101+ while (prefixIt.hasNext()) {
102+ QString prefixName = prefixIt.next();
103+ QScriptValue prefix = global.property(prefixName);
104+
105+ if ( prefix.isValid() && prefix.isObject()) {
106+
107+ QScriptValue init = prefix.property(function);
108+ if (init.isValid() && init.isFunction()) {
109+ if (m_bDebug) {
110+ qDebug() << "ControllerEngine: Executing" << prefixName << "." << function;
111+ }
112+ init.call(QScriptValue(), args);
113+ }
114+ else {
115+ qWarning() << "ControllerEngine:" << prefixName << "has no" << function << " method";
116+ }
117+ }
118+ else {
119+ qWarning() << "ControllerEngine: No" << prefixName << "object in script";
120+ }
121+ }
122+}
123+
124+/* -------- ------------------------------------------------------
125+Purpose: Resolves a function name to a QScriptValue including
126+ OBJECT.Function calls
127+Input: -
128+Output: -
129+-------- ------------------------------------------------------ */
130+QScriptValue ControllerEngine::resolveFunction(QString function) const {
131+ QScriptValue object = m_pEngine->globalObject();
132+ QStringList parts = function.split(".");
133+
134+ for (int i = 0; i < parts.size(); i++) {
135+ object = object.property(parts.at(i));
136+ if (!object.isValid())
137+ return QScriptValue();
138+ }
139+
140+ if (!object.isFunction())
141+ return QScriptValue();
142+
143+ return object;
144 }
145
146 /* -------- ------------------------------------------------------
147@@ -85,25 +145,14 @@
148 stopAllTimers();
149
150 // Call each script's shutdown function if it exists
151- foreach (QString prefix, m_scriptFunctionPrefixes) {
152- if (prefix == "") {
153- continue;
154- }
155- QString shutdownName = QString("%1.shutdown").arg(prefix);
156- if (m_bDebug) {
157- qDebug() << " Executing" << shutdownName;;
158- }
159- if (!internalExecute(shutdownName)) {
160- qWarning() << "ControllerEngine: No" << shutdownName << "function in script";
161- }
162- }
163-
164- // Prevents leaving decks in an unstable state if the controller is shut
165- // down while scratching
166+ callFunctionOnObjects(m_scriptFunctionPrefixes, "shutdown");
167+
168+ // Prevents leaving decks in an unstable state
169+ // if the controller is shut down while scratching
170 QHashIterator<int, int> i(m_scratchTimers);
171 while (i.hasNext()) {
172 i.next();
173- qDebug() << " Aborting scratching on deck" << i.value();
174+ qDebug() << "Aborting scratching on deck" << i.value();
175 // Clear scratch2_enable
176 QString group = QString("[Channel%1]").arg(i.value());
177 ControlObjectThread *cot = getControlObjectThread(group, "scratch2_enable");
178@@ -133,7 +182,7 @@
179 }
180
181 void ControllerEngine::initializeScriptEngine() {
182- // Create the script engine
183+ // Create the Script Engine
184 m_pEngine = new QScriptEngine(this);
185
186 // Make this ControllerEngine instance available to scripts as 'engine'.
187@@ -145,6 +194,7 @@
188
189 // Make the Controller instance available to scripts
190 engineGlobalObject.setProperty("controller", m_pEngine->newQObject(m_pController));
191+
192 // ...under the legacy name as well
193 engineGlobalObject.setProperty("midi", m_pEngine->newQObject(m_pController));
194 }
195@@ -251,12 +301,13 @@
196
197
198 /* -------- ------------------------------------------------------
199- Purpose: Evaluate & run script code
200- Input: Code string
201- Output: false if an exception
202- -------- ------------------------------------------------------ */
203-bool ControllerEngine::internalExecute(QString scriptCode) {
204- // A special version of execute since we're evaluating strings, not actual functions
205+Purpose: Evaluate & run script code
206+Input: 'this' object if applicable, Code string
207+Output: false if an exception
208+-------- ------------------------------------------------------ */
209+bool ControllerEngine::internalExecute(QScriptValue thisObject,
210+ QString scriptCode) {
211+ // A special version of safeExecute since we're evaluating strings, not actual functions
212 // (execute() would print an error that it's not a function every time a timer fires.)
213 if (m_pEngine == NULL)
214 return false;
215@@ -296,13 +347,25 @@
216 return false;
217 }
218
219+ return internalExecute(thisObject, scriptFunction);
220+}
221+
222+/* -------- ------------------------------------------------------
223+Purpose: Evaluate & run script code
224+Input: 'this' object if applicable, Code string
225+Output: false if an exception
226+-------- ------------------------------------------------------ */
227+bool ControllerEngine::internalExecute(QScriptValue thisObject, QScriptValue functionObject) {
228+ if(m_pEngine == NULL)
229+ return false;
230+
231 // If it's not a function, we're done.
232- if (!scriptFunction.isFunction()) {
233- return true;
234+ if (!functionObject.isFunction()) {
235+ return false;
236 }
237
238 // If it does happen to be a function, call it.
239- scriptFunction.call(QScriptValue());
240+ functionObject.call(thisObject);
241 if (checkException()) {
242 qDebug() << "Exception";
243 return false;
244@@ -326,16 +389,37 @@
245
246 if (checkException())
247 return false;
248- if (!scriptFunction.isFunction())
249- return false;
250-
251- scriptFunction.call(QScriptValue(), args);
252+
253+ return execute(scriptFunction, args);
254+}
255+
256+/**-------- ------------------------------------------------------
257+ Purpose: Evaluate & call a script function with argument list
258+ Input: Function name, argument list
259+ Output: false if an invalid function or an exception
260+ -------- ------------------------------------------------------ */
261+bool ControllerEngine::execute(QScriptValue functionObject, QScriptValueList args) {
262+
263+ if(m_pEngine == NULL) {
264+ qDebug() << "ControllerEngine::execute: No script engine exists!";
265+ return false;
266+ }
267+
268+ //qDebug() << "Calling MIDI Script Function";
269+ if (!functionObject.isFunction()) {
270+ qDebug() << "Not a function";
271+ return false;
272+ }
273+ QScriptValue rc = functionObject.call(m_pEngine->globalObject(), args);
274+ if (!rc.isValid()) {
275+ qDebug() << "QScriptValue is not a function or ...";
276+ return false;
277+ }
278
279 if (checkException())
280 return false;
281 return true;
282 }
283-
284 /**-------- ------------------------------------------------------
285 Purpose: Evaluate & call a script function
286 Input: Function name, data string (e.g. device ID)
287@@ -362,12 +446,7 @@
288 QScriptValueList args;
289 args << QScriptValue(data);
290
291- scriptFunction.call(QScriptValue(), args);
292- if (checkException()) {
293- qDebug() << "ControllerEngine::execute: Exception";
294- return false;
295- }
296- return true;
297+ return execute(scriptFunction, args);
298 }
299
300 /**-------- ------------------------------------------------------
301@@ -389,22 +468,30 @@
302
303 if (checkException())
304 return false;
305- if (!scriptFunction.isFunction())
306- return false;
307-
308-// const char* buffer=reinterpret_cast<const char*>(data);
309+
310+ return execute(scriptFunction, data);
311+}
312+
313+/**-------- ------------------------------------------------------
314+ Purpose: Evaluate & call a script function
315+ Input: Function name, ponter to data buffer, length of buffer
316+ Output: false if an invalid function or an exception
317+ -------- ------------------------------------------------------ */
318+bool ControllerEngine::execute(QScriptValue function, const QByteArray data) {
319+ if (m_pEngine == NULL) {
320+ return false;
321+ }
322+
323+ if (checkException())
324+ return false;
325+ if (!function.isFunction())
326+ return false;
327
328 QScriptValueList args;
329-// args << QScriptValue(data);
330 args << QScriptValue(m_pBaClass->newInstance(data));
331 args << QScriptValue(data.size());
332-// args << QScriptValue(m_pBaClass->newInstance(QByteArray::fromRawData(buffer,length)));
333-// args << QScriptValue(length);
334
335- scriptFunction.call(QScriptValue(), args);
336- if (checkException())
337- return false;
338- return true;
339+ return execute(function, args);
340 }
341
342 /* -------- ------------------------------------------------------
343@@ -500,46 +587,6 @@
344 }
345 }
346
347-/* -------- ------------------------------------------------------
348- Purpose: Returns a list of functions available in the QtScript
349- code
350- Input: -
351- Output: functionList QStringList
352- -------- ------------------------------------------------------ */
353-QStringList ControllerEngine::getScriptFunctions() {
354- QStringList ret = m_scriptFunctions;
355- return ret;
356-}
357-
358-void ControllerEngine::generateScriptFunctions(QString scriptCode) {
359-// QStringList functionList;
360- QStringList codeLines = scriptCode.split("\n");
361-
362-// qDebug() << "ControllerEngine: m_scriptCode=" << m_scriptCode;
363-
364- if (m_bDebug)
365- qDebug() << "ControllerEngine:" << codeLines.count() << "lines of code being searched for functions";
366-
367- // grep 'function' midi/midi-mappings-scripts.js|grep -i '(msg)'|sed -e 's/function \(.*\)(msg).*/\1/i' -e 's/[= ]//g'
368- QRegExp rx("*.*function*(*)*"); // Find all lines with function names in them
369- rx.setPatternSyntax(QRegExp::Wildcard);
370-
371- int position = codeLines.indexOf(rx);
372-
373- while (position != -1) { // While there are more matches
374-
375- QString line = codeLines.takeAt(position); // Pull & remove the current match from the list.
376-
377- if (line.indexOf('#') != 0 && line.indexOf("//") != 0) { // ignore commented out lines
378- QStringList field = line.split(" ");
379- if (m_bDebug) qDebug() << "ControllerEngine: Found function:" << field[0]
380- << "at line" << position;
381- m_scriptFunctions.append(field[0]);
382- }
383- position = codeLines.indexOf(rx);
384- }
385-}
386-
387 ControlObjectThread* ControllerEngine::getControlObjectThread(QString group, QString name) {
388 ConfigKey key = ConfigKey(group, name);
389
390@@ -620,45 +667,128 @@
391 script function name, true if you want to disconnect
392 Output: true if successful
393 -------- ------------------------------------------------------ */
394-bool ControllerEngine::connectControl(QString group, QString name, QString function, bool disconnect) {
395- ControlObjectThread* cobj = getControlObjectThread(group, name);
396+QScriptValue ControllerEngine::connectControl(QString group, QString name, QScriptValue callback, bool disconnect) {
397 ConfigKey key(group, name);
398+ ControlObject* cobj = ControlObject::getControl(key);
399+ ControllerEngineConnection conn;
400+ QScriptValue function;
401
402 if (cobj == NULL) {
403 qWarning() << "ControllerEngine: script connecting [" << group << "," << name
404 << "], which is non-existent. ignoring.";
405- return false;
406- }
407-
408- // Don't add duplicates
409- if (!disconnect && m_connectedControls.contains(key, function)) {
410- return true;
411- }
412-
413- if (m_pEngine == NULL) {
414- return false;
415- }
416-
417- QScriptValue slot = m_pEngine->evaluate(function);
418-
419- if (!checkException() && slot.isFunction()) {
420- if (disconnect) {
421- //qDebug() << "ControllerEngine::connectControl disconnected " << group << name << " from " << function;
422- m_connectedControls.remove(key, function);
423- // Only disconnect the signal if there are no other instances of this control using it
424- if (!m_connectedControls.contains(key)) {
425- this->disconnect(cobj, SIGNAL(valueChanged(double)),
426- this, SLOT(slotValueChanged(double)));
427- }
428- } else {
429- //qDebug() << "ControllerEngine::connectControl connected " << group << name << " to " << function;
430- connect(cobj, SIGNAL(valueChanged(double)),
431- this, SLOT(slotValueChanged(double)));
432- m_connectedControls.insert(key, function);
433- }
434- return true;
435- }
436- return false;
437+ return QScriptValue();
438+ }
439+
440+ if(m_pEngine == NULL) {
441+ return QScriptValue(FALSE);
442+ }
443+
444+ if (callback.isString()) {
445+ ControllerEngineConnection cb;
446+ cb.key = key;
447+ cb.id = callback.toString();
448+
449+ if(disconnect) {
450+ disconnectControl(cb);
451+ return QScriptValue(TRUE);
452+ }
453+
454+ function = m_pEngine->evaluate(callback.toString());
455+ if (checkException() || !function.isFunction()) {
456+ return QScriptValue(FALSE);
457+ }
458+
459+ // Do not allow multiple connections to named functions
460+ else if (m_connectedControls.contains(key, cb)) {
461+ // Return a wrapper to the conn
462+ QHash<ConfigKey, ControllerEngineConnection>::iterator i =
463+ m_connectedControls.find(key);
464+
465+ ControllerEngineConnection conn = i.value();
466+ return m_pEngine->newQObject(
467+ new ControllerEngineConnectionScriptValue(conn),
468+ QScriptEngine::ScriptOwnership
469+ );
470+ }
471+ }
472+ else if (callback.isFunction()) {
473+ function = callback;
474+ }
475+ // Assume a ControllerEngineConnection
476+ else if (callback.isQObject()) {
477+ QObject *qobject = callback.toQObject();
478+ const QMetaObject *qmeta = qobject->metaObject();
479+
480+ if (!strcmp(qmeta->className(), "ControllerEngineConnectionScriptValue")) {
481+ ControllerEngineConnectionScriptValue *proxy =
482+ (ControllerEngineConnectionScriptValue *)qobject;
483+ proxy->disconnect();
484+ }
485+ }
486+ else {
487+ qWarning() << "Invalid callback";
488+ return QScriptValue(FALSE);
489+ }
490+
491+ if (function.isFunction()) {
492+ qDebug() << "Connection:" << group << name;
493+ connect(cobj, SIGNAL(valueChanged(double)),
494+ this, SLOT(slotValueChanged(double)),
495+ Qt::QueuedConnection);
496+
497+ ControllerEngineConnection conn;
498+ conn.key = key;
499+ conn.ce = this;
500+ conn.function = function;
501+ QScriptContext *ctxt = m_pEngine->currentContext();
502+ conn.context = ctxt ? ctxt->thisObject() : QScriptValue();
503+
504+ if (callback.isString()) {
505+ conn.id = callback.toString();
506+ }
507+ else {
508+ QUuid uuid = QUuid::createUuid();
509+ conn.id = uuid.toString();
510+ }
511+
512+ m_connectedControls.insert(key, conn);
513+ return m_pEngine->newQObject(
514+ new ControllerEngineConnectionScriptValue(conn),
515+ QScriptEngine::ScriptOwnership
516+ );
517+ }
518+
519+ return QScriptValue(FALSE);
520+}
521+
522+/* -------- ------------------------------------------------------
523+ Purpose: (Dis)connects a ControlObject valueChanged() signal to/from a script function
524+ Input: Control group (e.g. [Channel1]), Key name (e.g. [filterHigh]),
525+ script function name, true if you want to disconnect
526+ Output: true if successful
527+ -------- ------------------------------------------------------ */
528+void ControllerEngine::disconnectControl(const ControllerEngineConnection conn) {
529+ ControlObject* cobj = ControlObject::getControl(ConfigKey(conn.key.group, conn.key.item));
530+
531+ if(m_pEngine == NULL) {
532+ return;
533+ }
534+
535+ if (m_connectedControls.contains(conn.key, conn)) {
536+ m_connectedControls.remove(conn.key, conn);
537+ // Only disconnect the signal if there are no other instances of this control using it
538+ if (!m_connectedControls.contains(conn.key)) {
539+ this->disconnect(cobj, SIGNAL(valueChanged(double)),
540+ this, SLOT(slotValueChanged(double)));
541+ }
542+ }
543+ else {
544+ qWarning() << "Could not Disconnect connection" << conn.id;
545+ }
546+}
547+
548+void ControllerEngineConnectionScriptValue::disconnect() {
549+ this->conn.ce->disconnectControl(this->conn);
550 }
551
552 /**-------- ------------------------------------------------------
553@@ -666,39 +796,46 @@
554 fires off the appropriate script function.
555 -------- ------------------------------------------------------ */
556 void ControllerEngine::slotValueChanged(double value) {
557- ControlObjectThread* sender = dynamic_cast<ControlObjectThread*>(this->sender());
558- if (sender == NULL) {
559+
560+ ControlObject* sender = (ControlObject*)this->sender();
561+ if(sender == NULL) {
562 qWarning() << "ControllerEngine::slotValueChanged() Shouldn't happen -- sender == NULL";
563 return;
564 }
565-
566- ControlObject* pSenderCO = sender->getControlObject();
567- if (pSenderCO == NULL) {
568- qWarning() << "ControllerEngine::slotValueChanged() The sender's CO is NULL.";
569- return;
570- }
571- ConfigKey key = pSenderCO->getKey();
572-
573- if (m_connectedControls.contains(key)) {
574- QMultiHash<ConfigKey, QString>::iterator i = m_connectedControls.find(key);
575- while (i != m_connectedControls.end() && i.key() == key) {
576- QString function = i.value();
577-
578- //qDebug() << "ControllerEngine::slotValueChanged() received signal from " << key.group << key.item << " ... firing : " << function;
579-
580- // Could branch to execute from here, but for now do it this way.
581- QScriptValue function_value = m_pEngine->evaluate(function);
582+ ConfigKey key = sender->getKey();
583+
584+ qDebug() << "[Controller]: SlotValueChanged" << key.group << key.item;
585+
586+ if(m_connectedControls.contains(key)) {
587+ QHash<ConfigKey, ControllerEngineConnection>::iterator iter =
588+ m_connectedControls.find(key);
589+ QList<ControllerEngineConnection> conns;
590+
591+ // Create a temporary list to allow callbacks to disconnect
592+ // -Phillip Whelan
593+ while (iter != m_connectedControls.end() && iter.key() == key) {
594+ conns.append(iter.value());
595+ ++iter;
596+ }
597+
598+ for (int i = 0; i < conns.size(); i++) {
599+ ControllerEngineConnection conn = conns.at(i);
600 QScriptValueList args;
601+
602 args << QScriptValue(value);
603- args << QScriptValue(key.group); // Added by Math`
604- args << QScriptValue(key.item); // Added by Math`
605- QScriptValue result = function_value.call(QScriptValue(), args);
606+ args << QScriptValue(key.group);
607+ args << QScriptValue(key.item);
608+ QScriptValue result = conn.function.call(conn.context, args);
609 if (result.isError()) {
610- qWarning()<< "ControllerEngine: Call to " << function << " resulted in an error: " << result.toString();
611- }
612- ++i;
613+ qWarning()<< "ControllerEngine: Call to callback" << conn.id
614+ << "resulted in an error:" << result.toString();
615+ }
616+ else {
617+ qDebug() << "Called Connection for" << conn.id;
618+ }
619 }
620- } else {
621+ }
622+ else {
623 qWarning() << "ControllerEngine::slotValueChanged() Received signal from ControlObject that is not connected to a script function.";
624 }
625 }
626@@ -809,9 +946,6 @@
627 return false;
628 }
629
630- // Add the code we evaluated to our index
631- generateScriptFunctions(scriptCode);
632-
633 return true;
634 }
635
636@@ -839,24 +973,37 @@
637 whether it should fire just once
638 Output: The timer's ID, 0 if starting it failed
639 -------- ------------------------------------------------------ */
640-int ControllerEngine::beginTimer(int interval, QString scriptCode, bool oneShot) {
641- if (interval<20) {
642- qWarning() << "Timer request for" << interval << "ms is too short. Setting to the minimum of 20ms.";
643- interval=20;
644- }
645- // This makes use of every QObject's internal timer mechanism. Nice, clean, and simple.
646- // See http://doc.trolltech.com/4.6/qobject.html#startTimer for details
647+int ControllerEngine::beginTimer(int interval, QScriptValue timerCallback,
648+ bool oneShot) {
649+
650+ if (!timerCallback.isFunction() && !timerCallback.isString()) {
651+ qWarning() << "Invalid timer callback provided to beginTimer."
652+ << "Valid callbacks are strings and functions.";
653+ return 0;
654+ }
655+
656+ if (interval < 20) {
657+ qWarning() << "Timer request for" << interval
658+ << "ms is too short. Setting to the minimum of 20ms.";
659+ interval = 20;
660+ }
661+ // This makes use of every QObject's internal timer mechanism. Nice, clean,
662+ // and simple. See http://doc.trolltech.com/4.6/qobject.html#startTimer for
663+ // details
664 int timerId = startTimer(interval);
665- QPair<QString, bool> timerTarget;
666- timerTarget.first = scriptCode;
667-// timerTarget.second = oneShot;
668- m_timers[timerId]=timerTarget;
669-
670+ TimerInfo info;
671+ info.callback = timerCallback;
672+ QScriptContext *ctxt = m_pEngine->currentContext();
673+ info.context = ctxt ? ctxt->thisObject() : QScriptValue();
674+ info.oneShot = oneShot;
675+ m_timers[timerId] = info;
676 if (timerId == 0) {
677- qWarning() << "Controller script timer could not be created";
678+ qWarning() << "MIDI Script timer could not be created";
679 } else if (m_bDebug) {
680- qDebug() << "Starting" << (oneShot ? "one-shot timer:" : "timer:")
681- << timerId;
682+ if (oneShot)
683+ qDebug() << "Starting one-shot timer:" << timerId;
684+ else
685+ qDebug() << "Starting timer:" << timerId;
686 }
687 return timerId;
688 }
689@@ -885,7 +1032,7 @@
690 Output: -
691 -------- ------------------------------------------------------ */
692 void ControllerEngine::stopAllTimers() {
693- QMutableHashIterator<int, QPair<QString, bool> > i(m_timers);
694+ QMutableHashIterator<int, TimerInfo> i(m_timers);
695 while (i.hasNext()) {
696 i.next();
697 stopTimer(i.key());
698@@ -911,12 +1058,16 @@
699 return;
700 }
701
702- QPair<QString, bool> timerTarget = m_timers[timerId];
703- if (timerTarget.second) {
704+ TimerInfo timerTarget = m_timers[timerId];
705+ if (timerTarget.oneShot) {
706 stopTimer(timerId);
707 }
708
709- internalExecute(timerTarget.first);
710+ if (timerTarget.callback.isString()) {
711+ internalExecute(timerTarget.context, timerTarget.callback.toString());
712+ } else if (timerTarget.callback.isFunction()) {
713+ internalExecute(timerTarget.context, timerTarget.callback);
714+ }
715 }
716
717 /* -------- ------------------------------------------------------
718
719=== modified file 'mixxx/src/controllers/controllerengine.h'
720--- mixxx/src/controllers/controllerengine.h 2012-04-27 04:04:33 +0000
721+++ mixxx/src/controllers/controllerengine.h 2012-05-25 04:46:23 +0000
722@@ -20,6 +20,41 @@
723 // Forward declaration(s)
724 class Controller;
725 class ControlObjectThread;
726+class ControllerEngine;
727+
728+// ControllerEngineConnection class for closure-compatible engine.connectControl
729+class ControllerEngineConnection {
730+ public:
731+ ConfigKey key;
732+ QString id;
733+ QScriptValue function;
734+ ControllerEngine *ce;
735+ QScriptValue context;
736+};
737+
738+class ControllerEngineConnectionScriptValue : public QObject {
739+ Q_OBJECT
740+ Q_PROPERTY(QString id READ readId)
741+ // We cannot expose ConfigKey directly since it's not a
742+ // QObject
743+ //Q_PROPERTY(ConfigKey key READ key)
744+ // There's little use in exposing the function...
745+ //Q_PROPERTY(QScriptValue function READ function)
746+ public:
747+ ControllerEngineConnectionScriptValue(ControllerEngineConnection conn) {
748+ this->conn = conn;
749+ }
750+ QString readId() const { return this->conn.id; }
751+ Q_INVOKABLE void disconnect();
752+
753+ private:
754+ ControllerEngineConnection conn;
755+};
756+
757+/* comparison function for ControllerEngineConnection */
758+inline bool operator==(const ControllerEngineConnection &c1, const ControllerEngineConnection &c2) {
759+ return c1.id == c2.id && c1.key.group == c2.key.group && c1.key.item == c2.key.item;
760+}
761
762 class ControllerEngine : public QObject {
763 Q_OBJECT
764@@ -39,20 +74,22 @@
765 m_bPopups = bPopups;
766 }
767
768- // Look up registered script functions
769- QStringList getScriptFunctions();
770-
771- // Look up registered script function prefixes
772+ /** Resolve a function name to a QScriptValue. */
773+ QScriptValue resolveFunction(QString function) const;
774+ /** Look up registered script function prefixes */
775 QList<QString>& getScriptFunctionPrefixes() { return m_scriptFunctionPrefixes; };
776+ /** Disconnect a ControllerEngineConnection */
777+ void disconnectControl(const ControllerEngineConnection conn);
778
779 protected:
780 Q_INVOKABLE double getValue(QString group, QString name);
781 Q_INVOKABLE void setValue(QString group, QString name, double newValue);
782- Q_INVOKABLE bool connectControl(QString group, QString name,
783- QString function, bool disconnect = false);
784+ Q_INVOKABLE QScriptValue connectControl(QString group, QString name,
785+ QScriptValue function, bool disconnect = false);
786+ // Called indirectly by the objects returned by connectControl
787 Q_INVOKABLE void trigger(QString group, QString name);
788 Q_INVOKABLE void log(QString message);
789- Q_INVOKABLE int beginTimer(int interval, QString scriptCode, bool oneShot = false);
790+ Q_INVOKABLE int beginTimer(int interval, QScriptValue scriptCode, bool oneShot = false);
791 Q_INVOKABLE void stopTimer(int timerId);
792 Q_INVOKABLE void scratchEnable(int deck, int intervalsPerRev, float rpm,
793 float alpha, float beta, bool ramp = true);
794@@ -67,14 +104,20 @@
795 void slotValueChanged(double value);
796 // Evaluate a script file
797 bool evaluate(QString filepath);
798+
799 // Execute a particular function
800 bool execute(QString function);
801 // Execute a particular function with a list of arguments
802 bool execute(QString function, QScriptValueList args);
803+ bool execute(QScriptValue function, QScriptValueList args);
804 // Execute a particular function with a data string (e.g. a device ID)
805 bool execute(QString function, QString data);
806+ // Execute a particular function with a list of arguments
807+ bool execute(QString function, const QByteArray data);
808+ bool execute(QScriptValue function, const QByteArray data);
809 // Execute a particular function with a data buffer
810- bool execute(QString function, const QByteArray data);
811+ //TODO: redo this one
812+ //bool execute(QString function, const QByteArray data);
813 void loadScriptFiles(QString configPath,
814 QList<QString> scriptFileNames);
815 void initializeScripts(const QList<QString> scriptFunctionPrefixes);
816@@ -90,12 +133,15 @@
817 private:
818 bool evaluate(QString scriptName, QList<QString> scriptPaths);
819 bool internalExecute(QString scriptCode);
820+ bool internalExecute(QScriptValue thisObject, QString scriptCode);
821+ bool internalExecute(QScriptValue thisObject, QScriptValue functionObject);
822 void initializeScriptEngine();
823
824 void scriptErrorDialog(QString detailedError);
825 void generateScriptFunctions(QString code);
826 void stopAllTimers();
827
828+ void callFunctionOnObjects(QList<QString>, QString, QScriptValueList args = QScriptValueList());
829 bool checkException();
830 QScriptEngine *m_pEngine;
831
832@@ -107,12 +153,16 @@
833 Controller* m_pController;
834 bool m_bDebug;
835 bool m_bPopups;
836- QMultiHash<ConfigKey, QString> m_connectedControls;
837- QStringList m_scriptFunctions;
838+ QMultiHash<ConfigKey, ControllerEngineConnection> m_connectedControls;
839 QList<QString> m_scriptFunctionPrefixes;
840 QMap<QString,QStringList> m_scriptErrors;
841 QHash<ConfigKey, ControlObjectThread*> m_controlCache;
842- QHash<int, QPair<QString, bool> > m_timers;
843+ struct TimerInfo {
844+ QScriptValue callback;
845+ QScriptValue context;
846+ bool oneShot;
847+ };
848+ QHash<int, TimerInfo> m_timers;
849 SoftTakeover m_st;
850 ByteArrayClass *m_pBaClass;
851 // 256 (default) available virtual decks is enough I would think.
852
853=== modified file 'mixxx/src/controllers/midi/midicontroller.cpp'
854--- mixxx/src/controllers/midi/midicontroller.cpp 2012-05-24 21:59:28 +0000
855+++ mixxx/src/controllers/midi/midicontroller.cpp 2012-05-25 04:46:23 +0000
856@@ -284,6 +284,7 @@
857 return;
858 }
859
860+ QScriptValue function = pEngine->resolveFunction(mc.item());
861 QScriptValueList args;
862 args << QScriptValue(status & 0x0F);
863 args << QScriptValue(control);
864@@ -291,7 +292,7 @@
865 args << QScriptValue(status);
866 args << QScriptValue(mc.group());
867
868- pEngine->execute(mc.item(), args);
869+ pEngine->execute(function, args);
870 return;
871 }
872
873@@ -507,7 +508,8 @@
874 if (pEngine == NULL) {
875 return;
876 }
877- if (!pEngine->execute(mc.item(), data)) {
878+ QScriptValue function = pEngine->resolveFunction(mc.item());
879+ if (!pEngine->execute(function, data)) {
880 qDebug() << "MidiController: Invalid script function" << mc.item();
881 }
882 return;
883@@ -520,3 +522,4 @@
884 (((unsigned int)byte1) << 8) | status;
885 send(word);
886 }
887+
888
889=== modified file 'mixxx/src/controllers/mixxxcontrol.cpp'
890--- mixxx/src/controllers/mixxxcontrol.cpp 2012-04-23 14:16:08 +0000
891+++ mixxx/src/controllers/mixxxcontrol.cpp 2012-05-25 04:46:23 +0000
892@@ -1,6 +1,12 @@
893 #include <QtCore>
894 #include <QtXml>
895
896+#include "mixxxcontrol.h"
897+
898+#ifdef __MIDISCRIPT__
899+#include <QScriptValue>
900+#endif
901+
902 #include "controllers/mixxxcontrol.h"
903
904 MixxxControl::MixxxControl(QString controlobject_group, QString controlobject_item,
905@@ -8,6 +14,7 @@
906 : m_sGroup(controlobject_group),
907 m_sItem(controlobject_item),
908 m_sDescription(controlobject_description) {
909+ m_scriptFunction = QScriptValue();
910 }
911
912 /** Constructor that deserializes a MixxxControl object from a <control> or <output>
913
914=== modified file 'mixxx/src/controllers/mixxxcontrol.h'
915--- mixxx/src/controllers/mixxxcontrol.h 2012-04-23 14:16:08 +0000
916+++ mixxx/src/controllers/mixxxcontrol.h 2012-05-25 04:46:23 +0000
917@@ -2,9 +2,12 @@
918 #define MIXXXCONTROL_H
919
920 #include <QDebug>
921+
922 #include <QVariant>
923+#include <QScriptValue>
924 #include "controlobject.h"
925
926+
927 class MixxxControl {
928 public:
929 MixxxControl(QString controlobject_group="", QString controlobject_item="",
930@@ -18,6 +21,10 @@
931 QString group() const { return m_sGroup; };
932 QString item() const { return m_sItem; };
933 QString description() const { return m_sDescription; };
934+
935+ QScriptValue getScriptFunction() const { return m_scriptFunction; };
936+ void setScriptFunction(QScriptValue func) { m_scriptFunction = func; };
937+
938 ControlObject* getControlObject() const {
939 return ControlObject::getControl(ConfigKey(m_sGroup, m_sItem));
940 };
941@@ -31,6 +38,7 @@
942 QString m_sGroup;
943 QString m_sItem;
944 QString m_sDescription;
945+ QScriptValue m_scriptFunction;
946 };
947
948 inline bool operator<(const MixxxControl &first, const MixxxControl &second)
949@@ -57,3 +65,4 @@
950 }*/
951
952 #endif
953+
954
955=== modified file 'mixxx/src/test/controllerengine_test.cpp'
956--- mixxx/src/test/controllerengine_test.cpp 2012-04-07 14:20:50 +0000
957+++ mixxx/src/test/controllerengine_test.cpp 2012-05-25 04:46:23 +0000
958@@ -4,8 +4,10 @@
959 #include <QApplication>
960 #include <QObject>
961 #include <QFile>
962+#include <QThread>
963
964 #include "controlobject.h"
965+#include "controlpotmeter.h"
966 #include "configobject.h"
967 #include "controllers/controllerengine.h"
968
969@@ -14,10 +16,13 @@
970 class ControllerEngineTest : public testing::Test {
971 protected:
972 virtual void SetUp() {
973+ QApplication *app;
974 qDebug() << "SetUp";
975 static int argc = 1;
976- static char** argv = NULL;
977+ static char* argv[2] = { "test", NULL };
978+ QThread::currentThread()->setObjectName("Main");
979 app = new QApplication(argc, argv);
980+ new ControlPotmeter(ConfigKey("[Test]", "potmeter"),-1.,1.);
981 Controller* pController = NULL;
982 cEngine = new ControllerEngine(pController);
983 cEngine->setDebug(true);
984@@ -88,6 +93,178 @@
985 f.remove();
986 }
987
988+TEST_F(ControllerEngineTest, scriptConnectDisconnectControlNamedFunction) {
989+ QString script = "test.js";
990+ QFile f(script);
991+ f.open(QIODevice::ReadWrite | QIODevice::Truncate);
992+ f.write(
993+ "var executed = false;\n"
994+ "var connection;\n"
995+ "testConnectDisconnectControlCallback = function() {\n"
996+ " executed = true;\n"
997+ "};\n"
998+ "testConnectDisconnectControl = function() { \n"
999+ " connection = engine.connectControl('[Test]', 'potmeter', \n"
1000+ " 'testConnectDisconnectControlCallback');\n"
1001+ " engine.trigger('[Test]', 'potmeter');\n"
1002+ " return true;\n"
1003+ "};\n"
1004+ "checkConnectDisconnectControl = function() {\n"
1005+ " connection.disconnect();\n"
1006+ " if (!executed) {\n"
1007+ " throw 'Did Not Execute Callback';\n"
1008+ " }\n"
1009+ " return executed;\n"
1010+ "};\n"
1011+ );
1012+ f.close();
1013+
1014+ cEngine->evaluate(script);
1015+ EXPECT_FALSE(cEngine->hasErrors(script));
1016+
1017+ EXPECT_TRUE(cEngine->execute("testConnectDisconnectControl"));
1018+ EXPECT_TRUE(cEngine->execute("checkConnectDisconnectControl"));
1019+
1020+ f.remove();
1021+}
1022+
1023+TEST_F(ControllerEngineTest, scriptConnectDisconnectControlClosure) {
1024+ QString script = "test.js";
1025+ QFile f(script);
1026+ f.open(QIODevice::ReadWrite | QIODevice::Truncate);
1027+ f.write(
1028+ "var executed = false;\n"
1029+ "var connection;\n"
1030+ "testConnectDisconnectControl = function() { \n"
1031+ " connection = engine.connectControl('[Test]', 'potmeter', \n"
1032+ " function() { executed = true; }\n"
1033+ " );\n"
1034+ " engine.trigger('[Test]', 'potmeter');\n"
1035+ " return true;\n"
1036+ "};\n"
1037+ "checkConnectDisconnectControl = function() {\n"
1038+ " connection.disconnect();\n"
1039+ " if (!executed) {\n"
1040+ " throw 'Did Not Execute Callback';\n"
1041+ " }\n"
1042+ " return executed;\n"
1043+ "};\n"
1044+ );
1045+ f.close();
1046+
1047+ cEngine->evaluate(script);
1048+ EXPECT_FALSE(cEngine->hasErrors(script));
1049+
1050+ EXPECT_TRUE(cEngine->execute("testConnectDisconnectControl"));
1051+ EXPECT_TRUE(cEngine->execute("checkConnectDisconnectControl"));
1052+
1053+ f.remove();
1054+}
1055+
1056+TEST_F(ControllerEngineTest, scriptConnectDisconnectControlIsDisconnected) {
1057+ QString script = "test.js";
1058+ QFile f(script);
1059+ f.open(QIODevice::ReadWrite | QIODevice::Truncate);
1060+ f.write(
1061+ "var executed = false;\n"
1062+ "var connection;\n"
1063+ "testConnectDisconnectControl = function() { \n"
1064+ " connection = engine.connectControl('[Test]', 'potmeter', \n"
1065+ " function() { executed = true; }\n"
1066+ " );\n"
1067+ " if (typeof connection == 'undefined')\n"
1068+ " throw 'Unable to Connect controller';\n"
1069+ " connection.disconnect();\n"
1070+ " engine.trigger('[Test]', 'potmeter');\n"
1071+ " return true;\n"
1072+ "};\n"
1073+ "checkConnectDisconnectControl = function() {\n"
1074+ " if (executed) {\n"
1075+ " throw 'Callback was executed';\n"
1076+ " }\n"
1077+ " return executed==false;\n"
1078+ "};\n"
1079+ );
1080+ f.close();
1081+
1082+ cEngine->evaluate(script);
1083+ EXPECT_FALSE(cEngine->hasErrors(script));
1084+
1085+ EXPECT_TRUE(cEngine->execute("testConnectDisconnectControl"));
1086+ EXPECT_TRUE(cEngine->execute("checkConnectDisconnectControl"));
1087+
1088+ f.remove();
1089+}
1090+
1091+TEST_F(ControllerEngineTest, scriptConnectDisconnectControlIsDisconnectedByName) {
1092+ QString script = "test.js";
1093+ QFile f(script);
1094+ f.open(QIODevice::ReadWrite | QIODevice::Truncate);
1095+ f.write(
1096+ "var executed = false;\n"
1097+ "var connection;\n"
1098+ "connectionCallback = function() { executed = true; }\n"
1099+ "testConnectDisconnectControl = function() { \n"
1100+ " connection = engine.connectControl('[Test]', 'potmeter', 'connectionCallback');\n"
1101+ " if (typeof connection == 'undefined')\n"
1102+ " throw 'Unable to Connect controller';\n"
1103+ " engine.connectControl('[Test]', 'potmeter', 'connectionCallback', 1);\n"
1104+ " engine.trigger('[Test]', 'potmeter');\n"
1105+ " return true;\n"
1106+ "};\n"
1107+ "checkConnectDisconnectControl = function() {\n"
1108+ " if (executed) {\n"
1109+ " throw 'Callback was executed';\n"
1110+ " }\n"
1111+ " return executed==false;\n"
1112+ "};\n"
1113+ );
1114+ f.close();
1115+
1116+ cEngine->evaluate(script);
1117+ EXPECT_FALSE(cEngine->hasErrors(script));
1118+
1119+ EXPECT_TRUE(cEngine->execute("testConnectDisconnectControl"));
1120+ EXPECT_TRUE(cEngine->execute("checkConnectDisconnectControl"));
1121+
1122+ f.remove();
1123+}
1124+
1125+TEST_F(ControllerEngineTest, scriptConnectDisconnectControlIsDisconnectedByObject) {
1126+ QString script = "test.js";
1127+ QFile f(script);
1128+ f.open(QIODevice::ReadWrite | QIODevice::Truncate);
1129+ f.write(
1130+ "var executed = false;\n"
1131+ "var connection;\n"
1132+ "testConnectDisconnectControl = function() { \n"
1133+ " connection = engine.connectControl('[Test]', 'potmeter', \n"
1134+ " function() { executed = true; }\n"
1135+ " );\n"
1136+ " if (typeof connection == 'undefined')\n"
1137+ " throw 'Unable to Connect controller';\n"
1138+ " engine.connectControl('[Test]', 'potmeter', connection, 1);\n"
1139+ " engine.trigger('[Test]', 'potmeter');\n"
1140+ " return true;\n"
1141+ "};\n"
1142+ "checkConnectDisconnectControl = function() {\n"
1143+ " if (executed) {\n"
1144+ " throw 'Callback was executed';\n"
1145+ " }\n"
1146+ " return executed==false;\n"
1147+ "};\n"
1148+ );
1149+ f.close();
1150+
1151+ cEngine->evaluate(script);
1152+ EXPECT_FALSE(cEngine->hasErrors(script));
1153+
1154+ EXPECT_TRUE(cEngine->execute("testConnectDisconnectControl"));
1155+ EXPECT_TRUE(cEngine->execute("checkConnectDisconnectControl"));
1156+
1157+ f.remove();
1158+}
1159+
1160 TEST_F(ControllerEngineTest, setInvalidControlObject) {
1161 QString script = "test.js";
1162 QFile f(script);

Subscribers

People subscribed via source and target branches