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

mcallegari / qlcplus / 27918870711

21 Jun 2026 09:28PM UTC coverage: 35.287% (-0.009%) from 35.296%
27918870711

push

github

mcallegari
engine: fix RGBMatrix dimmer mode not applied correctly to generic dimmers

0 of 3 new or added lines in 1 file covered. (0.0%)

422 existing lines in 3 files now uncovered.

18536 of 52529 relevant lines covered (35.29%)

41043.21 hits per line

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

38.9
/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      QStringLiteral("MonoColor")
39
#define KXMLQLCRGBMatrixEndColor        QStringLiteral("EndColor")
40
#define KXMLQLCRGBMatrixColor           QStringLiteral("Color")
41
#define KXMLQLCRGBMatrixColorIndex      QStringLiteral("Index")
42

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

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

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

58
static const int RGBMatrixColorMask = 0x00FFFFFF;
59

60
/****************************************************************************
61
 * Initialization
62
 ****************************************************************************/
63

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

85
    m_rgbColors.fill(QColor(), RGBAlgorithmColorDisplayCount);
9✔
86
    setColor(0, Qt::red);
9✔
87

88
    setAlgorithm(RGBAlgorithm::algorithm(doc, "Stripes"));
9✔
89

90
    for (int i = 0; i < ColorAttributeCount; ++i)
54✔
91
    {
92
        registerAttribute(tr("Color %1").arg(i + 1), LastWins | Single, -1.0, 16777215.0,
54✔
93
                          getColor(i).isValid() ? int(getColor(i).rgb() & RGBMatrixColorMask) : -1);
99✔
94
    }
95

96
    int algoCount = RGBAlgorithm::algorithms(doc).count();
9✔
97
    registerAttribute(tr("Pattern"), LastWins | Single, 0.0, algoCount > 0 ? algoCount - 1 : 0, algorithmIndex());
9✔
98
}
9✔
99

100
RGBMatrix::~RGBMatrix()
11✔
101
{
102
    //if (m_runAlgorithm != NULL)
103
    //    delete m_runAlgorithm;
104
    delete m_algorithm;
9✔
105
    delete m_stepHandler;
9✔
106
}
11✔
107

UNCOV
108
QIcon RGBMatrix::getIcon() const
×
109
{
UNCOV
110
    return QIcon(":/rgbmatrix.png");
×
111
}
112

113
int RGBMatrix::algorithmIndex() const
22✔
114
{
115
    if (m_algorithm == NULL || doc() == NULL)
22✔
UNCOV
116
        return 0;
×
117

118
    QStringList algoList = RGBAlgorithm::algorithms(doc());
22✔
119
    int idx = algoList.indexOf(m_algorithm->name());
22✔
120
    return idx >= 0 ? idx : 0;
22✔
121
}
22✔
122

123
void RGBMatrix::setTotalDuration(quint32 msec)
1✔
124
{
125
    QMutexLocker algorithmLocker(&m_algorithmMutex);
1✔
126

127
    if (m_algorithm == NULL)
1✔
UNCOV
128
        return;
×
129

130
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
1✔
131
    if (grp == NULL)
1✔
UNCOV
132
        return;
×
133

134
    int steps = m_algorithm->rgbMapStepCount(grp->size());
1✔
135
    setDuration(msec / steps);
1✔
136
}
1✔
137

138
quint32 RGBMatrix::totalDuration()
3✔
139
{
140
    QMutexLocker algorithmLocker(&m_algorithmMutex);
3✔
141

142
    if (m_algorithm == NULL)
3✔
UNCOV
143
        return 0;
×
144

145
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
3✔
146
    if (grp == NULL)
3✔
147
        return 0;
1✔
148

149
    //qDebug () << "Algorithm steps:" << m_algorithm->rgbMapStepCount(grp->size());
150
    return m_algorithm->rgbMapStepCount(grp->size()) * duration();
2✔
151
}
3✔
152

153
void RGBMatrix::setDimmerControl(bool dimmerControl)
1✔
154
{
155
    m_dimmerControl = dimmerControl;
1✔
156
}
1✔
157

158
bool RGBMatrix::dimmerControl() const
2✔
159
{
160
    return m_dimmerControl;
2✔
161
}
162

163
/****************************************************************************
164
 * Copying
165
 ****************************************************************************/
166

167
Function* RGBMatrix::createCopy(Doc* doc, bool addToDoc)
1✔
168
{
169
    Q_ASSERT(doc != NULL);
1✔
170

171
    Function *copy = new RGBMatrix(doc);
1✔
172
    if (copy->copyFrom(this) == false)
1✔
173
    {
174
        delete copy;
×
UNCOV
175
        copy = NULL;
×
176
    }
177
    if (addToDoc == true && doc->addFunction(copy) == false)
1✔
178
    {
179
        delete copy;
×
UNCOV
180
        copy = NULL;
×
181
    }
182

183
    return copy;
1✔
184
}
185

186
bool RGBMatrix::copyFrom(const Function* function)
1✔
187
{
188
    const RGBMatrix *mtx = qobject_cast<const RGBMatrix*> (function);
1✔
189
    if (mtx == NULL)
1✔
UNCOV
190
        return false;
×
191

192
    setDimmerControl(mtx->dimmerControl());
1✔
193
    setFixtureGroup(mtx->fixtureGroup());
1✔
194

195
    m_rgbColors.clear();
1✔
196
    foreach (QColor col, mtx->getColors())
7✔
197
        m_rgbColors.append(col);
6✔
198

199
    if (mtx->algorithm() != NULL)
1✔
200
        setAlgorithm(mtx->algorithm()->clone());
1✔
201
    else
UNCOV
202
        setAlgorithm(NULL);
×
203

204
    setControlMode(mtx->controlMode());
1✔
205

206
    return Function::copyFrom(function);
1✔
207
}
208

209
/****************************************************************************
210
 * Fixtures
211
 ****************************************************************************/
212

213
quint32 RGBMatrix::fixtureGroup() const
35✔
214
{
215
    return m_fixtureGroupID;
35✔
216
}
217

218
void RGBMatrix::setFixtureGroup(quint32 id)
8✔
219
{
220
    m_fixtureGroupID = id;
8✔
221
    {
222
        QMutexLocker algoLocker(&m_algorithmMutex);
8✔
223
        m_group = doc()->fixtureGroup(m_fixtureGroupID);
8✔
224
    }
8✔
225
    m_stepsCount = algorithmStepsCount();
8✔
226
}
8✔
227

228
QList<quint32> RGBMatrix::components() const
2✔
229
{
230
    if (m_group != NULL)
2✔
231
        return m_group->fixtureList();
1✔
232

233
    return QList<quint32>();
1✔
234
}
235

236
/****************************************************************************
237
 * Algorithm
238
 ****************************************************************************/
239

240
void RGBMatrix::setAlgorithm(RGBAlgorithm *algo)
13✔
241
{
242
    {
243
        QMutexLocker algorithmLocker(&m_algorithmMutex);
13✔
244
        delete m_algorithm;
13✔
245
        m_algorithm = algo;
13✔
246

247
        m_requestEngineCreation = true;
13✔
248

249
        /** If there's been a change of Script algorithm "on the fly",
250
         *  then re-apply the properties currently set in this RGBMatrix */
251
        if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
13✔
252
        {
253
            RGBScript *script = static_cast<RGBScript*> (m_algorithm);
13✔
254
            QMapIterator<QString, QString> it(m_properties);
13✔
255
            while (it.hasNext())
13✔
256
            {
257
                it.next();
×
UNCOV
258
                if (script->setProperty(it.key(), it.value()) == false)
×
259
                {
260
                    /** If the new algorithm doesn't expose a property,
261
                     *  then remove it from the cached list, otherwise
262
                     *  it would be carried around forever (and saved on XML) */
UNCOV
263
                    m_properties.take(it.key());
×
264
                }
265
            }
266

267
            QVector<uint> colors = script->rgbMapGetColors();
13✔
268
            for (int i = 0; i < colors.count(); i++)
13✔
UNCOV
269
                m_rgbColors.replace(i, QColor::fromRgb(colors.at(i)));
×
270
        }
13✔
271
    }
13✔
272
    m_stepsCount = algorithmStepsCount();
13✔
273

274
    if (m_applyingStyleAttributes == false)
13✔
275
        Function::adjustAttribute(algorithmIndex(), PatternAttr);
13✔
276

277
    emit changed(id());
13✔
278
}
13✔
279

280
RGBAlgorithm *RGBMatrix::algorithm() const
18✔
281
{
282
    return m_algorithm;
18✔
283
}
284

285
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
286
QMutex& RGBMatrix::algorithmMutex()
287
{
288
    return m_algorithmMutex;
289
}
290
#else
UNCOV
291
QRecursiveMutex& RGBMatrix::algorithmMutex()
×
292
{
UNCOV
293
    return m_algorithmMutex;
×
294
}
295
#endif
296

297

298
int RGBMatrix::stepsCount() const
2✔
299
{
300
    return m_stepsCount;
2✔
301
}
302

303
int RGBMatrix::algorithmStepsCount()
22✔
304
{
305
    QMutexLocker algorithmLocker(&m_algorithmMutex);
22✔
306

307
    if (m_algorithm == NULL)
22✔
UNCOV
308
        return 0;
×
309

310
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
22✔
311
    if (grp != NULL)
22✔
312
        return m_algorithm->rgbMapStepCount(grp->size());
6✔
313

314
    return 0;
16✔
315
}
22✔
316

317
void RGBMatrix::previewMap(int step, RGBMatrixStep *handler)
7✔
318
{
319
    QMutexLocker algorithmLocker(&m_algorithmMutex);
7✔
320
    if (m_algorithm == NULL || handler == NULL)
7✔
UNCOV
321
        return;
×
322

323
    if (m_group == NULL)
7✔
324
        m_group = doc()->fixtureGroup(fixtureGroup());
1✔
325

326
    if (m_group != NULL)
7✔
327
    {
328
        setMapColors(m_algorithm);
6✔
329
        m_algorithm->rgbMap(m_group->size(), handler->stepColor().rgb(), step, handler->m_map);
6✔
330
    }
331
}
7✔
332

333
/****************************************************************************
334
 * Color
335
 ****************************************************************************/
336

337
void RGBMatrix::setColor(int i, QColor c)
25✔
338
{
339
    if (i < 0)
25✔
UNCOV
340
        return;
×
341

342
    if (i >= m_rgbColors.count())
25✔
UNCOV
343
        m_rgbColors.resize(i + 1);
×
344

345
    m_rgbColors.replace(i, c);
25✔
346
    {
347
        QMutexLocker algorithmLocker(&m_algorithmMutex);
25✔
348
        if (m_algorithm != NULL)
25✔
349
        {
350
            m_algorithm->setColors(m_rgbColors);
16✔
351
            updateColorDelta();
16✔
352
        }
353
    }
25✔
354
    setMapColors(m_algorithm);
25✔
355

356
    if (m_applyingStyleAttributes == false && i >= 0 && i < ColorAttributeCount)
25✔
357
        Function::adjustAttribute(c.isValid() ? int(c.rgb() & RGBMatrixColorMask) : -1, Color1Attr + i);
25✔
358

359
    emit changed(id());
25✔
360
}
361

362
QColor RGBMatrix::getColor(int i) const
64✔
363
{
364
    if (i < 0 || i >= m_rgbColors.count())
64✔
UNCOV
365
        return QColor();
×
366

367
    return m_rgbColors.at(i);
64✔
368
}
369

370
QVector<QColor> RGBMatrix::getColors() const
1✔
371
{
372
    return m_rgbColors;
1✔
373
}
374

375
void RGBMatrix::updateColorDelta()
16✔
376
{
377
    if (m_rgbColors.count() > 1)
16✔
378
        m_stepHandler->calculateColorDelta(m_rgbColors[0], m_rgbColors[1], m_algorithm);
16✔
379
}
16✔
380

381
void RGBMatrix::setMapColors(RGBAlgorithm *algorithm)
31✔
382
{
383
    QMutexLocker algorithmLocker(&m_algorithmMutex);
31✔
384
    if (algorithm == NULL)
31✔
385
        return;
9✔
386

387
    if (algorithm->apiVersion() < 3)
22✔
388
        return;
22✔
389

390
    if (m_group == NULL)
×
UNCOV
391
        m_group = doc()->fixtureGroup(fixtureGroup());
×
392

393
    QVector<unsigned int> rawColors;
×
394
    const int acceptColors = algorithm->acceptColors();
×
395
    rawColors.reserve(acceptColors);
×
UNCOV
396
    for (int i = 0; i < acceptColors; i++)
×
397
    {
UNCOV
398
        if (m_rgbColors.count() > i)
×
399
        {
400
            QColor col = m_rgbColors.at(i);
×
UNCOV
401
            rawColors.append(col.isValid() ? col.rgb() : 0);
×
402
        }
403
        else
404
        {
UNCOV
405
            rawColors.append(0);
×
406
        }
407
    }
408

UNCOV
409
    algorithm->rgbMapSetColors(rawColors);
×
410
}
31✔
411

412
/************************************************************************
413
 * Properties
414
 ************************************************************************/
415

416
void RGBMatrix::setProperty(QString propName, QString value)
1✔
417
{
418
    QMutexLocker algoLocker(&m_algorithmMutex);
1✔
419

420
    // Remember the old step count before changing it (used to scale the step index)
421
    int oldStepsCount = m_stepsCount;
1✔
422

423
    m_properties[propName] = value;
1✔
424
    if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
1✔
425
    {
426
        RGBScript *script = static_cast<RGBScript*> (m_algorithm);
1✔
427
        script->setProperty(propName, value);
1✔
428

429
        QVector<uint> colors = script->rgbMapGetColors();
1✔
430
        for (int i = 0; i < colors.count(); i++)
1✔
UNCOV
431
            setColor(i, QColor::fromRgb(colors.at(i)));
×
432
    }
1✔
433
    m_stepsCount = algorithmStepsCount();
1✔
434

435
    // Scale currentStepIndex to the new step count to preserve the phase.
436
    // We use m_continuousPhase (a continuous 0.0-1.0 value) instead of the discrete
437
    // stepIndex to avoid accumulating rounding errors over multiple speed changes.
438
    // Analogous to EFXFixture::durationChanged() using m_currentAngle.
439
    if (m_stepHandler != NULL && m_stepsCount > 0 && oldStepsCount != m_stepsCount)
1✔
440
    {
441
        // If m_continuousPhase is not up to date yet (first change),
442
        // compute it from the current stepIndex
UNCOV
443
        if (oldStepsCount > 0)
×
444
        {
UNCOV
445
            int currentStepIndex = m_stepHandler->currentStepIndex();
×
UNCOV
446
            m_continuousPhase = double(currentStepIndex) / double(oldStepsCount);
×
447
        }
448

449
        // Rescale the phase to the new step count (preserve the continuous phase,
450
        // not the discrete index)
UNCOV
451
        int newStepIndex = int(round(m_continuousPhase * double(m_stepsCount)));
×
452
        // Make sure it stays within range
UNCOV
453
        if (newStepIndex >= m_stepsCount)
×
UNCOV
454
            newStepIndex = m_stepsCount - 1;
×
UNCOV
455
        if (newStepIndex < 0)
×
UNCOV
456
            newStepIndex = 0;
×
UNCOV
457
        m_stepHandler->setCurrentStepIndex(newStepIndex);
×
458
    }
459
}
1✔
460

461
QString RGBMatrix::property(QString propName)
3✔
462
{
463
    QMutexLocker algoLocker(&m_algorithmMutex);
3✔
464

465
    /** If the property is cached, then return it right away */
466
    QMap<QString, QString>::iterator it = m_properties.find(propName);
3✔
467
    if (it != m_properties.end())
3✔
468
        return it.value();
1✔
469

470
    /** Otherwise, let's retrieve it from the Script */
471
    if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
2✔
472
    {
473
        RGBScript *script = static_cast<RGBScript*> (m_algorithm);
2✔
474
        return script->property(propName);
2✔
475
    }
476

477
    return QString();
×
478
}
3✔
479

480
/****************************************************************************
481
 * Load & Save
482
 ****************************************************************************/
483

484
bool RGBMatrix::loadXML(QXmlStreamReader &root)
3✔
485
{
486
    if (root.name() != KXMLQLCFunction)
3✔
487
    {
488
        qWarning() << Q_FUNC_INFO << "Function node not found";
1✔
489
        return false;
1✔
490
    }
491

492
    if (root.attributes().value(KXMLQLCFunctionType).toString() != typeToString(Function::RGBMatrixType))
4✔
493
    {
494
        qWarning() << Q_FUNC_INFO << "Function is not an RGB matrix";
1✔
495
        return false;
1✔
496
    }
497

498
    /* Load matrix contents */
499
    while (root.readNextStartElement())
12✔
500
    {
501
        if (root.name() == KXMLQLCFunctionSpeed)
11✔
502
        {
503
            loadXMLSpeed(root);
1✔
504
        }
505
        else if (root.name() == KXMLQLCFunctionTempoType)
10✔
506
        {
UNCOV
507
            loadXMLTempoType(root);
×
508
        }
509
        else if (root.name() == KXMLQLCRGBAlgorithm)
10✔
510
        {
511
            setAlgorithm(RGBAlgorithm::loader(doc(), root));
1✔
512
        }
513
        else if (root.name() == KXMLQLCRGBMatrixFixtureGroup)
9✔
514
        {
515
            setFixtureGroup(root.readElementText().toUInt());
1✔
516
        }
517
        else if (root.name() == KXMLQLCFunctionDirection)
8✔
518
        {
519
            loadXMLDirection(root);
1✔
520
        }
521
        else if (root.name() == KXMLQLCFunctionRunOrder)
7✔
522
        {
523
            loadXMLRunOrder(root);
1✔
524
        }
525
        // Legacy support
526
        else if (root.name() == KXMLQLCRGBMatrixStartColor)
6✔
527
        {
UNCOV
528
            setColor(0, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
×
529
        }
530
        else if (root.name() == KXMLQLCRGBMatrixEndColor)
6✔
531
        {
UNCOV
532
            setColor(1, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
×
533
        }
534
        else if (root.name() == KXMLQLCRGBMatrixColor)
6✔
535
        {
536
            int colorIdx = root.attributes().value(KXMLQLCRGBMatrixColorIndex).toInt();
10✔
537
            setColor(colorIdx, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
5✔
538
        }
539
        else if (root.name() == KXMLQLCRGBMatrixControlMode)
1✔
540
        {
541
            setControlMode(stringToControlMode(root.readElementText()));
1✔
542
        }
UNCOV
543
        else if (root.name() == KXMLQLCRGBMatrixProperty)
×
544
        {
UNCOV
545
            QString name = root.attributes().value(KXMLQLCRGBMatrixPropertyName).toString();
×
UNCOV
546
            QString value = root.attributes().value(KXMLQLCRGBMatrixPropertyValue).toString();
×
UNCOV
547
            setProperty(name, value);
×
UNCOV
548
            root.skipCurrentElement();
×
UNCOV
549
        }
×
UNCOV
550
        else if (root.name() == KXMLQLCRGBMatrixDimmerControl)
×
551
        {
UNCOV
552
            setDimmerControl(root.readElementText().toInt());
×
553
        }
554
        else
555
        {
UNCOV
556
            qWarning() << Q_FUNC_INFO << "Unknown RGB matrix tag:" << root.name();
×
UNCOV
557
            root.skipCurrentElement();
×
558
        }
559
    }
560

561
    return true;
1✔
562
}
563

564
bool RGBMatrix::saveXML(QXmlStreamWriter *doc) const
1✔
565
{
566
    Q_ASSERT(doc != NULL);
1✔
567

568
    /* Function tag */
569
    doc->writeStartElement(KXMLQLCFunction);
2✔
570

571
    /* Common attributes */
572
    saveXMLCommon(doc);
1✔
573

574
    /* Tempo type */
575
    saveXMLTempoType(doc);
1✔
576

577
    /* Speeds */
578
    saveXMLSpeed(doc);
1✔
579

580
    /* Direction */
581
    saveXMLDirection(doc);
1✔
582

583
    /* Run order */
584
    saveXMLRunOrder(doc);
1✔
585

586
    /* Algorithm */
587
    if (m_algorithm != NULL)
1✔
588
        m_algorithm->saveXML(doc);
1✔
589

590
    /* LEGACY - Dimmer Control */
591
    if (dimmerControl())
1✔
UNCOV
592
        doc->writeTextElement(KXMLQLCRGBMatrixDimmerControl, QString::number(dimmerControl()));
×
593

594
    /* Colors */
595
    for (int i = 0; i < m_rgbColors.count(); i++)
6✔
596
    {
597
        if (m_rgbColors.at(i).isValid() == false)
5✔
UNCOV
598
            continue;
×
599

600
        doc->writeStartElement(KXMLQLCRGBMatrixColor);
10✔
601
        doc->writeAttribute(KXMLQLCRGBMatrixColorIndex, QString::number(i));
10✔
602
        doc->writeCharacters(QString::number(m_rgbColors.at(i).rgb()));
5✔
603
        doc->writeEndElement();
5✔
604
    }
605

606
    /* Control Mode */
607
    doc->writeTextElement(KXMLQLCRGBMatrixControlMode, RGBMatrix::controlModeToString(m_controlMode));
2✔
608

609
    /* Fixture Group */
610
    doc->writeTextElement(KXMLQLCRGBMatrixFixtureGroup, QString::number(fixtureGroup()));
2✔
611

612
    /* Properties */
613
    QMapIterator<QString, QString> it(m_properties);
1✔
614
    while (it.hasNext())
1✔
615
    {
UNCOV
616
        it.next();
×
617
        doc->writeStartElement(KXMLQLCRGBMatrixProperty);
×
UNCOV
618
        doc->writeAttribute(KXMLQLCRGBMatrixPropertyName, it.key());
×
619
        doc->writeAttribute(KXMLQLCRGBMatrixPropertyValue, it.value());
×
620
        doc->writeEndElement();
×
621
    }
622

623
    /* End the <Function> tag */
624
    doc->writeEndElement();
1✔
625

626
    return true;
1✔
627
}
1✔
628

629
/****************************************************************************
630
 * Running
631
 ****************************************************************************/
632

633
void RGBMatrix::tap()
×
634
{
UNCOV
635
    if (stopped() == false)
×
636
    {
UNCOV
637
        FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
×
638
        // Filter out taps that are too close to each other
UNCOV
639
        if (grp != NULL && uint(m_roundTime.elapsed()) >= (duration() / 4))
×
640
        {
641
            roundCheck();
×
UNCOV
642
            resetElapsed();
×
643
        }
644
    }
645
}
×
646

647
void RGBMatrix::checkEngineCreation()
×
648
{
649
    m_runAlgorithm = m_algorithm;
×
650
    m_requestEngineCreation = false;
×
UNCOV
651
}
×
652

653
void RGBMatrix::preRun(MasterTimer *timer)
×
654
{
655
    {
656
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
657

UNCOV
658
        m_group = doc()->fixtureGroup(m_fixtureGroupID);
×
UNCOV
659
        if (m_group == NULL)
×
660
        {
661
            // No fixture group to control
662
            stop(FunctionParent::master());
×
UNCOV
663
            return;
×
664
        }
665

UNCOV
666
        if (m_algorithm != NULL)
×
667
        {
UNCOV
668
            checkEngineCreation();
×
669

670
            // Copy direction from parent class direction
UNCOV
671
            m_stepHandler->initializeDirection(direction(), m_rgbColors[0], m_rgbColors[1], m_stepsCount, m_runAlgorithm);
×
672

673
            // Update continuous phase when starting playback
UNCOV
674
            if (m_stepsCount > 0)
×
UNCOV
675
                m_continuousPhase = double(m_stepHandler->currentStepIndex()) / double(m_stepsCount);
×
676

677
            if (m_runAlgorithm->type() == RGBAlgorithm::Script)
×
678
            {
UNCOV
679
                RGBScript *script = static_cast<RGBScript*> (m_runAlgorithm);
×
UNCOV
680
                QMapIterator<QString, QString> it(m_properties);
×
681
                while (it.hasNext())
×
682
                {
UNCOV
683
                    it.next();
×
684
                    script->setProperty(it.key(), it.value());
×
685
                }
UNCOV
686
            }
×
UNCOV
687
            else if (m_runAlgorithm->type() == RGBAlgorithm::Image)
×
688
            {
689
                RGBImage *image = static_cast<RGBImage*> (m_runAlgorithm);
×
UNCOV
690
                if (image->animatedSource())
×
691
                    image->rewindAnimation();
×
692
            }
693
        }
694
    }
×
695

696
    m_roundTime.restart();
×
697

UNCOV
698
    Function::preRun(timer);
×
699
}
700

701
void RGBMatrix::write(MasterTimer *timer, QList<Universe *> universes)
×
702
{
703
    Q_UNUSED(timer);
704

705
    {
UNCOV
706
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
707
        if (m_group == NULL)
×
708
        {
709
            // No fixture group to control
710
            stop(FunctionParent::master());
×
UNCOV
711
            return;
×
712
        }
713

714
        // No time to do anything.
UNCOV
715
        if (duration() == 0)
×
UNCOV
716
            return;
×
717

UNCOV
718
        if (m_algorithm != NULL && m_requestEngineCreation)
×
UNCOV
719
            checkEngineCreation();
×
720

721
        // Invalid/nonexistent script
UNCOV
722
        if (m_runAlgorithm == NULL || m_runAlgorithm->apiVersion() == 0)
×
723
            return;
×
724

725
        if (isPaused() == false)
×
726
        {
727
            // Get a new map every time elapsed is reset to zero
UNCOV
728
            if (elapsed() < MasterTimer::tick())
×
729
            {
UNCOV
730
                if (tempoType() == Beats)
×
731
                    m_stepBeatDuration = beatsToTime(duration(), timer->beatTimeDuration());
×
732

733
                //qDebug() << "RGBMatrix step" << m_stepHandler->currentStepIndex() << ", color:" << QString::number(m_stepHandler->stepColor().rgb(), 16);
UNCOV
734
                m_runAlgorithm->rgbMap(m_group->size(), m_stepHandler->stepColor().rgb(),
×
735
                                       m_stepHandler->currentStepIndex(), m_stepHandler->m_map);
×
736
                updateMapChannels(m_stepHandler->m_map, m_group, universes);
×
737
            }
738
        }
739
    }
×
740

741
    if (isPaused() == false)
×
742
    {
743
        // Increment the ms elapsed time
UNCOV
744
        incrementElapsed();
×
745

746
        /* Check if we need to change direction, stop completely or go to next step
747
         * The cases are:
748
         * 1- time tempo type: act normally, on ms elapsed time
749
         * 2- beat tempo type, beat occurred: check if the elapsed beats is a multiple of
750
         *    the step beat duration. If so, proceed to the next step
751
         * 3- beat tempo type, not beat: if the ms elapsed time reached the step beat
752
         *    duration in ms, and the ms time to the next beat is not less than 1/16 of
753
         *    the step beat duration in ms, then proceed to the next step. If the ms time to the
754
         *    next beat is less than 1/16 of the step beat duration in ms, then defer the step
755
         *    change to case #2, to resync the matrix to the next beat
756
         */
757
        if (tempoType() == Time && elapsed() >= duration())
×
758
        {
UNCOV
759
            roundCheck();
×
760
        }
761
        else if (tempoType() == Beats)
×
762
        {
UNCOV
763
            if (timer->isBeat())
×
764
            {
UNCOV
765
                incrementElapsedBeats();
×
766
                qDebug() << "Elapsed beats:" << elapsedBeats() << ", time elapsed:" << elapsed() << ", step time:" << m_stepBeatDuration;
×
767
                if (elapsedBeats() % duration() == 0)
×
768
                {
UNCOV
769
                    roundCheck();
×
UNCOV
770
                    resetElapsed();
×
771
                }
772
            }
UNCOV
773
            else if (elapsed() >= m_stepBeatDuration && (uint)timer->timeToNextBeat() > m_stepBeatDuration / 16)
×
774
            {
775
                qDebug() << "Elapsed exceeded";
×
776
                roundCheck();
×
777
            }
778
        }
779
    }
780
}
781

UNCOV
782
void RGBMatrix::postRun(MasterTimer *timer, QList<Universe *> universes)
×
783
{
UNCOV
784
    uint fadeout = overrideFadeOutSpeed() == defaultSpeed() ? fadeOutSpeed() : overrideFadeOutSpeed();
×
785

786
    /* If no fade out is needed, dismiss all the requested faders.
787
     * Otherwise, set all the faders to fade out and let Universe dismiss them
788
     * when done */
789
    if (fadeout == 0)
×
790
    {
UNCOV
791
        dismissAllFaders();
×
792
    }
793
    else
794
    {
795
        if (tempoType() == Beats)
×
UNCOV
796
            fadeout = beatsToTime(fadeout, timer->beatTimeDuration());
×
797

798
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
799
        {
800
            if (!fader.isNull())
×
UNCOV
801
                fader->setFadeOut(true, fadeout);
×
UNCOV
802
        }
×
803
    }
804

UNCOV
805
    m_fadersMap.clear();
×
806

807
    {
UNCOV
808
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
809
        checkEngineCreation();
×
810
        if (m_runAlgorithm != NULL)
×
811
            m_runAlgorithm->postRun();
×
812
    }
×
813

814
    Function::postRun(timer, universes);
×
UNCOV
815
}
×
816

817
void RGBMatrix::roundCheck()
×
818
{
UNCOV
819
    QMutexLocker algorithmLocker(&m_algorithmMutex);
×
820
    if (m_algorithm == NULL)
×
UNCOV
821
        return;
×
822

823
    if (m_stepHandler->checkNextStep(runOrder(), m_rgbColors[0], m_rgbColors[1], m_stepsCount) == false)
×
824
        stop(FunctionParent::master());
×
825

826
    // Update continuous phase based on current step index (prevents cumulative rounding errors)
827
    // This is analogous to how EFX uses m_currentAngle for phase scaling
828
    if (m_stepsCount > 0)
×
UNCOV
829
        m_continuousPhase = double(m_stepHandler->currentStepIndex()) / double(m_stepsCount);
×
830

831
    m_roundTime.restart();
×
832

833
    if (tempoType() == Beats)
×
UNCOV
834
        roundElapsed(m_stepBeatDuration);
×
835
    else
UNCOV
836
        roundElapsed(duration());
×
UNCOV
837
}
×
838

839
QSharedPointer<GenericFader> RGBMatrix::getFader(Universe *universe)
×
840
{
841
    // get the universe Fader first. If doesn't exist, create it
842
    if (universe == NULL)
×
843
        return QSharedPointer<GenericFader>();
×
844

845
    QSharedPointer<GenericFader> fader = m_fadersMap.value(universe->id(), QSharedPointer<GenericFader>());
×
846
    if (fader.isNull())
×
847
    {
848
        fader = universe->requestFader();
×
UNCOV
849
        fader->adjustIntensity(getAttributeValue(Intensity));
×
850
        fader->setBlendMode(blendMode());
×
851
        fader->setName(name());
×
UNCOV
852
        fader->setParentFunctionID(id());
×
853
        m_fadersMap[universe->id()] = fader;
×
854
    }
855

UNCOV
856
    return fader;
×
857
}
×
858

859
void RGBMatrix::updateFaderValues(FadeChannel &fc, uchar value, uint fadeTime)
×
860
{
861
    fc.setStart(fc.current());
×
UNCOV
862
    fc.setTarget(value);
×
863
    fc.setElapsed(0);
×
864
    fc.setReady(false);
×
865
    // fade in/out depends on target value
UNCOV
866
    if (value == 0)
×
UNCOV
867
        fc.setFadeTime(fadeOutSpeed());
×
868
    else
869
        fc.setFadeTime(fadeTime);
×
UNCOV
870
}
×
871

UNCOV
872
void RGBMatrix::updateMapChannels(const RGBMap& map, const FixtureGroup *grp, QList<Universe *> universes)
×
873
{
874
    uint fadeTime = (overrideFadeInSpeed() == defaultSpeed()) ? fadeInSpeed() : overrideFadeInSpeed();
×
875

876
    // Create/modify fade channels for ALL heads in the group
877
    QMapIterator<QLCPoint, GroupHead> it(grp->headsMap());
×
UNCOV
878
    while (it.hasNext())
×
879
    {
UNCOV
880
        it.next();
×
881
        QLCPoint pt = it.key();
×
UNCOV
882
        GroupHead grpHead = it.value();
×
883
        Fixture *fxi = doc()->fixture(grpHead.fxi);
×
UNCOV
884
        if (fxi == NULL)
×
885
            continue;
×
886

UNCOV
887
        QLCFixtureHead head = fxi->head(grpHead.head);
×
888

889
        if (pt.y() >= map.count() || pt.x() >= map[pt.y()].count())
×
UNCOV
890
            continue;
×
891

892
        uint col = map[pt.y()][pt.x()];
×
UNCOV
893
        QVector<quint32> channelList;
×
UNCOV
894
        QVector<uchar> valueList;
×
895

UNCOV
896
        if (m_controlMode == ControlModeRgb)
×
897
        {
UNCOV
898
            channelList = head.rgbChannels();
×
899

UNCOV
900
            if (channelList.size() == 3)
×
901
            {
UNCOV
902
                valueList.append(qRed(col));
×
UNCOV
903
                valueList.append(qGreen(col));
×
UNCOV
904
                valueList.append(qBlue(col));
×
905
            }
906
            else
907
            {
UNCOV
908
                channelList = head.cmyChannels();
×
909

UNCOV
910
                if (channelList.size() == 3)
×
911
                {
912
                    // CMY color mixing
UNCOV
913
                    QColor cmyCol(col);
×
914
                    valueList.append(cmyCol.cyan());
×
UNCOV
915
                    valueList.append(cmyCol.magenta());
×
916
                    valueList.append(cmyCol.yellow());
×
917
                }
918
            }
919
        }
920
        else if (m_controlMode == ControlModeShutter)
×
921
        {
922
            channelList = head.shutterChannels();
×
923

UNCOV
924
            if (channelList.size())
×
925
            {
926
                // make sure only one channel is in the list
UNCOV
927
                channelList.resize(1);
×
928
                valueList.append(rgbToGrey(col));
×
929
            }
930
        }
931
        else if (m_controlMode == ControlModeDimmer || m_dimmerControl)
×
932
        {
933
            // Collect all dimmers that affect current head:
934
            // They are the master dimmer (affects whole fixture)
935
            // and per-head dimmer.
936
            //
937
            // If there are no RGB or CMY channels, the least important* dimmer channel
938
            // is used to create grayscale image.
939
            //
940
            // The rest of the dimmer channels are set to full if dimmer control is
941
            // enabled and target color is > 0 (see
942
            // https://www.qlcplus.org/forum/viewtopic.php?f=29&t=11090)
943
            //
944
            // Note: If there is only one head, and only one dimmer channel,
945
            // make it a master dimmer in fixture definition.
946
            //
947
            // *least important - per head dimmer if present,
948
            // otherwise per fixture dimmer if present
949

UNCOV
950
            quint32 masterDim = fxi->masterIntensityChannel();
×
951
            quint32 headDim = head.channelNumber(QLCChannel::Intensity, QLCChannel::MSB);
×
952

953
            if (masterDim != QLCChannel::invalid())
×
954
            {
UNCOV
955
                channelList.append(masterDim);
×
956
                valueList.append(rgbToGrey(col));
×
957
            }
958

959
            if (headDim != QLCChannel::invalid() && headDim != masterDim)
×
960
            {
UNCOV
961
                channelList.append(headDim);
×
962
                // If a master dimmer is present, it carries the greyscale fade and the
963
                // per-head dimmer is just opened fully (on/off). With no master dimmer
964
                // (e.g. generic single-channel dimmers), the head dimmer must carry the
965
                // greyscale value itself, otherwise it would only ever output 0 or 255.
NEW
966
                if (masterDim != QLCChannel::invalid())
×
NEW
967
                    valueList.append(rgbToGrey(col) == 0 ? 0 : 255);
×
968
                else
NEW
969
                    valueList.append(rgbToGrey(col));
×
970
            }
UNCOV
971
        }
×
972
        else
973
        {
UNCOV
974
            if (m_controlMode == ControlModeWhite)
×
UNCOV
975
                channelList.append(head.channelNumber(QLCChannel::White, QLCChannel::MSB));
×
UNCOV
976
            else if (m_controlMode == ControlModeAmber)
×
UNCOV
977
                channelList.append(head.channelNumber(QLCChannel::Amber, QLCChannel::MSB));
×
UNCOV
978
            else if (m_controlMode == ControlModeUV)
×
UNCOV
979
                channelList.append(head.channelNumber(QLCChannel::UV, QLCChannel::MSB));
×
980

UNCOV
981
            valueList.append(rgbToGrey(col));
×
982
        }
983

984
        quint32 absAddress = fxi->universeAddress();
×
985

986
        for (int i = 0; i < channelList.count(); i++)
×
987
        {
988
            if (channelList.at(i) == QLCChannel::invalid())
×
989
                continue;
×
990

UNCOV
991
            quint32 universeIndex = floor((absAddress + channelList.at(i)) / 512);
×
992
            Universe *universe = universes.at(universeIndex);
×
UNCOV
993
            QSharedPointer<GenericFader> fader = getFader(universe);
×
994
            if (fader.isNull())
×
UNCOV
995
                continue;
×
996

UNCOV
997
            const quint32 fixtureID = grpHead.fxi;
×
998
            const quint32 channel = channelList.at(i);
×
UNCOV
999
            const uchar value = valueList.at(i);
×
UNCOV
1000
            fader->updateChannel(doc(), universe, fixtureID, channel, [this, value, fadeTime](FadeChannel &fc)
×
1001
            {
UNCOV
1002
                updateFaderValues(fc, value, fadeTime);
×
UNCOV
1003
            });
×
1004
        }
×
UNCOV
1005
    }
×
1006
}
×
1007

UNCOV
1008
uchar RGBMatrix::rgbToGrey(uint col)
×
1009
{
1010
    uchar r = qRed(col);
×
UNCOV
1011
    uchar g = qGreen(col);
×
1012
    uchar b = qBlue(col);
×
1013

1014
    // Special case: if R=G=B (grayscale), return the value directly.
1015
    // This avoids floating-point precision issues.
UNCOV
1016
    if (r == g && g == b)
×
1017
        return r;
×
1018

1019
    // the weights are taken from
1020
    // https://en.wikipedia.org/wiki/YUV#SDTV_with_BT.601
1021
    return uchar(round(0.299 * r + 0.587 * g + 0.114 * b));
×
1022
}
1023

1024
/*********************************************************************
1025
 * Attributes
1026
 *********************************************************************/
1027

UNCOV
1028
int RGBMatrix::adjustAttribute(qreal fraction, int attributeId)
×
1029
{
UNCOV
1030
    int attrIndex = Function::adjustAttribute(fraction, attributeId);
×
1031

1032
    if (attrIndex == Intensity)
×
1033
    {
1034
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
1035
        {
1036
            if (!fader.isNull())
×
UNCOV
1037
                fader->adjustIntensity(getAttributeValue(Function::Intensity));
×
1038
        }
×
1039
    }
1040
    else if (attrIndex >= Color1Attr && attrIndex <= ColorLastAttr)
×
1041
    {
1042
        applyColorAttribute(attrIndex - Color1Attr, getAttributeValue(attrIndex));
×
1043
    }
1044
    else if (attrIndex == PatternAttr)
×
1045
    {
1046
        applyPatternAttribute(getAttributeValue(PatternAttr));
×
1047
    }
1048

UNCOV
1049
    return attrIndex;
×
1050
}
1051

UNCOV
1052
void RGBMatrix::applyStyleAttributes()
×
1053
{
1054
    for (int i = 0; i < ColorAttributeCount; ++i)
×
UNCOV
1055
        applyColorAttribute(i, getAttributeValue(Color1Attr + i));
×
1056

1057
    applyPatternAttribute(getAttributeValue(PatternAttr));
×
1058
}
×
1059

1060
void RGBMatrix::applyColorAttribute(int colorIndex, qreal packedColor)
×
1061
{
UNCOV
1062
    if (colorIndex < 0 || colorIndex >= ColorAttributeCount)
×
UNCOV
1063
        return;
×
1064

UNCOV
1065
    int packed = qRound(packedColor);
×
1066
    QColor targetColor = packed < 0 ? QColor() :
UNCOV
1067
                                      QColor::fromRgb(static_cast<QRgb>((packed & RGBMatrixColorMask) | 0xFF000000));
×
1068
    if (getColor(colorIndex) == targetColor)
×
1069
        return;
×
1070

1071
    bool previous = m_applyingStyleAttributes;
×
UNCOV
1072
    m_applyingStyleAttributes = true;
×
1073
    setColor(colorIndex, targetColor);
×
1074
    m_applyingStyleAttributes = previous;
×
1075
}
1076

1077
void RGBMatrix::applyPatternAttribute(qreal patternIndex)
×
1078
{
UNCOV
1079
    if (doc() == NULL)
×
UNCOV
1080
        return;
×
1081

UNCOV
1082
    QStringList algoList = RGBAlgorithm::algorithms(doc());
×
UNCOV
1083
    if (algoList.isEmpty())
×
UNCOV
1084
        return;
×
1085

UNCOV
1086
    int idx = qRound(patternIndex);
×
UNCOV
1087
    if (idx < 0)
×
UNCOV
1088
        idx = 0;
×
UNCOV
1089
    else if (idx >= algoList.count())
×
UNCOV
1090
        idx = algoList.count() - 1;
×
1091

UNCOV
1092
    RGBAlgorithm *algo = RGBAlgorithm::algorithm(doc(), algoList.at(idx));
×
UNCOV
1093
    if (algo == NULL)
×
UNCOV
1094
        return;
×
1095

UNCOV
1096
    if (m_algorithm != NULL && m_algorithm->name() == algo->name())
×
1097
    {
UNCOV
1098
        delete algo;
×
UNCOV
1099
        return;
×
1100
    }
1101

1102
    algo->setColors(getColors());
×
1103

1104
    bool previous = m_applyingStyleAttributes;
×
1105
    m_applyingStyleAttributes = true;
×
1106
    setAlgorithm(algo);
×
1107
    m_applyingStyleAttributes = previous;
×
1108
}
×
1109

1110
/*************************************************************************
1111
 * Blend mode
1112
 *************************************************************************/
1113

UNCOV
1114
void RGBMatrix::setBlendMode(Universe::BlendMode mode)
×
1115
{
UNCOV
1116
    if (mode == blendMode())
×
UNCOV
1117
        return;
×
1118

UNCOV
1119
    foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
1120
    {
UNCOV
1121
        if (!fader.isNull())
×
1122
            fader->setBlendMode(mode);
×
1123
    }
×
1124

1125
    Function::setBlendMode(mode);
×
1126
    emit changed(id());
×
1127
}
1128

1129
/*************************************************************************
1130
 * Control Mode
1131
 *************************************************************************/
1132

1133
RGBMatrix::ControlMode RGBMatrix::controlMode() const
2✔
1134
{
1135
    return m_controlMode;
2✔
1136
}
1137

1138
void RGBMatrix::setControlMode(RGBMatrix::ControlMode mode)
3✔
1139
{
1140
    m_controlMode = mode;
3✔
1141
    emit changed(id());
3✔
1142
}
3✔
1143

1144
RGBMatrix::ControlMode RGBMatrix::stringToControlMode(QString mode)
1✔
1145
{
1146
    if (mode == KXMLQLCRGBMatrixControlModeRgb)
1✔
1147
        return ControlModeRgb;
1✔
UNCOV
1148
    else if (mode == KXMLQLCRGBMatrixControlModeAmber)
×
UNCOV
1149
        return ControlModeAmber;
×
UNCOV
1150
    else if (mode == KXMLQLCRGBMatrixControlModeWhite)
×
UNCOV
1151
        return ControlModeWhite;
×
UNCOV
1152
    else if (mode == KXMLQLCRGBMatrixControlModeUV)
×
UNCOV
1153
        return ControlModeUV;
×
UNCOV
1154
    else if (mode == KXMLQLCRGBMatrixControlModeDimmer)
×
UNCOV
1155
        return ControlModeDimmer;
×
UNCOV
1156
    else if (mode == KXMLQLCRGBMatrixControlModeShutter)
×
UNCOV
1157
        return ControlModeShutter;
×
1158

UNCOV
1159
    return ControlModeRgb;
×
1160
}
1161

1162
QString RGBMatrix::controlModeToString(RGBMatrix::ControlMode mode)
1✔
1163
{
1164
    switch(mode)
1✔
1165
    {
1166
        default:
1✔
1167
        case ControlModeRgb:
1168
            return QString(KXMLQLCRGBMatrixControlModeRgb);
1✔
1169
        break;
UNCOV
1170
        case ControlModeAmber:
×
UNCOV
1171
            return QString(KXMLQLCRGBMatrixControlModeAmber);
×
1172
        break;
UNCOV
1173
        case ControlModeWhite:
×
UNCOV
1174
            return QString(KXMLQLCRGBMatrixControlModeWhite);
×
1175
        break;
UNCOV
1176
        case ControlModeUV:
×
UNCOV
1177
            return QString(KXMLQLCRGBMatrixControlModeUV);
×
1178
        break;
UNCOV
1179
        case ControlModeDimmer:
×
UNCOV
1180
            return QString(KXMLQLCRGBMatrixControlModeDimmer);
×
1181
        break;
UNCOV
1182
        case ControlModeShutter:
×
UNCOV
1183
            return QString(KXMLQLCRGBMatrixControlModeShutter);
×
1184
        break;
1185
    }
1186
}
1187

1188

1189
/*************************************************************************
1190
 *************************************************************************
1191
 *                          RGBMatrixStep class
1192
 *************************************************************************
1193
 *************************************************************************/
1194

1195
RGBMatrixStep::RGBMatrixStep()
10✔
1196
    : m_direction(Function::Forward)
10✔
1197
    , m_currentStepIndex(0)
10✔
1198
    , m_stepColor(QColor())
10✔
1199
    , m_crDelta(0)
10✔
1200
    , m_cgDelta(0)
10✔
1201
    , m_cbDelta(0)
10✔
1202
{
1203

1204
}
10✔
1205

1206
void RGBMatrixStep::setCurrentStepIndex(int index)
×
1207
{
UNCOV
1208
    m_currentStepIndex = index;
×
UNCOV
1209
}
×
1210

1211
int RGBMatrixStep::currentStepIndex() const
1✔
1212
{
1213
    return m_currentStepIndex;
1✔
1214
}
1215

1216
void RGBMatrixStep::calculateColorDelta(const QColor& startColor, const QColor& endColor, const RGBAlgorithm *algorithm)
16✔
1217
{
1218
    m_crDelta = 0;
16✔
1219
    m_cgDelta = 0;
16✔
1220
    m_cbDelta = 0;
16✔
1221

1222
    if (endColor.isValid() && algorithm != NULL && algorithm->acceptColors() > 1)
16✔
1223
    {
1224
        m_crDelta = endColor.red() - startColor.red();
10✔
1225
        m_cgDelta = endColor.green() - startColor.green();
10✔
1226
        m_cbDelta = endColor.blue() - startColor.blue();
10✔
1227

1228
        //qDebug() << "Color deltas:" << m_crDelta << m_cgDelta << m_cbDelta;
1229
    }
1230
}
16✔
1231

1232
void RGBMatrixStep::setStepColor(QColor color)
×
1233
{
UNCOV
1234
    m_stepColor = color;
×
1235
}
×
1236

1237
QColor RGBMatrixStep::stepColor() const
6✔
1238
{
1239
    return m_stepColor;
6✔
1240
}
1241

1242
void RGBMatrixStep::updateStepColor(int stepIndex, QColor startColor, int stepsCount)
×
1243
{
1244
    if (stepsCount <= 0)
×
1245
        return;
×
1246

1247
    if (stepsCount == 1)
×
1248
    {
1249
        m_stepColor = startColor;
×
1250
    }
1251
    else
1252
    {
1253
        m_stepColor.setRed(startColor.red() + (m_crDelta * stepIndex / (stepsCount - 1)));
×
1254
        m_stepColor.setGreen(startColor.green() + (m_cgDelta * stepIndex / (stepsCount - 1)));
×
UNCOV
1255
        m_stepColor.setBlue(startColor.blue() + (m_cbDelta * stepIndex / (stepsCount - 1)));
×
1256
    }
1257

1258
    //qDebug() << "RGBMatrix step" << stepIndex << ", color:" << QString::number(m_stepColor.rgb(), 16);
1259
}
1260

1261
void RGBMatrixStep::initializeDirection(Function::Direction direction, const QColor& startColor, const QColor& endColor, int stepsCount, const RGBAlgorithm *algorithm)
×
1262
{
UNCOV
1263
    m_direction = direction;
×
1264

1265
    if (m_direction == Function::Forward)
×
1266
    {
1267
        setCurrentStepIndex(0);
×
UNCOV
1268
        setStepColor(startColor);
×
1269
    }
1270
    else
1271
    {
UNCOV
1272
        setCurrentStepIndex(stepsCount - 1);
×
1273

1274
        if (endColor.isValid())
×
UNCOV
1275
            setStepColor(endColor);
×
1276
        else
UNCOV
1277
            setStepColor(startColor);
×
1278
    }
1279

1280
    calculateColorDelta(startColor, endColor, algorithm);
×
UNCOV
1281
}
×
1282

1283
bool RGBMatrixStep::checkNextStep(Function::RunOrder order,
×
1284
                                  QColor startColor, QColor endColor, int stepsNumber)
1285
{
UNCOV
1286
    if (order == Function::PingPong)
×
1287
    {
UNCOV
1288
        if (m_direction == Function::Forward && (m_currentStepIndex + 1) == stepsNumber)
×
1289
        {
1290
            m_direction = Function::Backward;
×
UNCOV
1291
            m_currentStepIndex = stepsNumber - 2;
×
1292
            if (endColor.isValid())
×
UNCOV
1293
                m_stepColor = endColor;
×
1294

1295
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1296
        }
UNCOV
1297
        else if (m_direction == Function::Backward && (m_currentStepIndex - 1) < 0)
×
1298
        {
1299
            m_direction = Function::Forward;
×
1300
            m_currentStepIndex = 1;
×
UNCOV
1301
            m_stepColor = startColor;
×
UNCOV
1302
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1303
        }
1304
        else
1305
        {
UNCOV
1306
            if (m_direction == Function::Forward)
×
1307
                m_currentStepIndex++;
×
1308
            else
1309
                m_currentStepIndex--;
×
UNCOV
1310
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1311
        }
1312
    }
1313
    else if (order == Function::SingleShot)
×
1314
    {
UNCOV
1315
        if (m_direction == Function::Forward)
×
1316
        {
UNCOV
1317
            if (m_currentStepIndex >= stepsNumber - 1)
×
UNCOV
1318
                return false;
×
1319
            else
1320
            {
UNCOV
1321
                m_currentStepIndex++;
×
UNCOV
1322
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1323
            }
1324
        }
1325
        else
1326
        {
UNCOV
1327
            if (m_currentStepIndex <= 0)
×
UNCOV
1328
                return false;
×
1329
            else
1330
            {
UNCOV
1331
                m_currentStepIndex--;
×
UNCOV
1332
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1333
            }
1334
        }
1335
    }
1336
    else
1337
    {
UNCOV
1338
        if (m_direction == Function::Forward)
×
1339
        {
UNCOV
1340
            if (m_currentStepIndex >= stepsNumber - 1)
×
1341
            {
UNCOV
1342
                m_currentStepIndex = 0;
×
UNCOV
1343
                m_stepColor = startColor;
×
1344
            }
1345
            else
1346
            {
UNCOV
1347
                m_currentStepIndex++;
×
UNCOV
1348
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1349
            }
1350
        }
1351
        else
1352
        {
UNCOV
1353
            if (m_currentStepIndex <= 0)
×
1354
            {
UNCOV
1355
                m_currentStepIndex = stepsNumber - 1;
×
UNCOV
1356
                if (endColor.isValid())
×
UNCOV
1357
                    m_stepColor = endColor;
×
1358
            }
1359
            else
1360
            {
UNCOV
1361
                m_currentStepIndex--;
×
UNCOV
1362
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1363
            }
1364
        }
1365
    }
1366

UNCOV
1367
    return true;
×
1368
}
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