• 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

73.74
/engine/src/fixture.cpp
1
/*
2
  Q Light Controller Plus
3
  fixture.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 <QString>
24
#include <QtMath>
25
#include <QDebug>
26

27
#include "qlcfixturedefcache.h"
28
#include "channelmodifier.h"
29
#include "qlcfixturemode.h"
30
#include "qlcfixturehead.h"
31
#include "qlcfixturedef.h"
32
#include "qlccapability.h"
33
#include "qlcchannel.h"
34
#include "qlcfile.h"
35

36
#include "fixture.h"
37
#include "doc.h"
38

39
/*****************************************************************************
40
 * Initialization
41
 *****************************************************************************/
42

43
Fixture::Fixture(QObject* parent) : QObject(parent)
16,834✔
44
{
45
    m_id = Fixture::invalidId();
16,834✔
46

47
    m_address = 0;
16,834✔
48
    m_channels = 0;
16,834✔
49
    m_crossUniverse = false;
16,834✔
50

51
    m_fixtureDef = NULL;
16,834✔
52
    m_fixtureMode = NULL;
16,834✔
53
}
16,834✔
54

55
Fixture::~Fixture()
33,646✔
56
{
57
}
33,646✔
58

59
bool Fixture::operator<(const Fixture& fxi)
12✔
60
{
61
    if (m_address < fxi.m_address)
12✔
62
        return true;
5✔
63
    else
64
        return false;
7✔
65
}
66

67
/*****************************************************************************
68
 * Fixture ID
69
 *****************************************************************************/
70

71
void Fixture::setID(quint32 id)
16,817✔
72
{
73
    m_id = id;
16,817✔
74
    emit changed(m_id);
16,817✔
75
}
16,817✔
76

77
quint32 Fixture::id() const
18,151✔
78
{
79
    return m_id;
18,151✔
80
}
81

82
quint32 Fixture::invalidId()
831,758✔
83
{
84
    return UINT_MAX;
831,758✔
85
}
86

87
/*****************************************************************************
88
 * Name
89
 *****************************************************************************/
90

91
void Fixture::setName(const QString& name)
83✔
92
{
93
    m_name = name;
83✔
94
    emit changed(m_id);
83✔
95
}
83✔
96

97
QString Fixture::name() const
36✔
98
{
99
    return m_name;
36✔
100
}
101

102
/*****************************************************************************
103
 * Fixture type
104
 *****************************************************************************/
105

106
QString Fixture::typeString()
12✔
107
{
108
    if (m_fixtureDef != NULL)
12✔
109
        return m_fixtureDef->typeToString(m_fixtureDef->type());
11✔
110
    else
111
        return QString(KXMLFixtureDimmer);
1✔
112
}
113

114
QLCFixtureDef::FixtureType Fixture::type() const
25✔
115
{
116
    if (m_fixtureDef != NULL)
25✔
117
        return m_fixtureDef->type();
23✔
118
    else
119
        return QLCFixtureDef::Dimmer;
2✔
120
}
121

122
/*****************************************************************************
123
 * Universe
124
 *****************************************************************************/
125

126
void Fixture::setUniverse(quint32 universe)
95✔
127
{
128
    /* The universe part is stored in the highest 7 bits */
129
    m_address = (m_address & 0x01FF) | (universe << 9);
95✔
130

131
    emit changed(m_id);
95✔
132
}
95✔
133

134
quint32 Fixture::universe() const
795,785✔
135
{
136
    /* The universe part is stored in the highest 7 bits */
137
    return (m_address >> 9);
795,785✔
138
}
139

140
void Fixture::setCrossUniverse(bool enable)
×
141
{
142
    m_crossUniverse = enable;
×
143
}
×
144

145
bool Fixture::crossUniverse() const
18✔
146
{
147
    return m_crossUniverse;
18✔
148
}
149

150
/*****************************************************************************
151
 * Address
152
 *****************************************************************************/
153

154
void Fixture::setAddress(quint32 address)
383✔
155
{
156
    /* Don't allow more than 512 channels per universe */
157
    if (address > 511)
383✔
158
        return;
3✔
159

160
    /* The address part is stored in the lowest 9 bits */
161
    m_address = (m_address & 0xFE00) | (address & 0x01FF);
380✔
162

163
    emit changed(m_id);
380✔
164
}
165

166
quint32 Fixture::address() const
795,758✔
167
{
168
    /* The address part is stored in the lowest 9 bits */
169
    return (m_address & 0x01FF);
795,758✔
170
}
171

172
quint32 Fixture::universeAddress() const
7,811✔
173
{
174
    return m_address;
7,811✔
175
}
176

177
/*****************************************************************************
178
 * Channels
179
 *****************************************************************************/
180

181
void Fixture::setChannels(quint32 channels)
234✔
182
{
183
    if (m_fixtureDef == NULL && m_fixtureMode == NULL)
234✔
184
    {
185
        QLCFixtureDef *fixtureDef = genericDimmerDef(channels);
234✔
186
        QLCFixtureMode *fixtureMode = genericDimmerMode(fixtureDef, channels);
234✔
187
        setFixtureDefinition(fixtureDef, fixtureMode);
234✔
188
    }
234✔
189
    else
190
    {
191
        if ((quint32)m_fixtureMode->channels().size() != channels)
×
192
        {
193
            QLCFixtureDef *fixtureDef = genericDimmerDef(channels);
×
194
            QLCFixtureMode *fixtureMode = genericDimmerMode(fixtureDef, channels);
×
195
            setFixtureDefinition(fixtureDef, fixtureMode);
×
196
        }
197
    }
198

199
    m_channels = channels;
234✔
200

201
    emit changed(m_id);
234✔
202
}
234✔
203

204
quint32 Fixture::channels() const
10,422✔
205
{
206
    if (m_fixtureDef != NULL && m_fixtureMode != NULL)
10,422✔
207
        return m_fixtureMode->channels().size();
10,402✔
208
    else
209
        return m_channels;
20✔
210
}
211

212
const QLCChannel* Fixture::channel(quint32 channel) const
798,020✔
213
{
214
    if (m_fixtureDef != NULL && m_fixtureMode != NULL)
798,020✔
215
        return m_fixtureMode->channel(channel);
798,017✔
216
    else
217
        return NULL;
3✔
218
}
219

220
quint32 Fixture::channelAddress(quint32 channel) const
7✔
221
{
222
    if (channel < channels())
7✔
223
        return universeAddress() + channel;
5✔
224
    else
225
        return QLCChannel::invalid();
2✔
226
}
227

228
quint32 Fixture::channel(QLCChannel::Group group,
4✔
229
    QLCChannel::PrimaryColour color) const
230
{
231
    if (m_fixtureDef == NULL && m_fixtureMode == NULL)
4✔
232
    {
233
        /* There's just one generic channel object with "Intensity" as
234
           its name that is the same for all channel numbers. So
235
           there's really no point in returning 0 or any otherwise
236
           valid channel number here. Which one of them the user would
237
           want to get? */
238
        return QLCChannel::invalid();
×
239
    }
240
    else
241
    {
242
        /* Search for the channel name (and group) from our list */
243
        for (quint32 i = 0; i < quint32(m_fixtureMode->channels().size()); i++)
21✔
244
        {
245
            const QLCChannel* ch = m_fixtureMode->channel(i);
21✔
246
            Q_ASSERT(ch != NULL);
21✔
247

248
            if (group != QLCChannel::NoGroup && ch->group() != group)
21✔
249
            {
250
                /* Given group name doesn't match */
251
                continue;
15✔
252
            }
253
            else if (group != QLCChannel::Intensity || ch->colour() == color)
6✔
254
            {
255
                /* Found the channel */
256
                return i;
4✔
257
            }
258
        }
259

260
        /* Went thru all channels but a match was not found */
261
        return QLCChannel::invalid();
×
262
    }
263
}
264

265
QSet <quint32> Fixture::channels(QLCChannel::Group group, QLCChannel::PrimaryColour color) const
4✔
266
{
267
    QSet <quint32> set;
4✔
268
    if (m_fixtureDef != NULL && m_fixtureMode != NULL)
4✔
269
    {
270
        /* Search for the channel name (and group) from our list */
271
        for (quint32 i = 0; i < quint32(m_fixtureMode->channels().size()); i++)
148✔
272
        {
273
            const QLCChannel* ch = m_fixtureMode->channel(i);
144✔
274
            Q_ASSERT(ch != NULL);
144✔
275

276
            if (group != QLCChannel::NoGroup && ch->group() != group)
144✔
277
            {
278
                /* Given group name doesn't match */
279
                continue;
48✔
280
            }
281
            else if (group != QLCChannel::Intensity || ch->colour() == color)
96✔
282
            {
283
                /* Found the channel */
284
                set << i;
24✔
285
            }
286
        }
287
    }
288

289
    return set;
4✔
290
}
×
291

