~rryan/mixxx/features_key

« back to all changes in this revision

Viewing changes to mixxx/src/controllers/controllerengine.cpp

  • Committer: Varun Jewalikar
  • Date: 2012-05-25 22:27:23 UTC
  • mfrom: (2684.18.232 trunk)
  • Revision ID: mr.unwell2006@gmail.com-20120525222723-0j4kc3av9cg59faf
MergingĀ fromĀ lp:mixxx

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/***************************************************************************
 
2
                          controllerengine.cpp  -  description
 
3
                          -------------------
 
4
    begin                : Sat Apr 30 2011
 
5
    copyright            : (C) 2011 by Sean M. Pappalardo
 
6
    email                : spappalardo@mixxx.org
 
7
 ***************************************************************************/
 
8
 
 
9
#include "controllers/controllerengine.h"
 
10
 
 
11
#include "controllers/controller.h"
 
12
#include "controllers/defs_controllers.h"
 
13
#include "controlobject.h"
 
14
#include "controlobjectthread.h"
 
15
#include "errordialoghandler.h"
 
16
 
 
17
// #include <QScriptSyntaxCheckResult>
 
18
 
 
19
#ifdef _MSC_VER
 
20
    #include <float.h>  // for _isnan() on VC++
 
21
    #define isnan(x) _isnan(x)  // VC++ uses _isnan() instead of isnan()
 
22
#else
 
23
    #include <math.h>  // for isnan() everywhere else
 
24
#endif
 
25
 
 
26
const int kDecks = 16;
 
27
 
 
28
ControllerEngine::ControllerEngine(Controller* controller)
 
29
    : m_pEngine(NULL),
 
30
      m_pController(controller),
 
31
      m_bDebug(false),
 
32
      m_bPopups(false),
 
33
      m_pBaClass(NULL) {
 
34
 
 
35
    // Handle error dialog buttons
 
36
    qRegisterMetaType<QMessageBox::StandardButton>("QMessageBox::StandardButton");
 
37
 
 
38
    // Pre-allocate arrays for average number of virtual decks
 
39
    m_intervalAccumulator.resize(kDecks);
 
40
    m_dx.resize(kDecks);
 
41
    m_rampTo.resize(kDecks);
 
42
    m_ramp.resize(kDecks);
 
43
    m_pitchFilter.resize(kDecks);
 
44
 
 
45
    // Initialize arrays used for testing and pointers
 
46
    for (int i=0; i < kDecks; i++) {
 
47
        m_dx[i] = 0.0;
 
48
        m_pitchFilter[i] = new PitchFilter();
 
49
        m_ramp[i] = false;
 
50
    }
 
51
 
 
52
    initializeScriptEngine();
 
53
}
 
54
 
 
55
ControllerEngine::~ControllerEngine() {
 
56
    // Clean up
 
57
    for (int i=0; i < kDecks; i++) {
 
58
        delete m_pitchFilter[i];
 
59
        m_pitchFilter[i] = NULL;
 
60
    }
 
61
 
 
62
    // Delete the script engine, first clearing the pointer so that
 
63
    // other threads will not get the dead pointer after we delete it.
 
64
    if (m_pEngine != NULL) {
 
65
        QScriptEngine *engine = m_pEngine;
 
66
        m_pEngine = NULL;
 
67
        engine->deleteLater();
 
68
    }
 
69
}
 
70
 
 
71
/* -------- ------------------------------------------------------
 
72
Purpose: Shuts down scripts in an orderly fashion
 
73
            (stops timers then executes shutdown functions)
 
74
Input:   -
 
75
Output:  -
 
76
-------- ------------------------------------------------------ */
 
77
void ControllerEngine::gracefulShutdown() {
 
78
    qDebug() << "ControllerEngine shutting down...";
 
79
 
 
80
    // Clear the m_connectedControls hash so we stop responding
 
81
    // to signals.
 
82
    m_connectedControls.clear();
 
83
 
 
84
    // Stop all timers
 
85
    stopAllTimers();
 
86
 
 
87
    // Call each script's shutdown function if it exists
 
88
    foreach (QString prefix, m_scriptFunctionPrefixes) {
 
89
        if (prefix == "") {
 
90
            continue;
 
91
        }
 
92
        QString shutdownName = QString("%1.shutdown").arg(prefix);
 
93
        if (m_bDebug) {
 
94
            qDebug() << "  Executing" << shutdownName;;
 
95
        }
 
96
        if (!internalExecute(shutdownName)) {
 
97
            qWarning() << "ControllerEngine: No" << shutdownName << "function in script";
 
98
        }
 
99
    }
 
100
 
 
101
    // Prevents leaving decks in an unstable state if the controller is shut
 
102
    // down while scratching
 
103
    QHashIterator<int, int> i(m_scratchTimers);
 
104
    while (i.hasNext()) {
 
105
        i.next();
 
106
        qDebug() << "  Aborting scratching on deck" << i.value();
 
107
        // Clear scratch2_enable
 
108
        QString group = QString("[Channel%1]").arg(i.value());
 
109
        ControlObjectThread *cot = getControlObjectThread(group, "scratch2_enable");
 
110
        if (cot != NULL) {
 
111
            cot->slotSet(0);
 
112
        }
 
113
    }
 
114
 
 
115
    // Free all the control object threads
 
116
    QList<ConfigKey> keys = m_controlCache.keys();
 
117
    QList<ConfigKey>::iterator it = keys.begin();
 
118
    QList<ConfigKey>::iterator end = keys.end();
 
119
    while (it != end) {
 
120
        ConfigKey key = *it;
 
121
        ControlObjectThread *cot = m_controlCache.take(key);
 
122
        delete cot;
 
123
        it++;
 
124
    }
 
125
 
 
126
    delete m_pBaClass;
 
127
    m_pBaClass = NULL;
 
128
}
 
129
 
 
130
bool ControllerEngine::isReady() {
 
131
    bool ret = m_pEngine != NULL;
 
132
    return ret;
 
133
}
 
