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

mcallegari / qlcplus / 11388022654

17 Oct 2024 03:21PM UTC coverage: 31.573% (-0.4%) from 31.983%
11388022654

Pull #1422

github

web-flow
Merge 4147c937e into 5f77fc96f
Pull Request #1422: RgbScript make stage colors available to scripts

56 of 908 new or added lines in 11 files covered. (6.17%)

12 existing lines in 8 files now uncovered.

14057 of 44522 relevant lines covered (31.57%)

26666.02 hits per line

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

78.74
/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)
362✔
52
    : RGBAlgorithm(doc)
53
    , m_apiVersion(0)
362✔
54
{
55
}
362✔
56

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

70
RGBScript::~RGBScript()
97✔
71
{
72
}
97✔
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
17✔
100
{
101
    RGBScript* script = new RGBScript(*this);
17✔
102
    return static_cast<RGBAlgorithm*> (script);
17✔
103
}
104

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

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

114
    QMutexLocker engineLocker(s_engineMutex);
180✔
115

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

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

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

135
    QScriptSyntaxCheckResult result = QScriptEngine::checkSyntax(m_contents);
360✔
136
    if (result.state() == QScriptSyntaxCheckResult::Valid)
180✔
137
        return evaluate();
180✔
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
92✔
148
{
149
    return m_fileName;
92✔
150
}
151

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

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

161
    m_script = s_engine->evaluate(m_contents, m_fileName);
261✔
162
    if (s_engine->hasUncaughtException() == true)
261✔
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");
260✔
173
        if (m_rgbMap.isFunction() == false)
260✔
174
        {
175
            qWarning() << m_fileName << "is missing the rgbMap() function!";
4✔
176
            return false;
4✔
177
        }
178

179
        m_rgbMapStepCount = m_script.property("rgbMapStepCount");
256✔
180
        if (m_rgbMapStepCount.isFunction() == false)
256✔
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();
255✔
187
        if (m_apiVersion > 0)
255✔
188
        {
189
            if (m_apiVersion >= 3)
254✔
190
            {
191
                m_rgbMapSetColors = m_script.property("rgbMapSetColors");
27✔
192
                if (m_rgbMapSetColors.isFunction() == false)
27✔
193
                {
NEW
194
                    qWarning() << m_fileName << "is missing the rgbMapSetColors() function!";
×
NEW
195
                    return false;
×
196
                }
197
            }
198
            if (m_apiVersion >= 2)
254✔
199
                return loadProperties();
209✔
200
            return true;
201
        }
202
        else
203
        {
204
            qWarning() << m_fileName << "has an invalid apiVersion:" << m_apiVersion;
1✔
205
            return false;
1✔
206
        }
207
    }
208
}
209

210
void RGBScript::initEngine()
180✔
211
{
212
    if (s_engineMutex == NULL)
180✔
213
    {
214
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
215
        s_engineMutex = new QMutex(QMutex::Recursive);
216
#else
217
        s_engineMutex = new QRecursiveMutex();
6✔
218
#endif
219
        s_engine = new QScriptEngine(QCoreApplication::instance());
3✔
220
    }
221
    Q_ASSERT(s_engineMutex != NULL);
222
    Q_ASSERT(s_engine != NULL);
223
}
180✔
224

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

237
/****************************************************************************
238
 * Script API
239
 ****************************************************************************/
240

241
int RGBScript::rgbMapStepCount(const QSize& size)
57✔
242
{
243
    QMutexLocker engineLocker(s_engineMutex);
57✔
244

245
    if (m_rgbMapStepCount.isValid() == false)
57✔
246
        return -1;
247

248
    QScriptValueList args;
56✔
249
    args << size.width() << size.height();
168✔
250
    QScriptValue value = m_rgbMapStepCount.call(QScriptValue(), args);
112✔
251
    if (value.isError())
56✔
252
    {
253
        displayError(value, m_fileName);
×
254
        return -1;
×
255
    } 
256
    else 
257
    {
258
        int ret = value.isNumber() ? value.toInteger() : -1;
56✔
259
        return ret;
56✔
260
    }
261
}
262

263
void RGBScript::rgbMapSetColors(QVector<uint> &colors)
2,644✔
264
{
265
    QMutexLocker engineLocker(s_engineMutex);
2,644✔
266
    if (m_apiVersion <= 2)
2,644✔
267
        return;
268
    if (m_rgbMapSetColors.isValid() == false)
101✔
269
        return;
270

271
    int accColors = acceptColors();
101✔
272
    int rawColorCount = colors.count();
101✔
273
    QScriptValue jsRawColors = s_engine->newArray(accColors);
202✔
274
    for (int i = 0; i < rawColorCount && i < accColors; i++)
303✔
275
        jsRawColors.setProperty(i, QScriptValue(colors.at(i)));
202✔
276

277
    QScriptValueList args;
101✔
278
    args << jsRawColors;
279

280
    QScriptValue value = m_rgbMapSetColors.call(QScriptValue(), args);
202✔
281
    if (value.isError())
101✔
NEW
282
        displayError(value, m_fileName);
×
283
}
284

285
void RGBScript::rgbMap(const QSize& size, uint rgb, int step, RGBMap &map)
2,657✔
286
{
287
    QMutexLocker engineLocker(s_engineMutex);
2,657✔
288

289
    if (m_rgbMap.isValid() == false)
2,657✔
290
        return;
291

292
    QScriptValueList args;
2,656✔
293
    args << size.width() << size.height() << rgb << step;
13,280✔
294

295
    QScriptValue yarray = m_rgbMap.call(QScriptValue(), args);
5,312✔
296

297
    if (yarray.isError())
2,656✔
298
        displayError(yarray, m_fileName);
×
299

300
    if (yarray.isArray())
2,656✔
301
    {
302
        int ylen = yarray.property("length").toInteger();
2,656✔
303
        map.resize(ylen);
2,656✔
304
        for (int y = 0; y < ylen && y < size.height(); y++)
33,762✔
305
        {
306
            QScriptValue xarray = yarray.property(QString::number(y));
93,318✔
307
            int xlen = xarray.property("length").toInteger();
31,106✔
308
            map[y].resize(xlen);
31,106✔
309
            for (int x = 0; x < xlen && x < size.width(); x++)
268,402✔
310
            {
311
                QScriptValue yx = xarray.property(QString::number(x));
237,296✔
312
                map[y][x] = yx.toInteger();
474,592✔
313
            }
314
        }
315
    }
316
    else
317
    {
318
        qWarning() << "Returned value is not an array within an array!";
×
319
    }
320
}
321

322
QString RGBScript::name() const
2,115✔
323
{
324
    QMutexLocker engineLocker(s_engineMutex);
2,115✔
325

326
    QScriptValue name = m_script.property("name");
4,230✔
327
    QString ret = name.isValid() ? name.toString() : QString();
2,115✔
328
    return ret;
2,115✔
329
}
330

331
QString RGBScript::author() const
47✔
332
{
333
    QMutexLocker engineLocker(s_engineMutex);
47✔
334

335
    QScriptValue author = m_script.property("author");
94✔
336
    QString ret = author.isValid() ? author.toString() : QString();
47✔
337
    return ret;
47✔
338
}
339

340
int RGBScript::apiVersion() const
214✔
341
{
342
    return m_apiVersion;
214✔
343
}
344

345
RGBAlgorithm::Type RGBScript::type() const
63✔
346
{
347
    return RGBAlgorithm::Script;
63✔
348
}
349

350
int RGBScript::acceptColors() const
52,099✔
351
{
352
    QMutexLocker engineLocker(s_engineMutex);
52,099✔
353

354
    QScriptValue accColors = m_script.property("acceptColors");
104,198✔
355
    if (accColors.isValid())
52,099✔
356
        return accColors.toInt32();
30,281✔
357
    // if no property is provided, let's assume the script
358
    // will accept both start and end colors
359
    return 2;
360
}
361

362
bool RGBScript::loadXML(QXmlStreamReader &root)
×
363
{
364
    Q_UNUSED(root)
365

366
    return false;
×
367
}
368

369
bool RGBScript::saveXML(QXmlStreamWriter *doc) const
1✔
370
{
371
    Q_ASSERT(doc != NULL);
372

373
    if (apiVersion() > 0 && name().isEmpty() == false)
1✔
374
    {
375
        doc->writeStartElement(KXMLQLCRGBAlgorithm);
1✔
376
        doc->writeAttribute(KXMLQLCRGBAlgorithmType, KXMLQLCRGBScript);
1✔
377
        doc->writeCharacters(name());
1✔
378
        doc->writeEndElement();
1✔
379
        return true;
1✔
380
    }
381
    else
382
    {
383
        return false;
384
    }
385
}
386

387
/************************************************************************
388
 * Capabilities
389
 ************************************************************************/
390

391
QList<RGBScriptProperty> RGBScript::properties()
144✔
392
{
393
    return m_properties;
144✔
394
}
395

396
QHash<QString, QString> RGBScript::propertiesAsStrings()
×
397
{
398
    QMutexLocker engineLocker(s_engineMutex);
×
399

400
    QHash<QString, QString> properties;
401
    foreach (RGBScriptProperty cap, m_properties)
×
402
    {
403
        QScriptValue readMethod = m_script.property(cap.m_readMethod);
×
404
        if (readMethod.isFunction())
×
405
        {
406
            QScriptValueList args;
×
407
            QScriptValue value = readMethod.call(QScriptValue(), args);
×
408
            if (value.isError())
×
409
            {
410
                displayError(value, m_fileName);
×
411
            }
412
            else if (value.isValid())
×
413
            {
414
                properties.insert(cap.m_name, value.toString());
×
415
            }
416
        }
417
    }
418
    return properties;
×
419
}
420

421
bool RGBScript::setProperty(QString propertyName, QString value)
823✔
422
{
423
    QMutexLocker engineLocker(s_engineMutex);
823✔
424

425
    foreach (RGBScriptProperty cap, m_properties)
4,855✔
426
    {
427
        if (cap.m_name == propertyName)
2,839✔
428
        {
429
            QScriptValue writeMethod = m_script.property(cap.m_writeMethod);
1,646✔
430
            if (writeMethod.isFunction() == false)
823✔
431
            {
432
                qWarning() << name() << "doesn't have a write function for" << propertyName;
×
433
                return false;
×
434
            }
435
            QScriptValueList args;
823✔
436
            args << value;
1,646✔
437
            QScriptValue written = writeMethod.call(QScriptValue(), args);
1,646✔
438
            if (written.isError())
823✔
439
            {
440
                displayError(written, m_fileName);
×
441
                return false;
×
442
            }
443
            else
444
            {
445
                return true;
446
            }
447
        }
448
    }
449
    return false;
×
450
}
451

452
QString RGBScript::property(QString propertyName) const
979✔
453
{
454
    QMutexLocker engineLocker(s_engineMutex);
979✔
455

456
    foreach (RGBScriptProperty cap, m_properties)
5,599✔
457
    {
458
        if (cap.m_name == propertyName)
3,288✔
459
        {
460
            QScriptValue readMethod = m_script.property(cap.m_readMethod);
1,956✔
461
            if (readMethod.isFunction() == false)
978✔
462
            {
463
                qWarning() << name() << "doesn't have a read function for" << propertyName;
×
464
                return QString();
465
            }
466
            QScriptValueList args;
978✔
467
            QScriptValue value = readMethod.call(QScriptValue(), args);
1,956✔
468
            if (value.isError())
978✔
469
            {
470
                displayError(value, m_fileName);
×
471
                return QString();
472
            }
473
            else if (value.isValid())
978✔
474
            {
475
                return value.toString();
978✔
476
            }
477
            else
478
            {
479
                return QString();
480
            }
481
        }
482
    }
483
    return QString();
484
}
485

