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

mcallegari / qlcplus / 13215211607

08 Feb 2025 11:29AM UTC coverage: 31.507% (+0.02%) from 31.492%
13215211607

push

github

mcallegari
engine: fix test units

4 of 4 new or added lines in 1 file covered. (100.0%)

196 existing lines in 2 files now uncovered.

14157 of 44933 relevant lines covered (31.51%)

26901.88 hits per line

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

42.18
/engine/src/rgbmatrix.cpp
1
/*
2
  Q Light Controller Plus
3
  rgbmatrix.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
#include <QCoreApplication>
24
#include <QElapsedTimer>
25
#include <QDebug>
26
#include <cmath>
27
#include <QDir>
28

29
#include "rgbscriptscache.h"
30
#include "qlcfixturehead.h"
31
#include "fixturegroup.h"
32
#include "genericfader.h"
33
#include "fadechannel.h"
34
#include "rgbmatrix.h"
35
#include "rgbimage.h"
36
#include "doc.h"
37

38
#define KXMLQLCRGBMatrixStartColor      QString("MonoColor")
39
#define KXMLQLCRGBMatrixEndColor        QString("EndColor")
40
#define KXMLQLCRGBMatrixColor           QString("Color")
41
#define KXMLQLCRGBMatrixColorIndex      QString("Index")
42

43
#define KXMLQLCRGBMatrixFixtureGroup    QString("FixtureGroup")
44
#define KXMLQLCRGBMatrixDimmerControl   QString("DimmerControl")
45

46
#define KXMLQLCRGBMatrixProperty        QString("Property")
47
#define KXMLQLCRGBMatrixPropertyName    QString("Name")
48
#define KXMLQLCRGBMatrixPropertyValue   QString("Value")
49

50
#define KXMLQLCRGBMatrixControlMode         QString("ControlMode")
51
#define KXMLQLCRGBMatrixControlModeRgb      QString("RGB")
52
#define KXMLQLCRGBMatrixControlModeAmber    QString("Amber")
53
#define KXMLQLCRGBMatrixControlModeWhite    QString("White")
54
#define KXMLQLCRGBMatrixControlModeUV       QString("UV")
55
#define KXMLQLCRGBMatrixControlModeDimmer   QString("Dimmer")
56
#define KXMLQLCRGBMatrixControlModeShutter  QString("Shutter")
57

58
/****************************************************************************
59
 * Initialization
60
 ****************************************************************************/
61

62
RGBMatrix::RGBMatrix(Doc *doc)
9✔
63
    : Function(doc, Function::RGBMatrixType)
64
    , m_dimmerControl(false)
65
    , m_fixtureGroupID(FixtureGroup::invalidId())
9✔
66
    , m_group(NULL)
67
    , m_requestEngineCreation(true)
68
    , m_previewAlgorithm(NULL)
69
    , m_algorithm(NULL)
70
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
71
    , m_algorithmMutex(QMutex::Recursive)
72
#endif
73
    , m_stepHandler(new RGBMatrixStep())
9✔
74
    , m_roundTime(new QElapsedTimer())
9✔
75
    , m_stepsCount(0)
76
    , m_stepBeatDuration(0)
77
    , m_controlMode(RGBMatrix::ControlModeRgb)
18✔
78
{
79
    setName(tr("New RGB Matrix"));
9✔
80
    setDuration(500);
9✔
81

82
    m_rgbColors.fill(QColor(), RGBAlgorithmColorDisplayCount);
9✔
83
    setColor(0, Qt::red);
9✔
84

85
    setAlgorithm(RGBAlgorithm::algorithm(doc, "Stripes"));
9✔
86
}
9✔
87

88
RGBMatrix::~RGBMatrix()
11✔
89
{
90
    //if (m_previewAlgorithm != NULL)
91
    //    delete m_previewAlgorithm;
92
    delete m_algorithm;
9✔
93
    delete m_roundTime;
9✔
94
    delete m_stepHandler;
18✔
95
}
11✔
96

UNCOV
97
QIcon RGBMatrix::getIcon() const
×
98
{
UNCOV
99
    return QIcon(":/rgbmatrix.png");
×
100
}
101

102
void RGBMatrix::setTotalDuration(quint32 msec)
1✔
103
{
104
    QMutexLocker algorithmLocker(&m_algorithmMutex);
1✔
105

106
    if (m_algorithm == NULL)
1✔
107
        return;
108

109
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
1✔
110
    if (grp == NULL)
1✔
111
        return;
112

113
    int steps = m_algorithm->rgbMapStepCount(grp->size());
1✔
114
    setDuration(msec / steps);
1✔
115
}
116

117
quint32 RGBMatrix::totalDuration()
3✔
118
{
119
    QMutexLocker algorithmLocker(&m_algorithmMutex);
3✔
120

121
    if (m_algorithm == NULL)
3✔
122
        return 0;
123

124
    FixtureGroup* grp = doc()->fixtureGroup(fixtureGroup());
3✔
125
    if (grp == NULL)
3✔
126
        return 0;
127

128
    //qDebug () << "Algorithm steps:" << m_algorithm->rgbMapStepCount(grp->size());
129
    return m_algorithm->rgbMapStepCount(grp->size()) * duration();
2✔
130
}
131

132
void RGBMatrix::setDimmerControl(bool dimmerControl)
1✔
133
{
134
    m_dimmerControl = dimmerControl;
1✔
135
}
1✔
136

137
bool RGBMatrix::dimmerControl() const
2✔
138
{
139
    return m_dimmerControl;
2✔
140
}
141

142
/****************************************************************************
143
 * Copying
144
 ****************************************************************************/
145

146
Function* RGBMatrix::createCopy(Doc* doc, bool addToDoc)
1✔
147
{
148
    Q_ASSERT(doc != NULL);
149

150
    Function *copy = new RGBMatrix(doc);
1✔
151
    if (copy->copyFrom(this) == false)
1✔
152
    {
UNCOV
153
        delete copy;
×
154
        copy = NULL;
155
    }
156
    if (addToDoc == true && doc->addFunction(copy) == false)
1✔
157
    {
UNCOV
158
        delete copy;
×
159
        copy = NULL;
160
    }
161

162
    return copy;
1✔
163
}
164

165
bool RGBMatrix::copyFrom(const Function* function)
1✔
166
{
167
    const RGBMatrix *mtx = qobject_cast<const RGBMatrix*> (function);
168
    if (mtx == NULL)
1✔
169
        return false;
170

171
    setDimmerControl(mtx->dimmerControl());
1✔
172
    setFixtureGroup(mtx->fixtureGroup());
1✔
173

174
    m_rgbColors.clear();
1✔
175
    foreach (QColor col, mtx->getColors())
12✔
176
        m_rgbColors.append(col);
5✔
177

178
    if (mtx->algorithm() != NULL)
1✔
179
        setAlgorithm(mtx->algorithm()->clone());
1✔
180
    else
UNCOV
181
        setAlgorithm(NULL);
×
182

183
    setControlMode(mtx->controlMode());
1✔
184

185
    return Function::copyFrom(function);
1✔
186
}
187

188
/****************************************************************************
189
 * Fixtures
190
 ****************************************************************************/
191

192
quint32 RGBMatrix::fixtureGroup() const
35✔
193
{
194
    return m_fixtureGroupID;
35✔
195
}
196

197
void RGBMatrix::setFixtureGroup(quint32 id)
8✔
198
{
199
    m_fixtureGroupID = id;
8✔
200
    {
201
        QMutexLocker algoLocker(&m_algorithmMutex);
8✔
202
        m_group = doc()->fixtureGroup(m_fixtureGroupID);
8✔
203
    }
204
    m_stepsCount = algorithmStepsCount();
8✔
205
}
8✔
206

207
QList<quint32> RGBMatrix::components()
2✔
208
{
209
    if (m_group != NULL)
2✔
210
        return m_group->fixtureList();
1✔
211

212
    return QList<quint32>();
213
}
214

215
/****************************************************************************
216
 * Algorithm
217
 ****************************************************************************/
218

219
void RGBMatrix::setAlgorithm(RGBAlgorithm *algo)
13✔
220
{
221
    {
222
        QMutexLocker algorithmLocker(&m_algorithmMutex);
13✔
223
        delete m_algorithm;
13✔
224
        m_algorithm = algo;
13✔
225

226
        if (m_previewAlgorithm != NULL)
13✔
UNCOV
227
            m_previewAlgorithm = algo;
×
228

229
        m_requestEngineCreation = true;
13✔
230

231
        /** If there's been a change of Script algorithm "on the fly",
232
         *  then re-apply the properties currently set in this RGBMatrix */
233
        if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
13✔
234
        {
235
            RGBScript *script = static_cast<RGBScript*> (m_algorithm);
13✔
236
            QHashIterator<QString, QString> it(m_properties);
13✔
237
            while (it.hasNext())
13✔
238
            {
239
                it.next();
UNCOV
240
                if (script->setProperty(it.key(), it.value()) == false)
×
241
                {
242
                    /** If the new algorithm doesn't expose a property,
243
                     *  then remove it from the cached list, otherwise
244
                     *  it would be carried around forever (and saved on XML) */
UNCOV
245
                    m_properties.take(it.key());
×
246
                }
247
            }
248
        }
249
    }
250
    m_stepsCount = algorithmStepsCount();
13✔
251

252
    emit changed(id());
13✔
253
}
13✔
254

255
RGBAlgorithm *RGBMatrix::algorithm() const
18✔
256
{
257
    return m_algorithm;
18✔
258
}
259

260
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
261
QMutex& RGBMatrix::algorithmMutex()
262
{
263
    return m_algorithmMutex;
264
}
265
#else
UNCOV
266
QRecursiveMutex& RGBMatrix::algorithmMutex()
×
267
{
UNCOV
268
    return m_algorithmMutex;
×
269
}
270
#endif
271

272

273
int RGBMatrix::stepsCount()
2✔
274
{
275
    return m_stepsCount;
2✔
276
}
277

278
int RGBMatrix::algorithmStepsCount()
22✔
279
{
280
    QMutexLocker algorithmLocker(&m_algorithmMutex);
22✔
281

282
    if (m_algorithm == NULL)
22✔
283
        return 0;
284

285
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
22✔
286
    if (grp != NULL)
22✔
287
        return m_algorithm->rgbMapStepCount(grp->size());
6✔
288

289
    return 0;
290
}
291

292
void RGBMatrix::previewMap(int step, RGBMatrixStep *handler)
7✔
293
{
294
    QMutexLocker algorithmLocker(&m_algorithmMutex);
7✔
295
    if (m_algorithm == NULL || handler == NULL)
7✔
296
        return;
297

298
    if (m_group == NULL)
7✔
299
        m_group = doc()->fixtureGroup(fixtureGroup());
1✔
300

301
    if (m_group != NULL)
7✔
302
    {
303
        if (m_previewAlgorithm == NULL)
6✔
304
            m_previewAlgorithm = m_algorithm;
1✔
305

306
        setMapColors(m_previewAlgorithm);
6✔
307
        m_previewAlgorithm->rgbMap(m_group->size(), handler->stepColor().rgb(), step, handler->m_map);
6✔
308
    }
309
}
310

311
/****************************************************************************
312
 * Color
313
 ****************************************************************************/
314

315
void RGBMatrix::setColor(int i, QColor c)
25✔
316
{
317
    if (i < 0)
25✔
318
        return;
319

320
    if (i >= m_rgbColors.count())
25✔
UNCOV
321
        m_rgbColors.resize(i + 1);
×
322

323
    m_rgbColors.replace(i, c);
25✔
324
    {
325
        QMutexLocker algorithmLocker(&m_algorithmMutex);
25✔
326
        if (m_algorithm != NULL)
25✔
327
        {
328
            m_algorithm->setColors(m_rgbColors);
16✔
329
            updateColorDelta();
16✔
330
        }
331
    }
332
    setMapColors(m_algorithm);
25✔
333
    emit changed(id());
25✔
334
}
335

336
QColor RGBMatrix::getColor(int i) const
10✔
337
{
338
    if (i < 0 || i >= m_rgbColors.count())
10✔
339
        return QColor();
340

341
    return m_rgbColors.at(i);
342
}
343

344
QVector<QColor> RGBMatrix::getColors() const
1✔
345
{
346
    return m_rgbColors;
1✔
347
}
348

349
void RGBMatrix::updateColorDelta()
16✔
350
{
351
    m_stepHandler->calculateColorDelta(m_rgbColors[0], m_rgbColors[1], m_algorithm);
32✔
352
}
16✔
353

354
void RGBMatrix::setMapColors(RGBAlgorithm *algorithm)
31✔
355
{
356
    QMutexLocker algorithmLocker(&m_algorithmMutex);
31✔
357
    if (algorithm == NULL)
31✔
358
        return;
359

360
    if (algorithm->apiVersion() < 3)
22✔
361
        return;
362

UNCOV
363
    if (m_group == NULL)
×
UNCOV
364
        m_group = doc()->fixtureGroup(fixtureGroup());
×
365

UNCOV
366
    if (m_group != NULL)
×
367
    {
UNCOV
368
        QVector<unsigned int> rawColors;
×
UNCOV
369
        for (int i = 0; i < algorithm->acceptColors(); i++)
×
370
        {
UNCOV
371
            QColor col = m_rgbColors.at(i);
×
UNCOV
372
            rawColors.append(col.isValid() ? col.rgb() : 0);
×
373
        }
374

UNCOV
375
        algorithm->rgbMapSetColors(rawColors);
×
376
    }
377
}
378

379
/************************************************************************
380
 * Properties
381
 ************************************************************************/
382

383
void RGBMatrix::setProperty(QString propName, QString value)
1✔
384
{
385
    QMutexLocker algoLocker(&m_algorithmMutex);
1✔
386
    m_properties[propName] = value;
1✔
387
    if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
1✔
388
    {
389
        RGBScript *script = static_cast<RGBScript*> (m_algorithm);
1✔
390
        script->setProperty(propName, value);
1✔
391

392
        QVector<uint> colors = script->rgbMapGetColors();
2✔
393
        for (int i = 0; i < colors.count(); i++)
1✔
UNCOV
394
            setColor(i, QColor::fromRgb(colors.at(i)));
×
395
    }
396
    m_stepsCount = algorithmStepsCount();
1✔
397
}
1✔
398

399
QString RGBMatrix::property(QString propName)
3✔
400
{
401
    QMutexLocker algoLocker(&m_algorithmMutex);
3✔
402

403
    /** If the property is cached, then return it right away */
404
    if (m_properties.contains(propName))
3✔
405
        return m_properties[propName];
1✔
406

407
    /** Otherwise, let's retrieve it from the Script */
408
    if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
2✔
409
    {
410
        RGBScript *script = static_cast<RGBScript*> (m_algorithm);
2✔
411
        return script->property(propName);
2✔
412
    }
413

414
    return QString();
415
}
416

417
/****************************************************************************
418
 * Load & Save
419
 ****************************************************************************/
420

421
bool RGBMatrix::loadXML(QXmlStreamReader &root)
3✔
422
{
423
    if (root.name() != KXMLQLCFunction)
6✔
424
    {
425
        qWarning() << Q_FUNC_INFO << "Function node not found";
1✔
426
        return false;
1✔
427
    }
428

429
    if (root.attributes().value(KXMLQLCFunctionType).toString() != typeToString(Function::RGBMatrixType))
4✔
430
    {
431
        qWarning() << Q_FUNC_INFO << "Function is not an RGB matrix";
1✔
432
        return false;
1✔
433
    }
434

435
    /* Load matrix contents */
436
    while (root.readNextStartElement())
12✔
437
    {
438
        if (root.name() == KXMLQLCFunctionSpeed)
22✔
439
        {
440
            loadXMLSpeed(root);
1✔
441
        }
442
        else if (root.name() == KXMLQLCRGBAlgorithm)
20✔
443
        {
444
            setAlgorithm(RGBAlgorithm::loader(doc(), root));
1✔
445
        }
446
        else if (root.name() == KXMLQLCRGBMatrixFixtureGroup)
18✔
447
        {
448
            setFixtureGroup(root.readElementText().toUInt());
1✔
449
        }
450
        else if (root.name() == KXMLQLCFunctionDirection)
16✔
451
        {
452
            loadXMLDirection(root);
1✔
453
        }
454
        else if (root.name() == KXMLQLCFunctionRunOrder)
14✔
455
        {
456
            loadXMLRunOrder(root);
1✔
457
        }
458
        // Legacy support
459
        else if (root.name() == KXMLQLCRGBMatrixStartColor)
12✔
460
        {
UNCOV
461
            setColor(0, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
×
462
        }
463
        else if (root.name() == KXMLQLCRGBMatrixEndColor)
12✔
464
        {
465
            setColor(1, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
×
466
        }
467
        else if (root.name() == KXMLQLCRGBMatrixColor)
12✔
468
        {
469
            int colorIdx = root.attributes().value(KXMLQLCRGBMatrixColorIndex).toInt();
5✔
470
            setColor(colorIdx, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
5✔
471
        }
472
        else if (root.name() == KXMLQLCRGBMatrixControlMode)
2✔
473
        {
474
            setControlMode(stringToControlMode(root.readElementText()));
1✔
475
        }
UNCOV
476
        else if (root.name() == KXMLQLCRGBMatrixProperty)
×
477
        {
UNCOV
478
            QString name = root.attributes().value(KXMLQLCRGBMatrixPropertyName).toString();
×
UNCOV
479
            QString value = root.attributes().value(KXMLQLCRGBMatrixPropertyValue).toString();
×
UNCOV
480
            setProperty(name, value);
×
UNCOV
481
            root.skipCurrentElement();
×
482
        }
UNCOV
483
        else if (root.name() == KXMLQLCRGBMatrixDimmerControl)
×
484
        {
UNCOV
485
            setDimmerControl(root.readElementText().toInt());
×
486
        }
487
        else
488
        {
UNCOV
489
            qWarning() << Q_FUNC_INFO << "Unknown RGB matrix tag:" << root.name();
×
UNCOV
490
            root.skipCurrentElement();
×
491
        }
492
    }
493

494
    return true;
495
}
496

497
bool RGBMatrix::saveXML(QXmlStreamWriter *doc)
1✔
498
{
499
    Q_ASSERT(doc != NULL);
500

501
    /* Function tag */
502
    doc->writeStartElement(KXMLQLCFunction);
1✔
503

504
    /* Common attributes */
505
    saveXMLCommon(doc);
1✔
506

507
    /* Speeds */
508
    saveXMLSpeed(doc);
1✔
509

510
    /* Direction */
511
    saveXMLDirection(doc);
1✔
512

513
    /* Run order */
514
    saveXMLRunOrder(doc);
1✔
515

516
    /* Algorithm */
517
    if (m_algorithm != NULL)
1✔
518
        m_algorithm->saveXML(doc);
1✔
519

520
    /* LEGACY - Dimmer Control */
521
    if (dimmerControl())
1✔
UNCOV
522
        doc->writeTextElement(KXMLQLCRGBMatrixDimmerControl, QString::number(dimmerControl()));
×
523

524
    /* Colors */
525
    for (int i = 0; i < m_rgbColors.count(); i++)
6✔
526
    {
527
        doc->writeStartElement(KXMLQLCRGBMatrixColor);
5✔
528
        doc->writeAttribute(KXMLQLCRGBMatrixColorIndex, QString::number(i));
5✔
529
        doc->writeCharacters(QString::number(m_rgbColors.at(i).rgb()));
5✔
530
        doc->writeEndElement();
5✔
531
    }
532

533
    /* Control Mode */
534
    doc->writeTextElement(KXMLQLCRGBMatrixControlMode, RGBMatrix::controlModeToString(m_controlMode));
1✔
535

536
    /* Fixture Group */
537
    doc->writeTextElement(KXMLQLCRGBMatrixFixtureGroup, QString::number(fixtureGroup()));
1✔
538

539
    /* Properties */
540
    QHashIterator<QString, QString> it(m_properties);
1✔
541
    while (it.hasNext())
1✔
542
    {
543
        it.next();
544
        doc->writeStartElement(KXMLQLCRGBMatrixProperty);
×
UNCOV
545
        doc->writeAttribute(KXMLQLCRGBMatrixPropertyName, it.key());
×
546
        doc->writeAttribute(KXMLQLCRGBMatrixPropertyValue, it.value());
×
UNCOV
547
        doc->writeEndElement();
×
548
    }
549

550
    /* End the <Function> tag */
551
    doc->writeEndElement();
1✔
552

553
    return true;
1✔
554
}
555

556
/****************************************************************************
557
 * Running
558
 ****************************************************************************/
559

UNCOV
560
void RGBMatrix::tap()
×
561
{
UNCOV
562
    if (stopped() == false)
×
563
    {
564
        FixtureGroup* grp = doc()->fixtureGroup(fixtureGroup());
×
565
        // Filter out taps that are too close to each other
UNCOV
566
        if (grp != NULL && uint(m_roundTime->elapsed()) >= (duration() / 4))
×
567
        {
UNCOV
568
            roundCheck();
×
UNCOV
569
            resetElapsed();
×
570
        }
571
    }
UNCOV
572
}
×
573

574
void RGBMatrix::preRun(MasterTimer *timer)
×
575
{
576
    {
UNCOV
577
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
578

579
        m_group = doc()->fixtureGroup(m_fixtureGroupID);
×
580
        if (m_group == NULL)
×
581
        {
582
            // No fixture group to control
583
            stop(FunctionParent::master());
×
584
            return;
585
        }
586

UNCOV
587
        if (m_algorithm != NULL)
×
588
        {
589
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
590
            if (m_requestEngineCreation)
591
            {
592
                // And here's the hack: Qt6 JS engine is not thread safe. Nice job!
593
                // It's not possible to use the instance created in the main thread
594
                // so we need to create a clone on the fly from the MasterTimer thread :vomiting_face:
595
                RGBAlgorithm *algo = m_algorithm->clone();
596
                delete m_algorithm;
597
                m_algorithm = algo;
598
                m_requestEngineCreation = false;
599
            }
600
#endif
601

602
            // Copy direction from parent class direction
UNCOV
603
            m_stepHandler->initializeDirection(direction(), m_rgbColors[0], m_rgbColors[1], m_stepsCount, m_algorithm);
×
604

605
            if (m_algorithm->type() == RGBAlgorithm::Script)
×
606
            {
UNCOV
607
                RGBScript *script = static_cast<RGBScript*> (m_algorithm);
×
UNCOV
608
                QHashIterator<QString, QString> it(m_properties);
×
609
                while (it.hasNext())
×
610
                {
611
                    it.next();
UNCOV
612
                    script->setProperty(it.key(), it.value());
×
613
                }
614
            }
UNCOV
615
            else if (m_algorithm->type() == RGBAlgorithm::Image)
×
616
            {
UNCOV
617
                RGBImage *image = static_cast<RGBImage*> (m_algorithm);
×
618
                if (image->animatedSource())
×
619
                    image->rewindAnimation();
×
620
            }
621
        }
622
    }
623

624
    m_roundTime->restart();
×
625

626
    Function::preRun(timer);
×
627
}
628

UNCOV
629
void RGBMatrix::write(MasterTimer *timer, QList<Universe *> universes)
×
630
{
631
    Q_UNUSED(timer);
632

633
    {
UNCOV
634
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
UNCOV
635
        if (m_group == NULL)
×
636
        {
637
            // No fixture group to control
UNCOV
638
            stop(FunctionParent::master());
×
639
            return;
640
        }
641

642
        // No time to do anything.
UNCOV
643
        if (duration() == 0)
×
644
            return;
645

646
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
647
        if (m_algorithm != NULL && m_requestEngineCreation)
648
        {
649
            // And here's the hack: Qt6 JS engine is not thread safe. Nice job!
650
            // It's not possible to use the instance created in the main thread
651
            // so we need to create a clone on the fly from the MasterTimer thread :vomiting_face:
652
            RGBAlgorithm *algo = m_algorithm->clone();
653
            delete m_algorithm;
654
            m_algorithm = algo;
655
            m_requestEngineCreation = false;
656
        }
657
#endif
658

659
        // Invalid/nonexistent script
UNCOV
660
        if (m_algorithm == NULL || m_algorithm->apiVersion() == 0)
×
661
            return;
×
662

663
        if (isPaused() == false)
×
664
        {
665
            // Get a new map every time elapsed is reset to zero
666
            if (elapsed() < MasterTimer::tick())
×
667
            {
UNCOV
668
                if (tempoType() == Beats)
×
669
                    m_stepBeatDuration = beatsToTime(duration(), timer->beatTimeDuration());
×
670

671
                //qDebug() << "RGBMatrix step" << m_stepHandler->currentStepIndex() << ", color:" << QString::number(m_stepHandler->stepColor().rgb(), 16);
672
                m_algorithm->rgbMap(m_group->size(), m_stepHandler->stepColor().rgb(),
×
UNCOV
673
                                    m_stepHandler->currentStepIndex(), m_stepHandler->m_map);
×
UNCOV
674
                updateMapChannels(m_stepHandler->m_map, m_group, universes);
×
675
            }
676
        }
677
    }
678

UNCOV
679
    if (isPaused() == false)
×
680
    {
681
        // Increment the ms elapsed time
UNCOV
682
        incrementElapsed();
×
683

684
        /* Check if we need to change direction, stop completely or go to next step
685
         * The cases are:
686
         * 1- time tempo type: act normally, on ms elapsed time
687
         * 2- beat tempo type, beat occurred: check if the elapsed beats is a multiple of
688
         *    the step beat duration. If so, proceed to the next step
689
         * 3- beat tempo type, not beat: if the ms elapsed time reached the step beat
690
         *    duration in ms, and the ms time to the next beat is not less than 1/16 of
691
         *    the step beat duration in ms, then proceed to the next step. If the ms time to the
692
         *    next beat is less than 1/16 of the step beat duration in ms, then defer the step
693
         *    change to case #2, to resync the matrix to the next beat
694
         */
UNCOV
695
        if (tempoType() == Time && elapsed() >= duration())
×
696
        {
697
            roundCheck();
×
698
        }
UNCOV
699
        else if (tempoType() == Beats)
×
700
        {
701
            if (timer->isBeat())
×
702
            {
UNCOV
703
                incrementElapsedBeats();
×
704
                qDebug() << "Elapsed beats:" << elapsedBeats() << ", time elapsed:" << elapsed() << ", step time:" << m_stepBeatDuration;
705
                if (elapsedBeats() % duration() == 0)
×
706
                {
UNCOV
707
                    roundCheck();
×
UNCOV
708
                    resetElapsed();
×
709
                }
710
            }
UNCOV
711
            else if (elapsed() >= m_stepBeatDuration && (uint)timer->timeToNextBeat() > m_stepBeatDuration / 16)
×
712
            {
713
                qDebug() << "Elapsed exceeded";
714
                roundCheck();
×
715
            }
716
        }
717
    }
718
}
719

UNCOV
720
void RGBMatrix::postRun(MasterTimer *timer, QList<Universe *> universes)
×
721
{
UNCOV
722
    uint fadeout = overrideFadeOutSpeed() == defaultSpeed() ? fadeOutSpeed() : overrideFadeOutSpeed();
×
723

724
    /* If no fade out is needed, dismiss all the requested faders.
725
     * Otherwise, set all the faders to fade out and let Universe dismiss them
726
     * when done */
UNCOV
727
    if (fadeout == 0)
×
728
    {
729
        dismissAllFaders();
×
730
    }
731
    else
732
    {
UNCOV
733
        if (tempoType() == Beats)
×
UNCOV
734
            fadeout = beatsToTime(fadeout, timer->beatTimeDuration());
×
735

736
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap.values())
×
737
        {
738
            if (!fader.isNull())
×
739
                fader->setFadeOut(true, fadeout);
×
740
        }
741
    }
742

743
    m_fadersMap.clear();
×
744

745
    {
746
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
UNCOV
747
        if (m_algorithm != NULL)
×
UNCOV
748
            m_algorithm->postRun();
×
749
    }
750

751
    Function::postRun(timer, universes);
×
752
}
×
753

754
void RGBMatrix::roundCheck()
×
755
{
756
    QMutexLocker algorithmLocker(&m_algorithmMutex);
×
757
    if (m_algorithm == NULL)
×
758
        return;
759

760
    if (m_stepHandler->checkNextStep(runOrder(), m_rgbColors[0], m_rgbColors[1], m_stepsCount) == false)
×
UNCOV
761
        stop(FunctionParent::master());
×
762

UNCOV
763
    m_roundTime->restart();
×
764

UNCOV
765
    if (tempoType() == Beats)
×
UNCOV
766
        roundElapsed(m_stepBeatDuration);
×
767
    else
768
        roundElapsed(duration());
×
769
}
770

771
FadeChannel *RGBMatrix::getFader(Universe *universe, quint32 fixtureID, quint32 channel)
×
772
{
773
    // get the universe Fader first. If doesn't exist, create it
774
    if (universe == NULL)
×
775
        return NULL;
776

777
    QSharedPointer<GenericFader> fader = m_fadersMap.value(universe->id(), QSharedPointer<GenericFader>());
×
UNCOV
778
    if (fader.isNull())
×
779
    {
780
        fader = universe->requestFader();
×
UNCOV
781
        fader->adjustIntensity(getAttributeValue(Intensity));
×
782
        fader->setBlendMode(blendMode());
×
783
        fader->setName(name());
×
784
        fader->setParentFunctionID(id());
×
UNCOV
785
        m_fadersMap[universe->id()] = fader;
×
786
    }
787

788
    return fader->getChannelFader(doc(), universe, fixtureID, channel);
×
789
}
790

UNCOV
791
void RGBMatrix::updateFaderValues(FadeChannel *fc, uchar value, uint fadeTime)
×
792
{
793
    fc->setStart(fc->current());
×
794
    fc->setTarget(value);
×
UNCOV
795
    fc->setElapsed(0);
×
UNCOV
796
    fc->setReady(false);
×
797
    // fade in/out depends on target value
798
    if (value == 0)
×
UNCOV
799
        fc->setFadeTime(fadeOutSpeed());
×
800
    else
UNCOV
801
        fc->setFadeTime(fadeTime);
×
UNCOV
802
}
×
803

804
void RGBMatrix::updateMapChannels(const RGBMap& map, const FixtureGroup *grp, QList<Universe *> universes)
×
805
{
806
    uint fadeTime = (overrideFadeInSpeed() == defaultSpeed()) ? fadeInSpeed() : overrideFadeInSpeed();
×
807

808
    // Create/modify fade channels for ALL heads in the group
UNCOV
809
    QMapIterator<QLCPoint, GroupHead> it(grp->headsMap());
×
810
    while (it.hasNext())
×
811
    {
812
        it.next();
UNCOV
813
        QLCPoint pt = it.key();
×
814
        GroupHead grpHead = it.value();
×
UNCOV
815
        Fixture *fxi = doc()->fixture(grpHead.fxi);
×
UNCOV
816
        if (fxi == NULL)
×
817
            continue;
×
818

UNCOV
819
        QLCFixtureHead head = fxi->head(grpHead.head);
×
820

821
        if (pt.y() >= map.count() || pt.x() >= map[pt.y()].count())
×
UNCOV
822
            continue;
×
823

UNCOV
824
        uint col = map[pt.y()][pt.x()];
×
UNCOV
825
        QVector<quint32> channelList;
×
UNCOV
826
        QVector<uchar> valueList;
×
827

UNCOV
828
        if (m_controlMode == ControlModeRgb)
×
829
        {
UNCOV
830
            channelList = head.rgbChannels();
×
831

UNCOV
832
            if (channelList.size() == 3)
×
833
            {
UNCOV
834
                valueList.append(qRed(col));
×
UNCOV
835
                valueList.append(qGreen(col));
×
UNCOV
836
                valueList.append(qBlue(col));
×
837
            }
838
            else
839
            {
840
                channelList = head.cmyChannels();
×
841

UNCOV
842
                if (channelList.size() == 3)
×
843
                {
844
                    // CMY color mixing
845
                    QColor cmyCol(col);
×
846
                    valueList.append(cmyCol.cyan());
×
UNCOV
847
                    valueList.append(cmyCol.magenta());
×
UNCOV
848
                    valueList.append(cmyCol.yellow());
×
849
                }
850
            }
851
        }
852
        else if (m_controlMode == ControlModeShutter)
×
853
        {
UNCOV
854
            channelList = head.shutterChannels();
×
855

UNCOV
856
            if (channelList.size())
×
857
            {
858
                // make sure only one channel is in the list
859
                channelList.resize(1);
×
860
                valueList.append(rgbToGrey(col));
×
861
            }
862
        }
UNCOV
863
        else if (m_controlMode == ControlModeDimmer || m_dimmerControl)
×
864
        {
865
            // Collect all dimmers that affect current head:
866
            // They are the master dimmer (affects whole fixture)
867
            // and per-head dimmer.
868
            //
869
            // If there are no RGB or CMY channels, the least important* dimmer channel
870
            // is used to create grayscale image.
871
            //
872
            // The rest of the dimmer channels are set to full if dimmer control is
873
            // enabled and target color is > 0 (see
874
            // http://www.qlcplus.org/forum/viewtopic.php?f=29&t=11090)
875
            //
876
            // Note: If there is only one head, and only one dimmer channel,
877
            // make it a master dimmer in fixture definition.
878
            //
879
            // *least important - per head dimmer if present,
880
            // otherwise per fixture dimmer if present
881

882
            quint32 masterDim = fxi->masterIntensityChannel();
×
UNCOV
883
            quint32 headDim = head.channelNumber(QLCChannel::Intensity, QLCChannel::MSB);
×
884

UNCOV
885
            if (masterDim != QLCChannel::invalid())
×
886
            {
UNCOV
887
                channelList.append(masterDim);
×
UNCOV
888
                valueList.append(rgbToGrey(col));
×
889
            }
890

UNCOV
891
            if (headDim != QLCChannel::invalid() && headDim != masterDim)
×
892
            {
893
                channelList.append(headDim);
×
UNCOV
894
                valueList.append(col == 0 ? 0 : 255);
×
895
            }
×
896
        }
897
        else
898
        {
899
            if (m_controlMode == ControlModeWhite)
×
UNCOV
900
                channelList.append(head.channelNumber(QLCChannel::White, QLCChannel::MSB));
×
901
            else if (m_controlMode == ControlModeAmber)
×
902
                channelList.append(head.channelNumber(QLCChannel::Amber, QLCChannel::MSB));
×
UNCOV
903
            else if (m_controlMode == ControlModeUV)
×
UNCOV
904
                channelList.append(head.channelNumber(QLCChannel::UV, QLCChannel::MSB));
×
905

906
            valueList.append(rgbToGrey(col));
×
907
        }
908

UNCOV
909
        quint32 absAddress = fxi->universeAddress();
×
910

UNCOV
911
        for (int i = 0; i < channelList.count(); i++)
×
912
        {
913
            if (channelList.at(i) == QLCChannel::invalid())
×
UNCOV
914
                continue;
×
915

UNCOV
916
            quint32 universeIndex = floor((absAddress + channelList.at(i)) / 512);
×
917

918
            FadeChannel *fc = getFader(universes.at(universeIndex), grpHead.fxi, channelList.at(i));
×
UNCOV
919
            updateFaderValues(fc, valueList.at(i), fadeTime);
×
920
        }
921
    }
UNCOV
922
}
×
923

924
uchar RGBMatrix::rgbToGrey(uint col)
×
925
{
926
    // the weights are taken from
927
    // https://en.wikipedia.org/wiki/YUV#SDTV_with_BT.601
UNCOV
928
    return (0.299 * qRed(col) + 0.587 * qGreen(col) + 0.114 * qBlue(col));
×
929
}
930

931
/*********************************************************************
932
 * Attributes
933
 *********************************************************************/
934

UNCOV
935
int RGBMatrix::adjustAttribute(qreal fraction, int attributeId)
×
936
{
UNCOV
937
    int attrIndex = Function::adjustAttribute(fraction, attributeId);
×
938

UNCOV
939
    if (attrIndex == Intensity)
×
940
    {
UNCOV
941
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap.values())
×
942
        {
UNCOV
943
            if (!fader.isNull())
×
UNCOV
944
                fader->adjustIntensity(getAttributeValue(Function::Intensity));
×
945
        }
946
    }
947

UNCOV
948
    return attrIndex;
×
949
}
950

951
/*************************************************************************
952
 * Blend mode
953
 *************************************************************************/
954

955
void RGBMatrix::setBlendMode(Universe::BlendMode mode)
×
956
{
UNCOV
957
    if (mode == blendMode())
×
958
        return;
959

UNCOV
960
    foreach (QSharedPointer<GenericFader> fader, m_fadersMap.values())
×
961
    {
UNCOV
962
        if (!fader.isNull())
×
UNCOV
963
            fader->setBlendMode(mode);
×
964
    }
965

UNCOV
966
    Function::setBlendMode(mode);
×
UNCOV
967
    emit changed(id());
×
968
}
969

970
/*************************************************************************
971
 * Control Mode
972
 *************************************************************************/
973

974
RGBMatrix::ControlMode RGBMatrix::controlMode() const
2✔
975
{
976
    return m_controlMode;
2✔
977
}
978

979
void RGBMatrix::setControlMode(RGBMatrix::ControlMode mode)
3✔
980
{
981
    m_controlMode = mode;
3✔
982
    emit changed(id());
3✔
983
}
3✔
984

985
RGBMatrix::ControlMode RGBMatrix::stringToControlMode(QString mode)
1✔
986
{
987
    if (mode == KXMLQLCRGBMatrixControlModeRgb)
1✔
988
        return ControlModeRgb;
UNCOV
989
    else if (mode == KXMLQLCRGBMatrixControlModeAmber)
×
990
        return ControlModeAmber;
UNCOV
991
    else if (mode == KXMLQLCRGBMatrixControlModeWhite)
×
992
        return ControlModeWhite;
UNCOV
993
    else if (mode == KXMLQLCRGBMatrixControlModeUV)
×
994
        return ControlModeUV;
UNCOV
995
    else if (mode == KXMLQLCRGBMatrixControlModeDimmer)
×
996
        return ControlModeDimmer;
UNCOV
997
    else if (mode == KXMLQLCRGBMatrixControlModeShutter)
×
UNCOV
998
        return ControlModeShutter;
×
999

1000
    return ControlModeRgb;
1001
}
1002

1003
QString RGBMatrix::controlModeToString(RGBMatrix::ControlMode mode)
1✔
1004
{
1005
    switch(mode)
1✔
1006
    {
1007
        default:
1✔
1008
        case ControlModeRgb:
1009
            return QString(KXMLQLCRGBMatrixControlModeRgb);
1✔
1010
        break;
UNCOV
1011
        case ControlModeAmber:
×
UNCOV
1012
            return QString(KXMLQLCRGBMatrixControlModeAmber);
×
1013
        break;
UNCOV
1014
        case ControlModeWhite:
×
UNCOV
1015
            return QString(KXMLQLCRGBMatrixControlModeWhite);
×
1016
        break;
UNCOV
1017
        case ControlModeUV:
×
UNCOV
1018
            return QString(KXMLQLCRGBMatrixControlModeUV);
×
1019
        break;
UNCOV
1020
        case ControlModeDimmer:
×
UNCOV
1021
            return QString(KXMLQLCRGBMatrixControlModeDimmer);
×
1022
        break;
UNCOV
1023
        case ControlModeShutter:
×
UNCOV
1024
            return QString(KXMLQLCRGBMatrixControlModeShutter);
×
1025
        break;
1026
    }
1027
}
1028

1029

1030
/*************************************************************************
1031
 *************************************************************************
1032
 *                          RGBMatrixStep class
1033
 *************************************************************************
1034
 *************************************************************************/
1035

1036
RGBMatrixStep::RGBMatrixStep()
10✔
1037
    : m_direction(Function::Forward)
1038
    , m_currentStepIndex(0)
1039
    , m_stepColor(QColor())
1040
    , m_crDelta(0)
1041
    , m_cgDelta(0)
1042
    , m_cbDelta(0)
10✔
1043
{
1044

1045
}
10✔
1046

UNCOV
1047
void RGBMatrixStep::setCurrentStepIndex(int index)
×
1048
{
UNCOV
1049
    m_currentStepIndex = index;
×
UNCOV
1050
}
×
1051

1052
int RGBMatrixStep::currentStepIndex() const
1✔
1053
{
1054
    return m_currentStepIndex;
1✔
1055
}
1056

1057
void RGBMatrixStep::calculateColorDelta(QColor startColor, QColor endColor, RGBAlgorithm *algorithm)
16✔
1058
{
1059
    m_crDelta = 0;
16✔
1060
    m_cgDelta = 0;
16✔
1061
    m_cbDelta = 0;
16✔
1062

1063
    if (endColor.isValid() && algorithm != NULL && algorithm->acceptColors() > 1)
16✔
1064
    {
1065
        m_crDelta = endColor.red() - startColor.red();
10✔
1066
        m_cgDelta = endColor.green() - startColor.green();
10✔
1067
        m_cbDelta = endColor.blue() - startColor.blue();
10✔
1068

1069
        //qDebug() << "Color deltas:" << m_crDelta << m_cgDelta << m_cbDelta;
1070
    }
1071
}
16✔
1072

1073
void RGBMatrixStep::setStepColor(QColor color)
×
1074
{
UNCOV
1075
    m_stepColor = color;
×
1076
}
×
1077

1078
QColor RGBMatrixStep::stepColor()
6✔
1079
{
1080
    return m_stepColor;
6✔
1081
}
1082

UNCOV
1083
void RGBMatrixStep::updateStepColor(int stepIndex, QColor startColor, int stepsCount)
×
1084
{
1085
    if (stepsCount <= 0)
×
1086
        return;
1087

UNCOV
1088
    if (stepsCount == 1)
×
1089
    {
1090
        m_stepColor = startColor;
×
1091
    }
1092
    else
1093
    {
1094
        m_stepColor.setRed(startColor.red() + (m_crDelta * stepIndex / (stepsCount - 1)));
×
UNCOV
1095
        m_stepColor.setGreen(startColor.green() + (m_cgDelta * stepIndex / (stepsCount - 1)));
×
1096
        m_stepColor.setBlue(startColor.blue() + (m_cbDelta * stepIndex / (stepsCount - 1)));
×
1097
    }
1098

1099
    //qDebug() << "RGBMatrix step" << stepIndex << ", color:" << QString::number(m_stepColor.rgb(), 16);
1100
}
1101

UNCOV
1102
void RGBMatrixStep::initializeDirection(Function::Direction direction, QColor startColor, QColor endColor, int stepsCount, RGBAlgorithm *algorithm)
×
1103
{
UNCOV
1104
    m_direction = direction;
×
1105

1106
    if (m_direction == Function::Forward)
×
1107
    {
1108
        setCurrentStepIndex(0);
×
1109
        setStepColor(startColor);
×
1110
    }
1111
    else
1112
    {
UNCOV
1113
        setCurrentStepIndex(stepsCount - 1);
×
1114

UNCOV
1115
        if (endColor.isValid())
×
1116
            setStepColor(endColor);
×
1117
        else
UNCOV
1118
            setStepColor(startColor);
×
1119
    }
1120

1121
    calculateColorDelta(startColor, endColor, algorithm);
×
UNCOV
1122
}
×
1123

UNCOV
1124
bool RGBMatrixStep::checkNextStep(Function::RunOrder order,
×
1125
                                  QColor startColor, QColor endColor, int stepsNumber)
1126
{
UNCOV
1127
    if (order == Function::PingPong)
×
1128
    {
UNCOV
1129
        if (m_direction == Function::Forward && (m_currentStepIndex + 1) == stepsNumber)
×
1130
        {
1131
            m_direction = Function::Backward;
×
UNCOV
1132
            m_currentStepIndex = stepsNumber - 2;
×
UNCOV
1133
            if (endColor.isValid())
×
UNCOV
1134
                m_stepColor = endColor;
×
1135

UNCOV
1136
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1137
        }
UNCOV
1138
        else if (m_direction == Function::Backward && (m_currentStepIndex - 1) < 0)
×
1139
        {
UNCOV
1140
            m_direction = Function::Forward;
×
1141
            m_currentStepIndex = 1;
×
1142
            m_stepColor = startColor;
×
UNCOV
1143
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1144
        }
1145
        else
1146
        {
1147
            if (m_direction == Function::Forward)
×
UNCOV
1148
                m_currentStepIndex++;
×
1149
            else
UNCOV
1150
                m_currentStepIndex--;
×
UNCOV
1151
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1152
        }
1153
    }
1154
    else if (order == Function::SingleShot)
×
1155
    {
1156
        if (m_direction == Function::Forward)
×
1157
        {
UNCOV
1158
            if (m_currentStepIndex >= stepsNumber - 1)
×
1159
                return false;
1160
            else
1161
            {
UNCOV
1162
                m_currentStepIndex++;
×
UNCOV
1163
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1164
            }
1165
        }
1166
        else
1167
        {
UNCOV
1168
            if (m_currentStepIndex <= 0)
×
1169
                return false;
1170
            else
1171
            {
UNCOV
1172
                m_currentStepIndex--;
×
UNCOV
1173
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1174
            }
1175
        }
1176
    }
1177
    else
1178
    {
UNCOV
1179
        if (m_direction == Function::Forward)
×
1180
        {
UNCOV
1181
            if (m_currentStepIndex >= stepsNumber - 1)
×
1182
            {
UNCOV
1183
                m_currentStepIndex = 0;
×
UNCOV
1184
                m_stepColor = startColor;
×
1185
            }
1186
            else
1187
            {
UNCOV
1188
                m_currentStepIndex++;
×
UNCOV
1189
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1190
            }
1191
        }
1192
        else
1193
        {
UNCOV
1194
            if (m_currentStepIndex <= 0)
×
1195
            {
UNCOV
1196
                m_currentStepIndex = stepsNumber - 1;
×
UNCOV
1197
                if (endColor.isValid())
×
UNCOV
1198
                    m_stepColor = endColor;
×
1199
            }
1200
            else
1201
            {
UNCOV
1202
                m_currentStepIndex--;
×
UNCOV
1203
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1204
            }
1205
        }
1206
    }
1207

1208
    return true;
1209
}
1210

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