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

mcallegari / qlcplus / 18357067171

08 Oct 2025 08:16PM UTC coverage: 34.26% (+2.2%) from 32.066%
18357067171

push

github

mcallegari
Merge branch 'master' into filedialog

1282 of 4424 new or added lines in 152 files covered. (28.98%)

1342 existing lines in 152 files now uncovered.

17704 of 51675 relevant lines covered (34.26%)

19430.31 hits per line

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

36.59
/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
        }
UNCOV
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)"));
2✔
154
#else
155
        QStringList lines = m_data.split(QRegExp("(\r\n|\n\r|\r|\n)"));
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);
13✔
163
                if (ok == false)
13✔
164
                    m_syntaxErrorLines.append(i);
2✔
165
            }
166
            i++;
14✔
167
        }
15✔
168
    }
1✔
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 &&
26✔
183
            line.first().size() == 2 && line.first()[0] == Script::labelCmd)
26✔
184
        {
185
            m_labels[line.first()[1]] = i;
1✔
186
            qDebug() << QString("Map label '%1' to line '%2'").arg(line.first()[1]).arg(i);
1✔
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"));
1✔
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)
NEW
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;
×
UNCOV
215
}
×
216

217
QList<quint32> Script::functionList() const
7✔
218
{
219
    QList<quint32> list;
7✔
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)
7✔
228
        {
229
            list.append(tokens[0][1].toUInt());
7✔
230
            list.append(i);
7✔
231
        }
232
    }
7✔
233

234
    return list;
7✔
UNCOV
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
        }
UNCOV
252
    }
×
253

254
    return list;
×
UNCOV
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

NEW
308
    scanForLabels();
×
309

UNCOV
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 */
NEW
333
    foreach (QString cmd, dataLines())
×
334
    {
335
        doc->writeTextElement(KXMLQLCScriptCommand, QUrl::toPercentEncoding(cmd));
×
UNCOV
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
1✔
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✔
UNCOV
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());
1✔
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.
NEW
412
        return true;
×
413
    }
414
    // Not waiting.
415
    return false;
1✔
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
UNCOV
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;
4✔
456
    QString error;
4✔
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
    {
NEW
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;
1✔
495
    }
NEW
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.
NEW
501
        error = handleWaitFunction(tokens, true);
×
NEW
502
        if (error.isEmpty() == true)
×
NEW
503
            continueLoop = false;
×
504
    }
NEW
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.
NEW
510
        error = handleWaitFunction(tokens, false);
×
NEW
511
        if (error.isEmpty() == true)
×
NEW
512
            continueLoop = false;
×
513
    }
UNCOV
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);
3✔
543

544
    return continueLoop;
4✔
545
}
4✔
546

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

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

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

NEW
558
    return QString();
×
559
}
560

561
QString Script::handleStartFunction(const QList<QStringList>& tokens, MasterTimer* timer)
1✔
562
{
563
    qDebug() << Q_FUNC_INFO;
1✔
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());
1✔
574
    Q_ASSERT(doc != NULL);
1✔
575

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

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

593
QString Script::handleStopFunction(const QList <QStringList>& tokens)
1✔
594
{
595
    qDebug() << Q_FUNC_INFO;
1✔
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());
1✔
606
    Q_ASSERT(doc != NULL);
1✔
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);
1✔
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;
1✔
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;
1✔
678

679
    return QString();
1✔
680
}
1✔
681

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

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

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

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

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

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

NEW
720
    return QString();
×
721
}
722

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

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

UNCOV
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
        }
UNCOV
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();
×
UNCOV
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();
×
UNCOV
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

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

UNCOV
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
NEW
871
        QList<Function*>::iterator it = m_startedFunctions.begin();
×
NEW
872
        while (it != m_startedFunctions.end()) {
×
NEW
873
            if ((*it)->stopped())
×
NEW
874
                it = m_startedFunctions.erase(it);
×
875
            else
NEW
876
                ++it;
×
877
        }
878

UNCOV
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;
14✔
890
    QString keyword;
14✔
891
    QString value;
14✔
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)
14✔
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;
13✔
904
        int left = 0;
13✔
905

906
        while (left != -1)
26✔
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;
13✔
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);
1✔
927
                if (ok != NULL)
1✔
928
                    *ok = false;
1✔
929
                break;
1✔
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;
15✔
940
            if (line.mid(left, 1) == "\"")
15✔
941
                quoteleft = left + 1;
2✔
942
            if (quoteleft != -1)
15✔
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);
13✔
964
#else
965
                right = line.indexOf(QRegExp("\\s"), left);
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)
15✔
983
            {
984
                qDebug() << "Syntax error. Unknown keyword detected:" << keyword.trimmed();
1✔
985
                if (ok != NULL)
1✔
986
                    *ok = false;
1✔
987
                break;
1✔
988
            }
989
            else
990
                tokens << (QStringList() << keyword.trimmed() << value.trimmed());
14✔
991
        }
992
    }
13✔
993

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

996
    return tokens;
28✔
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

© 2026 Coveralls, Inc