486
bool RGBScript::loadProperties()
245✔
487
{
488
    QMutexLocker engineLocker(s_engineMutex);
245✔
489

490
    QScriptValue svCaps = m_script.property("properties");
490✔
491
    if (svCaps.isArray() == false)
245✔
492
    {
493
        qWarning() << m_fileName << "properties is not an array!";
×
494
        return false;
×
495
    }
496
    QVariant varCaps = svCaps.toVariant();
490✔
497
    if (varCaps.isValid() == false)
245✔
498
    {
499
        qWarning() << m_fileName << "has invalid properties!";
×
500
        return false;
×
501
    }
502

503
    m_properties.clear();
245✔
504

505
    QStringList slCaps = varCaps.toStringList();
245✔
506
    foreach (QString cap, slCaps)
1,611✔
507
    {
508
        RGBScriptProperty newCap;
1,366✔
509

510
        QStringList propsList = cap.split("|");
1,366✔
511
        foreach (QString prop, propsList)
8,879✔
512
        {
513
            QStringList keyValue = prop.split(":");
8,196✔
514
            if (keyValue.length() < 2)
4,098✔
515
            {
516
                qWarning() << prop << ": malformed property. Please fix it.";
×
517
                continue;
518
            }
519
            QString key = keyValue.at(0).simplified();
4,098✔
520
            QString value = keyValue.at(1);
4,098✔
521
            if (key == "name")
4,098✔
522
            {
523
                newCap.m_name = value;
683✔
524
            }
525
            else if (key == "type")
3,415✔
526
            {
527
                if (value == "list") newCap.m_type = RGBScriptProperty::List;
683✔
528
                else if (value == "float") newCap.m_type = RGBScriptProperty::Float;
286✔
529
                else if (value == "range") newCap.m_type = RGBScriptProperty::Range;
286✔
530
                else if (value == "string") newCap.m_type = RGBScriptProperty::String;
×
531
            }
532
            else if (key == "display")
2,732✔
533
            {
534
                newCap.m_displayName = value.simplified();
683✔
535
            }
536
            else if (key == "values")
2,049✔
537
            {
538
                QStringList values = value.split(",");
683✔
539
                switch(newCap.m_type)
683✔
540
                {
541
                    case RGBScriptProperty::List:
542
                        newCap.m_listValues = values;
543
                    break;
544
                    case RGBScriptProperty::Range:
545
                    {
546
                        if (values.length() < 2)
286✔
547
                        {
548
                            qWarning() << value << ": malformed property. A range should be defined as 'min,max'. Please fix it.";
×
549
                        }
550
                        else
551
                        {
552
                            newCap.m_rangeMinValue = values.at(0).toInt();
286✔
553
                            newCap.m_rangeMaxValue = values.at(1).toInt();
286✔
554
                        }
555
                    }
556
                    break;
557
                    default:
558
                        qWarning() << value << ": values cannot be applied before the 'type' property or on type:integer and type:string";
×
559
                    break;
×
560
                }
561
            }
562
            else if (key == "write")
1,366✔
563
            {
564
                newCap.m_writeMethod = value.simplified();
683✔
565
            }
566
            else if (key == "read")
683✔
567
            {
568
                newCap.m_readMethod = value.simplified();
683✔
569
            }
570
            else
571
            {
572
                qWarning() << value << ": unknown property!";
×
573
            }
574
        }
575

576
        if (newCap.m_name.isEmpty() == false &&
683✔
577
            newCap.m_type != RGBScriptProperty::None)
683✔
578
                m_properties.append(newCap);
683✔
579
    }
580

581
    return true;
582
}
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