134
 
 
135
void ControllerEngine::initializeScriptEngine() {
 
136
    // Create the script engine
 
137
    m_pEngine = new QScriptEngine(this);
 
138
 
 
139
    // Make this ControllerEngine instance available to scripts as 'engine'.
 
140
    QScriptValue engineGlobalObject = m_pEngine->globalObject();
 
141
    engineGlobalObject.setProperty("engine", m_pEngine->newQObject(this));
 
142
 
 
143
    if (m_pController) {
 
144
        qDebug() << "Controller in script engine is:" << m_pController->getName();
 
145
 
 
146
        // Make the Controller instance available to scripts
 
147
        engineGlobalObject.setProperty("controller", m_pEngine->newQObject(m_pController));
 
148
        // ...under the legacy name as well
 
149
        engineGlobalObject.setProperty("midi", m_pEngine->newQObject(m_pController));
 
150
    }
 
151
 
 
152
    m_pBaClass = new ByteArrayClass(m_pEngine);
 
153
    engineGlobalObject.setProperty("ByteArray", m_pBaClass->constructor());
 
154
}
 
155
 
 
156
/* -------- ------------------------------------------------------
 
157
   Purpose: Load all script files given in the supplied list
 
158
   Input:   Global ConfigObject, QString list of file names to load
 
159
   Output:  -
 
160
   -------- ------------------------------------------------------ */
 
161
void ControllerEngine::loadScriptFiles(QString configPath,
 
162
                                       QList<QString> scriptFileNames) {
 
163
    // Set the Debug flag
 
164
    if (m_pController)
 
165
        m_bDebug = m_pController->debugging();
 
166
 
 
167
    qDebug() << "ControllerEngine: Loading & evaluating all script code";
 
168
 
 
169
    // scriptPaths holds the paths to search in when we're looking for scripts
 
170
    QList<QString> scriptPaths;
 
171
    scriptPaths.append(USER_PRESETS_PATH);
 
172
    scriptPaths.append(LOCAL_PRESETS_PATH);
 
173
    scriptPaths.append(configPath.append("controllers/"));
 
174
 
 
175
    foreach (QString curScriptFileName, scriptFileNames) {
 
176
        evaluate(curScriptFileName, scriptPaths);
 
177
 
 
178
        if (m_scriptErrors.contains(curScriptFileName)) {
 
179
            qDebug() << "Errors occured while loading " << curScriptFileName;
 
180
        }
 
181
    }
 
182
 
 
183
    emit(initialized());
 
184
}
 
185
 
 
186
/* -------- ------------------------------------------------------
 
187
   Purpose: Run the initialization function for each loaded script
 
188
                if it exists
 
189
   Input:   -
 
190
   Output:  -
 
191
   -------- ------------------------------------------------------ */
 
192
void ControllerEngine::initializeScripts(QList<QString> scriptFunctionPrefixes) {
 
193
    m_scriptFunctionPrefixes = scriptFunctionPrefixes;
 
194
 
 
195
    foreach (QString prefix, m_scriptFunctionPrefixes) {
 
196
        if (prefix == "") {
 
197
            continue;
 
198
        }
 
199
        QString initMethod = QString("%1.init").arg(prefix);
 
200
        if (m_bDebug) {
 
201
            qDebug() << "ControllerEngine: Executing" << initMethod;
 
202
        }
 
203
 
 
204
        QScriptValueList args;
 
205
        args << QScriptValue(m_pController->getName());
 
206
        args << QScriptValue(m_bDebug);
 
207
        if (!execute(initMethod, args)) {
 
208
            qWarning() << "ControllerEngine: No" << initMethod << "function in script";
 
209
        }
 
210
    }
 
211
 
 
212
    emit(initialized());
 
213
}
 
214
 
 
215
/* -------- ------------------------------------------------------
 
216
   Purpose: Validate script syntax, then evaluate() it so the
 
217
            functions are registered & available for use.
 
218
   Input:   -
 
219
   Output:  -
 
220
   -------- ------------------------------------------------------ */
 
221
bool ControllerEngine::evaluate(QString filepath) {
 
222
    QList<QString> dummy;
 
223
    bool ret = evaluate(filepath, dummy);
 
224
 
 
225
    return ret;
 
226
}
 
227
 
 
228
/* -------- ------------------------------------------------------
 
229
   Purpose: Evaluate & call a script function
 
230
   Input:   Function name
 
231
   Output:  false if an invalid function or an exception
 
232
   -------- ------------------------------------------------------ */
 
233
bool ControllerEngine::execute(QString function) {
 
234
    if (m_pEngine == NULL)
 
235
        return false;
 
236
 
 
237
    QScriptValue scriptFunction = m_pEngine->evaluate(function);
 
238
 
 
239
    if (checkException())
 
240
        return false;
 
241
 
 
242
    if (!scriptFunction.isFunction())
 
243
        return false;
 
244
 
 
245
    scriptFunction.call(QScriptValue());
 
246
    if (checkException())
 
247
        return false;
 
248
 
 
249
    return true;
 
250
}
 
251
 
 
252
 
 
253
/* -------- ------------------------------------------------------
 
254
    Purpose: Evaluate & run script code
 
255
    Input:   Code string
 
256
    Output:  false if an exception
 
257
    -------- ------------------------------------------------------ */
 
258
bool ControllerEngine::internalExecute(QString scriptCode) {
 
259
    // A special version of execute since we're evaluating strings, not actual functions
 
260
    //  (execute() would print an error that it's not a function every time a timer fires.)
 
261
    if (m_pEngine == NULL)
 
262
        return false;
 
263
 
 
264
    // Check syntax
 
265
    QScriptSyntaxCheckResult result = m_pEngine->checkSyntax(scriptCode);
 
266
    QString error = "";
 
267
    switch (result.state()) {
 
268
        case (QScriptSyntaxCheckResult::Valid): break;
 
269
        case (QScriptSyntaxCheckResult::Intermediate):
 
270
            error = "Incomplete code";
 
271
            break;
 
272
        case (QScriptSyntaxCheckResult::Error):
 
273
            error = "Syntax error";
 
274
            break;
 
275
    }
 
276
    if (error!="") {
 
277
        error = QString("%1: %2 at line %3, column %4 of script code:\n%5\n")
 
278
                .arg(error,
 
279
                     result.errorMessage(),
 
280
                     QString::number(result.errorLineNumber()),
 
281
                     QString::number(result.errorColumnNumber()),
 
282
                     scriptCode);
 
283
 
 
284
        if (m_bDebug) {
 
285
            qCritical() << "ControllerEngine:" << error;
 
286
        } else {
 
287
            scriptErrorDialog(error);
 
288
        }
 
289
        return false;
 
290
    }
 
291
 
 
292
    QScriptValue scriptFunction = m_pEngine->evaluate(scriptCode);
 
293
 
 
294
    if (checkException()) {
 
295
        qDebug() << "Exception";
 
296
        return false;
 
297
    }
 
298
 
 
299
    // If it's not a function, we're done.
 
300
    if (!scriptFunction.isFunction()) {
 
301
        return true;
 
302
    }
 
303
 
 
304
    // If it does happen to be a function, call it.
 
305
    scriptFunction.call(QScriptValue());
 
306
    if (checkException()) {
 
307
        qDebug() << "Exception";
 
308
        return false;
 
309
    }
 
310
 
 
311
    return true;
 
312
}
 
