• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mcallegari / qlcplus / 13633248611

03 Mar 2025 02:31PM UTC coverage: 31.871% (+0.4%) from 31.5%
13633248611

push

github

web-flow
actions: add chrpath to profile

14689 of 46089 relevant lines covered (31.87%)

26426.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

37.37
/engine/src/script.cpp
1
/*
2
  Q Light Controller Plus
3
  script.cpp
4

5
  Copyright (C) Heikki Junnila
6
                Massimo Callegari
7

8
  Licensed under the Apache License, Version 2.0 (the "License");
9
  you may not use this file except in compliance with the License.
10
  You may obtain a copy of the License at
11

12
      http://www.apache.org/licenses/LICENSE-2.0.txt
13

14
  Unless required by applicable law or agreed to in writing, software
15
  distributed under the License is distributed on an "AS IS" BASIS,
16
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
  See the License for the specific language governing permissions and
18
  limitations under the License.
19
*/
20

21
#include <QXmlStreamReader>
22
#include <QXmlStreamWriter>
23
#if !defined(Q_OS_IOS)
24
#include <QProcess>
25
#endif
26
#include <QDebug>
27
#include <QUrl>
28
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
29
#include <QRandomGenerator>
30
#endif
31
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
32
#include <QRegularExpression>
33
#endif
34

35
#include "genericfader.h"
36
#include "fadechannel.h"
37
#include "mastertimer.h"
38
#include "universe.h"
39
#include "script.h"
40
#include "doc.h"
41

42
#define KXMLQLCScriptCommand "Command"
43

44
const QString Script::stopOnExitCmd = QString("stoponexit");
45
const QString Script::startFunctionCmd = QString("startfunction");
46
const QString Script::stopFunctionCmd = QString("stopfunction");
47
const QString Script::blackoutCmd = QString("blackout");
48

49
const QString Script::waitCmd = QString("wait");
50
const QString Script::waitKeyCmd = QString("waitkey");
51
const QString Script::waitFunctionStartCmd = QString("waitfunctionstart");
52
const QString Script::waitFunctionStopCmd = QString("waitfunctionstop");
53

54
const QString Script::setFixtureCmd = QString("setfixture");
55
const QString Script::systemCmd = QString("systemcommand");
56

57
const QString Script::labelCmd = QString("label");
58
const QString Script::jumpCmd = QString("jump");
59

60
const QString Script::blackoutOn = QString("on");
61
const QString Script::blackoutOff = QString("off");
62

63
const QStringList knownKeywords(QStringList() << "ch" << "val" << "arg");
64

65
/****************************************************************************
66
 * Initialization
67
 ****************************************************************************/
68

69
Script::Script(Doc* doc) : Function(doc, Function::ScriptType)
2✔
70
    , m_currentCommand(0)
2✔
71
    , m_waitCount(0)
2✔
72
    , m_waitFunction(NULL)
2✔
73
{
74
    setName(tr("New Script"));
2✔
75
}
2✔
76

77
Script::~Script()
3✔
78
{
79
}
3✔
80

81
QIcon Script::getIcon() const
×
82
{
83
    return QIcon(":/script.png");
×
84
}
85

86
quint32 Script::totalDuration()
×
87
{
88
    quint32 totalDuration = 0;
89

90
    for (int i = 0; i < m_lines.count(); i++)
×
91
    {
92
        QList <QStringList> tokens = m_lines[i];
×
93
        if (tokens.isEmpty() || tokens[0].size() < 2)
×
94
            continue;
95

96
        if (tokens[0][0] == Script::waitCmd)
×
97
        {
98
            bool ok = false;
×
99
            quint32 waitTime = getValueFromString(tokens[0][1], &ok);
×
100
            if (ok == true)
×
101
                totalDuration += waitTime;
×
102
        }
103
    }
×
104

105
    return totalDuration;
×
106
}
107

108
Function* Script::createCopy(Doc* doc, bool addToDoc)
×
109
{
110
    Q_ASSERT(doc != NULL);
111

112
    Function* copy = new Script(doc);
×
113
    if (copy->copyFrom(this) == false)
×
114
    {
115
        delete copy;
×
116
        copy = NULL;
117
    }
118
    if (addToDoc == true && doc->addFunction(copy) == false)
×
119
    {
120
        delete copy;
×
121
        copy = NULL;
122
    }
123

124
    return copy;
×
125
}
126

