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

mcallegari / qlcplus / 27031669590

05 Jun 2026 06:04PM UTC coverage: 35.286% (+0.2%) from 35.039%
27031669590

push

github

mcallegari
engine: add missing test unit file

18430 of 52230 relevant lines covered (35.29%)

41144.28 hits per line

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

0.0
/ui/src/fixtureremap.cpp
1
/*
2
  Q Light Controller Plus
3
  fixtureremap.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 <QProgressDialog>
22
#include <QFileDialog>
23
#include <QMessageBox>
24
#include <QScrollBar>
25
#include <QDebug>
26
#include <QDir>
27
#include <QSettings>
28

29
#include "vcaudiotriggers.h"
30
#include "fixtureremapper.h"
31
#include "virtualconsole.h"
32
#include "qlcfixturemode.h"
33
#include "qlcfixturedef.h"
34
#include "fixtureremap.h"
35
#include "remapwidget.h"
36
#include "qlcchannel.h"
37
#include "addfixture.h"
38
#include "scenevalue.h"
39
#include "audiobar.h"
40
#include "vcslider.h"
41
#include "qlcfile.h"
42
#include "vcxypad.h"
43
#include "vcframe.h"
44
#include "doc.h"
45
#include "app.h"
46

47
#define KColumnName         0
48
#define KColumnAddress      1
49
#define KColumnUniverse     2
50
#define KColumnID           3
51
#define KColumnChIdx        4
52

53
#define SETTINGS_GEOMETRY "fixturemap/geometry"
54

55
FixtureRemap::FixtureRemap(Doc *doc, QWidget *parent)
×
56
    : QDialog(parent)
57
    , m_doc(doc)
×
58
{
59
    Q_ASSERT(doc != NULL);
×
60

61
    setupUi(this);
×
62

63
    QSettings settings;
×
64
    QVariant geometrySettings = settings.value(SETTINGS_GEOMETRY);
×
65
    if (geometrySettings.isValid() == true)
×
66
        restoreGeometry(geometrySettings.toByteArray());
×
67

68
    connect(m_importButton, SIGNAL(clicked()),
×
69
            this, SLOT(slotImportFixtures()));
70
    connect(m_addButton, SIGNAL(clicked()),
×
71
            this, SLOT(slotAddTargetFixture()));
72
    connect(m_removeButton, SIGNAL(clicked()),
×
73
            this, SLOT(slotRemoveTargetFixture()));
74
    connect(m_cloneButton, SIGNAL(clicked()),
×
75
            this, SLOT(slotCloneSourceFixture()));
76
    connect(m_remapButton, SIGNAL(clicked()),
×
77
            this, SLOT(slotAddRemap()));
78
    connect(m_unmapButton, SIGNAL(clicked()),
×
79
            this, SLOT(slotRemoveRemap()));
80

81
    m_cloneButton->setEnabled(false);
×
82

83
    remapWidget = new RemapWidget(m_sourceTree, m_targetTree, this);
×
84
    remapWidget->show();
×
85
    m_remapLayout->addWidget(remapWidget);
×
86

87
    m_targetDoc = new Doc(this);
×
88
    /* Load user fixtures first so that they override system fixtures */
89
    m_targetDoc->fixtureDefCache()->load(QLCFixtureDefCache::userDefinitionDirectory());
×
90
    m_targetDoc->fixtureDefCache()->loadMap(QLCFixtureDefCache::systemDefinitionDirectory());
×
91

92
    /* Remove the default set of universes from the target Doc and re-fill it
93
     * with the current Doc universe list */
94
    m_targetDoc->inputOutputMap()->removeAllUniverses();
×
95

96
    int index = 0;
×
97
    foreach (Universe *uni, m_doc->inputOutputMap()->universes())
×
98
    {
99
        m_targetDoc->inputOutputMap()->addUniverse(uni->id());
×
100
        m_targetDoc->inputOutputMap()->setUniverseName(index, uni->name());
×
101
        m_targetDoc->inputOutputMap()->startUniverses();
×
102
        index++;
×
103
    }
×
104

105
    m_sourceTree->setIconSize(QSize(24, 24));
×
106
    m_sourceTree->setAllColumnsShowFocus(true);
×
107
    fillFixturesTree(m_doc, m_sourceTree);
×
108

109
    m_targetTree->setIconSize(QSize(24, 24));
×
110
    m_targetTree->setAllColumnsShowFocus(true);
×
111

112
    connect(m_sourceTree->verticalScrollBar(), SIGNAL(valueChanged(int)),
×
113
            this, SLOT(slotUpdateConnections()));
114
    connect(m_sourceTree, SIGNAL(clicked(QModelIndex)),
×
115
            this, SLOT(slotUpdateConnections()));
116
    connect(m_sourceTree, SIGNAL(expanded(QModelIndex)),
×
117
            this, SLOT(slotUpdateConnections()));
118
    connect(m_sourceTree, SIGNAL(collapsed(QModelIndex)),
×
119
            this, SLOT(slotUpdateConnections()));
120
    connect(m_sourceTree, SIGNAL(itemSelectionChanged()),
×
121
            this, SLOT(slotSourceSelectionChanged()));
122

123
    connect(m_targetTree->verticalScrollBar(), SIGNAL(valueChanged(int)),
×
124
            this, SLOT(slotUpdateConnections()));
125
    connect(m_targetTree, SIGNAL(clicked(QModelIndex)),
×
126
            this, SLOT(slotUpdateConnections()));
127
    connect(m_targetTree, SIGNAL(expanded(QModelIndex)),
×
128
            this, SLOT(slotUpdateConnections()));
129
    connect(m_targetTree, SIGNAL(collapsed(QModelIndex)),
×
130
            this, SLOT(slotUpdateConnections()));
131

132
    // retrieve the original project name from the QLC+ App class
133
    App *mainApp = (App *)m_doc->parent();
×
134
    QString prjName = mainApp->fileName();
×
135

136
    if (prjName.lastIndexOf(".") > 0)
×
137
        prjName.insert(prjName.lastIndexOf("."), tr(" (remapped)"));
×
138
    else
139
        prjName.append(tr(" (remapped)"));
×
140

141
    m_targetProjectLabel->setText(prjName);
×
142
}
×
143

144
FixtureRemap::~FixtureRemap()
×
145
{
146
    QSettings settings;
×
147
    settings.setValue(SETTINGS_GEOMETRY, saveGeometry());
×
148

149
    delete m_targetDoc;
×
150
}
×
151

152
QTreeWidgetItem *FixtureRemap::getUniverseItem(Doc *doc, quint32 universe, QTreeWidget *tree)
×
153
{
154
    QTreeWidgetItem *topItem = NULL;
×
155

156
    for (int i = 0; i < tree->topLevelItemCount(); i++)
×
157
    {
158
        QTreeWidgetItem* tItem = tree->topLevelItem(i);
×
159
        quint32 tUni = tItem->text(KColumnUniverse).toUInt();
×
160
        if (tUni == universe)
×
161
        {
162
            topItem = tItem;
×
163
            break;
×
164
        }
165
    }
166

167
    // Haven't found this universe node ? Create it.
168
    if (topItem == NULL)
×
169
    {
170
        topItem = new QTreeWidgetItem(tree);
×
171
        topItem->setText(KColumnName, doc->inputOutputMap()->universes().at(universe)->name());
×
172
        topItem->setText(KColumnUniverse, QString::number(universe));
×
173
        topItem->setText(KColumnID, QString::number(Function::invalidId()));
×
174
        topItem->setExpanded(true);
×
175
    }
176

177
    return topItem;
×
178
}
179

180
void FixtureRemap::fillFixturesTree(Doc *doc, QTreeWidget *tree)
×
181
{
182
    foreach (Fixture *fxi, doc->fixtures())
×
183
    {
184
        quint32 uni = fxi->universe();
×
185
        QTreeWidgetItem *topItem = getUniverseItem(doc, uni, tree);
×
186

187
        quint32 baseAddr = fxi->address();
×
188
        QTreeWidgetItem *fItem = new QTreeWidgetItem(topItem);
×
189
        fItem->setText(KColumnName, fxi->name());
×
190
        fItem->setIcon(KColumnName, fxi->getIconFromType());
×
191
        fItem->setText(KColumnAddress, QString("%1 - %2").arg(baseAddr + 1).arg(baseAddr + fxi->channels()));
×
192
        fItem->setText(KColumnUniverse, QString::number(uni));
×
193
        fItem->setText(KColumnID, QString::number(fxi->id()));
×
194

195
        for (quint32 c = 0; c < fxi->channels(); c++)
×
196
        {
197
            const QLCChannel* channel = fxi->channel(c);
×
198
            QTreeWidgetItem *item = new QTreeWidgetItem(fItem);
×
199
            item->setText(KColumnName, QString("%1:%2").arg(c + 1)
×
200
                          .arg(channel->name()));
×
201
            item->setIcon(KColumnName, channel->getIcon());
×
202
            item->setText(KColumnUniverse, QString::number(uni));
×
203
            item->setText(KColumnID, QString::number(fxi->id()));
×
204
            item->setText(KColumnChIdx, QString::number(c));
×
205
        }
206
    }
×
207

208
    tree->resizeColumnToContents(KColumnName);
×
209
}
×
210

