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

mcallegari / qlcplus / 27867971263

20 Jun 2026 10:09AM UTC coverage: 35.268% (-0.1%) from 35.377%
27867971263

push

github

mcallegari
Back to 5.3.0 debug

18433 of 52265 relevant lines covered (35.27%)

41250.48 hits per line

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

60.87
/engine/src/sequence.cpp
1
/*
2
  Q Light Controller Plus
3
  sequence.cpp
4

5
  Copyright (C) Massimo Callegari
6

7
  Licensed under the Apache License, Version 2.0 (the "License");
8
  you may not use this file except in compliance with the License.
9
  You may obtain a copy of the License at
10

11
      http://www.apache.org/licenses/LICENSE-2.0.txt
12

13
  Unless required by applicable law or agreed to in writing, software
14
  distributed under the License is distributed on an "AS IS" BASIS,
15
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
  See the License for the specific language governing permissions and
17
  limitations under the License.
18
*/
19

20
#include <QXmlStreamReader>
21
#include <QXmlStreamWriter>
22
#include <QDebug>
23
#include <algorithm>
24

25
#include "sequence.h"
26

27
#define KXMLQLCSequenceBoundScene QStringLiteral("BoundScene")
28

29
Sequence::Sequence(Doc* doc)
11✔
30
    : Chaser(doc)
31
    , m_boundSceneID(Function::invalidId())
22✔
32
    , m_needFixup(true)
11✔
33
{
34
    m_type = Function::SequenceType;
11✔
35
    setName(tr("New Sequence"));
11✔
36
}
11✔
37

38
Sequence::~Sequence()
19✔
39
{
40
}
19✔
41

42
QIcon Sequence::getIcon() const
×
43
{
44
    return QIcon(":/sequence.png");
×
45
}
46

47
Function *Sequence::createCopy(Doc *doc, bool addToDoc)
1✔
48
{
49
    Q_ASSERT(doc != NULL);
1✔
50

51
    Function* copy = new Sequence(doc);
1✔
52
    if (copy->copyFrom(this) == false)
1✔
53
    {
54
        delete copy;
×
55
        copy = NULL;
×
56
    }
57
    if (addToDoc == true && doc->addFunction(copy) == false)
1✔
58
    {
59
        delete copy;
×
60
        copy = NULL;
×
61
    }
62

63
    return copy;
1✔
64
}
65

66
bool Sequence::copyFrom(const Function *function)
1✔
67
{
68
    const Sequence* sequence = qobject_cast<const Sequence*> (function);
1✔
69
    if (sequence == NULL)
1✔
70
        return false;
×
71

72
    // Copy sequence stuff
73
    m_steps = sequence->m_steps;
1✔
74
    m_fadeInMode = sequence->m_fadeInMode;
1✔
75
    m_fadeOutMode = sequence->m_fadeOutMode;
1✔
76
    m_holdMode = sequence->m_holdMode;
1✔
77
    m_boundSceneID = sequence->m_boundSceneID;
1✔
78

79
    // Copy common function stuff
80
    return Function::copyFrom(function);
1✔
81
}
82

83
void Sequence::setBoundSceneID(quint32 sceneID)
7✔
84
{
85
    m_boundSceneID = sceneID;
7✔
86
}
7✔
87

88
quint32 Sequence::boundSceneID() const
23✔
89
{
90
    return m_boundSceneID;
23✔
91
}
92

93
QList<quint32> Sequence::components() const
×
94
{
95
    QList<quint32> ids;
×
96
    if (m_boundSceneID != Function::invalidId())
×
97
        ids.append(m_boundSceneID);
×
98
    return ids;
×
99
}
×
100

101
void Sequence::applyDumpValues(const QList<SceneValue> &dumpedValues, int targetStepIndex)
×
102
{
103
    Scene *scene = qobject_cast<Scene *>(doc()->function(boundSceneID()));
×
104
    if (scene == nullptr)
×
105
        return;
×
106

107
    QList<SceneValue> sceneValues = scene->values();
×
108
    std::sort(sceneValues.begin(), sceneValues.end());
×
109

110
    QList<SceneValue> zeroSceneValues = sceneValues;
×
111
    for (SceneValue &scv : zeroSceneValues)
×
112
        scv.value = 0;
×
113

114
    // Keep step values aligned with the bound Scene channels, preserving
115
    // existing step values where channels already exist.
116
    for (int i = 0; i < stepsCount(); i++)
×
117
    {
118
        ChaserStep step = steps().at(i);
×
119
        QList<SceneValue> oldValues = step.values;
×
120

121
        step.values = zeroSceneValues;
×
122
        for (const SceneValue &oldScv : oldValues)
×
123
        {
124
            int index = step.values.indexOf(oldScv);
×
125
            if (index != -1)
×
126
                step.values.replace(index, oldScv);
×
127
        }
128

129
        replaceStep(step, i);
×
130
    }
×
131

132
    if (targetStepIndex < 0 || targetStepIndex >= stepsCount())
×
133
    {
134
        ChaserStep newStep(boundSceneID());
×
135
        newStep.values = zeroSceneValues;
×
136
        for (const SceneValue &scv : dumpedValues)
×
137
            newStep.setValue(scv);
×
138
        addStep(newStep);
×
139
    }
×
140
    else
141
    {
142
        ChaserStep step = steps().at(targetStepIndex);
×
143
        for (const SceneValue &scv : dumpedValues)
×
144
            step.setValue(scv);
×
145
        replaceStep(step, targetStepIndex);
×
146
    }
×
147
}
×
148

149
/*****************************************************************************
150
 * Fixtures
151
 *****************************************************************************/
152

153
void Sequence::slotFixtureRemoved(quint32 fxID)
×
154
{
155
    for (ChaserStep& step : m_steps)
×
156
    {
157
        QMutableListIterator<SceneValue> it(step.values);
×
158
        while (it.hasNext())
×
159
        {
160
            SceneValue& value(it.next());
×
161
            if (value.fxi == fxID)
×
162
                it.remove();
×
163
        }
164
    }
165
}
×
166

167
/*****************************************************************************
168
 * Save & Load
169
 *****************************************************************************/
170

171
bool Sequence::saveXML(QXmlStreamWriter *doc) const
1✔
172
{
173
    Q_ASSERT(doc != NULL);
1✔
174

175
    /* Function tag */
176
    doc->writeStartElement(KXMLQLCFunction);
2✔
177

178
    /* Common attributes */
179
    saveXMLCommon(doc);
1✔
180

181
    doc->writeAttribute(KXMLQLCSequenceBoundScene, QString::number(boundSceneID()));
2✔
182

183
    /* Tempo type */
184
    saveXMLTempoType(doc);
1✔
185

186
    /* Speed */
187
    saveXMLSpeed(doc);
1✔
188

189
    /* Direction */
190
    saveXMLDirection(doc);
1✔
191

192
    /* Run order */
193
    saveXMLRunOrder(doc);
1✔
194

195
    /* Speed modes */
196
    doc->writeStartElement(KXMLQLCChaserSpeedModes);
2✔
197
    doc->writeAttribute(KXMLQLCFunctionSpeedFadeIn, speedModeToString(fadeInMode()));
2✔
198
    doc->writeAttribute(KXMLQLCFunctionSpeedFadeOut, speedModeToString(fadeOutMode()));
2✔
199
    doc->writeAttribute(KXMLQLCFunctionSpeedDuration, speedModeToString(durationMode()));
2✔
200
    doc->writeEndElement();
1✔
201

202
    /* Steps */
203
    for (int i = 0; i < m_steps.count(); i++)
4✔
204
        m_steps.at(i).saveXML(doc, i, true);
3✔
205

206
    /* End the <Function> tag */
207
    doc->writeEndElement();
1✔
208

209
    return true;
1✔
210
}
211

212
bool Sequence::loadXML(QXmlStreamReader &root)
4✔
213
{
214
    if (root.name() != KXMLQLCFunction)
4✔
215
    {
216
        qWarning() << Q_FUNC_INFO << "Function node not found";
1✔
217
        return false;
1✔
218
    }
219

220
    QXmlStreamAttributes funcAttrs = root.attributes();
3✔
221

222
    if (funcAttrs.value(KXMLQLCFunctionType).toString() != typeToString(Function::SequenceType))
6✔
223
    {
224
        qWarning() << Q_FUNC_INFO << funcAttrs.value(KXMLQLCFunctionType).toString()
3✔
225
                   << "is not a Sequence";
1✔
226
        return false;
1✔
227
    }
228

229
    if (funcAttrs.hasAttribute(KXMLQLCSequenceBoundScene) == false)
2✔
230
    {
231
        qWarning() << Q_FUNC_INFO << "Sequence doesn't have a bound Scene ID";
×
232
        return false;
×
233
    }
234

235
    setBoundSceneID(funcAttrs.value(KXMLQLCSequenceBoundScene).toString().toUInt());
2✔
236

237
    Scene *scene = qobject_cast<Scene *>(doc()->function(boundSceneID()));
2✔
238
    QList<SceneValue> sceneValues;
2✔
239
    if (scene != NULL)
2✔
240
    {
241
        sceneValues = scene->values();
1✔
242
        std::sort(sceneValues.begin(), sceneValues.end());
1✔
243
        m_needFixup = false;
1✔
244
    }
245

246
    /* Load Sequence contents */
247
    while (root.readNextStartElement())
16✔
248
    {
249
        if (root.name() == KXMLQLCFunctionSpeed)
14✔
250
        {
251
            loadXMLSpeed(root);
2✔
252
        }
253
        else if (root.name() == KXMLQLCFunctionDirection)
12✔
254
        {
255
            loadXMLDirection(root);
2✔
256
        }
257
        else if (root.name() == KXMLQLCFunctionRunOrder)
10✔
258
        {
259
            loadXMLRunOrder(root);
2✔
260
        }
261
        else if (root.name() == KXMLQLCFunctionTempoType)
8✔
262
        {
263
            loadXMLTempoType(root);
×
264
        }
265
        else if (root.name() == KXMLQLCChaserSpeedModes)
8✔
266
        {
267
            loadXMLSpeedModes(root);
2✔
268
        }
269
        else if (root.name() == KXMLQLCFunctionStep)
6✔
270
        {
271
            //! @todo stepNumber is useless if the steps are in the wrong order
272
            ChaserStep step;
6✔
273
            int stepNumber = -1;
6✔
274

275
            if (sceneValues.isEmpty() == false)
6✔
276
                step.values = sceneValues;
3✔
277

278
            if (step.loadXML(root, stepNumber, doc()) == true)
6✔
279
            {
280
                step.fid = boundSceneID();
6✔
281

282
                if (stepNumber >= m_steps.size())
6✔
283
                    m_steps.append(step);
6✔
284
                else
285
                    m_steps.insert(stepNumber, step);
×
286
            }
287
        }
6✔
288
        else
289
        {
290
            qWarning() << Q_FUNC_INFO << "Unknown Sequence tag:" << root.name();
×
291
            root.skipCurrentElement();
×
292
        }
293
    }
294

295
    return true;
2✔
296
}
3✔
297

298
void Sequence::postLoad()
1✔
299
{
300
    if (m_needFixup == false)
1✔
301
        return;
×
302

303
    Doc* doc = this->doc();
1✔
304
    Q_ASSERT(doc != NULL);
1✔
305

306
    Scene *scene = qobject_cast<Scene *>(doc->function(boundSceneID()));
1✔
307
    QList<SceneValue> sceneValues;
1✔
308
    if (scene != NULL)
1✔
309
    {
310
        sceneValues = scene->values();
1✔
311

312
        if (sceneValues.count() == 0)
1✔
313
        {
314
            qDebug() << "The bound Scene is empty ! This should never happen. Trying to fix it...";
×
315
            if (stepsCount())
×
316
            {
317
                foreach (SceneValue value, m_steps.at(0).values)
×
318
                {
319
                    value.value = 0;
×
320
                    if (doc->fixture(value.fxi) != NULL)
×
321
                        scene->setValue(value);
×
322
                }
×
323
            }
324
            m_needFixup = false;
×
325
            return;
×
326
        }
327

328
        std::sort(sceneValues.begin(), sceneValues.end());
1✔
329
    }
330

331
    int stepIndex = 0;
1✔
332

333
    QMutableListIterator <ChaserStep> it(m_steps);
1✔
334
    while (it.hasNext() == true)
4✔
335
    {
336
        ChaserStep step(it.next());
3✔
337
        if (sceneValues.count() == step.values.count())
3✔
338
        {
339
            stepIndex++;
×
340
            continue;
×
341
        }
342

343
        QList <SceneValue> tmpList = step.values;
3✔
344
        step.values = sceneValues;
3✔
345
        for (int i = 0; i < tmpList.count(); i++)
21✔
346
        {
347
            int tmpIndex = step.values.indexOf(tmpList.at(i));
18✔
348
            if (tmpIndex == -1)
18✔
349
                continue;
×
350
            step.values.replace(tmpIndex, tmpList.at(i));
18✔
351
        }
352

353
        replaceStep(step, stepIndex);
3✔
354
        stepIndex++;
3✔
355
    }
3✔
356
    m_needFixup = false;
1✔
357

358
    qDebug() << "Sequence" << name() << "steps fixed. Values:" << sceneValues.count();
1✔
359
}
1✔
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