127
bool Script::copyFrom(const Function* function)
×
128
{
129
    const Script* script = qobject_cast<const Script*> (function);
130
    if (script == NULL)
×
131
        return false;
132

133
    setData(script->data());
×
134

135
    return Function::copyFrom(function);
×
136
}
137

138
/****************************************************************************
139
 * Script data
140
 ****************************************************************************/
141

142
bool Script::setData(const QString& str)
1✔
143
{
144
    m_data = str;
1✔
145
    m_syntaxErrorLines.clear();
1✔
146

147
    // Construct individual code lines from the data
148
    m_lines.clear();
1✔
149
    if (m_data.isEmpty() == false)
1✔
150
    {
151
        int i = 1;
1✔
152
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
153
        QStringList lines = m_data.split(QRegularExpression("(\\r\\n|\\n\\r|\\r|\\n)"));
154
#else
155
        QStringList lines = m_data.split(QRegExp("(\r\n|\n\r|\r|\n)"));
2✔
156
#endif
157
        foreach (QString line, lines)
15✔
158
        {
159
            bool ok = false;
14✔
160
            if (line.isEmpty() == false)
14✔
161
            {
162
                m_lines << tokenizeLine(line + QString("\n"), &ok);
26✔
163
                if (ok == false)
13✔
164
                    m_syntaxErrorLines.append(i);
2✔
165
            }
166
            i++;
14✔
167
        }
14✔
168
    }
169

170
    scanForLabels();
1✔
171

172
    return true;
1✔
173
}
174

175
void Script::scanForLabels()
1✔
176
{
177
    // Map all labels to their individual line numbers for fast jumps
178
    m_labels.clear();
1✔
179
    for (int i = 0; i < m_lines.size(); i++)
14✔
180
    {
181
        QList <QStringList> line = m_lines[i];
13✔
182
        if (line.isEmpty() == false &&
13✔
183
            line.first().size() == 2 && line.first()[0] == Script::labelCmd)
25✔
184
        {
185
            m_labels[line.first()[1]] = i;
1✔
186
            qDebug() << QString("Map label '%1' to line '%2'").arg(line.first()[1]).arg(i);
187
        }
188
    }
13✔
189
}
1✔
190

191
bool Script::appendData(const QString &str)
1✔
192
{
193
    m_data.append(str + QString("\n"));
1✔
194
    m_lines << tokenizeLine(str + QString("\n"));
2✔
195

196
    return true;
1✔
197
}
198

199
QString Script::data() const
×
200
{
201
    return m_data;
×
202
}
203

204
QStringList Script::dataLines() const
×
205
{
206
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
207
    QStringList result = m_data.split(QRegularExpression("(\\r\\n|\\n\\r|\\r|\\n)"));
208
#else
209
    QStringList result = m_data.split(QRegExp("(\r\n|\n\r|\r|\n)"));
×
210
#endif
211
    while (result.count() && result.last().isEmpty())
×
212
        result.takeLast();
×
213

214
    return result;
×
215
}
216

217
QList<quint32> Script::functionList() const
7✔
218
{
219
    QList<quint32> list;
220

221
    for (int i = 0; i < m_lines.count(); i++)
14✔
222
    {
223
        QList <QStringList> tokens = m_lines[i];
7✔
224
        if (tokens.isEmpty() == true)
7✔
225
            continue;
226

227
        if (tokens[0].size() >= 2 && tokens[0][0] == Script::startFunctionCmd)
14✔
228
        {
229
            list.append(tokens[0][1].toUInt());
7✔
230
            list.append(i);
7✔
231
        }
232
    }
7✔
233

234
    return list;
7✔
235
}
×
236

237
QList<quint32> Script::fixtureList() const
×
238
{
239
    QList<quint32> list;
240

241
    for (int i = 0; i < m_lines.count(); i++)
×
242
    {
243
        QList <QStringList> tokens = m_lines[i];
×
244
        if (tokens.isEmpty() == true)
×
245
            continue;
246

247
        if (tokens[0].size() >= 2 && tokens[0][0] == Script::setFixtureCmd)
×
248
        {
249
            list.append(tokens[0][1].toUInt());
×
250
            list.append(i);
×
251
        }
252
    }
×
253

254
    return list;
×
255
}
×
256

