• 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/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      QStringLiteral("workspace/geometry")
87
#define SETTINGS_WORKINGPATH   QStringLiteral("workspace/workingpath")
88
#define SETTINGS_RECENTFILE    QStringLiteral("workspace/recent")
89
#define KXMLQLCWorkspaceWindow QStringLiteral("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();
×
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
                {
247
                    setGeometry(geometry.width(), geometry.height(), 1, 1);
×
248
                }
249
                else
250
                {
251
                    int w = geometry.width();
×
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
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)
×
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."));
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 " \
×
409
                                            "before closing the application?")) == true)
×
410
        {
411
            e->accept();
×
412
        }
413
        else
414
        {
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
            {
427
                e->ignore();
×
428
                return;
×
429
            }
430
        }
431

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)
×
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
{
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(needAutosave()), this, SLOT(slotDocAutosave()));
×
502
    connect(m_doc, SIGNAL(modeChanged(Doc::Mode)), this, SLOT(slotModeChanged(Doc::Mode)));
×
503
#ifdef DEBUG_SPEED
504
    speedTime.start();
505
#endif
506
    /* Load user fixtures first so that they override system fixtures */
507
    m_doc->fixtureDefCache()->load(QLCFixtureDefCache::userDefinitionDirectory());
×
508
    m_doc->fixtureDefCache()->loadMap(QLCFixtureDefCache::systemDefinitionDirectory());
×
509

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

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

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

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

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

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

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

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

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

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

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

560
void App::slotDocAutosave()
×
561
{
562
    saveXML(autoSaveFileName(), true);
×
563
}
×
564

565
void App::slotUniverseWritten(quint32 idx, const QByteArray &ua)
×
566
{
567
    foreach (Fixture *fixture, m_doc->fixtures())
×
568
    {
569
        if (fixture->universe() != idx)
×
570
            continue;
×
571

572
        fixture->setChannelValues(ua);
×
573
    }
×
574
}
×
575

576
/*****************************************************************************
577
 * Main application Mode
578
 *****************************************************************************/
579

580
void App::enableKioskMode()
×
581
{
582
    // Turn on operate mode
583
    m_doc->setKiosk(true);
×
584
    m_doc->setMode(Doc::Operate);
×
585

586
    // No need for these
587
    m_tab->removeTab(m_tab->indexOf(FixtureManager::instance()));
×
588
    m_tab->removeTab(m_tab->indexOf(FunctionManager::instance()));
×
589
    m_tab->removeTab(m_tab->indexOf(ShowManager::instance()));
×
590
    m_tab->removeTab(m_tab->indexOf(SimpleDesk::instance()));
×
591
    m_tab->removeTab(m_tab->indexOf(InputOutputManager::instance()));
×
592

593
    // Hide the tab bar to save some pixels
594
    m_tab->tabBar()->hide();
×
595

596
    // No need for the toolbar
597
    delete m_toolbar;
×
598
    m_toolbar = NULL;
×
599
}
×
600

601
void App::createKioskCloseButton(const QRect& rect)
×
602
{
603
    QPushButton* btn = new QPushButton(VirtualConsole::instance()->contents());
×
604
    btn->setIcon(QIcon(":/exit.png"));
×
605
    btn->setToolTip(tr("Exit"));
×
606
    btn->setGeometry(rect);
×
607
    connect(btn, SIGNAL(clicked()), this, SLOT(close()));
×
608
    btn->show();
×
609
}
×
610

611
void App::slotModeOperate()
×
612
{
613
    m_doc->setMode(Doc::Operate);
×
614
}
×
615

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

629
        if (result == QMessageBox::No)
×
630
            return;
×
631
        else
632
            m_doc->masterTimer()->stopAllFunctions();
×
633
    }
634

635
    m_liveEditVirtualConsoleAction->setChecked(false);
×
636
    m_doc->setMode(Doc::Design);
×
637
}
638

639
void App::slotModeToggle()
×
640
{
641
    if (m_doc->mode() == Doc::Design)
×
642
        slotModeOperate();
×
643
    else
644
        slotModeDesign();
×
645
}
×
646

647
void App::slotModeChanged(Doc::Mode mode)
×
648
{
649
    if (mode == Doc::Operate)
×
650
    {
651
        /* Disable editing features */
652
        m_fileNewAction->setEnabled(false);
×
653
        m_fileOpenAction->setEnabled(false);
×
654
        m_liveEditAction->setEnabled(true);
×
655
        m_liveEditVirtualConsoleAction->setEnabled(true);
×
656

657
        m_modeToggleAction->setIcon(QIcon(":/design.png"));
×
658
        m_modeToggleAction->setText(tr("Design"));
×
659
        m_modeToggleAction->setToolTip(tr("Switch to design mode"));
×
660
    }
661
    else if (mode == Doc::Design)
×
662
    {
663
        /* Enable editing features */
664
        m_fileNewAction->setEnabled(true);
×
665
        m_fileOpenAction->setEnabled(true);
×
666
        m_liveEditAction->setEnabled(false);
×
667
        m_liveEditVirtualConsoleAction->setEnabled(false);
×
668

669
        m_modeToggleAction->setIcon(QIcon(":/operate.png"));
×
670
        m_modeToggleAction->setText(tr("Operate"));
×
671
        m_modeToggleAction->setToolTip(tr("Switch to operate mode"));
×
672
    }
673
}
×
674

675
/*****************************************************************************
676
 * Actions and toolbar
677
 *****************************************************************************/
678

679
void App::initActions()
×
680
{
681
    /* File actions */
682
    m_fileNewAction = new QAction(QIcon(":/filenew.png"), tr("&New"), this);
×
683
    m_fileNewAction->setShortcut(QKeySequence(tr("CTRL+N", "File|New")));
×
684
    connect(m_fileNewAction, SIGNAL(triggered(bool)), this, SLOT(slotFileNew()));
×
685

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

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

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

697
    /* Control actions */
698
    m_modeToggleAction = new QAction(QIcon(":/operate.png"), tr("&Operate"), this);
×
699
    m_modeToggleAction->setToolTip(tr("Switch to operate mode"));
×
700
    m_modeToggleAction->setShortcut(QKeySequence(tr("CTRL+F12", "Control|Toggle operate/design mode")));
×
701
    connect(m_modeToggleAction, SIGNAL(triggered(bool)), this, SLOT(slotModeToggle()));
×
702

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

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

710
    m_controlBlackoutAction = new QAction(QIcon(":/blackout.png"), tr("Toggle &Blackout"), this);
×
711
    m_controlBlackoutAction->setCheckable(true);
×
712
    connect(m_controlBlackoutAction, SIGNAL(triggered(bool)), this, SLOT(slotControlBlackout()));
×
713
    m_controlBlackoutAction->setChecked(m_doc->inputOutputMap()->blackout());
×
714

715
    m_liveEditAction = new QAction(QIcon(":/liveedit.png"), tr("Live edit a function"), this);
×
716
    connect(m_liveEditAction, SIGNAL(triggered()), this, SLOT(slotFunctionLiveEdit()));
×
717
    m_liveEditAction->setEnabled(false);
×
718

719
    m_liveEditVirtualConsoleAction = new QAction(QIcon(":/liveedit_vc.png"), tr("Toggle Virtual Console Live edit"), this);
×
720
    connect(m_liveEditVirtualConsoleAction, SIGNAL(triggered()), this, SLOT(slotLiveEditVirtualConsole()));
×
721
    m_liveEditVirtualConsoleAction->setCheckable(true);
×
722
    m_liveEditVirtualConsoleAction->setEnabled(false);
×
723

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

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

732
    m_fadeAndStopMenu = new QMenu();
×
733
    QAction *fade1 = new QAction(tr("Fade 1 second and stop"), this);
×
734
    fade1->setData(QVariant(1000));
×
735
    connect(fade1, SIGNAL(triggered()), this, SLOT(slotFadeAndStopAll()));
×
736
    m_fadeAndStopMenu->addAction(fade1);
×
737

738
    QAction *fade5 = new QAction(tr("Fade 5 seconds and stop"), this);
×
739
    fade5->setData(QVariant(5000));
×
740
    connect(fade5, SIGNAL(triggered()), this, SLOT(slotFadeAndStopAll()));
×
741
    m_fadeAndStopMenu->addAction(fade5);
×
742

743
    QAction *fade10 = new QAction(tr("Fade 10 second and stop"), this);
×
744
    fade10->setData(QVariant(10000));
×
745
    connect(fade10, SIGNAL(triggered()), this, SLOT(slotFadeAndStopAll()));
×
746
    m_fadeAndStopMenu->addAction(fade10);
×
747

748
    QAction *fade30 = new QAction(tr("Fade 30 second and stop"), this);
×
749
    fade30->setData(QVariant(30000));
×
750
    connect(fade30, SIGNAL(triggered()), this, SLOT(slotFadeAndStopAll()));
×
751
    m_fadeAndStopMenu->addAction(fade30);
×
752

753
    m_controlPanicAction->setMenu(m_fadeAndStopMenu);
×
754

755
    m_controlFullScreenAction = new QAction(QIcon(":/fullscreen.png"), tr("Toggle Full Screen"), this);
×
756
    m_controlFullScreenAction->setCheckable(true);
×
757
    m_controlFullScreenAction->setShortcut(QKeySequence(tr("CTRL+F11", "Control|Toggle Full Screen")));
×
758
    connect(m_controlFullScreenAction, SIGNAL(triggered(bool)), this, SLOT(slotControlFullScreen()));
×
759

760
    /* Help actions */
761
    m_helpIndexAction = new QAction(QIcon(":/help.png"), tr("&Index"), this);
×
762
    m_helpIndexAction->setShortcut(QKeySequence(tr("SHIFT+F1", "Help|Index")));
×
763
    connect(m_helpIndexAction, SIGNAL(triggered(bool)), this, SLOT(slotHelpIndex()));
×
764

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

768
    if (QLCFile::hasWindowManager() == false)
×
769
    {
770
        m_quitAction = new QAction(QIcon(":/exit.png"), tr("Quit QLC+"), this);
×
771
        m_quitAction->setShortcut(QKeySequence("CTRL+ALT+Backspace"));
×
772
        connect(m_quitAction, SIGNAL(triggered(bool)), this, SLOT(close()));
×
773
    }
774
}
×
775

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

798
    /* Create an empty widget between help items to flush them to the right */
799
    QWidget* widget = new QWidget(this);
×
800
    widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
×
801
    m_toolbar->addWidget(widget);
×
802
    m_toolbar->addAction(m_dumpDmxAction);
×
803
    m_toolbar->addAction(m_liveEditAction);
×
804
    m_toolbar->addAction(m_liveEditVirtualConsoleAction);
×
805
    m_toolbar->addSeparator();
×
806
    m_toolbar->addAction(m_controlPanicAction);
×
807
    m_toolbar->addSeparator();
×
808
    m_toolbar->addAction(m_controlBlackoutAction);
×
809
    m_toolbar->addSeparator();
×
810
    m_toolbar->addAction(m_modeToggleAction);
×
811

812
    QToolButton* btn = qobject_cast<QToolButton*> (m_toolbar->widgetForAction(m_fileOpenAction));
×
813
    Q_ASSERT(btn != NULL);
×
814
    btn->setPopupMode(QToolButton::DelayedPopup);
×
815
    updateFileOpenMenu("");
×
816

817
    btn = qobject_cast<QToolButton*> (m_toolbar->widgetForAction(m_controlPanicAction));
×
818
    Q_ASSERT(btn != NULL);
×
819
    btn->setPopupMode(QToolButton::DelayedPopup);
×
820
}
×
821

822
/*****************************************************************************
823
 * File action slots
824
 *****************************************************************************/
825

826
bool App::handleFileError(QFile::FileError error)
×
827
{
828
    QString msg;
×
829

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

862
    QMessageBox::warning(this, tr("File error"), msg);
×
863

864
    return false;
×
865
}
×
866

