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

mcallegari / qlcplus / 13633248611

03 Mar 2025 02:31PM UTC coverage: 31.871% (+0.4%) from 31.5%
13633248611

push

github

web-flow
actions: add chrpath to profile

14689 of 46089 relevant lines covered (31.87%)

26426.11 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 <QtCore>
24
#include <QtWidgets>
25

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

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

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

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

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

64
//#define DEBUG_SPEED
65

66
#ifdef DEBUG_SPEED
67
 #include <QTime>
68
 QTime speedTime;
69
#endif
70

71
#define SETTINGS_GEOMETRY "workspace/geometry"
72
#define SETTINGS_WORKINGPATH "workspace/workingpath"
73
#define SETTINGS_RECENTFILE "workspace/recent"
74
#define KXMLQLCWorkspaceWindow "CurrentWindow"
75

76
#define MAX_RECENT_FILES    10
77

78
#define KModeTextOperate QObject::tr("Operate")
79
#define KModeTextDesign QObject::tr("Design")
80
#define KUniverseCount 4
81

82
/*****************************************************************************
83
 * Initialization
84
 *****************************************************************************/
85

86
App::App()
×
87
    : QMainWindow()
88
    , m_tab(NULL)
×
89
    , m_overscan(false)
×
90
    , m_noGui(false)
×
91
    , m_progressDialog(NULL)
×
92
    , m_doc(NULL)
×
93

94
    , m_fileNewAction(NULL)
×
95
    , m_fileOpenAction(NULL)
×
96
    , m_fileSaveAction(NULL)
×
97
    , m_fileSaveAsAction(NULL)
×
98

99
    , m_modeToggleAction(NULL)
×
100
    , m_controlMonitorAction(NULL)
×
101
    , m_addressToolAction(NULL)
×
102
    , m_controlFullScreenAction(NULL)
×
103
    , m_controlBlackoutAction(NULL)
×
104
    , m_controlPanicAction(NULL)
×
105
    , m_dumpDmxAction(NULL)
×
106
    , m_liveEditAction(NULL)
×
107
    , m_liveEditVirtualConsoleAction(NULL)
×
108

109
    , m_helpIndexAction(NULL)
×
110
    , m_helpAboutAction(NULL)
×
111
    , m_quitAction(NULL)
×
112
    , m_fileOpenMenu(NULL)
×
113
    , m_fadeAndStopMenu(NULL)
×
114

115
    , m_toolbar(NULL)
×
116

117
    , m_dumpProperties(NULL)
×
118
    , m_videoProvider(NULL)
×
119
{
120
    QCoreApplication::setOrganizationName("qlcplus");
×
121
    QCoreApplication::setOrganizationDomain("sf.net");
×
122
    QCoreApplication::setApplicationName(APPNAME);
×
123
}
×
124

125
App::~App()
×
126
{
127
    QSettings settings;
×
128

129
    // Don't save kiosk-mode window geometry because that will screw things up
130
    if (m_doc->isKiosk() == false && QLCFile::hasWindowManager())
×
131
        settings.setValue(SETTINGS_GEOMETRY, saveGeometry());
×
132
    else
133
        settings.setValue(SETTINGS_GEOMETRY, QVariant());
×
134

135
    if (Monitor::instance() != NULL)
×
136
        delete Monitor::instance();
×
137

138
    if (FixtureManager::instance() != NULL)
×
139
        delete FixtureManager::instance();
×
140

141
    if (FunctionManager::instance() != NULL)
×
142
        delete FunctionManager::instance();
×
143

144
    if (ShowManager::instance() != NULL)
×
145
        delete ShowManager::instance();
×
146

147
    if (InputOutputManager::instance() != NULL)
×
148
        delete InputOutputManager::instance();
×
149

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

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

156
    if (m_dumpProperties != NULL)
×
157
        delete m_dumpProperties;
×
158

159
    if (m_videoProvider != NULL)
×
160
        delete m_videoProvider;
×
161

162
    if (m_doc != NULL)
×
163
        delete m_doc;
×
164

165
    m_doc = NULL;
×
166
}
×
167

168
void App::startup()
×
169
{
170
#if defined(__APPLE__) || defined(Q_OS_MAC)
171
    createProgressDialog();
172
#endif
173

174
    init();
×
175
    slotModeDesign();
×
176
    slotDocModified(false);
×
177

178
#if defined(__APPLE__) || defined(Q_OS_MAC)
179
    destroyProgressDialog();
180
#endif
181

182
    // Activate FixtureManager
183
    setActiveWindow(FixtureManager::staticMetaObject.className());
×
184
}
×
185

186
void App::enableOverscan()
×
187
{
188
    m_overscan = true;
×
189
}
×
190

191
void App::disableGUI()
×
192
{
193
    m_noGui = true;
×
194
}
×
195

196
void App::init()
×
197
{
198
    QSettings settings;
×
199

200
    setWindowIcon(QIcon(":/qlcplus.png"));
×
201

202
    m_tab = new QTabWidget(this);
×
203
    m_tab->setTabPosition(QTabWidget::South);
×
204
    setCentralWidget(m_tab);
×
205

206
#if defined(__APPLE__) || defined(Q_OS_MAC)
207
    m_tab->setElideMode(Qt::TextElideMode::ElideNone);
208
    qt_set_sequence_auto_mnemonic(true);
209
#endif
210

211
    QVariant var = settings.value(SETTINGS_GEOMETRY);
×
212
    if (var.isValid() == true)
×
213
    {
214
        this->restoreGeometry(var.toByteArray());
×
215
    }
216
    else
217
    {
218
        /* Application geometry and window state */
219
        QSize size = settings.value("/workspace/size").toSize();
×
220
        if (size.isValid() == true)
221
        {
222
            resize(size);
×
223
        }
224
        else
225
        {
226
            if (QLCFile::hasWindowManager() == false)
×
227
            {
228
                QScreen *screen = QGuiApplication::screens().first();
×
229
                QRect geometry = screen->geometry();
×
230
                if (m_noGui == true)
×
231
                {
232
                    setGeometry(geometry.width(), geometry.height(), 1, 1);
×
233
                }
234
                else
235
                {
236
                    int w = geometry.width();
237
                    int h = geometry.height();
238
                    if (m_overscan == true)
×
239
                    {
240
                        // if overscan is requested, introduce a 5% margin
241
                        w = (float)geometry.width() * 0.95;
×
242
                        h = (float)geometry.height() * 0.95;
×
243
                    }
244
                    setGeometry((geometry.width() - w) / 2, (geometry.height() - h) / 2, w, h);
×
245
                }
246
            }
247
            else
248
                resize(800, 600);
249
        }
250

251
        QVariant state = settings.value("/workspace/state", Qt::WindowNoState);
×
252
        if (state.isValid() == true)
×
253
            setWindowState(Qt::WindowState(state.toInt()));
×
254
    }
×
255

256
    QVariant dir = settings.value(SETTINGS_WORKINGPATH);
×
257
    if (dir.isValid() == true)
×
258
        m_workingDirectory = QDir(dir.toString());
×
259

260
    // The engine object
261
    initDoc();
×
262
    // Main view actions
263
    initActions();
×
264
    // Main tool bar
265
    initToolBar();
×
266

267
    m_dumpProperties = new DmxDumpFactoryProperties(KUniverseCount);
×
268

269
    // Create primary views.
270
    m_tab->setIconSize(QSize(24, 24));
×
271
    QWidget* w = new FixtureManager(m_tab, m_doc);
×
272
    m_tab->addTab(w, QIcon(":/fixture.png"), tr("Fixtures"));
×
273
    w = new FunctionManager(m_tab, m_doc);
×
274
    m_tab->addTab(w, QIcon(":/function.png"), tr("Functions"));
×
275
    w = new ShowManager(m_tab, m_doc);
×
276
    m_tab->addTab(w, QIcon(":/show.png"), tr("Shows"));
×
277
    w = new VirtualConsole(m_tab, m_doc);
×
278
    m_tab->addTab(w, QIcon(":/virtualconsole.png"), tr("Virtual Console"));
×
279
    w = new SimpleDesk(m_tab, m_doc);
×
280
    m_tab->addTab(w, QIcon(":/slidermatrix.png"), tr("Simple Desk"));
×
281
    w = new InputOutputManager(m_tab, m_doc);
×
282
    m_tab->addTab(w, QIcon(":/input_output.png"), tr("Inputs/Outputs"));
×
283

284
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
285
    /* Detach the tab's widget onto a new window on doubleClick */
286
    connect(m_tab, SIGNAL(tabBarDoubleClicked(int)), this, SLOT(slotDetachContext(int)));
×
287
#endif
288

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

292
    // Listen to DMX value changes and update each Fixture values array
293
    connect(m_doc->inputOutputMap(), SIGNAL(universeWritten(quint32, const QByteArray&)),
×
294
            this, SLOT(slotUniverseWritten(quint32, const QByteArray&)));
295

296
    // Enable/Disable panic button
297
    connect(m_doc->masterTimer(), SIGNAL(functionListChanged()), this, SLOT(slotRunningFunctionsChanged()));
×
298
    slotRunningFunctionsChanged();
×
299

300
    // Start up in non-modified state
301
    m_doc->resetModified();
×
302

303
#if defined(WIN32) || defined(Q_OS_WIN)
304
    HotPlugMonitor::setWinId(winId());
305
#endif
306

307
    this->setStyleSheet(AppUtil::getStyleSheet("MAIN"));
×
308

309
    m_videoProvider = new VideoProvider(m_doc, this);
×
310
}
×
311

312
void App::setActiveWindow(const QString& name)
×
313
{
314
    if (name.isEmpty() == true)
×
315
        return;
316

317
    for (int i = 0; i < m_tab->count(); i++)
×
318
    {
319
        QWidget* widget = m_tab->widget(i);
×
320
        if (widget != NULL && widget->metaObject()->className() == name)
×
321
        {
322
            m_tab->setCurrentIndex(i);
×
323
            break;
×
324
        }
325
    }
326
}
327

328
#if defined(WIN32) || defined(Q_OS_WIN)
329
bool App::nativeEvent(const QByteArray &eventType, void *message, long *result)
330
{
331
    Q_UNUSED(eventType)
332
    //qDebug() << Q_FUNC_INFO << eventType;
333
    return HotPlugMonitor::parseWinEvent(message, result);
334
}
335
#endif
336

337
void App::closeEvent(QCloseEvent* e)
×
338
{
339
    if (m_doc->mode() == Doc::Operate && m_doc->isKiosk() == false)
×
340
    {
341
        QMessageBox::warning(this,
×
342
                             tr("Cannot exit in Operate mode"),
×
343
                             tr("You must switch back to Design mode " \
×
344
                                "to close the application."));
345
        e->ignore();
346
        return;
×
347
    }
348

349
    if (m_doc->isKiosk() == false)
×
350
    {
351
        if (saveModifiedDoc(tr("Close"), tr("Do you wish to save the current workspace " \
×
352
                                            "before closing the application?")) == true)
353
        {
354
            e->accept();
355
        }
356
        else
357
        {
358
            e->ignore();
359
        }
360
    }
361
    else
362
    {
363
        if (m_doc->isKiosk() == true)
×
364
        {
365
            int result = QMessageBox::warning(this, tr("Close the application?"),
×
366
                                              tr("Do you wish to close the application?"),
×
367
                                              QMessageBox::Yes, QMessageBox::No);
368
            if (result == QMessageBox::No)
×
369
            {
370
                e->ignore();
371
                return;
×
372
            }
373
        }
374

375
        e->accept();
376
    }
377
}
378

379
/*****************************************************************************
380
 * Progress dialog
381
 *****************************************************************************/
382

383
void App::createProgressDialog()
×
384
{
385
    m_progressDialog = new QProgressDialog;
×
386
    m_progressDialog->setCancelButton(NULL);
×
387
    m_progressDialog->show();
×
388
    m_progressDialog->raise();
×
389
    m_progressDialog->setRange(0, 10);
×
390
    slotSetProgressText(QString());
×
391
    QApplication::processEvents();
×
392
}
×
393

394
void App::destroyProgressDialog()
×
395
{
396
    delete m_progressDialog;
×
397
    m_progressDialog = NULL;
×
398
}
×
399

400
void App::slotSetProgressText(const QString& text)
×
401
{
402
    if (m_progressDialog == NULL)
×
403
        return;
404

405
    static int progress = 0;
406
    m_progressDialog->setValue(progress++);
×
407
    m_progressDialog->setLabelText(QString("<B>%1</B><BR/>%2")
×
408
                                   .arg(tr("Starting Q Light Controller Plus"))
×
409
                                   .arg(text));
×
410
    QApplication::processEvents();
×
411
}
412

413
/*****************************************************************************
414
 * Doc
415
 *****************************************************************************/
416

417
void App::clearDocument()
×
418
{
419
    m_doc->masterTimer()->stop();
×
420
    VirtualConsole::instance()->resetContents();
×
421
    ShowManager::instance()->clearContents();
×
422
    m_doc->clearContents();
×
423
    if (Monitor::instance() != NULL)
×
424
        Monitor::instance()->updateView();
×
425
    SimpleDesk::instance()->clearContents();
×
426
    m_doc->inputOutputMap()->resetUniverses();
×
427
    setFileName(QString());
×
428
    m_doc->resetModified();
×
429
    m_doc->inputOutputMap()->startUniverses();
×
430
    m_doc->masterTimer()->start();
×
431
}
×
432

433
Doc *App::doc()
×
434
{
435
    return m_doc;
×
436
}
437

438
void App::initDoc()
×
439
{
440
    Q_ASSERT(m_doc == NULL);
441
    m_doc = new Doc(this);
×
442

443
    connect(m_doc, SIGNAL(modified(bool)), this, SLOT(slotDocModified(bool)));
×
444
    connect(m_doc, SIGNAL(modeChanged(Doc::Mode)), this, SLOT(slotModeChanged(Doc::Mode)));
×
445
#ifdef DEBUG_SPEED
446
    speedTime.start();
447
#endif
448
    /* Load user fixtures first so that they override system fixtures */
449
    m_doc->fixtureDefCache()->load(QLCFixtureDefCache::userDefinitionDirectory());
×
450
    m_doc->fixtureDefCache()->loadMap(QLCFixtureDefCache::systemDefinitionDirectory());
×
451

452
    /* Load channel modifiers templates */
453
    m_doc->modifiersCache()->load(QLCModifiersCache::systemTemplateDirectory(), true);
×
454
    m_doc->modifiersCache()->load(QLCModifiersCache::userTemplateDirectory());
×
455

456
    /* Load RGB scripts */
457
    m_doc->rgbScriptsCache()->load(RGBScriptsCache::systemScriptsDirectory());
×
458
    m_doc->rgbScriptsCache()->load(RGBScriptsCache::userScriptsDirectory());
×
459

460
    /* Load plugins */
461
    connect(m_doc->ioPluginCache(), SIGNAL(pluginLoaded(const QString&)),
×
462
            this, SLOT(slotSetProgressText(const QString&)));
463
    m_doc->ioPluginCache()->load(IOPluginCache::systemPluginDirectory());
×
464

465
    /* Load audio decoder plugins
466
     * This doesn't use a AudioPluginCache::systemPluginDirectory() cause
467
     * otherwise the qlcconfig.h creation should have been moved into the
468
     * audio folder, which doesn't make much sense */
469
    m_doc->audioPluginCache()->load(QLCFile::systemDirectory(AUDIOPLUGINDIR, KExtPlugin));
×
470

471
    /* Restore outputmap settings */
472
    Q_ASSERT(m_doc->inputOutputMap() != NULL);
473

474
    /* Load input plugins & profiles */
475
    m_doc->inputOutputMap()->loadProfiles(InputOutputMap::userProfileDirectory());
×
476
    m_doc->inputOutputMap()->loadProfiles(InputOutputMap::systemProfileDirectory());
×
477
    m_doc->inputOutputMap()->loadDefaults();
×
478

479
#ifdef DEBUG_SPEED
480
    qDebug() << "[App] Doc initialization took" << speedTime.elapsed() << "ms";
481
#endif
482

483
    m_doc->inputOutputMap()->startUniverses();
×
484
    m_doc->masterTimer()->start();
×
485
}
×
486

487
void App::slotDocModified(bool state)
×
488
{
489
    QString caption(APPNAME);
×
490

491
    if (fileName().isEmpty() == false)
×
492
        caption += QString(" - ") + QDir::toNativeSeparators(fileName());
×
493
    else
494
        caption += tr(" - New Workspace");
×
495

496
    if (state == true)
×
497
        setWindowTitle(caption + QString(" *"));
×
498
    else
499
        setWindowTitle(caption);
×
500
}
×
501

502
void App::slotUniverseWritten(quint32 idx, const QByteArray &ua)
×
503
{
504
    foreach (Fixture *fixture, m_doc->fixtures())
×
505
    {
506
        if (fixture->universe() != idx)
×
507
            continue;
×
508

509
        fixture->setChannelValues(ua);
×
510
    }
511
}
×
512

513
/*****************************************************************************
514
 * Main application Mode
515
 *****************************************************************************/
516

517
void App::enableKioskMode()
×
518
{
519
    // Turn on operate mode
520
    m_doc->setKiosk(true);
×
521
    m_doc->setMode(Doc::Operate);
×
522

523
    // No need for these
524
    m_tab->removeTab(m_tab->indexOf(FixtureManager::instance()));
×
525
    m_tab->removeTab(m_tab->indexOf(FunctionManager::instance()));
×
526
    m_tab->removeTab(m_tab->indexOf(ShowManager::instance()));
×
527
    m_tab->removeTab(m_tab->indexOf(SimpleDesk::instance()));
×
528
    m_tab->removeTab(m_tab->indexOf(InputOutputManager::instance()));
×
529

530
    // Hide the tab bar to save some pixels
531
    m_tab->tabBar()->hide();
×
532

533
    // No need for the toolbar
534
    delete m_toolbar;
×
535
    m_toolbar = NULL;
×
536
}
×
537

538
void App::createKioskCloseButton(const QRect& rect)
×
539
{
540
    QPushButton* btn = new QPushButton(VirtualConsole::instance()->contents());
×
541
    btn->setIcon(QIcon(":/exit.png"));
×
542
    btn->setToolTip(tr("Exit"));
×
543
    btn->setGeometry(rect);
×
544
    connect(btn, SIGNAL(clicked()), this, SLOT(close()));
×
545
    btn->show();
×
546
}
×
547

548
void App::slotModeOperate()
×
549
{
550
    m_doc->setMode(Doc::Operate);
×
551
}
×
552

553
void App::slotModeDesign()
×
554
{
555
    if (m_doc->masterTimer()->runningFunctions() > 0)
×
556
    {
557
        int result = QMessageBox::warning(
×
558
                         this,
559
                         tr("Switch to Design Mode"),
×
560
                         tr("There are still running functions.\n"
×
561
                            "Really stop them and switch back to "
562
                            "Design mode?"),
563
                         QMessageBox::Yes,
564
                         QMessageBox::No);
565

566
        if (result == QMessageBox::No)
×
567
            return;
568
        else
569
            m_doc->masterTimer()->stopAllFunctions();
×
570
    }
571

572
    m_liveEditVirtualConsoleAction->setChecked(false);
×
573
    m_doc->setMode(Doc::Design);
×
574
}
575

576
void App::slotModeToggle()
×
577
{
578
    if (m_doc->mode() == Doc::Design)
×
579
        slotModeOperate();
×
580
    else
581
        slotModeDesign();
×
582
}
×
583

584
void App::slotModeChanged(Doc::Mode mode)
×
585
{
586
    if (mode == Doc::Operate)
×
587
    {
588
        /* Disable editing features */
589
        m_fileNewAction->setEnabled(false);
×
590
        m_fileOpenAction->setEnabled(false);
×
591
        m_liveEditAction->setEnabled(true);
×
592
        m_liveEditVirtualConsoleAction->setEnabled(true);
×
593

594
        m_modeToggleAction->setIcon(QIcon(":/design.png"));
×
595
        m_modeToggleAction->setText(tr("Design"));
×
596
        m_modeToggleAction->setToolTip(tr("Switch to design mode"));
×
597
    }
598
    else if (mode == Doc::Design)
×
599
    {
600
        /* Enable editing features */
601
        m_fileNewAction->setEnabled(true);
×
602
        m_fileOpenAction->setEnabled(true);
×
603
        m_liveEditAction->setEnabled(false);
×
604
        m_liveEditVirtualConsoleAction->setEnabled(false);
×
605

606
        m_modeToggleAction->setIcon(QIcon(":/operate.png"));
×
607
        m_modeToggleAction->setText(tr("Operate"));
×
608
        m_modeToggleAction->setToolTip(tr("Switch to operate mode"));
×
609
    }
610
}
×
611

612
/*****************************************************************************
613
 * Actions and toolbar
614
 *****************************************************************************/
615

616
void App::initActions()
×
617
{
618
    /* File actions */
619
    m_fileNewAction = new QAction(QIcon(":/filenew.png"), tr("&New"), this);
×
620
    m_fileNewAction->setShortcut(QKeySequence(tr("CTRL+N", "File|New")));
×
621
    connect(m_fileNewAction, SIGNAL(triggered(bool)), this, SLOT(slotFileNew()));
×
622

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

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

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

634
    /* Control actions */
635
    m_modeToggleAction = new QAction(QIcon(":/operate.png"), tr("&Operate"), this);
×
636
    m_modeToggleAction->setToolTip(tr("Switch to operate mode"));
×
637
    m_modeToggleAction->setShortcut(QKeySequence(tr("CTRL+F12", "Control|Toggle operate/design mode")));
×
638
    connect(m_modeToggleAction, SIGNAL(triggered(bool)), this, SLOT(slotModeToggle()));
×
639

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

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

647
    m_controlBlackoutAction = new QAction(QIcon(":/blackout.png"), tr("Toggle &Blackout"), this);
×
648
    m_controlBlackoutAction->setCheckable(true);
×
649
    connect(m_controlBlackoutAction, SIGNAL(triggered(bool)), this, SLOT(slotControlBlackout()));
×
650
    m_controlBlackoutAction->setChecked(m_doc->inputOutputMap()->blackout());
×
651

652
    m_liveEditAction = new QAction(QIcon(":/liveedit.png"), tr("Live edit a function"), this);
×
653
    connect(m_liveEditAction, SIGNAL(triggered()), this, SLOT(slotFunctionLiveEdit()));
×
654
    m_liveEditAction->setEnabled(false);
×
655

656
    m_liveEditVirtualConsoleAction = new QAction(QIcon(":/liveedit_vc.png"), tr("Toggle Virtual Console Live edit"), this);
×
657
    connect(m_liveEditVirtualConsoleAction, SIGNAL(triggered()), this, SLOT(slotLiveEditVirtualConsole()));
×
658
    m_liveEditVirtualConsoleAction->setCheckable(true);
×
659
    m_liveEditVirtualConsoleAction->setEnabled(false);
×
660

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

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

669
    m_fadeAndStopMenu = new QMenu();
×
670
    QAction *fade1 = new QAction(tr("Fade 1 second and stop"), this);
×
671
    fade1->setData(QVariant(1000));
×
672
    connect(fade1, SIGNAL(triggered()), this, SLOT(slotFadeAndStopAll()));
×
673
    m_fadeAndStopMenu->addAction(fade1);
×
674

675
    QAction *fade5 = new QAction(tr("Fade 5 seconds and stop"), this);
×
676
    fade5->setData(QVariant(5000));
×
677
    connect(fade5, SIGNAL(triggered()), this, SLOT(slotFadeAndStopAll()));
×
678
    m_fadeAndStopMenu->addAction(fade5);
×
679

680
    QAction *fade10 = new QAction(tr("Fade 10 second and stop"), this);
×
681
    fade10->setData(QVariant(10000));
×
682
    connect(fade10, SIGNAL(triggered()), this, SLOT(slotFadeAndStopAll()));
×
683
    m_fadeAndStopMenu->addAction(fade10);
×
684

685
    QAction *fade30 = new QAction(tr("Fade 30 second and stop"), this);
×
686
    fade30->setData(QVariant(30000));
×
687
    connect(fade30, SIGNAL(triggered()), this, SLOT(slotFadeAndStopAll()));
×
688
    m_fadeAndStopMenu->addAction(fade30);
×
689

690
    m_controlPanicAction->setMenu(m_fadeAndStopMenu);
×
691

692
    m_controlFullScreenAction = new QAction(QIcon(":/fullscreen.png"), tr("Toggle Full Screen"), this);
×
693
    m_controlFullScreenAction->setCheckable(true);
×
694
    m_controlFullScreenAction->setShortcut(QKeySequence(tr("CTRL+F11", "Control|Toggle Full Screen")));
×
695
    connect(m_controlFullScreenAction, SIGNAL(triggered(bool)), this, SLOT(slotControlFullScreen()));
×
696

697
    /* Help actions */
698
    m_helpIndexAction = new QAction(QIcon(":/help.png"), tr("&Index"), this);
×
699
    m_helpIndexAction->setShortcut(QKeySequence(tr("SHIFT+F1", "Help|Index")));
×
700
    connect(m_helpIndexAction, SIGNAL(triggered(bool)), this, SLOT(slotHelpIndex()));
×
701

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

705
    if (QLCFile::hasWindowManager() == false)
×
706
    {
707
        m_quitAction = new QAction(QIcon(":/exit.png"), tr("Quit QLC+"), this);
×
708
        m_quitAction->setShortcut(QKeySequence("CTRL+ALT+Backspace"));
×
709
        connect(m_quitAction, SIGNAL(triggered(bool)), this, SLOT(close()));
×
710
    }
711
}
×
712

713
void App::initToolBar()
×
714
{
715
    m_toolbar = new QToolBar(tr("Workspace"), this);
×
716
    m_toolbar->setFloatable(false);
×
717
    m_toolbar->setMovable(false);
×
718
    m_toolbar->setAllowedAreas(Qt::TopToolBarArea);
×
719
    m_toolbar->setContextMenuPolicy(Qt::CustomContextMenu);
×
720
    addToolBar(m_toolbar);
×
721
    m_toolbar->addAction(m_fileNewAction);
×
722
    m_toolbar->addAction(m_fileOpenAction);
×
723
    m_toolbar->addAction(m_fileSaveAction);
×
724
    m_toolbar->addAction(m_fileSaveAsAction);
×
725
    m_toolbar->addSeparator();
×
726
    m_toolbar->addAction(m_controlMonitorAction);
×
727
    m_toolbar->addAction(m_addressToolAction);
×
728
    m_toolbar->addSeparator();
×
729
    m_toolbar->addAction(m_controlFullScreenAction);
×
730
    m_toolbar->addAction(m_helpIndexAction);
×
731
    m_toolbar->addAction(m_helpAboutAction);
×
732
    if (QLCFile::hasWindowManager() == false)
×
733
        m_toolbar->addAction(m_quitAction);
×
734

735
    /* Create an empty widget between help items to flush them to the right */
736
    QWidget* widget = new QWidget(this);
×
737
    widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
×
738
    m_toolbar->addWidget(widget);
×
739
    m_toolbar->addAction(m_dumpDmxAction);
×
740
    m_toolbar->addAction(m_liveEditAction);
×
741
    m_toolbar->addAction(m_liveEditVirtualConsoleAction);
×
742
    m_toolbar->addSeparator();
×
743
    m_toolbar->addAction(m_controlPanicAction);
×
744
    m_toolbar->addSeparator();
×
745
    m_toolbar->addAction(m_controlBlackoutAction);
×
746
    m_toolbar->addSeparator();
×
747
    m_toolbar->addAction(m_modeToggleAction);
×
748

749
    QToolButton* btn = qobject_cast<QToolButton*> (m_toolbar->widgetForAction(m_fileOpenAction));
×
750
    Q_ASSERT(btn != NULL);
751
    btn->setPopupMode(QToolButton::DelayedPopup);
×
752
    updateFileOpenMenu("");
×
753

754
    btn = qobject_cast<QToolButton*> (m_toolbar->widgetForAction(m_controlPanicAction));
×
755
    Q_ASSERT(btn != NULL);
756
    btn->setPopupMode(QToolButton::DelayedPopup);
×
757
}
×
758

759
/*****************************************************************************
760
 * File action slots
761
 *****************************************************************************/
762