257
QList<int> Script::syntaxErrorsLines()
×
258
{
259
    return m_syntaxErrorLines;
×
260
}
261

262
/****************************************************************************
263
 * Load & Save
264
 ****************************************************************************/
265

266
bool Script::loadXML(QXmlStreamReader &root)
×
267
{
268
    if (root.name() != KXMLQLCFunction)
×
269
    {
270
        qWarning() << Q_FUNC_INFO << "Function node not found";
×
271
        return false;
×
272
    }
273

274
    if (root.attributes().value(KXMLQLCFunctionType).toString() != typeToString(Function::ScriptType))
×
275
    {
276
        qWarning() << Q_FUNC_INFO << root.attributes().value(KXMLQLCFunctionType).toString()
×
277
                   << "is not a script";
×
278
        return false;
×
279
    }
280

281
    /* Load script contents */
282
    while (root.readNextStartElement())
×
283
    {
284
        if (root.name() == KXMLQLCFunctionSpeed)
×
285
        {
286
            loadXMLSpeed(root);
×
287
        }
288
        else if (root.name() == KXMLQLCFunctionDirection)
×
289
        {
290
            loadXMLDirection(root);
×
291
        }
292
        else if (root.name() == KXMLQLCFunctionRunOrder)
×
293
        {
294
            loadXMLRunOrder(root);
×
295
        }
296
        else if (root.name() == KXMLQLCScriptCommand)
×
297
        {
298
            appendData(QUrl::fromPercentEncoding(root.readElementText().toUtf8()));
×
299
            //appendData(tag.text().toUtf8());
300
        }
301
        else
302
        {
303
            qWarning() << Q_FUNC_INFO << "Unknown script tag:" << root.name();
×
304
            root.skipCurrentElement();
×
305
        }
306
    }
307

308
    scanForLabels();
×
309

310
    return true;
×
311
}
312

313
bool Script::saveXML(QXmlStreamWriter *doc)
×
314
{
315
    Q_ASSERT(doc != NULL);
316

317
    /* Function tag */
318
    doc->writeStartElement(KXMLQLCFunction);
×
319

320
    /* Common attributes */
321
    saveXMLCommon(doc);
×
322

323
    /* Speed */
324
    saveXMLSpeed(doc);
×
325

326
    /* Direction */
327
    saveXMLDirection(doc);
×
328

329
    /* Run order */
330
    saveXMLRunOrder(doc);
×
331

332
    /* Contents */
333
    foreach (QString cmd, dataLines())
×
334
    {
335
        doc->writeTextElement(KXMLQLCScriptCommand, QUrl::toPercentEncoding(cmd));
×
336
    }
×
337

338
    /* End the <Function> tag */
339
    doc->writeEndElement();
×
340

341
    return true;
×
342
}
343

344
/****************************************************************************
345
 * Running
346
 ****************************************************************************/
347

348
void Script::preRun(MasterTimer *timer)
1✔
349
{
350
    // Reset
351
    m_waitCount = 0;
1✔
352
    m_waitFunction = NULL;
1✔
353
    m_currentCommand = 0;
1✔
354
    m_startedFunctions.clear();
1✔
355
    m_stopOnExit = true;
1✔
356

357
    Function::preRun(timer);
1✔
358
}
1✔
359

360
void Script::write(MasterTimer *timer, QList<Universe *> universes)
1✔
361
{
362
    if (stopped() || isPaused())
1✔
363
        return;
×
364

365
    incrementElapsed();
1✔
366

367
    if (waiting() == false)
1✔
368
    {
369
        // Not currently waiting for anything. Free to proceed to next command.
370
        while (m_currentCommand < m_lines.size() && stopped() == false)
4✔
371
        {
372
            bool continueLoop = executeCommand(m_currentCommand, timer, universes);
4✔
373
            m_currentCommand++;
4✔
374
            if (continueLoop == false)
4✔
375
                break; // Executed command told to skip to the next cycle
376
        }
377

378
        // In case a wait command is the last command, don't stop the script prematurely
379
        if (m_currentCommand >= m_lines.size() && m_waitCount == 0 && m_waitFunction == NULL)
1✔
380
            stop(FunctionParent::master());
×
381
    }
382

383
    // Handle GenericFader tasks (setltp/sethtp/setfixture)
384
    //if (m_fader != NULL)
385
    //    m_fader->write(universes);
386
}
387

388
void Script::postRun(MasterTimer *timer, QList<Universe *> universes)
1✔
389
{
390
    // Stop all functions started by this script
391
    foreach (Function *function, m_startedFunctions)
1✔
392
        function->stop(FunctionParent::master());
×
393

394
    m_startedFunctions.clear();
1✔
395

396
    dismissAllFaders();
1✔
397

398
    Function::postRun(timer, universes);
1✔
399
}
1✔
400

401
bool Script::waiting()
1✔
402
{
403
    if (m_waitCount > 0)
1✔
404
    {
405
        // Still waiting for at least one cycle.
406
        m_waitCount--;
×
407
        return true;
×
408
    }
409
    else if (m_waitFunction != NULL)
1✔
410
    {
411
        // Still waiting for the function to start/stop.
412
        return true;
×
413
    }
414
    // Not waiting.
415
    return false;
416
}
417

418
quint32 Script::getValueFromString(QString str, bool *ok)
×
419
{
420
    if (str.startsWith("random") == false)
×
421
    {
422
        *ok = true;
×
423
        return Function::stringToSpeed(str);
×
424
    }
425

426
    QString strippedStr = str.remove("random(");
×
427
    strippedStr.remove(")");
×
428
    if (strippedStr.contains(",") == false)
×
429
        return -1;
430

431
    QStringList valList = strippedStr.split(",");
×
432
    int min = Function::stringToSpeed(valList.at(0));
×
433
    int max = Function::stringToSpeed(valList.at(1));
×
434

435
    *ok = true;
×
436
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
437
    return qrand() % ((max + 1) - min) + min;
438
#else
439
      return QRandomGenerator::global()->generate() % ((max + 1) - min) + min;
×
440
#endif
441
}
×
442

443
bool Script::executeCommand(int index, MasterTimer* timer, QList<Universe *> universes)
4✔
444
{
445
    if (index < 0 || index >= m_lines.size())
4✔
446
    {
447
        qWarning() << "Invalid command index:" << index;
×
448
        return false;
×
449
    }
450

451
    QList <QStringList> tokens = m_lines[index];
4✔
452
    if (tokens.isEmpty() == true)
4✔
453
        return true; // Empty line
454

455
    bool continueLoop = true;
456
    QString error;
457
    if (tokens[0].size() < 2)
4✔
458
    {
459
        error = QString("Syntax error");
1✔
460
    }
461
    else if (tokens[0][0] == Script::stopOnExitCmd)
3✔
462
    {
463
        error = handleStopOnExit(tokens);
×
464
    }
465
    else if (tokens[0][0] == Script::startFunctionCmd)
3✔
466
    {
467
        error = handleStartFunction(tokens, timer);
1✔
468
    }
469
    else if (tokens[0][0] == Script::stopFunctionCmd)
2✔
470
    {
471
        error = handleStopFunction(tokens);
1✔
472
    }
473
    else if (tokens[0][0] == Script::blackoutCmd)
1✔
474
    {
475
        error = handleBlackout(tokens);
×
476
        continueLoop = false;
477
    }
478
    else if (tokens[0][0] == Script::waitCmd)
1✔
479
    {
480
        // Waiting should break out of the execution loop to prevent skipping
481
        // straight to the next command. If there is no error in wait parsing,
482
        // we must wait at least one cycle.
483
        error = handleWait(tokens);
×
484
        if (error.isEmpty() == true)
×
485
            continueLoop = false;
486
    }
487
    else if (tokens[0][0] == Script::waitKeyCmd)
1✔
488
    {
489
        // Waiting for a key should break out of the execution loop to prevent
490
        // skipping straight to the next command. If there is no error in waitkey
491
        // parsing,we must wait at least one cycle.
492
        error = handleWaitKey(tokens);
1✔
493
        if (error.isEmpty() == true)
1✔
494
            continueLoop = false;
495
    }
496
    else if (tokens[0][0] == Script::waitFunctionStartCmd)
×
497
    {
498
        // Waiting for a funcion should break out of the execution loop to
499
        // prevent skipping straight to the next command. If there is no error
500
        // in waitfunctionstart parsing, we must wait at least one cycle.
501
        error = handleWaitFunction(tokens, true);
×
502
        if (error.isEmpty() == true)
×
503
            continueLoop = false;
504
    }
505
    else if (tokens[0][0] == Script::waitFunctionStopCmd)
×
506
    {
507
        // Waiting for a funcion should break out of the execution loop to
508
        // prevent skipping straight to the next command. If there is no error
509
        // in waitfunctionstop parsing, we must wait at least one cycle.
510
        error = handleWaitFunction(tokens, false);
×
511
        if (error.isEmpty() == true)
×
512
            continueLoop = false;
513
    }
514
    else if (tokens[0][0] == Script::setFixtureCmd)
×
515
    {
516
        error = handleSetFixture(tokens, universes);
×
517
    }
518
    else if (tokens[0][0] == Script::systemCmd)
×
519
    {
520
        error = handleSystemCommand(tokens);
×
521
    }
522
    else if (tokens[0][0] == Script::labelCmd)
×
523
    {
524
        error = handleLabel(tokens);
×
525
    }
526
    else if (tokens[0][0] == Script::jumpCmd)
×
527
    {
528
        // Jumping can cause an infinite non-waiting loop, causing starvation
529
        // among other functions. Therefore, the script must relinquish its
530
        // time slot after each jump. If there is no error in jumping, the jump
531
        // must have happened.
532
        error = handleJump(tokens);
×
533
        if (error.isEmpty() == true)
×
534
            continueLoop = false;
535
    }
536
    else
537
    {
538
        error = QString("Unknown command: %1").arg(tokens[0][0]);
×
539
    }
540

541
    if (error.isEmpty() == false)
4✔
542
        qWarning() << QString("Script:%1, line:%2, error:%3").arg(name()).arg(index).arg(error);
6✔
543

544
    return continueLoop;
545
}
4✔
546