313
 
 
314
/**-------- ------------------------------------------------------
 
315
   Purpose: Evaluate & call a script function with argument list
 
316
   Input:   Function name, argument list
 
317
   Output:  false if an invalid function or an exception
 
318
   -------- ------------------------------------------------------ */
 
319
bool ControllerEngine::execute(QString function, QScriptValueList args) {
 
320
    if(m_pEngine == NULL) {
 
321
        qDebug() << "ControllerEngine::execute: No script engine exists!";
 
322
        return false;
 
323
    }
 
324
 
 
325
    QScriptValue scriptFunction = m_pEngine->evaluate(function);
 
326
 
 
327
    if (checkException())
 
328
        return false;
 
329
    if (!scriptFunction.isFunction())
 
330
        return false;
 
331
 
 
332
    scriptFunction.call(QScriptValue(), args);
 
333
 
 
334
    if (checkException())
 
335
        return false;
 
336
    return true;
 
337
}
 
338
 
 
339
/**-------- ------------------------------------------------------
 
340
   Purpose: Evaluate & call a script function
 
341
   Input:   Function name, data string (e.g. device ID)
 
342
   Output:  false if an invalid function or an exception
 
343
   -------- ------------------------------------------------------ */
 
344
bool ControllerEngine::execute(QString function, QString data) {
 
345
    if (m_pEngine == NULL) {
 
346
        qDebug() << "ControllerEngine::execute: No script engine exists!";
 
347
        return false;
 
348
    }
 
349
 
 
350
    QScriptValue scriptFunction = m_pEngine->evaluate(function);
 
351
 
 
352
    if (checkException()) {
 
353
        qDebug() << "ControllerEngine::execute: Exception";
 
354
        return false;
 
355
    }
 
356
 
 
357
    if (!scriptFunction.isFunction()) {
 
358
        qDebug() << "ControllerEngine::execute: Not a function";
 
359
        return false;
 
360
    }
 
361
 
 
362
    QScriptValueList args;
 
363
    args << QScriptValue(data);
 
364
 
 
365
    scriptFunction.call(QScriptValue(), args);
 
366
    if (checkException()) {
 
367
        qDebug() << "ControllerEngine::execute: Exception";
 
368
        return false;
 
369
    }
 
370
    return true;
 
371
}
 
372
 
 
373
/**-------- ------------------------------------------------------
 
374
   Purpose: Evaluate & call a script function
 
375
   Input:   Function name, ponter to data buffer, length of buffer
 
376
   Output:  false if an invalid function or an exception
 
377
   -------- ------------------------------------------------------ */
 
378
bool ControllerEngine::execute(QString function, const QByteArray data) {
 
379
    if (m_pEngine == NULL) {
 
380
        return false;
 
381
    }
 
382
 
 
383
    if (!m_pEngine->canEvaluate(function)) {
 
384
        qCritical() << "ControllerEngine: ?Syntax error in function" << function;
 
385
        return false;
 
386
    }
 
387
 
 
388
    QScriptValue scriptFunction = m_pEngine->evaluate(function);
 
389
 
 
390
    if (checkException())
 
391
        return false;
 
392
    if (!scriptFunction.isFunction())
 
393
        return false;
 
394
 
 
395
//     const char* buffer=reinterpret_cast<const char*>(data);
 
396
 
 
397
    QScriptValueList args;
 
398
//     args << QScriptValue(data);
 
399
    args << QScriptValue(m_pBaClass->newInstance(data));
 
400
    args << QScriptValue(data.size());
 
401
//     args << QScriptValue(m_pBaClass->newInstance(QByteArray::fromRawData(buffer,length)));
 
402
//     args << QScriptValue(length);
 
403
 
 
404
    scriptFunction.call(QScriptValue(), args);
 
405
    if (checkException())
 
406
        return false;
 
407
    return true;
 
408
}
 
409
 
 
410
/* -------- ------------------------------------------------------
 
411
   Purpose: Check to see if a script threw an exception
 
412
   Input:   QScriptValue returned from call(scriptFunctionName)
 
413
   Output:  true if there was an exception
 
414
   -------- ------------------------------------------------------ */
 
415
bool ControllerEngine::checkException() {
 
416
    if(m_pEngine == NULL) {
 
417
        return false;
 
418
    }
 
419
 
 
420
    if (m_pEngine->hasUncaughtException()) {
 
421
        QScriptValue exception = m_pEngine->uncaughtException();
 
422
        QString errorMessage = exception.toString();
 
423
        int line = m_pEngine->uncaughtExceptionLineNumber();
 
424
        QStringList backtrace = m_pEngine->uncaughtExceptionBacktrace();
 
425
        QString filename = exception.property("fileName").toString();
 
426
 
 
427
        QStringList error;
 
428
        error << (filename.isEmpty() ? "" : filename) << errorMessage << QString(line);
 
429
        m_scriptErrors.insert((filename.isEmpty() ? "passed code" : filename), error);
 
430
 
 
431
        QString errorText = QString(tr("Uncaught exception at line %1 in file %2: %3"))
 
432
                            .arg(QString::number(line),
 
433
                                (filename.isEmpty() ? "" : filename),
 
434
                                errorMessage);
 
435
 
 
436
        if (filename.isEmpty())
 
437
            errorText = QString(tr("Uncaught exception at line %1 in passed code: %2"))
 
438
                        .arg(QString::number(line), errorMessage);
 
439
 
 
440
        if (m_bDebug)
 
441
            qCritical() << "ControllerEngine:" << errorText
 
442
                        << "\nBacktrace:\n"
 
443
                        << backtrace;
 
444
        else scriptErrorDialog(errorText);
 
445
        return true;
 
446
    }
 
447
    return false;
 
448
}
 