763
bool App::handleFileError(QFile::FileError error)
×
764
{
765
    QString msg;
766

767
    switch (error)
×
768
    {
769
        case QFile::NoError:
770
            return true;
771
        break;
772
        case QFile::ReadError:
×
773
            msg = tr("Unable to read from file");
×
774
        break;
×
775
        case QFile::WriteError:
×
776
            msg = tr("Unable to write to file");
×
777
        break;
×
778
        case QFile::FatalError:
×
779
            msg = tr("A fatal error occurred");
×
780
        break;
×
781
        case QFile::ResourceError:
×
782
            msg = tr("Unable to access resource");
×
783
        break;
×
784
        case QFile::OpenError:
×
785
            msg = tr("Unable to open file for reading or writing");
×
786
        break;
×
787
        case QFile::AbortError:
×
788
            msg = tr("Operation was aborted");
×
789
        break;
×
790
        case QFile::TimeOutError:
×
791
            msg = tr("Operation timed out");
×
792
        break;
×
793
        default:
×
794
        case QFile::UnspecifiedError:
795
            msg = tr("An unspecified error has occurred. Nice.");
×
796
        break;
×
797
    }
798

799
    QMessageBox::warning(this, tr("File error"), msg);
×
800

801
    return false;
×
802
}
×
803

804
bool App::saveModifiedDoc(const QString & title, const QString & message)
×
805
{
806
    // if it's not modified, there's nothing to save
807
    if (m_doc->isModified() == false)
×
808
        return true;
809

810
    int result = QMessageBox::warning(this, title,
×
811
                                          message,
812
                                          QMessageBox::Yes |
813
                                          QMessageBox::No |
814
                                          QMessageBox::Cancel);
815
    if (result == QMessageBox::Yes)
×
816
    {
817
        slotFileSave();
×
818
        // we check whether m_doc is not modified anymore, rather than
819
        // result of slotFileSave() since the latter returns NoError
820
        // in cases like when the user pressed cancel in the save dialog
821
        if (m_doc->isModified() == false)
×
822
        {
823
            return true;
824
        }
825
        else
826
        {
827
            return false;
×
828
        }
829
    }
830
    else if (result == QMessageBox::No)
×
831
    {
832
        return true;
833
    }
834
    else
835
    {
836
        return false;
×
837
    }
838
}
839

840
void App::updateFileOpenMenu(QString addRecent)
×
841
{
842
    QSettings settings;
×
843
    QStringList menuRecentList;
844

845
    if (m_fileOpenMenu == NULL)
×
846
    {
847
        m_fileOpenMenu = new QMenu(this);
×
848
        QString style = "QMenu { background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #B9D9E8, stop:1 #A4C0CE);"
849
                        "border: 1px solid black; font:bold; }"
850
                        "QMenu::item { background-color: transparent; padding: 5px 10px 5px 10px; border: 1px solid black; }"
851
                        "QMenu::item:selected { background-color: #2D8CFF; }";
×
852
        m_fileOpenMenu->setStyleSheet(style);
×
853
        connect(m_fileOpenMenu, SIGNAL(triggered(QAction*)),
×
854
                this, SLOT(slotRecentFileClicked(QAction*)));
855
    }
×
856

857
    foreach (QAction* a, m_fileOpenMenu->actions())
×
858
    {
859
        menuRecentList.append(a->text());
×
860
        m_fileOpenMenu->removeAction(a);
×
861
    }
862

863
    if (addRecent.isEmpty() == false)
×
864
    {
865
        menuRecentList.removeAll(addRecent); // in case the string is already present, remove it...
×
866
        menuRecentList.prepend(addRecent); // and add it to the top
×
867
        for (int i = 0; i < menuRecentList.count(); i++)
×
868
        {
869
            settings.setValue(QString("%1%2").arg(SETTINGS_RECENTFILE).arg(i), menuRecentList.at(i));
×
870
            m_fileOpenMenu->addAction(menuRecentList.at(i));
×
871
        }
872
    }
873
    else
874
    {
875
        for (int i = 0; i < MAX_RECENT_FILES; i++)
×
876
        {
877
            QVariant recent = settings.value(QString("%1%2").arg(SETTINGS_RECENTFILE).arg(i));
×
878
            if (recent.isValid() == true)
×
879
            {
880
                menuRecentList.append(recent.toString());
×
881
                m_fileOpenMenu->addAction(menuRecentList.at(i));
×
882
            }
883
        }
×
884
    }
885

886
    // Set the recent files menu to the file open action
887
    if (menuRecentList.isEmpty() == false)
×
888
        m_fileOpenAction->setMenu(m_fileOpenMenu);
×
889
}
×
890

891
bool App::slotFileNew()
×
892
{
893
    QString msg(tr("Do you wish to save the current workspace?\n" \
894
                   "Changes will be lost if you don't save them."));
895
    if (saveModifiedDoc(tr("New Workspace"), msg) == false)
×
896
    {
897
        return false;
898
    }
899

900
    clearDocument();
×
901
    return true;
902
}
×
903

904
QFile::FileError App::slotFileOpen()
×
905
{
906
    QString fn;
907

908
    /* Check that the user is aware of losing previous changes */
909
    QString msg(tr("Do you wish to save the current workspace?\n" \
910
                   "Changes will be lost if you don't save them."));
911
    if (saveModifiedDoc(tr("Open Workspace"), msg) == false)
×
912
    {
913
        /* Second thoughts... Cancel loading. */
914
        return QFile::NoError;
915
    }
916

917
    /* Create a file open dialog */
918
    QFileDialog dialog(this);
×
919
    dialog.setWindowTitle(tr("Open Workspace"));
×
920
    dialog.setAcceptMode(QFileDialog::AcceptOpen);
×
921
    dialog.selectFile(fileName());
×
922
    if (m_workingDirectory.exists() == true)
×
923
        dialog.setDirectory(m_workingDirectory);
×
924

925
    /* Append file filters to the dialog */
926
    QStringList filters;
927
    filters << tr("Workspaces (*%1)").arg(KExtWorkspace);
×
928
#if defined(WIN32) || defined(Q_OS_WIN)
929
    filters << tr("All Files (*.*)");
930
#else
931
    filters << tr("All Files (*)");
×
932
#endif
933
    dialog.setNameFilters(filters);
×
934

935
    /* Append useful URLs to the dialog */
936
    QList <QUrl> sidebar;
937
    sidebar.append(QUrl::fromLocalFile(QDir::homePath()));
×
938
    sidebar.append(QUrl::fromLocalFile(QDir::rootPath()));
×
939
    dialog.setSidebarUrls(sidebar);
×
940

941
    /* Get file name */
942
    if (dialog.exec() != QDialog::Accepted)
×
943
        return QFile::NoError;
944
    QSettings settings;
×
945
    m_workingDirectory = dialog.directory();
×
946
    settings.setValue(SETTINGS_WORKINGPATH, m_workingDirectory.absolutePath());
×
947

948
    fn = dialog.selectedFiles().first();
×
949
    if (fn.isEmpty() == true)
×
950
        return QFile::NoError;
951

952
    /* Clear existing document data */
953
    clearDocument();
×
954

955
#ifdef DEBUG_SPEED
956
    speedTime.restart();
957
#endif
958

959
    /* Load the file */
960
    QFile::FileError error = loadXML(fn);
×
961
    if (handleFileError(error) == true)
×
962
        m_doc->resetModified();
×
963

964
#ifdef DEBUG_SPEED
965
    qDebug() << "[App] Project loaded in" << speedTime.elapsed() << "ms.";
966
#endif
967

968
    /* Update these in any case, since they are at least emptied now as
969
       a result of calling clearDocument() a few lines ago. */
970
    //if (FunctionManager::instance() != NULL)
971
    //    FunctionManager::instance()->updateTree();
972
    if (FixtureManager::instance() != NULL)
×
973
        FixtureManager::instance()->updateView();
×
974
    if (InputOutputManager::instance() != NULL)
×
975
        InputOutputManager::instance()->updateList();
×
976
    if (Monitor::instance() != NULL)
×
977
        Monitor::instance()->updateView();
×
978

979
    updateFileOpenMenu(fn);
×
980

981
    return error;
×
982
}
×
983

984
QFile::FileError App::slotFileSave()
×
985
{
986
    QFile::FileError error;
987

988
    /* Attempt to save with the existing name. Fall back to Save As. */
989
    if (fileName().isEmpty() == true)
×
990
        error = slotFileSaveAs();
×
991
    else
992
        error = saveXML(fileName());
×
993

994
    handleFileError(error);
×
995
    return error;
×
996
}
997

998
QFile::FileError App::slotFileSaveAs()
×
999
{
1000
    QString fn;
1001

1002
    /* Create a file save dialog */
1003
    QFileDialog dialog(this);
×
1004
    dialog.setWindowTitle(tr("Save Workspace As"));
×
1005
    dialog.setAcceptMode(QFileDialog::AcceptSave);
×
1006
    dialog.selectFile(fileName());
×
1007

1008
    /* Append file filters to the dialog */
1009
    QStringList filters;
1010
    filters << tr("Workspaces (*%1)").arg(KExtWorkspace);
×
1011
#if defined(WIN32) || defined(Q_OS_WIN)
1012
    filters << tr("All Files (*.*)");
1013
#else
1014
    filters << tr("All Files (*)");
×
1015
#endif
1016
    dialog.setNameFilters(filters);
×
1017

1018
    /* Append useful URLs to the dialog */
1019
    QList <QUrl> sidebar;
1020
    sidebar.append(QUrl::fromLocalFile(QDir::homePath()));
×
1021
    sidebar.append(QUrl::fromLocalFile(QDir::rootPath()));
×
1022
    dialog.setSidebarUrls(sidebar);
×
1023

1024
    /* Get file name */
1025
    if (dialog.exec() != QDialog::Accepted)
×
1026
        return QFile::NoError;
1027

1028
    fn = dialog.selectedFiles().first();
×
1029
    if (fn.isEmpty() == true)
×
1030
        return QFile::NoError;
1031

1032
    /* Always use the workspace suffix */
1033
    if (fn.right(4) != KExtWorkspace)
×
1034
        fn += KExtWorkspace;
×
1035

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

1040
    /* Save the document and set workspace name */
1041
    QFile::FileError error = saveXML(fn);
×
1042
    handleFileError(error);
×
1043

1044
    updateFileOpenMenu(fn);
×
1045
    return error;
×
1046
}
×
1047

1048
/*****************************************************************************
1049
 * Control action slots
1050
 *****************************************************************************/
1051

1052
void App::slotControlMonitor()
×
1053
{
1054
    Monitor::createAndShow(this, m_doc);
×
1055
}
×
1056

1057
void App::slotAddressTool()
×
1058
{
1059
    AddressTool at(this);
×
1060
    at.exec();
×
1061
}
×
1062

1063
void App::slotControlBlackout()
×
1064
{
1065
    m_doc->inputOutputMap()->setBlackout(!m_doc->inputOutputMap()->blackout());
×
1066
}
×
1067

1068
void App::slotBlackoutChanged(bool state)
×
1069
{
1070
    m_controlBlackoutAction->setChecked(state);
×
1071
}
×
1072

1073
void App::slotControlPanic()
×
1074
{
1075
    m_doc->masterTimer()->stopAllFunctions();
×
1076
}
×
1077

1078
void App::slotFadeAndStopAll()
×
1079
{
1080
    QAction *action = (QAction *)sender();
×
1081
    int timeout = action->data().toInt();
×
1082

1083
    m_doc->masterTimer()->fadeAndStopAll(timeout);
×
1084
}
×
1085

1086
void App::slotRunningFunctionsChanged()
×
1087
{
1088
    if (m_doc->masterTimer()->runningFunctions() > 0)
×
1089
        m_controlPanicAction->setEnabled(true);
×
1090
    else
1091
        m_controlPanicAction->setEnabled(false);
×
1092
}
×
1093

1094
void App::slotDumpDmxIntoFunction()
×
1095
{
1096
    DmxDumpFactory ddf(m_doc, m_dumpProperties, this);
×
1097
    if (ddf.exec() != QDialog::Accepted)
×
1098
        return;
1099
}
×
1100

1101
void App::slotFunctionLiveEdit()
×
1102
{
1103
    FunctionSelection fs(this, m_doc);
×
1104
    fs.setMultiSelection(false);
×
1105
    fs.setFilter(Function::SceneType | Function::ChaserType | Function::SequenceType | Function::EFXType | Function::RGBMatrixType);
×
1106
    fs.disableFilters(Function::ShowType | Function::ScriptType | Function::CollectionType | Function::AudioType);
×
1107

1108
    if (fs.exec() == QDialog::Accepted)
×
1109
    {
1110
        if (fs.selection().count() > 0)
×
1111
        {
1112
            FunctionLiveEditDialog fle(m_doc, fs.selection().first(), this);
×
1113
            fle.exec();
×
1114
        }
×
1115
    }
1116
}
×
1117

1118
void App::slotLiveEditVirtualConsole()
×
1119
{
1120
    VirtualConsole::instance()->toggleLiveEdit();
×
1121
}
×
1122

1123
void App::slotDetachContext(int index)
×
1124
{
1125
    /* Get the widget that has been double-clicked */
1126
    QWidget *context = m_tab->widget(index);
×
1127
    context->setProperty("tabIndex", index);
×
1128
    context->setProperty("tabIcon", QVariant::fromValue(m_tab->tabIcon(index)));
×
1129
    context->setProperty("tabLabel", m_tab->tabText(index));
×
1130

1131
    qDebug() << "Detaching context" << context;
1132

1133
    DetachedContext *detachedWindow = new DetachedContext(this);
×
1134
    detachedWindow->setCentralWidget(context);
×
1135
    detachedWindow->resize(800, 600);
×
1136
    detachedWindow->show();
×
1137
    context->show();
×
1138

1139
    connect(detachedWindow, SIGNAL(closing()),
×
1140
            this, SLOT(slotReattachContext()));
1141
}
×
1142

1143
void App::slotReattachContext()
×
1144
{
1145
    DetachedContext *window = qobject_cast<DetachedContext *>(sender());
×
1146

1147
    QWidget *context = window->centralWidget();
×
1148
    int tabIndex = context->property("tabIndex").toInt();
×
1149
    QIcon tabIcon = context->property("tabIcon").value<QIcon>();
×
1150
    QString tabLabel = context->property("tabLabel").toString();
×
1151

1152
    qDebug() << "Reattaching context" << tabIndex << tabLabel << context;
1153

1154
    context->setParent(m_tab);
×
1155
    m_tab->insertTab(tabIndex, context, tabIcon, tabLabel);
×
1156
}
×
1157

1158
void App::slotControlFullScreen()
×
1159
{
1160
    static int wstate = windowState();
×
1161

1162
    if (windowState() & Qt::WindowFullScreen)
×
1163
    {
1164
        if (wstate & Qt::WindowMaximized)
×
1165
            showMaximized();
×
1166
        else
1167
            showNormal();
×
1168
        wstate = windowState();
×
1169
    }
1170
    else
1171
    {
1172
        wstate = windowState();
×
1173
        showFullScreen();
×
1174

1175
        // In case slotControlFullScreen() is called programmatically (from main.cpp)
1176
        if (m_controlFullScreenAction->isChecked() == false)
×
1177
            m_controlFullScreenAction->setChecked(true);
×
1178
    }
1179
}
×
1180

1181
void App::slotControlFullScreen(bool usingGeometry)
×
1182
{
1183
    if (usingGeometry == true)
×
1184
    {
1185
        QScreen *screen = QGuiApplication::screens().first();
×
1186
        setGeometry(screen->geometry());
×
1187
    }
1188
    else
1189
    {
1190
        slotControlFullScreen();
×
1191
    }
1192
}
×
1193

1194
/*****************************************************************************
1195
 * Help action slots
1196
 *****************************************************************************/
1197

1198
void App::slotHelpIndex()
×
1199
{
1200
    QDesktopServices::openUrl(QUrl("https://docs.qlcplus.org/"));
×
1201
}
×
1202

1203
void App::slotHelpAbout()
×
1204
{
1205
    AboutBox ab(this);
×
1206
    ab.exec();
×
1207
}
×
1208

1209
void App::slotRecentFileClicked(QAction *recent)
×
1210
{
1211
    if (recent == NULL)
×
1212
        return;
×
1213

1214
    QString recentAbsPath = recent->text();
×
1215
    QFile testFile(recentAbsPath);
×
1216
    if (testFile.exists() == false)
×
1217
    {
1218
        QMessageBox::critical(this, tr("Error"),
×
1219
                              tr("File not found!\nThe selected file has been moved or deleted."),
×
1220
                              QMessageBox::Close);
1221
        return;
×
1222
    }
1223

1224
    /* Check that the user is aware of losing previous changes */
1225
    QString msg(tr("Do you wish to save the current workspace?\n" \
1226
                   "Changes will be lost if you don't save them."));
1227
    if (saveModifiedDoc(tr("Open Workspace"), msg) == false)
×
1228
    {
1229
        /* Second thoughts... Cancel loading. */
1230
        return;
1231
    }
1232

1233
    m_workingDirectory = QFileInfo(recentAbsPath).absoluteDir();
×
1234
    QSettings settings;
×
1235
    settings.setValue(SETTINGS_WORKINGPATH, m_workingDirectory.absolutePath());
×
1236

1237
    /* Clear existing document data */
1238
    clearDocument();
×
1239

1240
#ifdef DEBUG_SPEED
1241
    speedTime.restart();
1242
#endif
1243

1244
    /* Load the file */
1245
    QFile::FileError error = loadXML(recentAbsPath);
×
1246
    if (handleFileError(error) == true)
×
1247
        m_doc->resetModified();
×
1248

1249
#ifdef DEBUG_SPEED
1250
    qDebug() << "[App] Project loaded in" << speedTime.elapsed() << "ms.";
1251
#endif
1252

1253
    /* Update these in any case, since they are at least emptied now as
1254
       a result of calling clearDocument() a few lines ago. */
1255
    //if (FunctionManager::instance() != NULL)
1256
    //    FunctionManager::instance()->updateTree();
1257
    if (FixtureManager::instance() != NULL)
×
1258
        FixtureManager::instance()->updateView();
×
1259
    if (InputOutputManager::instance() != NULL)
×
1260
        InputOutputManager::instance()->updateList();
×
1261
    if (Monitor::instance() != NULL)
×
1262
        Monitor::instance()->updateView();
×
1263
}
×
1264

1265
/*****************************************************************************
1266
 * Load & Save
1267
 *****************************************************************************/
1268

1269
void App::setFileName(const QString& fileName)
×
1270
{
1271
    m_fileName = fileName;
×
1272
}
×
1273

1274
QString App::fileName() const
×
1275
{
1276
    return m_fileName;
×
1277
}
1278

1279
QFile::FileError App::loadXML(const QString& fileName)
×
1280
{
1281
    QFile::FileError retval = QFile::NoError;
1282

1283
    if (fileName.isEmpty() == true)
×
1284
        return QFile::OpenError;
1285

1286
    QXmlStreamReader *doc = QLCFile::getXMLReader(fileName);
×
1287
    if (doc == NULL || doc->device() == NULL || doc->hasError())
×
1288
    {
1289
        qWarning() << Q_FUNC_INFO << "Unable to read from" << fileName;
×
1290
        return QFile::ReadError;
×
1291
    }
1292

1293
    while (!doc->atEnd())
×
1294
    {
1295
        if (doc->readNext() == QXmlStreamReader::DTD)
×
1296
            break;
1297
    }
1298
    if (doc->hasError())
×
1299
    {
1300
        QLCFile::releaseXMLReader(doc);
×
1301
        return QFile::ResourceError;
×
1302
    }
1303

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

1308
    if (doc->dtdName() == KXMLQLCWorkspace)
×
1309
    {
1310
        if (loadXML(*doc) == false)
×
1311
        {
1312
            retval = QFile::ReadError;
1313
        }
1314
        else
1315
        {
1316
            setFileName(fileName);
×
1317
            m_doc->resetModified();
×
1318
            retval = QFile::NoError;
1319
        }
1320
    }
1321
    else
1322
    {
1323
        retval = QFile::ReadError;
1324
        qWarning() << Q_FUNC_INFO << fileName
×
1325
                   << "is not a workspace file";
×
1326
    }
1327

1328
    QLCFile::releaseXMLReader(doc);
×
1329

1330
    return retval;
×
1331
}
1332

1333
bool App::loadXML(QXmlStreamReader& doc, bool goToConsole, bool fromMemory)
×
1334
{
1335
    if (doc.readNextStartElement() == false)
×
1336
        return false;
1337

1338
    if (doc.name() != KXMLQLCWorkspace)
×
1339
    {
1340
        qWarning() << Q_FUNC_INFO << "Workspace node not found";
×
1341
        return false;
×
1342
    }
1343

1344
    QString activeWindowName = doc.attributes().value(KXMLQLCWorkspaceWindow).toString();
×
1345

1346
    while (doc.readNextStartElement())
×
1347
    {
1348
        if (doc.name() == KXMLQLCEngine)
×
1349
        {
1350
            m_doc->loadXML(doc);
×
1351
        }
1352
        else if (doc.name() == KXMLQLCVirtualConsole)
×
1353
        {
1354
            VirtualConsole::instance()->loadXML(doc);
×
1355
        }
1356
        else if (doc.name() == KXMLQLCSimpleDesk)
×
1357
        {
1358
            SimpleDesk::instance()->loadXML(doc);
×
1359
        }
1360
        else if (doc.name() == KXMLFixture)
×
1361
        {
1362
            /* Legacy support code, nowadays in Doc */
1363
            Fixture::loader(doc, m_doc);
×
1364
        }
1365
        else if (doc.name() == KXMLQLCFunction)
×
1366
        {
1367
            /* Legacy support code, nowadays in Doc */
1368
            Function::loader(doc, m_doc);
×
1369
        }
1370
        else if (doc.name() == KXMLQLCCreator)
×
1371
        {
1372
            /* Ignore creator information */
1373
            doc.skipCurrentElement();
×
1374
        }
1375
        else
1376
        {
1377
            qWarning() << Q_FUNC_INFO << "Unknown Workspace tag:" << doc.name();
×
1378
            doc.skipCurrentElement();
×
1379
        }
1380
    }
1381

1382
    if (goToConsole == true)
×
1383
        // Force the active window to be Virtual Console
1384
        setActiveWindow(VirtualConsole::staticMetaObject.className());
×
1385
    else
1386
        // Set the active window to what was saved in the workspace file
1387
        setActiveWindow(activeWindowName);
×
1388

1389
    // Perform post-load operations
1390
    VirtualConsole::instance()->postLoad();
×
1391

1392
    if (m_doc->errorLog().isEmpty() == false &&
×
1393
        fromMemory == false)
1394
    {
1395
        QMessageBox msg(QMessageBox::Warning, tr("Warning"),
×
1396
                        tr("Some errors occurred while loading the project:") + "<br><br>" + m_doc->errorLog(),
×
1397
                        QMessageBox::Ok);
×
1398
        msg.setTextFormat(Qt::RichText);
×
1399
        QSpacerItem* horizontalSpacer = new QSpacerItem(800, 0, QSizePolicy::Minimum, QSizePolicy::Expanding);
×
1400
        QGridLayout* layout = (QGridLayout*)msg.layout();
×
1401
        layout->addItem(horizontalSpacer, layout->rowCount(), 0, 1, layout->columnCount());
×
1402
        msg.exec();
×
1403
    }
×
1404

1405
    m_doc->inputOutputMap()->startUniverses();
×
1406

1407
    return true;
1408
}
×
1409

1410
QFile::FileError App::saveXML(const QString& fileName)
×
1411
{
1412
    QString tempFileName(fileName);
1413
    tempFileName += ".temp";
×
1414
    QFile file(tempFileName);
×
1415
    if (file.open(QIODevice::WriteOnly) == false)
×
1416
        return file.error();
×
1417

1418
    QXmlStreamWriter doc(&file);
×
1419
    doc.setAutoFormatting(true);
×
1420
    doc.setAutoFormattingIndent(1);
×
1421
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1422
    doc.setCodec("UTF-8");
×
1423
#endif
1424

1425
    doc.writeStartDocument();
×
1426
    doc.writeDTD(QString("<!DOCTYPE %1>").arg(KXMLQLCWorkspace));
×
1427

1428
    doc.writeStartElement(KXMLQLCWorkspace);
×
1429
    doc.writeAttribute("xmlns", QString("%1%2").arg(KXMLQLCplusNamespace).arg(KXMLQLCWorkspace));
×
1430
    /* Currently active window */
1431
    QWidget* widget = m_tab->currentWidget();
×
1432
    if (widget != NULL)
×
1433
        doc.writeAttribute(KXMLQLCWorkspaceWindow, QString(widget->metaObject()->className()));
×
1434

1435
    doc.writeStartElement(KXMLQLCCreator);
×
1436
    doc.writeTextElement(KXMLQLCCreatorName, APPNAME);
×
1437
    doc.writeTextElement(KXMLQLCCreatorVersion, APPVERSION);
×
1438
    doc.writeTextElement(KXMLQLCCreatorAuthor, QLCFile::currentUserName());
×
1439
    doc.writeEndElement(); // close KXMLQLCCreator
×
1440

1441
    /* Write engine components to the XML document */
1442
    m_doc->saveXML(&doc);
×
1443

1444
    /* Write virtual console to the XML document */
1445
    VirtualConsole::instance()->saveXML(&doc);
×
1446

1447
    /* Write Simple Desk to the XML document */
1448
    SimpleDesk::instance()->saveXML(&doc);
×
1449

1450
    doc.writeEndElement(); // close KXMLQLCWorkspace
×
1451

1452
    /* End the document and close all the open elements */
1453
    doc.writeEndDocument();
×
1454
    file.close();
×
1455

1456
    // Save to actual requested file name
1457
    QFile currFile(fileName);
×
1458
    if (currFile.exists() && !currFile.remove())
×
1459
    {
1460
        qWarning() << "Could not erase" << fileName;
×
1461
        return currFile.error();
×
1462
    }
1463
    if (!file.rename(fileName))
×
1464
    {
1465
        qWarning() << "Could not rename" << tempFileName << "to" << fileName;
×
1466
        return file.error();
×
1467
    }
1468

1469
    /* Set the file name for the current Doc instance and
1470
       set it also in an unmodified state. */
1471
    setFileName(fileName);
×
1472
    m_doc->resetModified();
×
1473

1474
    return QFile::NoError;
1475
}
×
1476

1477
void App::slotLoadDocFromMemory(QString xmlData)
×
1478
{
1479
    if (xmlData.isEmpty())
×
1480
        return;
×
1481

1482
    /* Clear existing document data */
1483
    clearDocument();
×
1484

1485
    QBuffer databuf;
×
1486
    databuf.setData(xmlData.simplified().toUtf8());
×
1487
    databuf.open(QIODevice::ReadOnly | QIODevice::Text);
×
1488

1489
    //qDebug() << "Buffer data:" << databuf.data();
1490
    QXmlStreamReader doc(&databuf);
×
1491

1492
    if (doc.hasError())
×
1493
    {
1494
        qWarning() << Q_FUNC_INFO << "Unable to read from XML in memory";
×
1495
        return;
×
1496
    }
1497

1498
    while (!doc.atEnd())
×
1499
    {
1500
        if (doc.readNext() == QXmlStreamReader::DTD)
×
1501
            break;
1502
    }
1503
    if (doc.hasError())
×
1504
    {
1505
        qDebug() << "XML has errors:" << doc.errorString();
1506
        return;
1507
    }
1508

1509
    if (doc.dtdName() == KXMLQLCWorkspace)
×
1510
        loadXML(doc, true, true);
×
1511
    else
1512
        qDebug() << "XML doesn't have a Workspace tag";
1513
}
×
1514

1515
void App::slotSaveAutostart(QString fileName)
×
1516
{
1517
    /* Set the workspace path before saving the new XML. In this way local files
1518
       can be loaded even if the workspace file will be moved */
1519
    m_doc->setWorkspacePath(QFileInfo(fileName).absolutePath());
×
1520

1521
    /* Save the document and set workspace name */
1522
    QFile::FileError error = saveXML(fileName);
×
1523
    handleFileError(error);
×
1524
}
×
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

© 2025 Coveralls, Inc