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

mcallegari / qlcplus / 19144422256

06 Nov 2025 05:33PM UTC coverage: 34.256% (-0.1%) from 34.358%
19144422256

push

github

mcallegari
Back to 5.1.0 debug

17718 of 51723 relevant lines covered (34.26%)

19528.23 hits per line

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

76.63
/engine/src/scene.cpp
1
/*
2
  Q Light Controller Plus
3
  scene.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 <QDebug>
24
#include <cmath>
25
#include <QList>
26
#include <QFile>
27

28
#include "qlcfixturedef.h"
29
#include "qlccapability.h"
30

31
#include "genericfader.h"
32
#include "mastertimer.h"
33
#include "universe.h"
34
#include "scene.h"
35
#include "doc.h"
36
#include "bus.h"
37

38
/*****************************************************************************
39
 * Initialization
40
 *****************************************************************************/
41

42
Scene::Scene(Doc* doc)
214✔
43
    : Function(doc, Function::SceneType)
44
    , m_legacyFadeBus(Bus::invalid())
428✔
45
    , m_flashOverrides(false)
214✔
46
    , m_flashForceLTP(false)
214✔
47
    , m_blendFunctionID(Function::invalidId())
214✔
48
{
49
    setName(tr("New Scene"));
214✔
50
    registerAttribute(tr("ParentIntensity"), Multiply | Single);
214✔
51
}
214✔
52

53
Scene::~Scene()
414✔
54
{
55
}
414✔
56

57
QIcon Scene::getIcon() const
×
58
{
59
    return QIcon(":/scene.png");
×
60
}
61

62
quint32 Scene::totalDuration()
2✔
63
{
64
    return (quint32)duration();
2✔
65
}
66

67
/*****************************************************************************
68
 * Copying
69
 *****************************************************************************/
70

71
Function* Scene::createCopy(Doc* doc, bool addToDoc)
3✔
72
{
73
    Q_ASSERT(doc != NULL);
3✔
74

75
    Function* copy = new Scene(doc);
3✔
76
    if (copy->copyFrom(this) == false)
3✔
77
    {
78
        delete copy;
×
79
        copy = NULL;
×
80
    }
81
    if (addToDoc == true && doc->addFunction(copy) == false)
3✔
82
    {
83
        delete copy;
×
84
        copy = NULL;
×
85
    }
86

87
    return copy;
3✔
88
}
89

90
bool Scene::copyFrom(const Function* function)
6✔
91
{
92
    const Scene* scene = qobject_cast<const Scene*> (function);
6✔
93
    if (scene == NULL)
6✔
94
        return false;
1✔
95

96
    m_values.clear();
5✔
97
    m_values = scene->m_values;
5✔
98
    m_fixtures.clear();
5✔
99
    m_fixtures = scene->m_fixtures;
5✔
100
    m_channelGroups.clear();
5✔
101
    m_channelGroups = scene->m_channelGroups;
5✔
102
    m_channelGroupsLevels.clear();
5✔
103
    m_channelGroupsLevels = scene->m_channelGroupsLevels;
5✔
104
    m_fixtureGroups.clear();
5✔
105
    m_fixtureGroups = scene->m_fixtureGroups;
5✔
106
    m_palettes.clear();
5✔
107
    m_palettes = scene->m_palettes;
5✔
108

109
    return Function::copyFrom(function);
5✔
110
}
111

112
/*****************************************************************************
113
 * Values
114
 *****************************************************************************/
115

116
void Scene::setValue(const SceneValue& scv, bool blind, bool checkHTP)
556✔
117
{
118
    bool valChanged = false;
556✔
119

120
    if (!m_fixtures.contains(scv.fxi))
556✔
121
    {
122
        qWarning() << Q_FUNC_INFO << "Setting value for unknown fixture" << scv.fxi << ". Adding it.";
202✔
123
        m_fixtures.append(scv.fxi);
202✔
124
    }
125

126
    {
127
        QMutexLocker locker(&m_valueListMutex);
556✔
128

129
        QMap<SceneValue, uchar>::iterator it = m_values.find(scv);
556✔
130
        if (it == m_values.end())
556✔
131
        {
132
            m_values.insert(scv, scv.value);
553✔
133
            valChanged = true;
553✔
134
        }
135
        else if (it.value() != scv.value)
3✔
136
        {
137
            const_cast<uchar&>(it.key().value) = scv.value;
3✔
138
            it.value() = scv.value;
3✔
139
            valChanged = true;
3✔
140
        }
141

142
        // if the scene is running, we must
143
        // update/add the changed channel
144
        if (blind == false && m_fadersMap.isEmpty() == false)
556✔
145
        {
146
            Fixture *fixture = doc()->fixture(scv.fxi);
2✔
147
            if (fixture != NULL)
2✔
148
            {
149
                quint32 universe = fixture->universe();
2✔
150

151
                FadeChannel fc(doc(), scv.fxi, scv.channel);
2✔
152
                fc.setStart(scv.value);
2✔
153
                fc.setTarget(scv.value);
2✔
154
                fc.setCurrent(scv.value);
2✔
155
                fc.setFadeTime(0);
2✔
156

157
                if (m_fadersMap.contains(universe))
2✔
158
                {
159
                    if (checkHTP == false)
2✔
160
                        m_fadersMap[universe]->replace(fc);
1✔
161
                    else
162
                        m_fadersMap[universe]->add(fc);
1✔
163
                }
164
            }
2✔
165
        }
166
    }
556✔
167

168
    emit changed(this->id());
556✔
169
    if (valChanged)
556✔
170
        emit valueChanged(scv);
556✔
171
}
556✔
172

173
void Scene::setValue(quint32 fxi, quint32 ch, uchar value)
547✔
174
{
175
    setValue(SceneValue(fxi, ch, value));
547✔
176
}
547✔
177

178
void Scene::unsetValue(quint32 fxi, quint32 ch)
5✔
179
{
180
    if (!m_fixtures.contains(fxi))
5✔
181
        qWarning() << Q_FUNC_INFO << "Unsetting value for unknown fixture" << fxi;
1✔
182

183
    {
184
        QMutexLocker locker(&m_valueListMutex);
5✔
185
        m_values.remove(SceneValue(fxi, ch, 0));
5✔
186
    }
5✔
187

188
    emit changed(this->id());
5✔
189
}
5✔
190

191
uchar Scene::value(quint32 fxi, quint32 ch)
421✔
192
{
193
    return m_values.value(SceneValue(fxi, ch, 0), 0);
421✔
194
}
195

196
bool Scene::checkValue(SceneValue val)
409✔
197
{
198
    return m_values.contains(val);
409✔
199
}
200

201
QList <SceneValue> Scene::values() const
222✔
202
{
203
    return m_values.keys();
222✔
204
}
205

206
QList<quint32> Scene::components()
1✔
207
{
208
    QList<quint32> ids;
1✔
209

210
    QMap <SceneValue, uchar>::iterator it = m_values.begin();
1✔
211
    for (; it != m_values.end(); it++)
5✔
212
    {
213
        const SceneValue& scv = it.key();
4✔
214
        if (ids.contains(scv.fxi) == false)
4✔
215
            ids.append(scv.fxi);
3✔
216
    }
217

218
    return ids;
2✔
219
}
×
220

221
QColor Scene::colorValue(quint32 fxi)
3✔
222
{
223
    int rVal = 0, gVal = 0, bVal = 0;
3✔
224
    int cVal = -1, mVal = -1, yVal = -1;
3✔
225
    bool found = false;
3✔
226
    QColor CMYcol;
3✔
227

228
    QMap <SceneValue, uchar>::iterator it = m_values.begin();
3✔
229
    for (; it != m_values.end(); it++)
27✔
230
    {
231
        const SceneValue& scv = it.key();
24✔
232

233
        if (fxi != Fixture::invalidId() && fxi != scv.fxi)
24✔
234
            continue;
16✔
235

236
        Fixture *fixture = doc()->fixture(scv.fxi);
8✔
237
        if (fixture == NULL)
8✔
238
            continue;
×
239

240
        const QLCChannel* channel = fixture->channel(scv.channel);
8✔
241
        if (channel == NULL)
8✔
242
            continue;
×
243

244
        if (channel->group() == QLCChannel::Intensity)
8✔
245
        {
246
            QLCChannel::PrimaryColour col = channel->colour();
7✔
247
            switch (col)
7✔
248
            {
249
                case QLCChannel::Red: rVal = scv.value; found = true; break;
1✔
250
                case QLCChannel::Green: gVal = scv.value; found = true; break;
1✔
251
                case QLCChannel::Blue: bVal = scv.value; found = true; break;
1✔
252
                case QLCChannel::Cyan: cVal = scv.value; break;
1✔
253
                case QLCChannel::Magenta: mVal = scv.value; break;
1✔
254
                case QLCChannel::Yellow: yVal = scv.value; break;
1✔
255
                case QLCChannel::White: rVal = gVal = bVal = scv.value; found = true; break;
×
256
                default: break;
1✔
257
            }
258
        }
259
        else if (channel->group() == QLCChannel::Colour)
1✔
260
        {
261
            QLCCapability *cap = channel->searchCapability(scv.value);
1✔
262
            if (cap &&
2✔
263
                (cap->presetType() == QLCCapability::SingleColor ||
1✔
264
                 cap->presetType() == QLCCapability::DoubleColor))
×
265
            {
266
                QColor col = cap->resource(0).value<QColor>();
1✔
267
                rVal = col.red();
1✔
268
                gVal = col.green();
1✔
269
                bVal = col.blue();
1✔
270
                found = true;
1✔
271
            }
272
        }
273

274
        if (cVal >= 0 && mVal >= 0 && yVal >= 0)
8✔
275
        {
276
            CMYcol.setCmyk(cVal, mVal, yVal, 0);
1✔
277
            rVal = CMYcol.red();
1✔
278
            gVal = CMYcol.green();
1✔
279
            bVal = CMYcol.blue();
1✔
280
            found = true;
1✔
281
        }
282
    }
283

284
    if (found)
3✔
285
        return QColor(rVal, gVal, bVal);
3✔
286

287
    return QColor();
×
288
}
289