292
quint32 Fixture::channelNumber(int type, int controlByte, int head) const
310✔
293
{
294
    if (m_fixtureMode == NULL || head < 0 || head >= m_fixtureMode->heads().size())
310✔
295
        return QLCChannel::invalid();
4✔
296

297
    return m_fixtureMode->heads().at(head).channelNumber(type, controlByte);
306✔
298
}
299

300
quint32 Fixture::masterIntensityChannel() const
40✔
301
{
302
    if (m_fixtureMode == NULL)
40✔
303
        return QLCChannel::invalid();
1✔
304

305
    return m_fixtureMode->masterIntensityChannel();
39✔
306
}
307

308
QVector <quint32> Fixture::rgbChannels(int head) const
15✔
309
{
310
    if (m_fixtureMode == NULL || head < 0 || head >= m_fixtureMode->heads().size())
15✔
311
        return QVector <quint32> ();
×
312

313
    return m_fixtureMode->heads().at(head).rgbChannels();
15✔
314
}
315

316
QVector <quint32> Fixture::cmyChannels(int head) const
1✔
317
{
318
    if (m_fixtureMode == NULL || head < 0 || head >= m_fixtureMode->heads().size())
1✔
319
        return QVector <quint32> ();
×
320

321
    return m_fixtureMode->heads().at(head).cmyChannels();
1✔
322
}
323

324
QList<SceneValue> Fixture::positionToValues(int type, float degrees, bool isRelative)
2✔
325
{
326
    QList<SceneValue> posList;
2✔
327
    // cache a list of channels processed, to avoid duplicates
328
    QList<quint32> chDone;
2✔
329

330
    if (m_fixtureMode == NULL)
2✔
331
        return posList;
×
332

333
    QLCPhysical phy = fixtureMode()->physical();
2✔
334
    qreal headDegrees = degrees, maxDegrees;
2✔
335

336
    if (type == QLCChannel::Pan)
2✔
337
    {
338
        maxDegrees = phy.focusPanMax();
1✔
339
        if (maxDegrees == 0) maxDegrees = 360;
1✔
340

341
        for (int i = 0; i < heads(); i++)
2✔
342
        {
343
            quint32 panMSB = channelNumber(QLCChannel::Pan, QLCChannel::MSB, i);
1✔
344
            if (panMSB == QLCChannel::invalid() || chDone.contains(panMSB))
1✔
345
                continue;
×
346
            quint32 panLSB = channelNumber(QLCChannel::Pan, QLCChannel::LSB, i);
1✔
347

348
            if (isRelative)
1✔
349
            {
350
                // degrees is a relative value upon the current value.
351
                // Recalculate absolute degrees here
352
                float chDegrees = (maxDegrees / 256.0) * channelValueAt(panMSB);
×
353
                headDegrees = qBound(0.0, chDegrees + headDegrees, maxDegrees);
×
354

355
                if (panLSB != QLCChannel::invalid())
×
356
                {
357
                    chDegrees = (maxDegrees / 65536.0) * channelValueAt(panLSB);
×
358
                    headDegrees = qBound(0.0, chDegrees + headDegrees, maxDegrees);
×
359
                }
360
            }
361

362
            quint16 degToDmx = (headDegrees * 65535.0) / maxDegrees;
1✔
363
            posList.append(SceneValue(id(), panMSB, static_cast<uchar>(degToDmx >> 8)));
1✔
364

365
            if (panLSB != QLCChannel::invalid())
1✔
366
                posList.append(SceneValue(id(), panLSB, static_cast<uchar>(degToDmx & 0x00FF)));
1✔
367

368
            qDebug() << "[positionToValues] Pan MSB idx:" << panMSB << "LSB idx:" << panLSB << "value" << QString::number(degToDmx, 16);
1✔
369

370
            chDone.append(panMSB);
1✔
371
        }
372
    }
373
    else if (type == QLCChannel::Tilt)
1✔
374
    {
375
        maxDegrees = phy.focusTiltMax();
1✔
376
        if (maxDegrees == 0) maxDegrees = 270;
1✔
377

378
        for (int i = 0; i < heads(); i++)
2✔
379
        {
380
            quint32 tiltMSB = channelNumber(QLCChannel::Tilt, QLCChannel::MSB, i);
1✔
381
            if (tiltMSB == QLCChannel::invalid() || chDone.contains(tiltMSB))
1✔
382
                continue;
×
383
            quint32 tiltLSB = channelNumber(QLCChannel::Tilt, QLCChannel::LSB, i);
1✔
384

385
            if (isRelative)
1✔
386
            {
387
                // degrees is a relative value upon the current value.
388
                // Recalculate absolute degrees here
389
                float chDegrees = (maxDegrees / 256.0) * channelValueAt(tiltMSB);
×
390
                headDegrees = qBound(0.0, chDegrees + headDegrees, maxDegrees);
×
391

392
                if (tiltLSB != QLCChannel::invalid())
×
393
                {
394
                    chDegrees = (maxDegrees / 65536.0) * channelValueAt(tiltLSB);
×
395
                    headDegrees = qBound(0.0, chDegrees + headDegrees, maxDegrees);
×
396
                }
397
            }
398

399
            quint16 degToDmx = (headDegrees * 65535.0) / maxDegrees;
1✔
400
            posList.append(SceneValue(id(), tiltMSB, static_cast<uchar>(degToDmx >> 8)));
1✔
401

402
            if (tiltLSB != QLCChannel::invalid())
1✔
403
                posList.append(SceneValue(id(), tiltLSB, static_cast<uchar>(degToDmx & 0x00FF)));
1✔
404

405
            qDebug() << "[positionToValues] Tilt MSB idx:" << tiltMSB << "LSB idx:" << tiltLSB << "value" << QString::number(degToDmx, 16);
1✔
406

407
            chDone.append(tiltMSB);
1✔
408
        }
409

410
    }
411

412
    return posList;
2✔
413
}
2✔
414

415
QList<SceneValue> Fixture::zoomToValues(float degrees, bool isRelative)
×
416
{
417
    QList<SceneValue> chList;
×
418

419
    if (m_fixtureMode == NULL)
×
420
        return chList;
×
421

422
    QLCPhysical phy = fixtureMode()->physical();
×
423
    if (!isRelative)
×
424
        degrees = qBound(float(phy.lensDegreesMin()), degrees, float(phy.lensDegreesMax()));
×
425

426
    float deltaDegrees = phy.lensDegreesMax() - phy.lensDegreesMin();
×
427
    // delta : 0xFFFF = deg : x
428
    quint16 degToDmx = ((qAbs(degrees) - (isRelative ? 0 : float(phy.lensDegreesMin()))) * 65535.0) / deltaDegrees;
×
429
    qDebug() << "Degrees" << degrees << "DMX" << QString::number(degToDmx, 16);
×
430

431
    for (quint32 i = 0; i < quint32(m_fixtureMode->channels().size()); i++)
×
432
    {
433
        const QLCChannel *ch = m_fixtureMode->channel(i);
×
434

435
        if (ch->group() != QLCChannel::Beam)
×
436
            continue;
×
437

438
        if (ch->preset() != QLCChannel::BeamZoomBigSmall &&
×
439
            ch->preset() != QLCChannel::BeamZoomSmallBig &&
×
440
            ch->preset() != QLCChannel::BeamZoomFine)
×
441
            continue;
×
442

443
        if (isRelative)
×
444
        {
445
            // degrees is a relative value upon the current value.
446
            // Recalculate absolute degrees here
447
            qreal divider = ch->controlByte() == QLCChannel::MSB ? 256.0 : 65536.0;
×
448
            uchar currDmxValue = ch->preset() == QLCChannel::BeamZoomBigSmall ? UCHAR_MAX - channelValueAt(i) : channelValueAt(i);
×
449
            float chDegrees = float((phy.lensDegreesMax() - phy.lensDegreesMin()) / divider) * float(currDmxValue);
×
450

451
            qDebug() << "Relative channel degrees:" << chDegrees << "MSB?" << ch->controlByte();
×
452

453
            quint16 currDmxVal = (chDegrees * 65535.0) / deltaDegrees;
×
454
            if (degrees > 0)
×
455
                degToDmx = qBound(0, currDmxVal + degToDmx, 65535);
×
456
            else
457
                degToDmx = qBound(0, currDmxVal - degToDmx, 65535);
×
458
        }
459

460
        if (ch->controlByte() == QLCChannel::MSB)
×
461
        {
462
            if (ch->preset() == QLCChannel::BeamZoomBigSmall)
×
463
                chList.append(SceneValue(id(), i, static_cast<uchar>(UCHAR_MAX - (degToDmx >> 8))));
×
464
            else
465
                chList.append(SceneValue(id(), i, static_cast<uchar>(degToDmx >> 8)));
×
466
        }
467
        else if (ch->controlByte() == QLCChannel::LSB)
×
468
        {
469
            chList.append(SceneValue(id(), i, static_cast<uchar>(degToDmx & 0x00FF)));
×
470
        }
471
    }
472

473
    return chList;
×
474
}
×
475

476
void Fixture::setExcludeFadeChannels(QList<int> indices)
11✔
477
{
478
    if (indices.count() > (int)channels())
11✔
479
        return;
×
480

481
    m_excludeFadeIndices = indices;
11✔
482
}
483

484
QList<int> Fixture::excludeFadeChannels()
2✔
485
{
486
    return m_excludeFadeIndices;
2✔
487
}
488

489
void Fixture::setChannelCanFade(int idx, bool canFade)
1✔
490
{
491
    if (canFade == false && m_excludeFadeIndices.contains(idx) == false)
1✔
492
    {
493
        m_excludeFadeIndices.append(idx);
1✔
494
        std::sort(m_excludeFadeIndices.begin(), m_excludeFadeIndices.end());
1✔
495
    }
496
    else if (canFade == true && m_excludeFadeIndices.contains(idx) == true)
×
497
    {
498
        m_excludeFadeIndices.removeOne(idx);
×
499
    }
500
}
1✔
501

502
bool Fixture::channelCanFade(int index)
795,213✔
503
{
504
    if (m_excludeFadeIndices.contains(index))
795,213✔
505
        return false;
1✔
506

507
    return true;
795,212✔
508
}
509

510
void Fixture::setForcedHTPChannels(QList<int> indices)
13✔
511
{
512
    if (indices.count() > (int)channels())
13✔
513
        return;
×
514
    m_forcedHTPIndices = indices;
13✔
515
    // cross check: if a channel is forced HTP it must be removed from
516
    // the forced LTP list (if present)
517
    for (int i = 0; i < m_forcedHTPIndices.count(); i++)
16✔
518
        m_forcedLTPIndices.removeAll(m_forcedHTPIndices.at(i));
3✔
519
}
520

521
QList<int> Fixture::forcedHTPChannels()
795,627✔
522
{
523
    return m_forcedHTPIndices;
795,627✔
524
}
525

526
void Fixture::setForcedLTPChannels(QList<int> indices)
13✔
527
{
528
    if (indices.count() > (int)channels())
13✔
529
        return;
×
530
    m_forcedLTPIndices = indices;
13✔
531
    // cross check: if a channel is forced LTP it must be removed from
532
    // the forced HTP list (if present)
533
    for (int i = 0; i < m_forcedLTPIndices.count(); i++)
15✔
534
        m_forcedHTPIndices.removeAll(m_forcedLTPIndices.at(i));
2✔
535
}
536

537
QList<int> Fixture::forcedLTPChannels()
795,626✔
538
{
539
    return m_forcedLTPIndices;
795,626✔
540
}
541

542
void Fixture::setChannelModifier(quint32 idx, ChannelModifier *mod)
×
543
{
544
    if (idx >= channels())
×
545
        return;
×
546

547
    if (mod == NULL)
×
548
    {
549
        m_channelModifiers.remove(idx);
×
550
        return;
×
551
    }
552

553
    qDebug() << Q_FUNC_INFO << idx << mod->name();
×
554
    m_channelModifiers[idx] = mod;
×
555
}
556

557
ChannelModifier *Fixture::channelModifier(quint32 idx)
2,603✔
558
{
559
    return m_channelModifiers.value(idx, NULL);
2,603✔
560
}
561

562
/*********************************************************************
563
 * Channel info
564
 *********************************************************************/
565

566
bool Fixture::setChannelValues(const QByteArray &values)
1✔
567
{
568
    const int addr = address();
1✔
569
    if (addr >= values.size())
1✔
570
        return false;
×
571

572
    const int chNum = qMin(values.size() - addr, (int)channels());
1✔
573
    bool changed = false;
1✔
574

575
    // Most of the times there are no changes,
576
    // so the lock is inside the cycle
577
    for (int i = 0; i < chNum; i++)
7✔
578
    {
579
        if (m_values.at(i) != values.at(i + addr))
6✔
580
        {
581
            changed = true;
6✔
582
            QMutexLocker locker(&m_channelsInfoMutex);
6✔
583
            m_values[i] = values.at(i + addr);
6✔
584
            checkAlias(i, m_values[i]);
6✔
585
        }
6✔
586
    }
587

588
    if (changed == true)
1✔
589
        emit valuesChanged();
1✔
590

591
    return changed;
1✔
592
}
593

594
QByteArray Fixture::channelValues()
11✔
595
{
596
    QMutexLocker locker(&m_channelsInfoMutex);
11✔
597
    return m_values;
11✔
598
}
11✔
599

600
uchar Fixture::channelValueAt(int idx)
×
601
{
602
    QMutexLocker locker(&m_channelsInfoMutex);
×
603
    if (idx >= 0 && idx < m_values.length())
×
604
        return (uchar)m_values.at(idx);
×
605
    return 0;
×
606
}
×
607

608
void Fixture::checkAlias(int chIndex, uchar value)
6✔
609
{
610
    if (chIndex < 0 || chIndex >= m_aliasInfo.count() ||
12✔
611
        m_aliasInfo[chIndex].m_hasAlias == false)
6✔
612
        return;
6✔
613

614
    // If the channel @chIndex has aliases, check
615
    // if replacements are to be done
616
    QLCCapability *cap = m_fixtureMode->channel(chIndex)->searchCapability(value);
×
617
    if (cap == NULL || cap == m_aliasInfo[chIndex].m_currCap)
×
618
        return;
×
619

620
    // first, revert any channel replaced to the original channel set
621
    foreach (AliasInfo alias, m_aliasInfo[chIndex].m_currCap->aliasList())
×
622
    {
623
        QLCFixtureMode *mode = m_fixtureDef->mode(alias.targetMode);
×
624
        if (mode != m_fixtureMode)
×
625
            continue;
×
626

627
        QLCChannel *currChannel = m_fixtureMode->channel(alias.targetChannel);
×
628
        QLCChannel *origChannel = m_fixtureDef->channel(alias.sourceChannel);
×
629

630
        m_fixtureMode->replaceChannel(currChannel, origChannel);
×
631
    }
×
632

633
    // now, apply the current alias changes
634
    foreach (AliasInfo alias, cap->aliasList())
×
635
    {
636
        QLCFixtureMode *mode = m_fixtureDef->mode(alias.targetMode);
×
637
        if (mode != m_fixtureMode)
×
638
            continue;
×
639

640
        QLCChannel *currChannel = m_fixtureMode->channel(alias.sourceChannel);
×
641
        QLCChannel *newChannel = m_fixtureDef->channel(alias.targetChannel);
×
642

643
        m_fixtureMode->replaceChannel(currChannel, newChannel);
×
644
    }
×
645

646
    emit aliasChanged();
×
647

648
    m_aliasInfo[chIndex].m_currCap = cap;
×
649

650
}
651

652
/*****************************************************************************
653
 * Fixture definition
654
 *****************************************************************************/
655

656
void Fixture::setFixtureDefinition(QLCFixtureDef* fixtureDef,
462✔
657
                                   QLCFixtureMode* fixtureMode)
658
{
659
    if (fixtureDef != NULL && fixtureMode != NULL)
462✔
660
    {
661
        int i, chNum;
662

663
        if (m_fixtureDef != NULL && m_fixtureDef != fixtureDef &&
40✔
664
            m_fixtureDef->manufacturer() == KXMLFixtureGeneric &&
960✔
665
            m_fixtureDef->model() == KXMLFixtureGeneric)
469✔
666
        {
667
            delete m_fixtureDef;
3✔
668
        }
669

670
        m_fixtureDef = fixtureDef;
460✔
671
        m_fixtureMode = fixtureMode;
460✔
672
        chNum = fixtureMode->channels().size();
460✔
673

674
        // If there are no head entries in the mode, create one that contains
675
        // all channels. This const_cast is a bit heretic, but it's easier this
676
        // way, than to change everything def & mode related non-const, which would
677
        // be worse than one constness violation here.
678
        if (fixtureMode->heads().size() == 0)
460✔
679
        {
680
            QLCFixtureHead head;
37✔
681
            for (i = 0; i < chNum; i++)
321✔
682
                head.addChannel(i);
284✔
683
            fixtureMode->insertHead(-1, head);
37✔
684
        }
37✔
685

686
        m_aliasInfo.resize(chNum);
460✔
687

688
        for (i = 0; i < chNum; i++)
3,764✔
689
        {
690
            QLCChannel *channel = fixtureMode->channel(i);
3,304✔
691
            const QList <QLCCapability*> capsList = channel->capabilities();
3,304✔
692

693
            // initialize values with the channel default
694
            m_values.append(channel->defaultValue());
3,304✔
695

696
            // look for aliases
697
            m_aliasInfo[i].m_hasAlias = false;
3,304✔
698
            m_aliasInfo[i].m_currCap = capsList.count() ? capsList.at(0) : NULL;
3,304✔
699

700
            foreach (QLCCapability *cap, capsList)
13,450✔
701
            {
702
                if (cap->preset() == QLCCapability::Alias)
10,146✔
703
                    m_aliasInfo[i].m_hasAlias = true;
×
704
            }
3,304✔
705
        }
3,304✔
706

707
        // Cache all head channels
708
        fixtureMode->cacheHeads();
460✔
709
    }
460✔
710
    else
711
    {
712
        m_fixtureDef = NULL;
2✔
713
        m_fixtureMode = NULL;
2✔
714
    }
715

716
    emit changed(m_id);
462✔
717
}
462✔
718

719
QLCFixtureDef* Fixture::fixtureDef() const
33✔
720
{
721
    return m_fixtureDef;
33✔
722
}
723

724
QLCFixtureMode* Fixture::fixtureMode() const
795,316✔
725
{
726
    return m_fixtureMode;
795,316✔
727
}
728

729
int Fixture::heads() const
820✔
730
{
731
    return m_fixtureMode->heads().size();
820✔
732
}
733

734
QLCFixtureHead Fixture::head(int index) const
30✔
735
{
736
    if (index < m_fixtureMode->heads().size())
30✔
737
        return m_fixtureMode->heads().at(index);
30✔
738
    else
739
        return QLCFixtureHead();
×
740
}
741

742
QString Fixture::iconResource(bool svg) const
13✔
743
{
744
    QString prefix = svg ? "qrc" : "";
13✔
745
    QString ext = svg ? "svg" : "png";
13✔
746

747
    switch(type())
13✔
748
    {
749
        case QLCFixtureDef::ColorChanger: return QString("%1:/fixture.%2").arg(prefix).arg(ext);
1✔
750
        case QLCFixtureDef::Dimmer: return QString("%1:/dimmer.%2").arg(prefix).arg(ext);
2✔
751
        case QLCFixtureDef::Effect: return QString("%1:/effect.%2").arg(prefix).arg(ext);
1✔
752
        case QLCFixtureDef::Fan: return QString("%1:/fan.%2").arg(prefix).arg(ext);
×
753
        case QLCFixtureDef::Flower: return QString("%1:/flower.%2").arg(prefix).arg(ext);
1✔
754
        case QLCFixtureDef::Hazer: return QString("%1:/hazer.%2").arg(prefix).arg(ext);
1✔
755
        case QLCFixtureDef::Laser: return QString("%1:/laser.%2").arg(prefix).arg(ext);
1✔
756
        case QLCFixtureDef::MovingHead: return QString("%1:/movinghead.%2").arg(prefix).arg(ext);
1✔
757
        case QLCFixtureDef::Scanner: return QString("%1:/scanner.%2").arg(prefix).arg(ext);
1✔
758
        case QLCFixtureDef::Smoke: return QString("%1:/smoke.%2").arg(prefix).arg(ext);
1✔
759
        case QLCFixtureDef::Strobe: return QString("%1:/strobe.%2").arg(prefix).arg(ext);
1✔
760
        case QLCFixtureDef::LEDBarBeams: return QString("%1:/ledbar_beams.%2").arg(prefix).arg(ext);
1✔
761
        case QLCFixtureDef::LEDBarPixels: return QString("%1:/ledbar_pixels.%2").arg(prefix).arg(ext);
1✔
762
        default: break;
×
763
    }
764

765
    return QString("%1:/other.%2").arg(prefix).arg(ext);
×
766
}
13✔
767

768
QIcon Fixture::getIconFromType() const
1✔
769
{
770
    return QIcon(iconResource());
2✔
771
}
772

773
QRectF Fixture::degreesRange(int head) const
7✔
774
{
775
    // TODO: handle fixtures with only pan or tilt
776

777
    if (m_fixtureMode != NULL && head < m_fixtureMode->heads().size())
7✔
778
    {
779
        QLCPhysical physical(m_fixtureMode->physical());
7✔
780
        qreal pan = physical.focusPanMax();
7✔
781
        qreal tilt = physical.focusTiltMax();
7✔
782

783
        if (pan != 0 && tilt != 0)
7✔
784
        {
785
            return QRectF(-pan/2, -tilt/2, pan, tilt);
7✔
786
        }
787
    }
7✔
788

789
    return QRectF();
×
790
}
791

792
/*********************************************************************
793
 * Generic Dimmer
794
 *********************************************************************/
795

796
QLCFixtureDef *Fixture::genericDimmerDef(int channels)
234✔
797
{
798
    QLCFixtureDef *def = new QLCFixtureDef();
234✔
799
    def->setManufacturer(KXMLFixtureGeneric);
468✔
800
    def->setModel(KXMLFixtureGeneric);
468✔
801
    def->setType(QLCFixtureDef::Dimmer);
234✔
802
    def->setAuthor("QLC+");
234✔
803

804
    for (int i = 0; i < channels; i++)
957✔
805
    {
806
        QLCChannel *intensity = new QLCChannel();
723✔
807
        intensity->setGroup(QLCChannel::Intensity);
723✔
808
        intensity->setName(tr("Dimmer #%1").arg(i + 1));
723✔
809
        intensity->addCapability(new QLCCapability(0, UCHAR_MAX, tr("Intensity")));
723✔
810
        def->addChannel(intensity);
723✔
811
    }
812

813
    return def;
234✔
814
}
815

816
QLCFixtureMode *Fixture::genericDimmerMode(QLCFixtureDef *def, int channels)
234✔
817
{
818
    Q_ASSERT(def != NULL);
234✔
819
    QLCFixtureMode *mode = new QLCFixtureMode(def);
234✔
820

821
    mode->setName(QString("%1 Channel").arg(channels));
234✔
822
    QList<QLCChannel *>chList = def->channels();
234✔
823
    for (int i = 0; i < chList.count(); i++)
957✔
824
    {
825
        QLCChannel *ch = chList.at(i);
723✔
826
        mode->insertChannel(ch, i);
723✔
827
        QLCFixtureHead head;
723✔
828
        head.addChannel(i);
723✔
829
        mode->insertHead(-1, head);
723✔
830
    }
723✔
831

832
    QLCPhysical physical;
234✔
833
    physical.setWidth(300 * channels);
234✔
834
    physical.setHeight(300);
234✔
835
    physical.setDepth(300);
234✔
836

837
    mode->setPhysical(physical);
234✔
838
    def->addMode(mode);
234✔
839

840
    return mode;
234✔
841
}
234✔
842

843
/*********************************************************************
844
 * Generic RGB panel
845
 *********************************************************************/
846

847
QString Fixture::componentsToString(Components comp, bool is16bit)
8✔
848
{
849
    QString compStr;
8✔
850

851
    switch (comp)
8✔
852
    {
853
        case BGR:
1✔
854
            compStr = "BGR";
1✔
855
        break;
1✔
856
        case BRG:
1✔
857
            compStr = "BRG";
1✔
858
        break;
1✔
859
        case GBR:
1✔
860
            compStr = "GBR";
1✔
861
        break;
1✔
862
        case GRB:
1✔
863
            compStr = "GRB";
1✔
864
        break;
1✔
865
        case RBG:
1✔
866
            compStr = "RBG";
1✔
867
        break;
1✔
868
        case RGBW:
1✔
869
            compStr = "RGBW";
1✔
870
        break;
1✔
871
        default:
2✔
872
            compStr = "RGB";
2✔
873
        break;
2✔
874
    }
875

876
    if (is16bit)
8✔
877
        compStr += " 16bit";
1✔
878

879
    return compStr;
8✔
880
}
×
881

882
Fixture::Components Fixture::stringToComponents(QString str, bool &is16bit)
×
883
{
884
    QStringList strToken = str.split(' ');
×
885
    is16bit = false;
×
886

887
    if (strToken.count() == 2)
×
888
    {
889
        if (strToken.at(1) == "16bit")
×
890
            is16bit = true;
×
891
    }
892

893
    if (strToken.at(0) == "BGR") return BGR;
×
894
    else if (strToken.at(0) == "BRG") return BRG;
×
895
    else if (strToken.at(0) == "GBR") return GBR;
×
896
    else if (strToken.at(0) == "GRB") return GRB;
×
897
    else if (strToken.at(0) == "RBG") return RBG;
×
898
    else if (strToken.at(0) == "RGBW") return RGBW;
×
899
    else return RGB;
×
900
}
×
901

902
QLCFixtureDef *Fixture::genericRGBPanelDef(int columns, Components components, bool is16bit)
8✔
903
{
904
    QLCFixtureDef *def = new QLCFixtureDef();
8✔
905
    def->setManufacturer(KXMLFixtureGeneric);
16✔
906
    def->setModel(KXMLFixtureRGBPanel);
16✔
907
    def->setType(QLCFixtureDef::LEDBarPixels);
8✔
908
    def->setAuthor("QLC+");
8✔
909

910
    for (int i = 0; i < columns; i++)
88✔
911
    {
912
        QLCChannel *red = new QLCChannel();
80✔
913
        red->setName(QString("Red %1").arg(i + 1));
80✔
914
        red->setGroup(QLCChannel::Intensity);
80✔
915
        red->setColour(QLCChannel::Red);
80✔
916

917
        QLCChannel *green = new QLCChannel();
80✔
918
        green->setName(QString("Green %1").arg(i + 1));
80✔
919
        green->setGroup(QLCChannel::Intensity);
80✔
920
        green->setColour(QLCChannel::Green);
80✔
921

922
        QLCChannel *blue = new QLCChannel();
80✔
923
        blue->setName(QString("Blue %1").arg(i + 1));
80✔
924
        blue->setGroup(QLCChannel::Intensity);
80✔
925
        blue->setColour(QLCChannel::Blue);
80✔
926

927
        QLCChannel *redFine = NULL;
80✔
928
        QLCChannel *greenFine = NULL;
80✔
929
        QLCChannel *blueFine = NULL;
80✔
930

931
        if (is16bit)
80✔
932
        {
933
            redFine = new QLCChannel();
10✔
934
            redFine->setName(QString("Red Fine %1").arg(i + 1));
10✔
935
            redFine->setGroup(QLCChannel::Intensity);
10✔
936
            redFine->setColour(QLCChannel::Red);
10✔
937
            redFine->setControlByte(QLCChannel::LSB);
10✔
938

939
            greenFine = new QLCChannel();
10✔
940
            greenFine->setName(QString("Green Fine %1").arg(i + 1));
10✔
941
            greenFine->setGroup(QLCChannel::Intensity);
10✔
942
            greenFine->setColour(QLCChannel::Green);
10✔
943
            greenFine->setControlByte(QLCChannel::LSB);
10✔
944

945
            blueFine = new QLCChannel();
10✔
946
            blueFine->setName(QString("Blue Fine %1").arg(i + 1));
10✔
947
            blueFine->setGroup(QLCChannel::Intensity);
10✔
948
            blueFine->setColour(QLCChannel::Blue);
10✔
949
            blueFine->setControlByte(QLCChannel::LSB);
10✔
950
        }
951

952
        if (components == BGR)
80✔
953
        {
954
            def->addChannel(blue);
10✔
955
            if (is16bit) def->addChannel(blueFine);
10✔
956
            def->addChannel(green);
10✔
957
            if (is16bit) def->addChannel(greenFine);
10✔
958
            def->addChannel(red);
10✔
959
            if (is16bit) def->addChannel(redFine);
10✔
960
        }
961
        else if (components == BRG)
70✔
962
        {
963
            def->addChannel(blue);
10✔
964
            if (is16bit) def->addChannel(blueFine);
10✔
965
            def->addChannel(red);
10✔
966
            if (is16bit) def->addChannel(redFine);
10✔
967
            def->addChannel(green);
10✔
968
            if (is16bit) def->addChannel(greenFine);
10✔
969
        }
970
        else if (components == GBR)
60✔
971
        {
972
            def->addChannel(green);
10✔
973
            if (is16bit) def->addChannel(greenFine);
10✔
974
            def->addChannel(blue);
10✔
975
            if (is16bit) def->addChannel(blueFine);
10✔
976
            def->addChannel(red);
10✔
977
            if (is16bit) def->addChannel(redFine);
10✔
978
        }
979
        else if (components == GRB)
50✔
980
        {
981
            def->addChannel(green);
10✔
982
            if (is16bit) def->addChannel(greenFine);
10✔
983
            def->addChannel(red);
10✔
984
            if (is16bit) def->addChannel(redFine);
10✔
985
            def->addChannel(blue);
10✔
986
            if (is16bit) def->addChannel(blueFine);
10✔
987
        }
988
        else if (components == RBG)
40✔
989
        {
990
            def->addChannel(red);
10✔
991
            if (is16bit) def->addChannel(redFine);
10✔
992
            def->addChannel(blue);
10✔
993
            if (is16bit) def->addChannel(blueFine);
10✔
994
            def->addChannel(green);
10✔
995
            if (is16bit) def->addChannel(greenFine);
10✔
996
        }
997
        else if (components == RGBW)
30✔
998
        {
999
            QLCChannel *white = new QLCChannel();
10✔
1000
            white->setName(QString("White %1").arg(i + 1));
10✔
1001
            white->setGroup(QLCChannel::Intensity);
10✔
1002
            white->setColour(QLCChannel::White);
10✔
1003

1004
            def->addChannel(red);
10✔
1005
            if (is16bit) def->addChannel(redFine);
10✔
1006
            def->addChannel(green);
10✔
1007
            if (is16bit) def->addChannel(greenFine);
10✔
1008
            def->addChannel(blue);
10✔
1009
            if (is16bit) def->addChannel(blueFine);
10✔
1010
            def->addChannel(white);
10✔
1011

1012
            if (is16bit)
10✔
1013
            {
1014
                QLCChannel *whiteFine = new QLCChannel();
×
1015
                whiteFine->setName(QString("White Fine %1").arg(i + 1));
×
1016
                whiteFine->setGroup(QLCChannel::Intensity);
×
1017
                whiteFine->setColour(QLCChannel::White);
×
1018
                whiteFine->setControlByte(QLCChannel::LSB);
×
1019
                def->addChannel(whiteFine);
×
1020
            }
1021
        }
1022
        else
1023
        {
1024
            def->addChannel(red);
20✔
1025
            if (is16bit) def->addChannel(redFine);
20✔
1026
            def->addChannel(green);
20✔
1027
            if (is16bit) def->addChannel(greenFine);
20✔
1028
            def->addChannel(blue);
20✔
1029
            if (is16bit) def->addChannel(blueFine);
20✔
1030
        }
1031
    }
1032

1033
    return def;
8✔
1034
}
1035

1036
QLCFixtureMode *Fixture::genericRGBPanelMode(QLCFixtureDef *def, Components components, bool is16bit,
8✔
1037
                                             quint32 width, quint32 height)
1038
{
1039
    Q_ASSERT(def != NULL);
8✔
1040

1041
    QLCFixtureMode *mode = new QLCFixtureMode(def);
8✔
1042
    QString modeName = componentsToString(components, is16bit);
8✔
1043
    mode->setName(modeName);
8✔
1044

1045
    int compNum = components == RGBW ? 4 : 3;
8✔
1046
    if (is16bit)
8✔
1047
        compNum *= 2;
1✔
1048

1049
    QList<QLCChannel *>channels = def->channels();
8✔
1050
    int i = 0;
8✔
1051

1052
    // add channels and heads
1053
    for (int h = 0; h < channels.count() / compNum; h++)
88✔
1054
    {
1055
        QLCFixtureHead head;
80✔
1056

1057
        for (int c = 0; c < compNum; c++, i++)
360✔
1058
        {
1059
            QLCChannel *ch = channels.at(i);
280✔
1060
            mode->insertChannel(ch, i);
280✔
1061
            head.addChannel(i);
280✔
1062
        }
1063

1064
        mode->insertHead(-1, head);
80✔
1065
    }
80✔
1066

1067
    QLCPhysical physical;
8✔
1068
    physical.setWidth(width);
8✔
1069
    physical.setHeight(height);
8✔
1070
    physical.setDepth(height);
8✔
1071
    physical.setLayoutSize(QSize(mode->heads().count(), 1));
8✔
1072

1073
    mode->setPhysical(physical);
8✔
1074
    def->addMode(mode);
8✔
1075

1076
    return mode;
8✔
1077
}
8✔
1078

1079
/*****************************************************************************
1080
 * Load & Save
1081
 *****************************************************************************/
1082

1083
bool Fixture::loader(QXmlStreamReader &root, Doc* doc)
4✔
1084
{
1085
    bool result = false;
4✔
1086

1087
    Fixture* fxi = new Fixture(doc);
4✔
1088
    Q_ASSERT(fxi != NULL);
4✔
1089

1090
    if (fxi->loadXML(root, doc, doc->fixtureDefCache()) == true)
4✔
1091
    {
1092
        if (doc->addFixture(fxi, fxi->id(), fxi->crossUniverse()) == true)
4✔
1093
        {
1094
            /* Success */
1095
            result = true;
4✔
1096
        }
1097
        else
1098
        {
1099
            /* Doc is full */
1100
            qWarning() << Q_FUNC_INFO << "Fixture" << fxi->name()
×
1101
                       << "cannot be created.";
×
1102
            delete fxi;
×
1103
        }
1104
    }
1105
    else
1106
    {
1107
        qWarning() << Q_FUNC_INFO << "Fixture" << fxi->name() << "cannot be loaded.";
×
1108
        delete fxi;
×
1109
    }
1110

1111
    return result;
4✔
1112
}
1113

1114
bool Fixture::loadXML(QXmlStreamReader &xmlDoc, Doc *doc,
11✔
1115
                      QLCFixtureDefCache *fixtureDefCache)
1116
{
1117
    QLCFixtureDef* fixtureDef = NULL;
11✔
1118
    QLCFixtureMode* fixtureMode = NULL;
11✔
1119
    QString manufacturer;
11✔
1120
    QString model;
11✔
1121
    QString modeName;
11✔
1122
    QString name;
11✔
1123
    quint32 id = Fixture::invalidId();
11✔
1124
    quint32 universe = 0;
11✔
1125
    quint32 address = 0;
11✔
1126
    quint32 channels = 0;
11✔
1127
    quint32 width = 0, height = 0;
11✔
1128
    QList<int> excludeList;
11✔
1129
    QList<int> forcedHTP;
11✔
1130
    QList<int> forcedLTP;
11✔
1131
    QList<quint32>modifierIndices;
11✔
1132
    QList<ChannelModifier *>modifierPointers;
11✔
1133

1134
    if (xmlDoc.name() != KXMLFixture)
11✔
1135
    {
1136
        qWarning() << Q_FUNC_INFO << "Fixture node not found";
1✔
1137
        return false;
1✔
1138
    }
1139

1140
    while (xmlDoc.readNextStartElement())
90✔
1141
    {
1142
        if (xmlDoc.name() == KXMLQLCFixtureDefManufacturer)
80✔
1143
        {
1144
            manufacturer = xmlDoc.readElementText();
10✔
1145
        }
1146
        else if (xmlDoc.name() == KXMLQLCFixtureDefModel)
70✔
1147
        {
1148
            model = xmlDoc.readElementText();
10✔
1149
        }
1150
        else if (xmlDoc.name() == KXMLQLCFixtureMode)
60✔
1151
        {
1152
            modeName = xmlDoc.readElementText();
10✔
1153
        }
1154
        else if (xmlDoc.name() == KXMLQLCPhysicalDimensionsWidth)
50✔
1155
        {
1156
            width = xmlDoc.readElementText().toUInt();
×
1157
        }
1158
        else if (xmlDoc.name() == KXMLQLCPhysicalDimensionsHeight)
50✔
1159
        {
1160
            height = xmlDoc.readElementText().toUInt();
×
1161
        }
1162
        else if (xmlDoc.name() == KXMLFixtureID)
50✔
1163
        {
1164
            id = xmlDoc.readElementText().toUInt();
10✔
1165
        }
1166
        else if (xmlDoc.name() == KXMLFixtureName)
40✔
1167
        {
1168
            name = xmlDoc.readElementText();
10✔
1169
        }
1170
        else if (xmlDoc.name() == KXMLFixtureUniverse)
30✔
1171
        {
1172
            universe = xmlDoc.readElementText().toInt();
10✔
1173
        }
1174
        else if (xmlDoc.name() == KXMLFixtureCrossUniverse)
20✔
1175
        {
1176
            xmlDoc.readElementText();
×
1177
            setCrossUniverse(true);
×
1178
        }
1179
        else if (xmlDoc.name() == KXMLFixtureAddress)
20✔
1180
        {
1181
            address = xmlDoc.readElementText().toInt();
10✔
1182
        }
1183
        else if (xmlDoc.name() == KXMLFixtureChannels)
10✔
1184
        {
1185
            channels = xmlDoc.readElementText().toInt();
10✔
1186
        }
1187
        else if (xmlDoc.name() == KXMLFixtureExcludeFade)
×
1188
        {
1189
            QString list = xmlDoc.readElementText();
×
1190
            QStringList values = list.split(",");
×
1191

1192
            for (int i = 0; i < values.count(); i++)
×
1193
                excludeList.append(values.at(i).toInt());
×
1194
        }
×
1195
        else if (xmlDoc.name() == KXMLFixtureForcedHTP)
×
1196
        {
1197
            QString list = xmlDoc.readElementText();
×
1198
            QStringList values = list.split(",");
×
1199

1200
            for (int i = 0; i < values.count(); i++)
×
1201
                forcedHTP.append(values.at(i).toInt());
×
1202
        }
×
1203
        else if (xmlDoc.name() == KXMLFixtureForcedLTP)
×
1204
        {
1205
            QString list = xmlDoc.readElementText();
×
1206
            QStringList values = list.split(",");
×
1207

1208
            for (int i = 0; i < values.count(); i++)
×
1209
                forcedLTP.append(values.at(i).toInt());
×
1210
        }
×
1211
        else if (xmlDoc.name() == KXMLFixtureChannelModifier)
×
1212
        {
1213
            QXmlStreamAttributes attrs = xmlDoc.attributes();
×
1214
            if (attrs.hasAttribute(KXMLFixtureChannelIndex) &&
×
1215
                attrs.hasAttribute(KXMLFixtureModifierName))
×
1216
            {
1217
                quint32 chIdx = attrs.value(KXMLFixtureChannelIndex).toString().toUInt();
×
1218
                QString modName = attrs.value(KXMLFixtureModifierName).toString();
×
1219
                ChannelModifier *chMod = doc->modifiersCache()->modifier(modName);
×
1220
                if (chMod != NULL)
×
1221
                {
1222
                    modifierIndices.append(chIdx);
×
1223
                    modifierPointers.append(chMod);
×
1224
                }
1225
                xmlDoc.skipCurrentElement();
×
1226
            }
×
1227
        }
×
1228
        else
1229
        {
1230
            qWarning() << Q_FUNC_INFO << "Unknown fixture tag:" << xmlDoc.name();
×
1231
            xmlDoc.skipCurrentElement();
×
1232
        }
1233
    }
1234

1235
    /* Find the given fixture definition, unless its a generic dimmer */
1236
    if (model != KXMLFixtureGeneric && model != KXMLFixtureRGBPanel)
30✔
1237
    {
1238
        fixtureDef = fixtureDefCache->fixtureDef(manufacturer, model);
10✔
1239
        if (fixtureDef == NULL)
10✔
1240
        {
1241
            // fallback to project local path
1242
            QString man(manufacturer);
7✔
1243
            QString mod(model);
7✔
1244
            QString path = QString("%1%2%3-%4%5")
7✔
1245
                    .arg(doc->workspacePath()).arg(QDir::separator())
14✔
1246
                    .arg(man.replace(" ", "-")).arg(mod.replace(" ", "-")).arg(KExtFixture);
21✔
1247

1248
            qDebug() << "Fixture not found. Fallback to:" << path;
7✔
1249

1250
            if (fixtureDefCache->loadQXF(path, true) == false)
7✔
1251
                qDebug() << "Failed to load definition" << path;
7✔
1252

1253
            fixtureDef = fixtureDefCache->fixtureDef(manufacturer, model);
7✔
1254
            if (fixtureDef == NULL)
7✔
1255
            {
1256
                doc->appendToErrorLog(QString("No fixture definition found for <b>%1</b> <b>%2</b>")
21✔
1257
                                      .arg(manufacturer).arg(model));
14✔
1258
            }
1259
        }
7✔
1260

1261
        if (fixtureDef != NULL)
10✔
1262
        {
1263
            /* Find the given fixture mode */
1264
            fixtureMode = fixtureDef->mode(modeName);
3✔
1265
            if (fixtureMode == NULL)
3✔
1266
            {
1267
                doc->appendToErrorLog(QString("Fixture mode <b>%1</b> not found for <b>%2</b> <b>%3</b>")
×
1268
                                      .arg(modeName).arg(manufacturer).arg(model));
×
1269

1270
                /* Set this also NULL so that a generic dimmer will be
1271
                   created instead as a backup. */
1272
                fixtureDef = NULL;
×
1273
            }
1274
        }
1275
    }
1276

1277
    /* Number of channels */
1278
    if (channels <= 0)
10✔
1279
    {
1280
        doc->appendToErrorLog(QString("%1 channels of fixture <b>%2</b> are out of bounds")
×
1281
                              .arg(QString::number(channels))
×
1282
                              .arg(name));
×
1283
        channels = 1;
×
1284
    }
1285

1286
    /* Make sure that address is something sensible */
1287
    if (!crossUniverse() && (address > 511 || address + (channels - 1) > 511))
10✔
1288
    {
1289
        doc->appendToErrorLog(QString("Fixture address range %1-%2 is out of DMX bounds")
2✔
1290
                              .arg(QString::number(address))
2✔
1291
                              .arg(QString::number(address + channels - 1)));
2✔
1292
        address = 0;
1✔
1293
    }
1294

1295
    /* Check that the invalid ID is not used */
1296
    if (id == Fixture::invalidId())
10✔
1297
    {
1298
        qWarning() << Q_FUNC_INFO << "Fixture ID" << id << "is not allowed.";
1✔
1299
        return false;
1✔
1300
    }
1301

1302
    if (model == KXMLFixtureGeneric)
9✔
1303
    {
1304
        fixtureDef = genericDimmerDef(channels);
×
1305
        fixtureMode = genericDimmerMode(fixtureDef, channels);
×
1306
    }
1307
    else if (model == KXMLFixtureRGBPanel)
9✔
1308
    {
1309
        bool is16bit = false;
×
1310
        Components components = stringToComponents(modeName, is16bit);
×
1311
        int compNum = components == RGBW ? 4 : 3;
×
1312
        if (is16bit)
×
1313
            compNum *= 2;
×
1314

1315
        fixtureDef = genericRGBPanelDef(channels / compNum, components, is16bit);
×
1316
        fixtureMode = genericRGBPanelMode(fixtureDef, components, is16bit, width, height);
×
1317
    }
1318

1319
    if (fixtureDef != NULL && fixtureMode != NULL)
9✔
1320
    {
1321
        /* Assign fixtureDef & mode only if BOTH are not NULL */
1322
        setFixtureDefinition(fixtureDef, fixtureMode);
2✔
1323
    }
1324
    else
1325
    {
1326
        /* Otherwise set just the channel count */
1327
        setChannels(channels);
7✔
1328
    }
1329

1330
    setAddress(address);
9✔
1331
    setUniverse(universe);
9✔
1332
    setName(name);
9✔
1333
    setExcludeFadeChannels(excludeList);
9✔
1334
    setForcedHTPChannels(forcedHTP);
9✔
1335
    setForcedLTPChannels(forcedLTP);
9✔
1336
    for (int i = 0; i < modifierIndices.count(); i++)
9✔
1337
        setChannelModifier(modifierIndices.at(i), modifierPointers.at(i));
×
1338
    setID(id);
9✔
1339

1340
    return true;
9✔
1341
}
11✔
1342

1343
bool Fixture::saveXML(QXmlStreamWriter *doc) const
4✔
1344
{
1345
    Q_ASSERT(doc != NULL);
4✔
1346

1347
    /* Fixture Instance entry */
1348
    doc->writeStartElement(KXMLFixture);
8✔
1349

1350
    /* Manufacturer */
1351
    if (m_fixtureDef != NULL)
4✔
1352
        doc->writeTextElement(KXMLQLCFixtureDefManufacturer, m_fixtureDef->manufacturer());
8✔
1353
    else
1354
        doc->writeTextElement(KXMLQLCFixtureDefManufacturer, KXMLFixtureGeneric);
×
1355

1356
    /* Model */
1357
    if (m_fixtureDef != NULL)
4✔
1358
        doc->writeTextElement(KXMLQLCFixtureDefModel, m_fixtureDef->model());
8✔
1359
    else
1360
        doc->writeTextElement(KXMLQLCFixtureDefModel, KXMLFixtureGeneric);
×
1361

1362
    /* Fixture mode */
1363
    if (m_fixtureMode != NULL)
4✔
1364
        doc->writeTextElement(KXMLQLCFixtureMode, m_fixtureMode->name());
8✔
1365
    else
1366
        doc->writeTextElement(KXMLQLCFixtureMode, KXMLFixtureGeneric);
×
1367

1368
    /* RGB Panel physical dimensions */
1369
    if (m_fixtureDef != NULL && m_fixtureDef->model() == KXMLFixtureRGBPanel && m_fixtureMode != NULL)
8✔
1370
    {
1371
        doc->writeTextElement(KXMLQLCPhysicalDimensionsWidth,
×
1372
                              QString::number(m_fixtureMode->physical().width()));
×
1373

1374
        doc->writeTextElement(KXMLQLCPhysicalDimensionsHeight,
×
1375
                              QString::number(m_fixtureMode->physical().height()));
×
1376
    }
1377

1378
    /* ID */
1379
    doc->writeTextElement(KXMLFixtureID, QString::number(id()));
8✔
1380
    /* Name */
1381
    doc->writeTextElement(KXMLFixtureName, m_name);
8✔
1382
    /* Universe */
1383
    doc->writeTextElement(KXMLFixtureUniverse, QString::number(universe()));
8✔
1384
    if (crossUniverse())
4✔
1385
        doc->writeTextElement(KXMLFixtureCrossUniverse, KXMLQLCTrue);
×
1386
    /* Address */
1387
    doc->writeTextElement(KXMLFixtureAddress, QString::number(address()));
8✔
1388
    /* Channel count */
1389
    doc->writeTextElement(KXMLFixtureChannels, QString::number(channels()));
8✔
1390

1391
    if (m_excludeFadeIndices.count() > 0)
4✔
1392
    {
1393
        QString list;
×
1394
        for (int i = 0; i < m_excludeFadeIndices.count(); i++)
×
1395
        {
1396
            if (list.isEmpty() == false)
×
1397
                list.append(QString(","));
×
1398
            list.append(QString("%1").arg(m_excludeFadeIndices.at(i)));
×
1399
        }
1400
        doc->writeTextElement(KXMLFixtureExcludeFade, list);
×
1401
    }
×
1402

1403
    if (m_forcedHTPIndices.count() > 0)
4✔
1404
    {
1405
        QString list;
×
1406
        for (int i = 0; i < m_forcedHTPIndices.count(); i++)
×
1407
        {
1408
            if (list.isEmpty() == false)
×
1409
                list.append(QString(","));
×
1410
            list.append(QString("%1").arg(m_forcedHTPIndices.at(i)));
×
1411
        }
1412
        doc->writeTextElement(KXMLFixtureForcedHTP, list);
×
1413
    }
×
1414

1415
    if (m_forcedLTPIndices.count() > 0)
4✔
1416
    {
1417
        QString list;
×
1418
        for (int i = 0; i < m_forcedLTPIndices.count(); i++)
×
1419
        {
1420
            if (list.isEmpty() == false)
×
1421
                list.append(QString(","));
×
1422
            list.append(QString("%1").arg(m_forcedLTPIndices.at(i)));
×
1423
        }
1424
        doc->writeTextElement(KXMLFixtureForcedLTP, list);
×
1425
    }
×
1426

1427
    {
1428
        QMapIterator<quint32, ChannelModifier *> it(m_channelModifiers);
4✔
1429
        while (it.hasNext())
4✔
1430
        {
1431
            it.next();
×
1432
            quint32 ch = it.key();
×
1433
            ChannelModifier *mod = it.value();
×
1434
            if (mod != NULL)
×
1435
            {
1436
                doc->writeStartElement(KXMLFixtureChannelModifier);
×
1437
                doc->writeAttribute(KXMLFixtureChannelIndex, QString::number(ch));
×
1438
                doc->writeAttribute(KXMLFixtureModifierName, mod->name());
×
1439
                doc->writeEndElement();
×
1440
            }
1441
        }
1442
    }
4✔
1443

1444
    /* End the <Fixture> tag */
1445
    doc->writeEndElement();
4✔
1446

1447
    return true;
4✔
1448
}
1449

1450
/*****************************************************************************
1451
 * Status
1452
 *****************************************************************************/
1453

1454
QString Fixture::status() const
7✔
1455
{
1456
    QString info;
7✔
1457
    QString t;
7✔
1458

1459
    QString title("<TR><TD CLASS='hilite' COLSPAN='3'>%1</TD></TR>");
7✔
1460
    QString subTitle("<TR><TD CLASS='subhi' COLSPAN='3'>%1</TD></TR>");
7✔
1461
    QString genInfo("<TR><TD CLASS='emphasis'>%1</TD><TD COLSPAN='2'>%2</TD></TR>");
7✔
1462

1463
    /********************************************************************
1464
     * General info
1465
     ********************************************************************/
1466

1467
    info += "<TABLE COLS='3' WIDTH='100%'>";
7✔
1468

1469
    // Fixture title
1470
    info += title.arg(name());
7✔
1471

1472
    if (m_fixtureDef != NULL && m_fixtureMode != NULL)
7✔
1473
    {
1474
        // Manufacturer
1475
        info += genInfo.arg(tr("Manufacturer")).arg(m_fixtureDef->manufacturer());
2✔
1476
        info += genInfo.arg(tr("Model")).arg(m_fixtureDef->model());
2✔
1477
        info += genInfo.arg(tr("Mode")).arg(m_fixtureMode->name());
2✔
1478
        info += genInfo.arg(tr("Type")).arg(m_fixtureDef->typeToString(m_fixtureDef->type()));
2✔
1479
    }
1480

1481
    // Universe
1482
    info += genInfo.arg(tr("Universe")).arg(universe() + 1);
7✔
1483

1484
    // Address
1485
    QString range = QString("%1 - %2").arg(address() + 1).arg(address() + channels());
7✔
1486
    info += genInfo.arg(tr("Address Range")).arg(range);
7✔
1487

1488
    // Channels
1489
    info += genInfo.arg(tr("Channels")).arg(channels());
7✔
1490

1491
    // Binary address
1492
    QString binaryStr = QString("%1").arg(address() + 1, 10, 2, QChar('0'));
7✔
1493
    QString dipTable("<TABLE COLS='33' cellspacing='0'><TR><TD COLSPAN='33'><IMG SRC=\"" ":/ds_top.png\"></TD></TR>");
7✔
1494
    dipTable += "<TR><TD><IMG SRC=\"" ":/ds_border.png\"></TD><TD><IMG SRC=\"" ":/ds_border.png\"></TD>";
7✔
1495
    for (int i = 9; i >= 0; i--)
77✔
1496
    {
1497
        if (binaryStr.at(i) == '0')
70✔
1498
            dipTable += "<TD COLSPAN='3'><IMG SRC=\"" ":/ds_off.png\"></TD>";
45✔
1499
        else
1500
            dipTable += "<TD COLSPAN='3'><IMG SRC=\"" ":/ds_on.png\"></TD>";
25✔
1501
    }
1502
    dipTable += "<TD><IMG SRC=\"" ":/ds_border.png\"></TD></TR>";
7✔
1503
    dipTable += "<TR><TD COLSPAN='33'><IMG SRC=\"" ":/ds_bottom.png\"></TD></TR>";
7✔
1504
    dipTable += "</TABLE>";
7✔
1505

1506
    info += genInfo.arg(tr("Binary Address (DIP)"))
14✔
1507
            .arg(QString("%1").arg(dipTable));
7✔
1508

1509
    /********************************************************************
1510
     * Channels
1511
     ********************************************************************/
1512

1513
    // Title row
1514
    info += QString("<TR><TD CLASS='subhi'>%1</TD>").arg(tr("Channel"));
7✔
1515
    info += QString("<TD CLASS='subhi'>%1</TD>").arg(tr("DMX"));
7✔
1516
    info += QString("<TD CLASS='subhi'>%1</TD></TR>").arg(tr("Name"));
7✔
1517

1518
    // Fill table with the fixture's channels
1519
    for (quint32 ch = 0; ch < channels();        ch++)
28✔
1520
    {
1521
        QString chInfo("<TR><TD>%1</TD><TD>%2</TD><TD>%3</TD></TR>");
21✔
1522
        info += chInfo.arg(ch + 1).arg(address() + ch + 1)
42✔
1523
                .arg(channel(ch)->name());
21✔
1524
    }
21✔
1525

1526
    /********************************************************************
1527
     * Extended device information
1528
     ********************************************************************/
1529

1530
    if (m_fixtureMode != NULL)
7✔
1531
    {
1532
        QLCPhysical physical = m_fixtureMode->physical();
2✔
1533
        info += title.arg(tr("Physical"));
2✔
1534

1535
        float mmInch = 0.0393700787;
2✔
1536
        float kgLbs = 2.20462262;
2✔
1537
        QString mm("%1mm (%2\")");
2✔
1538
        QString kg("%1kg (%2 lbs)");
2✔
1539
        QString W("%1W");
2✔
1540
        info += genInfo.arg(tr("Width")).arg(mm.arg(physical.width()))
4✔
1541
                                        .arg(physical.width() * mmInch, 0, 'g', 4);
2✔
1542
        info += genInfo.arg(tr("Height")).arg(mm.arg(physical.height()))
4✔
1543
                                         .arg(physical.height() * mmInch, 0, 'g', 4);
2✔
1544
        info += genInfo.arg(tr("Depth")).arg(mm.arg(physical.depth()))
4✔
1545
                                        .arg(physical.depth() * mmInch, 0, 'g', 4);
2✔
1546
        info += genInfo.arg(tr("Weight")).arg(kg.arg(physical.weight()))
4✔
1547
                                         .arg(physical.weight() * kgLbs, 0, 'g', 4);
2✔
1548
        info += genInfo.arg(tr("Power consumption")).arg(W.arg(physical.powerConsumption()));
2✔
1549
        info += genInfo.arg(tr("DMX Connector")).arg(physical.dmxConnector());
2✔
1550

1551
        // Bulb
1552
        QString K("%1K");
2✔
1553
        QString lm("%1lm");
2✔
1554
        info += subTitle.arg(tr("Bulb"));
2✔
1555
        info += genInfo.arg(tr("Type")).arg(physical.bulbType());
2✔
1556
        info += genInfo.arg(tr("Luminous Flux")).arg(lm.arg(physical.bulbLumens()));
2✔
1557
        info += genInfo.arg(tr("Colour Temperature")).arg(K.arg(physical.bulbColourTemperature()));
2✔
1558

1559
        // Lens
1560
        QString angle1("%1&deg;");
2✔
1561
        QString angle2("%1&deg; &ndash; %2&deg;");
2✔
1562

1563
        info += subTitle.arg(tr("Lens"));
2✔
1564
        info += genInfo.arg(tr("Name")).arg(physical.lensName());
2✔
1565

1566
        if (physical.lensDegreesMin() == physical.lensDegreesMax())
2✔
1567
        {
1568
            info += genInfo.arg(tr("Beam Angle"))
4✔
1569
                .arg(angle1.arg(physical.lensDegreesMin()));
2✔
1570
        }
1571
        else
1572
        {
1573
            info += genInfo.arg(tr("Beam Angle"))
×
1574
                .arg(angle2.arg(physical.lensDegreesMin())
×
1575
                .arg(physical.lensDegreesMax()));
×
1576
        }
1577

1578
        // Focus
1579
        QString frange("%1&deg;");
2✔
1580
        info += subTitle.arg(tr("Head(s)"));
2✔
1581
        info += genInfo.arg(tr("Type")).arg(physical.focusType());
2✔
1582
        info += genInfo.arg(tr("Pan Range")).arg(frange.arg(physical.focusPanMax()));
2✔
1583
        info += genInfo.arg(tr("Tilt Range")).arg(frange.arg(physical.focusTiltMax()));
2✔
1584
        if (physical.layoutSize() != QSize(1, 1))
2✔
1585
        {
1586
            info += genInfo.arg(tr("Layout"))
×
1587
                           .arg(QString("%1 x %2").arg(physical.layoutSize().width()).arg(physical.layoutSize().height()));
×
1588
        }
1589
    }
2✔
1590

1591
    // HTML document & table closure
1592
    info += "</TABLE>";
7✔
1593

1594
    if (m_fixtureDef != NULL)
7✔
1595
    {
1596
        info += "<HR>";
2✔
1597
        info += "<DIV CLASS='author' ALIGN='right'>";
2✔
1598
        info += tr("Fixture definition author: ") + m_fixtureDef->author();
2✔
1599
        info += "</DIV>";
2✔
1600
    }
1601

1602
    return info;
14✔
1603
}
7✔
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