547
QString Script::handleStopOnExit(const QList<QStringList>& tokens)
×
548
{
549
    qDebug() << Q_FUNC_INFO;
550

551
    if (tokens.size() > 1)
×
552
        return QString("Too many arguments");
×
553

554
    bool flag = QVariant(tokens[0][1]).toBool();
×
555
    
556
    m_stopOnExit = flag;
×
557

558
    return QString();
559
}
560

561
QString Script::handleStartFunction(const QList<QStringList>& tokens, MasterTimer* timer)
1✔
562
{
563
    qDebug() << Q_FUNC_INFO;
564

565
    if (tokens.size() > 1)
1✔
566
        return QString("Too many arguments");
×
567

568
    bool ok = false;
1✔
569
    quint32 id = tokens[0][1].toUInt(&ok);
1✔
570
    if (ok == false)
1✔
571
        return QString("Invalid function ID: %1").arg(tokens[0][1]);
×
572

573
    Doc* doc = qobject_cast<Doc*> (parent());
574
    Q_ASSERT(doc != NULL);
575

576
    Function* function = doc->function(id);
1✔
577
    if (function != NULL)
1✔
578
    {
579
        function->start(timer, FunctionParent::master());
×
580

581
        if (m_stopOnExit)
×
582
        {
583
            m_startedFunctions << function;
×
584
        }
585
        return QString();
586
    }
587
    else
588
    {
589
        return QString("No such function (ID %1)").arg(id);
2✔
590
    }
591
}
592

593
QString Script::handleStopFunction(const QList <QStringList>& tokens)
1✔
594
{
595
    qDebug() << Q_FUNC_INFO;
596

597
    if (tokens.size() > 1)
1✔
598
        return QString("Too many arguments");
×
599

600
    bool ok = false;
1✔
601
    quint32 id = tokens[0][1].toUInt(&ok);
1✔
602
    if (ok == false)
1✔
603
        return QString("Invalid function ID: %1").arg(tokens[0][1]);
×
604

605
    Doc *doc = qobject_cast<Doc*> (parent());
606
    Q_ASSERT(doc != NULL);
607

608
    Function *function = doc->function(id);
1✔
609
    if (function != NULL)
1✔
610
    {
611
        function->stop(FunctionParent::master());
×
612

613
        m_startedFunctions.removeAll(function);
×
614
        return QString();
615
    }
616
    else
617
    {
618
        return QString("No such function (ID %1)").arg(id);
2✔
619
    }
620
}
621

622
QString Script::handleBlackout(const QList <QStringList>& tokens)
×
623
{
624
    qDebug() << Q_FUNC_INFO;
625

626
    if (tokens.size() > 1)
×
627
        return QString("Too many arguments");
×
628

629
    InputOutputMap::BlackoutRequest request = InputOutputMap::BlackoutRequestNone;
630

631
    if (tokens[0][1] == blackoutOn)
×
632
    {
633
        request = InputOutputMap::BlackoutRequestOn;
634
    }
635
    else if (tokens[0][1] == blackoutOff)
×
636
    {
637
        request = InputOutputMap::BlackoutRequestOff;
638
    }
639
    else
640
    {
641
        return QString("Invalid argument: %1").arg(tokens[0][1]);
×
642
    }
643

644
    Doc* doc = qobject_cast<Doc*> (parent());
645
    Q_ASSERT(doc != NULL);
646

647
    doc->inputOutputMap()->requestBlackout(request);
×
648

649
    return QString();
650
}
651

652
QString Script::handleWait(const QList<QStringList>& tokens)
×
653
{
654
    qDebug() << Q_FUNC_INFO;
655

656
    if (tokens.size() > 2)
×
657
        return QString("Too many arguments");
×
658

659
    bool ok = false;
×
660
    uint time = getValueFromString(tokens[0][1], &ok);
×
661

662
    qDebug() << "Wait time:" << time;
663

664
    m_waitCount = time / MasterTimer::tick();
×
665

666
    return QString();
667
}
668

669
QString Script::handleWaitKey(const QList<QStringList>& tokens)
1✔
670
{
671
    qDebug() << Q_FUNC_INFO << tokens;
672

673
    if (tokens.size() > 1)
1✔
674
        return QString("Too many arguments");
×
675

676
    QString key = QString(tokens[0][1]).remove("\"");
2✔
677
    qDebug() << "Ought to wait for" << key;
678

679
    return QString();
680
}
1✔
681

682
QString Script::handleWaitFunction(const QList<QStringList> &tokens, bool start)
×
683
{
684
    qDebug() << Q_FUNC_INFO << tokens;
685

686
    if (tokens.size() > 1)
×
687
        return QString("Too many arguments");
×
688

689
    bool ok = false;
×
690
    quint32 id = tokens[0][1].toUInt(&ok);
×
691
    if (ok == false)
×
692
        return QString("Invalid function ID: %1").arg(tokens[0][1]);
×
693

694
    Doc *doc = qobject_cast<Doc *>(parent());
695
    Q_ASSERT(doc != NULL);
696

697
    Function *function = doc->function(id);
×
698
    if (function == NULL)
×
699
    {
700
        return QString("No such function (ID %1)").arg(id);
×
701
    }
702

703
    if (start)
×
704
    {
705
        if (!function->isRunning())
×
706
        {
707
            m_waitFunction = function;
×
708
            connect(m_waitFunction, SIGNAL(running(quint32)), this, SLOT(slotWaitFunctionStarted(quint32)));
×
709
        }
710
    }
711
    else
712
    {
713
        if (!function->stopped())
×
714
        {
715
            m_waitFunction = function;
×
716
            connect(m_waitFunction, SIGNAL(stopped(quint32)), this, SLOT(slotWaitFunctionStopped(quint32)));
×
717
        }
718
    }
719

720
    return QString();
721
}
722

723
void Script::slotWaitFunctionStarted(quint32 fid)
×
724
{
725
    if (m_waitFunction != NULL && m_waitFunction->id() == fid) {
×
726
        disconnect(m_waitFunction, SIGNAL(running(quint32)), this, SLOT(slotWaitFunctionStarted(quint32)));
×
727
        m_waitFunction = NULL;
×
728
    }
729
}
×
730

731
void Script::slotWaitFunctionStopped(quint32 fid)
×
732
{
733
    if (m_waitFunction != NULL && m_waitFunction->id() == fid) {
×
734
        disconnect(m_waitFunction, SIGNAL(stopped(quint32)), this, SLOT(slotWaitFunctionStopped(quint32)));
×
735
        m_startedFunctions.removeAll(m_waitFunction);
×
736
        m_waitFunction = NULL;
×
737
    }
738
}
×
739

