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

mcallegari / qlcplus / 12547752943

30 Dec 2024 02:17PM UTC coverage: 31.592% (-0.4%) from 31.949%
12547752943

push

github

web-flow
Merge pull request #1653 from mcallegari/stagecolors

RGB Matrix: allow to get/set multiple colors programmatically

72 of 893 new or added lines in 11 files covered. (8.06%)

12 existing lines in 7 files now uncovered.

14085 of 44584 relevant lines covered (31.59%)

26912.59 hits per line

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

77.44
/engine/src/rgbscript.cpp
1
/*
2
  Q Light Controller
3
  rgbscript.cpp
4

5
  Copyright (c) Heikki Junnila
6

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

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

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

20
#include <QXmlStreamReader>
21
#include <QXmlStreamWriter>
22
#include <QCoreApplication>
23
#include <QScriptEngine>
24
#include <QScriptValue>
25
#include <QStringList>
26
#include <QDebug>
27
#include <QFile>
28
#include <QSize>
29
#include <QDir>
30

31
#if defined(WIN32) || defined(Q_OS_WIN)
32
#   include <windows.h>
33
#else
34
#   include <unistd.h>
35
#endif
36

37
#include "rgbscript.h"
38
#include "rgbscriptscache.h"
39

40
QScriptEngine* RGBScript::s_engine = NULL;
41
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
42
  QMutex* RGBScript::s_engineMutex = NULL;
43
#else
44
  QRecursiveMutex* RGBScript::s_engineMutex = NULL;
45
#endif
46

47
/****************************************************************************
48
 * Initialization
49
 ****************************************************************************/
50

51
RGBScript::RGBScript(Doc * doc)
338✔
52
    : RGBAlgorithm(doc)
53
    , m_apiVersion(0)
338✔
54
{
55
}
338✔
56

57
RGBScript::RGBScript(const RGBScript& s)
70✔
58
    : RGBAlgorithm(s.doc())
59
    , m_fileName(s.m_fileName)
60
    , m_contents(s.m_contents)
61
    , m_apiVersion(0)
70✔
62
{
63
    evaluate();
70✔
64
    foreach (RGBScriptProperty cap, s.m_properties)
360✔
65
    {
66
        setProperty(cap.m_name, s.property(cap.m_name));
220✔
67
    }
68
}
70✔
69

70
RGBScript::~RGBScript()
93✔
71
{
72
}
93✔
73

74
RGBScript &RGBScript::operator=(const RGBScript &s)
1✔
75
{
76
    if (this != &s)
1✔
77
    {
78
        m_fileName = s.m_fileName;
1✔
79
        m_contents = s.m_contents;
1✔
80
        m_apiVersion = s.m_apiVersion;
1✔
81
        evaluate();
1✔
82
        foreach (RGBScriptProperty cap, s.m_properties)
3✔
83
        {
84
            setProperty(cap.m_name, s.property(cap.m_name));
2✔
85
        }
86
    }
87

88
    return *this;
1✔
89
}
90

91
bool RGBScript::operator==(const RGBScript& s) const
×
92
{
93
    if (this->fileName().isEmpty() == false && this->fileName() == s.fileName())
×
94
        return true;
95
    else
96
        return false;
×
97
}
98

99
RGBAlgorithm* RGBScript::clone() const
18✔
100
{
101
    RGBScript* script = new RGBScript(*this);
18✔
102
    return static_cast<RGBAlgorithm*> (script);
18✔
103
}
104

105
/****************************************************************************
106
 * Load & Evaluation
107
 ****************************************************************************/
108

109
bool RGBScript::load(const QDir& dir, const QString& fileName)
156✔
110
{
111
    // Create the script engine when it's first needed
112
    initEngine();
156✔
113

114
    QMutexLocker engineLocker(s_engineMutex);
156✔
115

116
    m_contents.clear();
156✔
117
    m_script = QScriptValue();
156✔
118
    m_rgbMap = QScriptValue();
156✔
119
    m_rgbMapStepCount = QScriptValue();
156✔
120
    m_rgbMapSetColors = QScriptValue();
156✔
121
    m_apiVersion = 0;
156✔
122

123
    m_fileName = fileName;
156✔
124
    QFile file(dir.absoluteFilePath(m_fileName));
468✔
125
    if (file.open(QIODevice::ReadOnly) == false)
156✔
126
    {
127
        qWarning() << "Unable to load RGB script" << m_fileName << "from" << dir.absolutePath();
×
128
        return false;
×
129
    }
130

131
    QTextStream stream(&file);
312✔
132
    m_contents = stream.readAll();
156✔
133
    file.close();
156✔
134

135
    QScriptSyntaxCheckResult result = QScriptEngine::checkSyntax(m_contents);
312✔
136
    if (result.state() == QScriptSyntaxCheckResult::Valid)
156✔
137
        return evaluate();
156✔
138
    else
139
    {
140
        qWarning() << m_fileName << "Error at line:" << result.errorLineNumber()
×
141
                   << ", column:" << result.errorColumnNumber()
×
142
                   << ":" << result.errorMessage();
×
143
        return false;
×
144
    }
145
}
146

147
QString RGBScript::fileName() const
80✔
148
{
149
    return m_fileName;
80✔
150
}
151

152
bool RGBScript::evaluate()
232✔
153
{
154
    QMutexLocker engineLocker(s_engineMutex);
232✔
155

156
    m_rgbMap = QScriptValue();
232✔
157
    m_rgbMapStepCount = QScriptValue();
232✔
158
    m_rgbMapSetColors = QScriptValue();
232✔
159
    m_apiVersion = 0;
232✔
160

161
    m_script = s_engine->evaluate(m_contents, m_fileName);
232✔
162
    if (s_engine->hasUncaughtException() == true)
232✔
163
    {
164
        QString msg("%1: %2");
2✔
165
        qWarning() << msg.arg(m_fileName).arg(s_engine->uncaughtException().toString());
1✔
166
        foreach (QString s, s_engine->uncaughtExceptionBacktrace())
2✔
167
            qDebug() << s;
168
        return false;
169
    }
170
    else
171
    {
172
        m_rgbMap = m_script.property("rgbMap");
231✔
173
        if (m_rgbMap.isFunction() == false)
231✔
174
        {
175
            qWarning() << m_fileName << "is missing the rgbMap() function!";
4✔
176
            return false;
4✔
177
        }
178

179
        m_rgbMapStepCount = m_script.property("rgbMapStepCount");
227✔
180
        if (m_rgbMapStepCount.isFunction() == false)
227✔
181
        {
182
            qWarning() << m_fileName << "is missing the rgbMapStepCount() function!";
1✔
183
            return false;
1✔
184
        }
185

186
        m_apiVersion = m_script.property("apiVersion").toInteger();
226✔
187
        if (m_apiVersion > 0)
226✔
188
        {
189
            if (m_apiVersion >= 3)
225✔
190
            {
191
                m_rgbMapSetColors = m_script.property("rgbMapSetColors");
28✔
192
                if (m_rgbMapSetColors.isFunction() == false)
28✔
193
                {
NEW
194
                    qWarning() << m_fileName << "is missing the rgbMapSetColors() function!";
×
NEW
195
                    return false;
×
196
                }
197

198
                // retrieve the non-mandatory get color function
199
                m_rgbMapGetColors = m_script.property("rgbMapGetColors");
28✔
200
                if (m_rgbMapGetColors.isFunction() == false)
28✔
201
                    qWarning() << m_fileName << "is missing the rgbMapGetColors() function!";
23✔
202
            }
203
            if (m_apiVersion >= 2)
225✔
204
                return loadProperties();
180✔
205
            return true;
206
        }
207
        else
208
        {
209
            qWarning() << m_fileName << "has an invalid apiVersion:" << m_apiVersion;
1✔
210
            return false;
1✔
211
        }
212
    }
213
}
214

215
void RGBScript::initEngine()
156✔
216
{
217
    if (s_engineMutex == NULL)
156✔
218
    {
219
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
220
        s_engineMutex = new QMutex(QMutex::Recursive);
221
#else
222
        s_engineMutex = new QRecursiveMutex();
6✔
223
#endif
224
        s_engine = new QScriptEngine(QCoreApplication::instance());
3✔
225
    }
226
    Q_ASSERT(s_engineMutex != NULL);
227
    Q_ASSERT(s_engine != NULL);
228
}
156✔
229

230
void RGBScript::displayError(QScriptValue e, const QString& fileName)
×
231
{
232
    if (e.isError()) 
×
233
    {
234
        QString msg("%1: Exception at line %2. Error: %3");
×
235
        qWarning() << msg.arg(fileName)
×
236
                         .arg(e.property("lineNumber").toInt32())
×
237
                         .arg(e.toString());
×
238
        qDebug() << "Stack: " << e.property("stack").toString();
239
    }
240
}
×
241

242
/****************************************************************************
243
 * Script API
244
 ****************************************************************************/
245

246
int RGBScript::rgbMapStepCount(const QSize& size)
51✔
247
{
248
    QMutexLocker engineLocker(s_engineMutex);
51✔
249

250
    if (m_rgbMapStepCount.isValid() == false)
51✔
251
        return -1;
252

253
    QScriptValueList args;
50✔
254
    args << size.width() << size.height();
150✔
255
    QScriptValue value = m_rgbMapStepCount.call(QScriptValue(), args);
100✔
256
    if (value.isError())
50✔
257
    {
258
        displayError(value, m_fileName);
×
259
        return -1;
×
260
    } 
261
    else 
262
    {
263
        int ret = value.isNumber() ? value.toInteger() : -1;
50✔
264
        return ret;
50✔
265
    }
266
}
267

268
void RGBScript::rgbMapSetColors(QVector<uint> &colors)
1,148✔
269
{
270
    QMutexLocker engineLocker(s_engineMutex);
1,148✔
271
    if (m_apiVersion <= 2)
1,148✔
272
        return;
273

274
    if (m_rgbMapSetColors.isValid() == false)
115✔
275
        return;
276

277
    int accColors = acceptColors();
115✔
278
    int rawColorCount = colors.count();
115✔
279
    QScriptValue jsRawColors = s_engine->newArray(accColors);
230✔
280
    for (int i = 0; i < rawColorCount && i < accColors; i++)
315✔
281
        jsRawColors.setProperty(i, QScriptValue(colors.at(i)));
200✔
282

283
    QScriptValueList args;
115✔
284
    args << jsRawColors;
285

286
    QScriptValue value = m_rgbMapSetColors.call(QScriptValue(), args);
230✔
287
    if (value.isError())
115✔
NEW
288
        displayError(value, m_fileName);
×
289
}
290

291
QVector<uint> RGBScript::rgbMapGetColors()
1✔
292
{
293
    QMutexLocker engineLocker(s_engineMutex);
1✔
294
    QVector<uint> colArray;
295

296
    if (m_apiVersion <= 2)
1✔
297
        return colArray;
298

NEW
299
    if (m_rgbMapGetColors.isValid() == false)
×
300
        return colArray;
301

NEW
302
    QScriptValue colors = m_rgbMapGetColors.call();
×
NEW
303
    if (colors.isValid() && colors.isArray())
×
304
    {
NEW
305
        QVariantList arr = colors.toVariant().toList();
×
NEW
306
        foreach (QVariant color, arr)
×
NEW
307
            colArray.append(color.toUInt());
×
308
    }
309

310
    return colArray;
311
}
312

313
void RGBScript::rgbMap(const QSize& size, uint rgb, int step, RGBMap &map)
1,161✔
314
{
315
    QMutexLocker engineLocker(s_engineMutex);
1,161✔
316

317
    if (m_rgbMap.isValid() == false)
1,161✔
318
        return;
319

320
    QScriptValueList args;
1,160✔
321
    args << size.width() << size.height() << rgb << step;
5,800✔
322

323
    QScriptValue yarray = m_rgbMap.call(QScriptValue(), args);
2,320✔
324

325
    if (yarray.isError())
1,160✔
326
        displayError(yarray, m_fileName);
×
327

328
    if (yarray.isArray())
1,160✔
329
    {
330
        int ylen = yarray.property("length").toInteger();
1,160✔
331
        map.resize(ylen);
1,160✔
332
        for (int y = 0; y < ylen && y < size.height(); y++)
15,634✔
333
        {
334
            QScriptValue xarray = yarray.property(QString::number(y));
43,422✔
335
            int xlen = xarray.property("length").toInteger();
14,474✔
336
            map[y].resize(xlen);
14,474✔
337
            for (int x = 0; x < xlen && x < size.width(); x++)
133,586✔
338
            {
339
                QScriptValue yx = xarray.property(QString::number(x));
119,112✔
340
                map[y][x] = yx.toInteger();
238,224✔
341
            }
342
        }
343
    }
344
    else
345
    {
346
        qWarning() << "Returned value is not an array within an array!";
×
347
    }
348
}
349

350
QString RGBScript::name() const
1,706✔
351
{
352
    QMutexLocker engineLocker(s_engineMutex);
1,706✔
353

354
    QScriptValue name = m_script.property("name");
3,412✔
355
    QString ret = name.isValid() ? name.toString() : QString();
1,706✔
356
    return ret;
1,706✔
357
}
358

359
QString RGBScript::author() const
41✔
360
{
361
    QMutexLocker engineLocker(s_engineMutex);
41✔
362

363
    QScriptValue author = m_script.property("author");
82✔
364
    QString ret = author.isValid() ? author.toString() : QString();
41✔
365
    return ret;
41✔
366
}
367

368
int RGBScript::apiVersion() const
186✔
369
{
370
    return m_apiVersion;
186✔
371
}
372

373
RGBAlgorithm::Type RGBScript::type() const
58✔
374
{
375
    return RGBAlgorithm::Script;
58✔
376
}
377

378
int RGBScript::acceptColors() const
46,988✔
379
{
380
    QMutexLocker engineLocker(s_engineMutex);
46,988✔
381

382
    QScriptValue accColors = m_script.property("acceptColors");
93,976✔
383
    if (accColors.isValid())
46,988✔
384
        return accColors.toInt32();
25,263✔
385
    // if no property is provided, let's assume the script
386
    // will accept both start and end colors
387
    return 2;
388
}
389

390
bool RGBScript::loadXML(QXmlStreamReader &root)
×
391
{
392
    Q_UNUSED(root)
393

394
    return false;
×
395
}
396

397
bool RGBScript::saveXML(QXmlStreamWriter *doc) const
1✔
398
{
399
    Q_ASSERT(doc != NULL);
400

401
    if (apiVersion() > 0 && name().isEmpty() == false)
1✔
402
    {
403
        doc->writeStartElement(KXMLQLCRGBAlgorithm);
1✔
404
        doc->writeAttribute(KXMLQLCRGBAlgorithmType, KXMLQLCRGBScript);
1✔
405
        doc->writeCharacters(name());
1✔
406
        doc->writeEndElement();
1✔
407
        return true;
1✔
408
    }
409
    else
410
    {
411
        return false;
412
    }
413
}
414

415
/************************************************************************
416
 * Capabilities
417
 ************************************************************************/
418

419
QList<RGBScriptProperty> RGBScript::properties()
106✔
420
{
421
    return m_properties;
106✔
422
}
423

424
QHash<QString, QString> RGBScript::propertiesAsStrings()
×
425
{
426
    QMutexLocker engineLocker(s_engineMutex);
×
427

428
    QHash<QString, QString> properties;
429
    foreach (RGBScriptProperty cap, m_properties)
×
430
    {
431
        QScriptValue readMethod = m_script.property(cap.m_readMethod);
×
432
        if (readMethod.isFunction())
×
433
        {
434
            QScriptValueList args;
×
435
            QScriptValue value = readMethod.call(QScriptValue(), args);
×
436
            if (value.isError())
×
437
            {
438
                displayError(value, m_fileName);
×
439
            }
440
            else if (value.isValid())
×
441
            {
442
                properties.insert(cap.m_name, value.toString());
×
443
            }
444
        }
445
    }
446
    return properties;
×
447
}
448

449
bool RGBScript::setProperty(QString propertyName, QString value)
336✔
450
{
451
    QMutexLocker engineLocker(s_engineMutex);
336✔
452

453
    foreach (RGBScriptProperty cap, m_properties)
1,512✔
454
    {
455
        if (cap.m_name == propertyName)
924✔
456
        {
457
            QScriptValue writeMethod = m_script.property(cap.m_writeMethod);
672✔
458
            if (writeMethod.isFunction() == false)
336✔
459
            {
460
                qWarning() << name() << "doesn't have a write function for" << propertyName;
×
461
                return false;
×
462
            }
463
            QScriptValueList args;
336✔
464
            args << value;
672✔
465
            QScriptValue written = writeMethod.call(QScriptValue(), args);
672✔
466
            if (written.isError())
336✔
467
            {
468
                displayError(written, m_fileName);
×
469
                return false;
×
470
            }
471
            else
472
            {
473
                return true;
474
            }
475
        }
476
    }
477
    return false;
×
478
}
479

480
QString RGBScript::property(QString propertyName) const
446✔
481
{
482
    QMutexLocker engineLocker(s_engineMutex);
446✔
483

484
    foreach (RGBScriptProperty cap, m_properties)
1,976✔
485
    {
486
        if (cap.m_name == propertyName)
1,210✔
487
        {
488
            QScriptValue readMethod = m_script.property(cap.m_readMethod);
890✔
489
            if (readMethod.isFunction() == false)
445✔
490
            {
491
                qWarning() << name() << "doesn't have a read function for" << propertyName;
×
492
                return QString();
493
            }
494
            QScriptValueList args;
445✔
495
            QScriptValue value = readMethod.call(QScriptValue(), args);
890✔
496
            if (value.isError())
445✔
497
            {
498
                displayError(value, m_fileName);
×
499
                return QString();
500
            }
501
            else if (value.isValid())
445✔
502
            {
503
                return value.toString();
445✔
504
            }
505
            else
506
            {
507
                return QString();
508
            }
509
        }
510
    }
511
    return QString();
512
}
513

514
bool RGBScript::loadProperties()
210✔
515
{
516
    QMutexLocker engineLocker(s_engineMutex);
210✔
517

518
    QScriptValue svCaps = m_script.property("properties");
420✔
519
    if (svCaps.isArray() == false)
210✔
520
    {
521
        qWarning() << m_fileName << "properties is not an array!";
×
522
        return false;
×
523
    }
524
    QVariant varCaps = svCaps.toVariant();
420✔
525
    if (varCaps.isValid() == false)
210✔
526
    {
527
        qWarning() << m_fileName << "has invalid properties!";
×
528
        return false;
×
529
    }
530

531
    m_properties.clear();
210✔
532

533
    QStringList slCaps = varCaps.toStringList();
210✔
534
    foreach (QString cap, slCaps)
1,200✔
535
    {
536
        RGBScriptProperty newCap;
990✔
537

538
        QStringList propsList = cap.split("|");
990✔
539
        foreach (QString prop, propsList)
6,435✔
540
        {
541
            QStringList keyValue = prop.split(":");
5,940✔
542
            if (keyValue.length() < 2)
2,970✔
543
            {
544
                qWarning() << prop << ": malformed property. Please fix it.";
×
545
                continue;
546
            }
547
            QString key = keyValue.at(0).simplified();
2,970✔
548
            QString value = keyValue.at(1);
2,970✔
549
            if (key == "name")
2,970✔
550
            {
551
                newCap.m_name = value;
495✔
552
            }
553
            else if (key == "type")
2,475✔
554
            {
555
                if (value == "list") newCap.m_type = RGBScriptProperty::List;
495✔
556
                else if (value == "float") newCap.m_type = RGBScriptProperty::Float;
204✔
557
                else if (value == "range") newCap.m_type = RGBScriptProperty::Range;
204✔
558
                else if (value == "string") newCap.m_type = RGBScriptProperty::String;
×
559
            }
560
            else if (key == "display")
1,980✔
561
            {
562
                newCap.m_displayName = value.simplified();
495✔
563
            }
564
            else if (key == "values")
1,485✔
565
            {
566
                QStringList values = value.split(",");
495✔
567
                switch(newCap.m_type)
495✔
568
                {
569
                    case RGBScriptProperty::List:
570
                        newCap.m_listValues = values;
571
                    break;
572
                    case RGBScriptProperty::Range:
573
                    {
574
                        if (values.length() < 2)
204✔
575
                        {
576
                            qWarning() << value << ": malformed property. A range should be defined as 'min,max'. Please fix it.";
×
577
                        }
578
                        else
579
                        {
580
                            newCap.m_rangeMinValue = values.at(0).toInt();
204✔
581
                            newCap.m_rangeMaxValue = values.at(1).toInt();
204✔
582
                        }
583
                    }
584
                    break;
585
                    default:
586
                        qWarning() << value << ": values cannot be applied before the 'type' property or on type:integer and type:string";
×
587
                    break;
×
588
                }
589
            }
590
            else if (key == "write")
990✔
591
            {
592
                newCap.m_writeMethod = value.simplified();
495✔
593
            }
594
            else if (key == "read")
495✔
595
            {
596
                newCap.m_readMethod = value.simplified();
495✔
597
            }
598
            else
599
            {
600
                qWarning() << value << ": unknown property!";
×
601
            }
602
        }
603

604
        if (newCap.m_name.isEmpty() == false &&
495✔
605
            newCap.m_type != RGBScriptProperty::None)
495✔
606
                m_properties.append(newCap);
495✔
607
    }
608

609
    return true;
610
}
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