449
 
 
450
/*  -------- ------------------------------------------------------
 
451
    Purpose: Common error dialog creation code for run-time exceptions
 
452
                Allows users to ignore the error or reload the mappings
 
453
    Input:   Detailed error string
 
454
    Output:  -
 
455
    -------- ------------------------------------------------------ */
 
456
void ControllerEngine::scriptErrorDialog(QString detailedError) {
 
457
    qWarning() << "ControllerEngine:" << detailedError;
 
458
    ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties();
 
459
    props->setType(DLG_WARNING);
 
460
    props->setTitle(tr("Controller script error"));
 
461
    props->setText(tr("A control you just used is not working properly."));
 
462
    props->setInfoText(tr("<html>(The script code needs to be fixed.)"
 
463
        "<br>For now, you can:<ul><li>Ignore this error for this session but you may experience erratic behavior</li>"
 
464
        "<li>Try to recover by resetting your controller</li></ul></html>"));
 
465
    props->setDetails(detailedError);
 
466
    props->setKey(detailedError);   // To prevent multiple windows for the same error
 
467
 
 
468
    // Allow user to suppress further notifications about this particular error
 
469
    props->addButton(QMessageBox::Ignore);
 
470
 
 
471
    props->addButton(QMessageBox::Retry);
 
472
    props->addButton(QMessageBox::Close);
 
473
    props->setDefaultButton(QMessageBox::Close);
 
474
    props->setEscapeButton(QMessageBox::Close);
 
475
    props->setModal(false);
 
476
 
 
477
    if (ErrorDialogHandler::instance()->requestErrorDialog(props)) {
 
478
        // Enable custom handling of the dialog buttons
 
479
        connect(ErrorDialogHandler::instance(), SIGNAL(stdButtonClicked(QString, QMessageBox::StandardButton)),
 
480
                this, SLOT(errorDialogButton(QString, QMessageBox::StandardButton)));
 
481
    }
 
482
}
 
483
 
 
484
/* -------- ------------------------------------------------------
 
485
    Purpose: Slot to handle custom button clicks in error dialogs
 
486
    Input:   Key of dialog, StandardButton that was clicked
 
487
    Output:  -
 
488
    -------- ------------------------------------------------------ */
 
489
void ControllerEngine::errorDialogButton(QString key, QMessageBox::StandardButton button) {
 
490
    Q_UNUSED(key);
 
491
 
 
492
    // Something was clicked, so disable this signal now
 
493
    disconnect(ErrorDialogHandler::instance(),
 
494
               SIGNAL(stdButtonClicked(QString, QMessageBox::StandardButton)),
 
495
               this,
 
496
               SLOT(errorDialogButton(QString, QMessageBox::StandardButton)));
 
497
 
 
498
    if (button == QMessageBox::Retry) {
 
499
        emit(resetController());
 
500
    }
 
501
}
 
502
 
 
503
/* -------- ------------------------------------------------------
 
504
   Purpose: Returns a list of functions available in the QtScript
 
505
            code
 
506
   Input:   -
 
507
   Output:  functionList QStringList
 
508
   -------- ------------------------------------------------------ */
 
509
QStringList ControllerEngine::getScriptFunctions() {
 
510
    QStringList ret = m_scriptFunctions;
 
511
    return ret;
 
512
}
 
513
 
 
514
void ControllerEngine::generateScriptFunctions(QString scriptCode) {
 
515
//     QStringList functionList;
 
516
    QStringList codeLines = scriptCode.split("\n");
 
517
 
 
518
//     qDebug() << "ControllerEngine: m_scriptCode=" << m_scriptCode;
 
519
 
 
520
    if (m_bDebug)
 
521
        qDebug() << "ControllerEngine:" << codeLines.count() << "lines of code being searched for functions";
 
522
 
 
523
    // grep 'function' midi/midi-mappings-scripts.js|grep -i '(msg)'|sed -e 's/function \(.*\)(msg).*/\1/i' -e 's/[= ]//g'
 
524
    QRegExp rx("*.*function*(*)*");    // Find all lines with function names in them
 
525
    rx.setPatternSyntax(QRegExp::Wildcard);
 
526
 
 
527
    int position = codeLines.indexOf(rx);
 
528
 
 
529
    while (position != -1) {    // While there are more matches
 
530
 
 
531
        QString line = codeLines.takeAt(position);    // Pull & remove the current match from the list.
 
532
 
 
533
        if (line.indexOf('#') != 0 && line.indexOf("//") != 0) {    // ignore commented out lines
 
534
            QStringList field = line.split(" ");
 
535
            if (m_bDebug) qDebug() << "ControllerEngine: Found function:" << field[0]
 
536
                                      << "at line" << position;
 
537
            m_scriptFunctions.append(field[0]);
 
538
        }
 
539
        position = codeLines.indexOf(rx);
 
540
    }
 
541
}
 
542
 
 
543
ControlObjectThread* ControllerEngine::getControlObjectThread(QString group, QString name) {
 
544
    ConfigKey key = ConfigKey(group, name);
 
545
 
 
546
    ControlObjectThread *cot = NULL;
 
547
    if(!m_controlCache.contains(key)) {
 
548
        ControlObject *co = ControlObject::getControl(key);
 
549
        if(co != NULL) {
 
550
            cot = new ControlObjectThread(co);
 
551
            m_controlCache.insert(key, cot);
 
552
        }
 
553
    } else {
 
554
        cot = m_controlCache.value(key);
 
555
    }
 
556
 
 
557
    return cot;
 
558
}
 
559
 
 
560
/* -------- ------------------------------------------------------
 
561
   Purpose: Returns the current value of a Mixxx control (for scripts)
 
562
   Input:   Control group (e.g. [Channel1]), Key name (e.g. [filterHigh])
 
563
   Output:  The value
 
564
   -------- ------------------------------------------------------ */
 
565
double ControllerEngine::getValue(QString group, QString name) {
 
566
    ControlObjectThread *cot = getControlObjectThread(group, name);
 
567
    if (cot == NULL) {
 
568
        qWarning() << "ControllerEngine: Unknown control" << group << name << ", returning 0.0";
 
569
        return 0.0;
 
570
    }
 
571
    return cot->get();
 
572
}
 
573
 
 
574
/* -------- ------------------------------------------------------
 
575
   Purpose: Sets new value of a Mixxx control (for scripts)
 
576
   Input:   Control group, Key name, new value
 
577
   Output:  -
 
578
   -------- ------------------------------------------------------ */
 
579
void ControllerEngine::setValue(QString group, QString name, double newValue) {
 
580
    if (isnan(newValue)) {
 
581
        qWarning() << "ControllerEngine: script setting [" << group << "," << name
 
582
                 << "] to NotANumber, ignoring.";
 
583
        return;
 
584
    }
 
585
 
 
586
    ControlObjectThread *cot = getControlObjectThread(group, name);
 
587
 
 
588
    if (cot != NULL && !m_st.ignore(cot->getControlObject(), newValue)) {
 
589
        cot->slotSet(newValue);
 
590
        // We call emitValueChanged so that script functions connected to this
 
591
        // control will get updates.
 
592
        cot->emitValueChanged();
 
593
    }
 
594
}
 
595
 
 
596
/* -------- ------------------------------------------------------
 
597
   Purpose: qDebugs script output so it ends up in mixxx.log
 
598
   Input:   String to log
 
599
   Output:  -
 
600
   -------- ------------------------------------------------------ */
 
601
void ControllerEngine::log(QString message) {
 
602
    qDebug() << message;
 
603
}
 
604
 
 
605
/* -------- ------------------------------------------------------
 
606
   Purpose: Emits valueChanged() so device outputs update
 
607
   Input:   -
 
608
   Output:  -
 
609
   -------- ------------------------------------------------------ */
 
610
void ControllerEngine::trigger(QString group, QString name) {
 
611
    ControlObjectThread *cot = getControlObjectThread(group, name);
 
612
    if(cot != NULL) {
 
613
        cot->emitValueChanged();
 
614
    }
 
615
}
 
616
 
 
617
/**-------- ------------------------------------------------------
 
618
   Purpose: (Dis)connects a ControlObject valueChanged() signal to/from a script function
 
619
   Input:   Control group (e.g. [Channel1]), Key name (e.g. [filterHigh]),
 
620
                script function name, true if you want to disconnect
 
621
   Output:  true if successful
 
622
   -------- ------------------------------------------------------ */
 
623
bool ControllerEngine::connectControl(QString group, QString name, QString function, bool disconnect) {
 
624
    ControlObjectThread* cobj = getControlObjectThread(group, name);
 
625
    ConfigKey key(group, name);
 
626
 
 
627
    if (cobj == NULL) {
 
628
        qWarning() << "ControllerEngine: script connecting [" << group << "," << name
 
629
                   << "], which is non-existent. ignoring.";
 
630
        return false;
 
631
    }
 
632
 
 
633
    // Don't add duplicates
 
634
    if (!disconnect && m_connectedControls.contains(key, function)) {
 
635
        return true;
 
636
    }
 
637
 
 
638
    if (m_pEngine == NULL) {
 
639
        return false;
 
640
    }
 
641
 
 
642
    QScriptValue slot = m_pEngine->evaluate(function);
 
643
 
 
644
    if (!checkException() && slot.isFunction()) {
 
645
        if (disconnect) {
 
646
            //qDebug() << "ControllerEngine::connectControl disconnected " << group << name << " from " << function;
 
647
            m_connectedControls.remove(key, function);
 
648
            // Only disconnect the signal if there are no other instances of this control using it
 
649
            if (!m_connectedControls.contains(key)) {
 
650
                this->disconnect(cobj, SIGNAL(valueChanged(double)),
 
651
                                 this, SLOT(slotValueChanged(double)));
 
652
            }
 
653
        } else {
 
654
            //qDebug() << "ControllerEngine::connectControl connected " << group << name << " to " << function;
 
655
            connect(cobj, SIGNAL(valueChanged(double)),
 
656
                    this, SLOT(slotValueChanged(double)));
 
657
            m_connectedControls.insert(key, function);
 
658
        }
 
659
        return true;
 
660
    }
 
661
    return false;
 
662
}
 
663
 
 
664
/**-------- ------------------------------------------------------
 
665
   Purpose: Receives valueChanged() slots from ControlObjects, and
 
666
   fires off the appropriate script function.
 
667
   -------- ------------------------------------------------------ */
 
668
void ControllerEngine::slotValueChanged(double value) {
 
669
    ControlObjectThread* sender = dynamic_cast<ControlObjectThread*>(this->sender());
 
670
    if (sender == NULL) {
 
671
        qWarning() << "ControllerEngine::slotValueChanged() Shouldn't happen -- sender == NULL";
 
672
        return;
 
673
    }
 
674
 
 
675
    ControlObject* pSenderCO = sender->getControlObject();
 
676
    if (pSenderCO == NULL) {
 
677
        qWarning() << "ControllerEngine::slotValueChanged() The sender's CO is NULL.";
 
678
        return;
 
679
    }
 
680
    ConfigKey key = pSenderCO->getKey();
 
681
 
 
682
    if (m_connectedControls.contains(key)) {
 
683
        QMultiHash<ConfigKey, QString>::iterator i = m_connectedControls.find(key);
 
684
        while (i != m_connectedControls.end() && i.key() == key) {
 
685
            QString function = i.value();
 
686
 
 
687
            //qDebug() << "ControllerEngine::slotValueChanged() received signal from " << key.group << key.item << " ... firing : " << function;
 
688
 
 
689
            // Could branch to execute from here, but for now do it this way.
 
690
            QScriptValue function_value = m_pEngine->evaluate(function);
 
691
            QScriptValueList args;
 
692
            args << QScriptValue(value);
 
693
            args << QScriptValue(key.group); // Added by Math`
 
694
            args << QScriptValue(key.item);  // Added by Math`
 
695
            QScriptValue result = function_value.call(QScriptValue(), args);
 
696
            if (result.isError()) {
 
697
                qWarning()<< "ControllerEngine: Call to " << function << " resulted in an error:  " << result.toString();
 
698
            }
 
699
            ++i;
 
700
        }
 
701
    } else {
 
702
        qWarning() << "ControllerEngine::slotValueChanged() Received signal from ControlObject that is not connected to a script function.";
 
703
    }
 
704
}
 
705
 
 
706
/* -------- ------------------------------------------------------
 
707
   Purpose: Evaluate a script file
 
708
   Input:   Script filename
 
709
   Output:  false if the script file has errors or doesn't exist
 
710
   -------- ------------------------------------------------------ */
 
