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

mcallegari / qlcplus / 7252848206

18 Dec 2023 07:26PM UTC coverage: 32.067% (+0.001%) from 32.066%
7252848206

push

github

mcallegari
Code style review #1427

199 of 628 new or added lines in 101 files covered. (31.69%)

8 existing lines in 2 files now uncovered.

15169 of 47304 relevant lines covered (32.07%)

23733.74 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 "docbrowser.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
//#define DEBUG_SPEED
66

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

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

77
#define MAX_RECENT_FILES    10
78

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

83
/*****************************************************************************
84
 * Initialization
85
 *****************************************************************************/
86

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

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

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

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

116
    , m_toolbar(NULL)
117

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

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

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

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

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

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

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

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

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

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

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

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

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

166
    m_doc = NULL;
×
167
}
×
168

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

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

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

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

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

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

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

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

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

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

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

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

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

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

268
    m_dumpProperties = new DmxDumpFactoryProperties(KUniverseCount);
×
269

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

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

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

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

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

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

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

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

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

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

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

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

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

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

376
        e->accept();
×
377
    }
378
}
379

380
/*****************************************************************************
381
 * Progress dialog
382
 *****************************************************************************/
383

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

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

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

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

414
/*****************************************************************************
415
 * Doc
416
 *****************************************************************************/
417

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

691
    m_controlPanicAction->setMenu(m_fadeAndStopMenu);
×
692

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

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

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

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

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

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

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

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

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

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

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

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

802
    return false;
×
803
}
804

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

980
    updateFileOpenMenu(fn);
×
981

982
    return error;
×
983
}
984

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1132
    qDebug() << "Detaching context" << context;
×
1133

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

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

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

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

1153
    qDebug() << "Reattaching context" << tabIndex << tabLabel << context;
×
1154

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

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

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

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

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

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

1199
void App::slotHelpIndex()
×
1200
{
1201
    DocBrowser::createAndShow(this);
×
1202
}
×
1203

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1329
    QLCFile::releaseXMLReader(doc);
×
1330

1331
    return retval;
×
1332
}
1333

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

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

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

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

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

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

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

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

1408
    return true;
×
1409
}
1410

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

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

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

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

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

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

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

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

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

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

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

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

1475
    return QFile::NoError;
×
1476
}
1477

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

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

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

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

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

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

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

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

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