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

mcallegari / qlcplus / 27868188413

20 Jun 2026 10:19AM UTC coverage: 35.301% (+0.03%) from 35.268%
27868188413

push

github

mcallegari
qmlui: major update to palettes

- introduced 3D position palette
- added support for shutter palettes
- fix palette manager filters
- do not show fanning when editing a palette

58 of 233 new or added lines in 3 files covered. (24.89%)

6 existing lines in 2 files now uncovered.

18530 of 52491 relevant lines covered (35.3%)

41072.9 hits per line

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

80.15
/engine/src/qlccapability.cpp
1
/*
2
  Q Light Controller Plus
3
  qlccapability.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 <QCoreApplication>
22
#include <QXmlStreamReader>
23
#include <QMetaEnum>
24
#include <QString>
25
#include <QDebug>
26
#include <QFile>
27

28
#include "qlccapability.h"
29
#include "qlcmacros.h"
30
#include "qlcconfig.h"
31
#include "qlcfile.h"
32

33
/************************************************************************
34
 * Initialization
35
 ************************************************************************/
36

37
QLCCapability::QLCCapability(uchar min, uchar max, const QString& name, QObject *parent)
4,355✔
38
    : QObject(parent)
39
    , m_preset(Custom)
4,355✔
40
    , m_min(min)
4,355✔
41
    , m_max(max)
4,355✔
42
    , m_name(name)
4,355✔
43
    , m_warning(WarningType::NoWarning)
4,355✔
44
{
45
}
4,355✔
46

47
QLCCapability *QLCCapability::createCopy()
9✔
48
{
49
    QLCCapability *copy = new QLCCapability(m_min, m_max, m_name);
9✔
50
    copy->setWarning(m_warning);
9✔
51
    copy->setPreset(preset());
9✔
52
    for (int i = 0; i < m_resources.count(); i++)
9✔
53
        copy->setResource(i, m_resources.at(i));
×
54
    foreach (AliasInfo alias, m_aliases)
9✔
55
        copy->addAlias(alias);
9✔
56

57
    return copy;
9✔
58
}
59

60
QLCCapability::~QLCCapability()
6,728✔
61
{
62
}
6,728✔
63

64
bool QLCCapability::operator<(const QLCCapability& capability) const
26✔
65
{
66
    if (m_min < capability.m_min)
26✔
67
        return true;
14✔
68
    else
69
        return false;
12✔
70
}
71

72
QString QLCCapability::presetToString(QLCCapability::Preset preset)
1✔
73
{
74
    int index = staticMetaObject.indexOfEnumerator("Preset");
1✔
75
    return staticMetaObject.enumerator(index).valueToKey(preset);
2✔
76
}
77

78
QLCCapability::Preset QLCCapability::stringToPreset(const QString &preset)
824✔
79
{
80
    int index = staticMetaObject.indexOfEnumerator("Preset");
824✔
81
    return Preset(staticMetaObject.enumerator(index).keyToValue(preset.toStdString().c_str()));
824✔
82
}
83

84
QLCCapability::Preset QLCCapability::preset() const
11,122✔
85
{
86
    return m_preset;
11,122✔
87
}
88

NEW
89
int QLCCapability::presetInt() const
×
90
{
NEW
91
    return int(m_preset);
×
92
}
93

94
void QLCCapability::setPreset(QLCCapability::Preset preset)
844✔
95
{
96
    if (preset == m_preset)
844✔
97
        return;
9✔
98

99
    m_preset = preset;
835✔
100
}
101

102
/* please see
103
https://github.com/mcallegari/qlcplus/wiki/Fixture-definition-presets
104
when changing this function */
105
QLCCapability::PresetType QLCCapability::presetType() const
2,575✔
106
{
107
    switch (m_preset)
2,575✔
108
    {
109
        case StrobeFrequency:
139✔
110
        case PulseFrequency:
111
        case RampUpFrequency:
112
        case RampDownFrequency:
113
        case PrismEffectOn:
114
            return SingleValue;
139✔
115
        case StrobeFreqRange:
4✔
116
        case PulseFreqRange:
117
        case RampUpFreqRange:
118
        case RampDownFreqRange:
119
            return DoubleValue;
4✔
120
        case ColorMacro:
225✔
121
            return SingleColor;
225✔
122
        case ColorDoubleMacro:
135✔
123
            return DoubleColor;
135✔
124
        case GoboMacro:
81✔
125
        case GoboShakeMacro:
126
        case GenericPicture:
127
            return Picture;
81✔
128
        default: return None;
1,991✔
129
    }
130
}
131

132
/* please see
133
https://github.com/mcallegari/qlcplus/wiki/Fixture-definition-presets
134
when changing this function */
135
QString QLCCapability::presetUnits() const
×
136
{
137
    switch (m_preset)
×
138
    {
139
        case StrobeFrequency:
×
140
        case PulseFrequency:
141
        case RampUpFrequency:
142
        case RampDownFrequency:
143
        case StrobeFreqRange:
144
        case PulseFreqRange:
145
        case RampUpFreqRange:
146
        case RampDownFreqRange:
147
            return "Hz";
×
148
        break;
149
        case PrismEffectOn:
×
150
            return "Faces";
×
151
        break;
152
        default:
×
153
        break;
×
154
    }
155
    return QString();
×
156
}
157

158
/************************************************************************
159
 * Properties
160
 ************************************************************************/
161

162
uchar QLCCapability::min() const
88,621✔
163
{
164
    return m_min;
88,621✔
165
}
166

167
void QLCCapability::setMin(uchar value)
2,839✔
168
{
169
    if (m_min == value)
2,839✔
170
        return;
264✔
171

172
    m_min = value;
2,575✔
173
    emit minChanged();
2,575✔
174
}
175

176
uchar QLCCapability::max() const
3,610✔
177
{
178
    return m_max;
3,610✔
179
}
180

181
void QLCCapability::setMax(uchar value)
2,839✔
182
{
183
    if (m_max == value)
2,839✔
184
        return;
261✔
185

186
    m_max = value;
2,578✔
187
    emit maxChanged();
2,578✔
188
}
189

190
uchar QLCCapability::middle() const
41✔
191
{
192
    return int((m_max + m_min) / 2);
41✔
193
}
194

195
QString QLCCapability::name() const
2,650✔
196
{
197
    return m_name;
2,650✔
198
}
199

200
void QLCCapability::setName(const QString& name)
3,066✔
201
{
202
    if (m_name == name)
3,066✔
203
        return;
×
204

205
    m_name = name;
3,066✔
206
    emit nameChanged();
3,066✔
207
}
208

209
QLCCapability::WarningType QLCCapability::warning() const
×
210
{
211
    return m_warning;
×
212
}
213

214
void QLCCapability::setWarning(QLCCapability::WarningType type)
9✔
215
{
216
    if (m_warning == type)
9✔
217
        return;
9✔
218

219
    m_warning = type;
×
220
    emit warningChanged();
×
221
}
222

223
QVariant QLCCapability::resource(int index) const
1,027✔
224
{
225
    if (index < 0 || index >= m_resources.count())
1,027✔
226
        return QVariant();
1,024✔
227

228
    return m_resources.at(index);
3✔
229
}
230

231
void QLCCapability::setResource(int index, QVariant value)
730✔
232
{
233
    if (index < 0)
730✔
234
        return;
×
235
    else if (index < m_resources.count())
730✔
236
        m_resources[index] = value;
×
237
    else
238
        m_resources.append(value);
730✔
239
}
240

241
QVariantList QLCCapability::resources() const
×
242
{
243
    return m_resources;
×
244
}
245

246
bool QLCCapability::overlaps(const QLCCapability *cap) const
21,565✔
247
{
248
    if (m_min >= cap->min() && m_min <= cap->max())
21,565✔
249
        return true;
11✔
250
    else if (m_max >= cap->min() && m_max <= cap->max())
21,554✔
251
        return true;
6✔
252
    else if (m_min <= cap->min() && m_max >= cap->min())
21,548✔
253
        return true;
3✔
254
    else
255
        return false;
21,545✔
256
}
257

258
/********************************************************************
259
 * Aliases
260
 ********************************************************************/
261

262
QList<AliasInfo> QLCCapability::aliasList() const
11✔
263
{
264
    return m_aliases;
11✔
265
}
266

267
void QLCCapability::addAlias(AliasInfo alias)
9✔
268
{
269
    m_aliases.append(alias);
9✔
270
}
9✔
271

272
void QLCCapability::removeAlias(const AliasInfo& alias)
2✔
273
{
274
    for (int i = 0; i < m_aliases.count(); i++)
2✔
275
    {
276
        AliasInfo info = m_aliases.at(i);
2✔
277

278
        if (alias.targetMode == info.targetMode &&
4✔
279
            alias.sourceChannel == info.sourceChannel &&
4✔
280
            alias.targetChannel == info.targetChannel)
2✔
281
        {
282
            m_aliases.takeAt(i);
2✔
283
            return;
2✔
284
        }
285
    }
2✔
286
}
287

288
void QLCCapability::replaceAliases(QList<AliasInfo> list)
1✔
289
{
290
    m_aliases.clear();
1✔
291
    foreach (AliasInfo info, list)
3✔
292
        m_aliases.append(info);
3✔
293
}
1✔
294

295
/************************************************************************
296
 * Save & Load
297
 ************************************************************************/
298

299
bool QLCCapability::saveXML(QXmlStreamWriter *doc) const
7✔
300
{
301
    Q_ASSERT(doc != NULL);
7✔
302

303
    /* QLCCapability entry */
304
    doc->writeStartElement(KXMLQLCCapability);
14✔
305

306
    /* Min limit attribute */
307
    doc->writeAttribute(KXMLQLCCapabilityMin, QString::number(m_min));
14✔
308

309
    /* Max limit attribute */
310
    doc->writeAttribute(KXMLQLCCapabilityMax, QString::number(m_max));
14✔
311

312
    /* Preset attribute if not custom */
313
    if (m_preset != Custom)
7✔
314
        doc->writeAttribute(KXMLQLCCapabilityPreset, presetToString(m_preset));
2✔
315

316
    /* Resource attributes */
317
    for (int i = 0; i < m_resources.count(); i++)
9✔
318
    {
319
        switch (presetType())
2✔
320
        {
321
            case Picture:
×
322
            {
323
                QString modFilename = resource(i).toString();
×
324
                QDir dir = QDir::cleanPath(QLCFile::systemDirectory(GOBODIR).path());
×
325

326
                if (modFilename.contains(dir.path()))
×
327
                {
328
                    modFilename.remove(dir.path());
×
329
                    // The following line is a dirty workaround for an issue raised on Windows
330
                    // When building with MinGW, dir.path() is something like "C:/QLC+/Gobos"
331
                    // while QDir::separator() returns "\"
332
                    // So, to avoid any string mismatch I remove the first character
333
                    // no matter what it is
334
                    modFilename.remove(0, 1);
×
335
                }
336

337
                doc->writeAttribute(KXMLQLCCapabilityRes1, modFilename);
×
338
            }
×
339
            break;
×
340
            case SingleColor:
×
341
            case DoubleColor:
342
            {
343
                QColor col = resource(i).value<QColor>();
×
344
                if (i == 0 && col.isValid())
×
345
                    doc->writeAttribute(KXMLQLCCapabilityRes1, col.name());
×
346
                else if (i == 1 && col.isValid())
×
347
                    doc->writeAttribute(KXMLQLCCapabilityRes2, col.name());
×
348
            }
349
            break;
×
350
            case SingleValue:
2✔
351
            case DoubleValue:
352
            {
353
                if (i == 0)
2✔
354
                    doc->writeAttribute(KXMLQLCCapabilityRes1, QString::number(resource(i).toFloat()));
2✔
355
                else if (i == 1)
1✔
356
                    doc->writeAttribute(KXMLQLCCapabilityRes2, QString::number(resource(i).toFloat()));
2✔
357
            }
358
            break;
2✔
359
            default:
×
360
            break;
×
361
        }
362
    }
363

364
    /* Name */
365
    if (m_aliases.isEmpty())
7✔
366
        doc->writeCharacters(m_name);
6✔
367
    else
368
        doc->writeCharacters(QString("%1\n   ").arg(m_name)); // to preserve indentation
1✔
369

370
    /* Aliases */
371
    foreach (AliasInfo info, m_aliases)
8✔
372
    {
373
        doc->writeStartElement(KXMLQLCCapabilityAlias);
2✔
374
        doc->writeAttribute(KXMLQLCCapabilityAliasMode, info.targetMode);
2✔
375
        doc->writeAttribute(KXMLQLCCapabilityAliasSourceName, info.sourceChannel);
2✔
376
        doc->writeAttribute(KXMLQLCCapabilityAliasTargetName, info.targetChannel);
2✔
377
        doc->writeEndElement();
1✔
378
    }
8✔
379

380
    doc->writeEndElement();
7✔
381

382
    return true;
7✔
383
}
384

385
bool QLCCapability::loadXML(QXmlStreamReader &doc)
2,576✔
386
{
387
    uchar min = 0;
2,576✔
388
    uchar max = 0;
2,576✔
389
    QString str;
2,576✔
390

391
    if (doc.name() != KXMLQLCCapability)
2,576✔
392
    {
393
        qWarning() << Q_FUNC_INFO << "Capability node not found";
1✔
394
        return false;
1✔
395
    }
396

397
    /* Get low limit attribute (mandatory) */
398
    QXmlStreamAttributes attrs = doc.attributes();
2,575✔
399
    str = attrs.value(KXMLQLCCapabilityMin).toString();
2,575✔
400
    if (str.isEmpty() == true)
2,575✔
401
    {
402
        qWarning() << Q_FUNC_INFO << "Capability has no minimum limit.";
2✔
403
        return false;
2✔
404
    }
405
    else
406
    {
407
        min = CLAMP(str.toInt(), 0, (int)UCHAR_MAX);
2,573✔
408
    }
409

410
    /* Get high limit attribute (mandatory) */
411
    str = attrs.value(KXMLQLCCapabilityMax).toString();
2,573✔
412
    if (str.isEmpty() == true)
2,573✔
413
    {
414
        qWarning() << Q_FUNC_INFO << "Capability has no maximum limit.";
1✔
415
        return false;
1✔
416
    }
417
    else
418
    {
419
        max = CLAMP(str.toInt(), 0, (int)UCHAR_MAX);
2,572✔
420
    }
421

422
    if (attrs.hasAttribute(KXMLQLCCapabilityPreset))
2,572✔
423
    {
424
        str = attrs.value(KXMLQLCCapabilityPreset).toString();
824✔
425
        setPreset(stringToPreset(str));
824✔
426
    }
427

428
    switch(presetType())
2,572✔
429
    {
430
        case Picture:
81✔
431
        {
432
            QString path = attrs.value(KXMLQLCCapabilityRes1).toString();
81✔
433
            if (QFileInfo(path).isRelative())
81✔
434
            {
435
                QDir dir = QLCFile::systemDirectory(GOBODIR);
162✔
436
                path = dir.path() + QDir::separator() + path;
81✔
437
            }
81✔
438
            setResource(0, path);
81✔
439
        }
81✔
440
        break;
81✔
441
        case SingleColor:
359✔
442
        case DoubleColor:
443
        {
444
            QColor col1 = QColor(attrs.value(KXMLQLCCapabilityRes1).toString());
359✔
445
            QColor col2 = QColor();
359✔
446
            if (attrs.hasAttribute(KXMLQLCCapabilityRes2))
359✔
447
                col2 = QColor(attrs.value(KXMLQLCCapabilityRes2).toString());
135✔
448

449
            if (col1.isValid())
359✔
450
            {
451
                setResource(0, col1);
359✔
452
                if (col2.isValid())
359✔
453
                    setResource(1, col2);
135✔
454
            }
455
        }
456
        break;
359✔
457
        case SingleValue:
141✔
458
        case DoubleValue:
459
        {
460
            float value = attrs.value(KXMLQLCCapabilityRes1).toString().toFloat();
141✔
461
            setResource(0, value);
141✔
462

463
            if (attrs.hasAttribute(KXMLQLCCapabilityRes2))
141✔
464
            {
465
                value = attrs.value(KXMLQLCCapabilityRes2).toString().toFloat();
2✔
466
                setResource(1, value);
2✔
467
            }
468
        }
469
        break;
141✔
470
        default:
1,991✔
471
        break;
1,991✔
472
    }
473

474
    /* ************************* LEGACY ATTRIBUTES ************************* */
475

476
    /* Get (optional) resource name for gobo/effect/... */
477
    if (attrs.hasAttribute(KXMLQLCCapabilityResource))
2,572✔
478
    {
479
        QString path = attrs.value(KXMLQLCCapabilityResource).toString();
10✔
480
        if (QFileInfo(path).isRelative())
10✔
481
        {
482
            QDir dir = QLCFile::systemDirectory(GOBODIR);
20✔
483
            path = dir.path() + QDir::separator() + path;
10✔
484
            setPreset(GoboMacro);
10✔
485
        }
10✔
486
        else
487
            setPreset(GenericPicture);
×
488
        setResource(0, path);
10✔
489
    }
10✔
490

491
    /* Get (optional) color resource for color presets */
492
    if (attrs.hasAttribute(KXMLQLCCapabilityColor1))
2,572✔
493
    {
494
        QColor col1 = QColor(attrs.value(KXMLQLCCapabilityColor1).toString());
×
495
        QColor col2 = QColor();
×
496
        if (attrs.hasAttribute(KXMLQLCCapabilityColor2))
×
497
            col2 = QColor(attrs.value(KXMLQLCCapabilityColor2).toString());
×
498

499
        if (col1.isValid())
×
500
        {
501
            setResource(0, col1);
×
502

503
            if (col2.isValid())
×
504
            {
505
                setResource(1, col2);
×
506
                setPreset(ColorDoubleMacro);
×
507
            }
508
            else
509
            {
510
                setPreset(ColorMacro);
×
511
            }
512
        }
513
    }
514

515
    if (min <= max)
2,572✔
516
    {
517
        doc.readNext();
2,571✔
518
        setName(doc.text().toString().simplified());
2,571✔
519
        setMin(min);
2,571✔
520
        setMax(max);
2,571✔
521
        if (name().isEmpty())
2,571✔
522
        {
523
            qWarning() << "Empty description provided. This should be fixed in the definition!";
×
524
            return true;
×
525
        }
526
    }
527
    else
528
    {
529
        qWarning() << Q_FUNC_INFO << "Capability min(" << min
2✔
530
                   << ") is greater than max(" << max << ")";
1✔
531
        return false;
1✔
532
    }
533

534
    /* Subtags */
535
    while (doc.readNextStartElement())
2,576✔
536
    {
537
        if (doc.name() == KXMLQLCCapabilityAlias)
5✔
538
        {
539
            AliasInfo alias;
5✔
540
            QXmlStreamAttributes attrs = doc.attributes();
5✔
541

542
            alias.targetMode = attrs.value(KXMLQLCCapabilityAliasMode).toString();
5✔
543
            alias.sourceChannel = attrs.value(KXMLQLCCapabilityAliasSourceName).toString();
5✔
544
            alias.targetChannel = attrs.value(KXMLQLCCapabilityAliasTargetName).toString();
5✔
545
            addAlias(alias);
5✔
546

547
            //qDebug() << "Alias found for mode" << alias.targetMode;
548
        }
5✔
549
        else
550
        {
551
            qWarning() << Q_FUNC_INFO << "Unknown capability tag: " << doc.name();
×
552
        }
553
        doc.skipCurrentElement();
5✔
554
    }
555

556
    return true;
2,571✔
557
}
2,576✔
558

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