• 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

33.38
/engine/src/qlcpalette.cpp
1
/*
2
  Q Light Controller Plus
3
  qlcpalette.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 <QVector3D>
23
#include <QMatrix4x4>
24
#include <QtMath>
25
#include <QDebug>
26

27
#include "monitorproperties.h"
28
#include "qlcfixturemode.h"
29
#include "qlcfixturehead.h"
30
#include "qlccapability.h"
31
#include "qlcpalette.h"
32
#include "qlcchannel.h"
33
#include "scenevalue.h"
34
#include "doc.h"
35

36
#define KXMLQLCPaletteType      QStringLiteral("Type")
37
#define KXMLQLCPaletteName      QStringLiteral("Name")
38
#define KXMLQLCPaletteValue     QStringLiteral("Value")
39
#define KXMLQLCPaletteFanning   QStringLiteral("Fan")
40
#define KXMLQLCPaletteFanLayout QStringLiteral("Layout")
41
#define KXMLQLCPaletteFanAmount QStringLiteral("Amount")
42
#define KXMLQLCPaletteFanValue  QStringLiteral("FanValue")
43

44
QLCPalette::QLCPalette(QLCPalette::PaletteType type, QObject *parent)
19✔
45
    : QObject(parent)
46
    , m_id(QLCPalette::invalidId())
38✔
47
    , m_isTemporary(true)
19✔
48
    , m_type(type)
19✔
49
    , m_fanningType(Flat)
19✔
50
    , m_fanningLayout(XAscending)
19✔
51
    , m_fanningAmount(100)
19✔
52
{
53
}
19✔
54

55
QLCPalette *QLCPalette::createCopy()
×
56
{
57
    QLCPalette *copy = new QLCPalette(type());
×
58
    copy->setValues(this->values());
×
59
    copy->setName(this->name());
×
60
    copy->setFanningType(this->fanningType());
×
61
    copy->setFanningLayout(this->fanningLayout());
×
62
    copy->setFanningAmount(this->fanningAmount());
×
63
    copy->setFanningValue(this->fanningValue());
×
64

65
    return copy;
×
66
}
67

68
QLCPalette::~QLCPalette()
21✔
69
{
70

71
}
21✔
72

73
/************************************************************************
74
 * Properties
75
 ************************************************************************/
76

77
quint32 QLCPalette::id() const
5✔
78
{
79
    return m_id;
5✔
80
}
81

82
void QLCPalette::setID(quint32 id)
5✔
83
{
84
    m_id = id;
5✔
85
}
5✔
86

87
quint32 QLCPalette::invalidId()
26✔
88
{
89
    return UINT_MAX;
26✔
90
}
91

92
bool QLCPalette::isTemporary() const
×
93
{
94
    return m_isTemporary;
×
95
}
96

97
void QLCPalette::setTemporary(bool temporary)
2✔
98
{
99
    if (m_isTemporary == temporary)
2✔
100
        return;
×
101

102
    m_isTemporary = temporary;
2✔
103
    emit temporaryChanged();
2✔
104
}
105

106
QLCPalette::PaletteType QLCPalette::type() const
11✔
107
{
108
    return m_type;
11✔
109
}
110

111
QString QLCPalette::typeToString(QLCPalette::PaletteType type)
11✔
112
{
113
    switch (type)
11✔
114
    {
115
        case Dimmer:    return "Dimmer";
1✔
116
        case Color:     return "Color";
1✔
117
        case Pan:       return "Pan";
1✔
118
        case Tilt:      return "Tilt";
1✔
119
        case PanTilt:   return "PanTilt";
2✔
120
        case Shutter:   return "Shutter";
1✔
121
        case Gobo:      return "Gobo";
1✔
122
        case Zoom:       return "Zoom";
1✔
123
        case Position3D: return "Position3D";
1✔
124
        case Undefined:  return "";
1✔
125
    }
126

127
    return "";
×
128
}
129

130
QLCPalette::PaletteType QLCPalette::stringToType(const QString &str)
11✔
131
{
132
    if (str == "Dimmer")
11✔
133
        return Dimmer;
1✔
134
    else if (str == "Color")
10✔
135
        return Color;
2✔
136
    else if (str == "Pan")
8✔
137
        return Pan;
1✔
138
    else if (str == "Tilt")
7✔
139
        return Tilt;
1✔
140
    else if (str == "PanTilt")
6✔
141
        return PanTilt;
1✔
142
    else if (str == "Shutter")
5✔
143
        return Shutter;
1✔
144
    else if (str == "Gobo")
4✔
145
        return Gobo;
1✔
146
    else if (str == "Zoom")
3✔
147
        return Zoom;
1✔
148
    else if (str == "Position3D")
2✔
149
        return Position3D;
1✔
150

151
    return Undefined;
1✔
152
}
153

154
QString QLCPalette::iconResource(bool svg) const
9✔
155
{
156
    QString prefix = svg ? "qrc" : "";
9✔
157
    QString ext = svg ? "svg" : "png";
9✔
158

159
    switch(type())
9✔
160
    {
161
        case Dimmer: return QString("%1:/intensity.%2").arg(prefix).arg(ext);
1✔
162
        case Color: return QString("%1:/color.%2").arg(prefix).arg(ext);
1✔
163
        case Pan: return QString("%1:/pan.%2").arg(prefix).arg(ext);
1✔
164
        case Tilt: return QString("%1:/tilt.%2").arg(prefix).arg(ext);
1✔
165
        case PanTilt: return QString("%1:/position.%2").arg(prefix).arg(ext);
1✔
166
        case Shutter: return QString("%1:/shutter.%2").arg(prefix).arg(ext);
1✔
167
        case Gobo: return QString("%1:/gobo.%2").arg(prefix).arg(ext);
1✔
168
        case Zoom: return QString("%1:/beam.%2").arg(prefix).arg(ext);
1✔
169
        case Position3D: return QString("%1:/3dpoint.%2").arg(prefix).arg(ext);
1✔
UNCOV
170
        default: return "";
×
171
    }
172
}
9✔
173

