• 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

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 "monitorproperties.h"
30
#include "vcaudiotriggers.h"
31
#include "virtualconsole.h"
32
#include "qlcfixturemode.h"
33
#include "qlcfixturedef.h"
34
#include "channelsgroup.h"
35
#include "fixtureremap.h"
36
#include "remapwidget.h"
37
#include "qlcchannel.h"
38
#include "addfixture.h"
39
#include "efxfixture.h"
40
#include "scenevalue.h"
41
#include "chaserstep.h"
42
#include "audiobar.h"
43
#include "vcslider.h"
44
#include "sequence.h"
45
#include "qlcfile.h"
46
#include "vcxypad.h"
47
#include "vcframe.h"
48
#include "chaser.h"
49
#include "scene.h"
50
#include "efx.h"
51
#include "doc.h"
52
#include "app.h"
53

54
#define KColumnName         0
55
#define KColumnAddress      1
56
#define KColumnUniverse     2
57
#define KColumnID           3
58
#define KColumnChIdx        4
59

60
#define SETTINGS_GEOMETRY "fixturemap/geometry"
61

62
FixtureRemap::FixtureRemap(Doc *doc, QWidget *parent)
×
63
    : QDialog(parent)
64
    , m_doc(doc)
×
65
{
66
    Q_ASSERT(doc != NULL);
×
67

68
    setupUi(this);
×
69

70
    QSettings settings;
×
71
    QVariant geometrySettings = settings.value(SETTINGS_GEOMETRY);
×
72
    if (geometrySettings.isValid() == true)
×
73
        restoreGeometry(geometrySettings.toByteArray());
×
74

75
    connect(m_importButton, SIGNAL(clicked()),
×
76
            this, SLOT(slotImportFixtures()));
77
    connect(m_addButton, SIGNAL(clicked()),
×
78
            this, SLOT(slotAddTargetFixture()));
79
    connect(m_removeButton, SIGNAL(clicked()),
×
80
            this, SLOT(slotRemoveTargetFixture()));
81
    connect(m_cloneButton, SIGNAL(clicked()),
×
82
            this, SLOT(slotCloneSourceFixture()));
83
    connect(m_remapButton, SIGNAL(clicked()),
×
84
            this, SLOT(slotAddRemap()));
85
    connect(m_unmapButton, SIGNAL(clicked()),
×
86
            this, SLOT(slotRemoveRemap()));
87

88
    m_cloneButton->setEnabled(false);
×
89

90
    remapWidget = new RemapWidget(m_sourceTree, m_targetTree, this);
×
91
    remapWidget->show();
×
92
    m_remapLayout->addWidget(remapWidget);
×
93

94
    m_targetDoc = new Doc(this);
×
95
    /* Load user fixtures first so that they override system fixtures */
96
    m_targetDoc->fixtureDefCache()->load(QLCFixtureDefCache::userDefinitionDirectory());
×
97
    m_targetDoc->fixtureDefCache()->loadMap(QLCFixtureDefCache::systemDefinitionDirectory());
×
98

99
    /* Remove the default set of universes from the target Doc and re-fill it
100
     * with the current Doc universe list */
101
    m_targetDoc->inputOutputMap()->removeAllUniverses();
×
102

103
    int index = 0;
×
104
    foreach (Universe *uni, m_doc->inputOutputMap()->universes())
×
105
    {
106
        m_targetDoc->inputOutputMap()->addUniverse(uni->id());
×
107
        m_targetDoc->inputOutputMap()->setUniverseName(index, uni->name());
×
108
        m_targetDoc->inputOutputMap()->startUniverses();
×
109
        index++;
×
110
    }
×
111

112
    m_sourceTree->setIconSize(QSize(24, 24));
×
113
    m_sourceTree->setAllColumnsShowFocus(true);
×
114
    fillFixturesTree(m_doc, m_sourceTree);
×
115

116
    m_targetTree->setIconSize(QSize(24, 24));
×
117
    m_targetTree->setAllColumnsShowFocus(true);
×
118

119
    connect(m_sourceTree->verticalScrollBar(), SIGNAL(valueChanged(int)),
×
120
            this, SLOT(slotUpdateConnections()));
121
    connect(m_sourceTree, SIGNAL(clicked(QModelIndex)),
×
122
            this, SLOT(slotUpdateConnections()));
123
    connect(m_sourceTree, SIGNAL(expanded(QModelIndex)),
×
124
            this, SLOT(slotUpdateConnections()));
125
    connect(m_sourceTree, SIGNAL(collapsed(QModelIndex)),
×
126
            this, SLOT(slotUpdateConnections()));
127
    connect(m_sourceTree, SIGNAL(itemSelectionChanged()),
×
128
            this, SLOT(slotSourceSelectionChanged()));
129

130
    connect(m_targetTree->verticalScrollBar(), SIGNAL(valueChanged(int)),
×
131
            this, SLOT(slotUpdateConnections()));
132
    connect(m_targetTree, SIGNAL(clicked(QModelIndex)),
×
133
            this, SLOT(slotUpdateConnections()));
134
    connect(m_targetTree, SIGNAL(expanded(QModelIndex)),
×
135
            this, SLOT(slotUpdateConnections()));
136
    connect(m_targetTree, SIGNAL(collapsed(QModelIndex)),
×
137
            this, SLOT(slotUpdateConnections()));
138

139
    // retrieve the original project name from the QLC+ App class
140
    App *mainApp = (App *)m_doc->parent();
×
141
    QString prjName = mainApp->fileName();
×
142

143
    if (prjName.lastIndexOf(".") > 0)
×
144
        prjName.insert(prjName.lastIndexOf("."), tr(" (remapped)"));
×
145
    else
146
        prjName.append(tr(" (remapped)"));
×
147

148
    m_targetProjectLabel->setText(prjName);
×
149
}
×
150

151
FixtureRemap::~FixtureRemap()
×
152
{
153
    QSettings settings;
×
154
    settings.setValue(SETTINGS_GEOMETRY, saveGeometry());
×
155

156
    delete m_targetDoc;
×
157
}
×
158

159
QTreeWidgetItem *FixtureRemap::getUniverseItem(Doc *doc, quint32 universe, QTreeWidget *tree)
×
160
{
161
    QTreeWidgetItem *topItem = NULL;
×
162

163
    for (int i = 0; i < tree->topLevelItemCount(); i++)
×
164
    {
165
        QTreeWidgetItem* tItem = tree->topLevelItem(i);
×
166
        quint32 tUni = tItem->text(KColumnUniverse).toUInt();
×
167
        if (tUni == universe)
×
168
        {
169
            topItem = tItem;
×
170
            break;
×
171
        }
172
    }
173

174
    // Haven't found this universe node ? Create it.
175
    if (topItem == NULL)
×
176
    {
177
        topItem = new QTreeWidgetItem(tree);
×
178
        topItem->setText(KColumnName, doc->inputOutputMap()->universes().at(universe)->name());
×
179
        topItem->setText(KColumnUniverse, QString::number(universe));
×
180
        topItem->setText(KColumnID, QString::number(Function::invalidId()));
×
181
        topItem->setExpanded(true);
×
182
    }
183

184
    return topItem;
×
185
}
186

187
void FixtureRemap::fillFixturesTree(Doc *doc, QTreeWidget *tree)
×
188
{
189
    foreach (Fixture *fxi, doc->fixtures())
×
190
    {
191
        quint32 uni = fxi->universe();
×
192
        QTreeWidgetItem *topItem = getUniverseItem(doc, uni, tree);
×
193

194
        quint32 baseAddr = fxi->address();
×
195
        QTreeWidgetItem *fItem = new QTreeWidgetItem(topItem);
×
196
        fItem->setText(KColumnName, fxi->name());
×
197
        fItem->setIcon(KColumnName, fxi->getIconFromType());
×
198
        fItem->setText(KColumnAddress, QString("%1 - %2").arg(baseAddr + 1).arg(baseAddr + fxi->channels()));
×
199
        fItem->setText(KColumnUniverse, QString::number(uni));
×
200
        fItem->setText(KColumnID, QString::number(fxi->id()));
×
201

202
        for (quint32 c = 0; c < fxi->channels(); c++)
×
203
        {
204
            const QLCChannel* channel = fxi->channel(c);
×
205
            QTreeWidgetItem *item = new QTreeWidgetItem(fItem);
×
206
            item->setText(KColumnName, QString("%1:%2").arg(c + 1)
×
207
                          .arg(channel->name()));
×
208
            item->setIcon(KColumnName, channel->getIcon());
×
209
            item->setText(KColumnUniverse, QString::number(uni));
×
210
            item->setText(KColumnID, QString::number(fxi->id()));
×
211
            item->setText(KColumnChIdx, QString::number(c));
×
212
        }
213
    }
×
214

215
    tree->resizeColumnToContents(KColumnName);
×
216
}
×
217

218
QString FixtureRemap::createImportDialog()
×
219
{
220
    QString fileName;
×
221

222
    /* Create a file save dialog */
223
    QFileDialog dialog(this);
×
224
    dialog.setWindowTitle(tr("Import Fixtures List"));
×
225
    dialog.setAcceptMode(QFileDialog::AcceptOpen);
×
226

227
    /* Append file filters to the dialog */
228
    QStringList filters;
×
229
    filters << tr("Fixtures List (*%1)").arg(KExtFixtureList);
×
230
#if defined(WIN32) || defined(Q_OS_WIN)
231
    filters << tr("All Files (*.*)");
232
#else
233
    filters << tr("All Files (*)");
×
234
#endif
235
    dialog.setNameFilters(filters);
×
236

237
    /* Append useful URLs to the dialog */
238
    QList <QUrl> sidebar;
×
239
    sidebar.append(QUrl::fromLocalFile(QDir::homePath()));
×
240
    sidebar.append(QUrl::fromLocalFile(QDir::rootPath()));
×
241
    dialog.setSidebarUrls(sidebar);
×
242

243
    /* Get file name */
244
    if (dialog.exec() != QDialog::Accepted)
×
245
        return "";
×
246

247
    fileName = dialog.selectedFiles().first();
×
248
    if (fileName.isEmpty() == true)
×
249
        return "";
×
250

251
    return fileName;
×
252
}
×
253

254
void FixtureRemap::slotImportFixtures()
×
255
{
256
    QString fileName = createImportDialog();
×
257

258
    QMessageBox msgBox;
×
259
    msgBox.setText(tr("Do you want to automatically connect fixtures with the same name?"));
×
260
    msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
×
261
    msgBox.setDefaultButton(QMessageBox::Yes);
×
262
    bool autoConnect = msgBox.exec() == QMessageBox::Yes ? true : false;
×
263

264
    QXmlStreamReader *doc = QLCFile::getXMLReader(fileName);
×
265
    if (doc == NULL || doc->device() == NULL || doc->hasError())
×
266
    {
267
        qWarning() << Q_FUNC_INFO << "Unable to read from" << fileName;
×
268
        return;
×
269
    }
270

271
    while (!doc->atEnd())
×
272
    {
273
        if (doc->readNext() == QXmlStreamReader::DTD)
×
274
            break;
×
275
    }
276
    if (doc->hasError())
×
277
    {
278
        QLCFile::releaseXMLReader(doc);
×
279
        return;
×
280
    }
281

282
    if (doc->dtdName() == KXMLQLCFixturesList)
×
283
    {
284
        doc->readNextStartElement();
×
285
        if (doc->name() != KXMLQLCFixturesList)
×
286
        {
287
            qWarning() << Q_FUNC_INFO << "Fixture Definition node not found";
×
288
            QLCFile::releaseXMLReader(doc);
×
289
            return;
×
290
        }
291

292
        while (doc->readNextStartElement())
×
293
        {
294
            if (doc->name() == KXMLFixture)
×
295
            {
296
                Fixture* fxi = new Fixture(m_targetDoc);
×
297
                Q_ASSERT(fxi != NULL);
×
298

299
                if (fxi->loadXML(*doc, m_targetDoc, m_doc->fixtureDefCache()) == true)
×
300
                {
301
                    if (m_targetDoc->addFixture(fxi) == false)
×
302
                    {
303
                        qWarning() << Q_FUNC_INFO << "Fixture" << fxi->name() << "cannot be created.";
×
304
                        delete fxi;
×
305
                    }
306
                }
307
                else
308
                {
309
                    qWarning() << Q_FUNC_INFO << "Fixture" << fxi->name() << "cannot be loaded.";
×
310
                    delete fxi;
×
311
                }
312
            }
313
            else if (doc->name() == KXMLQLCFixtureGroup)
×
314
            {
315
                FixtureGroup* grp = new FixtureGroup(m_targetDoc);
×
316
                Q_ASSERT(grp != NULL);
×
317

318
                if (grp->loadXML(*doc) == true)
×
319
                {
320
                    m_targetDoc->addFixtureGroup(grp, grp->id());
×
321
                }
322
                else
323
                {
324
                    qWarning() << Q_FUNC_INFO << "FixtureGroup" << grp->name() << "cannot be loaded.";
×
325
                    delete grp;
×
326
                }
327
            }
328
            else
329
            {
330
                qWarning() << Q_FUNC_INFO << "Unknown label tag:" << doc->name().toString();
×
331
                doc->skipCurrentElement();
×
332
            }
333
        }
334
        fillFixturesTree(m_targetDoc, m_targetTree);
×
335

336
        if (autoConnect)
×
337
        {
338
            for (int tu = 0; tu < m_targetTree->topLevelItemCount(); tu++)
×
339
            {
340
                QTreeWidgetItem *tgtUniItem = m_targetTree->topLevelItem(tu);
×
341

342
                for (int ti = 0; ti < tgtUniItem->childCount(); ti++)
×
343
                {
344
                    QTreeWidgetItem *tgtItem = tgtUniItem->child(ti);
×
345

346
                    for (int su = 0; su < m_sourceTree->topLevelItemCount(); su++)
×
347
                    {
348
                        QTreeWidgetItem *srcUniItem = m_sourceTree->topLevelItem(su);
×
349

350
                        for (int si = 0; si < srcUniItem->childCount(); si++)
×
351
                        {
352
                            QTreeWidgetItem *srcItem = srcUniItem->child(si);
×
353

354
                            if (srcItem->text(KColumnName) == tgtItem->text(KColumnName))
×
355
                            {
356
                                connectFixtures(srcItem, tgtItem);
×
357
                                break;
×
358
                            }
359
                        }
360
                    }
361
                }
362
            }
363
            remapWidget->setRemapList(m_remapList);
×
364
        }
365
    }
366
    QLCFile::releaseXMLReader(doc);
×
367
}
×
368

369
void FixtureRemap::slotAddTargetFixture()
×
370
{
371
    AddFixture af(this, m_targetDoc);
×
372
    if (af.exec() == QDialog::Rejected)
×
373
        return;
×
374

375
    QString name = af.name();
×
376
    quint32 address = af.address();
×
377
    quint32 universe = af.universe();
×
378
    quint32 channels = af.channels();
×
379
    QLCFixtureDef* fixtureDef = af.fixtureDef();
×
380
    QLCFixtureMode* mode = af.mode();
×
381
    int gap = af.gap();
×
382

383
    for (int i = 0; i < af.amount(); i++)
×
384
    {
385
        QString modname;
×
386

387
        /* If an empty name was given use the model instead */
388
        if (name.simplified().isEmpty())
×
389
        {
390
            if (fixtureDef != NULL)
×
391
                name = fixtureDef->model();
×
392
            else
393
                name = tr("Generic Dimmer");
×
394
        }
395

396
        /* If we're adding more than one fixture,
397
           append a number to the end of the name */
398
        if (af.amount() > 1)
×
399
            modname = QString("%1 #%2").arg(name).arg(i+1);
×
400
        else
401
            modname = name;
×
402

403
        /* Create the target fixture */
404
        Fixture* fxi = new Fixture(m_targetDoc);
×
405

406
        /* Add the first fixture without gap, at the given address */
407
        fxi->setAddress(address + (i * channels) + (i * gap));
×
408
        fxi->setUniverse(universe);
×
409
        fxi->setName(modname);
×
410

411
        /* Set a fixture definition & mode if they were selected.
412
           Otherwise assign channels to a generic dimmer. */
413
        if (fixtureDef != NULL && mode != NULL)
×
414
            fxi->setFixtureDefinition(fixtureDef, mode);
×
415
        else
416
        {
417
            fixtureDef = fxi->genericDimmerDef(channels);
×
418
            mode = fxi->genericDimmerMode(fixtureDef, channels);
×
419
            fxi->setFixtureDefinition(fixtureDef, mode);
×
420
            //fxi->setChannels(channels);
421
        }
422

423
        m_targetDoc->addFixture(fxi);
×
424

425
        QTreeWidgetItem *topItem = getUniverseItem(m_targetDoc, universe, m_targetTree);
×
426

427
        quint32 baseAddr = fxi->address();
×
428
        QTreeWidgetItem *fItem = new QTreeWidgetItem(topItem);
×
429
        fItem->setText(KColumnName, fxi->name());
×
430
        fItem->setIcon(KColumnName, fxi->getIconFromType());
×
431
        fItem->setText(KColumnAddress, QString("%1 - %2").arg(baseAddr + 1).arg(baseAddr + fxi->channels()));
×
432
        fItem->setText(KColumnUniverse, QString::number(universe));
×
433
        fItem->setText(KColumnID, QString::number(fxi->id()));
×
434

435
        for (quint32 c = 0; c < fxi->channels(); c++)
×
436
        {
437
            const QLCChannel* channel = fxi->channel(c);
×
438
            QTreeWidgetItem *item = new QTreeWidgetItem(fItem);
×
439
            item->setText(KColumnName, QString("%1:%2").arg(c + 1)
×
440
                          .arg(channel->name()));
×
441
            item->setIcon(KColumnName, channel->getIcon());
×
442
            item->setText(KColumnUniverse, QString::number(universe));
×
443
            item->setText(KColumnID, QString::number(fxi->id()));
×
444
            item->setText(KColumnChIdx, QString::number(c));
×
445
        }
446
    }
×
447
    m_targetTree->resizeColumnToContents(KColumnName);
×
448

449
    qDebug() << "Fixtures in target doc:" << m_targetDoc->fixtures().count();
×
450
}
×
451

452
void FixtureRemap::slotRemoveTargetFixture()
×
453
{
454
    if (m_targetTree->selectedItems().count() == 0)
×
455
        return;
×
456

457
    QTreeWidgetItem *item = m_targetTree->selectedItems().first();
×
458
    bool ok = false;
×
459
    quint32 fxid = item->text(KColumnID).toUInt(&ok);
×
460
    if (ok == false)
×
461
        return;
×
462

463
    // Ask before deletion
464
    if (QMessageBox::question(this, tr("Delete Fixtures"),
×
465
                              tr("Do you want to delete the selected items?"),
×
466
                              QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
×
467
    {
468
        return;
×
469
    }
470

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

492
    qDebug() << "Fixtures in target doc:" << m_targetDoc->fixtures().count();
×
493
}
×
494

495
void FixtureRemap::slotCloneSourceFixture()
×
496
{
497
    if (m_sourceTree->selectedItems().count() == 0)
×
498
        return; // popup here ??
×
499

500
    QTreeWidgetItem *sItem = m_sourceTree->selectedItems().first();
×
501
    quint32 fxID = sItem->text(KColumnID).toUInt();
×
502
    Fixture *srcFix = m_doc->fixture(fxID);
×
503
    if (srcFix == NULL)
×
504
        return; // popup here ?
×
505

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

520
    // create a copy of the fixture and add it to the target document
521
    /* Create the target fixture */
522
    Fixture* tgtFix = new Fixture(m_targetDoc);
×
523

524
    /* Add the first fixture without gap, at the given address */
525
    tgtFix->setAddress(srcFix->address());
×
526
    tgtFix->setUniverse(srcFix->universe());
×
527
    tgtFix->setName(srcFix->name());
×
528

529
    /* Set a fixture definition & mode if they were selected.
530
       Otherwise assign channels to a generic dimmer. */
531
    if (srcFix->fixtureDef()->manufacturer() == KXMLFixtureGeneric &&
×
532
        srcFix->fixtureDef()->model() == KXMLFixtureGeneric)
×
533
            tgtFix->setChannels(srcFix->channels());
×
534
    else
535
        tgtFix->setFixtureDefinition(srcFix->fixtureDef(), srcFix->fixtureMode());
×
536

537
    m_targetDoc->addFixture(tgtFix);
×
538

539
    // create the tree element and add it to the target tree
540
    QTreeWidgetItem *topItem = getUniverseItem(m_targetDoc, tgtFix->universe(), m_targetTree);
×
541
    quint32 baseAddr = tgtFix->address();
×
542
    QTreeWidgetItem *fItem = new QTreeWidgetItem(topItem);
×
543
    fItem->setText(KColumnName, tgtFix->name());
×
544
    fItem->setIcon(KColumnName, tgtFix->getIconFromType());
×
545
    fItem->setText(KColumnAddress, QString("%1 - %2").arg(baseAddr + 1).arg(baseAddr + tgtFix->channels()));
×
546
    fItem->setText(KColumnUniverse, QString::number(tgtFix->universe()));
×
547
    fItem->setText(KColumnID, QString::number(tgtFix->id()));
×
548

549
    for (quint32 c = 0; c < tgtFix->channels(); c++)
×
550
    {
551
        const QLCChannel* channel = tgtFix->channel(c);
×
552
        QTreeWidgetItem *item = new QTreeWidgetItem(fItem);
×
553
        item->setText(KColumnName, QString("%1:%2").arg(c + 1)
×
554
                      .arg(channel->name()));
×
555
        item->setIcon(KColumnName, channel->getIcon());
×
556
        item->setText(KColumnUniverse, QString::number(tgtFix->universe()));
×
557
        item->setText(KColumnID, QString::number(tgtFix->id()));
×
558
        item->setText(KColumnChIdx, QString::number(c));
×
559
    }
560

561
    m_targetTree->resizeColumnToContents(KColumnName);
×
562

563
    foreach (QTreeWidgetItem *it, m_targetTree->selectedItems())
×
564
        it->setSelected(false);
×
565
    fItem->setSelected(true);
×
566

567
    slotAddRemap();
×
568
}
569

570
void FixtureRemap::slotAddRemap()
×
571
{
572
    if (m_sourceTree->selectedItems().count() == 0 ||
×
573
        m_targetTree->selectedItems().count() == 0)
×
574
    {
575
        QMessageBox::warning(this,
×
576
                tr("Invalid selection"),
×
577
                tr("Please select a source and a target fixture or channel to perform this operation."));
×
578
        return;
×
579
    }
580

581
    connectFixtures(m_sourceTree->selectedItems().first(),
×
582
                    m_targetTree->selectedItems().first());
×
583

584
    remapWidget->setRemapList(m_remapList);
×
585
}
586

587
void FixtureRemap::connectFixtures(QTreeWidgetItem *sourceItem, QTreeWidgetItem *targetItem)
×
588
{
589
    if (sourceItem == NULL || targetItem == NULL)
×
590
        return;
×
591

592
    RemapInfo newRemap;
593
    newRemap.source = sourceItem;
×
594
    newRemap.target = targetItem;
×
595

596
    quint32 srcFxiID = newRemap.source->text(KColumnID).toUInt();
×
597
    Fixture *srcFxi = m_doc->fixture(srcFxiID);
×
598
    quint32 tgtFxiID = newRemap.target->text(KColumnID).toUInt();
×
599
    Fixture *tgtFxi = m_targetDoc->fixture(tgtFxiID);
×
600
    if (srcFxi == NULL || tgtFxi == NULL)
×
601
    {
602
        QMessageBox::warning(this,
×
603
                tr("Invalid selection"),
×
604
                tr("Please select a source and a target fixture or channel to perform this operation."));
×
605
        return;
×
606
    }
607

608
    bool srcFxiSelected = false;
×
609
    bool tgtFxiSelected = false;
×
610

611
    bool ok = false;
×
612
    int srcIdx = newRemap.source->text(KColumnChIdx).toInt(&ok);
×
613
    if (ok == false)
×
614
        srcFxiSelected = true;
×
615
    ok = false;
×
616
    int tgtIdx = newRemap.target->text(KColumnChIdx).toInt(&ok);
×
617
    if (ok == false)
×
618
        tgtFxiSelected = true;
×
619

620
    qDebug() << "Idx:" << srcIdx << ", src:" << srcFxiSelected << ", tgt:" << tgtFxiSelected;
×
621

622
    if ((srcFxiSelected == true && tgtFxiSelected == false) ||
×
623
        (srcFxiSelected == false && tgtFxiSelected == true))
×
624
    {
625
        QMessageBox::warning(this,
×
626
                             tr("Invalid selection"),
×
627
                             tr("To perform a fixture remap, please select fixtures on both lists."));
×
628
        return;
×
629
    }
630
    else if (srcFxiSelected == true && tgtFxiSelected == true)
×
631
    {
632
        // perform a full fixture remap
633
        const QLCFixtureDef *srcFxiDef = srcFxi->fixtureDef();
×
634
        const QLCFixtureDef *tgtFxiDef = tgtFxi->fixtureDef();
×
635
        const QLCFixtureMode *srcFxiMode = srcFxi->fixtureMode();
×
636
        const QLCFixtureMode *tgtFxiMode = tgtFxi->fixtureMode();
×
637
        bool oneToOneRemap = false;
×
638

639
        if (m_remapNamesCheck->isChecked())
×
640
        {
641
            tgtFxi->setName(srcFxi->name());
×
642
            newRemap.target->setText(KColumnName, srcFxi->name());
×
643
        }
644

645
        // 1-to-1 channel remapping is required for fixtures with
646
        // the same definition and mode
647
        if (srcFxiDef != NULL && tgtFxiDef != NULL &&
×
648
            srcFxiMode != NULL && tgtFxiMode != NULL)
×
649
        {
650
            if (srcFxiDef->name() == tgtFxiDef->name() &&
×
651
                srcFxiMode->name() == tgtFxiMode->name())
×
652
                    oneToOneRemap = true;
×
653
        }
654
        // 1-to-1 channel remapping is required for
655
        // generic dimmer packs
656
        else if (srcFxiDef == NULL && tgtFxiDef == NULL &&
×
657
                 srcFxiMode == NULL && tgtFxiMode == NULL)
×
658
                    oneToOneRemap = true;
×
659

660
        if (oneToOneRemap == true)
×
661
        {
662
            // copy forced LTP/HTP channels right away
663
            tgtFxi->setForcedHTPChannels(srcFxi->forcedHTPChannels());
×
664
            tgtFxi->setForcedLTPChannels(srcFxi->forcedLTPChannels());
×
665
        }
666

667
        for (quint32 s = 0; s < srcFxi->channels(); s++)
×
668
        {
669
            if (oneToOneRemap == true)
×
670
            {
671
                if (s < tgtFxi->channels())
×
672
                {
673
                    RemapInfo matchInfo;
674
                    matchInfo.source = newRemap.source->child(s);
×
675
                    matchInfo.target = newRemap.target->child(s);
×
676
                    m_remapList.append(matchInfo);
×
677

678
                    if (srcFxi->channelCanFade(s) == false)
×
679
                        tgtFxi->setChannelCanFade(s, false);
×
680

681
                    // copy channel modifiers
682
                    ChannelModifier *chMod = srcFxi->channelModifier(s);
×
683
                    if (chMod != NULL)
×
684
                        tgtFxi->setChannelModifier(s, chMod);
×
685
                }
686
            }
687
            else
688
            {
689
                const QLCChannel *srcCh = srcFxi->channel(s);
×
690

691
                for (quint32 t = 0; t < tgtFxi->channels(); t++)
×
692
                {
693
                    const QLCChannel *tgtCh = tgtFxi->channel(t);
×
694

695
                    if ((tgtCh->group() == srcCh->group()) &&
×
696
                        (tgtCh->controlByte() == srcCh->controlByte()))
×
697
                    {
698
                        if (tgtCh->group() == QLCChannel::Intensity &&
×
699
                            tgtCh->colour() != srcCh->colour())
×
700
                                continue;
×
701

702
                        RemapInfo matchInfo;
703
                        matchInfo.source = newRemap.source->child(s);
×
704
                        matchInfo.target = newRemap.target->child(t);
×
705
                        m_remapList.append(matchInfo);
×
706

707
                        if (srcFxi->channelCanFade(s) == false)
×
708
                            tgtFxi->setChannelCanFade(t, false);
×
709
                        break;
×
710
                    }
711
                }
712
            }
713
        }
714
    }
×
715
    else
716
    {
717
        // perform a single channel remap
718
        m_remapList.append(newRemap);
×
719
        if (srcFxi->channelCanFade(srcIdx) == false)
×
720
            tgtFxi->setChannelCanFade(tgtIdx, false);
×
721
    }
722
}
723

724
void FixtureRemap::slotRemoveRemap()
×
725
{
726
    if (m_sourceTree->selectedItems().count() == 0 ||
×
727
        m_targetTree->selectedItems().count() == 0)
×
728
    {
729
        QMessageBox::warning(this,
×
730
                             tr("Invalid selection"),
×
731
                             tr("Please select a source and a target fixture or channel to perform this operation."));
×
732
        return;
×
733
    }
734

735
    RemapInfo delRemap;
736
    delRemap.source = m_sourceTree->selectedItems().first();
×
737
    delRemap.target = m_targetTree->selectedItems().first();
×
738

739
    bool tgtFxiSelected = false;
×
740
    bool fxok = false, chok = false;
×
741
    quint32 fxid = delRemap.target->text(KColumnID).toUInt(&fxok);
×
742
    delRemap.target->text(KColumnChIdx).toInt(&chok);
×
743
    if (fxok == true && chok == false)
×
744
        tgtFxiSelected = true;
×
745

746
    for (int i = 0; i < m_remapList.count(); i++)
×
747
    {
748
        RemapInfo info = m_remapList.at(i);
×
749
        // full fixture remap delete
750
        if (tgtFxiSelected == true)
×
751
        {
752
            quint32 rmpFxID = info.target->text(KColumnID).toUInt();
×
753
            if (rmpFxID == fxid)
×
754
            {
755
                m_remapList.takeAt(i);
×
756
                i--;
×
757
            }
758
        }
759
        // single channel remap delete. Source and target must match
760
        else if (info.source == delRemap.source && info.target == delRemap.target)
×
761
        {
762
            m_remapList.takeAt(i);
×
763
            i--;
×
764
        }
765
    }
766
    remapWidget->setRemapList(m_remapList);
×
767
}
768

769
void FixtureRemap::slotUpdateConnections()
×
770
{
771
    remapWidget->update();
×
772
    m_sourceTree->resizeColumnToContents(KColumnName);
×
773
    m_targetTree->resizeColumnToContents(KColumnName);
×
774
}
×
775

776
void FixtureRemap::slotSourceSelectionChanged()
×
777
{
778
    if (m_sourceTree->selectedItems().count() > 0)
×
779
    {
780
        QTreeWidgetItem *item = m_sourceTree->selectedItems().first();
×
781
        bool fxOK = false, chOK = false;
×
782
        item->text(KColumnID).toUInt(&fxOK);
×
783
        item->text(KColumnChIdx).toInt(&chOK);
×
784
        if (fxOK == true && chOK == false)
×
785
            m_cloneButton->setEnabled(true);
×
786
        else
787
            m_cloneButton->setEnabled(false);
×
788
    }
789
    else
790
        m_cloneButton->setEnabled(false);
×
791
}
×
792

793
QList<SceneValue> FixtureRemap::remapSceneValues(QList<SceneValue> funcList,
×
794
                                    QList<SceneValue> &srcList,
795
                                    QList<SceneValue> &tgtList)
796
{
797
    QList <SceneValue> newValuesList;
×
798
    foreach (SceneValue val, funcList)
×
799
    {
800
        for (int v = 0; v < srcList.count(); v++)
×
801
        {
802
            if (val == srcList.at(v))
×
803
            {
804
                SceneValue tgtVal = tgtList.at(v);
×
805
                //qDebug() << "[Scene] Remapping" << val.fxi << val.channel << " to " << tgtVal.fxi << tgtVal.channel;
806
                newValuesList.append(SceneValue(tgtVal.fxi, tgtVal.channel, val.value));
×
807
            }
×
808
        }
809
    }
×
810
    std::sort(newValuesList.begin(), newValuesList.end());
×
811
    return newValuesList;
×
812
}
×
813

814
void FixtureRemap::accept()
×
815
{
816
    /* **********************************************************************
817
     * 1 - create a map of SceneValues from the fixtures channel associations
818
     * ********************************************************************** */
819
    QList<SceneValue> sourceList;
×
820
    QList<SceneValue> targetList;
×
821

822
    foreach (RemapInfo info, m_remapList)
×
823
    {
824
        quint32 srcFxiID = info.source->text(KColumnID).toUInt();
×
825
        quint32 srcChIdx = info.source->text(KColumnChIdx).toUInt();
×
826

827
        quint32 tgtFxiID = info.target->text(KColumnID).toUInt();
×
828
        quint32 tgtChIdx = info.target->text(KColumnChIdx).toUInt();
×
829

830
        sourceList.append(SceneValue(srcFxiID, srcChIdx));
×
831
        targetList.append(SceneValue(tgtFxiID, tgtChIdx));
×
832

833
        // qDebug() << "Remapping fx" << srcFxiID << "ch" << srcChIdx << "to fx" << tgtFxiID << "ch" << tgtChIdx;
834
    }
×
835

836
    /* **********************************************************************
837
     * 2 - Show a progress dialog, in case the operation takes a while
838
     * ********************************************************************** */
839
    QProgressDialog progress(tr("This might take a while..."), tr("Cancel"), 0, 100, this);
×
840
    progress.setWindowModality(Qt::WindowModal);
×
841
    progress.show();
×
842

843
    /* **********************************************************************
844
     * 3 - replace original project fixtures
845
     * ********************************************************************** */
846

847
    m_doc->replaceFixtures(m_targetDoc->fixtures());
×
848

849
    /* **********************************************************************
850
     * 4 - remap fixture groups and channel groups
851
     * ********************************************************************** */
852
    foreach (FixtureGroup *group, m_doc->fixtureGroups())
×
853
    {
854
        QMap<QLCPoint, GroupHead> grpHash = group->headsMap();
×
855
        group->reset();
×
856

857
        QMapIterator<QLCPoint, GroupHead> it(grpHash);
×
858
        while (it.hasNext())
×
859
        {
860
            it.next();
×
861

862
            QLCPoint pt(it.key());
×
863
            GroupHead head(it.value());
×
864

865
            if (head.isValid() == false)
×
866
                continue;
×
867

868
            for (int i = 0; i < sourceList.count(); i++)
×
869
            {
870
                if (sourceList.at(i).fxi == head.fxi)
×
871
                {
872
                    head.fxi = targetList.at(i).fxi;
×
873
                    group->resignHead(pt);
×
874
                    group->assignHead(pt, head);
×
875
                    break;
×
876
                }
877
            }
878
        }
×
879
    }
×
880

881
    foreach (ChannelsGroup *grp, m_doc->channelsGroups())
×
882
    {
883
        QList<SceneValue> grpChannels = grp->getChannels();
×
884
        // this is crucial: here all the "unmapped" channels will be lost forever !
885
        grp->resetChannels();
×
886
        QList <SceneValue> newList = remapSceneValues(grpChannels, sourceList, targetList);
×
887
        foreach (SceneValue val, newList)
×
888
            grp->addChannel(val.fxi, val.channel);
×
889
    }
×
890

891
    /* **********************************************************************
892
     * 5 - scan project functions and perform remapping
893
     * ********************************************************************** */
894
    int funcNum = m_doc->functions().count();
×
895
    int f = 0;
×
896
    foreach (Function *func, m_doc->functions())
×
897
    {
898
        switch (func->type())
×
899
        {
900
            case Function::SceneType:
×
901
            {
902
                Scene *s = qobject_cast<Scene*>(func);
×
903
                qDebug() << "Analyzing Scene #" << s->id();
×
904
                QList <SceneValue> newList = remapSceneValues(s->values(), sourceList, targetList);
×
905
                // this is crucial: here all the "unmapped" channels will be lost forever !
906
                s->clear();
×
907

908
                for (int i = 0; i < newList.count(); i++)
×
909
                {
910
                    s->addFixture(newList.at(i).fxi);
×
911
                    s->setValue(newList.at(i));
×
912
                }
913
            }
×
914
            break;
×
915
            case Function::SequenceType:
×
916
            {
917
                Sequence *s = qobject_cast<Sequence*>(func);
×
918
                for (int idx = 0; idx < s->stepsCount(); idx++)
×
919
                {
920
                    ChaserStep *cs = s->stepAt(idx);
×
921
                    QList <SceneValue> newList = remapSceneValues(cs->values, sourceList, targetList);
×
922
                    //qDebug() << "Step" << idx << "remapped" << cs.values.count() << "to" << newList.count();
923
                    // this is crucial: here all the "unmapped" channels will be lost forever !
924
                    cs->values.clear();
×
925
                    cs->values = newList;
×
926
                    //s->replaceStep(cs, idx);
927
                }
×
928
            }
929
            break;
×
930
            case Function::EFXType:
×
931
            {
932
                EFX *e = qobject_cast<EFX*>(func);
×
933
                // make a copy of this EFX fixtures list
934
                QList <EFXFixture*> fixListCopy;
×
935
                foreach (EFXFixture *efxFix, e->fixtures())
×
936
                {
937
                    EFXFixture* ef = new EFXFixture(e);
×
938
                    ef->copyFrom(efxFix);
×
939
                    fixListCopy.append(ef);
×
940
                }
×
941
                // this is crucial: here all the "unmapped" fixtures will be lost forever !
942
                e->removeAllFixtures();
×
943
                QList<quint32>remappedFixtures;
×
944

945
                foreach (EFXFixture *efxFix, fixListCopy)
×
946
                {
947
                    quint32 fxID = efxFix->head().fxi;
×
948
                    for (int i = 0; i < sourceList.count(); i++)
×
949
                    {
950
                        SceneValue srcVal = sourceList.at(i);
×
951
                        SceneValue tgtVal = targetList.at(i);
×
952
                        // check for fixture ID match. EFX remapping must be performed
953
                        // just once for each target fixture
954
                        if (srcVal.fxi == fxID && remappedFixtures.contains(tgtVal.fxi) == false)
×
955
                        {
956
                            Fixture *docFix = m_doc->fixture(tgtVal.fxi);
×
957
                            quint32 fxCh = tgtVal.channel;
×
958
                            const QLCChannel *chan = docFix->channel(fxCh);
×
959
                            if (chan->group() == QLCChannel::Pan ||
×
960
                                chan->group() == QLCChannel::Tilt)
×
961
                            {
962
                                EFXFixture* ef = new EFXFixture(e);
×
963
                                ef->copyFrom(efxFix);
×
964
                                ef->setHead(GroupHead(tgtVal.fxi, 0)); // TODO!!! head!!!
×
965
                                if (e->addFixture(ef) == false)
×
966
                                    delete ef;
×
967
                                qDebug() << "EFX remap" << srcVal.fxi << "to" << tgtVal.fxi;
×
968
                                remappedFixtures.append(tgtVal.fxi);
×
969
                            }
970
                        }
971
                    }
×
972
                }
×
973
                fixListCopy.clear();
×
974
            }
×
975
            break;
×
976
            default:
×
977
            break;
×
978
        }
979
        if (progress.wasCanceled())
×
980
            break;
×
981
        f++;
×
982
        progress.setValue((f * 100) / funcNum);
×
983
        QApplication::processEvents();
×
984
    }
×
985

986
    /* **********************************************************************
987
     * 6 - remap Virtual Console widgets
988
     * ********************************************************************** */
989
    VCFrame* contents = VirtualConsole::instance()->contents();
×
990
    QList<VCWidget *> widgetsList = contents->findChildren<VCWidget*>();
×
991

992
    foreach (VCWidget *widget, widgetsList)
×
993
    {
994
        switch (widget->type())
×
995
        {
996
            case VCWidget::SliderWidget:
×
997
            {
998
                VCSlider *slider = qobject_cast<VCSlider*>(widget);
×
999
                if (slider->sliderMode() == VCSlider::Level)
×
1000
                {
1001
                    qDebug() << "Remapping slider:" << slider->caption();
×
1002
                    QList <SceneValue> newChannels;
×
1003

1004
                    foreach (VCSlider::LevelChannel chan, slider->levelChannels())
×
1005
                    {
1006
                        for (int v = 0; v < sourceList.count(); v++)
×
1007
                        {
1008
                            SceneValue val = sourceList.at(v);
×
1009
                            if (val.fxi == chan.fixture && val.channel == chan.channel)
×
1010
                            {
1011
                                qDebug() << "Matching channel:" << chan.fixture << chan.channel << "to target:" << targetList.at(v).fxi << targetList.at(v).channel;
×
1012
                                newChannels.append(SceneValue(targetList.at(v).fxi, targetList.at(v).channel));
×
1013
                            }
1014
                        }
×
1015
                    }
×
1016
                    // this is crucial: here all the "unmapped" channels will be lost forever !
1017
                    slider->clearLevelChannels();
×
1018
                    foreach (SceneValue rmpChan, newChannels)
×
1019
                        slider->addLevelChannel(rmpChan.fxi, rmpChan.channel);
×
1020
                }
×
1021
            }
1022
            break;
×
1023
            case VCWidget::AudioTriggersWidget:
×
1024
            {
1025
                VCAudioTriggers *triggers = qobject_cast<VCAudioTriggers*>(widget);
×
1026
                foreach (AudioBar *bar, triggers->getAudioBars())
×
1027
                {
1028
                    if (bar->m_type == AudioBar::DMXBar)
×
1029
                    {
1030
                        QList <SceneValue> newList = remapSceneValues(bar->m_dmxChannels, sourceList, targetList);
×
1031
                        // this is crucial: here all the "unmapped" channels will be lost forever !
1032
                        bar->attachDmxChannels(m_doc, newList);
×
1033
                    }
×
1034
                }
×
1035
            }
1036
            break;
×
1037
            case VCWidget::XYPadWidget:
×
1038
            {
1039
                VCXYPad *xypad = qobject_cast<VCXYPad*>(widget);
×
1040
                QList<VCXYPadFixture> copyFixtures;
×
1041
                foreach (VCXYPadFixture fix, xypad->fixtures())
×
1042
                {
1043
                    quint32 srxFxID = fix.head().fxi; // TODO: heads !!
×
1044
                    for (int i = 0; i < sourceList.count(); i++)
×
1045
                    {
1046
                        SceneValue val = sourceList.at(i);
×
1047
                        if (val.fxi == srxFxID)
×
1048
                        {
1049
                            SceneValue tgtVal = targetList.at(i);
×
1050
                            Fixture *docFix = m_doc->fixture(tgtVal.fxi);
×
1051
                            quint32 fxCh = tgtVal.channel;
×
1052
                            const QLCChannel *chan = docFix->channel(fxCh);
×
1053
                            if (chan->group() == QLCChannel::Pan ||
×
1054
                                chan->group() == QLCChannel::Tilt)
×
1055
                            {
1056
                                VCXYPadFixture tgtFix(m_doc);
×
1057
                                GroupHead head(tgtVal.fxi, 0);
×
1058
                                tgtFix.setHead(head);
×
1059
                                copyFixtures.append(tgtFix);
×
1060
                            }
×
1061
                        }
×
1062
                    }
×
1063
                }
×
1064
                // this is crucial: here all the "unmapped" fixtures will be lost forever !
1065
                xypad->clearFixtures();
×
1066
                foreach (VCXYPadFixture fix, copyFixtures)
×
1067
                    xypad->appendFixture(fix);
×
1068
            }
×
1069
            break;
×
1070
            default:
×
1071
            break;
×
1072
        }
1073
    }
×
1074

1075
    /* **********************************************************************
1076
     * 7 - remap 2D monitor properties, if defined
1077
     * ********************************************************************** */
1078
    MonitorProperties *props = m_doc->monitorProperties();
×
1079
    if (props != NULL)
×
1080
    {
1081
        QMap <quint32, FixturePreviewItem> remappedFixtureItems;
×
1082

1083
        foreach (quint32 fxID, props->fixtureItemsID())
×
1084
        {
1085
            for (int v = 0; v < sourceList.count(); v++)
×
1086
            {
1087
                if (sourceList.at(v).fxi == fxID)
×
1088
                {
1089
                    FixturePreviewItem rmpProp = props->fixtureProperties(fxID);
×
1090
                    remappedFixtureItems[targetList.at(v).fxi] = rmpProp;
×
1091
                    break;
×
1092
                }
×
1093
            }
1094

1095
            props->removeFixture(fxID);
×
1096
        }
×
1097

1098
        QMapIterator <quint32, FixturePreviewItem> it(remappedFixtureItems);
×
1099
        while (it.hasNext())
×
1100
        {
1101
            it.next();
×
1102
            props->setFixtureProperties(it.key(), it.value());
×
1103
        }
1104
    }
×
1105

1106
    /* **********************************************************************
1107
     * 8 - save the remapped project into a new file
1108
     * ********************************************************************** */
1109
    App *mainApp = (App *)m_doc->parent();
×
1110
    if (m_targetProjectLabel->text().endsWith(".qxw") == false)
×
1111
        m_targetProjectLabel->setText(m_targetProjectLabel->text() + ".qxw");
×
1112
    mainApp->setFileName(m_targetProjectLabel->text());
×
1113
    mainApp->slotFileSave();
×
1114

1115
    progress.hide();
×
1116

1117
    /* Close dialog */
1118
    QDialog::accept();
×
1119
}
×
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