711
bool ControllerEngine::evaluate(QString scriptName, QList<QString> scriptPaths) {
 
712
    if (m_pEngine == NULL) {
 
713
        return false;
 
714
    }
 
715
 
 
716
    QString filename = "";
 
717
    QFile input;
 
718
 
 
719
    if (scriptPaths.length() == 0) {
 
720
        // If we aren't given any paths to search, assume that scriptName
 
721
        // contains the full file name
 
722
        filename = scriptName;
 
723
        input.setFileName(filename);
 
724
    } else {
 
725
        foreach (QString scriptPath, scriptPaths) {
 
726
            QDir scriptPathDir(scriptPath);
 
727
            filename = scriptPathDir.absoluteFilePath(scriptName);
 
728
            input.setFileName(filename);
 
729
            if (input.exists())  {
 
730
                break;
 
731
            }
 
732
        }
 
733
    }
 
734
 
 
735
    qDebug() << "ControllerEngine: Loading" << filename;
 
736
 
 
737
    // Read in the script file
 
738
    if (!input.open(QIODevice::ReadOnly)) {
 
739
        QString errorLog =
 
740
            QString("ControllerEngine: Problem opening the script file: %1, error # %2, %3")
 
741
                .arg(filename, QString("%1").arg(input.error()), input.errorString());
 
742
 
 
743
        if (m_bDebug) {
 
744
            qCritical() << errorLog;
 
745
        } else {
 
746
            qWarning() << errorLog;
 
747
            if (m_bPopups) {
 
748
                // Set up error dialog
 
749
                ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties();
 
750
                props->setType(DLG_WARNING);
 
751
                props->setTitle("Controller script file problem");
 
752
                props->setText(QString("There was a problem opening the controller script file %1.").arg(filename));
 
753
                props->setInfoText(input.errorString());
 
754
 
 
755
                // Ask above layer to display the dialog & handle user response
 
756
                ErrorDialogHandler::instance()->requestErrorDialog(props);
 
757
            }
 
758
        }
 
759
        return false;
 
760
    }
 
761
 
 
762
    QString scriptCode = "";
 
763
    scriptCode.append(input.readAll());
 
764
    scriptCode.append('\n');
 
765
    input.close();
 
766
 
 
767
    // Check syntax
 
768
    QScriptSyntaxCheckResult result = m_pEngine->checkSyntax(scriptCode);
 
769
    QString error="";
 
770
    switch (result.state()) {
 
771
        case (QScriptSyntaxCheckResult::Valid): break;
 
772
        case (QScriptSyntaxCheckResult::Intermediate):
 
773
            error = "Incomplete code";
 
774
            break;
 
775
        case (QScriptSyntaxCheckResult::Error):
 
776
            error = "Syntax error";
 
777
            break;
 
778
    }
 
779
    if (error != "") {
 
780
        error = QString("%1 at line %2, column %3 in file %4: %5")
 
781
                    .arg(error,
 
782
                         QString::number(result.errorLineNumber()),
 
783
                         QString::number(result.errorColumnNumber()),
 
784
                         filename, result.errorMessage());
 
785
 
 
786
        if (m_bDebug) {
 
787
            qCritical() << "ControllerEngine:" << error;
 
788
        } else {
 
789
            qWarning() << "ControllerEngine:" << error;
 
790
            if (m_bPopups) {
 
791
                ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties();
 
792
                props->setType(DLG_WARNING);
 
793
                props->setTitle("Controller script file error");
 
794
                props->setText(QString("There was an error in the controller script file %1.").arg(filename));
 
795
                props->setInfoText("The functionality provided by this script file will be disabled.");
 
796
                props->setDetails(error);
 
797
 
 
798
                ErrorDialogHandler::instance()->requestErrorDialog(props);
 
799
            }
 
800
        }
 
801
        return false;
 
802
    }
 
803
 
 
804
    // Evaluate the code
 
805
    QScriptValue scriptFunction = m_pEngine->evaluate(scriptCode, filename);
 
806
 
 
807
    // Record errors
 
808
    if (checkException()) {
 
809
        return false;
 
810
    }
 
811
 
 
812
    // Add the code we evaluated to our index
 
813
    generateScriptFunctions(scriptCode);
 
814
 
 
815
    return true;
 
816
}
 
817
 
 
818
/*
 
819
 * Check whether a source file that was evaluated()'d has errors.
 
820
 */
 
821
bool ControllerEngine::hasErrors(QString filename) {
 
822
    bool ret = m_scriptErrors.contains(filename);
 
823
    return ret;
 
824
}
 
825
 
 
826
/*
 
827
 * Get the errors for a source file that was evaluated()'d
 
828
 */
 
829
const QStringList ControllerEngine::getErrors(QString filename) {
 
830
    QStringList ret = m_scriptErrors.value(filename, QStringList());
 
831
    return ret;
 
832
}
 
833
 
 
834
 
 
835
/* -------- ------------------------------------------------------
 
836
   Purpose: Creates & starts a timer that runs some script code
 
837
                on timeout
 
838
   Input:   Number of milliseconds, script function to call,
 
839
                whether it should fire just once
 
840
   Output:  The timer's ID, 0 if starting it failed
 
841
   -------- ------------------------------------------------------ */
 
842
int ControllerEngine::beginTimer(int interval, QString scriptCode, bool oneShot) {
 
843
    if (interval<20) {
 
844
        qWarning() << "Timer request for" << interval << "ms is too short. Setting to the minimum of 20ms.";
 
845
        interval=20;
 
846
    }
 
847
    // This makes use of every QObject's internal timer mechanism. Nice, clean, and simple.
 
848
    // See http://doc.trolltech.com/4.6/qobject.html#startTimer for details
 
849
    int timerId = startTimer(interval);
 
850
    QPair<QString, bool> timerTarget;
 
851
    timerTarget.first = scriptCode;
 
852
//     timerTarget.second = oneShot;
 
853
    m_timers[timerId]=timerTarget;
 
854
 
 
855
    if (timerId == 0) {
 
856
        qWarning() << "Controller script timer could not be created";
 
857
    } else if (m_bDebug) {
 
858
        qDebug() << "Starting" << (oneShot ? "one-shot timer:" : "timer:")
 
859
                 << timerId;
 
860
    }
 
861
    return timerId;
 
862
}
 
863
 
 
864
/* -------- ------------------------------------------------------
 
865
   Purpose: Stops & removes a timer
 
866
   Input:   ID of timer to stop
 
867
   Output:  -
 
868
   -------- ------------------------------------------------------ */
 
869
void ControllerEngine::stopTimer(int timerId) {
 
870
    if (!m_timers.contains(timerId)) {
 
871
        qWarning() << "Killing timer" << timerId << ": That timer does not exist!";
 
872
        return;
 
873
    }
 
874
    if (m_bDebug) {
 
875
        qDebug() << "Killing timer:" << timerId;
 
876
    }
 
877
 
 
878
    killTimer(timerId);
 
879
    m_timers.remove(timerId);
 
880
}
 
881
 
 
882
/* -------- ------------------------------------------------------
 
883
   Purpose: Stops & removes all timers (for shutdown)
 
884
   Input:   -
 
885
   Output:  -
 
886
   -------- ------------------------------------------------------ */
 
887
void ControllerEngine::stopAllTimers() {
 
888
    QMutableHashIterator<int, QPair<QString, bool> > i(m_timers);
 
889
    while (i.hasNext()) {
 
890
        i.next();
 
891
        stopTimer(i.key());
 
892
    }
 
893
}
 
894
 
 
895
/* -------- ------------------------------------------------------
 
896
   Purpose: Runs the appropriate script code on timer events
 
897
   Input:   -
 
898
   Output:  -
 
899
   -------- ------------------------------------------------------ */
 
900
void ControllerEngine::timerEvent(QTimerEvent *event) {
 
901
    int timerId = event->timerId();
 
902
 
 
903
    // See if this is a scratching timer
 
904
    if (m_scratchTimers.contains(timerId)) {
 
905
        scratchProcess(timerId);
 
906
        return;
 
907
    }
 
908
 
 
909
    if (!m_timers.contains(timerId)) {
 
910
        qWarning() << "Timer" << timerId << "fired but there's no function mapped to it!";
 
911
        return;
 
912
    }
 
913
 
 
914
    QPair<QString, bool> timerTarget = m_timers[timerId];
 
915
    if (timerTarget.second) {
 
916
        stopTimer(timerId);
 
917
    }
 
918
 
 
919
    internalExecute(timerTarget.first);
 
920
}
 
921
 
 
922
/* -------- ------------------------------------------------------
 
923
    Purpose: Enables scratching for relative controls
 
924
    Input:   Virtual deck to scratch,
 
925
             Number of intervals per revolution of the controller wheel,
 
926
             RPM for the track at normal speed (usually 33+1/3),
 
927
             (optional) alpha value for the filter,
 
928
             (optional) beta value for the filter
 
929
    Output:  -
 
930
    -------- ------------------------------------------------------ */
 
931
void ControllerEngine::scratchEnable(int deck, int intervalsPerRev, float rpm,
 
932
                                     float alpha, float beta, bool ramp) {
 
933
 
 
934
    // If we're already scratching this deck, override that with this request
 
935
    if (m_dx[deck]) {
 
936
        //qDebug() << "Already scratching deck" << deck << ". Overriding.";
 
937
        int timerId = m_scratchTimers.key(deck);
 
938
        killTimer(timerId);
 
939
        m_scratchTimers.remove(timerId);
 
940
    }
 
941
 
 
942
    // Controller resolution in intervals per second at normal speed (rev/min * ints/rev * mins/sec)
 
943
    float intervalsPerSecond = (rpm * intervalsPerRev)/60;
 
944
 
 
945
    m_dx[deck] = 1/intervalsPerSecond;
 
946
    m_intervalAccumulator[deck] = 0;
 
947
    m_ramp[deck] = false;
 
948
 
 
949
    QString group = QString("[Channel%1]").arg(deck);
 
950
 
 
951
    // Ramp
 
952
    float initVelocity = 0.0;   // Default to stopped
 
953
    ControlObjectThread *cot = getControlObjectThread(group, "scratch2_enable");
 
954
 
 
955
    // If ramping is desired, figure out the deck's current speed
 
956
    if (ramp) {
 
957
        // See if the deck is already being scratched
 
958
        if (cot != NULL && cot->get() == 1) {
 
959
            // If so, set the filter's initial velocity to the scratch speed
 
960
            cot = getControlObjectThread(group, "scratch2");
 
961
            if (cot != NULL) {
 
962
                initVelocity=cot->get();
 
963
            }
 
964
        } else {
 
965
            // See if deck is playing
 
966
            cot = getControlObjectThread(group, "play");
 
967
            if (cot != NULL && cot->get() == 1) {
 
968
                // If so, set the filter's initial velocity to the playback speed
 
969
                float rate = 0;
 
970
 
 
971
                cot = getControlObjectThread(group, "rate");
 
972
                if (cot != NULL) {
 
973
                    rate = cot->get();
 
974
                }
 
975
 
 
976
                cot = getControlObjectThread(group, "rateRange");
 
977
                if (cot != NULL) {
 
978
                    rate = rate * cot->get();
 
979
                }
 
980
 
 
981
                // Add 1 since the deck is playing
 
982
                rate++;
 
983
 
 
984
                // See if we're in reverse play
 
985
                cot = getControlObjectThread(group, "reverse");
 
986
 
 
987
                if (cot != NULL && cot->get() == 1) {
 
988
                    rate = -rate;
 
989
                }
 
990
                initVelocity = rate;
 
991
            }
 
992
        }
 
993
    }
 
994
 
 
995
    // Initialize pitch filter (0.001s = 1ms) (We're assuming the OS actually
 
996
    // gives us a 1ms timer below)
 
997
    if (alpha && beta) {
 
998
        m_pitchFilter[deck]->init(0.001, initVelocity, alpha, beta);
 
999
    } else {
 
1000
        // Use filter's defaults if not specified
 
1001
        m_pitchFilter[deck]->init(0.001, initVelocity);
 
1002
    }
 
1003
 
 
1004
    // 1ms is shortest possible, OS dependent
 
1005
    int timerId = startTimer(1);
 
1006
 
 
1007
    // Associate this virtual deck with this timer for later processing
 
1008
    m_scratchTimers[timerId] = deck;
 
1009
 
 
1010
    // Set scratch2_enable
 
1011
    cot = getControlObjectThread(group, "scratch2_enable");
 
1012
    if(cot != NULL) {
 
1013
        cot->slotSet(1);
 
1014
    }
 
1015
}
 
1016
 
 
1017
/* -------- ------------------------------------------------------
 
1018
    Purpose: Accumulates "ticks" of the controller wheel
 
1019
    Input:   Virtual deck to scratch, interval value (usually +1 or -1)
 
1020
    Output:  -
 
1021
    -------- ------------------------------------------------------ */
 
1022
void ControllerEngine::scratchTick(int deck, int interval) {
 
1023
    m_intervalAccumulator[deck] += interval;
 
1024
}
 
1025
 
 
1026
/* -------- ------------------------------------------------------
 
1027
    Purpose: Applies the accumulated movement to the track speed
 
1028
    Input:   ID of timer for this deck
 
1029
    Output:  -
 
1030
    -------- ------------------------------------------------------ */
 
1031
void ControllerEngine::scratchProcess(int timerId) {
 
1032
    int deck = m_scratchTimers[timerId];
 
1033
    PitchFilter* filter = m_pitchFilter[deck];
 
1034
    QString group = QString("[Channel%1]").arg(deck);
 
1035
 
 
1036
    if (!filter) {
 
1037
        qWarning() << "Scratch filter pointer is null on deck" << deck;
 
1038
        return;
 
1039
    }
 
1040
 
 
1041
    // Give the filter a data point:
 
1042
 
 
1043
    // If we're ramping to end scratching, feed fixed data
 
1044
    if (m_ramp[deck]) {
 
1045
        filter->observation(m_rampTo[deck]*0.001);
 
1046
    } else {
 
1047
        //  This will (and should) be 0 if no net ticks have been accumulated
 
1048
        //  (i.e. the wheel is stopped)
 
1049
        filter->observation(m_dx[deck] * m_intervalAccumulator[deck]);
 
1050
    }
 
1051
 
 
1052
    // Actually do the scratching
 
1053
    ControlObjectThread *cot = getControlObjectThread(group, "scratch2");
 
1054
    if(cot != NULL) {
 
1055
        cot->slotSet(filter->currentPitch());
 
1056
    }
 
1057
 
 
1058
    // Reset accumulator
 
1059
    m_intervalAccumulator[deck] = 0;
 
1060
 
 
1061
    // If we're ramping and the current pitch is really close to the rampTo
 
1062
    // value, end scratching
 
1063
 
 
1064
    //if (m_ramp[deck]) qDebug() << "Ramping to" << m_rampTo[deck] << " Currently at:" << filter->currentPitch();
 
1065
    if (m_ramp[deck] && fabs(m_rampTo[deck]-filter->currentPitch()) <= 0.00001) {
 
1066
        // Not ramping no mo'
 
1067
        m_ramp[deck] = false;
 
1068
 
 
1069
        // Clear scratch2_enable
 
1070
        cot = getControlObjectThread(group, "scratch2_enable");
 
1071
        if(cot != NULL) {
 
1072
            cot->slotSet(0);
 
1073
        }
 
1074
 
 
1075
        // Remove timer
 
1076
        killTimer(timerId);
 
1077
        m_scratchTimers.remove(timerId);
 
1078
 
 
1079
        m_dx[deck] = 0.0;
 
1080
    }
 
1081
}
 
1082
 
 
1083
/* -------- ------------------------------------------------------
 
1084
    Purpose: Stops scratching the specified virtual deck
 
1085
    Input:   Virtual deck to stop scratching
 
1086
    Output:  -
 
1087
    -------- ------------------------------------------------------ */
 
1088
void ControllerEngine::scratchDisable(int deck, bool ramp) {
 
1089
    QString group = QString("[Channel%1]").arg(deck);
 
1090
 
 
1091
    m_rampTo[deck] = 0.0;
 
1092
 
 
1093
    // If no ramping is desired, disable scratching immediately
 
1094
    if (!ramp) {
 
1095
        // Clear scratch2_enable
 
1096
        ControlObjectThread *cot = getControlObjectThread(group, "scratch2_enable");
 
1097
        if(cot != NULL) cot->slotSet(0);
 
1098
        // Can't return here because we need scratchProcess to stop the timer.
 
1099
        //  So it's still actually ramping, we just won't hear or see it.
 
1100
    } else {
 
1101
        // See if deck is playing
 
1102
        ControlObjectThread *cot = getControlObjectThread(group, "play");
 
1103
        if (cot != NULL && cot->get() == 1) {
 
1104
            // If so, set the target velocity to the playback speed
 
1105
            float rate=0;
 
1106
            // Get the pitch slider value
 
1107
            cot = getControlObjectThread(group, "rate");
 
1108
            if (cot != NULL) {
 
1109
                rate = cot->get();
 
1110
            }
 
1111
 
 
1112
            // Get the pitch slider directions
 
1113
            cot = getControlObjectThread(group, "rate_dir");
 
1114
            if (cot != NULL && cot->get() == -1) {
 
1115
                rate = -rate;
 
1116
            }
 
1117
 
 
1118
            // Multiply by the pitch range
 
1119
            cot = getControlObjectThread(group, "rateRange");
 
1120
            if (cot != NULL) {
 
1121
                rate = rate * cot->get();
 
1122
            }
 
1123
 
 
1124
            // Add 1 since the deck is playing
 
1125
            rate++;
 
1126
 
 
1127
            // See if we're in reverse play
 
1128
            cot = getControlObjectThread(group, "reverse");
 
1129
            if (cot != NULL && cot->get() == 1) {
 
1130
                rate = -rate;
 
1131
            }
 
1132
 
 
1133
            m_rampTo[deck] = rate;
 
1134
        }
 
1135
    }
 
1136
 
 
1137
    m_ramp[deck] = true;    // Activate the ramping in scratchProcess()
 
1138
}
 
1139
 
 
1140
/*  -------- ------------------------------------------------------
 
1141
    Purpose: [En/dis]ables soft-takeover status for a particular ControlObject
 
1142
    Input:   ControlObject group and key values,
 
1143
                whether to set the soft-takeover status or not
 
1144
    Output:  -
 
1145
    -------- ------------------------------------------------------ */
 
1146
void ControllerEngine::softTakeover(QString group, QString name, bool set) {
 
1147
    ControlObject* pControl = ControlObject::getControl(ConfigKey(group, name));
 
1148
    if (!pControl) {
 
1149
        return;
 
1150
    }
 
1151
    if (set) {
 
1152
        m_st.enable(pControl);
 
1153
    } else {
 
1154
        m_st.disable(pControl);
 
1155
    }
 
1156
}