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

mcallegari / qlcplus / 6657559791

26 Oct 2023 05:17PM UTC coverage: 28.072% (+0.002%) from 28.07%
6657559791

push

github

mcallegari
ui: add helper to load more custom styles from file

40 of 40 new or added lines in 4 files covered. (100.0%)

15386 of 54810 relevant lines covered (28.07%)

20267.26 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
    this->setStyleSheet(AppUtil::getStyleSheet("MAIN"));
×
305

306
    m_videoProvider = new VideoProvider(m_doc, this);
×
307
}
×
308

309
void App::setActiveWindow(const QString& name)
×
310
{
311
    if (name.isEmpty() == true)
×
312
        return;
×
313

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

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

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

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

372
        e->accept();
×
373
    }
374
}
375

376
/*****************************************************************************
377
 * Progress dialog
378
 *****************************************************************************/
379

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

391
void App::destroyProgressDialog()
×
392
{
393
    delete m_progressDialog;
×
394
    m_progressDialog = NULL;
×
395
}
×
396

397
void App::slotSetProgressText(const QString& text)
×
398
{
399
    if (m_progressDialog == NULL)
×
400
        return;
×
401

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

410
/*****************************************************************************
411
 * Doc
412
 *****************************************************************************/
413

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

430
Doc *App::doc()
×
431
{
432
    return m_doc;
×
433
}
434

435
void App::initDoc()
×
436
{
437
    Q_ASSERT(m_doc == NULL);
×
438
    m_doc = new Doc(this);
×
439

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

449
    /* Load channel modifiers templates */
450
    m_doc->modifiersCache()->load(QLCModifiersCache::systemTemplateDirectory(), true);
×
451
    m_doc->modifiersCache()->load(QLCModifiersCache::userTemplateDirectory());
×
452

453
    /* Load RGB scripts */
454
    m_doc->rgbScriptsCache()->load(RGBScriptsCache::systemScriptsDirectory());
×
455
    m_doc->rgbScriptsCache()->load(RGBScriptsCache::userScriptsDirectory());
×
456

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

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

468
    /* Restore outputmap settings */
469
    Q_ASSERT(m_doc->inputOutputMap() != NULL);
×
470

471
    /* Load input plugins & profiles */
472
    m_doc->inputOutputMap()->loadProfiles(InputOutputMap::userProfileDirectory());
×
473
    m_doc->inputOutputMap()->loadProfiles(InputOutputMap::systemProfileDirectory());
×
474
    m_doc->inputOutputMap()->loadDefaults();
×
475

476
#ifdef DEBUG_SPEED
477
    qDebug() << "[App] Doc initialization took" << speedTime.elapsed() << "ms";
478
#endif
479

480
    m_doc->inputOutputMap()->startUniverses();
×
481
    m_doc->masterTimer()->start();
×
482
}
×
483

484
void App::slotDocModified(bool state)
×
485
{
486
    QString caption(APPNAME);
×
487

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

493
    if (state == true)
×
494
        setWindowTitle(caption + QString(" *"));
×
495
    else
496
        setWindowTitle(caption);
×
497
}
×
498

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

506
        fixture->setChannelValues(ua);
×
507
    }
508
}
×
509

510
/*****************************************************************************
511
 * Main application Mode
512
 *****************************************************************************/
513

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

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

527
    // Hide the tab bar to save some pixels
528
    m_tab->tabBar()->hide();
×
529

530
    // No need for the toolbar
531
    delete m_toolbar;
×
532
    m_toolbar = NULL;
×
533
}
×
534

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

545
void App::slotModeOperate()
×
546
{
547
    m_doc->setMode(Doc::Operate);
×
548
}
×
549

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

563
        if (result == QMessageBox::No)
×
564
            return;
×
565
        else
566
            m_doc->masterTimer()->stopAllFunctions();
×
567
    }
568

569
    m_liveEditVirtualConsoleAction->setChecked(false);
×
570
    m_doc->setMode(Doc::Design);
×
571
}
572

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

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

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

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

609
/*****************************************************************************
610
 * Actions and toolbar
611
 *****************************************************************************/
612

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

687
    m_controlPanicAction->setMenu(m_fadeAndStopMenu);
×
688

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

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

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

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

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

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

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

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

756
/*****************************************************************************
757
 * File action slots
758
 *****************************************************************************/
759

760
bool App::handleFileError(QFile::FileError error)
×
761
{
762
    QString msg;
×
763

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

796
    QMessageBox::warning(this, tr("File error"), msg);
×
797

798
    return false;
×
799
}
800

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

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

837
void App::updateFileOpenMenu(QString addRecent)
×
838
{
839
    QSettings settings;
×
840
    QStringList menuRecentList;
×
841

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

854
    foreach (QAction* a, m_fileOpenMenu->actions())
×
855
    {
856
        menuRecentList.append(a->text());
×
857
        m_fileOpenMenu->removeAction(a);
×
858
    }
859

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

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

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

897
    clearDocument();
×
898
    return true;
×
899
}
900

901
QFile::FileError App::slotFileOpen()
×
902
{
903
    QString fn;
×
904

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

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

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

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

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

945
    fn = dialog.selectedFiles().first();
×
946
    if (fn.isEmpty() == true)
×
947
        return QFile::NoError;
×
948

949
    /* Clear existing document data */
950
    clearDocument();
×
951

952
#ifdef DEBUG_SPEED
953
    speedTime.restart();
954
#endif
955

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

961
#ifdef DEBUG_SPEED
962
    qDebug() << "[App] Project loaded in" << speedTime.elapsed() << "ms.";
963
#endif
964

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

976
    updateFileOpenMenu(fn);
×
977

978
    return error;
×
979
}
980

981
QFile::FileError App::slotFileSave()
×
982
{
983
    QFile::FileError error;
984

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

991
    handleFileError(error);
×
992
    return error;
×
993
}
994

995
QFile::FileError App::slotFileSaveAs()
×
996
{
997
    QString fn;
×
998

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

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

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

1021
    /* Get file name */
1022
    if (dialog.exec() != QDialog::Accepted)
×
1023
        return QFile::NoError;
×
1024

1025
    fn = dialog.selectedFiles().first();
×
1026
    if (fn.isEmpty() == true)
×
1027
        return QFile::NoError;
×
1028

1029
    /* Always use the workspace suffix */
1030
    if (fn.right(4) != KExtWorkspace)
×
1031
        fn += KExtWorkspace;
×
1032

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

1037
    /* Save the document and set workspace name */
1038
    QFile::FileError error = saveXML(fn);
×
1039
    handleFileError(error);
×
1040

1041
    updateFileOpenMenu(fn);
×
1042
    return error;
×
1043
}
1044

1045
/*****************************************************************************
1046
 * Control action slots
1047
 *****************************************************************************/
1048

1049
void App::slotControlMonitor()
×
1050
{
1051
    Monitor::createAndShow(this, m_doc);
×
1052
}
×
1053

1054
void App::slotAddressTool()
×
1055
{
1056
    AddressTool at(this);
×
1057
    at.exec();
×
1058
}
×
1059

1060
void App::slotControlBlackout()
×
1061
{
1062
    m_doc->inputOutputMap()->setBlackout(!m_doc->inputOutputMap()->blackout());
×
1063
}
×
1064

1065
void App::slotBlackoutChanged(bool state)
×
1066
{
1067
    m_controlBlackoutAction->setChecked(state);
×
1068
}
×
1069

1070
void App::slotControlPanic()
×
1071
{
1072
    m_doc->masterTimer()->stopAllFunctions();
×
1073
}
×
1074

1075
void App::slotFadeAndStopAll()
×
1076
{
1077
    QAction *action = (QAction *)sender();
×
1078
    int timeout = action->data().toInt();
×
1079

1080
    m_doc->masterTimer()->fadeAndStopAll(timeout);
×
1081
}
×
1082

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

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

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

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

1115
void App::slotLiveEditVirtualConsole()
×
1116
{
1117
    VirtualConsole::instance()->toggleLiveEdit();
×
1118
}
×
1119

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

1128
    qDebug() << "Detaching context" << context;
×
1129

1130
    DetachedContext *detachedWindow = new DetachedContext(this);
×
1131
    detachedWindow->setCentralWidget(context);
×
1132
    detachedWindow->resize(800, 600);
×
1133
    detachedWindow->show();
×
1134
    context->show();
×
1135

1136
    connect(detachedWindow, SIGNAL(closing()),
×
1137
            this, SLOT(slotReattachContext()));
1138
}
×
1139

1140
void App::slotReattachContext()
×
1141
{
1142
    DetachedContext *window = qobject_cast<DetachedContext *>(sender());
×
1143

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

1149
    qDebug() << "Reattaching context" << tabIndex << tabLabel << context;
×
1150

1151
    context->setParent(m_tab);
×
1152
    m_tab->insertTab(tabIndex, context, tabIcon, tabLabel);
×
1153
}
×
1154

1155
void App::slotControlFullScreen()
×
1156
{
1157
    static int wstate = windowState();
×
1158

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

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

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

1191
/*****************************************************************************
1192
 * Help action slots
1193
 *****************************************************************************/
1194

1195
void App::slotHelpIndex()
×
1196
{
1197
    DocBrowser::createAndShow(this);
×
1198
}
×
1199

1200
void App::slotHelpAbout()
×
1201
{
1202
    AboutBox ab(this);
×
1203
    ab.exec();
×
1204
}
×
1205

1206
void App::slotRecentFileClicked(QAction *recent)
×
1207
{
1208
    if (recent == NULL)
×
1209
        return;
×
1210

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

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

1230
    m_workingDirectory = QFileInfo(recentAbsPath).absoluteDir();
×
1231
    QSettings settings;
×
1232
    settings.setValue(SETTINGS_WORKINGPATH, m_workingDirectory.absolutePath());
×
1233

1234
    /* Clear existing document data */
1235
    clearDocument();
×
1236

1237
#ifdef DEBUG_SPEED
1238
    speedTime.restart();
1239
#endif
1240

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

1246
#ifdef DEBUG_SPEED
1247
    qDebug() << "[App] Project loaded in" << speedTime.elapsed() << "ms.";
1248
#endif
1249

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

1262
/*****************************************************************************
1263
 * Load & Save
1264
 *****************************************************************************/
1265

1266
void App::setFileName(const QString& fileName)
×
1267
{
1268
    m_fileName = fileName;
×
1269
}
×
1270

1271
QString App::fileName() const
×
1272
{
1273
    return m_fileName;
×
1274
}
1275

1276
QFile::FileError App::loadXML(const QString& fileName)
×
1277
{
1278
    QFile::FileError retval = QFile::NoError;
×
1279

1280
    if (fileName.isEmpty() == true)
×
1281
        return QFile::OpenError;
×
1282

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

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

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

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

1325
    QLCFile::releaseXMLReader(doc);
×
1326

1327
    return retval;
×
1328
}
1329

1330
bool App::loadXML(QXmlStreamReader& doc, bool goToConsole, bool fromMemory)
×
1331
{
1332
    if (doc.readNextStartElement() == false)
×
1333
        return false;
×
1334

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

1341
    QString activeWindowName = doc.attributes().value(KXMLQLCWorkspaceWindow).toString();
×
1342

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

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

1386
    // Perform post-load operations
1387
    VirtualConsole::instance()->postLoad();
×
1388

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

1402
    m_doc->inputOutputMap()->startUniverses();
×
1403

1404
    return true;
×
1405
}
1406

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

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

1422
    doc.writeStartDocument();
×
1423
    doc.writeDTD(QString("<!DOCTYPE %1>").arg(KXMLQLCWorkspace));
×
1424

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

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

1438
    /* Write engine components to the XML document */
1439
    m_doc->saveXML(&doc);
×
1440

1441
    /* Write virtual console to the XML document */
1442
    VirtualConsole::instance()->saveXML(&doc);
×
1443

1444
    /* Write Simple Desk to the XML document */
1445
    SimpleDesk::instance()->saveXML(&doc);
×
1446

1447
    doc.writeEndElement(); // close KXMLQLCWorkspace
×
1448

1449
    /* End the document and close all the open elements */
1450
    doc.writeEndDocument();
×
1451
    file.close();
×
1452

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

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

1471
    return QFile::NoError;
×
1472
}
1473

1474
void App::slotLoadDocFromMemory(QString xmlData)
×
1475
{
1476
    if (xmlData.isEmpty())
×
1477
        return;
×
1478

1479
    /* Clear existing document data */
1480
    clearDocument();
×
1481

1482
    QBuffer databuf;
×
1483
    databuf.setData(xmlData.simplified().toUtf8());
×
1484
    databuf.open(QIODevice::ReadOnly | QIODevice::Text);
×
1485

1486
    //qDebug() << "Buffer data:" << databuf.data();
1487
    QXmlStreamReader doc(&databuf);
×
1488

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

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

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

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

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