290
void Scene::clear()
1✔
291
{
292
    m_values.clear();
1✔
293
    m_fixtures.clear();
1✔
294
    m_fixtureGroups.clear();
1✔
295
    m_palettes.clear();
1✔
296
}
1✔
297

298
/*********************************************************************
299
 * Channel Groups
300
 *********************************************************************/
301

302
void Scene::addChannelGroup(quint32 id)
2✔
303
{
304
    if (m_channelGroups.contains(id) == false)
2✔
305
    {
306
        m_channelGroups.append(id);
1✔
307
        m_channelGroupsLevels.append(0);
1✔
308
    }
309
}
2✔
310

311
void Scene::removeChannelGroup(quint32 id)
2✔
312
{
313
    int idx = m_channelGroups.indexOf(id);
2✔
314
    if (idx != -1)
2✔
315
    {
316
        m_channelGroups.removeAt(idx);
1✔
317
        m_channelGroupsLevels.removeAt(idx);
1✔
318
    }
319
}
2✔
320

321
void Scene::setChannelGroupLevel(quint32 id, uchar level)
1✔
322
{
323
    int idx = m_channelGroups.indexOf(id);
1✔
324
    if (idx >= 0 && idx < m_channelGroupsLevels.count())
1✔
325
        m_channelGroupsLevels[idx] = level;
1✔
326
}
1✔
327

328
QList<uchar> Scene::channelGroupsLevels()
6✔
329
{
330
    return m_channelGroupsLevels;
6✔
331
}
332

333
QList<quint32> Scene::channelGroups()
6✔
334
{
335
    return m_channelGroups;
6✔
336
}
337

338
/*****************************************************************************
339
 * Fixtures
340
 *****************************************************************************/
341

342
void Scene::slotFixtureRemoved(quint32 fxi_id)
4✔
343
{
344
    bool hasChanged = false;
4✔
345

346
    QMutableMapIterator <SceneValue, uchar> it(m_values);
4✔
347
    while (it.hasNext() == true)
10✔
348
    {
349
        SceneValue value(it.next().key());
6✔
350
        if (value.fxi == fxi_id)
6✔
351
        {
352
            it.remove();
2✔
353
            hasChanged = true;
2✔
354
        }
355
    }
6✔
356

357
    if (removeFixture(fxi_id))
4✔
358
        hasChanged = true;
2✔
359

360
    if (hasChanged)
4✔
361
        emit changed(this->id());
2✔
362
}
4✔
363

364
void Scene::addFixture(quint32 fixtureId)
7✔
365
{
366
    if (m_fixtures.contains(fixtureId) == false)
7✔
367
        m_fixtures.append(fixtureId);
7✔
368
}
7✔
369

370
bool Scene::removeFixture(quint32 fixtureId)
4✔
371
{
372
    return m_fixtures.removeOne(fixtureId);
4✔
373
}
374

375
QList<quint32> Scene::fixtures() const
2✔
376
{
377
    return m_fixtures;
2✔
378
}
379

380
/*********************************************************************
381
 * Fixture Groups
382
 *********************************************************************/
383

384
void Scene::addFixtureGroup(quint32 id)
×
385
{
386
    if (m_fixtureGroups.contains(id) == false)
×
387
        m_fixtureGroups.append(id);
×
388
}
×
389

390
bool Scene::removeFixtureGroup(quint32 id)
×
391
{
392
    return m_fixtureGroups.removeOne(id);
×
393
}
394

395
QList<quint32> Scene::fixtureGroups() const
×
396
{
397
    return m_fixtureGroups;
×
398
}
399

400
/*********************************************************************
401
 * Palettes
402
 *********************************************************************/
403

404
void Scene::addPalette(quint32 id)
×
405
{
406
    if (m_palettes.contains(id) == false)
×
407
        m_palettes.append(id);
×
408
}
×
409

410
bool Scene::removePalette(quint32 id)
×
411
{
412
    return m_palettes.removeOne(id);
×
413
}
414

415
QList<quint32> Scene::palettes() const
122✔
416
{
417
    return m_palettes;
122✔
418
}
419

420
/*****************************************************************************
421
 * Load & Save
422
 *****************************************************************************/
423

424
bool Scene::saveXML(QXmlStreamWriter *doc)
2✔
425
{
426
    Q_ASSERT(doc != NULL);
2✔
427

428
    /* Function tag */
429
    doc->writeStartElement(KXMLQLCFunction);
4✔
430

431
    /* Common attributes */
432
    saveXMLCommon(doc);
2✔
433

434
    /* Tempo type */
435
    saveXMLTempoType(doc);
2✔
436

437
    /* Speed */
438
    saveXMLSpeed(doc);
2✔
439

440
    /* Channel groups */
441
    if (m_channelGroups.count() > 0)
2✔
442
    {
443
        QString chanGroupsIDs;
×
444
        for (int i = 0; i < m_channelGroups.size(); ++i)
×
445
        {
446
            if (chanGroupsIDs.isEmpty() == false)
×
447
                chanGroupsIDs.append(QString(","));
×
448
            int id = m_channelGroups.at(i);
×
449
            int val = m_channelGroupsLevels.at(i);
×
450
            chanGroupsIDs.append(QString("%1,%2").arg(id).arg(val));
×
451
        }
452
        doc->writeTextElement(KXMLQLCSceneChannelGroupsValues, chanGroupsIDs);
×
453
    }
×
454

455
    /* Scene contents */
456
    // make a copy of the Scene values cause we need to empty it in the process
457
    QList<SceneValue> values = m_values.keys();
2✔
458

459
    // loop through the Scene Fixtures in the order they've been added
460
    foreach (quint32 fxId, m_fixtures)
4✔
461
    {
462
        QStringList currFixValues;
2✔
463
        bool found = false;
2✔
464

465
        // look for the values that match the current Fixture ID
466
        for (int j = 0; j < values.count(); j++)
6✔
467
        {
468
            SceneValue scv = values.at(j);
5✔
469
            if (scv.fxi != fxId)
5✔
470
            {
471
                if (found == true)
1✔
472
                    break;
1✔
473
                else
474
                    continue;
×
475
            }
476

477
            found = true;
4✔
478
            currFixValues.append(QString::number(scv.channel));
4✔
479
            // IMPORTANT: if a Scene is hidden, so used as a container by some Sequences,
480
            // it must be saved with values set to zero
481
            currFixValues.append(QString::number(isVisible() ? scv.value : 0));
4✔
482
            values.removeAt(j);
4✔
483
            j--;
4✔
484
        }
5✔
485

486
        saveXMLFixtureValues(doc, fxId, currFixValues);
2✔
487
    }
4✔
488

489
    /* Save referenced Fixture Groups */
490
    foreach (quint32 groupId, m_fixtureGroups)
2✔
491
    {
492
        doc->writeStartElement(KXMLQLCFixtureGroup);
×
493
        doc->writeAttribute(KXMLQLCFixtureGroupID, QString::number(groupId));
×
494
        doc->writeEndElement();
×
495
    }
2✔
496

497
    /* Save referenced Palettes */
498
    foreach (quint32 pId, m_palettes)
2✔
499
    {
500
        doc->writeStartElement(KXMLQLCPalette);
×
501
        doc->writeAttribute(KXMLQLCPaletteID, QString::number(pId));
×
502
        doc->writeEndElement();
×
503
    }
2✔
504

505
    /* End the <Function> tag */
506
    doc->writeEndElement();
2✔
507

508
    return true;
2✔
509
}
2✔
510

511
bool Scene::saveXMLFixtureValues(QXmlStreamWriter* doc, quint32 fixtureID, QStringList const& values)
2✔
512
{
513
    doc->writeStartElement(KXMLQLCFixtureValues);
4✔
514
    doc->writeAttribute(KXMLQLCFixtureID, QString::number(fixtureID));
4✔
515
    if (values.size() > 0)
2✔
516
        doc->writeCharacters(values.join(","));
2✔
517
    doc->writeEndElement();
2✔
518
    return true;
2✔
519
}
520

521
bool Scene::loadXML(QXmlStreamReader &root)
4✔
522
{
523
    if (root.name() != KXMLQLCFunction)
4✔
524
    {
525
        qWarning() << Q_FUNC_INFO << "Function node not found";
1✔
526
        return false;
1✔
527
    }
528

529
    if (root.attributes().value(KXMLQLCFunctionType).toString() != typeToString(Function::SceneType))
6✔
530
    {
531
        qWarning() << Q_FUNC_INFO << "Function is not a scene";
1✔
532
        return false;
1✔
533
    }
534

535
    /* Load scene contents */
536
    while (root.readNextStartElement())
9✔
537
    {
538
        if (root.name() == KXMLQLCBus)
7✔
539
        {
540
            m_legacyFadeBus = root.readElementText().toUInt();
2✔
541
        }
542
        else if (root.name() == KXMLQLCFunctionSpeed)
5✔
543
        {
544
            loadXMLSpeed(root);
1✔
545
        }
546
        else if (root.name() == KXMLQLCFunctionTempoType)
4✔
547
        {
548
            loadXMLTempoType(root);
×
549
        }
550
        else if (root.name() == KXMLQLCSceneChannelGroups)
4✔
551
        {
552
            QString chGrpIDs = root.readElementText();
×
553
            if (chGrpIDs.isEmpty() == false)
×
554
            {
555
                QStringList grpArray = chGrpIDs.split(",");
×
556
                foreach (QString grp, grpArray)
×
557
                {
558
                    m_channelGroups.append(grp.toUInt());
×
559
                    m_channelGroupsLevels.append(0);
×
560
                }
×
561
            }
×
562
        }
×
563
        else if (root.name() == KXMLQLCSceneChannelGroupsValues)
4✔
564
        {
565
            QString chGrpIDs = root.readElementText();
×
566
            if (chGrpIDs.isEmpty() == false)
×
567
            {
568
                QStringList grpArray = chGrpIDs.split(",");
×
569
                for (int i = 0; i + 1 < grpArray.count(); i+=2)
×
570
                {
571
                    m_channelGroups.append(grpArray.at(i).toUInt());
×
572
                    m_channelGroupsLevels.append(grpArray.at(i + 1).toUInt());
×
573
                }
574
            }
×
575
        }
×
576
        /* "old" style XML */
577
        else if (root.name() == KXMLQLCFunctionValue)
4✔
578
        {
579
            /* Channel value */
580
            SceneValue scv;
3✔
581
            if (scv.loadXML(root) == true)
3✔
582
                setValue(scv);
3✔
583
        }
3✔
584
        /* "new" style XML */
585
        else if (root.name() == KXMLQLCFixtureValues)
1✔
586
        {
587
            quint32 fxi = root.attributes().value(KXMLQLCFixtureID).toString().toUInt();
×
588
            addFixture(fxi);
×
589
            QString strvals = root.readElementText();
×
590
            if (strvals.isEmpty() == false)
×
591
            {
592
                QStringList varray = strvals.split(",");
×
593
                for (int i = 0; i + 1 < varray.count(); i+=2)
×
594
                {
595
                    SceneValue scv;
×
596
                    scv.fxi = fxi;
×
597
                    scv.channel = QString(varray.at(i)).toUInt();
×
598
                    scv.value = uchar(QString(varray.at(i + 1)).toInt());
×
599
                    setValue(scv);
×
600
                }
×
601
            }
×
602
        }
×
603
        else if (root.name() == KXMLQLCFixtureGroup)
1✔
604
        {
605
            quint32 id = root.attributes().value(KXMLQLCFixtureGroupID).toString().toUInt();
×
606
            addFixtureGroup(id);
×
607
            root.skipCurrentElement();
×
608
        }
609
        else if (root.name() == KXMLQLCPalette)
1✔
610
        {
611
            quint32 id = root.attributes().value(KXMLQLCPaletteID).toString().toUInt();
×
612
            addPalette(id);
×
613
            root.skipCurrentElement();
×
614
        }
615
        else
616
        {
617
            qWarning() << Q_FUNC_INFO << "Unknown scene tag:" << root.name();
1✔
618
            root.skipCurrentElement();
1✔
619
        }
620
    }
621

622
    return true;
2✔
623
}
624

