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

mcallegari / qlcplus / 20794594664

07 Jan 2026 07:52PM UTC coverage: 34.17% (-0.1%) from 34.276%
20794594664

push

github

mcallegari
Back to 5.1.1 debug

17716 of 51846 relevant lines covered (34.17%)

19821.52 hits per line

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

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

5
  Copyright (C) Heikki Junnila
6

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

11
      http://www.apache.org/licenses/LICENSE-2.0.txt
12

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

20
#include <QTextDocument>
21
#include <QInputDialog>
22
#include <QFileDialog>
23
#include <QTextCursor>
24
#include <QMessageBox>
25
#include <QFormLayout>
26
#include <QFileInfo>
27
#include <QAction>
28
#include <QDebug>
29
#include <QMenu>
30
#include <cmath>
31

32
#include "functionselection.h"
33
#include "channelsselection.h"
34
#include "assignhotkey.h"
35
#include "scripteditor.h"
36
#include "mastertimer.h"
37
#include "speeddial.h"
38
#include "script.h"
39
#include "doc.h"
40

41
ScriptEditor::ScriptEditor(QWidget* parent, Script* script, Doc* doc)
×
42
    : QWidget(parent)
43
    , m_script(script)
×
44
    , m_doc(doc)
×
45
    , m_lastUsedPath(QString())
×
46
{
47
    setupUi(this);
×
48
    initAddMenu();
×
49

50
    /* Name */
51
    m_nameEdit->setText(m_script->name());
×
52
    m_nameEdit->setSelection(0, m_nameEdit->text().length());
×
53
    connect(m_nameEdit, SIGNAL(textEdited(const QString&)),
×
54
            this, SLOT(slotNameEdited(const QString&)));
55

56
    /* Document */
57
    m_document = new QTextDocument(m_script->data(), this);
×
58
#if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0))
59
    m_editor->setTabStopWidth(20);
60
#else
61
    m_editor->setTabStopDistance(20);
×
62
#endif
63
    m_editor->setDocument(m_document);
×
64
    connect(m_document, SIGNAL(undoAvailable(bool)), m_undoButton, SLOT(setEnabled(bool)));
×
65
    m_document->setUndoRedoEnabled(false);
×
66
    m_document->setUndoRedoEnabled(true);
×
67
    m_document->clearUndoRedoStacks();
×
68

69
    m_editor->moveCursor(QTextCursor::End);
×
70
    connect(m_document, SIGNAL(contentsChanged()), this, SLOT(slotContentsChanged()));
×
71

72
    connect(m_testPlayButton, SIGNAL(clicked()), this, SLOT(slotTestRun()));
×
73
    connect(m_checkButton, SIGNAL(clicked()), this, SLOT(slotCheckSyntax()));
×
74

75
    connect(m_script, SIGNAL(stopped(quint32)), this, SLOT(slotFunctionStopped(quint32)));
×
76

77
    // Set focus to the editor
78
    m_nameEdit->setFocus();
×
79
}
×
80

81
ScriptEditor::~ScriptEditor()
×
82
{
83
    delete m_document;
×
84
    m_document = NULL;
×
85

86
    if (m_testPlayButton->isChecked() == true)
×
87
        m_script->stopAndWait();
×
88
}
×
89

90
void ScriptEditor::initAddMenu()
×
91
{
92
    m_addStartFunctionAction = new QAction(QIcon(":/function.png"), tr("Start Function"), this);
×
93
    connect(m_addStartFunctionAction, SIGNAL(triggered(bool)),
×
94
            this, SLOT(slotAddStartFunction()));
95

96
    m_addStopFunctionAction = new QAction(QIcon(":/fileclose.png"), tr("Stop Function"), this);
×
97
    connect(m_addStopFunctionAction, SIGNAL(triggered(bool)),
×
98
            this, SLOT(slotAddStopFunction()));
99

100
    m_addBlackoutAction = new QAction(QIcon(":/blackout.png"), tr("Blackout"), this);
×
101
    connect(m_addBlackoutAction, SIGNAL(triggered(bool)),
×
102
            this, SLOT(slotAddBlackout()));
103

104
    m_addWaitAction = new QAction(QIcon(":/speed.png"), tr("Wait"), this);
×
105
    connect(m_addWaitAction, SIGNAL(triggered(bool)),
×
106
            this, SLOT(slotAddWait()));
107

108
    m_addWaitKeyAction = new QAction(QIcon(":/key_bindings.png"), tr("Wait Key"), this);
×
109
    connect(m_addWaitKeyAction, SIGNAL(triggered(bool)),
×
110
            this, SLOT(slotAddWaitKey()));
111

112
    m_addSetHtpAction = new QAction(QIcon(":/fixture.png"), tr("Set HTP"), this);
×
113
    connect(m_addSetHtpAction, SIGNAL(triggered(bool)),
×
114
            this, SLOT(slotAddSetHtp()));
115

116
    m_addSetLtpAction = new QAction(QIcon(":/fixture.png"), tr("Set LTP"), this);
×
117
    connect(m_addSetLtpAction, SIGNAL(triggered(bool)),
×
118
            this, SLOT(slotAddSetLtp()));
119

120
    m_addSetFixtureAction = new QAction(QIcon(":/movinghead.png"), tr("Set Fixture"), this);
×
121
    connect(m_addSetFixtureAction, SIGNAL(triggered(bool)),
×
122
            this, SLOT(slotAddSetFixture()));
123

124
    m_addSystemCommandAction = new QAction(QIcon(":/player_play.png"), tr("System Command"), this);
×
125
    connect(m_addSystemCommandAction, SIGNAL(triggered(bool)),
×
126
            this, SLOT(slotAddSystemCommand()));
127

128
    m_addCommentAction = new QAction(QIcon(":/label.png"), tr("Comment"), this);
×
129
    connect(m_addCommentAction, SIGNAL(triggered(bool)),
×
130
            this, SLOT(slotAddComment()));
131

132
    m_addRandomAction = new QAction(QIcon(":/other.png"), tr("Random Number"), this);
×
133
    connect(m_addRandomAction, SIGNAL(triggered(bool)),
×
134
            this, SLOT(slotAddRandom()));
135

136
    m_addFilePathAction = new QAction(QIcon(":/fileopen.png"), tr("File Path"), this);
×
137
    connect(m_addFilePathAction, SIGNAL(triggered(bool)),
×
138
            this, SLOT(slotAddFilePath()));
139

140
    m_addMenu = new QMenu(this);
×
141
    m_addMenu->addAction(m_addStartFunctionAction);
×
142
    m_addMenu->addAction(m_addStopFunctionAction);
×
143
    m_addMenu->addAction(m_addBlackoutAction);
×
144
    //m_addMenu->addAction(m_addSetHtpAction);
145
    //m_addMenu->addAction(m_addSetLtpAction);
146
    m_addMenu->addAction(m_addSetFixtureAction);
×
147
    m_addMenu->addAction(m_addSystemCommandAction);
×
148
    m_addMenu->addSeparator();
×
149
    m_addMenu->addAction(m_addWaitAction);
×
150
    //m_addMenu->addAction(m_addWaitKeyAction);
151
    m_addMenu->addSeparator();
×
152
    m_addMenu->addAction(m_addCommentAction);
×
153
    m_addMenu->addAction(m_addRandomAction);
×
154
    m_addMenu->addAction(m_addFilePathAction);
×
155

156
    m_addButton->setMenu(m_addMenu);
×
157
}
×
158

159
QString ScriptEditor::getFilePath()
×
160
{
161
    /* Create a file open dialog */
162
    QFileDialog dialog(this);
×
163
    dialog.setWindowTitle(tr("Open Executable File"));
×
164
    dialog.setAcceptMode(QFileDialog::AcceptOpen);
×
165

166
    QStringList filters;
×
167
#if defined(WIN32) || defined(Q_OS_WIN)
168
    filters << tr("All Files (*.*)");
169
#else
170
    filters << tr("All Files (*)");
×
171
#endif
172
    dialog.setNameFilters(filters);
×
173

174
    /* Append useful URLs to the dialog */
175
    QList <QUrl> sidebar;
×
176
    sidebar.append(QUrl::fromLocalFile(QDir::homePath()));
×
177
    sidebar.append(QUrl::fromLocalFile(QDir::rootPath()));
×
178
    dialog.setSidebarUrls(sidebar);
×
179

180
    if (!m_lastUsedPath.isEmpty())
×
181
        dialog.setDirectory(m_lastUsedPath);
×
182

183
    /* Get file name */
184
    if (dialog.exec() != QDialog::Accepted)
×
185
        return QString();
×
186

187
    QString fn = dialog.selectedFiles().first();
×
188
    if (fn.isEmpty() == true)
×
189
        return QString();
×
190

191
#if defined(WIN32) || defined(Q_OS_WIN)
192
    fn.replace("/", "\\");
193
#endif
194

195
    if (fn.contains(" "))
×
196
        return QString("\"%1\"").arg(fn);
×
197
    else
198
        return fn;
×
199
}
×
200