174
QString QLCPalette::name() const
4✔
175
{
176
    return m_name;
4✔
177
}
178

179
void QLCPalette::setName(const QString &name)
3✔
180
{
181
    if (name == m_name)
3✔
182
        return;
×
183

184
    m_name = QString(name);
3✔
185
    emit nameChanged();
3✔
186
}
187

188
QVariant QLCPalette::value() const
4✔
189
{
190
    if (m_values.isEmpty())
4✔
191
        return QVariant();
×
192

193
    return m_values.first();
4✔
194
}
195

196
int QLCPalette::intValue1() const
×
197
{
198
    if (m_values.isEmpty())
×
199
        return -1;
×
200

201
    return m_values.at(0).toInt();
×
202
}
203

204
int QLCPalette::intValue2() const
×
205
{
206
    if (m_values.count() < 2)
×
207
        return -1;
×
208

209
    return m_values.at(1).toInt();
×
210
}
211

212
float QLCPalette::floatValue1() const
×
213
{
214
    if (m_values.isEmpty())
×
215
        return -1;
×
216

217
    return m_values.at(0).toFloat();
×
218
}
219

NEW
220
float QLCPalette::floatValue2() const
×
221
{
NEW
222
    if (m_values.count() < 2)
×
NEW
223
        return -1;
×
224

NEW
225
    return m_values.at(1).toFloat();
×
226
}
227

NEW
228
float QLCPalette::floatValue3() const
×
229
{
NEW
230
    if (m_values.count() < 3)
×
NEW
231
        return -1;
×
232

NEW
233
    return m_values.at(2).toFloat();
×
234
}
235

UNCOV
236
QString QLCPalette::strValue1() const
×
237
{
238
    if (m_values.isEmpty())
×
239
        return QString();
×
240

241
    return m_values.at(0).toString();
×
242
}
243

244
QColor QLCPalette::rgbValue() const
×
245
{
246
    if (m_values.isEmpty())
×
247
        return QColor();
×
248

249
    QColor rgb, wauv;
×
250
    stringToColor(m_values.at(0).toString(), rgb, wauv);
×
251

252
    return rgb;
×
253
}
254

255
QColor QLCPalette::wauvValue() const
×
256
{
257
    if (m_values.isEmpty())
×
258
        return QColor();
×
259

260
    QColor rgb, wauv;
×
261
    stringToColor(m_values.at(0).toString(), rgb, wauv);
×
262

263
    return wauv;
×
264
}
265

NEW
266
QVector3D QLCPalette::vector3DValue() const
×
267
{
NEW
268
    if (m_values.count() < 3)
×
NEW
269
        return QVector3D();
×
270

NEW
271
    return QVector3D(m_values.at(0).toFloat(), m_values.at(1).toFloat(), m_values.at(2).toFloat());
×
272
}
273

274
void QLCPalette::setValue(QVariant val)
3✔
275
{
276
    m_values.clear();
3✔
277
    m_values.append(val);
3✔
278
}
3✔
279

280
void QLCPalette::setValue(QVariant val1, QVariant val2)
2✔
281
{
282
    m_values.clear();
2✔
283
    m_values.append(val1);
2✔
284
    m_values.append(val2);
2✔
285
}
2✔
286

NEW
287
void QLCPalette::setValue(QVariant val1, QVariant val2, QVariant val3)
×
288
{
NEW
289
    m_values.clear();
×
NEW
290
    m_values.append(val1);
×
NEW
291
    m_values.append(val2);
×
NEW
292
    m_values.append(val3);
×
NEW
293
}
×
294

295
QVariantList QLCPalette::values() const
6✔
296
{
297
    return m_values;
6✔
298
}
299

300
void QLCPalette::setValues(QVariantList values)
×
301
{
302
    m_values = values;
×
303
}
×
304

305
void QLCPalette::resetValues()
×
306
{
307
    m_values.clear();
×
308
}
×
309

310
QList<SceneValue> QLCPalette::valuesFromFixtures(Doc *doc, QList<quint32> fixtures) const
×
311
{
312
    QList<SceneValue> list;
×
313

NEW
314
    qDebug() << "[QLCPalette] valuesFromFixtures type:" << typeToString(m_type)
×
NEW
315
             << "values:" << m_values << "fixtures:" << fixtures.count();
×
316

UNCOV
317
    int fxCount = fixtures.count();
×
318
    // normalized progress in [ 0.0, 1.0 ] range
319
    qreal progress = 0.0;
×
320
    int intFanValue = fanningValue().toInt();
×
321
    FanningType fType = fanningType();
×
322
    FanningLayout fLayout = fanningLayout();
×
323
    MonitorProperties *mProps = doc->monitorProperties();
×
324
    qreal centerCoord = 0.0;
×
325
    qreal maxCenterDistance = 0.0;
×
326

327
    if (fLayout == XCentered || fLayout == YCentered || fLayout == ZCentered)
×
328
    {
329
        bool hasCoord = false;
×
330
        qreal minCoord = 0.0;
×
331
        qreal maxCoord = 0.0;
×
332

333
        foreach (quint32 id, fixtures)
×
334
        {
335
            QVector3D pos = mProps->fixturePosition(id, 0, 0);
×
336
            qreal coord = 0.0;
×
337

338
            switch (fLayout)
×
339
            {
340
                case XCentered: coord = pos.x(); break;
×
341
                case YCentered: coord = pos.y(); break;
×
342
                case ZCentered: coord = pos.z(); break;
×
343
                default: break;
×
344
            }
345

346
            if (hasCoord == false)
×
347
            {
348
                minCoord = coord;
×
349
                maxCoord = coord;
×
350
                hasCoord = true;
×
351
            }
352
            else
353
            {
354
                minCoord = qMin(minCoord, coord);
×
355
                maxCoord = qMax(maxCoord, coord);
×
356
            }
357
        }
×
358

359
        centerCoord = (minCoord + maxCoord) / 2.0;
×
360
        maxCenterDistance = qMax(qAbs(minCoord - centerCoord), qAbs(maxCoord - centerCoord));
×
361
    }
362

363
    // sort the fixtures list based on selected layout
364
    std::sort(fixtures.begin(), fixtures.end(),
×
365
        [fLayout, mProps, centerCoord](quint32 a, quint32 b) {
×
366
            QVector3D posA = mProps->fixturePosition(a, 0, 0);
×
367
            QVector3D posB = mProps->fixturePosition(b, 0, 0);
×
368

369
            switch(fLayout)
×
370
            {
371
                case XAscending: return posA.x() < posB.x();
×
372
                case XDescending: return posB.x() < posA.x();
×
373
                case XCentered:
×
374
                {
375
                    qreal distA = qAbs(posA.x() - centerCoord);
×
376
                    qreal distB = qAbs(posB.x() - centerCoord);
×
377
                    return (distA == distB) ? (posA.x() < posB.x()) : (distA < distB);
×
378
                }
379
                case YAscending: return posA.y() < posB.y();
×
380
                case YDescending: return posB.y() < posA.y();
×
381
                case YCentered:
×
382
                {
383
                    qreal distA = qAbs(posA.y() - centerCoord);
×
384
                    qreal distB = qAbs(posB.y() - centerCoord);
×
385
                    return (distA == distB) ? (posA.y() < posB.y()) : (distA < distB);
×
386
                }
387
                case ZAscending: return posA.z() < posB.z();
×
388
                case ZDescending: return posB.z() < posA.z();
×
389
                case ZCentered:
×
390
                {
391
                    qreal distA = qAbs(posA.z() - centerCoord);
×
392
                    qreal distB = qAbs(posB.z() - centerCoord);
×
393
                    return (distA == distB) ? (posA.z() < posB.z()) : (distA < distB);
×
394
                }
395
                default: return false;
×
396
            }
397
        });
398

399
    foreach (quint32 id, fixtures)
×
400
    {
401
        Fixture *fixture = doc->fixture(id);
×
402
        if (fixture == NULL)
×
403
            continue;
×
404

405
        qreal factor = valueFactor(progress);
×
406
        int centeredSign = 1;
×
407

408
        if (fLayout == XCentered || fLayout == YCentered || fLayout == ZCentered)
×
409
        {
410
            QVector3D pos = mProps->fixturePosition(id, 0, 0);
×
411
            qreal coord = 0.0;
×
412

413
            switch (fLayout)
×
414
            {
415
                case XCentered: coord = pos.x(); break;
×
416
                case YCentered: coord = pos.y(); break;
×
417
                case ZCentered: coord = pos.z(); break;
×
418
                default: break;
×
419
            }
420

421
            if (coord > centerCoord)
×
422
                centeredSign = 1;
×
423
            else if (coord < centerCoord)
×
424
                centeredSign = -1;
×
425
            else
426
                centeredSign = 0;
×
427

428
            if (maxCenterDistance > 0.0)
×
429
            {
430
                qreal distance = qAbs(coord - centerCoord);
×
431
                factor = valueFactor(distance / maxCenterDistance);
×
432
            }
433
            else
434
            {
435
                factor = valueFactor(0.0);
×
436
            }
437
        }
438

439
        switch(type())
×
440
        {
441
            case Dimmer:
×
442
            {
443
                int dValue = value().toInt();
×
444
                quint32 masterIntensityChannel = fixture->type() == QLCFixtureDef::Dimmer ?
×
445
                            0 : fixture->masterIntensityChannel();
×
446

447
                if (fType != Flat)
×
448
                    dValue = int((qreal(intFanValue - dValue) * factor) + dValue);
×
449

450
                if (masterIntensityChannel != QLCChannel::invalid())
×
451
                    list << SceneValue(id, masterIntensityChannel, uchar(dValue));
×
452

453
                for (int i = 0; i < fixture->heads(); i++)
×
454
                {
455
                    quint32 headDimmerChannel = fixture->channelNumber(QLCChannel::Intensity, QLCChannel::MSB, i);
×
456
                    if (headDimmerChannel != QLCChannel::invalid())
×
457
                        list << SceneValue(id, headDimmerChannel, uchar(dValue));
×
458
                }
459
            }
460
            break;
×
461
            case Color:
×
462
            {
UNCOV
463
                QColor startColor = rgbValue();
×
464
                QColor col = startColor;
×
465
                QColor wauv = wauvValue();
×
466

467
                if (fType != Flat)
×
468
                {
469
                    QColor endColor = fanningValue().value<QColor>();
×
470
                    qreal rDelta = endColor.red() - startColor.red();
×
471
                    qreal gDelta = endColor.green() - startColor.green();
×
472
                    qreal bDelta = endColor.blue() - startColor.blue();
×
473
                    col.setRed(startColor.red() + qRound(rDelta * factor));
×
474
                    col.setGreen(startColor.green() + qRound(gDelta * factor));
×
475
                    col.setBlue(startColor.blue() + qRound(bDelta * factor));
×
476
                }
477

478
                for (int i = 0; i < fixture->heads(); i++)
×
479
                {
480
                    QVector<quint32> rgbCh = fixture->rgbChannels(i);
×
481
                    if (rgbCh.size() == 3)
×
482
                    {
483
                        list << SceneValue(id, rgbCh.at(0), uchar(col.red()));
×
484
                        list << SceneValue(id, rgbCh.at(1), uchar(col.green()));
×
485
                        list << SceneValue(id, rgbCh.at(2), uchar(col.blue()));
×
486
                    }
487
                    QVector<quint32> cmyCh = fixture->cmyChannels(i);
×
488
                    if (cmyCh.size() == 3)
×
489
                    {
490
                        list << SceneValue(id, cmyCh.at(0), uchar(col.cyan()));
×
491
                        list << SceneValue(id, cmyCh.at(1), uchar(col.magenta()));
×
492
                        list << SceneValue(id, cmyCh.at(2), uchar(col.yellow()));
×
493
                    }
494

495
                    if (wauv.isValid())
×
496
                    {
497
                        quint32 channel = fixture->channelNumber(QLCChannel::White, QLCChannel::MSB, i);
×
498
                        if (channel != QLCChannel::invalid())
×
499
                            list << SceneValue(id, channel, uchar(wauv.red()));
×
500
                        channel = fixture->channelNumber(QLCChannel::Amber, QLCChannel::MSB, i);
×
501
                        if (channel != QLCChannel::invalid())
×
502
                            list << SceneValue(id, channel, uchar(wauv.green()));
×
503
                        channel = fixture->channelNumber(QLCChannel::UV, QLCChannel::MSB, i);
×
504
                        if (channel != QLCChannel::invalid())
×
505
                            list << SceneValue(id, channel, uchar(wauv.blue()));
×
506
                    }
507
                }
×
508
            }
509
            break;
×
510
            case Pan:
×
511
            {
512
                int degrees = value().toInt();
×
513

514
                if (fType != Flat)
×
515
                {
516
                    int offset = int(qreal(intFanValue) * factor);
×
517
                    if (fLayout == XCentered || fLayout == YCentered || fLayout == ZCentered)
×
518
                        offset *= centeredSign;
×
519
                    degrees = int(qreal(degrees) + qreal(offset));
×
520
                }
521

522
                list << fixture->positionToValues(QLCChannel::Pan, degrees);
×
523
            }
524
            break;
×
525
            case Tilt:
×
526
            {
527
                int degrees = m_values.count() == 2 ? m_values.at(1).toInt() : value().toInt();
×
528

529
                if (fType != Flat)
×
530
                {
531
                    int offset = int(qreal(intFanValue) * factor);
×
532
                    if (fLayout == XCentered || fLayout == YCentered || fLayout == ZCentered)
×
533
                        offset *= centeredSign;
×
534
                    degrees = int(qreal(degrees) + qreal(offset));
×
535
                }
536

537
                list << fixture->positionToValues(QLCChannel::Tilt, degrees);
×
538
            }
539
            break;
×
540
            case PanTilt:
×
541
            {
542
                if (m_values.count() == 2)
×
543
                {
544
                    int panDegrees = m_values.at(0).toInt();
×
545
                    int tiltDegrees = m_values.at(1).toInt();
×
546

547
                    if (fType != Flat)
×
548
                    {
549
                        int offset = int(qreal(intFanValue) * factor);
×
550
                        if (fLayout == XCentered || fLayout == YCentered || fLayout == ZCentered)
×
551
                            offset *= centeredSign;
×
552
                        panDegrees = int((qreal(panDegrees) + qreal(offset)));
×
553
                        tiltDegrees = int((qreal(tiltDegrees) + qreal(offset)));
×
554
                    }
555

556
                    list << fixture->positionToValues(QLCChannel::Pan, panDegrees);
×
557
                    list << fixture->positionToValues(QLCChannel::Tilt, tiltDegrees);
×
558
                }
559
            }
560
            break;
×
561
            case Shutter:
×
562
            {
NEW
563
                if (m_values.count() < 2)
×
NEW
564
                    break;
×
565

NEW
566
                QLCCapability::Preset targetPreset = QLCCapability::Preset(m_values.at(0).toInt());
×
NEW
567
                int pct = m_values.at(1).toInt(); // 0-100
×
568

NEW
569
                for (int h = 0; h < fixture->heads(); h++)
×
570
                {
NEW
571
                    QLCFixtureHead head = fixture->head(h);
×
NEW
572
                    for (quint32 shCh : head.shutterChannels())
×
573
                    {
NEW
574
                        const QLCChannel *ch = fixture->channel(shCh);
×
NEW
575
                        if (ch == nullptr)
×
NEW
576
                            continue;
×
577

NEW
578
                        for (const QLCCapability *cap : ch->capabilities())
×
579
                        {
NEW
580
                            if (cap->preset() != targetPreset)
×
NEW
581
                                continue;
×
582

583
                            uchar dmx;
NEW
584
                            if (targetPreset == QLCCapability::ShutterOpen ||
×
585
                                targetPreset == QLCCapability::ShutterClose)
586
                            {
NEW
587
                                dmx = uchar((cap->min() + cap->max()) / 2);
×
588
                            }
589
                            else
590
                            {
NEW
591
                                dmx = uchar(cap->min() + (cap->max() - cap->min()) * pct / 100);
×
592
                            }
NEW
593
                            list << SceneValue(id, shCh, dmx);
×
NEW
594
                            break;
×
NEW
595
                        }
×
NEW
596
                    }
×
NEW
597
                }
×
598
            }
599
            break;
×
600
            case Gobo:
×
601
            {
602
                quint32 goboCh = fixture->channelNumber(QLCChannel::Gobo, QLCChannel::MSB);
×
603
                if (goboCh != QLCChannel::invalid())
×
604
                    list << SceneValue(id, goboCh, uchar(value().toUInt()));
×
605
            }
606
            break;
×
607
            case Zoom:
×
608
            {
609
                for (int i = 0; i < int(fixture->channels()); i++)
×
610
                {
611
                    const QLCChannel *ch = fixture->channel(quint32(i));
×
612
                    if (ch == nullptr)
×
613
                        continue;
×
614

615
                    if (ch->group() == QLCChannel::Beam &&
×
616
                        (ch->preset() == QLCChannel::BeamZoomSmallBig || ch->preset() == QLCChannel::BeamZoomBigSmall))
×
617
                    {
618
                        list << fixture->zoomToValues(value().toFloat(), false);
×
619
                        break;
×
620
                    }
621
                }
622
            }
623
            break;
×
NEW
624
            case Position3D:
×
625
            {
NEW
626
                if (m_values.count() < 3)
×
NEW
627
                    break;
×
628

NEW
629
                quint32 panMSB  = fixture->channelNumber(QLCChannel::Pan,  QLCChannel::MSB);
×
NEW
630
                quint32 tiltMSB = fixture->channelNumber(QLCChannel::Tilt, QLCChannel::MSB);
×
NEW
631
                if (panMSB == QLCChannel::invalid() && tiltMSB == QLCChannel::invalid())
×
NEW
632
                    break;
×
633

NEW
634
                QVector3D target(m_values.at(0).toFloat(),
×
NEW
635
                                 m_values.at(1).toFloat(),
×
NEW
636
                                 m_values.at(2).toFloat());
×
637

NEW
638
                QVector3D lightPos;
×
NEW
639
                QMatrix4x4 rotMatrix;
×
NEW
640
                MonitorProperties::fixtureBeamPosition(mProps, fixture, 0, lightPos, rotMatrix);
×
641

642
                // Build the same transposed lightMatrix used by FixtureUtils::lightProperties
NEW
643
                QVector4D xb = rotMatrix * QVector4D(1, 0, 0, 0);
×
NEW
644
                QVector4D yb = rotMatrix * QVector4D(0, 1, 0, 0);
×
NEW
645
                QVector4D zb = rotMatrix * QVector4D(0, 0, 1, 0);
×
NEW
646
                QVector3D xa = QVector3D(xb.x(), xb.y(), xb.z()).normalized();
×
NEW
647
                QVector3D ya = QVector3D(yb.x(), yb.y(), yb.z()).normalized();
×
NEW
648
                QVector3D za = QVector3D(zb.x(), zb.y(), zb.z()).normalized();
×
NEW
649
                QMatrix4x4 lightMatrix = QMatrix4x4(
×
650
                    xa.x(), xa.y(), xa.z(), 0,
651
                    ya.x(), ya.y(), ya.z(), 0,
652
                    za.x(), za.y(), za.z(), 0,
653
                    0, 0, 0, 1
NEW
654
                ).transposed();
×
655

656
                // Apply the same grid-offset adjustment as setPositionPickPoint
NEW
657
                float unitScale = mProps->gridUnits() == MonitorProperties::Meters ? 1.0f : 0.3048f;
×
NEW
658
                QVector3D gridMeters = mProps->gridSize() * unitScale;
×
NEW
659
                lightPos = QVector3D(lightPos.x() + gridMeters.x() / 2,
×
660
                                     lightPos.y(),
NEW
661
                                     lightPos.z() + gridMeters.z() / 2);
×
662

NEW
663
                QVector3D dir = (target - lightPos).normalized();
×
664

NEW
665
                quint32 itemFlags = mProps->fixtureFlags(id, 0, 0);
×
666

NEW
667
                if (panMSB != QLCChannel::invalid())
×
668
                {
NEW
669
                    QVector4D res = lightMatrix * QVector4D(1, 0, 0, 0);
×
NEW
670
                    QVector3D lxa = QVector3D(res.x(), res.y(), res.z());
×
NEW
671
                    res = lightMatrix * QVector4D(0, 0, 1, 0);
×
NEW
672
                    QVector3D lza = QVector3D(res.x(), res.y(), res.z());
×
673

NEW
674
                    QVector3D projDirX = QVector3D::dotProduct(dir, lxa) * lxa;
×
NEW
675
                    QVector3D projDirZ = QVector3D::dotProduct(dir, lza) * lza;
×
NEW
676
                    qreal b = projDirX.length();
×
NEW
677
                    qreal c = projDirZ.length();
×
NEW
678
                    qreal panDeg = qRadiansToDegrees(M_PI_2 - qAtan(c / b));
×
679

NEW
680
                    bool xLeft = QVector3D::dotProduct(projDirX, lxa) < 0.0;
×
NEW
681
                    bool zBack = QVector3D::dotProduct(projDirZ, lza) < 0.0;
×
NEW
682
                    if (xLeft && !zBack)       panDeg = 90.0  + (90.0 - panDeg);
×
NEW
683
                    else if (!xLeft && !zBack) panDeg = 180.0 + panDeg;
×
NEW
684
                    else if (!xLeft && zBack)  panDeg = 270.0 + (90.0 - panDeg);
×
685

NEW
686
                    if (itemFlags & MonitorProperties::InvertedPanFlag)
×
687
                    {
NEW
688
                        QLCPhysical phy = fixture->fixtureMode()->physical();
×
NEW
689
                        double maxPan = phy.focusPanMax() ? phy.focusPanMax() : 360.0;
×
NEW
690
                        panDeg = maxPan - panDeg;
×
NEW
691
                    }
×
692

NEW
693
                    list << fixture->positionToValues(QLCChannel::Pan, panDeg);
×
694
                }
695

NEW
696
                if (tiltMSB != QLCChannel::invalid())
×
697
                {
NEW
698
                    QVector4D res = lightMatrix * QVector4D(0, -1, 0, 0);
×
NEW
699
                    QVector3D lya = QVector3D(res.x(), res.y(), res.z());
×
NEW
700
                    qreal tiltDeg = qRadiansToDegrees(qAcos(QVector3D::dotProduct(dir, lya)));
×
701

NEW
702
                    QLCPhysical phy = fixture->fixtureMode()->physical();
×
NEW
703
                    tiltDeg = qBound(0.0, tiltDeg, (qreal)(phy.focusTiltMax() / 2));
×
704

NEW
705
                    if (itemFlags & MonitorProperties::InvertedTiltFlag)
×
NEW
706
                        tiltDeg = phy.focusTiltMax() / 2 + tiltDeg;
×
707
                    else
NEW
708
                        tiltDeg = phy.focusTiltMax() / 2 - tiltDeg;
×
709

NEW
710
                    list << fixture->positionToValues(QLCChannel::Tilt, tiltDeg);
×
NEW
711
                }
×
712
            }
NEW
713
            break;
×
714
            case Undefined:
×
715
            break;
×
716
        }
717

718
        progress += (1.0 / qreal(fxCount - 1));
×
719
    }
×
720

721
    return list;
×
722
}
×
723

724
QList<SceneValue> QLCPalette::valuesFromFixtureGroups(Doc *doc, QList<quint32> groups) const
×
725
{
726
    QList<quint32> fixturesList;
×
727

728
    foreach (quint32 id, groups)
×
729
    {
730
        FixtureGroup *group = doc->fixtureGroup(id);
×
731
        if (group == NULL)
×
732
            continue;
×
733

734
        fixturesList.append(group->fixtureList());
×
735
    }
×
736

737
    return valuesFromFixtures(doc, fixturesList);
×
738
}
×
739

740
qreal QLCPalette::valueFactor(qreal progress) const
×
741
{
742
    qreal factor = 1.0;
×
743
    qreal normalizedAmount = qreal(m_fanningAmount) / 100.0;
×
744

745
    switch (m_fanningType)
×
746
    {
747
        case Flat:
×
748
            // nothing to do. Factor is always 1.0
749
        break;
×
750
        case Linear:
×
751
        {
752
            if (normalizedAmount < 1.0)
×
753
            {
754
                if (progress > normalizedAmount)
×
755
                    factor = 1.0;
×
756
                else
757
                    factor = progress * normalizedAmount;
×
758
            }
759
            else if (normalizedAmount > 1.0)
×
760
            {
761
                factor = progress / normalizedAmount;
×
762
            }
763
            else
764
            {
765
                factor = progress;
×
766
            }
767
        }
768
        break;
×
769
        case Sine:
×
770
        {
771
            qreal degrees = (progress * 360.0) + 270.0;
×
772
            factor = (qSin(normalizedAmount * qDegreesToRadians(degrees)) + 1.0) / 2.0;
×
773
        }
774
        break;
×
775
        case Square:
×
776
        {
777
            factor = qSin(normalizedAmount * qDegreesToRadians(progress * 360.0)) < 0 ? 1 : 0;
×
778
        }
779
        break;
×
780
        case Saw:
×
781
        break;
×
782
    }
783

784
    return factor;
×
785
}
786

787
/************************************************************************
788
 * Fanning
789
 ************************************************************************/
790

791
QLCPalette::FanningType QLCPalette::fanningType() const
2✔
792
{
793
    return m_fanningType;
2✔
794
}
795

796
void QLCPalette::setFanningType(QLCPalette::FanningType type)
1✔
797
{
798
    if (type == m_fanningType)
1✔
799
        return;
×
800

801
    m_fanningType = type;
1✔
802

803
    emit fanningTypeChanged();
1✔
804
}
805

806
QString QLCPalette::fanningTypeToString(QLCPalette::FanningType type)
5✔
807
{
808
    switch (type)
5✔
809
    {
810
        case Flat:      return "Flat";
1✔
811
        case Linear:    return "Linear";
1✔
812
        case Sine:      return "Sine";
1✔
813
        case Square:    return "Square";
1✔
814
        case Saw:       return "Saw";
1✔
815
    }
816

817
    return "";
×
818
}
819

820
QLCPalette::FanningType QLCPalette::stringToFanningType(const QString &str)
7✔
821
{
822
    if (str == "Flat")
7✔
823
        return Flat;
1✔
824
    else if (str == "Linear")
6✔
825
        return Linear;
2✔
826
    else if (str == "Sine")
4✔
827
        return Sine;
1✔
828
    else if (str == "Square")
3✔
829
        return Square;
1✔
830
    else if (str == "Saw")
2✔
831
        return Saw;
1✔
832

833
    return Flat;
1✔
834
}
835

836
QLCPalette::FanningLayout QLCPalette::fanningLayout() const
2✔
837
{
838
    return m_fanningLayout;
2✔
839
}
840

841
void QLCPalette::setFanningLayout(QLCPalette::FanningLayout layout)
1✔
842
{
843
    if (layout == m_fanningLayout)
1✔
844
        return;
1✔
845

846
    m_fanningLayout = layout;
×
847

848
    emit fanningLayoutChanged();
×
849
}
850

851
QString QLCPalette::fanningLayoutToString(QLCPalette::FanningLayout layout)
9✔
852
{
853
    switch (layout)
9✔
854
    {
855
        case XAscending:    return "XAscending";
1✔
856
        case XDescending:   return "XDescending";
1✔
857
        case XCentered:     return "XCentered";
1✔
858
        case YAscending:    return "YAscending";
1✔
859
        case YDescending:   return "YDescending";
1✔
860
        case YCentered:     return "YCentered";
1✔
861
        case ZAscending:    return "ZAscending";
1✔
862
        case ZDescending:   return "ZDescending";
1✔
863
        case ZCentered:     return "ZCentered";
1✔
864
    }
865

866
    return "";
×
867
}
868

869
QLCPalette::FanningLayout QLCPalette::stringToFanningLayout(const QString &str)
11✔
870
{
871
    if (str == "XAscending")
11✔
872
        return XAscending;
1✔
873
    else if (str == "XDescending")
10✔
874
        return XDescending;
1✔
875
    else if (str == "XCentered")
9✔
876
        return XCentered;
1✔
877
    else     if (str == "YAscending")
8✔
878
        return YAscending;
1✔
879
    else if (str == "YDescending")
7✔
880
        return YDescending;
1✔
881
    else if (str == "YCentered")
6✔
882
        return YCentered;
1✔
883
    else if (str == "ZAscending")
5✔
884
        return ZAscending;
1✔
885
    else if (str == "ZDescending")
4✔
886
        return ZDescending;
1✔
887
    else if (str == "ZCentered")
3✔
888
        return ZCentered;
1✔
889

890
    return XAscending;
2✔
891
}
892

893
int QLCPalette::fanningAmount() const
3✔
894
{
895
    return m_fanningAmount;
3✔
896
}
897

898
void QLCPalette::setFanningAmount(int amount)
2✔
899
{
900
    if (amount == m_fanningAmount)
2✔
901
        return;
×
902

903
    m_fanningAmount = amount;
2✔
904

905
    emit fanningAmountChanged();
2✔
906
}
907

908
QVariant QLCPalette::fanningValue() const
2✔
909
{
910
    return m_fanningValue;
2✔
911
}
912

913
void QLCPalette::setFanningValue(QVariant value)
1✔
914
{
915
    if (value == m_fanningValue)
1✔
916
        return;
×
917

918
    m_fanningValue = value;
1✔
919

920
    emit fanningValueChanged();
1✔
921
}
922

923
/************************************************************************
924
 * Color helpers
925
 ************************************************************************/
926

927
QString QLCPalette::colorToString(QColor rgb, QColor wauv)
2✔
928
{
929
    QString final = rgb.name();
2✔
930
    final.append(wauv.name().right(6));
2✔
931
    return final;
2✔
932
}
×
933

934
bool QLCPalette::stringToColor(const QString& str, QColor &rgb, QColor &wauv)
3✔
935
{
936
    // string must be like #rrggbb or #rrggbbwwaauv
937
    if (str.length() != 7 && str.length() != 13)
3✔
938
        return false;
1✔
939

940
    rgb = QColor(str.left(7));
2✔
941

942
    if (str.length() == 13)
2✔
943
        wauv = QColor("#" + str.right(6));
2✔
944
    else
945
        wauv = QColor();
×
946

947
    return true;
2✔
948
}
949

950
/************************************************************************
951
 * Load & Save
952
 ************************************************************************/
953

954
bool QLCPalette::loader(QXmlStreamReader &xmlDoc, Doc *doc)
×
955
{
956
    QLCPalette *palette = new QLCPalette(Dimmer, doc);
×
957
    Q_ASSERT(palette != NULL);
×
958

959
    if (palette->loadXML(xmlDoc) == true)
×
960
    {
961
        doc->addPalette(palette, palette->id());
×
962
    }
963
    else
964
    {
965
        qWarning() << Q_FUNC_INFO << "QLCPalette" << palette->name() << "cannot be loaded.";
×
966
        delete palette;
×
967
        return false;
×
968
    }
969

970
    return true;
×
971
}
972

973
bool QLCPalette::loadXML(QXmlStreamReader &doc)
2✔
974
{
975
    if (doc.name() != KXMLQLCPalette)
2✔
976
    {
977
        qWarning() << Q_FUNC_INFO << "Palette node not found";
1✔
978
        return false;
1✔
979
    }
980

981
    QXmlStreamAttributes attrs = doc.attributes();
1✔
982

983
    bool ok = false;
1✔
984
    quint32 id = attrs.value(KXMLQLCPaletteID).toString().toUInt(&ok);
1✔
985
    if (ok == false)
1✔
986
    {
987
        qWarning() << "Invalid Palette ID:" << attrs.value(KXMLQLCPaletteID).toString();
×
988
        return false;
×
989
    }
990

991
    setID(id);
1✔
992

993
    if (attrs.hasAttribute(KXMLQLCPaletteType) == false)
1✔
994
    {
995
        qWarning() << "Palette type not found!";
×
996
        return false;
×
997
    }
998

999
    m_type = stringToType(attrs.value(KXMLQLCPaletteType).toString());
1✔
1000

1001
    if (attrs.hasAttribute(KXMLQLCPaletteName))
1✔
1002
        setName(attrs.value(KXMLQLCPaletteName).toString());
1✔
1003

1004
    if (attrs.hasAttribute(KXMLQLCPaletteValue))
1✔
1005
    {
1006
        QString strVal = attrs.value(KXMLQLCPaletteValue).toString();
1✔
1007
        switch (m_type)
1✔
1008
        {
1009
            case Pan:
×
1010
            case Tilt:
1011
                setValue(strVal.toInt());
×
1012
            break;
×
1013
            case Color:
1✔
1014
                setValue(strVal);
1✔
1015
            break;
1✔
1016
            case PanTilt:
×
1017
            {
1018
                QStringList posList = strVal.split(",");
×
1019
                if (posList.count() == 2)
×
1020
                    setValue(posList.at(0).toInt(), posList.at(1).toInt());
×
1021
            }
×
1022
            break;
×
NEW
1023
            case Position3D:
×
1024
            {
NEW
1025
                QStringList posList = strVal.split(",");
×
NEW
1026
                if (posList.count() == 3)
×
NEW
1027
                    setValue(posList.at(0).toFloat(), posList.at(1).toFloat(), posList.at(2).toFloat());
×
NEW
1028
            }
×
NEW
1029
            break;
×
UNCOV
1030
            case Dimmer:
×
1031
            case Zoom:
1032
                setValue(strVal.toFloat());
×
1033
            break;
×
NEW
1034
            case Shutter:
×
1035
            {
NEW
1036
                QStringList parts = strVal.split(",");
×
NEW
1037
                if (parts.count() == 2)
×
NEW
1038
                    setValue(parts.at(0).toInt(), parts.at(1).toInt());
×
NEW
1039
            }
×
NEW
1040
            break;
×
1041
            case Gobo:      break;
×
1042
            case Undefined: break;
×
1043
        }
1044
    }
1✔
1045

1046
    if (attrs.hasAttribute(KXMLQLCPaletteFanning))
1✔
1047
    {
1048
        setFanningType(stringToFanningType(attrs.value(KXMLQLCPaletteFanning).toString()));
1✔
1049

1050
        if (attrs.hasAttribute(KXMLQLCPaletteFanLayout))
1✔
1051
            setFanningLayout(stringToFanningLayout(attrs.value(KXMLQLCPaletteFanLayout).toString()));
1✔
1052

1053
        if (attrs.hasAttribute(KXMLQLCPaletteFanAmount))
1✔
1054
            setFanningAmount(attrs.value(KXMLQLCPaletteFanAmount).toInt());
1✔
1055

1056
        if (attrs.hasAttribute(KXMLQLCPaletteFanValue))
1✔
1057
        {
1058
            QString strVal = attrs.value(KXMLQLCPaletteFanValue).toString();
1✔
1059
            switch (m_type)
1✔
1060
            {
1061
                case Dimmer:
×
1062
                case Pan:
1063
                case Tilt:
1064
                case PanTilt:
1065
                case Zoom:
1066
                    setFanningValue(strVal.toInt());
×
1067
                break;
×
1068
                case Color:
1✔
1069
                    setFanningValue(strVal);
1✔
1070
                break;
1✔
NEW
1071
                case Position3D:
×
1072
                case Shutter:   break;
×
1073
                case Gobo:      break;
×
1074
                case Undefined: break;
×
1075
            }
1076
        }
1✔
1077
    }
1078

1079
    return true;
1✔
1080
}
1✔
1081

1082
bool QLCPalette::saveXML(QXmlStreamWriter *doc) const
1✔
1083
{
1084
    Q_ASSERT(doc != NULL);
1✔
1085

1086
    if (m_values.isEmpty())
1✔
1087
    {
1088
        qWarning() << "Unable to save a Palette without value!";
×
1089
        return false;
×
1090
    }
1091

1092
    /* write a Palette entry */
1093
    doc->writeStartElement(KXMLQLCPalette);
2✔
1094
    doc->writeAttribute(KXMLQLCPaletteID, QString::number(this->id()));
2✔
1095
    doc->writeAttribute(KXMLQLCPaletteType, typeToString(m_type));
2✔
1096
    doc->writeAttribute(KXMLQLCPaletteName, this->name());
2✔
1097

1098
    /* write value */
1099
    switch (m_type)
1✔
1100
    {
1101
        case Dimmer:
×
1102
        case Pan:
1103
        case Tilt:
1104
        case Zoom:
1105
        case Color:
1106
            doc->writeAttribute(KXMLQLCPaletteValue, value().toString());
×
1107
        break;
×
1108
        case PanTilt:
1✔
1109
            doc->writeAttribute(KXMLQLCPaletteValue,
2✔
1110
                                QString("%1,%2").arg(m_values.at(0).toInt()).arg(m_values.at(1).toInt()));
2✔
1111
        break;
1✔
NEW
1112
        case Position3D:
×
NEW
1113
            if (m_values.count() == 3)
×
NEW
1114
                doc->writeAttribute(KXMLQLCPaletteValue,
×
NEW
1115
                                    QString("%1,%2,%3").arg(m_values.at(0).toFloat())
×
NEW
1116
                                                       .arg(m_values.at(1).toFloat())
×
NEW
1117
                                                       .arg(m_values.at(2).toFloat()));
×
NEW
1118
        break;
×
NEW
1119
        case Shutter:
×
NEW
1120
            if (m_values.count() == 2)
×
NEW
1121
                doc->writeAttribute(KXMLQLCPaletteValue,
×
NEW
1122
                                    QString("%1,%2").arg(m_values.at(0).toInt()).arg(m_values.at(1).toInt()));
×
NEW
1123
        break;
×
1124
        case Gobo:      break;
×
1125
        case Undefined: break;
×
1126
    }
1127

1128
    /* write fanning */
1129
    if (m_fanningType != Flat)
1✔
1130
    {
1131
        doc->writeAttribute(KXMLQLCPaletteFanning, fanningTypeToString(m_fanningType));
×
1132
        doc->writeAttribute(KXMLQLCPaletteFanLayout, fanningLayoutToString(m_fanningLayout));
×
1133
        doc->writeAttribute(KXMLQLCPaletteFanAmount, QString::number(m_fanningAmount));
×
1134
        doc->writeAttribute(KXMLQLCPaletteFanValue, fanningValue().toString());
×
1135
    }
1136

1137
    doc->writeEndElement();
1✔
1138

1139
    return true;
1✔
1140
}
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