625
void Scene::postLoad()
×
626
{
627
    // Map legacy bus speed to fixed speed values
628
    if (m_legacyFadeBus != Bus::invalid())
×
629
    {
630
        quint32 value = Bus::instance()->value(m_legacyFadeBus);
×
631
        setFadeInSpeed((value / MasterTimer::frequency()) * 1000);
×
632
        setFadeOutSpeed((value / MasterTimer::frequency()) * 1000);
×
633
    }
634

635
    // Remove such fixtures and channels that don't exist
636
    QMutableMapIterator <SceneValue, uchar> it(m_values);
×
637
    while (it.hasNext() == true)
×
638
    {
639
        SceneValue value(it.next().key());
×
640
        Fixture* fxi = doc()->fixture(value.fxi);
×
641
        if (fxi == NULL || fxi->channel(value.channel) == NULL)
×
642
            it.remove();
×
643
    }
×
644
}
×
645

646
/****************************************************************************
647
 * Flashing
648
 ****************************************************************************/
649

650
void Scene::flash(MasterTimer *timer, bool shouldOverride, bool forceLTP)
6✔
651
{
652
    if (flashing() == true)
6✔
653
        return;
1✔
654

655
    m_flashOverrides = shouldOverride;
5✔
656
    m_flashForceLTP = forceLTP;
5✔
657

658
    Q_ASSERT(timer != NULL);
5✔
659
    Function::flash(timer, shouldOverride, forceLTP);
5✔
660
    timer->registerDMXSource(this);
5✔
661
}
662

663
void Scene::unFlash(MasterTimer *timer)
5✔
664
{
665
    if (flashing() == false)
5✔
666
        return;
×
667

668
    Q_ASSERT(timer != NULL);
5✔
669
    Function::unFlash(timer);
5✔
670
}
671

672
void Scene::writeDMX(MasterTimer *timer, QList<Universe *> ua)
5✔
673
{
674
    Q_ASSERT(timer != NULL);
5✔
675

676
    if (flashing() == true)
5✔
677
    {
678
        if (m_fadersMap.isEmpty())
2✔
679
        {
680
            // Keep HTP and LTP channels up. Flash is more or less a forceful intervention
681
            // so enforce all values that the user has chosen to flash.
682
            QMap <SceneValue, uchar>::iterator it = m_values.begin();
1✔
683
            for (; it != m_values.end(); it++)
4✔
684
            {
685
                const SceneValue& sv = it.key();
3✔
686

687
                FadeChannel fc(doc(), sv.fxi, sv.channel);
3✔
688
                quint32 universe = fc.universe();
3✔
689
                if (universe == Universe::invalid())
3✔
690
                    continue;
×
691

692
                QSharedPointer<GenericFader> fader = m_fadersMap.value(universe, QSharedPointer<GenericFader>());
3✔
693
                if (fader.isNull())
3✔
694
                {
695
                    fader = ua[universe]->requestFader(m_flashOverrides ? Universe::Flashing : Universe::Auto);
1✔
696

697
                    fader->adjustIntensity(getAttributeValue(Intensity));
1✔
698
                    fader->setBlendMode(blendMode());
1✔
699
                    fader->setName(name());
1✔
700
                    fader->setParentFunctionID(id());
1✔
701
                    m_fadersMap[universe] = fader;
1✔
702
                }
703

704
                if (m_flashForceLTP)
3✔
705
                    fc.addFlag(FadeChannel::ForceLTP);
×
706
                fc.setTarget(sv.value);
3✔
707
                fc.addFlag(FadeChannel::Flashing);
3✔
708
                fader->add(fc);
3✔
709
            }
3✔
710
        }
711
    }
712
    else
713
    {
714
        handleFadersEnd(timer);
3✔
715
        timer->unregisterDMXSource(this);
3✔
716
    }
717
}
5✔
718

719
/****************************************************************************
720
 * Running
721
 ****************************************************************************/
722

723
void Scene::processValue(MasterTimer *timer, QList<Universe*> ua, uint fadeIn, SceneValue &scv)
515✔
724
{
725
    Fixture *fixture = doc()->fixture(scv.fxi);
515✔
726

727
    if (fixture == NULL)
515✔
728
        return;
×
729

730
    int universeIndex = floor((fixture->universeAddress() + scv.channel) / 512);
515✔
731
    if (universeIndex >= ua.count())
515✔
732
        return;
×
733

734
    Universe *universe = ua.at(universeIndex);
515✔
735

736
    QSharedPointer<GenericFader> fader = m_fadersMap.value(universe->id(), QSharedPointer<GenericFader>());
515✔
737
    if (fader.isNull())
515✔
738
    {
739
        fader = universe->requestFader();
122✔
740
        fader->adjustIntensity(getAttributeValue(Intensity));
122✔
741
        fader->setBlendMode(blendMode());
122✔
742
        fader->setName(name());
122✔
743
        fader->setParentFunctionID(id());
122✔
744
        fader->setParentIntensity(getAttributeValue(ParentIntensity));
122✔
745
        fader->setHandleSecondary(true);
122✔
746
        m_fadersMap[universe->id()] = fader;
122✔
747
    }
748

749
    FadeChannel *fc = fader->getChannelFader(doc(), universe, scv.fxi, scv.channel);
515✔
750
    int chIndex = fc->channelIndex(scv.channel);
515✔
751

752
    /** If a blend Function has been set, check if this channel needs to
753
     *  be blended from a previous value. If so, mark it for crossfade
754
     *  and set its current value */
755
    if (blendFunctionID() != Function::invalidId())
515✔
756
    {
757
        Scene *blendScene = qobject_cast<Scene *>(doc()->function(blendFunctionID()));
407✔
758
        if (blendScene != NULL && blendScene->checkValue(scv))
407✔
759
        {
760
            fc->addFlag(FadeChannel::CrossFade);
407✔
761
            fc->setCurrent(blendScene->value(scv.fxi, scv.channel), chIndex);
407✔
762
            qDebug() << "----- BLEND from Scene" << blendScene->name()
814✔
763
                     << ", fixture:" << scv.fxi << ", channel:" << scv.channel << ", value:" << fc->current();
407✔
764
        }
765
    }
766
    else
767
    {
768
        qDebug() << "Scene" << name() << "add channel" << scv.channel << "from" << fc->current(chIndex) << "to" << scv.value;
108✔
769
    }
770

771
    fc->setStart(fc->current(chIndex), chIndex);
515✔
772
    fc->setTarget(scv.value, chIndex);
515✔
773

774
    if (fc->canFade() == false)
515✔
775
    {
776
        fc->setFadeTime(0);
×
777
    }
778
    else
779
    {
780
        if (tempoType() == Beats)
515✔
781
        {
782
            int fadeInTime = beatsToTime(fadeIn, timer->beatTimeDuration());
×
783
            int beatOffset = timer->nextBeatTimeOffset();
×
784

785
            if (fadeInTime - beatOffset > 0)
×
786
                fc->setFadeTime(fadeInTime - beatOffset);
×
787
            else
788
                fc->setFadeTime(fadeInTime);
×
789
        }
790
        else
791
        {
792
            fc->setFadeTime(fadeIn);
515✔
793
        }
794
    }
795
}
515✔
796

797
void Scene::handleFadersEnd(MasterTimer *timer)
111✔
798
{
799
    uint fadeout = overrideFadeOutSpeed() == defaultSpeed() ? fadeOutSpeed() : overrideFadeOutSpeed();
111✔
800

801
    /* If no fade out is needed, dismiss all the requested faders.
802
     * Otherwise, set all the faders to fade out and let Universe dismiss them
803
     * when done */
804
    if (fadeout == 0)
111✔
805
    {
806
        dismissAllFaders();
106✔
807
    }
808
    else
809
    {
810
        if (tempoType() == Beats)
5✔
811
            fadeout = beatsToTime(fadeout, timer->beatTimeDuration());
×
812

813
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
9✔
814
        {
815
            if (!fader.isNull())
4✔
816
                fader->setFadeOut(true, fadeout);
4✔
817
        }
9✔
818
    }
819

820
    m_fadersMap.clear();
111✔
821

822
    // autonomously reset a blend function if set
823
    setBlendFunctionID(Function::invalidId());
111✔
824
}
111✔
825

826
void Scene::write(MasterTimer *timer, QList<Universe*> ua)
402✔
827
{
828
    //qDebug() << Q_FUNC_INFO << elapsed();
829

830
    if (m_values.count() == 0 && m_palettes.count() == 0)
402✔
831
    {
832
        stop(FunctionParent::master());
1✔
833
        return;
1✔
834
    }
835

836
    if (m_fadersMap.isEmpty())
401✔
837
    {
838
        uint fadeIn = overrideFadeInSpeed() == defaultSpeed() ? fadeInSpeed() : overrideFadeInSpeed();
122✔
839

840
        foreach (quint32 paletteID, palettes())
244✔
841
        {
842
            QLCPalette *palette = doc()->palette(paletteID);
×
843
            if (palette == NULL)
×
844
                continue;
×
845

846
            foreach (SceneValue scv, palette->valuesFromFixtureGroups(doc(), fixtureGroups()))
×
847
                processValue(timer, ua, fadeIn, scv);
×
848

849
            foreach (SceneValue scv, palette->valuesFromFixtures(doc(), fixtures()))
×
850
                processValue(timer, ua, fadeIn, scv);
×
851
        }
122✔
852

853
        QMutexLocker locker(&m_valueListMutex);
122✔
854
        QMapIterator <SceneValue, uchar> it(m_values);
122✔
855
        while (it.hasNext() == true)
637✔
856
        {
857
            SceneValue scv(it.next().key());
515✔
858
            processValue(timer, ua, fadeIn, scv);
515✔
859
        }
515✔
860
    }
122✔
861

862
    if (isPaused() == false)
401✔
863
    {
864
        incrementElapsed();
398✔
865
        if (timer->isBeat() && tempoType() == Beats)
398✔
866
            incrementElapsedBeats();
×
867
    }
868
}
869

870
void Scene::postRun(MasterTimer* timer, QList<Universe *> ua)
108✔
871
{
872
    handleFadersEnd(timer);
108✔
873

874
    Function::postRun(timer, ua);
108✔
875
}
108✔
876

877
void Scene::setPause(bool enable)
6✔
878
{
879
    if (!isRunning())
6✔
880
        return;
×
881

882
    foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
8✔
883
    {
884
        if (!fader.isNull())
2✔
885
            fader->setPaused(enable);
2✔
886
    }
8✔
887
    Function::setPause(enable);
6✔
888
}
889

890
/****************************************************************************
891
 * Intensity
892
 ****************************************************************************/
893

894
int Scene::adjustAttribute(qreal fraction, int attributeId)
248✔
895
{
896
    int attrIndex = Function::adjustAttribute(fraction, attributeId);
248✔
897

898
    if (attrIndex == Intensity)
248✔
899
    {
900
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
134✔
901
        {
902
            if (!fader.isNull())
3✔
903
                fader->adjustIntensity(getAttributeValue(Function::Intensity));
3✔
904
        }
134✔
905
    }
906
    else if (attrIndex == ParentIntensity)
117✔
907
    {
908
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
120✔
909
        {
910
            if (!fader.isNull())
3✔
911
                fader->setParentIntensity(getAttributeValue(ParentIntensity));
3✔
912
        }
120✔
913
    }
914

915
    return attrIndex;
248✔
916
}
917

918
/*************************************************************************
919
 * Blend mode
920
 *************************************************************************/
921

922
void Scene::setBlendMode(Universe::BlendMode mode)
3✔
923
{
924
    if (mode == blendMode())
3✔
925
        return;
1✔
926

927
    qDebug() << "Scene" << name() << "blend mode set to" << Universe::blendModeToString(mode);
2✔
928

929
    foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
2✔
930
    {
931
        if (!fader.isNull())
×
932
            fader->setBlendMode(mode);
×
933
    }
2✔
934

935
    Function::setBlendMode(mode);
2✔
936
}
937

938
quint32 Scene::blendFunctionID() const
922✔
939
{
940
    return m_blendFunctionID;
922✔
941
}
942

943
void Scene::setBlendFunctionID(quint32 fid)
204✔
944
{
945
    m_blendFunctionID = fid;
204✔
946
    if (isRunning() && fid == Function::invalidId())
204✔
947
    {
948
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
109✔
949
        {
950
            if (!fader.isNull())
×
951
                fader->resetCrossfade();
×
952
        }
109✔
953
    }
954
}
204✔
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