201
void ScriptEditor::slotNameEdited(const QString& name)
×
202
{
203
    m_script->setName(name);
×
204
}
×
205

206
void ScriptEditor::slotContentsChanged()
×
207
{
208
    //! @todo: this might become quite heavy if there's a lot of content
209
    m_script->setData(m_document->toPlainText());
×
210
    m_doc->setModified();
×
211
}
×
212

213
void ScriptEditor::slotFunctionStopped(quint32 id)
×
214
{
215
    if (id == m_script->id())
×
216
    {
217
        m_testPlayButton->blockSignals(true);
×
218
        m_testPlayButton->setChecked(false);
×
219
        m_testPlayButton->blockSignals(false);
×
220
    }
221
}
×
222

223
void ScriptEditor::slotAddStartFunction()
×
224
{
225
    FunctionSelection fs(this, m_doc);
×
226
    fs.setDisabledFunctions(QList <quint32> () << m_script->id());
×
227
    if (fs.exec() == QDialog::Accepted)
×
228
    {
229
        m_editor->moveCursor(QTextCursor::StartOfLine);
×
230
        QTextCursor cursor(m_editor->textCursor());
×
231

232
        foreach (quint32 id, fs.selection())
×
233
        {
234
            Function* function = m_doc->function(id);
×
235
            Q_ASSERT(function != NULL);
×
236
            QString cmd = QString("%1:%2 // %3\n").arg(Script::startFunctionCmd)
×
237
                                                   .arg(id)
×
238
                                                   .arg(function->name());
×
239
            cursor.insertText(cmd);
×
240
            m_editor->moveCursor(QTextCursor::Down);
×
241
        }
×
242
    }
×
243
}
×
244

245
void ScriptEditor::slotAddStopFunction()
×
246
{
247
    FunctionSelection fs(this, m_doc);
×
248
    fs.setDisabledFunctions(QList <quint32> () << m_script->id());
×
249
    if (fs.exec() == QDialog::Accepted)
×
250
    {
251
        m_editor->moveCursor(QTextCursor::StartOfLine);
×
252
        QTextCursor cursor(m_editor->textCursor());
×
253

254
        foreach (quint32 id, fs.selection())
×
255
        {
256
            Function* function = m_doc->function(id);
×
257
            Q_ASSERT(function != NULL);
×
258
            QString cmd = QString("%1:%2 // %3\n").arg(Script::stopFunctionCmd)
×
259
                                                  .arg(id)
×
260
                                                  .arg(function->name());
×
261
            cursor.insertText(cmd);
×
262
            m_editor->moveCursor(QTextCursor::Down);
×
263
        }
×
264
    }
×
265
}
×
266

267
void ScriptEditor::slotAddBlackout()
×
268
{
269
    QDialog dialog(this);
×
270
    // Use a layout allowing to have a label next to each field
271
    QVBoxLayout dLayout(&dialog);
×
272

273
    QCheckBox *cb = new QCheckBox(tr("Blackout state"));
×
274
    cb->setChecked(true);
×
275
    dLayout.addWidget(cb);
×
276

277
    // Add some standard buttons (Cancel/Ok) at the bottom of the dialog
278
    QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
279
                               Qt::Horizontal, &dialog);
×
280
    dLayout.addWidget(&buttonBox);
×
281
    QObject::connect(&buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
×
282
    QObject::connect(&buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
×
283

284
    // Show the dialog as modal
285
    if (dialog.exec() == QDialog::Accepted)
×
286
    {
287
        m_editor->moveCursor(QTextCursor::StartOfLine);
×
288
        m_editor->textCursor().insertText(QString("%1:%2\n")
×
289
                              .arg(Script::blackoutCmd)
×
290
                              .arg(cb->isChecked() ? Script::blackoutOn : Script::blackoutOff));
×
291
    }
292
}
×
293

294
void ScriptEditor::slotAddWait()
×
295
{
296
    QDialog dialog(this);
×
297
    // Use a layout allowing to have a label next to each field
298
    QVBoxLayout dLayout(&dialog);
×
299

300
    dLayout.addWidget(new QLabel(tr("Enter the desired time")));
×
301
    SpeedDial *sd = new SpeedDial(this);
×
302
    ushort dialMask = sd->visibilityMask();
×
303
    dialMask = (dialMask & ~SpeedDial::Infinite);
×
304
    dialMask = (dialMask & ~SpeedDial::Tap);
×
305
    sd->setVisibilityMask(dialMask);
×
306
    sd->setValue(1000);
×
307
    dLayout.addWidget(sd);
×
308

309
    // Add some standard buttons (Cancel/Ok) at the bottom of the dialog
310
    QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
311
                               Qt::Horizontal, &dialog);
×
312
    dLayout.addWidget(&buttonBox);
×
313
    QObject::connect(&buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
×
314
    QObject::connect(&buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
×
315

316
    // Show the dialog as modal
317
    if (dialog.exec() == QDialog::Accepted)
×
318
    {
319
        m_editor->moveCursor(QTextCursor::StartOfLine);
×
320
        m_editor->textCursor().insertText(QString("%1:%2\n")
×
321
                              .arg(Script::waitCmd).arg(Function::speedToString(sd->value())));
×
322
    }
323
}
×
324

325
void ScriptEditor::slotAddWaitKey()
×
326
{
327
    AssignHotKey ahk(this);
×
328
    if (ahk.exec() == QDialog::Accepted)
×
329
    {
330
        m_editor->moveCursor(QTextCursor::StartOfLine);
×
331
        m_editor->textCursor().insertText(QString("%1:%2 // Not supported yet\n")
×
332
                                                    .arg(Script::waitKeyCmd)
×
333
                                                    .arg(ahk.keySequence().toString()));
×
334
    }
335
}
×
336

337
void ScriptEditor::slotAddSetHtp()
×
338
{
339
    m_editor->moveCursor(QTextCursor::StartOfLine);
×
340
    m_editor->textCursor().insertText(QString("sethtp:0 val:0 uni:1 // Not supported yet\n"));
×
341
    m_editor->moveCursor(QTextCursor::EndOfLine);
×
342
}
×
343

344
void ScriptEditor::slotAddSetLtp()
×
345
{
346
    m_editor->moveCursor(QTextCursor::StartOfLine);
×
347
    m_editor->textCursor().insertText(QString("setltp:0 val:0 uni:1 // Not supported yet\n"));
×
348
    m_editor->moveCursor(QTextCursor::EndOfLine);
×
349
}
×
350

351
void ScriptEditor::slotAddSetFixture()
×
352
{
353
    ChannelsSelection cfg(m_doc, this);
×
354
    if (cfg.exec() == QDialog::Rejected)
×
355
        return; // User pressed cancel
×
356

357
    QList<SceneValue> channelsList = cfg.channelsList();
×
358
    foreach (SceneValue sv, channelsList)
×
359
    {
360
        Fixture* fxi = m_doc->fixture(sv.fxi);
×
361
        if (fxi != NULL)
×
362
        {
363
            const QLCChannel* channel = fxi->channel(sv.channel);
×
364
            m_editor->moveCursor(QTextCursor::StartOfLine);
×
365
            m_editor->textCursor().insertText(QString("%1:%2 ch:%3 val:0 // %4, %5\n")
×
366
                                                .arg(Script::setFixtureCmd)
×
367
                                                .arg(fxi->id()).arg(sv.channel)
×
368
                                                .arg(fxi->name()).arg(channel->name()));
×
369
            m_editor->moveCursor(QTextCursor::Down);
×
370
        }
371
    }
×
372
}
×
373

374
void ScriptEditor::slotAddSystemCommand()
×
375
{
376
    QString fn = getFilePath();
×
377
    if (fn.isEmpty())
×
378
        return;
×
379

380
    QFileInfo fInfo(fn);
×
381
#if !defined(WIN32) && !defined(Q_OS_WIN)
382
    if (fInfo.isExecutable() == false)
×
383
    {
384
        QMessageBox::warning(this, tr("Invalid executable"), tr("Please select an executable file!"));
×
385
        return;
×
386
    }
387
#endif
388
    m_lastUsedPath = fInfo.absolutePath();
×
389

390
    QString args = QInputDialog::getText(this, tr("Enter the program arguments (leave empty if not required)"), "",
×
391
                                        QLineEdit::Normal, QString());
×
392

393
    QStringList argsList = args.split(" ");
×
394
    QString formattedArgs;
×
395
    foreach (QString arg, argsList)
×
396
    {
397
        formattedArgs.append(QString("arg:%1 ").arg(arg));
×
398
    }
×
399
    formattedArgs = formattedArgs.trimmed();
×
400

401
    m_editor->moveCursor(QTextCursor::StartOfLine);
×
402
    m_editor->textCursor().insertText(QString("%1:%2 %3\n")
×
403
                                        .arg(Script::systemCmd)
×
404
                                        .arg(fn).arg(formattedArgs));
×
405
    m_editor->moveCursor(QTextCursor::Down);
×
406
}
×
407

408
void ScriptEditor::slotAddComment()
×
409
{
410
    bool ok = false;
×
411
    QString str = QInputDialog::getText(this, tr("Add Comment"), "",
×
412
                                        QLineEdit::Normal, QString(), &ok);
×
413
    if (ok == true)
×
414
    {
415
        //m_editor->moveCursor(QTextCursor::StartOfLine);
416
        m_editor->textCursor().insertText(QString("// %1").arg(str));
×
417
        //m_editor->moveCursor(QTextCursor::EndOfLine);
418
    }
419
}
×
420

421
void ScriptEditor::slotAddRandom()
×
422
{
423
    QDialog dialog(this);
×
424
    // Use a layout allowing to have a label next to each field
425
    QFormLayout dLayout(&dialog);
×
426

427
    dLayout.addRow(new QLabel(tr("Enter the range for the randomization")));
×
428

429
    QSpinBox *minSB = new QSpinBox(this);
×
430
    minSB->setRange(0, 999);
×
431
    QSpinBox *maxSB = new QSpinBox(this);
×
432
    maxSB->setRange(0, 999);
×
433
    maxSB->setValue(255);
×
434
    dLayout.addRow(tr("Minimum value"), minSB);
×
435
    dLayout.addRow(tr("Maximum value"), maxSB);
×
436

437
    // Add some standard buttons (Cancel/Ok) at the bottom of the dialog
438
    QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
439
                               Qt::Horizontal, &dialog);
×
440
    dLayout.addRow(&buttonBox);
×
441
    QObject::connect(&buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
×
442
    QObject::connect(&buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
×
443

444
    // Show the dialog as modal
445
    if (dialog.exec() == QDialog::Accepted)
×
446
    {
447
        m_editor->moveCursor(QTextCursor::StartOfLine);
×
448
        m_editor->textCursor().insertText(QString("random(%1,%2)")
×
449
                              .arg(minSB->value()).arg(maxSB->value()));
×
450
        m_editor->moveCursor(QTextCursor::EndOfLine);
×
451
    }
452
}
×
453

454
void ScriptEditor::slotAddFilePath()
×
455
{
456
    QString fn = getFilePath();
×
457
    if (fn.isEmpty())
×
458
        return;
×
459

460
    QFileInfo fInfo(fn);
×
461
    m_lastUsedPath = fInfo.absolutePath();
×
462

463
    //m_editor->textCursor().insertText(QUrl::toPercentEncoding(fn));
464
    m_editor->textCursor().insertText(fn);
×
465
}
×
466

467
void ScriptEditor::slotCheckSyntax()
×
468
{
469
    QString errResult;
×
470
    QString scriptText = m_document->toPlainText();
×
471
    m_script->setData(scriptText);
×
472
    QList<int> errLines = m_script->syntaxErrorsLines();
×
473
    if (errLines.isEmpty())
×
474
    {
475
        errResult.append(tr("No syntax errors found in the script"));
×
476
    }
477
    else
478
    {
479
        QStringList lines = scriptText.split(QRegularExpression("(\\r\\n|\\n\\r|\\r|\\n)"));
×
480
        foreach (int line, errLines)
×
481
        {
482
            errResult.append(tr("Syntax error at line %1:\n%2\n\n").arg(line).arg(lines.at(line - 1)));
×
483
        }
×
484
    }
×
485
    QMessageBox::information(this, tr("Script check results"), errResult);
×
486
}
×
487

488
void ScriptEditor::slotTestRun()
×
489
{
490
    if (m_testPlayButton->isChecked() == true)
×
491
        m_script->start(m_doc->masterTimer(), functionParent());
×
492
    else
493
        m_script->stopAndWait();
×
494
}
×
495

496
FunctionParent ScriptEditor::functionParent() const
×
497
{
498
    return FunctionParent::master();
×
499
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc