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

mcallegari / qlcplus / 30930139750

04 Aug 2026 04:38PM UTC coverage: 35.313% (+0.03%) from 35.282%
30930139750

push

github

mcallegari
engine: expose RGBMatrix Script parameters as attributes

So that they can be used by VC Sliders in adjust mode

35 of 67 new or added lines in 1 file covered. (52.24%)

5 existing lines in 2 files now uncovered.

18602 of 52678 relevant lines covered (35.31%)

40968.79 hits per line

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

39.95
/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
    /** Register the fixed attributes before loading an algorithm, so that
89
     *  the attributes exposed by a Script are always appended after them */
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, 0);
9✔
98

99
    setAlgorithm(RGBAlgorithm::algorithm(doc, "Stripes"));
9✔
100
}
9✔
101

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

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

115
int RGBMatrix::algorithmIndex() const
13✔
116
{
117
    if (m_algorithm == NULL || doc() == NULL)
13✔
118
        return 0;
×
119

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

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

129
    if (m_algorithm == NULL)
1✔
130
        return;
×
131

132
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
1✔
133
    if (grp == NULL)
1✔
134
        return;
×
135

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

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

144
    if (m_algorithm == NULL)
3✔
145
        return 0;
×
146

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

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

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

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

165
/****************************************************************************
166
 * Copying
167
 ****************************************************************************/
168

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

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

185
    return copy;
1✔
186
}
187

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

194
    setDimmerControl(mtx->dimmerControl());
1✔
195
    setFixtureGroup(mtx->fixtureGroup());
1✔
196

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

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

206
    setControlMode(mtx->controlMode());
1✔
207

208
    return Function::copyFrom(function);
1✔
209
}
210

211
/****************************************************************************
212
 * Fixtures
213
 ****************************************************************************/
214

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

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

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

235
    return QList<quint32>();
1✔
236
}
237

238
/****************************************************************************
239
 * Algorithm
240
 ****************************************************************************/
241

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

247
        /** Unregister the attributes of the outgoing algorithm while it is
248
         *  still around, since their names come from its properties */
249
        unregisterScriptPropertyAttributes();
13✔
250

251
        delete m_algorithm;
13✔
252
        m_algorithm = algo;
13✔
253

254
        m_requestEngineCreation = true;
13✔
255

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

274
            QVector<uint> colors = script->rgbMapGetColors();
13✔
275
            for (int i = 0; i < colors.count(); i++)
13✔
276
                m_rgbColors.replace(i, QColor::fromRgb(colors.at(i)));
×
277
        }
13✔
278
    }
13✔
279
    m_stepsCount = algorithmStepsCount();
13✔
280

281
    /** Expose the properties of the new algorithm as Function attributes */
282
    registerScriptPropertyAttributes();
13✔
283

284
    if (m_applyingStyleAttributes == false)
13✔
285
        Function::adjustAttribute(algorithmIndex(), PatternAttr);
13✔
286

287
    emit changed(id());
13✔
288
}
13✔
289

290
RGBAlgorithm *RGBMatrix::algorithm() const
18✔
291
{
292
    return m_algorithm;
18✔
293
}
294

295
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
296
QMutex& RGBMatrix::algorithmMutex()
297
{
298
    return m_algorithmMutex;
299
}
300
#else
301
QRecursiveMutex& RGBMatrix::algorithmMutex()
×
302
{
303
    return m_algorithmMutex;
×
304
}
305
#endif
306

307

308
int RGBMatrix::stepsCount() const
2✔
309
{
310
    return m_stepsCount;
2✔
311
}
312

313
int RGBMatrix::algorithmStepsCount()
22✔
314
{
315
    QMutexLocker algorithmLocker(&m_algorithmMutex);
22✔
316

317
    if (m_algorithm == NULL)
22✔
318
        return 0;
×
319

320
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
22✔
321
    if (grp != NULL)
22✔
322
        return m_algorithm->rgbMapStepCount(grp->size());
6✔
323

324
    return 0;
16✔
325
}
22✔
326

327
void RGBMatrix::previewMap(int step, RGBMatrixStep *handler)
7✔
328
{
329
    QMutexLocker algorithmLocker(&m_algorithmMutex);
7✔
330
    if (m_algorithm == NULL || handler == NULL)
7✔
331
        return;
×
332

333
    if (m_group == NULL)
7✔
334
        m_group = doc()->fixtureGroup(fixtureGroup());
1✔
335

336
    if (m_group != NULL)
7✔
337
    {
338
        setMapColors(m_algorithm);
6✔
339
        m_algorithm->rgbMap(m_group->size(), handler->stepColor().rgb(), step, handler->m_map);
6✔
340
    }
341
}
7✔
342

343
/****************************************************************************
344
 * Color
345
 ****************************************************************************/
346

347
void RGBMatrix::setColor(int i, QColor c)
25✔
348
{
349
    if (i < 0)
25✔
350
        return;
×
351

352
    if (i >= m_rgbColors.count())
25✔
353
        m_rgbColors.resize(i + 1);
×
354

355
    m_rgbColors.replace(i, c);
25✔
356
    {
357
        QMutexLocker algorithmLocker(&m_algorithmMutex);
25✔
358
        if (m_algorithm != NULL)
25✔
359
        {
360
            m_algorithm->setColors(m_rgbColors);
16✔
361
            updateColorDelta();
16✔
362
        }
363
    }
25✔
364
    setMapColors(m_algorithm);
25✔
365

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

369
    emit changed(id());
25✔
370
}
371

372
QColor RGBMatrix::getColor(int i) const
64✔
373
{
374
    if (i < 0 || i >= m_rgbColors.count())
64✔
375
        return QColor();
×
376

377
    return m_rgbColors.at(i);
64✔
378
}
379

380
QVector<QColor> RGBMatrix::getColors() const
1✔
381
{
382
    return m_rgbColors;
1✔
383
}
384

385
void RGBMatrix::updateColorDelta()
16✔
386
{
387
    if (m_rgbColors.count() > 1)
16✔
388
        m_stepHandler->calculateColorDelta(m_rgbColors[0], m_rgbColors[1], m_algorithm);
16✔
389
}
16✔
390

391
void RGBMatrix::setMapColors(RGBAlgorithm *algorithm)
31✔
392
{
393
    QMutexLocker algorithmLocker(&m_algorithmMutex);
31✔
394
    if (algorithm == NULL)
31✔
395
        return;
9✔
396

397
    if (algorithm->apiVersion() < 3)
22✔
398
        return;
22✔
399

400
    if (m_group == NULL)
×
401
        m_group = doc()->fixtureGroup(fixtureGroup());
×
402

403
    QVector<unsigned int> rawColors;
×
404
    const int acceptColors = algorithm->acceptColors();
×
405
    rawColors.reserve(acceptColors);
×
406
    for (int i = 0; i < acceptColors; i++)
×
407
    {
408
        if (m_rgbColors.count() > i)
×
409
        {
410
            QColor col = m_rgbColors.at(i);
×
411
            rawColors.append(col.isValid() ? col.rgb() : 0);
×
412
        }
413
        else
414
        {
415
            rawColors.append(0);
×
416
        }
417
    }
418

419
    algorithm->rgbMapSetColors(rawColors);
×
420
}
31✔
421

422
/************************************************************************
423
 * Properties
424
 ************************************************************************/
425

426
void RGBMatrix::setProperty(QString propName, QString value)
1✔
427
{
428
    QMutexLocker algoLocker(&m_algorithmMutex);
1✔
429

430
    // Remember the old step count before changing it (used to scale the step index)
431
    int oldStepsCount = m_stepsCount;
1✔
432

433
    m_properties[propName] = value;
1✔
434
    if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
1✔
435
    {
436
        RGBScript *script = static_cast<RGBScript*> (m_algorithm);
1✔
437
        script->setProperty(propName, value);
1✔
438

439
        QVector<uint> colors = script->rgbMapGetColors();
1✔
440
        for (int i = 0; i < colors.count(); i++)
1✔
441
            setColor(i, QColor::fromRgb(colors.at(i)));
×
442
    }
1✔
443
    m_stepsCount = algorithmStepsCount();
1✔
444

445
    // Scale currentStepIndex to the new step count to preserve the phase.
446
    // We use m_continuousPhase (a continuous 0.0-1.0 value) instead of the discrete
447
    // stepIndex to avoid accumulating rounding errors over multiple speed changes.
448
    // Analogous to EFXFixture::durationChanged() using m_currentAngle.
449
    if (m_stepHandler != NULL && m_stepsCount > 0 && oldStepsCount != m_stepsCount)
1✔
450
    {
451
        // If m_continuousPhase is not up to date yet (first change),
452
        // compute it from the current stepIndex
453
        if (oldStepsCount > 0)
×
454
        {
455
            int currentStepIndex = m_stepHandler->currentStepIndex();
×
456
            m_continuousPhase = double(currentStepIndex) / double(oldStepsCount);
×
457
        }
458

459
        // Rescale the phase to the new step count (preserve the continuous phase,
460
        // not the discrete index)
461
        int newStepIndex = int(round(m_continuousPhase * double(m_stepsCount)));
×
462
        // Make sure it stays within range
463
        if (newStepIndex >= m_stepsCount)
×
464
            newStepIndex = m_stepsCount - 1;
×
465
        if (newStepIndex < 0)
×
466
            newStepIndex = 0;
×
467
        m_stepHandler->setCurrentStepIndex(newStepIndex);
×
468
    }
469
}
1✔
470

471
QString RGBMatrix::property(QString propName)
16✔
472
{
473
    QMutexLocker algoLocker(&m_algorithmMutex);
16✔
474

475
    /** If the property is cached, then return it right away */
476
    QMap<QString, QString>::iterator it = m_properties.find(propName);
16✔
477
    if (it != m_properties.end())
16✔
478
        return it.value();
1✔
479

480
    /** Otherwise, let's retrieve it from the Script */
481
    if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
15✔
482
    {
483
        RGBScript *script = static_cast<RGBScript*> (m_algorithm);
15✔
484
        return script->property(propName);
15✔
485
    }
486

487
    return QString();
×
488
}
16✔
489

490
/****************************************************************************
491
 * Load & Save
492
 ****************************************************************************/
493

494
bool RGBMatrix::loadXML(QXmlStreamReader &root)
3✔
495
{
496
    if (root.name() != KXMLQLCFunction)
3✔
497
    {
498
        qWarning() << Q_FUNC_INFO << "Function node not found";
1✔
499
        return false;
1✔
500
    }
501

502
    if (root.attributes().value(KXMLQLCFunctionType).toString() != typeToString(Function::RGBMatrixType))
4✔
503
    {
504
        qWarning() << Q_FUNC_INFO << "Function is not an RGB matrix";
1✔
505
        return false;
1✔
506
    }
507

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

571
    return true;
1✔
572
}
573

574
bool RGBMatrix::saveXML(QXmlStreamWriter *doc) const
1✔
575
{
576
    Q_ASSERT(doc != NULL);
1✔
577

578
    /* Function tag */
579
    doc->writeStartElement(KXMLQLCFunction);
2✔
580

581
    /* Common attributes */
582
    saveXMLCommon(doc);
1✔
583

584
    /* Tempo type */
585
    saveXMLTempoType(doc);
1✔
586

587
    /* Speeds */
588
    saveXMLSpeed(doc);
1✔
589

590
    /* Direction */
591
    saveXMLDirection(doc);
1✔
592

593
    /* Run order */
594
    saveXMLRunOrder(doc);
1✔
595

596
    /* Algorithm */
597
    if (m_algorithm != NULL)
1✔
598
        m_algorithm->saveXML(doc);
1✔
599

600
    /* LEGACY - Dimmer Control */
601
    if (dimmerControl())
1✔
602
        doc->writeTextElement(KXMLQLCRGBMatrixDimmerControl, QString::number(dimmerControl()));
×
603

604
    /* Colors */
605
    for (int i = 0; i < m_rgbColors.count(); i++)
6✔
606
    {
607
        if (m_rgbColors.at(i).isValid() == false)
5✔
608
            continue;
×
609

610
        doc->writeStartElement(KXMLQLCRGBMatrixColor);
10✔
611
        doc->writeAttribute(KXMLQLCRGBMatrixColorIndex, QString::number(i));
10✔
612
        doc->writeCharacters(QString::number(m_rgbColors.at(i).rgb()));
5✔
613
        doc->writeEndElement();
5✔
614
    }
615

616
    /* Control Mode */
617
    doc->writeTextElement(KXMLQLCRGBMatrixControlMode, RGBMatrix::controlModeToString(m_controlMode));
2✔
618

619
    /* Fixture Group */
620
    doc->writeTextElement(KXMLQLCRGBMatrixFixtureGroup, QString::number(fixtureGroup()));
2✔
621

622
    /* Properties */
623
    QMapIterator<QString, QString> it(m_properties);
1✔
624
    while (it.hasNext())
1✔
625
    {
626
        it.next();
×
627
        doc->writeStartElement(KXMLQLCRGBMatrixProperty);
×
628
        doc->writeAttribute(KXMLQLCRGBMatrixPropertyName, it.key());
×
629
        doc->writeAttribute(KXMLQLCRGBMatrixPropertyValue, it.value());
×
630
        doc->writeEndElement();
×
631
    }
632

633
    /* End the <Function> tag */
634
    doc->writeEndElement();
1✔
635

636
    return true;
1✔
637
}
1✔
638

639
/****************************************************************************
640
 * Running
641
 ****************************************************************************/
642

643
void RGBMatrix::tap()
×
644
{
645
    if (stopped() == false)
×
646
    {
647
        FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
×
648
        // Filter out taps that are too close to each other
649
        if (grp != NULL && uint(m_roundTime.elapsed()) >= (duration() / 4))
×
650
        {
651
            roundCheck();
×
652
            resetElapsed();
×
653
        }
654
    }
655
}
×
656

657
void RGBMatrix::checkEngineCreation()
×
658
{
659
    m_runAlgorithm = m_algorithm;
×
660
    m_requestEngineCreation = false;
×
661
}
×
662

663
void RGBMatrix::preRun(MasterTimer *timer)
×
664
{
665
    {
666
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
667

668
        m_group = doc()->fixtureGroup(m_fixtureGroupID);
×
669
        if (m_group == NULL)
×
670
        {
671
            // No fixture group to control
672
            stop(FunctionParent::master());
×
673
            return;
×
674
        }
675

676
        if (m_algorithm != NULL)
×
677
        {
678
            checkEngineCreation();
×
679

680
            // Copy direction from parent class direction
681
            m_stepHandler->initializeDirection(direction(), m_rgbColors[0], m_rgbColors[1], m_stepsCount, m_runAlgorithm);
×
682

683
            // Update continuous phase when starting playback
684
            if (m_stepsCount > 0)
×
685
                m_continuousPhase = double(m_stepHandler->currentStepIndex()) / double(m_stepsCount);
×
686

687
            if (m_runAlgorithm->type() == RGBAlgorithm::Script)
×
688
            {
689
                RGBScript *script = static_cast<RGBScript*> (m_runAlgorithm);
×
690
                QMapIterator<QString, QString> it(m_properties);
×
691
                while (it.hasNext())
×
692
                {
693
                    it.next();
×
694
                    script->setProperty(it.key(), it.value());
×
695
                }
696
            }
×
697
            else if (m_runAlgorithm->type() == RGBAlgorithm::Image)
×
698
            {
699
                RGBImage *image = static_cast<RGBImage*> (m_runAlgorithm);
×
700
                if (image->animatedSource())
×
701
                    image->rewindAnimation();
×
702
            }
703
        }
704
    }
×
705

706
    m_roundTime.restart();
×
707

708
    Function::preRun(timer);
×
709
}
710

711
void RGBMatrix::write(MasterTimer *timer, QList<Universe *> universes)
×
712
{
713
    Q_UNUSED(timer);
714

715
    {
716
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
717
        if (m_group == NULL)
×
718
        {
719
            // No fixture group to control
720
            stop(FunctionParent::master());
×
721
            return;
×
722
        }
723

724
        // No time to do anything.
725
        if (duration() == 0)
×
726
            return;
×
727

728
        if (m_algorithm != NULL && m_requestEngineCreation)
×
729
            checkEngineCreation();
×
730

731
        // Invalid/nonexistent script
732
        if (m_runAlgorithm == NULL || m_runAlgorithm->apiVersion() == 0)
×
733
            return;
×
734

735
        if (isPaused() == false)
×
736
        {
737
            // Get a new map every time elapsed is reset to zero
738
            if (elapsed() < MasterTimer::tick())
×
739
            {
740
                if (tempoType() == Beats)
×
741
                    m_stepBeatDuration = beatsToTime(duration(), timer->beatTimeDuration());
×
742

743
                //qDebug() << "RGBMatrix step" << m_stepHandler->currentStepIndex() << ", color:" << QString::number(m_stepHandler->stepColor().rgb(), 16);
744
                m_runAlgorithm->rgbMap(m_group->size(), m_stepHandler->stepColor().rgb(),
×
745
                                       m_stepHandler->currentStepIndex(), m_stepHandler->m_map);
×
746
                updateMapChannels(m_stepHandler->m_map, m_group, universes);
×
747
            }
748
        }
749
    }
×
750

751
    if (isPaused() == false)
×
752
    {
753
        // Increment the ms elapsed time
754
        incrementElapsed();
×
755

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

792
void RGBMatrix::postRun(MasterTimer *timer, QList<Universe *> universes)
×
793
{
794
    uint fadeout = overrideFadeOutSpeed() == defaultSpeed() ? fadeOutSpeed() : overrideFadeOutSpeed();
×
795

796
    /* If no fade out is needed, dismiss all the requested faders.
797
     * Otherwise, set all the faders to fade out and let Universe dismiss them
798
     * when done */
799
    if (fadeout == 0)
×
800
    {
801
        dismissAllFaders();
×
802
    }
803
    else
804
    {
805
        if (tempoType() == Beats)
×
806
            fadeout = beatsToTime(fadeout, timer->beatTimeDuration());
×
807

808
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
809
        {
810
            if (!fader.isNull())
×
811
                fader->setFadeOut(true, fadeout);
×
812
        }
×
813
    }
814

815
    m_fadersMap.clear();
×
816

817
    {
818
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
819
        checkEngineCreation();
×
820
        if (m_runAlgorithm != NULL)
×
821
            m_runAlgorithm->postRun();
×
822
    }
×
823

824
    Function::postRun(timer, universes);
×
825
}
×
826

827
void RGBMatrix::roundCheck()
×
828
{
829
    QMutexLocker algorithmLocker(&m_algorithmMutex);
×
830
    if (m_algorithm == NULL)
×
831
        return;
×
832

833
    if (m_stepHandler->checkNextStep(runOrder(), m_rgbColors[0], m_rgbColors[1], m_stepsCount) == false)
×
834
        stop(FunctionParent::master());
×
835

836
    // Update continuous phase based on current step index (prevents cumulative rounding errors)
837
    // This is analogous to how EFX uses m_currentAngle for phase scaling
838
    if (m_stepsCount > 0)
×
839
        m_continuousPhase = double(m_stepHandler->currentStepIndex()) / double(m_stepsCount);
×
840

841
    m_roundTime.restart();
×
842

843
    if (tempoType() == Beats)
×
844
        roundElapsed(m_stepBeatDuration);
×
845
    else
846
        roundElapsed(duration());
×
847
}
×
848

849
QSharedPointer<GenericFader> RGBMatrix::getFader(Universe *universe)
×
850
{
851
    // get the universe Fader first. If doesn't exist, create it
852
    if (universe == NULL)
×
853
        return QSharedPointer<GenericFader>();
×
854

855
    QSharedPointer<GenericFader> fader = m_fadersMap.value(universe->id(), QSharedPointer<GenericFader>());
×
856
    if (fader.isNull())
×
857
    {
858
        fader = universe->requestFader();
×
859
        fader->adjustIntensity(getAttributeValue(Intensity));
×
860
        fader->setBlendMode(blendMode());
×
861
        fader->setName(name());
×
862
        fader->setParentFunctionID(id());
×
863
        m_fadersMap[universe->id()] = fader;
×
864
    }
865

866
    return fader;
×
867
}
×
868

869
void RGBMatrix::updateFaderValues(FadeChannel &fc, uchar value, uint fadeTime)
×
870
{
871
    fc.setStart(fc.current());
×
872
    fc.setTarget(value);
×
873
    fc.setElapsed(0);
×
874
    fc.setReady(false);
×
875
    // fade in/out depends on target value
876
    if (value == 0)
×
877
        fc.setFadeTime(fadeOutSpeed());
×
878
    else
879
        fc.setFadeTime(fadeTime);
×
880
}
×
881

882
void RGBMatrix::updateMapChannels(const RGBMap& map, const FixtureGroup *grp, QList<Universe *> universes)
×
883
{
884
    uint fadeTime = (overrideFadeInSpeed() == defaultSpeed()) ? fadeInSpeed() : overrideFadeInSpeed();
×
885

886
    // Create/modify fade channels for ALL heads in the group
887
    QMapIterator<QLCPoint, GroupHead> it(grp->headsMap());
×
888
    while (it.hasNext())
×
889
    {
890
        it.next();
×
891
        QLCPoint pt = it.key();
×
892
        GroupHead grpHead = it.value();
×
893
        Fixture *fxi = doc()->fixture(grpHead.fxi);
×
894
        if (fxi == NULL)
×
895
            continue;
×
896

897
        QLCFixtureHead head = fxi->head(grpHead.head);
×
898

899
        if (pt.y() >= map.count() || pt.x() >= map[pt.y()].count())
×
900
            continue;
×
901

902
        uint col = map[pt.y()][pt.x()];
×
903
        QVector<quint32> channelList;
×
904
        QVector<uchar> valueList;
×
905

906
        if (m_controlMode == ControlModeRgb)
×
907
        {
908
            channelList = head.rgbChannels();
×
909

910
            if (channelList.size() == 3)
×
911
            {
912
                valueList.append(qRed(col));
×
913
                valueList.append(qGreen(col));
×
914
                valueList.append(qBlue(col));
×
915
            }
916
            else
917
            {
918
                channelList = head.cmyChannels();
×
919

920
                if (channelList.size() == 3)
×
921
                {
922
                    // CMY color mixing
923
                    QColor cmyCol(col);
×
924
                    valueList.append(cmyCol.cyan());
×
925
                    valueList.append(cmyCol.magenta());
×
926
                    valueList.append(cmyCol.yellow());
×
927
                }
928
            }
929
        }
930
        else if (m_controlMode == ControlModeShutter)
×
931
        {
932
            channelList = head.shutterChannels();
×
933

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

960
            quint32 masterDim = fxi->masterIntensityChannel();
×
961
            quint32 headDim = head.channelNumber(QLCChannel::Intensity, QLCChannel::MSB);
×
962

963
            if (masterDim != QLCChannel::invalid())
×
964
            {
965
                channelList.append(masterDim);
×
966
                valueList.append(rgbToGrey(col));
×
967
            }
968

969
            if (headDim != QLCChannel::invalid() && headDim != masterDim)
×
970
            {
971
                channelList.append(headDim);
×
972
                // If a master dimmer is present, it carries the greyscale fade and the
973
                // per-head dimmer is just opened fully (on/off). With no master dimmer
974
                // (e.g. generic single-channel dimmers), the head dimmer must carry the
975
                // greyscale value itself, otherwise it would only ever output 0 or 255.
976
                if (masterDim != QLCChannel::invalid())
×
977
                    valueList.append(rgbToGrey(col) == 0 ? 0 : 255);
×
978
                else
979
                    valueList.append(rgbToGrey(col));
×
980
            }
981
        }
×
982
        else
983
        {
984
            if (m_controlMode == ControlModeWhite)
×
985
                channelList.append(head.channelNumber(QLCChannel::White, QLCChannel::MSB));
×
986
            else if (m_controlMode == ControlModeAmber)
×
987
                channelList.append(head.channelNumber(QLCChannel::Amber, QLCChannel::MSB));
×
988
            else if (m_controlMode == ControlModeUV)
×
989
                channelList.append(head.channelNumber(QLCChannel::UV, QLCChannel::MSB));
×
990

991
            valueList.append(rgbToGrey(col));
×
992
        }
993

994
        quint32 absAddress = fxi->universeAddress();
×
995

996
        for (int i = 0; i < channelList.count(); i++)
×
997
        {
998
            if (channelList.at(i) == QLCChannel::invalid())
×
999
                continue;
×
1000

1001
            quint32 universeIndex = floor((absAddress + channelList.at(i)) / 512);
×
1002
            Universe *universe = universes.at(universeIndex);
×
1003
            QSharedPointer<GenericFader> fader = getFader(universe);
×
1004
            if (fader.isNull())
×
1005
                continue;
×
1006

1007
            const quint32 fixtureID = grpHead.fxi;
×
1008
            const quint32 channel = channelList.at(i);
×
1009
            const uchar value = valueList.at(i);
×
1010
            fader->updateChannel(doc(), universe, fixtureID, channel, [this, value, fadeTime](FadeChannel &fc)
×
1011
            {
1012
                updateFaderValues(fc, value, fadeTime);
×
1013
            });
×
1014
        }
×
1015
    }
×
1016
}
×
1017

1018
uchar RGBMatrix::rgbToGrey(uint col)
×
1019
{
1020
    uchar r = qRed(col);
×
1021
    uchar g = qGreen(col);
×
1022
    uchar b = qBlue(col);
×
1023

1024
    // Special case: if R=G=B (grayscale), return the value directly.
1025
    // This avoids floating-point precision issues.
1026
    if (r == g && g == b)
×
1027
        return r;
×
1028

1029
    // the weights are taken from
1030
    // https://en.wikipedia.org/wiki/YUV#SDTV_with_BT.601
1031
    return uchar(round(0.299 * r + 0.587 * g + 0.114 * b));
×
1032
}
1033

1034
/*********************************************************************
1035
 * Attributes
1036
 *********************************************************************/
1037

1038
int RGBMatrix::adjustAttribute(qreal fraction, int attributeId)
×
1039
{
1040
    int attrIndex = Function::adjustAttribute(fraction, attributeId);
×
1041

1042
    if (attrIndex == Intensity)
×
1043
    {
1044
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
1045
        {
1046
            if (!fader.isNull())
×
1047
                fader->adjustIntensity(getAttributeValue(Function::Intensity));
×
1048
        }
×
1049
    }
1050
    else if (attrIndex >= Color1Attr && attrIndex <= ColorLastAttr)
×
1051
    {
1052
        applyColorAttribute(attrIndex - Color1Attr, getAttributeValue(attrIndex));
×
1053
    }
1054
    else if (attrIndex == PatternAttr)
×
1055
    {
1056
        applyPatternAttribute(getAttributeValue(PatternAttr));
×
1057
    }
NEW
1058
    else if (attrIndex >= ScriptPropertyAttr)
×
1059
    {
NEW
1060
        applyScriptPropertyAttribute(attrIndex - ScriptPropertyAttr, getAttributeValue(attrIndex));
×
1061
    }
1062

1063
    return attrIndex;
×
1064
}
1065

1066
void RGBMatrix::applyStyleAttributes()
×
1067
{
1068
    for (int i = 0; i < ColorAttributeCount; ++i)
×
1069
        applyColorAttribute(i, getAttributeValue(Color1Attr + i));
×
1070

1071
    applyPatternAttribute(getAttributeValue(PatternAttr));
×
1072

NEW
1073
    for (int i = 0; i < scriptPropertyAttributes().count(); i++)
×
NEW
1074
        applyScriptPropertyAttribute(i, getAttributeValue(ScriptPropertyAttr + i));
×
NEW
1075
}
×
1076

1077
QList<RGBScriptProperty> RGBMatrix::scriptPropertyAttributes() const
26✔
1078
{
1079
    QList<RGBScriptProperty> list;
26✔
1080

1081
    if (m_algorithm == NULL || m_algorithm->type() != RGBAlgorithm::Script)
26✔
1082
        return list;
9✔
1083

1084
    RGBScript *script = static_cast<RGBScript*> (m_algorithm);
17✔
1085

1086
    foreach (RGBScriptProperty prop, script->properties())
51✔
1087
    {
1088
        /** Only the properties that a slider can drive are exposed:
1089
         *  a list needs at least two values to pick from, a range needs
1090
         *  a meaningful span and a string cannot be controlled at all */
1091
        if (prop.m_type == RGBScriptProperty::List)
17✔
1092
        {
1093
            if (prop.m_listValues.count() > 1)
17✔
1094
                list.append(prop);
17✔
1095
        }
NEW
1096
        else if (prop.m_type == RGBScriptProperty::Range)
×
1097
        {
NEW
1098
            if (prop.m_rangeMaxValue > prop.m_rangeMinValue)
×
NEW
1099
                list.append(prop);
×
1100
        }
NEW
1101
        else if (prop.m_type == RGBScriptProperty::Float)
×
1102
        {
NEW
1103
            list.append(prop);
×
1104
        }
1105
    }
34✔
1106

1107
    return list;
17✔
NEW
1108
}
×
1109

1110
QString RGBMatrix::scriptPropertyAttributeName(const RGBScriptProperty &prop)
17✔
1111
{
1112
    return prop.m_displayName.isEmpty() ? prop.m_name : prop.m_displayName;
17✔
1113
}
1114

1115
void RGBMatrix::unregisterScriptPropertyAttributes()
13✔
1116
{
1117
    foreach (RGBScriptProperty prop, scriptPropertyAttributes())
30✔
1118
        unregisterAttribute(scriptPropertyAttributeName(prop));
17✔
1119
}
13✔
1120

1121
void RGBMatrix::registerScriptPropertyAttributes()
13✔
1122
{
1123
    QMutexLocker algorithmLocker(&m_algorithmMutex);
13✔
1124

1125
    foreach (RGBScriptProperty prop, scriptPropertyAttributes())
39✔
1126
    {
1127
        qreal min = 0.0;
13✔
1128
        qreal max = 1.0;
13✔
1129

1130
        if (prop.m_type == RGBScriptProperty::List)
13✔
1131
            max = prop.m_listValues.count() - 1;
13✔
NEW
1132
        else if (prop.m_type == RGBScriptProperty::Range)
×
1133
        {
NEW
1134
            min = prop.m_rangeMinValue;
×
NEW
1135
            max = prop.m_rangeMaxValue;
×
1136
        }
1137
        /** Scripts don't declare a range for float properties,
1138
         *  so 0.0 - 1.0 is assumed */
1139

1140
        /** A list property holds a string, so the attribute value is the
1141
         *  index of the current value in the values list */
1142
        QString current = property(prop.m_name);
13✔
1143
        qreal value = prop.m_type == RGBScriptProperty::List ?
26✔
1144
                      qMax(0, prop.m_listValues.indexOf(current)) : current.toDouble();
13✔
1145

1146
        registerAttribute(scriptPropertyAttributeName(prop), LastWins | Single,
13✔
1147
                          min, max, qBound(min, value, max));
13✔
1148
    }
26✔
1149
}
13✔
1150

NEW
1151
void RGBMatrix::applyScriptPropertyAttribute(int attrIndex, qreal value)
×
1152
{
NEW
1153
    QList<RGBScriptProperty> props = scriptPropertyAttributes();
×
NEW
1154
    if (attrIndex < 0 || attrIndex >= props.count())
×
NEW
1155
        return;
×
1156

NEW
1157
    const RGBScriptProperty prop = props.at(attrIndex);
×
NEW
1158
    QString strValue;
×
1159

NEW
1160
    switch (prop.m_type)
×
1161
    {
NEW
1162
        case RGBScriptProperty::List:
×
NEW
1163
            strValue = prop.m_listValues.at(qBound(0, int(qRound(value)), prop.m_listValues.count() - 1));
×
NEW
1164
        break;
×
NEW
1165
        case RGBScriptProperty::Float:
×
NEW
1166
            strValue = QString::number(value);
×
NEW
1167
        break;
×
NEW
1168
        default:
×
NEW
1169
            strValue = QString::number(qRound(value));
×
NEW
1170
        break;
×
1171
    }
1172

NEW
1173
    if (property(prop.m_name) != strValue)
×
NEW
1174
        setProperty(prop.m_name, strValue);
×
UNCOV
1175
}
×
1176

1177
void RGBMatrix::applyColorAttribute(int colorIndex, qreal packedColor)
×
1178
{
1179
    if (colorIndex < 0 || colorIndex >= ColorAttributeCount)
×
1180
        return;
×
1181

1182
    int packed = qRound(packedColor);
×
1183
    QColor targetColor = packed < 0 ? QColor() :
1184
                                      QColor::fromRgb(static_cast<QRgb>((packed & RGBMatrixColorMask) | 0xFF000000));
×
1185
    if (getColor(colorIndex) == targetColor)
×
1186
        return;
×
1187

1188
    bool previous = m_applyingStyleAttributes;
×
1189
    m_applyingStyleAttributes = true;
×
1190
    setColor(colorIndex, targetColor);
×
1191
    m_applyingStyleAttributes = previous;
×
1192
}
1193

1194
void RGBMatrix::applyPatternAttribute(qreal patternIndex)
×
1195
{
1196
    if (doc() == NULL)
×
1197
        return;
×
1198

1199
    QStringList algoList = RGBAlgorithm::algorithms(doc());
×
1200
    if (algoList.isEmpty())
×
1201
        return;
×
1202

1203
    int idx = qRound(patternIndex);
×
1204
    if (idx < 0)
×
1205
        idx = 0;
×
1206
    else if (idx >= algoList.count())
×
1207
        idx = algoList.count() - 1;
×
1208

1209
    RGBAlgorithm *algo = RGBAlgorithm::algorithm(doc(), algoList.at(idx));
×
1210
    if (algo == NULL)
×
1211
        return;
×
1212

1213
    if (m_algorithm != NULL && m_algorithm->name() == algo->name())
×
1214
    {
1215
        delete algo;
×
1216
        return;
×
1217
    }
1218

1219
    algo->setColors(getColors());
×
1220

1221
    bool previous = m_applyingStyleAttributes;
×
1222
    m_applyingStyleAttributes = true;
×
1223
    setAlgorithm(algo);
×
1224
    m_applyingStyleAttributes = previous;
×
1225
}
×
1226

1227
/*************************************************************************
1228
 * Blend mode
1229
 *************************************************************************/
1230

1231
void RGBMatrix::setBlendMode(Universe::BlendMode mode)
×
1232
{
1233
    if (mode == blendMode())
×
1234
        return;
×
1235

1236
    foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
1237
    {
1238
        if (!fader.isNull())
×
1239
            fader->setBlendMode(mode);
×
1240
    }
×
1241

1242
    Function::setBlendMode(mode);
×
1243
    emit changed(id());
×
1244
}
1245

1246
/*************************************************************************
1247
 * Control Mode
1248
 *************************************************************************/
1249

1250
RGBMatrix::ControlMode RGBMatrix::controlMode() const
2✔
1251
{
1252
    return m_controlMode;
2✔
1253
}
1254

1255
void RGBMatrix::setControlMode(RGBMatrix::ControlMode mode)
3✔
1256
{
1257
    m_controlMode = mode;
3✔
1258
    emit changed(id());
3✔
1259
}
3✔
1260

1261
RGBMatrix::ControlMode RGBMatrix::stringToControlMode(QString mode)
1✔
1262
{
1263
    if (mode == KXMLQLCRGBMatrixControlModeRgb)
1✔
1264
        return ControlModeRgb;
1✔
1265
    else if (mode == KXMLQLCRGBMatrixControlModeAmber)
×
1266
        return ControlModeAmber;
×
1267
    else if (mode == KXMLQLCRGBMatrixControlModeWhite)
×
1268
        return ControlModeWhite;
×
1269
    else if (mode == KXMLQLCRGBMatrixControlModeUV)
×
1270
        return ControlModeUV;
×
1271
    else if (mode == KXMLQLCRGBMatrixControlModeDimmer)
×
1272
        return ControlModeDimmer;
×
1273
    else if (mode == KXMLQLCRGBMatrixControlModeShutter)
×
1274
        return ControlModeShutter;
×
1275

1276
    return ControlModeRgb;
×
1277
}
1278

1279
QString RGBMatrix::controlModeToString(RGBMatrix::ControlMode mode)
1✔
1280
{
1281
    switch(mode)
1✔
1282
    {
1283
        default:
1✔
1284
        case ControlModeRgb:
1285
            return QString(KXMLQLCRGBMatrixControlModeRgb);
1✔
1286
        break;
1287
        case ControlModeAmber:
×
1288
            return QString(KXMLQLCRGBMatrixControlModeAmber);
×
1289
        break;
1290
        case ControlModeWhite:
×
1291
            return QString(KXMLQLCRGBMatrixControlModeWhite);
×
1292
        break;
1293
        case ControlModeUV:
×
1294
            return QString(KXMLQLCRGBMatrixControlModeUV);
×
1295
        break;
1296
        case ControlModeDimmer:
×
1297
            return QString(KXMLQLCRGBMatrixControlModeDimmer);
×
1298
        break;
1299
        case ControlModeShutter:
×
1300
            return QString(KXMLQLCRGBMatrixControlModeShutter);
×
1301
        break;
1302
    }
1303
}
1304

1305

1306
/*************************************************************************
1307
 *************************************************************************
1308
 *                          RGBMatrixStep class
1309
 *************************************************************************
1310
 *************************************************************************/
1311

1312
RGBMatrixStep::RGBMatrixStep()
10✔
1313
    : m_direction(Function::Forward)
10✔
1314
    , m_currentStepIndex(0)
10✔
1315
    , m_stepColor(QColor())
10✔
1316
    , m_crDelta(0)
10✔
1317
    , m_cgDelta(0)
10✔
1318
    , m_cbDelta(0)
10✔
1319
{
1320

1321
}
10✔
1322

1323
void RGBMatrixStep::setCurrentStepIndex(int index)
×
1324
{
1325
    m_currentStepIndex = index;
×
1326
}
×
1327

1328
int RGBMatrixStep::currentStepIndex() const
1✔
1329
{
1330
    return m_currentStepIndex;
1✔
1331
}
1332

1333
void RGBMatrixStep::calculateColorDelta(const QColor& startColor, const QColor& endColor, const RGBAlgorithm *algorithm)
16✔
1334
{
1335
    m_crDelta = 0;
16✔
1336
    m_cgDelta = 0;
16✔
1337
    m_cbDelta = 0;
16✔
1338

1339
    if (endColor.isValid() && algorithm != NULL && algorithm->acceptColors() > 1)
16✔
1340
    {
1341
        m_crDelta = endColor.red() - startColor.red();
10✔
1342
        m_cgDelta = endColor.green() - startColor.green();
10✔
1343
        m_cbDelta = endColor.blue() - startColor.blue();
10✔
1344

1345
        //qDebug() << "Color deltas:" << m_crDelta << m_cgDelta << m_cbDelta;
1346
    }
1347
}
16✔
1348

1349
void RGBMatrixStep::setStepColor(QColor color)
×
1350
{
1351
    m_stepColor = color;
×
1352
}
×
1353

1354
QColor RGBMatrixStep::stepColor() const
6✔
1355
{
1356
    return m_stepColor;
6✔
1357
}
1358

1359
void RGBMatrixStep::updateStepColor(int stepIndex, QColor startColor, int stepsCount)
×
1360
{
1361
    if (stepsCount <= 0)
×
1362
        return;
×
1363

1364
    if (stepsCount == 1)
×
1365
    {
1366
        m_stepColor = startColor;
×
1367
    }
1368
    else
1369
    {
1370
        m_stepColor.setRed(startColor.red() + (m_crDelta * stepIndex / (stepsCount - 1)));
×
1371
        m_stepColor.setGreen(startColor.green() + (m_cgDelta * stepIndex / (stepsCount - 1)));
×
1372
        m_stepColor.setBlue(startColor.blue() + (m_cbDelta * stepIndex / (stepsCount - 1)));
×
1373
    }
1374

1375
    //qDebug() << "RGBMatrix step" << stepIndex << ", color:" << QString::number(m_stepColor.rgb(), 16);
1376
}
1377

1378
void RGBMatrixStep::initializeDirection(Function::Direction direction, const QColor& startColor, const QColor& endColor, int stepsCount, const RGBAlgorithm *algorithm)
×
1379
{
1380
    m_direction = direction;
×
1381

1382
    if (m_direction == Function::Forward)
×
1383
    {
1384
        setCurrentStepIndex(0);
×
1385
        setStepColor(startColor);
×
1386
    }
1387
    else
1388
    {
1389
        setCurrentStepIndex(stepsCount - 1);
×
1390

1391
        if (endColor.isValid())
×
1392
            setStepColor(endColor);
×
1393
        else
1394
            setStepColor(startColor);
×
1395
    }
1396

1397
    calculateColorDelta(startColor, endColor, algorithm);
×
1398
}
×
1399

1400
bool RGBMatrixStep::checkNextStep(Function::RunOrder order,
×
1401
                                  QColor startColor, QColor endColor, int stepsNumber)
1402
{
1403
    if (order == Function::PingPong)
×
1404
    {
1405
        if (m_direction == Function::Forward && (m_currentStepIndex + 1) == stepsNumber)
×
1406
        {
1407
            m_direction = Function::Backward;
×
1408
            m_currentStepIndex = stepsNumber - 2;
×
1409
            if (endColor.isValid())
×
1410
                m_stepColor = endColor;
×
1411

1412
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1413
        }
1414
        else if (m_direction == Function::Backward && (m_currentStepIndex - 1) < 0)
×
1415
        {
1416
            m_direction = Function::Forward;
×
1417
            m_currentStepIndex = 1;
×
1418
            m_stepColor = startColor;
×
1419
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1420
        }
1421
        else
1422
        {
1423
            if (m_direction == Function::Forward)
×
1424
                m_currentStepIndex++;
×
1425
            else
1426
                m_currentStepIndex--;
×
1427
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1428
        }
1429
    }
1430
    else if (order == Function::SingleShot)
×
1431
    {
1432
        if (m_direction == Function::Forward)
×
1433
        {
1434
            if (m_currentStepIndex >= stepsNumber - 1)
×
1435
                return false;
×
1436
            else
1437
            {
1438
                m_currentStepIndex++;
×
1439
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1440
            }
1441
        }
1442
        else
1443
        {
1444
            if (m_currentStepIndex <= 0)
×
1445
                return false;
×
1446
            else
1447
            {
1448
                m_currentStepIndex--;
×
1449
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1450
            }
1451
        }
1452
    }
1453
    else
1454
    {
1455
        if (m_direction == Function::Forward)
×
1456
        {
1457
            if (m_currentStepIndex >= stepsNumber - 1)
×
1458
            {
1459
                m_currentStepIndex = 0;
×
1460
                m_stepColor = startColor;
×
1461
            }
1462
            else
1463
            {
1464
                m_currentStepIndex++;
×
1465
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1466
            }
1467
        }
1468
        else
1469
        {
1470
            if (m_currentStepIndex <= 0)
×
1471
            {
1472
                m_currentStepIndex = stepsNumber - 1;
×
1473
                if (endColor.isValid())
×
1474
                    m_stepColor = endColor;
×
1475
            }
1476
            else
1477
            {
1478
                m_currentStepIndex--;
×
1479
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1480
            }
1481
        }
1482
    }
1483

1484
    return true;
×
1485
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc