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

mcallegari / qlcplus / 15805077468

22 Jun 2025 08:36AM UTC coverage: 31.876% (-0.01%) from 31.89%
15805077468

push

github

mcallegari
plugins/dmxusb: fix RDM discovery and commands while DMX is running

0 of 1 new or added line in 1 file covered. (0.0%)

3722 existing lines in 175 files now uncovered.

16438 of 51569 relevant lines covered (31.88%)

19266.08 hits per line

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

0.0
/ui/src/app.cpp
1
/*
2
  Q Light Controller Plus
3
  app.cpp
4

5
  Copyright (c) Heikki Junnila,
6
                Christopher Staite
7
                Massimo Callegari
8

9
  Licensed under the Apache License, Version 2.0 (the "License");
10
  you may not use this file except in compliance with the License.
11
  You may obtain a copy of the License at
12

13
      http://www.apache.org/licenses/LICENSE-2.0.txt
14

15
  Unless required by applicable law or agreed to in writing, software
16
  distributed under the License is distributed on an "AS IS" BASIS,
17
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
  See the License for the specific language governing permissions and
19
  limitations under the License.
20
*/
21

22
#include <QToolButton>
23
#include <QtWidgets>
24
#include <unistd.h>
25
#include <QtCore>
26

27
#if defined(WIN32) || defined(Q_OS_WIN)
28
  #include <windows.h>
29
#endif
30

31
#include "functionliveeditdialog.h"
32
#include "inputoutputmanager.h"
33
#include "functionselection.h"
34
#include "functionmanager.h"
35
#include "inputoutputmap.h"
36
#include "virtualconsole.h"
37
#include "fixturemanager.h"
38
#include "dmxdumpfactory.h"
39
#include "showmanager.h"
40
#include "mastertimer.h"
41
#include "addresstool.h"
42
#include "simpledesk.h"
43
#include "aboutbox.h"
44
#include "monitor.h"
45
#include "vcframe.h"
46
#include "app.h"
47
#include "doc.h"
48

49
#include "qlcfixturedefcache.h"
50
#include "audioplugincache.h"
51
#include "rgbscriptscache.h"
52
#include "videoprovider.h"
53
#include "qlcconfig.h"
54
#include "qlcfile.h"
55
#include "apputil.h"
56

57
#if defined(WIN32) || defined(Q_OS_WIN)
58
#   include "hotplugmonitor.h"
59
#endif
60

61
#if defined(__APPLE__) || defined(Q_OS_MAC)
62
extern void qt_set_sequence_auto_mnemonic(bool b);
63
#endif
64

65
#if defined(WIN32) || defined(Q_OS_WIN)
66
// Defined in Windows 11 headers but not in earlier versions.
67
#ifndef PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION
68
#define PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION 0x4
69
#endif
70

71
typedef BOOL (WINAPI *SetProcessInformationType)(
72
    HANDLE hProcess,
73
    PROCESS_INFORMATION_CLASS ProcessInformationClass,
74
    LPVOID ProcessInformation,
75
    DWORD ProcessInformationSize
76
);
77
#endif
78

79
//#define DEBUG_SPEED
80

81
#ifdef DEBUG_SPEED
82
 #include <QTime>
83
 QTime speedTime;
84
#endif
85

86
#define SETTINGS_GEOMETRY "workspace/geometry"
87
#define SETTINGS_WORKINGPATH "workspace/workingpath"
88
#define SETTINGS_RECENTFILE "workspace/recent"
89
#define KXMLQLCWorkspaceWindow "CurrentWindow"
90

91
#define MAX_RECENT_FILES    10
92

93
#define KModeTextOperate QObject::tr("Operate")
94
#define KModeTextDesign QObject::tr("Design")
95
#define KUniverseCount 4
96

97
/*****************************************************************************
98
 * Initialization
99
 *****************************************************************************/
100

101
App::App()
×
102
    : QMainWindow()
103
    , m_tab(NULL)
×
104
    , m_overscan(false)
×
105
    , m_noGui(false)
×
106
    , m_progressDialog(NULL)
×
107
    , m_doc(NULL)
×
108

109
    , m_fileNewAction(NULL)
×
110
    , m_fileOpenAction(NULL)
×
111
    , m_fileSaveAction(NULL)
×
112
    , m_fileSaveAsAction(NULL)
×
113

114
    , m_modeToggleAction(NULL)
×
115
    , m_controlMonitorAction(NULL)
×
116
    , m_addressToolAction(NULL)
×
117
    , m_controlFullScreenAction(NULL)
×
118
    , m_controlBlackoutAction(NULL)
×
119
    , m_controlPanicAction(NULL)
×
120
    , m_dumpDmxAction(NULL)
×
121
    , m_liveEditAction(NULL)
×
122
    , m_liveEditVirtualConsoleAction(NULL)
×
123

124
    , m_helpIndexAction(NULL)
×
125
    , m_helpAboutAction(NULL)
×
126
    , m_quitAction(NULL)
×
127
    , m_fileOpenMenu(NULL)
×
128
    , m_fadeAndStopMenu(NULL)
×
129

130
    , m_toolbar(NULL)
×
131

132
    , m_dumpProperties(NULL)
×
133
    , m_videoProvider(NULL)
×
134
{
135
    QCoreApplication::setOrganizationName("qlcplus");
×
136
    QCoreApplication::setOrganizationDomain("sf.net");
×
137
    QCoreApplication::setApplicationName(APPNAME);
×
138
}
×
139

140
App::~App()
×
141
{
142
    QSettings settings;
×
143

144
    // Don't save kiosk-mode window geometry because that will screw things up
145
    if (m_doc->isKiosk() == false && QLCFile::hasWindowManager())
×
146
        settings.setValue(SETTINGS_GEOMETRY, saveGeometry());
×
147
    else
148
        settings.setValue(SETTINGS_GEOMETRY, QVariant());
×
149

150
    if (Monitor::instance() != NULL)
×
151
        delete Monitor::instance();
×
152

153
    if (FixtureManager::instance() != NULL)
×
154
        delete FixtureManager::instance();
×
155

156
    if (FunctionManager::instance() != NULL)
×
157
        delete FunctionManager::instance();
×
158

159
    if (ShowManager::instance() != NULL)
×
160
        delete ShowManager::instance();
×
161

162
    if (InputOutputManager::instance() != NULL)
×
163
        delete InputOutputManager::instance();
×
164

165
    if (VirtualConsole::instance() != NULL)
×
166
        delete VirtualConsole::instance();
×
167

168
    if (SimpleDesk::instance() != NULL)
×
169
        delete SimpleDesk::instance();
×
170

171
    if (m_dumpProperties != NULL)
×
172
        delete m_dumpProperties;
×
173

174
    if (m_videoProvider != NULL)
×
175
        delete m_videoProvider;
×
176

177
    if (m_doc != NULL)
×
178
        delete m_doc;
×
179

180
    m_doc = NULL;
×
181
}
×
182

183
void App::startup()
×
184
{
185
#if defined(__APPLE__) || defined(Q_OS_MAC)
186
    createProgressDialog();
187
#endif
188

189
    init();
×
190
    slotModeDesign();
×
191
    slotDocModified(false);
×
192

193
#if defined(__APPLE__) || defined(Q_OS_MAC)
194
    destroyProgressDialog();
195
#endif
196

197
    // Activate FixtureManager
198
    setActiveWindow(FixtureManager::staticMetaObject.className());
×
199
}
×
200

201
void App::enableOverscan()
×
202
{
203
    m_overscan = true;
×
204
}
×
205

206
void App::disableGUI()
×
207
{
208
    m_noGui = true;
×
209
}
×
210

