Merge lp:~mixxxdevelopers/mixxx/features_direct_bind into lp:~mixxxdevelopers/mixxx/trunk
- features_direct_bind
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
RJ Skerry-Ryan | Needs Fixing | ||
Review via email: mp+104529@code.launchpad.net |
Commit message
Description of the change
Call script functions directly as QScriptValues instead of parsing small javascript snippets for invoking MIDI Scripts.
- 2875. By madjester <madjester@voidwalker>
-
Change declaration of ControllerEngin
e::timerEvent to match trunk.
Phillip Whelan (pwhelan) wrote : | # |
RJ Skerry-Ryan (rryan) wrote : | # |
Thanks! -- here are my comments sofar:
* ControllerEngin
* ControllerEngin
* In ControllerEngin
* 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.
* ControllerEngin
* 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:
* I think the m_scriptBindings cache in Controller/
* thanks for the tests :)
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:
>
> * ControllerEngin
> call()'ing the QScriptValue
>
> * ControllerEngin
> to isValid()
>
> * In ControllerEngin
> false if !isFunction() to match execute(
>
> * 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.
>
> * ControllerEngin
> QObject:
>
> * 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:
> result -- that's for use by the superclass which doesn't know about
> MidiControllerP
> below)
>
> * I think the m_scriptBindings cache in Controller/
> really necessary. Instead of the cache living in MixxxControl/
> and having to pre-cache every function we think we might want to call, why
> not put the cache in ControllerEngin
> ControllerEngin
> 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:/
> You are reviewing the proposed merge of
> lp:~mixxxdevelopers/mixxx/features_direct_bind into lp:mixxx.
>
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 ControllerEngin
e::callFunction OnObjects before calling a QScriptValue. - 2882. By madjester <madjester@voidwalker>
-
FIX: check if object.isFunction in ControllerEngin
e::resolveFunct ion. - 2883. By madjester <madjester@voidwalker>
-
FIX: return false in ControllerEngin
e::internalExec ute(QScriptValu e 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 ControllerEngin
e::connectContr ol. - 2886. By madjester <madjester@voidwalker>
-
Remove m_scriptBindings and bindScriptFunct
ions() from controllers and use ControllerEngin e::resolveFunct ion 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 ControllerEngin
e::resolveFunct ion.
Preview Diff
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); |
The runtime evaluation option and file reloading are in the works for after this is merged.