211
QString FixtureRemap::createImportDialog()
×
212
{
213
    QString fileName;
×
214

215
    /* Create a file save dialog */
216
    QFileDialog dialog(this);
×
217
    dialog.setWindowTitle(tr("Import Fixtures List"));
×
218
    dialog.setAcceptMode(QFileDialog::AcceptOpen);
×
219

220
    /* Append file filters to the dialog */
221
    QStringList filters;
×
222
    filters << tr("Fixtures List (*%1)").arg(KExtFixtureList);
×
223
#if defined(WIN32) || defined(Q_OS_WIN)
224
    filters << tr("All Files (*.*)");
225
#else
226
    filters << tr("All Files (*)");
×
227
#endif
228
    dialog.setNameFilters(filters);
×
229

230
    /* Append useful URLs to the dialog */
231
    QList <QUrl> sidebar;
×
232
    sidebar.append(QUrl::fromLocalFile(QDir::homePath()));
×
233
    sidebar.append(QUrl::fromLocalFile(QDir::rootPath()));
×
234
    dialog.setSidebarUrls(sidebar);
×
235

236
    /* Get file name */
237
    if (dialog.exec() != QDialog::Accepted)
×
238
        return "";
×
239

240
    fileName = dialog.selectedFiles().first();
×
241
    if (fileName.isEmpty() == true)
×
242
        return "";
×
243

244
    return fileName;
×
245
}
×
246

247
void FixtureRemap::slotImportFixtures()
×
248
{
249
    QString fileName = createImportDialog();
×
250

251
    QMessageBox msgBox;
×
252
    msgBox.setText(tr("Do you want to automatically connect fixtures with the same name?"));
×
253
    msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
×
254
    msgBox.setDefaultButton(QMessageBox::Yes);
×
255
    bool autoConnect = msgBox.exec() == QMessageBox::Yes ? true : false;
×
256

257
    QXmlStreamReader *doc = QLCFile::getXMLReader(fileName);
×
258
    if (doc == NULL || doc->device() == NULL || doc->hasError())
×
259
    {
260
        qWarning() << Q_FUNC_INFO << "Unable to read from" << fileName;
×
261
        return;
×
262
    }
263

264
    while (!doc->atEnd())
×
265
    {
266
        if (doc->readNext() == QXmlStreamReader::DTD)
×
267
            break;
×
268
    }
269
    if (doc->hasError())
×
270
    {
271
        QLCFile::releaseXMLReader(doc);
×
272
        return;
×
273
    }
274

275
    if (doc->dtdName() == KXMLQLCFixturesList)
×
276
    {
277
        doc->readNextStartElement();
×
278
        if (doc->name() != KXMLQLCFixturesList)
×
279
        {
280
            qWarning() << Q_FUNC_INFO << "Fixture Definition node not found";
×
281
            QLCFile::releaseXMLReader(doc);
×
282
            return;
×
283
        }
284

285
        while (doc->readNextStartElement())
×
286
        {
287
            if (doc->name() == KXMLFixture)
×
288
            {
289
                Fixture* fxi = new Fixture(m_targetDoc);
×
290
                Q_ASSERT(fxi != NULL);
×
291

292
                if (fxi->loadXML(*doc, m_targetDoc, m_doc->fixtureDefCache()) == true)
×
293
                {
294
                    if (m_targetDoc->addFixture(fxi) == false)
×
295
                    {
296
                        qWarning() << Q_FUNC_INFO << "Fixture" << fxi->name() << "cannot be created.";
×
297
                        delete fxi;
×
298
                    }
299
                }
300
                else
301
                {
302
                    qWarning() << Q_FUNC_INFO << "Fixture" << fxi->name() << "cannot be loaded.";
×
303
                    delete fxi;
×
304
                }
305
            }
306
            else if (doc->name() == KXMLQLCFixtureGroup)
×
307
            {
308
                FixtureGroup* grp = new FixtureGroup(m_targetDoc);
×
309
                Q_ASSERT(grp != NULL);
×
310

311
                if (grp->loadXML(*doc) == true)
×
312
                {
313
                    m_targetDoc->addFixtureGroup(grp, grp->id());
×
314
                }
315
                else
316
                {
317
                    qWarning() << Q_FUNC_INFO << "FixtureGroup" << grp->name() << "cannot be loaded.";
×
318
                    delete grp;
×
319
                }
320
            }
321
            else
322
            {
323
                qWarning() << Q_FUNC_INFO << "Unknown label tag:" << doc->name().toString();
×
324
                doc->skipCurrentElement();
×
325
            }
326
        }
327
        fillFixturesTree(m_targetDoc, m_targetTree);
×
328

329
        if (autoConnect)
×
330
        {
331
            for (int tu = 0; tu < m_targetTree->topLevelItemCount(); tu++)
×
332
            {
333
                QTreeWidgetItem *tgtUniItem = m_targetTree->topLevelItem(tu);
×
334

335
                for (int ti = 0; ti < tgtUniItem->childCount(); ti++)
×
336
                {
337
                    QTreeWidgetItem *tgtItem = tgtUniItem->child(ti);
×
338

339
                    for (int su = 0; su < m_sourceTree->topLevelItemCount(); su++)
×
340
                    {
341
                        QTreeWidgetItem *srcUniItem = m_sourceTree->topLevelItem(su);
×
342

343
                        for (int si = 0; si < srcUniItem->childCount(); si++)
×
344
                        {
345
                            QTreeWidgetItem *srcItem = srcUniItem->child(si);
×
346

347
                            if (srcItem->text(KColumnName) == tgtItem->text(KColumnName))
×
348
                            {
349
                                connectFixtures(srcItem, tgtItem);
×
350
                                break;
×
351
                            }
352
                        }
353
                    }
354
                }
355
            }
356
            remapWidget->setRemapList(m_remapList);
×
357
        }
358
    }
359
    QLCFile::releaseXMLReader(doc);
×
360
}
×
361

362
void FixtureRemap::slotAddTargetFixture()
×
363
{
364
    AddFixture af(this, m_targetDoc);
×
365
    if (af.exec() == QDialog::Rejected)
×
366
        return;
×
367

368
    QString name = af.name();
×
369
    quint32 address = af.address();
×
370
    quint32 universe = af.universe();
×
371
    quint32 channels = af.channels();
×
372
    QLCFixtureDef* fixtureDef = af.fixtureDef();
×
373
    QLCFixtureMode* mode = af.mode();
×
374
    int gap = af.gap();
×
375

376
    for (int i = 0; i < af.amount(); i++)
×
377
    {
378
        QString modname;
×
379

380
        /* If an empty name was given use the model instead */
381
        if (name.simplified().isEmpty())
×
382
        {
383
            if (fixtureDef != NULL)
×
384
                name = fixtureDef->model();
×
385
            else
386
                name = tr("Generic Dimmer");
×
387
        }
388

389
        /* If we're adding more than one fixture,
390
           append a number to the end of the name */
391
        if (af.amount() > 1)
×
392
            modname = QString("%1 #%2").arg(name).arg(i+1);
×
393
        else
394
            modname = name;
×
395

396
        /* Create the target fixture */
397
        Fixture* fxi = new Fixture(m_targetDoc);
×
398

399
        /* Add the first fixture without gap, at the given address */
400
        fxi->setAddress(address + (i * channels) + (i * gap));
×
401
        fxi->setUniverse(universe);
×
402
        fxi->setName(modname);
×
403

404
        /* Set a fixture definition & mode if they were selected.
405
           Otherwise assign channels to a generic dimmer. */
406
        if (fixtureDef != NULL && mode != NULL)
×
407
            fxi->setFixtureDefinition(fixtureDef, mode);
×
408
        else
409
        {
410
            fixtureDef = fxi->genericDimmerDef(channels);
×
411
            mode = fxi->genericDimmerMode(fixtureDef, channels);
×
412
            fxi->setFixtureDefinition(fixtureDef, mode);
×
413
            //fxi->setChannels(channels);
414
        }
415

416
        m_targetDoc->addFixture(fxi);
×
417

418
        QTreeWidgetItem *topItem = getUniverseItem(m_targetDoc, universe, m_targetTree);
×
419

420
        quint32 baseAddr = fxi->address();
×
421
        QTreeWidgetItem *fItem = new QTreeWidgetItem(topItem);
×
422
        fItem->setText(KColumnName, fxi->name());
×
423
        fItem->setIcon(KColumnName, fxi->getIconFromType());
×
424
        fItem->setText(KColumnAddress, QString("%1 - %2").arg(baseAddr + 1).arg(baseAddr + fxi->channels()));
×
425
        fItem->setText(KColumnUniverse, QString::number(universe));
×
426
        fItem->setText(KColumnID, QString::number(fxi->id()));
×
427

428
        for (quint32 c = 0; c < fxi->channels(); c++)
×
429
        {
430
            const QLCChannel* channel = fxi->channel(c);
×
431
            QTreeWidgetItem *item = new QTreeWidgetItem(fItem);
×
432
            item->setText(KColumnName, QString("%1:%2").arg(c + 1)
×
433
                          .arg(channel->name()));
×
434
            item->setIcon(KColumnName, channel->getIcon());
×
435
            item->setText(KColumnUniverse, QString::number(universe));
×
436
            item->setText(KColumnID, QString::number(fxi->id()));
×
437
            item->setText(KColumnChIdx, QString::number(c));
×
438
        }
439
    }
×
440
    m_targetTree->resizeColumnToContents(KColumnName);
×
441

442
    qDebug() << "Fixtures in target doc:" << m_targetDoc->fixtures().count();
×
443
}
×
444

445
void FixtureRemap::slotRemoveTargetFixture()
×
446
{
447
    if (m_targetTree->selectedItems().count() == 0)
×
448
        return;
×
449

450
    QTreeWidgetItem *item = m_targetTree->selectedItems().first();
×
451
    bool ok = false;
×
452
    quint32 fxid = item->text(KColumnID).toUInt(&ok);
×
453
    if (ok == false)
×
454
        return;
×
455

456
    // Ask before deletion
457
    if (QMessageBox::question(this, tr("Delete Fixtures"),
×
458
                              tr("Do you want to delete the selected items?"),
×
459
                              QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
×
460
    {
461
        return;
×
462
    }
463

464
    int i = 0;
×
465
    QListIterator <RemapInfo> it(m_remapList);
×
466
    while (it.hasNext() == true)
×
467
    {
468
        RemapInfo info = it.next();
×
469
        quint32 tgtID = info.target->text(KColumnID).toUInt();
×
470
        if (tgtID == fxid)
×
471
            m_remapList.takeAt(i);
×
472
        else
473
            i++;
×
474
    }
475
    remapWidget->setRemapList(m_remapList);
×
476
    m_targetDoc->deleteFixture(fxid);
×
477
    for (int i = 0; i < item->childCount(); i++)
×
478
    {
479
        QTreeWidgetItem *child = item->child(i);
×
480
        delete child;
×
481
    }
482
    delete item;
×
483
    m_targetTree->resizeColumnToContents(KColumnName);
×
484

485
    qDebug() << "Fixtures in target doc:" << m_targetDoc->fixtures().count();
×
486
}
×
487

488
void FixtureRemap::slotCloneSourceFixture()
×
489
{
490
    if (m_sourceTree->selectedItems().count() == 0)
×
491
        return; // popup here ??
×
492

493
    QTreeWidgetItem *sItem = m_sourceTree->selectedItems().first();
×
494
    quint32 fxID = sItem->text(KColumnID).toUInt();
×
495
    Fixture *srcFix = m_doc->fixture(fxID);
×
496
    if (srcFix == NULL)
×
497
        return; // popup here ?
×
498

499
    quint32 srcAddr = srcFix->universeAddress();
×
500
    for (quint32 i = srcAddr; i < srcAddr + srcFix->channels(); i++)
×
501
    {
502
        quint32 fxCheck = m_targetDoc->fixtureForAddress(i);
×
503
        if (fxCheck != Fixture::invalidId())
×
504
        {
505
            QMessageBox::warning(this,
×
506
                                 tr("Invalid operation"),
×
507
                                 tr("You are trying to clone a fixture on an address already in use. "
×
508
                                    "Please fix the target list first."));
509
            return;
×
510
        }
511
    }
512

513
    // create a copy of the fixture and add it to the target document
514
    /* Create the target fixture */
515
    Fixture* tgtFix = new Fixture(m_targetDoc);
×
516

517
    /* Add the first fixture without gap, at the given address */
518
    tgtFix->setAddress(srcFix->address());
×
519
    tgtFix->setUniverse(srcFix->universe());
×
520
    tgtFix->setName(srcFix->name());
×
521

522
    /* Set a fixture definition & mode if they were selected.
523
       Otherwise assign channels to a generic dimmer. */
524
    if (srcFix->fixtureDef()->manufacturer() == KXMLFixtureGeneric &&
×
525
        srcFix->fixtureDef()->model() == KXMLFixtureGeneric)
×
526
            tgtFix->setChannels(srcFix->channels());
×
527
    else
528
        tgtFix->setFixtureDefinition(srcFix->fixtureDef(), srcFix->fixtureMode());
×
529

530
    m_targetDoc->addFixture(tgtFix);
×
531

532
    // create the tree element and add it to the target tree
533
    QTreeWidgetItem *topItem = getUniverseItem(m_targetDoc, tgtFix->universe(), m_targetTree);
×
534
    quint32 baseAddr = tgtFix->address();
×
535
    QTreeWidgetItem *fItem = new QTreeWidgetItem(topItem);
×
536
    fItem->setText(KColumnName, tgtFix->name());
×
537
    fItem->setIcon(KColumnName, tgtFix->getIconFromType());
×
538
    fItem->setText(KColumnAddress, QString("%1 - %2").arg(baseAddr + 1).arg(baseAddr + tgtFix->channels()));
×
539
    fItem->setText(KColumnUniverse, QString::number(tgtFix->universe()));
×
540
    fItem->setText(KColumnID, QString::number(tgtFix->id()));
×
541

542
    for (quint32 c = 0; c < tgtFix->channels(); c++)
×
543
    {
544
        const QLCChannel* channel = tgtFix->channel(c);
×
545
        QTreeWidgetItem *item = new QTreeWidgetItem(fItem);
×
546
        item->setText(KColumnName, QString("%1:%2").arg(c + 1)
×
547
                      .arg(channel->name()));
×
548
        item->setIcon(KColumnName, channel->getIcon());
×
549
        item->setText(KColumnUniverse, QString::number(tgtFix->universe()));
×
550
        item->setText(KColumnID, QString::number(tgtFix->id()));
×
551
        item->setText(KColumnChIdx, QString::number(c));
×
552
    }
553

554
    m_targetTree->resizeColumnToContents(KColumnName);
×
555

556
    foreach (QTreeWidgetItem *it, m_targetTree->selectedItems())
×
557
        it->setSelected(false);
×
558
    fItem->setSelected(true);
×
559

560
    slotAddRemap();
×
561
}
562

563
void FixtureRemap::slotAddRemap()
×
564
{
565
    if (m_sourceTree->selectedItems().count() == 0 ||
×
566
        m_targetTree->selectedItems().count() == 0)
×
567
    {
568
        QMessageBox::warning(this,
×
569
                tr("Invalid selection"),
×
570
                tr("Please select a source and a target fixture or channel to perform this operation."));
×
571
        return;
×
572
    }
573

574
    connectFixtures(m_sourceTree->selectedItems().first(),
×
575
                    m_targetTree->selectedItems().first());
×
576

577
    remapWidget->setRemapList(m_remapList);
×
578
}
579

580
void FixtureRemap::connectFixtures(QTreeWidgetItem *sourceItem, QTreeWidgetItem *targetItem)
×
581
{
582
    if (sourceItem == NULL || targetItem == NULL)
×
583
        return;
×
584

585
    quint32 srcFxiID = sourceItem->text(KColumnID).toUInt();
×
586
    Fixture *srcFxi = m_doc->fixture(srcFxiID);
×
587
    quint32 tgtFxiID = targetItem->text(KColumnID).toUInt();
×
588
    Fixture *tgtFxi = m_targetDoc->fixture(tgtFxiID);
×
589
    if (srcFxi == NULL || tgtFxi == NULL)
×
590
    {
591
        QMessageBox::warning(this,
×
592
                tr("Invalid selection"),
×
593
                tr("Please select a source and a target fixture or channel to perform this operation."));
×
594
        return;
×
595
    }
596

597
    bool ok = false;
×
598
    int srcIdx = sourceItem->text(KColumnChIdx).toInt(&ok);
×
599
    bool srcFxiSelected = !ok;
×
600
    ok = false;
×
601
    int tgtIdx = targetItem->text(KColumnChIdx).toInt(&ok);
×
602
    bool tgtFxiSelected = !ok;
×
603

604
    qDebug() << "Idx:" << srcIdx << ", src:" << srcFxiSelected << ", tgt:" << tgtFxiSelected;
×
605

606
    if (srcFxiSelected != tgtFxiSelected)
×
607
    {
608
        QMessageBox::warning(this,
×
609
                             tr("Invalid selection"),
×
610
                             tr("To perform a fixture remap, please select fixtures on both lists."));
×
611
        return;
×
612
    }
613

614
    if (srcFxiSelected && tgtFxiSelected)
×
615
    {
616
        // Fixture-level remap: delegate channel matching to the engine class
617
        if (m_remapNamesCheck->isChecked())
×
618
        {
619
            tgtFxi->setName(srcFxi->name());
×
620
            targetItem->setText(KColumnName, srcFxi->name());
×
621
        }
622

623
        QList<QPair<quint32, quint32>> pairs = m_remapper.autoConnectFixtures(srcFxi, tgtFxi);
×
624
        for (const QPair<quint32, quint32> &p : pairs)
×
625
        {
626
            RemapInfo matchInfo;
627
            matchInfo.source = sourceItem->child(p.first);
×
628
            matchInfo.target = targetItem->child(p.second);
×
629
            m_remapList.append(matchInfo);
×
630
        }
631
    }
×
632
    else
633
    {
634
        // Single channel remap
635
        RemapInfo newRemap;
636
        newRemap.source = sourceItem;
×
637
        newRemap.target = targetItem;
×
638
        m_remapList.append(newRemap);
×
639
        m_remapper.addChannelRemap(srcFxiID, srcIdx, tgtFxiID, tgtIdx);
×
640

641
        if (srcFxi->channelCanFade(srcIdx) == false)
×
642
            tgtFxi->setChannelCanFade(tgtIdx, false);
×
643
    }
644
}
645

646
void FixtureRemap::slotRemoveRemap()
×
647
{
648
    if (m_sourceTree->selectedItems().count() == 0 ||
×
649
        m_targetTree->selectedItems().count() == 0)
×
650
    {
651
        QMessageBox::warning(this,
×
652
                             tr("Invalid selection"),
×
653
                             tr("Please select a source and a target fixture or channel to perform this operation."));
×
654
        return;
×
655
    }
656

657
    RemapInfo delRemap;
658
    delRemap.source = m_sourceTree->selectedItems().first();
×
659
    delRemap.target = m_targetTree->selectedItems().first();
×
660

661
    bool tgtFxiSelected = false;
×
662
    bool fxok = false, chok = false;
×
663
    quint32 fxid = delRemap.target->text(KColumnID).toUInt(&fxok);
×
664
    delRemap.target->text(KColumnChIdx).toInt(&chok);
×
665
    if (fxok == true && chok == false)
×
666
        tgtFxiSelected = true;
×
667

668
    for (int i = 0; i < m_remapList.count(); i++)
×
669
    {
670
        RemapInfo info = m_remapList.at(i);
×
671
        // full fixture remap delete
672
        if (tgtFxiSelected == true)
×
673
        {
674
            quint32 rmpFxID = info.target->text(KColumnID).toUInt();
×
675
            if (rmpFxID == fxid)
×
676
            {
677
                m_remapList.takeAt(i);
×
678
                i--;
×
679
            }
680
        }
681
        // single channel remap delete. Source and target must match
682
        else if (info.source == delRemap.source && info.target == delRemap.target)
×
683
        {
684
            m_remapList.takeAt(i);
×
685
            i--;
×
686
        }
687
    }
688
    remapWidget->setRemapList(m_remapList);
×
689
}
690

691
void FixtureRemap::slotUpdateConnections()
×
692
{
693
    remapWidget->update();
×
694
    m_sourceTree->resizeColumnToContents(KColumnName);
×
695
    m_targetTree->resizeColumnToContents(KColumnName);
×
696
}
×
697

698
void FixtureRemap::slotSourceSelectionChanged()
×
699
{
700
    if (m_sourceTree->selectedItems().count() > 0)
×
701
    {
702
        QTreeWidgetItem *item = m_sourceTree->selectedItems().first();
×
703
        bool fxOK = false, chOK = false;
×
704
        item->text(KColumnID).toUInt(&fxOK);
×
705
        item->text(KColumnChIdx).toInt(&chOK);
×
706
        if (fxOK == true && chOK == false)
×
707
            m_cloneButton->setEnabled(true);
×
708
        else
709
            m_cloneButton->setEnabled(false);
×
710
    }
711
    else
712
        m_cloneButton->setEnabled(false);
×
713
}
×
714

715
void FixtureRemap::accept()
×
716
{
717
    QProgressDialog progress(tr("This might take a while..."), tr("Cancel"), 0, 100, this);
×
718
    progress.setWindowModality(Qt::WindowModal);
×
719
    progress.show();
×
720
    QApplication::processEvents();
×
721

722
    /* Steps 1-5: replace fixtures, remap groups / functions / monitor */
723
    m_remapper.applyRemap(m_doc, m_targetDoc->fixtures());
×
724

725
    progress.setValue(70);
×
726
    QApplication::processEvents();
×
727

728
    /* Step 6 - remap Virtual Console widgets (UI types, stays here) */
729
    const QList<SceneValue> &sourceList = m_remapper.sourceList();
×
730
    const QList<SceneValue> &targetList = m_remapper.targetList();
×
731

732
    VCFrame* contents = VirtualConsole::instance()->contents();
×
733
    QList<VCWidget *> widgetsList = contents->findChildren<VCWidget*>();
×
734

735
    foreach (VCWidget *widget, widgetsList)
×
736
    {
737
        switch (widget->type())
×
738
        {
739
            case VCWidget::SliderWidget:
×
740
            {
741
                VCSlider *slider = qobject_cast<VCSlider*>(widget);
×
742
                if (slider->sliderMode() == VCSlider::Level)
×
743
                {
744
                    QList<SceneValue> newChannels;
×
745
                    foreach (VCSlider::LevelChannel chan, slider->levelChannels())
×
746
                    {
747
                        for (int v = 0; v < sourceList.count(); v++)
×
748
                        {
749
                            const SceneValue &val = sourceList.at(v);
×
750
                            if (val.fxi == chan.fixture && val.channel == chan.channel)
×
751
                                newChannels.append(SceneValue(targetList.at(v).fxi, targetList.at(v).channel));
×
752
                        }
753
                    }
×
754
                    slider->clearLevelChannels();
×
755
                    foreach (SceneValue rmpChan, newChannels)
×
756
                        slider->addLevelChannel(rmpChan.fxi, rmpChan.channel);
×
757
                }
×
758
            }
759
            break;
×
760
            case VCWidget::AudioTriggersWidget:
×
761
            {
762
                VCAudioTriggers *triggers = qobject_cast<VCAudioTriggers*>(widget);
×
763
                foreach (AudioBar *bar, triggers->getAudioBars())
×
764
                {
765
                    if (bar->m_type == AudioBar::BarType::DMXBar)
×
766
                    {
767
                        QList<SceneValue> newList = FixtureRemapper::remapSceneValues(
768
                                bar->m_dmxChannels, sourceList, targetList);
×
769
                        bar->attachDmxChannels(m_doc, newList);
×
770
                    }
×
771
                }
×
772
            }
773
            break;
×
774
            case VCWidget::XYPadWidget:
×
775
            {
776
                VCXYPad *xypad = qobject_cast<VCXYPad*>(widget);
×
777
                QList<VCXYPadFixture> copyFixtures;
×
778
                foreach (VCXYPadFixture fix, xypad->fixtures())
×
779
                {
780
                    quint32 srcFxID = fix.head().fxi;
×
781
                    for (int i = 0; i < sourceList.count(); i++)
×
782
                    {
783
                        const SceneValue &val = sourceList.at(i);
×
784
                        if (val.fxi == srcFxID)
×
785
                        {
786
                            const SceneValue &tgtVal = targetList.at(i);
×
787
                            Fixture *docFix = m_doc->fixture(tgtVal.fxi);
×
788
                            if (docFix == NULL)
×
789
                                continue;
×
790
                            const QLCChannel *chan = docFix->channel(tgtVal.channel);
×
791
                            if (chan && (chan->group() == QLCChannel::Pan ||
×
792
                                        chan->group() == QLCChannel::Tilt))
×
793
                            {
794
                                VCXYPadFixture tgtFix(m_doc);
×
795
                                tgtFix.setHead(GroupHead(tgtVal.fxi, 0));
×
796
                                copyFixtures.append(tgtFix);
×
797
                            }
×
798
                        }
799
                    }
800
                }
×
801
                xypad->clearFixtures();
×
802
                foreach (VCXYPadFixture fix, copyFixtures)
×
803
                    xypad->appendFixture(fix);
×
804
            }
×
805
            break;
×
806
            default:
×
807
            break;
×
808
        }
809

810
        if (progress.wasCanceled())
×
811
            break;
×
812
    }
×
813

814
    progress.setValue(95);
×
815
    QApplication::processEvents();
×
816

817
    /* Step 7 - save remapped project */
818
    App *mainApp = (App *)m_doc->parent();
×
819
    if (m_targetProjectLabel->text().endsWith(".qxw") == false)
×
820
        m_targetProjectLabel->setText(m_targetProjectLabel->text() + ".qxw");
×
821
    mainApp->setFileName(m_targetProjectLabel->text());
×
822
    mainApp->slotFileSave();
×
823

824
    progress.hide();
×
825
    QDialog::accept();
×
826
}
×
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