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

mcallegari / qlcplus / 15687200258

16 Jun 2025 03:05PM UTC coverage: 31.872%. Remained the same
15687200258

push

github

mcallegari
ui: fix recent and click&go look & feel with dark theme (fix #1764)

0 of 7 new or added lines in 2 files covered. (0.0%)

16436 of 51568 relevant lines covered (31.87%)

19123.71 hits per line

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

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

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

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

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

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

22
#include <QToolButton>
23
#include <QtCore>
24
#include <QtWidgets>
25

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

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

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

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

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

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

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

78
//#define DEBUG_SPEED
79

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

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

90
#define MAX_RECENT_FILES    10
91

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

96
/*****************************************************************************
97
 * Initialization
98
 *****************************************************************************/
99

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

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

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

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

129
    , m_toolbar(NULL)
×
130

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

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

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

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

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

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

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

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

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

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

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

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

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

179
    m_doc = NULL;
×
180
}
×
181

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

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

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

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

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

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

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

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

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

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

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

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

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

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

281
    m_dumpProperties = new DmxDumpFactoryProperties(KUniverseCount);
×
282

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

435
/*****************************************************************************
436
 * Progress dialog
437
 *****************************************************************************/
438

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

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

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

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

469
/*****************************************************************************
470
 * Doc
471
 *****************************************************************************/
472

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

746
    m_controlPanicAction->setMenu(m_fadeAndStopMenu);
×
747

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

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

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

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

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

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

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

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

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

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

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

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

857
    return false;
×
858
}
×
859

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

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

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

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

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

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

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

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

957
    clearDocument();
×
958
    return true;
×
959
}
×
960

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

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

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

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

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

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

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

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

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

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

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

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

1036
    updateFileOpenMenu(fn);
×
1037

1038
    return error;
×
1039
}
×
1040

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1385
    QLCFile::releaseXMLReader(doc);
×
1386

1387
    return retval;
×
1388
}
1389

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

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

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

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

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

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

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

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

1464
    return true;
×
1465
}
×
1466

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

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

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

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

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

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

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

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

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

1509
    /* End the document and close all the open elements */
1510
    doc.writeEndDocument();
×
1511
    file.close();
×
1512

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

1526
    /* Set the file name for the current Doc instance and
1527
       set it also in an unmodified state. */
1528
    setFileName(fileName);
×
1529
    m_doc->resetModified();
×
1530

1531
    return QFile::NoError;
×
1532
}
×
1533

1534
void App::slotLoadDocFromMemory(QString xmlData)
×
1535
{
1536
    if (xmlData.isEmpty())
×
1537
        return;
×
1538

1539
    /* Clear existing document data */
1540
    clearDocument();
×
1541

1542
    QBuffer databuf;
×
1543
    databuf.setData(xmlData.simplified().toUtf8());
×
1544
    databuf.open(QIODevice::ReadOnly | QIODevice::Text);
×
1545

1546
    //qDebug() << "Buffer data:" << databuf.data();
1547
    QXmlStreamReader doc(&databuf);
×
1548

1549
    if (doc.hasError())
×
1550
    {
1551
        qWarning() << Q_FUNC_INFO << "Unable to read from XML in memory";
×
1552
        return;
×
1553
    }
1554

1555
    while (!doc.atEnd())
×
1556
    {
1557
        if (doc.readNext() == QXmlStreamReader::DTD)
×
1558
            break;
×
1559
    }
1560
    if (doc.hasError())
×
1561
    {
1562
        qDebug() << "XML has errors:" << doc.errorString();
×
1563
        return;
×
1564
    }
1565

1566
    if (doc.dtdName() == KXMLQLCWorkspace)
×
1567
        loadXML(doc, true, true);
×
1568
    else
1569
        qDebug() << "XML doesn't have a Workspace tag";
×
1570
}
×
1571

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

1578
    /* Save the document and set workspace name */
1579
    QFile::FileError error = saveXML(fileName);
×
1580
    handleFileError(error);
×
1581
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc