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

mcallegari / qlcplus / 25854620976

14 May 2026 10:16AM UTC coverage: 35.035% (-0.005%) from 35.04%
25854620976

Pull #2020

github

web-flow
Merge f6c52cbb5 into a2ba0e823
Pull Request #2020: engine/sequence: fix crash after removing fixture

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

11 existing lines in 1 file now uncovered.

18283 of 52185 relevant lines covered (35.03%)

41144.15 hits per line

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

61.2
/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)
10✔
30
    : Chaser(doc)
31
    , m_boundSceneID(Function::invalidId())
20✔
32
    , m_needFixup(true)
10✔
33
{
34
    m_type = Function::SequenceType;
10✔
35
    setName(tr("New Sequence"));
10✔
36
}
10✔
37

38
Sequence::~Sequence()
17✔
39
{
40
}
17✔
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)
6✔
84
{
85
    m_boundSceneID = sceneID;
6✔
86
}
6✔
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

NEW
153
void Sequence::slotFixtureRemoved(quint32 fxID)
×
154
{
NEW
155
    if (fxID == Fixture::invalidId())
×
NEW
156
        return;
×
157

NEW
158
    for (ChaserStep& step : m_steps)
×
159
    {
NEW
160
        for (SceneValue& value : step.values)
×
161
        {
NEW
162
            if (value.fxi == fxID)
×
NEW
163
                step.unSetValue(value);
×
164
        }
165
    }
166
}
167

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

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

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

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

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

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

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

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

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

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

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

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

210
    return true;
1✔
211
}
212

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

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

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

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

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

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

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

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

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

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

296
    return true;
2✔
297
}
3✔
298

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

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

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

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

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

332
    int stepIndex = 0;
1✔
333

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

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

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

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