740
QString Script::handleSetFixture(const QList<QStringList>& tokens, QList<Universe *> universes)
×
741
{
742
    qDebug() << Q_FUNC_INFO;
743

744
    if (tokens.size() > 4)
×
745
        return QString("Too many arguments");
×
746

747
    bool ok = false;
×
748
    quint32 id = 0;
749
    quint32 ch = 0;
750
    uchar value = 0;
751
    double time = 0;
752

753
    id = getValueFromString(tokens[0][1], &ok);
×
754
    if (ok == false)
×
755
        return QString("Invalid fixture (ID: %1)").arg(tokens[0][1]);
×
756

757
    for (int i = 1; i < tokens.size(); i++)
×
758
    {
759
        QStringList list = tokens[i];
760
        list[0] = list[0].toLower().trimmed();
×
761
        if (list.size() == 2)
×
762
        {
763
            ok = false;
×
764
            if (list[0] == "val" || list[0] == "value")
×
765
                value = uchar(getValueFromString(list[1], &ok));
×
766
            else if (list[0] == "ch" || list[0] == "channel")
×
767
                ch = getValueFromString(list[1], &ok);
×
768
            else if (list[0] == "time")
×
769
                time = getValueFromString(list[1], &ok);
×
770
            else
771
                return QString("Unrecognized keyword: %1").arg(list[0]);
×
772

773
            if (ok == false)
×
774
                return QString("Invalid value (%1) for keyword: %2").arg(list[1]).arg(list[0]);
×
775
        }
776
    }
777

778
    Doc *doc = qobject_cast<Doc*> (parent());
779
    Q_ASSERT(doc != NULL);
780

781
    Fixture *fxi = doc->fixture(id);
×
782
    if (fxi != NULL)
×
783
    {
784
        if (ch < fxi->channels())
×
785
        {
786
            int address = fxi->address() + ch;
×
787
            if (address < 512)
×
788
            {
789
                quint32 universe = fxi->universe();
×
790
                QSharedPointer<GenericFader> fader = m_fadersMap.value(universe, QSharedPointer<GenericFader>());
×
791
                if (fader.isNull())
×
792
                {
793
                    fader = universes[universe]->requestFader();
×
794
                    fader->adjustIntensity(getAttributeValue(Intensity));
×
795
                    fader->setBlendMode(blendMode());
×
796
                    fader->setParentFunctionID(this->id());
×
797
                    fader->setName(name());
×
798
                    m_fadersMap[universe] = fader;
×
799
                }
800

801
                FadeChannel *fc = fader->getChannelFader(doc, universes[universe], fxi->id(), ch);
×
802
                fc->setTarget(value);
×
803
                fc->setFadeTime(time);
×
804

805
                return QString();
806
            }
807
            else
808
            {
809
                return QString("Invalid address: %1").arg(address);
×
810
            }
811
        }
812
        else
813
        {
814
            return QString("Fixture (%1) has no channel number %2").arg(fxi->name()).arg(ch);
×
815
        }
816
    }
817
    else
818
    {
819
        return QString("No such fixture (ID: %1)").arg(id);
×
820
    }
821
}
822

823
QString Script::handleSystemCommand(const QList<QStringList> &tokens)
×
824
{
825
    qDebug() << Q_FUNC_INFO;
826

827
    QString programName = tokens[0][1];
828
    QStringList programArgs;
829
    for (int i = 1; i < tokens.size(); i++)
×
830
        programArgs << tokens[i][1];
831
#if !defined(Q_OS_IOS)
832
    QProcess *newProcess = new QProcess();
×
833

834
    // startDetached() enables to delete QProcess object without killing actual process
835
    qint64 pid;
836
    newProcess->setProgram(programName);
×
837
    newProcess->setArguments(programArgs);
×
838
    newProcess->startDetached(&pid);
×
839
    delete newProcess;
×
840
#endif
841
    return QString();
×
842
}
×
843

844
QString Script::handleLabel(const QList<QStringList>& tokens)
×
845
{
846
    // A label just exists. Not much to do here.
847
    qDebug() << Q_FUNC_INFO;
848

849
    if (tokens.size() > 1)
×
850
        return QString("Too many arguments");
×
851

852
    qDebug() << QString("Found label '%1'").arg(tokens[0][1]);
853

854
    return QString();
855
}
856

857
QString Script::handleJump(const QList<QStringList>& tokens)
×
858
{
859
    qDebug() << Q_FUNC_INFO;
860

861
    if (tokens.size() > 1)
×
862
        return QString("Too many arguments");
×
863

864
    if (m_labels.contains(tokens[0][1]) == true)
×
865
    {
866
        int lineNumber = m_labels[tokens[0][1]];
×
867
        Q_ASSERT(lineNumber >= 0 && lineNumber < m_lines.size());
868
        m_currentCommand = lineNumber;
×
869

870
        // cleanup m_startedFunctions to avoid infinite growth
871
        QList<Function*>::iterator it = m_startedFunctions.begin();
×
872
        while (it != m_startedFunctions.end()) {
×
873
            if ((*it)->stopped())
×
874
                it = m_startedFunctions.erase(it);
×
875
            else
876
                ++it;
877
        }
878

879
        return QString();
880
    }
881
    else
882
    {
883
        return QString("No such label: %1").arg(tokens[0][1]);
×
884
    }
885
}
886

887
QList <QStringList> Script::tokenizeLine(const QString& str, bool* ok)
14✔
888
{
889
    QList<QStringList> tokens;
890
    QString keyword;
891
    QString value;
892

893
    if (ok != NULL)
14✔
894
        *ok = true; // in case, this is set to false afterwards
13✔
895

896
    if (str.simplified().startsWith("//") == true || str.simplified().isEmpty() == true)
27✔
897
    {
898
        tokens << QStringList(); // Return an empty string list for commented lines
1✔
899
    }
900
    else
901
    {
902
        // Truncate everything after the first comment sign
903
        QString line = str;
904
        int left = 0;
905

906
        while (left != -1)
39✔
907
        {
908
            left = line.indexOf("//", left);
13✔
909
            if (left != -1)
13✔
910
            {
911
                // if we stumbled into a URL like http:// or ftp://
912
                // then it's not a comment !
913
                if (line.at(left - 1) != ':')
×
914
                    line.truncate(left);
×
915
                left += 2;
×
916
            }
917
        }
918

919
        left = 0;
920
        while (left < line.length())
27✔
921
        {
922
            // Find the next colon to get the keyword
923
            int right = line.indexOf(":", left);
16✔
924
            if (right == -1)
16✔
925
            {
926
                qDebug() << "Syntax error:" << line.mid(left);
927
                if (ok != NULL)
1✔
928
                    *ok = false;
1✔
929
                break;
930
            }
931
            else
932
            {
933
                // Keyword found
934
                keyword = line.mid(left, right - left);
15✔
935
                left = right + 1;
15✔
936
            }
937

938
            // Try to see if there is a value between quotes
939
            int quoteleft = -1;
940
            if (line.mid(left, 1) == "\"")
15✔
941
                quoteleft = left + 1;
2✔
942
            if (quoteleft != -1)
2✔
943
            {
944
                int quoteright = line.indexOf("\"", quoteleft + 1);
2✔
945
                if (quoteright != -1)
2✔
946
                {
947
                    // Don't include the "" in the string
948
                    value = line.mid(quoteleft, quoteright - quoteleft);
2✔
949
                    left = quoteright + 2;
2✔
950
                }
951
                else
952
                {
953
                    qDebug() << "Syntax error:" << line.mid(quoteleft);
954
                    if (ok != NULL)
×
955
                        *ok = false;
×
956
                    break;
957
                }
958
            }
959
            else
960
            {
961
                // No quotes. Find the next whitespace.
962
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
963
                right = line.indexOf(QRegularExpression("\\s"), left);
964
#else
965
                right = line.indexOf(QRegExp("\\s"), left);
13✔
966
#endif
967
                if (right == -1)
13✔
968
                {
969
                    qDebug() << "Syntax error:" << line.mid(left);
970
                    if (ok != NULL)
×
971
                        *ok = false;
×
972
                    break;
973
                }
974
                else
975
                {
976
                    // Value found
977
                    value = line.mid(left, right - left);
13✔
978
                    left = right + 1;
13✔
979
                }
980
            }
981

982
            if (tokens.count() > 0 && knownKeywords.contains(keyword.trimmed()) == false)
17✔
983
            {
984
                qDebug() << "Syntax error. Unknown keyword detected:" << keyword.trimmed();
985
                if (ok != NULL)
1✔
986
                    *ok = false;
1✔
987
                break;
988
            }
989
            else
990
                tokens << (QStringList() << keyword.trimmed() << value.trimmed());
14✔
991
        }
992
    }
13✔
993

994
    qDebug() << "Tokens:" << tokens;
995

996
    return tokens;
14✔
997
}
14✔
998

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc