• 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

95.51
/engine/src/cuestack.cpp
1
/*
2
  Q Light Controller Plus
3
  cuestack.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 <qmath.h>
24
#include <QDebug>
25

26
#include "genericfader.h"
27
#include "fadechannel.h"
28
#include "mastertimer.h"
29
#include "qlcmacros.h"
30
#include "cuestack.h"
31
#include "universe.h"
32
#include "cue.h"
33
#include "doc.h"
34

35
/****************************************************************************
36
 * Initialization
37
 ****************************************************************************/
38

39
CueStack::CueStack(Doc* doc)
24✔
40
    : QObject(doc)
41
    , m_fadeInSpeed(0)
24✔
42
    , m_fadeOutSpeed(0)
24✔
43
    , m_duration(UINT_MAX)
24✔
44
    , m_running(false)
24✔
45
    , m_intensity(1.0)
24✔
46
    , m_currentIndex(-1)
24✔
47
    , m_flashing(false)
24✔
48
    , m_elapsed(0)
24✔
49
    , m_previous(false)
24✔
50
    , m_next(false)
24✔
51
{
52
    //qDebug() << Q_FUNC_INFO << (void*) this;
53
    Q_ASSERT(doc != NULL);
24✔
54
}
24✔
55

56
CueStack::~CueStack()
27✔
57
{
58
    //qDebug() << Q_FUNC_INFO << (void*) this;
59
    Q_ASSERT(isStarted() == false);
24✔
60
    Q_ASSERT(isFlashing() == false);
24✔
61
    m_cues.clear(); // Crashes without this, WTF?!
24✔
62
}
27✔
63

64
Doc* CueStack::doc() const
31✔
65
{
66
    return qobject_cast<Doc*> (parent());
31✔
67
}
68

69
/****************************************************************************
70
 * Name
71
 ****************************************************************************/
72

73
void CueStack::setName(const QString& name, int index)
1✔
74
{
75
    if (index < 0)
1✔
76
        m_name = name;
1✔
77
    else
78
        m_cues[index].setName(name);
×
79
    emit changed(index);
1✔
80
}
1✔
81

82
QString CueStack::name(int index) const
2✔
83
{
84
    if (index < 0)
2✔
85
        return m_name;
2✔
86
    else
87
        return m_cues[index].name();
×
88
}
89

90
/****************************************************************************
91
 * Speed
92
 ****************************************************************************/
93

94
void CueStack::setFadeInSpeed(uint ms, int index)
6✔
95
{
96
    if (index < 0)
6✔
97
        m_fadeInSpeed = ms;
6✔
98
    else
99
        m_cues[index].setFadeInSpeed(ms);
×
100
    emit changed(index);
6✔
101
}
6✔
102

103
uint CueStack::fadeInSpeed(int index) const
4✔
104
{
105
    if (index < 0)
4✔
106
        return m_fadeInSpeed;
4✔
107
    else
108
        return m_cues[index].fadeInSpeed();
×
109
}
110

111
void CueStack::setFadeOutSpeed(uint ms, int index)
6✔
112
{
113
    if (index < 0)
6✔
114
        m_fadeOutSpeed = ms;
6✔
115
    else
116
        m_cues[index].setFadeOutSpeed(ms);
×
117
    emit changed(index);
6✔
118
}
6✔
119

120
uint CueStack::fadeOutSpeed(int index) const
11✔
121
{
122
    if (index < 0)
11✔
123
        return m_fadeOutSpeed;
11✔
124
    else
125
        return m_cues[index].fadeOutSpeed();
×
126
}
127

128
void CueStack::setDuration(uint ms, int index)
6✔
129
{
130
    if (index < 0)
6✔
131
        m_duration = ms;
6✔
132
    else
133
        m_cues[index].setDuration(ms);
×
134
    emit changed(index);
6✔
135
}
6✔
136

137
uint CueStack::duration(int index) const
4✔
138
{
139
    if (index < 0)
4✔
140
        return m_duration;
4✔
141
    else
142
        return m_cues[index].duration();
×
143
}
144

145
/****************************************************************************
146
 * Cues
147
 ****************************************************************************/
148

149
void CueStack::appendCue(const Cue& cue)
48✔
150
{
151
    qDebug() << Q_FUNC_INFO;
48✔
152

153
    int index = 0;
48✔
154
    {
155
        QMutexLocker locker(&m_mutex);
48✔
156
        m_cues.append(cue);
48✔
157
        index = m_cues.size() - 1;
48✔
158
    }
48✔
159

160
    emit added(index);
48✔
161
}
48✔
162

163
void CueStack::insertCue(int index, const Cue& cue)
6✔
164
{
165
    qDebug() << Q_FUNC_INFO;
6✔
166

167
    bool cueAdded = false;
6✔
168

169
    {
170
        QMutexLocker locker(&m_mutex);
6✔
171

172
        if (index >= 0 && index < m_cues.size())
6✔
173
        {
174
            m_cues.insert(index, cue);
2✔
175
            cueAdded = true;
2✔
176
            emit added(index);
2✔
177

178
            if (m_currentIndex >= index)
2✔
179
            {
180
                m_currentIndex++;
1✔
181
                emit currentCueChanged(m_currentIndex);
1✔
182
            }
183
        }
184
    }
6✔
185

186
    if (!cueAdded)
6✔
187
        appendCue(cue);
4✔
188
}
6✔
189

190
void CueStack::replaceCue(int index, const Cue& cue)
4✔
191
{
192
    qDebug() << Q_FUNC_INFO;
4✔
193

194
    bool cueChanged = false;
4✔
195
    {
196
        QMutexLocker locker(&m_mutex);
4✔
197

198
        if (index >= 0 && index < m_cues.size())
4✔
199
        {
200
           m_cues[index] = cue;
2✔
201
           cueChanged = true;
2✔
202
        }
203
    }
4✔
204

205
    if (cueChanged)
4✔
206
    {
207
        emit changed(index);
2✔
208
    }
209
    else
210
    {
211
        appendCue(cue);
2✔
212
    }
213
}
4✔
214

215
void CueStack::removeCue(int index)
9✔
216
{
217
    qDebug() << Q_FUNC_INFO;
9✔
218

219
    QMutexLocker locker(&m_mutex);
9✔
220
    if (index >= 0 && index < m_cues.size())
9✔
221
    {
222
        m_cues.removeAt(index);
5✔
223
        emit removed(index);
5✔
224

225
        if (index < m_currentIndex)
5✔
226
        {
227
            m_currentIndex--;
2✔
228
            emit currentCueChanged(m_currentIndex);
2✔
229
        }
230
    }
231
}
9✔
232

233
void CueStack::removeCues(const QList <int>& indexes)
6✔
234
{
235
    qDebug() << Q_FUNC_INFO;
6✔
236

237
    // Sort the list so that the items can be removed in reverse order.
238
    // This way, the indices are always correct.
239
    QList <int> indexList = indexes;
6✔
240
    std::sort(indexList.begin(), indexList.end());
6✔
241

242
    QListIterator <int> it(indexList);
6✔
243
    it.toBack();
6✔
244

245
    QMutexLocker locker(&m_mutex);
6✔
246

247
    while (it.hasPrevious() == true)
15✔
248
    {
249
        int index(it.previous());
9✔
250
        if (index >= 0 && index < m_cues.size())
9✔
251
        {
252
            m_cues.removeAt(index);
5✔
253
            emit removed(index);
5✔
254

255
            if (index < m_currentIndex)
5✔
256
            {
257
                m_currentIndex--;
4✔
258
                emit currentCueChanged(m_currentIndex);
4✔
259
            }
260
        }
261
    }
262
}
6✔
263

264
QList <Cue> CueStack::cues() const
121✔
265
{
266
    return m_cues;
121✔
267
}
268

269
void CueStack::setCurrentIndex(int index)
14✔
270
{
271
    qDebug() << Q_FUNC_INFO;
14✔
272

273
    QMutexLocker locker(&m_mutex);
14✔
274
    m_currentIndex = CLAMP(index, -1, m_cues.size() - 1);
14✔
275
}
14✔
276

277
int CueStack::currentIndex() const
37✔
278
{
279
    return m_currentIndex;
37✔
280
}
281

282
void CueStack::previousCue()
3✔
283
{
284
    qDebug() << Q_FUNC_INFO;
3✔
285
    m_previous = true;
3✔
286
    if (isRunning() == false)
3✔
287
        start();
1✔
288
}
3✔
289

290
void CueStack::nextCue()
3✔
291
{
292
    qDebug() << Q_FUNC_INFO;
3✔
293
    m_next = true;
3✔
294
    if (isRunning() == false)
3✔
295
        start();
1✔
296
}
3✔
297

298
/****************************************************************************
299
 * Save & Load
300
 ****************************************************************************/
301

302
uint CueStack::loadXMLID(QXmlStreamReader &root)
4✔
303
{
304
    qDebug() << Q_FUNC_INFO;
4✔
305

306
    if (root.name() != KXMLQLCCueStack)
4✔
307
    {
308
        qWarning() << Q_FUNC_INFO << "CueStack node not found";
×
309
        return UINT_MAX;
×
310
    }
311

312
    QXmlStreamAttributes attrs = root.attributes();
4✔
313

314
    if (attrs.hasAttribute(KXMLQLCCueStackID) == true)
4✔
315
        return attrs.value(KXMLQLCCueStackID).toString().toUInt();
3✔
316
    else
317
        return UINT_MAX;
1✔
318
}
4✔
319

320
bool CueStack::loadXML(QXmlStreamReader &root)
4✔
321
{
322
    qDebug() << Q_FUNC_INFO;
4✔
323

324
    m_cues.clear();
4✔
325

326
    if (root.name() != KXMLQLCCueStack)
4✔
327
    {
328
        qWarning() << Q_FUNC_INFO << "CueStack node not found";
×
329
        return false;
×
330
    }
331

332
    while (root.readNextStartElement())
11✔
333
    {
334
        if (root.name() == KXMLQLCCue)
7✔
335
        {
336
            Cue cue;
4✔
337
            if (cue.loadXML(root) == true)
4✔
338
                appendCue(cue);
4✔
339
        }
4✔
340
        else if (root.name() == KXMLQLCCueStackSpeed)
3✔
341
        {
342
            setFadeInSpeed(root.attributes().value(KXMLQLCCueStackSpeedFadeIn).toString().toUInt());
4✔
343
            setFadeOutSpeed(root.attributes().value(KXMLQLCCueStackSpeedFadeOut).toString().toUInt());
4✔
344
            setDuration(root.attributes().value(KXMLQLCCueStackSpeedDuration).toString().toUInt());
4✔
345
            root.skipCurrentElement();
2✔
346
        }
347
        else
348
        {
349
            qWarning() << Q_FUNC_INFO << "Unrecognized CueStack tag:" << root.name();
1✔
350
            root.skipCurrentElement();
1✔
351
        }
352
    }
353

354
    return true;
4✔
355
}
356

357
bool CueStack::saveXML(QXmlStreamWriter *doc, uint id) const
2✔
358
{
359
    qDebug() << Q_FUNC_INFO;
2✔
360
    Q_ASSERT(doc != NULL);
2✔
361

362
    doc->writeStartElement(KXMLQLCCueStack);
4✔
363
    doc->writeAttribute(KXMLQLCCueStackID, QString::number(id));
4✔
364

365
    doc->writeStartElement(KXMLQLCCueStackSpeed);
4✔
366
    doc->writeAttribute(KXMLQLCCueStackSpeedFadeIn, QString::number(fadeInSpeed()));
4✔
367
    doc->writeAttribute(KXMLQLCCueStackSpeedFadeOut, QString::number(fadeOutSpeed()));
4✔
368
    doc->writeAttribute(KXMLQLCCueStackSpeedDuration, QString::number(duration()));
4✔
369
    doc->writeEndElement();
2✔
370

371
    foreach (Cue cue, cues())
8✔
372
        cue.saveXML(doc);
6✔
373

374
    /* End the <CueStack> tag */
375
    doc->writeEndElement();
2✔
376

377
    return true;
2✔
378
}
379

380
/****************************************************************************
381
 * Running
382
 ****************************************************************************/
383

384
void CueStack::start()
5✔
385
{
386
    qDebug() << Q_FUNC_INFO;
5✔
387
    m_running = true;
5✔
388
}
5✔
389

390
void CueStack::stop()
4✔
391
{
392
    qDebug() << Q_FUNC_INFO;
4✔
393
    m_running = false;
4✔
394
}
4✔
395

396
bool CueStack::isRunning() const
19✔
397
{
398
    return m_running;
19✔
399
}
400

401
void CueStack::adjustIntensity(qreal fraction)
1✔
402
{
403
    m_intensity = fraction;
1✔
404

405
    foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
1✔
406
    {
407
        if (!fader.isNull())
×
408
            fader->adjustIntensity(fraction);
×
409
    }
1✔
410
}
1✔
411

412
qreal CueStack::intensity() const
6✔
413
{
414
    return m_intensity;
6✔
415
}
416

417
/****************************************************************************
418
 * Flashing
419
 ****************************************************************************/
420

421
void CueStack::setFlashing(bool enable)
5✔
422
{
423
    qDebug() << Q_FUNC_INFO;
5✔
424
    if (m_flashing == enable || m_cues.isEmpty())
5✔
425
        return;
3✔
426

427
    m_flashing = enable;
2✔
428
    if (m_flashing == true)
2✔
429
        doc()->masterTimer()->registerDMXSource(this);
1✔
430
}
431

432
bool CueStack::isFlashing() const
33✔
433
{
434
    return m_flashing;
33✔
435
}
436

437
void CueStack::writeDMX(MasterTimer *timer, QList<Universe*> ua)
2✔
438
{
439
    Q_UNUSED(timer);
440
    if (m_cues.isEmpty())
2✔
441
        return;
×
442

443
    if (isFlashing())
2✔
444
    {
445
        if (m_fadersMap.isEmpty())
1✔
446
        {
447
            QMapIterator <uint,uchar> it(m_cues.first().values());
1✔
448
            while (it.hasNext() == true)
3✔
449
            {
450
                it.next();
2✔
451
                FadeChannel fc(doc(), Fixture::invalidId(), it.key());
2✔
452
                quint32 universe = fc.universe();
2✔
453
                if (universe == Universe::invalid())
2✔
454
                    continue;
×
455

456
                QSharedPointer<GenericFader> fader = m_fadersMap.value(universe, QSharedPointer<GenericFader>());
2✔
457
                if (fader.isNull())
2✔
458
                {
459
                    fader = ua[universe]->requestFader();
1✔
460
                    m_fadersMap[universe] = fader;
1✔
461
                }
462

463
                fc.setTarget(it.value());
2✔
464
                fc.addFlag(FadeChannel::Flashing);
2✔
465
                fader->add(fc);
2✔
466
            }
2✔
467
        }
1✔
468
    }
469
    else
470
    {
471
        QMapIterator <quint32, QSharedPointer<GenericFader> > it(m_fadersMap);
1✔
472
        while (it.hasNext() == true)
2✔
473
        {
474
            it.next();
1✔
475
            quint32 universe = it.key();
1✔
476
            QSharedPointer<GenericFader> fader = it.value();
1✔
477
            if (!fader.isNull())
1✔
478
                ua[universe]->dismissFader(fader);
1✔
479
        }
1✔
480

481
        m_fadersMap.clear();
1✔
482
        doc()->masterTimer()->unregisterDMXSource(this);
1✔
483
    }
1✔
484
}
485

486
/****************************************************************************
487
 * Writing
488
 ****************************************************************************/
489

490
bool CueStack::isStarted() const
33✔
491
{
492
    return m_fadersMap.isEmpty() ? false : true;
33✔
493
}
494

495
void CueStack::preRun()
5✔
496
{
497
    qDebug() << Q_FUNC_INFO;
5✔
498

499
    m_elapsed = 0;
5✔
500
    emit started();
5✔
501
}
5✔
502

503
void CueStack::write(QList<Universe*> ua)
4✔
504
{
505
    if (m_cues.size() == 0 || isRunning() == false)
4✔
506
        return;
1✔
507

508
    if (m_previous == true)
3✔
509
    {
510
        // previousCue() was requested by user
511
        m_elapsed = 0;
1✔
512
        int from = m_currentIndex;
1✔
513
        int to = previous();
1✔
514
        switchCue(from, to, ua);
1✔
515
        m_previous = false;
1✔
516
        emit currentCueChanged(from);
1✔
517
        emit currentCueChanged(to);
1✔
518
    }
519
    else if (m_next == true)
2✔
520
    {
521
        // nextCue() was requested by user
522
        m_elapsed = 0;
1✔
523
        int from = m_currentIndex;
1✔
524
        int to = next();
1✔
525
        switchCue(from, to, ua);
1✔
526
        m_next = false;
1✔
527
        emit currentCueChanged(from);
1✔
528
        emit currentCueChanged(to);
1✔
529
    }
530
/*
531
    else if (m_elapsed >= duration())
532
    {
533
        // Duration expired
534
        m_elapsed = 0;
535
        switchCue(next(), ua);
536
        emit currentCueChanged(m_currentIndex);
537
    }
538
*/
539
    //m_fader->write(ua);
540

541
    m_elapsed += MasterTimer::tick();
3✔
542
}
543

544
void CueStack::postRun(MasterTimer* timer, QList<Universe *> ua)
5✔
545
{
546
    qDebug() << Q_FUNC_INFO;
5✔
547

548
    Q_UNUSED(timer);
549

550
    /* If no fade out is needed, dismiss all the requested faders.
551
     * Otherwise, set all the faders to fade out and let Universe dismiss them
552
     * when done */
553
    if (fadeOutSpeed() == 0)
5✔
554
    {
555
        QMapIterator <quint32, QSharedPointer<GenericFader> > it(m_fadersMap);
3✔
556
        while (it.hasNext() == true)
4✔
557
        {
558
            it.next();
1✔
559
            quint32 universe = it.key();
1✔
560
            QSharedPointer<GenericFader> fader = it.value();
1✔
561
            if (!fader.isNull())
1✔
562
                ua[universe]->dismissFader(fader);
1✔
563
        }
1✔
564
    }
3✔
565
    else
566
    {
567
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
4✔
568
        {
569
            if (!fader.isNull())
2✔
570
                fader->setFadeOut(true, fadeOutSpeed());
2✔
571
        }
4✔
572
    }
573

574
    m_fadersMap.clear();
5✔
575

576
    m_currentIndex = -1;
5✔
577

578
    emit currentCueChanged(m_currentIndex);
5✔
579
    emit stopped();
5✔
580
}
5✔
581

582
int CueStack::previous()
9✔
583
{
584
    qDebug() << Q_FUNC_INFO;
9✔
585

586
    if (m_cues.size() == 0)
9✔
587
        return -1;
1✔
588

589
    QMutexLocker locker(&m_mutex);
8✔
590

591
    m_currentIndex--;
8✔
592
    if (m_currentIndex < 0)
8✔
593
        m_currentIndex = m_cues.size() - 1;
5✔
594

595
    return m_currentIndex;
8✔
596
}
8✔
597

598
FadeChannel *CueStack::getFader(QList<Universe *> universes, quint32 universeID, quint32 fixtureID, quint32 channel)
26✔
599
{
600
    // get the universe Fader first. If doesn't exist, create it
601
    QSharedPointer<GenericFader> fader = m_fadersMap.value(universeID, QSharedPointer<GenericFader>());
26✔
602
    if (fader.isNull())
26✔
603
    {
604
        fader = universes[universeID]->requestFader();
3✔
605
        fader->adjustIntensity(intensity());
3✔
606
        m_fadersMap[universeID] = fader;
3✔
607
    }
608

609
    return fader->getChannelFader(doc(), universes[universeID], fixtureID, channel);
52✔
610
}
26✔
611

612
void CueStack::updateFaderValues(FadeChannel *fc, uchar value, uint fadeTime)
23✔
613
{
614
    fc->setStart(fc->current());
23✔
615
    fc->setTarget(value);
23✔
616
    fc->setElapsed(0);
23✔
617
    fc->setReady(false);
23✔
618
    fc->setFadeTime(fadeTime);
23✔
619
}
23✔
620

621
int CueStack::next()
9✔
622
{
623
    qDebug() << Q_FUNC_INFO;
9✔
624

625
    if (m_cues.size() == 0)
9✔
626
        return -1;
1✔
627

628
    QMutexLocker locker(&m_mutex);
8✔
629
    m_currentIndex++;
8✔
630
    if (m_currentIndex >= m_cues.size())
8✔
631
        m_currentIndex = 0;
4✔
632

633
    return m_currentIndex;
8✔
634
}
8✔
635

636
void CueStack::switchCue(int from, int to, const QList<Universe *> ua)
8✔
637
{
638
    qDebug() << Q_FUNC_INFO;
8✔
639

640
    Cue newCue;
8✔
641
    Cue oldCue;
8✔
642

643
    {
644
        QMutexLocker locker(&m_mutex);
8✔
645

646
        if (to >= 0 && to < m_cues.size())
8✔
647
            newCue = m_cues[to];
5✔
648
        if (from >= 0 && from < m_cues.size())
8✔
649
            oldCue = m_cues[from];
3✔
650
    }
8✔
651

652
    // Fade out the HTP channels of the previous cue
653
    QMapIterator <uint,uchar> oldit(oldCue.values());
8✔
654
    while (oldit.hasNext() == true)
18✔
655
    {
656
        oldit.next();
10✔
657
        uint absChannel = oldit.key();
10✔
658
        quint32 universe = (absChannel >> 9);
10✔
659
        FadeChannel *fc = getFader(ua, universe, Fixture::invalidId(), absChannel);
10✔
660

661
        if (fc->flags() & FadeChannel::Intensity)
10✔
662
            updateFaderValues(fc, 0, oldCue.fadeOutSpeed());
7✔
663
    }
664

665
    // Fade in all channels of the new cue
666
    QMapIterator <uint,uchar> newit(newCue.values());
8✔
667
    while (newit.hasNext() == true)
24✔
668
    {
669
        newit.next();
16✔
670
        uint absChannel = newit.key();
16✔
671
        quint32 universe = (absChannel >> 9);
16✔
672
        FadeChannel *fc = getFader(ua, universe, Fixture::invalidId(), absChannel);
16✔
673
        updateFaderValues(fc, newit.value(), newCue.fadeInSpeed());
16✔
674
    }
675
}
8✔
676

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