211
void App::init()
×
212
{
213
    QSettings settings;
×
214

215
    setWindowIcon(QIcon(":/qlcplus.png"));
×
216

217
    m_tab = new QTabWidget(this);
×
218
    m_tab->setTabPosition(QTabWidget::South);
×
219
    setCentralWidget(m_tab);
×
220

221
#if defined(__APPLE__) || defined(Q_OS_MAC)
222
    m_tab->setElideMode(Qt::TextElideMode::ElideNone);
223
    qt_set_sequence_auto_mnemonic(true);
224
#endif
225

226
    QVariant var = settings.value(SETTINGS_GEOMETRY);
×
227
    if (var.isValid() == true)
×
228
    {
229
        this->restoreGeometry(var.toByteArray());
×
230
    }
231
    else
232
    {
233
        /* Application geometry and window state */
234
        QSize size = settings.value("/workspace/size").toSize();
×
UNCOV
235
        if (size.isValid() == true)
×
236
        {
237
            resize(size);
×
238
        }
239
        else
240
        {
241
            if (QLCFile::hasWindowManager() == false)
×
242
            {
243
                QScreen *screen = QGuiApplication::screens().first();
×
244
                QRect geometry = screen->geometry();
×
245
                if (m_noGui == true)
×
246
                {
UNCOV
247
                    setGeometry(geometry.width(), geometry.height(), 1, 1);
×
248
                }
249
                else
250
                {
UNCOV
251
                    int w = geometry.width();
×
UNCOV
252
                    int h = geometry.height();
×
253
                    if (m_overscan == true)
×
254
                    {
255
                        // if overscan is requested, introduce a 5% margin
256
                        w = (float)geometry.width() * 0.95;
×
257
                        h = (float)geometry.height() * 0.95;
×
258
                    }
259
                    setGeometry((geometry.width() - w) / 2, (geometry.height() - h) / 2, w, h);
×
260
                }
261
            }
262
            else
UNCOV
263
                resize(800, 600);
×
264
        }
265

266
        QVariant state = settings.value("/workspace/state", Qt::WindowNoState);
×
267
        if (state.isValid() == true)
×
268
            setWindowState(Qt::WindowState(state.toInt()));
×
269
    }
×
270

271
    QVariant dir = settings.value(SETTINGS_WORKINGPATH);
×
272
    if (dir.isValid() == true)
×
273
        m_workingDirectory = QDir(dir.toString());
×
274

275
    // The engine object
276
    initDoc();
×
277
    // Main view actions
278
    initActions();
×
279
    // Main tool bar
280
    initToolBar();
×
281

282
    m_dumpProperties = new DmxDumpFactoryProperties(KUniverseCount);
×
283

284
    // Create primary views.
285
    m_tab->setIconSize(QSize(24, 24));
×
286
    QWidget* w = new FixtureManager(m_tab, m_doc);
×
287
    m_tab->addTab(w, QIcon(":/fixture.png"), tr("Fixtures"));
×
288
    w = new FunctionManager(m_tab, m_doc);
×
289
    m_tab->addTab(w, QIcon(":/function.png"), tr("Functions"));
×
290
    w = new ShowManager(m_tab, m_doc);
×
291
    m_tab->addTab(w, QIcon(":/show.png"), tr("Shows"));
×
292
    w = new VirtualConsole(m_tab, m_doc);
×
293
    m_tab->addTab(w, QIcon(":/virtualconsole.png"), tr("Virtual Console"));
×
294
    w = new SimpleDesk(m_tab, m_doc);
×
295
    m_tab->addTab(w, QIcon(":/slidermatrix.png"), tr("Simple Desk"));
×
296
    w = new InputOutputManager(m_tab, m_doc);
×
297
    m_tab->addTab(w, QIcon(":/input_output.png"), tr("Inputs/Outputs"));
×
298

299
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
300
    /* Detach the tab's widget onto a new window on doubleClick */
301
    connect(m_tab, SIGNAL(tabBarDoubleClicked(int)), this, SLOT(slotDetachContext(int)));
×
302
#endif
303

304
    // Listen to blackout changes and toggle m_controlBlackoutAction
305
    connect(m_doc->inputOutputMap(), SIGNAL(blackoutChanged(bool)), this, SLOT(slotBlackoutChanged(bool)));
×
306

307
    // Listen to DMX value changes and update each Fixture values array
308
    connect(m_doc->inputOutputMap(), SIGNAL(universeWritten(quint32, const QByteArray&)),
×
309
            this, SLOT(slotUniverseWritten(quint32, const QByteArray&)));
310

311
    // Enable/Disable panic button
312
    connect(m_doc->masterTimer(), SIGNAL(functionListChanged()), this, SLOT(slotRunningFunctionsChanged()));
×
313
    slotRunningFunctionsChanged();
×
314

315
    // Start up in non-modified state
316
    m_doc->resetModified();
×
317

318
#if defined(WIN32) || defined(Q_OS_WIN)
319
    HotPlugMonitor::setWinId(winId());
320
    
321
    // When on Windows 11, disable system timer resolution throttling when
322
    // app is minimised, occluded, etc.
323
    disableTimerResolutionThrottling();
324
#endif
325

326
    this->setStyleSheet(AppUtil::getStyleSheet("MAIN"));
×
327

328
    m_videoProvider = new VideoProvider(m_doc, this);
×
329
}
×
330

331
void App::setActiveWindow(const QString& name)
×
332
{
333
    if (name.isEmpty() == true)
×
UNCOV
334
        return;
×
335

336
    for (int i = 0; i < m_tab->count(); i++)
×
337
    {
338
        QWidget* widget = m_tab->widget(i);
×
339
        if (widget != NULL && widget->metaObject()->className() == name)
×
340
        {
341
            m_tab->setCurrentIndex(i);
×
342
            break;
×
343
        }
344
    }
345
}
346

347
#if defined(WIN32) || defined(Q_OS_WIN)
348
bool App::nativeEvent(const QByteArray &eventType, void *message, long *result)
349
{
350
    Q_UNUSED(eventType)
351
    //qDebug() << Q_FUNC_INFO << eventType;
352
    return HotPlugMonitor::parseWinEvent(message, result);
353
}
354

355
void App::disableTimerResolutionThrottling()
356
{
357
    // On Windows 11, we want it to always honour system timer resolution requests,
358
    // because otherwise by default when an application is minimised, or otherwise
359
    // non-visible or non-audible to the end-user, Windows may ignore timer
360
    // resolution requests and not give a higher resolution than the default system
361
    // timer resolution (typically 15.625 ms).
362
    
363
    // Note: we must resolve the SetProcessInformation API function at run-time
364
    // because it does not exist prior to Windows 8. On supported Windows versions
365
    // earlier than 11, the call to SetProcessInformation will just fail, which we
366
    // can ignore.
367
    
368
    HMODULE hKernel32 = LoadLibrary(L"kernel32.dll");
369
    Q_ASSERT(hKernel32 != NULL); // Shouldn't ever fail because kernel32 already loaded into every process
370

371
    // Extra void* cast to avoid -Wcast-function-type warning.
372
    SetProcessInformationType pfnSetProcessInformation = (SetProcessInformationType)(void *)GetProcAddress(hKernel32, "SetProcessInformation");
373

374
    if (pfnSetProcessInformation != NULL)
375
    {
376
        PROCESS_POWER_THROTTLING_STATE pwrState = {
377
                .Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION,
378
                .ControlMask = PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION,
379
                .StateMask = 0 // Disables timer resolution throttling
380
        };
381

382
        if (!pfnSetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling, &pwrState, sizeof(pwrState)))
383
        {
384
            qWarning() << Q_FUNC_INFO << "SetProcessInformation() failed with error" << GetLastError() << "(ignore if Windows version < 11)";
385
        }
386
    }
387
    else
388
    {
389
        qDebug() << Q_FUNC_INFO << "SetProcessInformation() API does not exist on this version of Windows";
390
    }
391
}
392
#endif
393

394
void App::closeEvent(QCloseEvent* e)
×
395
{
396
    if (m_doc->mode() == Doc::Operate && m_doc->isKiosk() == false)
×
397
    {
398
        QMessageBox::warning(this,
×
399
                             tr("Cannot exit in Operate mode"),
×
400
                             tr("You must switch back to Design mode " \
×
401
                                "to close the application."));
UNCOV
402
        e->ignore();
×
403
        return;
×
404
    }
405

406
    if (m_doc->isKiosk() == false)
×
407
    {
408
        if (saveModifiedDoc(tr("Close"), tr("Do you wish to save the current workspace " \
×
UNCOV
409
                                            "before closing the application?")) == true)
×
410
        {
UNCOV
411
            e->accept();
×
412
        }
413
        else
414
        {
UNCOV
415
            e->ignore();
×
416
        }
417
    }
418
    else
419
    {
420
        if (m_doc->isKiosk() == true)
×
421
        {
422
            int result = QMessageBox::warning(this, tr("Close the application?"),
×
423
                                              tr("Do you wish to close the application?"),
×
424
                                              QMessageBox::Yes, QMessageBox::No);
425
            if (result == QMessageBox::No)
×
426
            {
UNCOV
427
                e->ignore();
×
428
                return;
×
429
            }
430
        }
431

UNCOV
432
        e->accept();
×
433
    }
434
}
435

436
/*****************************************************************************
437
 * Progress dialog
438
 *****************************************************************************/
439

440
void App::createProgressDialog()
×
441
{
442
    m_progressDialog = new QProgressDialog;
×
443
    m_progressDialog->setCancelButton(NULL);
×
444
    m_progressDialog->show();
×
445
    m_progressDialog->raise();
×
446
    m_progressDialog->setRange(0, 10);
×
447
    slotSetProgressText(QString());
×
448
    QApplication::processEvents();
×
449
}
×
450

451
void App::destroyProgressDialog()
×
452
{
453
    delete m_progressDialog;
×
454
    m_progressDialog = NULL;
×
455
}
×
456

457
void App::slotSetProgressText(const QString& text)
×
458
{
459
    if (m_progressDialog == NULL)
×
UNCOV
460
        return;
×
461

462
    static int progress = 0;
463
    m_progressDialog->setValue(progress++);
×
464
    m_progressDialog->setLabelText(QString("<B>%1</B><BR/>%2")
×
465
                                   .arg(tr("Starting Q Light Controller Plus"))
×
466
                                   .arg(text));
×
467
    QApplication::processEvents();
×
468
}
469

470
/*****************************************************************************
471
 * Doc
472
 *****************************************************************************/
473

474
void App::clearDocument()
×
475
{
476
    m_doc->masterTimer()->stop();
×
477
    VirtualConsole::instance()->resetContents();
×
478
    ShowManager::instance()->clearContents();
×
479
    m_doc->clearContents();
×
480
    if (Monitor::instance() != NULL)
×
481
        Monitor::instance()->updateView();
×
482
    SimpleDesk::instance()->clearContents();
×
483
    m_doc->inputOutputMap()->resetUniverses();
×
484
    setFileName(QString());
×
485
    m_doc->resetModified();
×
486
    m_doc->inputOutputMap()->startUniverses();
×
487
    m_doc->masterTimer()->start();
×
488
}
×
489

490
Doc *App::doc()
×
491
{
492
    return m_doc;
×
493
}
494

495
void App::initDoc()
×
496
{
UNCOV
497
    Q_ASSERT(m_doc == NULL);
×
498
    m_doc = new Doc(this);
×
499

500
    connect(m_doc, SIGNAL(modified(bool)), this, SLOT(slotDocModified(bool)));
×
501
    connect(m_doc, SIGNAL(modeChanged(Doc::Mode)), this, SLOT(slotModeChanged(Doc::Mode)));
×
502
#ifdef DEBUG_SPEED
503
    speedTime.start();
504
#endif
505
    /* Load user fixtures first so that they override system fixtures */
506
    m_doc->fixtureDefCache()->load(QLCFixtureDefCache::userDefinitionDirectory());
×
507
    m_doc->fixtureDefCache()->loadMap(QLCFixtureDefCache::systemDefinitionDirectory());
×
508

509
    /* Load channel modifiers templates */
510
    m_doc->modifiersCache()->load(QLCModifiersCache::systemTemplateDirectory(), true);
×
511
    m_doc->modifiersCache()->load(QLCModifiersCache::userTemplateDirectory());
×
512

513
    /* Load RGB scripts */
514
    m_doc->rgbScriptsCache()->load(RGBScriptsCache::systemScriptsDirectory());
×
515
    m_doc->rgbScriptsCache()->load(RGBScriptsCache::userScriptsDirectory());
×
516

517
    /* Load plugins */
518
    connect(m_doc->ioPluginCache(), SIGNAL(pluginLoaded(const QString&)),
×
519
            this, SLOT(slotSetProgressText(const QString&)));
520
    m_doc->ioPluginCache()->load(IOPluginCache::systemPluginDirectory());
×
521

522
    /* Load audio decoder plugins
523
     * This doesn't use a AudioPluginCache::systemPluginDirectory() cause
524
     * otherwise the qlcconfig.h creation should have been moved into the
525
     * audio folder, which doesn't make much sense */
526
    m_doc->audioPluginCache()->load(QLCFile::systemDirectory(AUDIOPLUGINDIR, KExtPlugin));
×
527

528
    /* Restore outputmap settings */
UNCOV
529
    Q_ASSERT(m_doc->inputOutputMap() != NULL);
×
530

531
    /* Load input plugins & profiles */
532
    m_doc->inputOutputMap()->loadProfiles(InputOutputMap::userProfileDirectory());
×
533
    m_doc->inputOutputMap()->loadProfiles(InputOutputMap::systemProfileDirectory());
×
534
    m_doc->inputOutputMap()->loadDefaults();
×
535

536
#ifdef DEBUG_SPEED
537
    qDebug() << "[App] Doc initialization took" << speedTime.elapsed() << "ms";
538
#endif
539

540
    m_doc->inputOutputMap()->startUniverses();
×
541
    m_doc->masterTimer()->start();
×
542
}
×
543

544
void App::slotDocModified(bool state)
×
545
{
546
    QString caption(APPNAME);
×
547

548
    if (fileName().isEmpty() == false)
×
549
        caption += QString(" - ") + QDir::toNativeSeparators(fileName());
×
550
    else
551
        caption += tr(" - New Workspace");
×
552

553
    if (state == true)
×
554
        setWindowTitle(caption + QString(" *"));
×
555
    else
556
        setWindowTitle(caption);
×
557
}
×
558

559
void App::slotUniverseWritten(quint32 idx, const QByteArray &ua)
×
560
{
561
    foreach (Fixture *fixture, m_doc->fixtures())
×
562
    {
563
        if (fixture->universe() != idx)
×
564
            continue;
×
565

566
        fixture->setChannelValues(ua);
×
UNCOV
567
    }
×
568
}
×
569

570
/*****************************************************************************
571
 * Main application Mode
572
 *****************************************************************************/
573

574
void App::enableKioskMode()
×
575
{
576
    // Turn on operate mode
577
    m_doc->setKiosk(true);
×
578
    m_doc->setMode(Doc::Operate);
×
579

580
    // No need for these
581
    m_tab->removeTab(m_tab->indexOf(FixtureManager::instance()));
×
582
    m_tab->removeTab(m_tab->indexOf(FunctionManager::instance()));
×
583
    m_tab->removeTab(m_tab->indexOf(ShowManager::instance()));
×
584
    m_tab->removeTab(m_tab->indexOf(SimpleDesk::instance()));
×
585
    m_tab->removeTab(m_tab->indexOf(InputOutputManager::instance()));
×
586

587
    // Hide the tab bar to save some pixels
588
    m_tab->tabBar()->hide();
×
589

590
    // No need for the toolbar
591
    delete m_toolbar;
×
592
    m_toolbar = NULL;
×
593
}
×
594

595
void App::createKioskCloseButton(const QRect& rect)
×
596
{
597
    QPushButton* btn = new QPushButton(VirtualConsole::instance()->contents());
×
598
    btn->setIcon(QIcon(":/exit.png"));
×
599
    btn->setToolTip(tr("Exit"));
×
600
    btn->setGeometry(rect);
×
601
    connect(btn, SIGNAL(clicked()), this, SLOT(close()));
×
602
    btn->show();
×
603
}
×
604

605
void App::slotModeOperate()
×
606
{
607
    m_doc->setMode(Doc::Operate);
×
608
}
×
609

610
void App::slotModeDesign()
×
611
{
612
    if (m_doc->masterTimer()->runningFunctions() > 0)
×
613
    {
614
        int result = QMessageBox::warning(
×
615
                         this,
616
                         tr("Switch to Design Mode"),
×
617
                         tr("There are still running functions.\n"
×
618
                            "Really stop them and switch back to "
619
                            "Design mode?"),
620
                         QMessageBox::Yes,
621
                         QMessageBox::No);
622

623
        if (result == QMessageBox::No)
×
UNCOV
624
            return;
×
625
        else
626
            m_doc->masterTimer()->stopAllFunctions();
×
627
    }
628

629
    m_liveEditVirtualConsoleAction->setChecked(false);
×
630
    m_doc->setMode(Doc::Design);
×
631
}
632

633
void App::slotModeToggle()
×
634
{
635
    if (m_doc->mode() == Doc::Design)
×
636
        slotModeOperate();
×
637
    else
638
        slotModeDesign();
×
639
}
×
640

641
void App::slotModeChanged(Doc::Mode mode)
×
642
{
643
    if (mode == Doc::Operate)
×
644
    {
645
        /* Disable editing features */
646
        m_fileNewAction->setEnabled(false);
×
647
        m_fileOpenAction->setEnabled(false);
×
648
        m_liveEditAction->setEnabled(true);
×
649
        m_liveEditVirtualConsoleAction->setEnabled(true);
×
650

651
        m_modeToggleAction->setIcon(QIcon(":/design.png"));
×
652
        m_modeToggleAction->setText(tr("Design"));
×
653
        m_modeToggleAction->setToolTip(tr("Switch to design mode"));
×
654
    }
655
    else if (mode == Doc::Design)
×
656
    {
657
        /* Enable editing features */
658
        m_fileNewAction->setEnabled(true);
×
659
        m_fileOpenAction->setEnabled(true);
×
660
        m_liveEditAction->setEnabled(false);
×
661
        m_liveEditVirtualConsoleAction->setEnabled(false);
×
662

663
        m_modeToggleAction->setIcon(QIcon(":/operate.png"));
×
664
        m_modeToggleAction->setText(tr("Operate"));
×
665
        m_modeToggleAction->setToolTip(tr("Switch to operate mode"));
×
666
    }
667
}
×
668

669
/*****************************************************************************
670
 * Actions and toolbar
671
 *****************************************************************************/
672

673
void App::initActions()
×
674
{
675
    /* File actions */
676
    m_fileNewAction = new QAction(QIcon(":/filenew.png"), tr("&New"), this);
×
677
    m_fileNewAction->setShortcut(QKeySequence(tr("CTRL+N", "File|New")));
×
678
    connect(m_fileNewAction, SIGNAL(triggered(bool)), this, SLOT(slotFileNew()));
×
679

680
    m_fileOpenAction = new QAction(QIcon(":/fileopen.png"), tr("&Open"), this);
×
681
    m_fileOpenAction->setShortcut(QKeySequence(tr("CTRL+O", "File|Open")));
×
682
    connect(m_fileOpenAction, SIGNAL(triggered(bool)), this, SLOT(slotFileOpen()));
×
683

684
    m_fileSaveAction = new QAction(QIcon(":/filesave.png"), tr("&Save"), this);
×
685
    m_fileSaveAction->setShortcut(QKeySequence(tr("CTRL+S", "File|Save")));
×
686
    connect(m_fileSaveAction, SIGNAL(triggered(bool)), this, SLOT(slotFileSave()));
×
687

688
    m_fileSaveAsAction = new QAction(QIcon(":/filesaveas.png"), tr("Save &As..."), this);
×
689
    connect(m_fileSaveAsAction, SIGNAL(triggered(bool)), this, SLOT(slotFileSaveAs()));
×
690

691
    /* Control actions */
692
    m_modeToggleAction = new QAction(QIcon(":/operate.png"), tr("&Operate"), this);
×
693
    m_modeToggleAction->setToolTip(tr("Switch to operate mode"));
×
694
    m_modeToggleAction->setShortcut(QKeySequence(tr("CTRL+F12", "Control|Toggle operate/design mode")));
×
695
    connect(m_modeToggleAction, SIGNAL(triggered(bool)), this, SLOT(slotModeToggle()));
×
696

697
    m_controlMonitorAction = new QAction(QIcon(":/monitor.png"), tr("&Monitor"), this);
×
698
    m_controlMonitorAction->setShortcut(QKeySequence(tr("CTRL+M", "Control|Monitor")));
×
699
    connect(m_controlMonitorAction, SIGNAL(triggered(bool)), this, SLOT(slotControlMonitor()));
×
700

701
    m_addressToolAction = new QAction(QIcon(":/diptool.png"), tr("Address Tool"), this);
×
702
    connect(m_addressToolAction, SIGNAL(triggered()), this, SLOT(slotAddressTool()));
×
703

704
    m_controlBlackoutAction = new QAction(QIcon(":/blackout.png"), tr("Toggle &Blackout"), this);
×
705
    m_controlBlackoutAction->setCheckable(true);
×
706
    connect(m_controlBlackoutAction, SIGNAL(triggered(bool)), this, SLOT(slotControlBlackout()));
×
707
    m_controlBlackoutAction->setChecked(m_doc->inputOutputMap()->blackout());
×
708

709
    m_liveEditAction = new QAction(QIcon(":/liveedit.png"), tr("Live edit a function"), this);
×
710
    connect(m_liveEditAction, SIGNAL(triggered()), this, SLOT(slotFunctionLiveEdit()));
×
711
    m_liveEditAction->setEnabled(false);
×
712

713
    m_liveEditVirtualConsoleAction = new QAction(QIcon(":/liveedit_vc.png"), tr("Toggle Virtual Console Live edit"), this);
×
714
    connect(m_liveEditVirtualConsoleAction, SIGNAL(triggered()), this, SLOT(slotLiveEditVirtualConsole()));
×
715
    m_liveEditVirtualConsoleAction->setCheckable(true);
×
716
    m_liveEditVirtualConsoleAction->setEnabled(false);
×
717

718
    m_dumpDmxAction = new QAction(QIcon(":/add_dump.png"), tr("Dump DMX values to a function"), this);
×
719
    m_dumpDmxAction->setShortcut(QKeySequence(tr("CTRL+D", "Control|Dump DMX")));
×
720
    connect(m_dumpDmxAction, SIGNAL(triggered()), this, SLOT(slotDumpDmxIntoFunction()));
×
721

722
    m_controlPanicAction = new QAction(QIcon(":/panic.png"), tr("Stop ALL functions!"), this);
×
723
    m_controlPanicAction->setShortcut(QKeySequence("CTRL+SHIFT+ESC"));
×
724
    connect(m_controlPanicAction, SIGNAL(triggered(bool)), this, SLOT(slotControlPanic()));
×
725

726
    m_fadeAndStopMenu = new QMenu();
×
727
    QAction *fade1 = new QAction(tr("Fade 1 second and stop"), this);
×
728
    fade1->setData(QVariant(1000));
×
729
    connect(fade1, SIGNAL(triggered()), this, SLOT(slotFadeAndStopAll()));
×
730
    m_fadeAndStopMenu->addAction(fade1);
×
731

732
    QAction *fade5 = new QAction(tr("Fade 5 seconds and stop"), this);
×
733
    fade5->setData(QVariant(5000));
×
734
    connect(fade5, SIGNAL(triggered()), this, SLOT(slotFadeAndStopAll()));
×
735
    m_fadeAndStopMenu->addAction(fade5);
×
736

737
    QAction *fade10 = new QAction(tr("Fade 10 second and stop"), this);
×
738
    fade10->setData(QVariant(10000));
×
739
    connect(fade10, SIGNAL(triggered()), this, SLOT(slotFadeAndStopAll()));
×
740
    m_fadeAndStopMenu->addAction(fade10);
×
741

742
    QAction *fade30 = new QAction(tr("Fade 30 second and stop"), this);
×
743
    fade30->setData(QVariant(30000));
×
744
    connect(fade30, SIGNAL(triggered()), this, SLOT(slotFadeAndStopAll()));
×
745
    m_fadeAndStopMenu->addAction(fade30);
×
746

747
    m_controlPanicAction->setMenu(m_fadeAndStopMenu);
×
748

749
    m_controlFullScreenAction = new QAction(QIcon(":/fullscreen.png"), tr("Toggle Full Screen"), this);
×
750
    m_controlFullScreenAction->setCheckable(true);
×
751
    m_controlFullScreenAction->setShortcut(QKeySequence(tr("CTRL+F11", "Control|Toggle Full Screen")));
×
752
    connect(m_controlFullScreenAction, SIGNAL(triggered(bool)), this, SLOT(slotControlFullScreen()));
×
753

754
    /* Help actions */
755
    m_helpIndexAction = new QAction(QIcon(":/help.png"), tr("&Index"), this);
×
756
    m_helpIndexAction->setShortcut(QKeySequence(tr("SHIFT+F1", "Help|Index")));
×
757
    connect(m_helpIndexAction, SIGNAL(triggered(bool)), this, SLOT(slotHelpIndex()));
×
758

759
    m_helpAboutAction = new QAction(QIcon(":/qlcplus.png"), tr("&About QLC+"), this);
×
760
    connect(m_helpAboutAction, SIGNAL(triggered(bool)), this, SLOT(slotHelpAbout()));
×
761

762
    if (QLCFile::hasWindowManager() == false)
×
763
    {
764
        m_quitAction = new QAction(QIcon(":/exit.png"), tr("Quit QLC+"), this);
×
765
        m_quitAction->setShortcut(QKeySequence("CTRL+ALT+Backspace"));
×
766
        connect(m_quitAction, SIGNAL(triggered(bool)), this, SLOT(close()));
×
767
    }
768
}
×
769

770
void App::initToolBar()
×
771
{
772
    m_toolbar = new QToolBar(tr("Workspace"), this);
×
773
    m_toolbar->setFloatable(false);
×
774
    m_toolbar->setMovable(false);
×
775
    m_toolbar->setAllowedAreas(Qt::TopToolBarArea);
×
776
    m_toolbar->setContextMenuPolicy(Qt::CustomContextMenu);
×
777
    addToolBar(m_toolbar);
×
778
    m_toolbar->addAction(m_fileNewAction);
×
779
    m_toolbar->addAction(m_fileOpenAction);
×
780
    m_toolbar->addAction(m_fileSaveAction);
×
781
    m_toolbar->addAction(m_fileSaveAsAction);
×
782
    m_toolbar->addSeparator();
×
783
    m_toolbar->addAction(m_controlMonitorAction);
×
784
    m_toolbar->addAction(m_addressToolAction);
×
785
    m_toolbar->addSeparator();
×
786
    m_toolbar->addAction(m_controlFullScreenAction);
×
787
    m_toolbar->addAction(m_helpIndexAction);
×
788
    m_toolbar->addAction(m_helpAboutAction);
×
789
    if (QLCFile::hasWindowManager() == false)
×
790
        m_toolbar->addAction(m_quitAction);
×
791

792
    /* Create an empty widget between help items to flush them to the right */
793
    QWidget* widget = new QWidget(this);
×
UNCOV
794
    widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
×
795
    m_toolbar->addWidget(widget);
×
796
    m_toolbar->addAction(m_dumpDmxAction);
×
797
    m_toolbar->addAction(m_liveEditAction);
×
798
    m_toolbar->addAction(m_liveEditVirtualConsoleAction);
×
799
    m_toolbar->addSeparator();
×
800
    m_toolbar->addAction(m_controlPanicAction);
×
801
    m_toolbar->addSeparator();
×
802
    m_toolbar->addAction(m_controlBlackoutAction);
×
803
    m_toolbar->addSeparator();
×
804
    m_toolbar->addAction(m_modeToggleAction);
×
805

806
    QToolButton* btn = qobject_cast<QToolButton*> (m_toolbar->widgetForAction(m_fileOpenAction));
×
UNCOV
807
    Q_ASSERT(btn != NULL);
×
808
    btn->setPopupMode(QToolButton::DelayedPopup);
×
809
    updateFileOpenMenu("");
×
810

811
    btn = qobject_cast<QToolButton*> (m_toolbar->widgetForAction(m_controlPanicAction));
×
UNCOV
812
    Q_ASSERT(btn != NULL);
×
813
    btn->setPopupMode(QToolButton::DelayedPopup);
×
814
}
×
815

816
/*****************************************************************************
817
 * File action slots
818
 *****************************************************************************/
819

820
bool App::handleFileError(QFile::FileError error)
×
821
{
822
    QString msg;
×
823

824
    switch (error)
×
825
    {
UNCOV
826
        case QFile::NoError:
×
UNCOV
827
            return true;
×
828
        break;
829
        case QFile::ReadError:
×
830
            msg = tr("Unable to read from file");
×
831
        break;
×
832
        case QFile::WriteError:
×
833
            msg = tr("Unable to write to file");
×
834
        break;
×
835
        case QFile::FatalError:
×
836
            msg = tr("A fatal error occurred");
×
837
        break;
×
838
        case QFile::ResourceError:
×
839
            msg = tr("Unable to access resource");
×
840
        break;
×
841
        case QFile::OpenError:
×
842
            msg = tr("Unable to open file for reading or writing");
×
843
        break;
×
844
        case QFile::AbortError:
×
845
            msg = tr("Operation was aborted");
×
846
        break;
×
847
        case QFile::TimeOutError:
×
848
            msg = tr("Operation timed out");
×
849
        break;
×
850
        default:
×
851
        case QFile::UnspecifiedError:
852
            msg = tr("An unspecified error has occurred. Nice.");
×
853
        break;
×
854
    }
855

856
    QMessageBox::warning(this, tr("File error"), msg);
×
857

858
    return false;
×
UNCOV
859
}
×
860

861
bool App::saveModifiedDoc(const QString & title, const QString & message)
×
862
{
863
    // if it's not modified, there's nothing to save
864
    if (m_doc->isModified() == false)
×
UNCOV
865
        return true;
×
866

867
    int result = QMessageBox::warning(this, title,
×
868
                                          message,
UNCOV
869
                                          QMessageBox::Yes |
×
UNCOV
870
                                          QMessageBox::No |
×
871
                                          QMessageBox::Cancel);
872
    if (result == QMessageBox::Yes)
×
873
    {
874
        slotFileSave();
×
875
        // we check whether m_doc is not modified anymore, rather than
876
        // result of slotFileSave() since the latter returns NoError
877
        // in cases like when the user pressed cancel in the save dialog
878
        if (m_doc->isModified() == false)
×
879
        {
UNCOV
880
            return true;
×
881
        }
882
        else
883
        {
884
            return false;
×
885
        }
886
    }
887
    else if (result == QMessageBox::No)
×
888
    {
UNCOV
889
        return true;
×
890
    }
891
    else
892
    {
893
        return false;
×
894
    }
895
}
896

897
void App::updateFileOpenMenu(QString addRecent)
×
898
{
899
    QSettings settings;
×
900
    QStringList menuRecentList;
×
901

902
    if (m_fileOpenMenu == NULL)
×
903
    {
904
        m_fileOpenMenu = new QMenu(this);
×
905
        QPalette p = palette();
×
906
        QString style = QString("QMenu { background: %1;"
×
907
                        "border: 1px solid black; font:bold; }"
908
                        "QMenu::item { background-color: transparent; padding: 5px 10px 5px 10px; border: 1px solid black; }"
909
                        "QMenu::item:selected { background-color: #2D8CFF; }").arg(p.color(QPalette::Window).name());
×
910
        m_fileOpenMenu->setStyleSheet(style);
×
911
        connect(m_fileOpenMenu, SIGNAL(triggered(QAction*)),
×
912
                this, SLOT(slotRecentFileClicked(QAction*)));
913
    }
×
914

915
    foreach (QAction* a, m_fileOpenMenu->actions())
×
916
    {
917
        menuRecentList.append(a->text());
×
918
        m_fileOpenMenu->removeAction(a);
×
UNCOV
919
    }
×
920

921
    if (addRecent.isEmpty() == false)
×
922
    {
UNCOV
923
        menuRecentList.removeAll(addRecent); // in case the string is already present, remove it...
×
UNCOV
924
        menuRecentList.prepend(addRecent); // and add it to the top
×
925
        for (int i = 0; i < menuRecentList.count(); i++)
×
926
        {
927
            settings.setValue(QString("%1%2").arg(SETTINGS_RECENTFILE).arg(i), menuRecentList.at(i));
×
928
            m_fileOpenMenu->addAction(menuRecentList.at(i));
×
929
        }
930
    }
931
    else
932
    {
933
        for (int i = 0; i < MAX_RECENT_FILES; i++)
×
934
        {
935
            QVariant recent = settings.value(QString("%1%2").arg(SETTINGS_RECENTFILE).arg(i));
×
936
            if (recent.isValid() == true)
×
937
            {
938
                menuRecentList.append(recent.toString());
×
939
                m_fileOpenMenu->addAction(menuRecentList.at(i));
×
940
            }
941
        }
×
942
    }
943

944
    // Set the recent files menu to the file open action
945
    if (menuRecentList.isEmpty() == false)
×
946
        m_fileOpenAction->setMenu(m_fileOpenMenu);
×
947
}
×
948

949
bool App::slotFileNew()
×
950
{
951
    QString msg(tr("Do you wish to save the current workspace?\n" \
UNCOV
952
                   "Changes will be lost if you don't save them."));
×
953
    if (saveModifiedDoc(tr("New Workspace"), msg) == false)
×
954
    {
UNCOV
955
        return false;
×
956
    }
957

958
    clearDocument();
×
UNCOV
959
    return true;
×
UNCOV
960
}
×
961

962
QFile::FileError App::slotFileOpen()
×
963
{
964
    QString fn;
×
965

966
    /* Check that the user is aware of losing previous changes */
967
    QString msg(tr("Do you wish to save the current workspace?\n" \
UNCOV
968
                   "Changes will be lost if you don't save them."));
×
969
    if (saveModifiedDoc(tr("Open Workspace"), msg) == false)
×
970
    {
971
        /* Second thoughts... Cancel loading. */
UNCOV
972
        return QFile::NoError;
×
973
    }
974

975
    /* Create a file open dialog */
976
    QFileDialog dialog(this);
×
977
    dialog.setWindowTitle(tr("Open Workspace"));
×
978
    dialog.setAcceptMode(QFileDialog::AcceptOpen);
×
979
    dialog.selectFile(fileName());
×
980
    if (m_workingDirectory.exists() == true)
×
981
        dialog.setDirectory(m_workingDirectory);
×
982

983
    /* Append file filters to the dialog */
984
    QStringList filters;
×
985
    filters << tr("Workspaces (*%1)").arg(KExtWorkspace);
×
986
#if defined(WIN32) || defined(Q_OS_WIN)
987
    filters << tr("All Files (*.*)");
988
#else
989
    filters << tr("All Files (*)");
×
990
#endif
991
    dialog.setNameFilters(filters);
×
992

993
    /* Append useful URLs to the dialog */
994
    QList <QUrl> sidebar;
×
995
    sidebar.append(QUrl::fromLocalFile(QDir::homePath()));
×
996
    sidebar.append(QUrl::fromLocalFile(QDir::rootPath()));
×
997
    dialog.setSidebarUrls(sidebar);
×
998

999
    /* Get file name */
1000
    if (dialog.exec() != QDialog::Accepted)
×
UNCOV
1001
        return QFile::NoError;
×
1002
    QSettings settings;
×
1003
    m_workingDirectory = dialog.directory();
×
1004
    settings.setValue(SETTINGS_WORKINGPATH, m_workingDirectory.absolutePath());
×
1005

1006
    fn = dialog.selectedFiles().first();
×
1007
    if (fn.isEmpty() == true)
×
UNCOV
1008
        return QFile::NoError;
×
1009

1010
    /* Clear existing document data */
1011
    clearDocument();
×
1012

1013
#ifdef DEBUG_SPEED
1014
    speedTime.restart();
1015
#endif
1016

1017
    /* Load the file */
1018
    QFile::FileError error = loadXML(fn);
×
1019
    if (handleFileError(error) == true)
×
1020
        m_doc->resetModified();
×
1021

1022
#ifdef DEBUG_SPEED
1023
    qDebug() << "[App] Project loaded in" << speedTime.elapsed() << "ms.";
1024
#endif
1025

1026
    /* Update these in any case, since they are at least emptied now as
1027
       a result of calling clearDocument() a few lines ago. */
1028
    //if (FunctionManager::instance() != NULL)
1029
    //    FunctionManager::instance()->updateTree();
1030
    if (FixtureManager::instance() != NULL)
×
1031
        FixtureManager::instance()->updateView();
×
1032
    if (InputOutputManager::instance() != NULL)
×
1033
        InputOutputManager::instance()->updateList();
×
1034
    if (Monitor::instance() != NULL)
×
1035
        Monitor::instance()->updateView();
×
1036

1037
    updateFileOpenMenu(fn);
×
1038

1039
    return error;
×
1040
}
×
1041

1042
QFile::FileError App::slotFileSave()
×
1043
{
1044
    QFile::FileError error;
1045

1046
    /* Attempt to save with the existing name. Fall back to Save As. */
1047
    if (fileName().isEmpty() == true)
×
1048
        error = slotFileSaveAs();
×
1049
    else
1050
        error = saveXML(fileName());
×
1051

1052
    handleFileError(error);
×
1053
    return error;
×
1054
}
1055

1056
QFile::FileError App::slotFileSaveAs()
×
1057
{
1058
    QString fn;
×
1059

1060
    /* Create a file save dialog */
1061
    QFileDialog dialog(this);
×
1062
    dialog.setWindowTitle(tr("Save Workspace As"));
×
1063
    dialog.setAcceptMode(QFileDialog::AcceptSave);
×
1064
    dialog.selectFile(fileName());
×
1065

1066
    /* Append file filters to the dialog */
1067
    QStringList filters;
×
1068
    filters << tr("Workspaces (*%1)").arg(KExtWorkspace);
×
1069
#if defined(WIN32) || defined(Q_OS_WIN)
1070
    filters << tr("All Files (*.*)");
1071
#else
1072
    filters << tr("All Files (*)");
×
1073
#endif
1074
    dialog.setNameFilters(filters);
×
1075

1076
    /* Append useful URLs to the dialog */
1077
    QList <QUrl> sidebar;
×
1078
    sidebar.append(QUrl::fromLocalFile(QDir::homePath()));
×
1079
    sidebar.append(QUrl::fromLocalFile(QDir::rootPath()));
×
1080
    dialog.setSidebarUrls(sidebar);
×
1081

1082
    /* Get file name */
1083
    if (dialog.exec() != QDialog::Accepted)
×
UNCOV
1084
        return QFile::NoError;
×
1085

1086
    fn = dialog.selectedFiles().first();
×
1087
    if (fn.isEmpty() == true)
×
UNCOV
1088
        return QFile::NoError;
×
1089

1090
    /* Always use the workspace suffix */
1091
    if (fn.right(4) != KExtWorkspace)
×
1092
        fn += KExtWorkspace;
×
1093

1094
    /* Set the workspace path before saving the new XML. In this way local files
1095
       can be loaded even if the workspace file will be moved */
1096
    m_doc->setWorkspacePath(QFileInfo(fn).absolutePath());
×
1097

1098
    /* Save the document and set workspace name */
1099
    QFile::FileError error = saveXML(fn);
×
1100
    handleFileError(error);
×
1101

1102
    updateFileOpenMenu(fn);
×
1103
    return error;
×
1104
}
×
1105

1106
/*****************************************************************************
1107
 * Control action slots
1108
 *****************************************************************************/
1109

1110
void App::slotControlMonitor()
×
1111
{
1112
    Monitor::createAndShow(this, m_doc);
×
1113
}
×
1114

1115
void App::slotAddressTool()
×
1116
{
1117
    AddressTool at(this);
×
1118
    at.exec();
×
1119
}
×
1120

1121
void App::slotControlBlackout()
×
1122
{
1123
    m_doc->inputOutputMap()->setBlackout(!m_doc->inputOutputMap()->blackout());
×
1124
}
×
1125

1126
void App::slotBlackoutChanged(bool state)
×
1127
{
1128
    m_controlBlackoutAction->setChecked(state);
×
1129
}
×
1130

1131
void App::slotControlPanic()
×
1132
{
1133
    m_doc->masterTimer()->stopAllFunctions();
×
1134
}
×
1135

1136
void App::slotFadeAndStopAll()
×
1137
{
1138
    QAction *action = (QAction *)sender();
×
1139
    int timeout = action->data().toInt();
×
1140

1141
    m_doc->masterTimer()->fadeAndStopAll(timeout);
×
1142
}
×
1143

1144
void App::slotRunningFunctionsChanged()
×
1145
{
1146
    if (m_doc->masterTimer()->runningFunctions() > 0)
×
1147
        m_controlPanicAction->setEnabled(true);
×
1148
    else
1149
        m_controlPanicAction->setEnabled(false);
×
1150
}
×
1151

1152
void App::slotDumpDmxIntoFunction()
×
1153
{
1154
    DmxDumpFactory ddf(m_doc, m_dumpProperties, this);
×
1155
    if (ddf.exec() != QDialog::Accepted)
×
UNCOV
1156
        return;
×
1157
}
×
1158

1159
void App::slotFunctionLiveEdit()
×
1160
{
1161
    FunctionSelection fs(this, m_doc);
×
1162
    fs.setMultiSelection(false);
×
1163
    fs.setFilter(Function::SceneType | Function::ChaserType | Function::SequenceType | Function::EFXType | Function::RGBMatrixType);
×
1164
    fs.disableFilters(Function::ShowType | Function::ScriptType | Function::CollectionType | Function::AudioType);
×
1165

1166
    if (fs.exec() == QDialog::Accepted)
×
1167
    {
1168
        if (fs.selection().count() > 0)
×
1169
        {
1170
            FunctionLiveEditDialog fle(m_doc, fs.selection().first(), this);
×
1171
            fle.exec();
×
1172
        }
×
1173
    }
1174
}
×
1175

1176
void App::slotLiveEditVirtualConsole()
×
1177
{
1178
    VirtualConsole::instance()->toggleLiveEdit();
×
1179
}
×
1180

1181
void App::slotDetachContext(int index)
×
1182
{
1183
    /* Get the widget that has been double-clicked */
1184
    QWidget *context = m_tab->widget(index);
×
1185
    context->setProperty("tabIndex", index);
×
1186
    context->setProperty("tabIcon", QVariant::fromValue(m_tab->tabIcon(index)));
×
1187
    context->setProperty("tabLabel", m_tab->tabText(index));
×
1188

1189
    qDebug() << "Detaching context" << context;
×
1190

1191
    DetachedContext *detachedWindow = new DetachedContext(this);
×
1192
    detachedWindow->setCentralWidget(context);
×
1193
    detachedWindow->resize(800, 600);
×
1194
    detachedWindow->show();
×
1195
    context->show();
×
1196

1197
    connect(detachedWindow, SIGNAL(closing()),
×
1198
            this, SLOT(slotReattachContext()));
1199
}
×
1200

1201
void App::slotReattachContext()
×
1202
{
1203
    DetachedContext *window = qobject_cast<DetachedContext *>(sender());
×
1204

1205
    QWidget *context = window->centralWidget();
×
1206
    int tabIndex = context->property("tabIndex").toInt();
×
1207
    QIcon tabIcon = context->property("tabIcon").value<QIcon>();
×
1208
    QString tabLabel = context->property("tabLabel").toString();
×
1209

1210
    qDebug() << "Reattaching context" << tabIndex << tabLabel << context;
×
1211

1212
    context->setParent(m_tab);
×
1213
    m_tab->insertTab(tabIndex, context, tabIcon, tabLabel);
×
1214
}
×
1215

1216
void App::slotControlFullScreen()
×
1217
{
1218
    static int wstate = windowState();
×
1219

1220
    if (windowState() & Qt::WindowFullScreen)
×
1221
    {
1222
        if (wstate & Qt::WindowMaximized)
×
1223
            showMaximized();
×
1224
        else
1225
            showNormal();
×
1226
        wstate = windowState();
×
1227
    }
1228
    else
1229
    {
1230
        wstate = windowState();
×
1231
        showFullScreen();
×
1232

1233
        // In case slotControlFullScreen() is called programmatically (from main.cpp)
1234
        if (m_controlFullScreenAction->isChecked() == false)
×
1235
            m_controlFullScreenAction->setChecked(true);
×
1236
    }
1237
}
×
1238

1239
void App::slotControlFullScreen(bool usingGeometry)
×
1240
{
1241
    if (usingGeometry == true)
×
1242
    {
1243
        QScreen *screen = QGuiApplication::screens().first();
×
1244
        setGeometry(screen->geometry());
×
1245
    }
1246
    else
1247
    {
1248
        slotControlFullScreen();
×
1249
    }
1250
}
×
1251

1252
/*****************************************************************************
1253
 * Help action slots
1254
 *****************************************************************************/
1255

1256
void App::slotHelpIndex()
×
1257
{
1258
    QDesktopServices::openUrl(QUrl("https://docs.qlcplus.org/"));
×
1259
}
×
1260

1261
void App::slotHelpAbout()
×
1262
{
1263
    AboutBox ab(this);
×
1264
    ab.exec();
×
1265
}
×
1266

1267
void App::slotRecentFileClicked(QAction *recent)
×
1268
{
1269
    if (recent == NULL)
×
1270
        return;
×
1271

1272
    QString recentAbsPath = recent->text();
×
1273
    QFile testFile(recentAbsPath);
×
1274
    if (testFile.exists() == false)
×
1275
    {
1276
        QMessageBox::critical(this, tr("Error"),
×
1277
                              tr("File not found!\nThe selected file has been moved or deleted."),
×
1278
                              QMessageBox::Close);
1279
        return;
×
1280
    }
1281

1282
    /* Check that the user is aware of losing previous changes */
1283
    QString msg(tr("Do you wish to save the current workspace?\n" \
UNCOV
1284
                   "Changes will be lost if you don't save them."));
×
1285
    if (saveModifiedDoc(tr("Open Workspace"), msg) == false)
×
1286
    {
1287
        /* Second thoughts... Cancel loading. */
UNCOV
1288
        return;
×
1289
    }
1290

1291
    m_workingDirectory = QFileInfo(recentAbsPath).absoluteDir();
×
1292
    QSettings settings;
×
1293
    settings.setValue(SETTINGS_WORKINGPATH, m_workingDirectory.absolutePath());
×
1294

1295
    /* Clear existing document data */
1296
    clearDocument();
×
1297

1298
#ifdef DEBUG_SPEED
1299
    speedTime.restart();
1300
#endif
1301

1302
    /* Load the file */
1303
    QFile::FileError error = loadXML(recentAbsPath);
×
1304
    if (handleFileError(error) == true)
×
1305
        m_doc->resetModified();
×
1306

1307
#ifdef DEBUG_SPEED
1308
    qDebug() << "[App] Project loaded in" << speedTime.elapsed() << "ms.";
1309
#endif
1310

1311
    /* Update these in any case, since they are at least emptied now as
1312
       a result of calling clearDocument() a few lines ago. */
1313
    //if (FunctionManager::instance() != NULL)
1314
    //    FunctionManager::instance()->updateTree();
1315
    if (FixtureManager::instance() != NULL)
×
1316
        FixtureManager::instance()->updateView();
×
1317
    if (InputOutputManager::instance() != NULL)
×
1318
        InputOutputManager::instance()->updateList();
×
1319
    if (Monitor::instance() != NULL)
×
1320
        Monitor::instance()->updateView();
×
1321
}
×
1322

1323
/*****************************************************************************
1324
 * Load & Save
1325
 *****************************************************************************/
1326

1327
void App::setFileName(const QString& fileName)
×
1328
{
1329
    m_fileName = fileName;
×
1330
}
×
1331

1332
QString App::fileName() const
×
1333
{
1334
    return m_fileName;
×
1335
}
1336

1337
QFile::FileError App::loadXML(const QString& fileName)
×
1338
{
UNCOV
1339
    QFile::FileError retval = QFile::NoError;
×
1340

1341
    if (fileName.isEmpty() == true)
×
UNCOV
1342
        return QFile::OpenError;
×
1343

1344
    QXmlStreamReader *doc = QLCFile::getXMLReader(fileName);
×
1345
    if (doc == NULL || doc->device() == NULL || doc->hasError())
×
1346
    {
1347
        qWarning() << Q_FUNC_INFO << "Unable to read from" << fileName;
×
1348
        return QFile::ReadError;
×
1349
    }
1350

1351
    while (!doc->atEnd())
×
1352
    {
1353
        if (doc->readNext() == QXmlStreamReader::DTD)
×
UNCOV
1354
            break;
×
1355
    }
1356
    if (doc->hasError())
×
1357
    {
1358
        QLCFile::releaseXMLReader(doc);
×
1359
        return QFile::ResourceError;
×
1360
    }
1361

1362
    /* Set the workspace path before loading the new XML. In this way local files
1363
       can be loaded even if the workspace file has been moved */
1364
    m_doc->setWorkspacePath(QFileInfo(fileName).absolutePath());
×
1365

1366
    if (doc->dtdName() == KXMLQLCWorkspace)
×
1367
    {
1368
        if (loadXML(*doc) == false)
×
1369
        {
UNCOV
1370
            retval = QFile::ReadError;
×
1371
        }
1372
        else
1373
        {
1374
            setFileName(fileName);
×
1375
            m_doc->resetModified();
×
UNCOV
1376
            retval = QFile::NoError;
×
1377
        }
1378
    }
1379
    else
1380
    {
UNCOV
1381
        retval = QFile::ReadError;
×
1382
        qWarning() << Q_FUNC_INFO << fileName
×
1383
                   << "is not a workspace file";
×
1384
    }
1385

1386
    QLCFile::releaseXMLReader(doc);
×
1387

1388
    return retval;
×
1389
}
1390

1391
bool App::loadXML(QXmlStreamReader& doc, bool goToConsole, bool fromMemory)
×
1392
{
1393
    if (doc.readNextStartElement() == false)
×
UNCOV
1394
        return false;
×
1395

1396
    if (doc.name() != KXMLQLCWorkspace)
×
1397
    {
1398
        qWarning() << Q_FUNC_INFO << "Workspace node not found";
×
1399
        return false;
×
1400
    }
1401

1402
    QString activeWindowName = doc.attributes().value(KXMLQLCWorkspaceWindow).toString();
×
1403

1404
    while (doc.readNextStartElement())
×
1405
    {
1406
        if (doc.name() == KXMLQLCEngine)
×
1407
        {
1408
            m_doc->loadXML(doc);
×
1409
        }
1410
        else if (doc.name() == KXMLQLCVirtualConsole)
×
1411
        {
1412
            VirtualConsole::instance()->loadXML(doc);
×
1413
        }
1414
        else if (doc.name() == KXMLQLCSimpleDesk)
×
1415
        {
1416
            SimpleDesk::instance()->loadXML(doc);
×
1417
        }
1418
        else if (doc.name() == KXMLFixture)
×
1419
        {
1420
            /* Legacy support code, nowadays in Doc */
1421
            Fixture::loader(doc, m_doc);
×
1422
        }
1423
        else if (doc.name() == KXMLQLCFunction)
×
1424
        {
1425
            /* Legacy support code, nowadays in Doc */
1426
            Function::loader(doc, m_doc);
×
1427
        }
1428
        else if (doc.name() == KXMLQLCCreator)
×
1429
        {
1430
            /* Ignore creator information */
1431
            doc.skipCurrentElement();
×
1432
        }
1433
        else
1434
        {
1435
            qWarning() << Q_FUNC_INFO << "Unknown Workspace tag:" << doc.name();
×
1436
            doc.skipCurrentElement();
×
1437
        }
1438
    }
1439

1440
    if (goToConsole == true)
×
1441
        // Force the active window to be Virtual Console
1442
        setActiveWindow(VirtualConsole::staticMetaObject.className());
×
1443
    else
1444
        // Set the active window to what was saved in the workspace file
1445
        setActiveWindow(activeWindowName);
×
1446

1447
    // Perform post-load operations
1448
    VirtualConsole::instance()->postLoad();
×
1449

1450
    if (m_doc->errorLog().isEmpty() == false &&
×
UNCOV
1451
        fromMemory == false)
×
1452
    {
1453
        QMessageBox msg(QMessageBox::Warning, tr("Warning"),
×
1454
                        tr("Some errors occurred while loading the project:") + "<br><br>" + m_doc->errorLog(),
×
1455
                        QMessageBox::Ok);
×
1456
        msg.setTextFormat(Qt::RichText);
×
1457
        QSpacerItem* horizontalSpacer = new QSpacerItem(800, 0, QSizePolicy::Minimum, QSizePolicy::Expanding);
×
1458
        QGridLayout* layout = (QGridLayout*)msg.layout();
×
1459
        layout->addItem(horizontalSpacer, layout->rowCount(), 0, 1, layout->columnCount());
×
1460
        msg.exec();
×
1461
    }
×
1462

1463
    m_doc->inputOutputMap()->startUniverses();
×
1464

UNCOV
1465
    return true;
×
UNCOV
1466
}
×
1467

1468
QFile::FileError App::saveXML(const QString& fileName)
×
1469
{
UNCOV
1470
    QString tempFileName(fileName);
×
1471
    tempFileName += ".temp";
×
1472
    QFile file(tempFileName);
×
1473
    if (file.open(QIODevice::WriteOnly) == false)
×
1474
        return file.error();
×
1475

1476
    QXmlStreamWriter doc(&file);
×
1477
    doc.setAutoFormatting(true);
×
1478
    doc.setAutoFormattingIndent(1);
×
1479
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1480
    doc.setCodec("UTF-8");
1481
#endif
1482

1483
    doc.writeStartDocument();
×
1484
    doc.writeDTD(QString("<!DOCTYPE %1>").arg(KXMLQLCWorkspace));
×
1485

1486
    doc.writeStartElement(KXMLQLCWorkspace);
×
1487
    doc.writeAttribute("xmlns", QString("%1%2").arg(KXMLQLCplusNamespace).arg(KXMLQLCWorkspace));
×
1488
    /* Currently active window */
1489
    QWidget* widget = m_tab->currentWidget();
×
1490
    if (widget != NULL)
×
1491
        doc.writeAttribute(KXMLQLCWorkspaceWindow, QString(widget->metaObject()->className()));
×
1492

1493
    doc.writeStartElement(KXMLQLCCreator);
×
1494
    doc.writeTextElement(KXMLQLCCreatorName, APPNAME);
×
1495
    doc.writeTextElement(KXMLQLCCreatorVersion, APPVERSION);
×
1496
    doc.writeTextElement(KXMLQLCCreatorAuthor, QLCFile::currentUserName());
×
1497
    doc.writeEndElement(); // close KXMLQLCCreator
×
1498

1499
    /* Write engine components to the XML document */
1500
    m_doc->saveXML(&doc);
×
1501

1502
    /* Write virtual console to the XML document */
1503
    VirtualConsole::instance()->saveXML(&doc);
×
1504

1505
    /* Write Simple Desk to the XML document */
1506
    SimpleDesk::instance()->saveXML(&doc);
×
1507

1508
    doc.writeEndElement(); // close KXMLQLCWorkspace
×
1509

1510
    /* End the document and close all the open elements */
1511
    doc.writeEndDocument();
×
1512
    file.close();
×
1513
#ifdef Q_OS_UNIX
1514
    sync();
×
1515
#endif
1516

1517
    // Save to actual requested file name
1518
    QFile currFile(fileName);
×
1519
    if (currFile.exists() && !currFile.remove())
×
1520
    {
1521
        qWarning() << "Could not erase" << fileName;
×
1522
        return currFile.error();
×
1523
    }
1524
    if (!file.rename(fileName))
×
1525
    {
1526
        qWarning() << "Could not rename" << tempFileName << "to" << fileName;
×
1527
        return file.error();
×
1528
    }
1529

1530
    /* Set the file name for the current Doc instance and
1531
       set it also in an unmodified state. */
1532
    setFileName(fileName);
×
1533
    m_doc->resetModified();
×
1534

UNCOV
1535
    return QFile::NoError;
×
1536
}
×
1537

1538
void App::slotLoadDocFromMemory(QString xmlData)
×
1539
{
1540
    if (xmlData.isEmpty())
×
1541
        return;
×
1542

1543
    /* Clear existing document data */
1544
    clearDocument();
×
1545

1546
    QBuffer databuf;
×
1547
    databuf.setData(xmlData.simplified().toUtf8());
×
1548
    databuf.open(QIODevice::ReadOnly | QIODevice::Text);
×
1549

1550
    //qDebug() << "Buffer data:" << databuf.data();
1551
    QXmlStreamReader doc(&databuf);
×
1552

1553
    if (doc.hasError())
×
1554
    {
1555
        qWarning() << Q_FUNC_INFO << "Unable to read from XML in memory";
×
1556
        return;
×
1557
    }
1558

1559
    while (!doc.atEnd())
×
1560
    {
1561
        if (doc.readNext() == QXmlStreamReader::DTD)
×
UNCOV
1562
            break;
×
1563
    }
1564
    if (doc.hasError())
×
1565
    {
1566
        qDebug() << "XML has errors:" << doc.errorString();
×
1567
        return;
×
1568
    }
1569

1570
    if (doc.dtdName() == KXMLQLCWorkspace)
×
1571
        loadXML(doc, true, true);
×
1572
    else
1573
        qDebug() << "XML doesn't have a Workspace tag";
×
1574
}
×
1575

1576
void App::slotSaveAutostart(QString fileName)
×
1577
{
1578
    /* Set the workspace path before saving the new XML. In this way local files
1579
       can be loaded even if the workspace file will be moved */
1580
    m_doc->setWorkspacePath(QFileInfo(fileName).absolutePath());
×
1581

1582
    /* Save the document and set workspace name */
1583
    QFile::FileError error = saveXML(fileName);
×
1584
    handleFileError(error);
×
1585
}
×
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