867
bool App::saveModifiedDoc(const QString & title, const QString & message)
×
868
{
869
    // if it's not modified, there's nothing to save
870
    if (m_doc->isModified() == false)
×
871
        return true;
×
872

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

903
void App::updateFileOpenMenu(QString addRecent)
×
904
{
905
    QSettings settings;
×
906
    QStringList menuRecentList;
×
907

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

921
    foreach (QAction* a, m_fileOpenMenu->actions())
×
922
    {
923
        menuRecentList.append(a->text());
×
924
        m_fileOpenMenu->removeAction(a);
×
925
    }
×
926

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

950
    // Set the recent files menu to the file open action
951
    if (menuRecentList.isEmpty() == false)
×
952
        m_fileOpenAction->setMenu(m_fileOpenMenu);
×
953
}
×
954

955
bool App::slotFileNew()
×
956
{
957
    QString msg(tr("Do you wish to save the current workspace?\n" \
958
                   "Changes will be lost if you don't save them."));
×
959
    if (saveModifiedDoc(tr("New Workspace"), msg) == false)
×
960
    {
961
        return false;
×
962
    }
963

964
    clearDocument();
×
965
    return true;
×
966
}
×
967

968
QFile::FileError App::slotFileOpen()
×
969
{
970
    QString fn;
×
971

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

981
    /* Create a file open dialog */
982
    QFileDialog dialog(this);
×
983
    dialog.setWindowTitle(tr("Open Workspace"));
×
984
    dialog.setAcceptMode(QFileDialog::AcceptOpen);
×
985
    dialog.selectFile(fileName());
×
986
    if (m_workingDirectory.exists() == true)
×
987
        dialog.setDirectory(m_workingDirectory);
×
988

989
    /* Append file filters to the dialog */
990
    QStringList filters;
×
991
    filters << tr("Workspaces (*%1)").arg(KExtWorkspace);
×
992
#if defined(WIN32) || defined(Q_OS_WIN)
993
    filters << tr("All Files (*.*)");
994
#else
995
    filters << tr("All Files (*)");
×
996
#endif
997
    dialog.setNameFilters(filters);
×
998

999
    /* Append useful URLs to the dialog */
1000
    QList <QUrl> sidebar;
×
1001
    sidebar.append(QUrl::fromLocalFile(QDir::homePath()));
×
1002
    sidebar.append(QUrl::fromLocalFile(QDir::rootPath()));
×
1003
    dialog.setSidebarUrls(sidebar);
×
1004

1005
    /* Get file name */
1006
    if (dialog.exec() != QDialog::Accepted)
×
1007
        return QFile::NoError;
×
1008
    QSettings settings;
×
1009
    m_workingDirectory = dialog.directory();
×
1010
    settings.setValue(SETTINGS_WORKINGPATH, m_workingDirectory.absolutePath());
×
1011

1012
    fn = dialog.selectedFiles().first();
×
1013
    if (fn.isEmpty() == true)
×
1014
        return QFile::NoError;
×
1015

1016
    /* Clear existing document data */
1017
    clearDocument();
×
1018

1019
#ifdef DEBUG_SPEED
1020
    speedTime.restart();
1021
#endif
1022

1023
    /* Load the file */
1024
    QFile::FileError error = loadXML(fn);
×
1025
    if (handleFileError(error) == true)
×
1026
        m_doc->resetModified();
×
1027

1028
#ifdef DEBUG_SPEED
1029
    qDebug() << "[App] Project loaded in" << speedTime.elapsed() << "ms.";
1030
#endif
1031

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

1043
    updateFileOpenMenu(fn);
×
1044

1045
    return error;
×
1046
}
×
1047

1048
QFile::FileError App::slotFileSave()
×
1049
{
1050
    QFile::FileError error;
1051
    QString asfName = autoSaveFileName();
×
1052

1053
    /* Attempt to save with the existing name. Fall back to Save As. */
1054
    if (fileName().isEmpty() == true)
×
1055
        error = slotFileSaveAs();
×
1056
    else
1057
        error = saveXML(fileName());
×
1058

1059
    if (handleFileError(error))
×
1060
    {
1061
        QFile asFile(asfName);
×
1062
        if (asFile.exists())
×
1063
            asFile.remove();
×
1064
    }
×
1065
    return error;
×
1066
}
×
1067

1068
QFile::FileError App::slotFileSaveAs()
×
1069
{
1070
    QString fn;
×
1071
    QString asfName = autoSaveFileName();
×
1072

1073
    /* Create a file save dialog */
1074
    QFileDialog dialog(this);
×
1075
    dialog.setWindowTitle(tr("Save Workspace As"));
×
1076
    dialog.setAcceptMode(QFileDialog::AcceptSave);
×
1077
    dialog.selectFile(fileName());
×
1078

1079
    /* Append file filters to the dialog */
1080
    QStringList filters;
×
1081
    filters << tr("Workspaces (*%1)").arg(KExtWorkspace);
×
1082
#if defined(WIN32) || defined(Q_OS_WIN)
1083
    filters << tr("All Files (*.*)");
1084
#else
1085
    filters << tr("All Files (*)");
×
1086
#endif
1087
    dialog.setNameFilters(filters);
×
1088

1089
    /* Append useful URLs to the dialog */
1090
    QList <QUrl> sidebar;
×
1091
    sidebar.append(QUrl::fromLocalFile(QDir::homePath()));
×
1092
    sidebar.append(QUrl::fromLocalFile(QDir::rootPath()));
×
1093
    dialog.setSidebarUrls(sidebar);
×
1094

1095
    /* Get file name */
1096
    if (dialog.exec() != QDialog::Accepted)
×
1097
        return QFile::NoError;
×
1098

1099
    fn = dialog.selectedFiles().first();
×
1100
    if (fn.isEmpty() == true)
×
1101
        return QFile::NoError;
×
1102

1103
    /* Always use the workspace suffix */
1104
    if (fn.right(4) != KExtWorkspace)
×
1105
        fn += KExtWorkspace;
×
1106

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

1111
    /* Save the document and set workspace name */
1112
    QFile::FileError error = saveXML(fn);
×
1113

1114
    if (handleFileError(error))
×
1115
    {
1116
        /* remove autosave file if present */
1117
        QFile asFile(asfName);
×
1118
        if (asFile.exists())
×
1119
            asFile.remove();
×
1120
    }
×
1121

1122
    updateFileOpenMenu(fn);
×
1123
    return error;
×
1124
}
×
1125

1126
/*****************************************************************************
1127
 * Control action slots
1128
 *****************************************************************************/
1129

1130
void App::slotControlMonitor()
×
1131
{
1132
    Monitor::createAndShow(this, m_doc);
×
1133
}
×
1134

1135
void App::slotAddressTool()
×
1136
{
1137
    AddressTool at(this);
×
1138
    at.exec();
×
1139
}
×
1140

1141
void App::slotControlBlackout()
×
1142
{
1143
    m_doc->inputOutputMap()->setBlackout(!m_doc->inputOutputMap()->blackout());
×
1144
}
×
1145

1146
void App::slotBlackoutChanged(bool state)
×
1147
{
1148
    m_controlBlackoutAction->setChecked(state);
×
1149
}
×
1150

1151
void App::slotControlPanic()
×
1152
{
1153
    m_doc->masterTimer()->stopAllFunctions();
×
1154
}
×
1155

1156
void App::slotFadeAndStopAll()
×
1157
{
1158
    QAction *action = (QAction *)sender();
×
1159
    int timeout = action->data().toInt();
×
1160

1161
    m_doc->masterTimer()->fadeAndStopAll(timeout);
×
1162
}
×
1163

1164
void App::slotRunningFunctionsChanged()
×
1165
{
1166
    if (m_doc->masterTimer()->runningFunctions() > 0)
×
1167
        m_controlPanicAction->setEnabled(true);
×
1168
    else
1169
        m_controlPanicAction->setEnabled(false);
×
1170
}
×
1171

1172
void App::slotDumpDmxIntoFunction()
×
1173
{
1174
    DmxDumpFactory ddf(m_doc, m_dumpProperties, this);
×
1175
    if (ddf.exec() != QDialog::Accepted)
×
1176
        return;
×
1177
}
×
1178

1179
void App::slotFunctionLiveEdit()
×
1180
{
1181
    FunctionSelection fs(this, m_doc);
×
1182
    fs.setMultiSelection(false);
×
1183
    fs.setFilter(Function::SceneType | Function::ChaserType | Function::SequenceType | Function::EFXType | Function::RGBMatrixType);
×
1184
    fs.disableFilters(Function::ShowType | Function::ScriptType | Function::CollectionType | Function::AudioType);
×
1185

1186
    if (fs.exec() == QDialog::Accepted)
×
1187
    {
1188
        if (fs.selection().count() > 0)
×
1189
        {
1190
            FunctionLiveEditDialog fle(m_doc, fs.selection().first(), this);
×
1191
            fle.exec();
×
1192
        }
×
1193
    }
1194
}
×
1195

1196
void App::slotLiveEditVirtualConsole()
×
1197
{
1198
    VirtualConsole::instance()->toggleLiveEdit();
×
1199
}
×
1200

1201
void App::slotDetachContext(int index)
×
1202
{
1203
    /* Get the widget that has been double-clicked */
1204
    QWidget *context = m_tab->widget(index);
×
1205
    context->setProperty("tabIndex", index);
×
1206
    context->setProperty("tabIcon", QVariant::fromValue(m_tab->tabIcon(index)));
×
1207
    context->setProperty("tabLabel", m_tab->tabText(index));
×
1208

1209
    qDebug() << "Detaching context" << context;
×
1210

1211
    DetachedContext *detachedWindow = new DetachedContext(this);
×
1212
    detachedWindow->setCentralWidget(context);
×
1213
    detachedWindow->resize(800, 600);
×
1214
    detachedWindow->show();
×
1215
    context->show();
×
1216

1217
    connect(detachedWindow, SIGNAL(closing()),
×
1218
            this, SLOT(slotReattachContext()));
1219
}
×
1220

1221
void App::slotReattachContext()
×
1222
{
1223
    DetachedContext *window = qobject_cast<DetachedContext *>(sender());
×
1224

1225
    QWidget *context = window->centralWidget();
×
1226
    int tabIndex = context->property("tabIndex").toInt();
×
1227
    QIcon tabIcon = context->property("tabIcon").value<QIcon>();
×
1228
    QString tabLabel = context->property("tabLabel").toString();
×
1229

1230
    qDebug() << "Reattaching context" << tabIndex << tabLabel << context;
×
1231

1232
    context->setParent(m_tab);
×
1233
    m_tab->insertTab(tabIndex, context, tabIcon, tabLabel);
×
1234
}
×
1235

1236
void App::slotControlFullScreen()
×
1237
{
1238
    static int wstate = windowState();
×
1239

1240
    if (windowState() & Qt::WindowFullScreen)
×
1241
    {
1242
        if (wstate & Qt::WindowMaximized)
×
1243
            showMaximized();
×
1244
        else
1245
            showNormal();
×
1246
        wstate = windowState();
×
1247
    }
1248
    else
1249
    {
1250
        wstate = windowState();
×
1251
        showFullScreen();
×
1252

1253
        // In case slotControlFullScreen() is called programmatically (from main.cpp)
1254
        if (m_controlFullScreenAction->isChecked() == false)
×
1255
            m_controlFullScreenAction->setChecked(true);
×
1256
    }
1257
}
×
1258

1259
void App::slotControlFullScreen(bool usingGeometry)
×
1260
{
1261
    if (usingGeometry == true)
×
1262
    {
1263
        QScreen *screen = QGuiApplication::screens().first();
×
1264
        setGeometry(screen->geometry());
×
1265
    }
1266
    else
1267
    {
1268
        slotControlFullScreen();
×
1269
    }
1270
}
×
1271

1272
/*****************************************************************************
1273
 * Help action slots
1274
 *****************************************************************************/
1275

1276
void App::slotHelpIndex()
×
1277
{
1278
    QDesktopServices::openUrl(QUrl("https://docs.qlcplus.org/"));
×
1279
}
×
1280

1281
void App::slotHelpAbout()
×
1282
{
1283
    AboutBox ab(this);
×
1284
    ab.exec();
×
1285
}
×
1286

1287
void App::slotRecentFileClicked(QAction *recent)
×
1288
{
1289
    if (recent == NULL)
×
1290
        return;
×
1291

1292
    QString recentAbsPath = recent->text();
×
1293
    QFile testFile(recentAbsPath);
×
1294
    if (testFile.exists() == false)
×
1295
    {
1296
        QMessageBox::critical(this, tr("Error"),
×
1297
                              tr("File not found!\nThe selected file has been moved or deleted."),
×
1298
                              QMessageBox::Close);
1299
        return;
×
1300
    }
1301

1302
    /* Check that the user is aware of losing previous changes */
1303
    QString msg(tr("Do you wish to save the current workspace?\n" \
1304
                   "Changes will be lost if you don't save them."));
×
1305
    if (saveModifiedDoc(tr("Open Workspace"), msg) == false)
×
1306
    {
1307
        /* Second thoughts... Cancel loading. */
1308
        return;
×
1309
    }
1310

1311
    m_workingDirectory = QFileInfo(recentAbsPath).absoluteDir();
×
1312
    QSettings settings;
×
1313
    settings.setValue(SETTINGS_WORKINGPATH, m_workingDirectory.absolutePath());
×
1314

1315
    /* Clear existing document data */
1316
    clearDocument();
×
1317

1318
#ifdef DEBUG_SPEED
1319
    speedTime.restart();
1320
#endif
1321

1322
    /* Load the file */
1323
    QFile::FileError error = loadXML(recentAbsPath);
×
1324
    if (handleFileError(error) == true)
×
1325
        m_doc->resetModified();
×
1326

1327
#ifdef DEBUG_SPEED
1328
    qDebug() << "[App] Project loaded in" << speedTime.elapsed() << "ms.";
1329
#endif
1330

1331
    /* Update these in any case, since they are at least emptied now as
1332
       a result of calling clearDocument() a few lines ago. */
1333
    //if (FunctionManager::instance() != NULL)
1334
    //    FunctionManager::instance()->updateTree();
1335
    if (FixtureManager::instance() != NULL)
×
1336
        FixtureManager::instance()->updateView();
×
1337
    if (InputOutputManager::instance() != NULL)
×
1338
        InputOutputManager::instance()->updateList();
×
1339
    if (Monitor::instance() != NULL)
×
1340
        Monitor::instance()->updateView();
×
1341
}
×
1342

1343
/*****************************************************************************
1344
 * Load & Save
1345
 *****************************************************************************/
1346

1347
void App::setFileName(const QString& fileName)
×
1348
{
1349
    m_fileName = fileName;
×
1350
}
×
1351

1352
QString App::fileName() const
×
1353
{
1354
    return m_fileName;
×
1355
}
1356

1357
QString App::autoSaveFileName() const
×
1358
{
1359
    QString fName = m_fileName;
×
1360

1361
    if (fName.isEmpty())
×
1362
        fName = "NewProject.autosave.qxw";
×
1363
    else
1364
    {
1365
        fName.remove(".qxw");
×
1366
        fName.append(".autosave.qxw");
×
1367
    }
1368

1369
    return fName;
×
1370
}
×
1371

1372
QFile::FileError App::loadXML(const QString& fileName)
×
1373
{
1374
    QFile::FileError retval = QFile::NoError;
×
1375

1376
    if (fileName.isEmpty() == true)
×
1377
        return QFile::OpenError;
×
1378

1379
    QXmlStreamReader *doc = QLCFile::getXMLReader(fileName);
×
1380
    if (doc == NULL || doc->device() == NULL || doc->hasError())
×
1381
    {
1382
        qWarning() << Q_FUNC_INFO << "Unable to read from" << fileName;
×
1383
        return QFile::ReadError;
×
1384
    }
1385

1386
    while (!doc->atEnd())
×
1387
    {
1388
        if (doc->readNext() == QXmlStreamReader::DTD)
×
1389
            break;
×
1390
    }
1391
    if (doc->hasError())
×
1392
    {
1393
        QLCFile::releaseXMLReader(doc);
×
1394
        return QFile::ResourceError;
×
1395
    }
1396

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

1401
    if (doc->dtdName() == KXMLQLCWorkspace)
×
1402
    {
1403
        if (loadXML(*doc) == false)
×
1404
        {
1405
            retval = QFile::ReadError;
×
1406
        }
1407
        else
1408
        {
1409
            setFileName(fileName);
×
1410
            m_doc->resetModified();
×
1411
            retval = QFile::NoError;
×
1412
        }
1413
    }
1414
    else
1415
    {
1416
        retval = QFile::ReadError;
×
1417
        qWarning() << Q_FUNC_INFO << fileName
×
1418
                   << "is not a workspace file";
×
1419
    }
1420

1421
    QLCFile::releaseXMLReader(doc);
×
1422

1423
    return retval;
×
1424
}
1425

1426
bool App::loadXML(QXmlStreamReader& doc, bool goToConsole, bool fromMemory)
×
1427
{
1428
    if (doc.readNextStartElement() == false)
×
1429
        return false;
×
1430

1431
    if (doc.name() != KXMLQLCWorkspace)
×
1432
    {
1433
        qWarning() << Q_FUNC_INFO << "Workspace node not found";
×
1434
        return false;
×
1435
    }
1436

1437
    QString activeWindowName = doc.attributes().value(KXMLQLCWorkspaceWindow).toString();
×
1438

1439
    while (doc.readNextStartElement())
×
1440
    {
1441
        if (doc.name() == KXMLQLCEngine)
×
1442
        {
1443
            m_doc->loadXML(doc);
×
1444
        }
1445
        else if (doc.name() == KXMLQLCVirtualConsole)
×
1446
        {
1447
            VirtualConsole::instance()->loadXML(doc);
×
1448
        }
1449
        else if (doc.name() == KXMLQLCSimpleDesk)
×
1450
        {
1451
            SimpleDesk::instance()->loadXML(doc);
×
1452
        }
1453
        else if (doc.name() == KXMLFixture)
×
1454
        {
1455
            /* Legacy support code, nowadays in Doc */
1456
            Fixture::loader(doc, m_doc);
×
1457
        }
1458
        else if (doc.name() == KXMLQLCFunction)
×
1459
        {
1460
            /* Legacy support code, nowadays in Doc */
1461
            Function::loader(doc, m_doc);
×
1462
        }
1463
        else if (doc.name() == KXMLQLCCreator)
×
1464
        {
1465
            /* Ignore creator information */
1466
            doc.skipCurrentElement();
×
1467
        }
1468
        else
1469
        {
1470
            qWarning() << Q_FUNC_INFO << "Unknown Workspace tag:" << doc.name();
×
1471
            doc.skipCurrentElement();
×
1472
        }
1473
    }
1474

1475
    if (goToConsole == true)
×
1476
        // Force the active window to be Virtual Console
1477
        setActiveWindow(VirtualConsole::staticMetaObject.className());
×
1478
    else
1479
        // Set the active window to what was saved in the workspace file
1480
        setActiveWindow(activeWindowName);
×
1481

1482
    // Perform post-load operations
1483
    VirtualConsole::instance()->postLoad();
×
1484

1485
    if (m_doc->errorLog().isEmpty() == false &&
×
1486
        fromMemory == false)
×
1487
    {
1488
        QMessageBox msg(QMessageBox::Warning, tr("Warning"),
×
1489
                        tr("Some errors occurred while loading the project:") + "<br><br>" + m_doc->errorLog(),
×
1490
                        QMessageBox::Ok);
×
1491
        msg.setTextFormat(Qt::RichText);
×
1492
        QSpacerItem* horizontalSpacer = new QSpacerItem(800, 0, QSizePolicy::Minimum, QSizePolicy::Expanding);
×
1493
        QGridLayout* layout = (QGridLayout*)msg.layout();
×
1494
        layout->addItem(horizontalSpacer, layout->rowCount(), 0, 1, layout->columnCount());
×
1495
        msg.exec();
×
1496
    }
×
1497

1498
    m_doc->inputOutputMap()->startUniverses();
×
1499

1500
    return true;
×
1501
}
×
1502

1503
QFile::FileError App::saveXML(const QString& fileName, bool autosave)
×
1504
{
1505
    QString tempFileName(fileName);
×
1506
    tempFileName += ".temp";
×
1507
    QFile file(tempFileName);
×
1508
    if (file.open(QIODevice::WriteOnly) == false)
×
1509
        return file.error();
×
1510

1511
    QXmlStreamWriter doc(&file);
×
1512
    doc.setAutoFormatting(true);
×
1513
    doc.setAutoFormattingIndent(1);
×
1514
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1515
    doc.setCodec("UTF-8");
1516
#endif
1517

1518
    doc.writeStartDocument();
×
1519
    doc.writeDTD(QString("<!DOCTYPE %1>").arg(KXMLQLCWorkspace));
×
1520

1521
    doc.writeStartElement(KXMLQLCWorkspace);
×
1522
    doc.writeAttribute("xmlns", QString("%1%2").arg(KXMLQLCplusNamespace).arg(KXMLQLCWorkspace));
×
1523
    /* Currently active window */
1524
    QWidget* widget = m_tab->currentWidget();
×
1525
    if (widget != NULL)
×
1526
        doc.writeAttribute(KXMLQLCWorkspaceWindow, QString(widget->metaObject()->className()));
×
1527

1528
    doc.writeStartElement(KXMLQLCCreator);
×
1529
    doc.writeTextElement(KXMLQLCCreatorName, APPNAME);
×
1530
    doc.writeTextElement(KXMLQLCCreatorVersion, APPVERSION);
×
1531
    doc.writeTextElement(KXMLQLCCreatorAuthor, QLCFile::currentUserName());
×
1532
    doc.writeEndElement(); // close KXMLQLCCreator
×
1533

1534
    /* Write engine components to the XML document */
1535
    m_doc->saveXML(&doc);
×
1536

1537
    /* Write virtual console to the XML document */
1538
    VirtualConsole::instance()->saveXML(&doc);
×
1539

1540
    /* Write Simple Desk to the XML document */
1541
    SimpleDesk::instance()->saveXML(&doc);
×
1542

1543
    doc.writeEndElement(); // close KXMLQLCWorkspace
×
1544

1545
    /* End the document and close all the open elements */
1546
    doc.writeEndDocument();
×
1547
    file.close();
×
1548
#ifdef Q_OS_UNIX
1549
    sync();
×
1550
#endif
1551

1552
    // Save to actual requested file name
1553
    QFile currFile(fileName);
×
1554
    if (currFile.exists() && !currFile.remove())
×
1555
    {
1556
        qWarning() << "Could not erase" << fileName;
×
1557
        return currFile.error();
×
1558
    }
1559
    if (!file.rename(fileName))
×
1560
    {
1561
        qWarning() << "Could not rename" << tempFileName << "to" << fileName;
×
1562
        return file.error();
×
1563
    }
1564

1565
    if (!autosave)
×
1566
    {
1567
        /* Set the file name for the current Doc instance and
1568
           set it also in an unmodified state. */
1569
        setFileName(fileName);
×
1570
        m_doc->resetModified();
×
1571
    }
1572

1573
    return QFile::NoError;
×
1574
}
×
1575

1576
void App::slotLoadDocFromMemory(QString xmlData)
×
1577
{
1578
    if (xmlData.isEmpty())
×
1579
        return;
×
1580

1581
    /* Clear existing document data */
1582
    clearDocument();
×
1583

1584
    QBuffer databuf;
×
1585
    databuf.setData(xmlData.simplified().toUtf8());
×
1586
    databuf.open(QIODevice::ReadOnly | QIODevice::Text);
×
1587

1588
    //qDebug() << "Buffer data:" << databuf.data();
1589
    QXmlStreamReader doc(&databuf);
×
1590

1591
    if (doc.hasError())
×
1592
    {
1593
        qWarning() << Q_FUNC_INFO << "Unable to read from XML in memory";
×
1594
        return;
×
1595
    }
1596

1597
    while (!doc.atEnd())
×
1598
    {
1599
        if (doc.readNext() == QXmlStreamReader::DTD)
×
1600
            break;
×
1601
    }
1602
    if (doc.hasError())
×
1603
    {
1604
        qDebug() << "XML has errors:" << doc.errorString();
×
1605
        return;
×
1606
    }
1607

1608
    if (doc.dtdName() == KXMLQLCWorkspace)
×
1609
        loadXML(doc, true, true);
×
1610
    else
1611
        qDebug() << "XML doesn't have a Workspace tag";
×
1612
}
×
1613

1614
void App::slotSaveAutostart(QString fileName)
×
1615
{
1616
    /* Set the workspace path before saving the new XML. In this way local files
1617
       can be loaded even if the workspace file will be moved */
1618
    m_doc->setWorkspacePath(QFileInfo(fileName).absolutePath());
×
1619

1620
    /* Save the document and set workspace name */
1621
    QFile::FileError error = saveXML(fileName);
×
1622
    handleFileError(error);
×
1623
}
×
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