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

mcallegari / qlcplus / 16868879954

11 Aug 2025 01:34AM UTC coverage: 34.258% (-0.03%) from 34.289%
16868879954

Pull #1789

github

web-flow
Merge 49d2794a2 into db91467b7
Pull Request #1789: #1786 sync virtual console button colour to RGB button custom feedback

22 of 111 new or added lines in 7 files covered. (19.82%)

3 existing lines in 2 files now uncovered.

17722 of 51731 relevant lines covered (34.26%)

19401.42 hits per line

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

79.55
/ui/src/virtualconsole/vcbutton.cpp
1
/*
2
  Q Light Controller Plus
3
  vcbutton.cpp
4

5
  Copyright (c) Heikki Junnila
6
                Massimo Callegari
7

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

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

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

21
#include <QStyleOptionButton>
22
#include <QXmlStreamReader>
23
#include <QXmlStreamWriter>
24
#include <QWidgetAction>
25
#include <QColorDialog>
26
#include <QImageReader>
27
#include <QFileDialog>
28
#include <QPaintEvent>
29
#include <QMouseEvent>
30
#include <QMessageBox>
31
#include <QByteArray>
32
#include <QSettings>
33
#include <QPainter>
34
#include <QString>
35
#include <QDebug>
36
#include <QEvent>
37
#include <QTimer>
38
#include <QBrush>
39
#include <QStyle>
40
#include <QMenu>
41
#include <QSize>
42
#include <QPen>
43

44
#if defined(WIN32) || defined(Q_OS_WIN)
45
 #include <QStyleFactory>
46
#endif
47

48
#include "qlcinputsource.h"
49
#include "qlcmacros.h"
50
#include "qlcfile.h"
51

52
#include "vcbuttonproperties.h"
53
#include "vcpropertieseditor.h"
54
#include "virtualconsole.h"
55
#include "chaseraction.h"
56
#include "mastertimer.h"
57
#include "vcsoloframe.h"
58
#include "vcbutton.h"
59
#include "function.h"
60
#include "apputil.h"
61
#include "chaser.h"
62
#include "doc.h"
63

64
const QSize VCButton::defaultSize(QSize(50, 50));
65

66
/*****************************************************************************
67
 * Initialization
68
 *****************************************************************************/
69

70
VCButton::VCButton(QWidget* parent, Doc* doc) : VCWidget(parent, doc)
25✔
71
    , m_iconPath()
25✔
72
    , m_blackoutFadeOutTime(0)
25✔
73
    , m_startupIntensityEnabled(false)
25✔
74
    , m_startupIntensity(1.0)
25✔
75
    , m_flashOverrides(false)
25✔
76
    , m_flashForceLTP(false)
50✔
77
{
78
    /* Set the class name "VCButton" as the object name as well */
79
    setObjectName(VCButton::staticMetaObject.className());
25✔
80

81
    /* No function is initially attached to the button */
82
    m_function = Function::invalidId();
25✔
83

84
    setType(VCWidget::ButtonWidget);
25✔
85
    setCaption(QString());
25✔
86
    setState(Inactive);
25✔
87
    m_action = Action(-1); // avoid use of uninitialized value
25✔
88
    setAction(Toggle);
25✔
89
    setFrameStyle(KVCFrameStyleNone);
25✔
90

91
    /* Menu actions */
92
    m_chooseIconAction = new QAction(QIcon(":/image.png"), tr("Choose..."),
50✔
93
                                     this);
25✔
94
    m_chooseIconAction->setShortcut(QKeySequence("SHIFT+C"));
25✔
95

96
    m_resetIconAction = new QAction(QIcon(":/undo.png"), tr("None"), this);
25✔
97
    m_resetIconAction->setShortcut(QKeySequence("SHIFT+ALT+C"));
25✔
98

99
    connect(m_chooseIconAction, SIGNAL(triggered(bool)),
25✔
100
            this, SLOT(slotChooseIcon()));
101
    connect(m_resetIconAction, SIGNAL(triggered(bool)),
25✔
102
            this, SLOT(slotResetIcon()));
103

104
    /* Initial size */
105
    QSettings settings;
25✔
106
    QVariant var = settings.value(SETTINGS_BUTTON_SIZE);
25✔
107
    if (var.isValid() == true)
25✔
108
        resize(var.toSize());
×
109
    else
110
        resize(defaultSize);
25✔
111

112
    var = settings.value(SETTINGS_BUTTON_STATUSLED);
25✔
113
    if (var.isValid() == true && var.toBool() == true)
25✔
114
        m_ledStyle = true;
×
115
    else
116
        m_ledStyle = false;
25✔
117

118
    setStyle(AppUtil::saneStyle());
25✔
119

120
    /* Listen to function removals */
121
    connect(m_doc, SIGNAL(functionRemoved(quint32)),
25✔
122
            this, SLOT(slotFunctionRemoved(quint32)));
123
    syncStatus = true;
25✔
124
}
25✔
125

126
VCButton::~VCButton()
29✔
127
{
128
}
29✔
129

130
void VCButton::setID(quint32 id)
1✔
131
{
132
    VCWidget::setID(id);
1✔
133

134
    if (caption().isEmpty())
1✔
135
        setCaption(tr("Button %1").arg(id));
×
136
}
1✔
137

138
/*****************************************************************************
139
 * Clipboard
140
 *****************************************************************************/
141

142
VCWidget* VCButton::createCopy(VCWidget* parent)
2✔
143
{
144
    Q_ASSERT(parent != NULL);
2✔
145

146
    VCButton* button = new VCButton(parent, m_doc);
2✔
147
    if (button->copyFrom(this) == false)
2✔
148
    {
149
        delete button;
×
150
        button = NULL;
×
151
    }
152

153
    return button;
2✔
154
}
155

156
bool VCButton::copyFrom(const VCWidget* widget)
2✔
157
{
158
    const VCButton* button = qobject_cast <const VCButton*> (widget);
2✔
159
    if (button == NULL)
2✔
160
        return false;
×
161

162
    /* Copy button-specific stuff */
163
    setIconPath(button->iconPath());
2✔
164
    setKeySequence(button->keySequence());
2✔
165
    setFunction(button->function());
2✔
166
    enableStartupIntensity(button->isStartupIntensityEnabled());
2✔
167
    setStartupIntensity(button->startupIntensity());
2✔
168
    setStopAllFadeOutTime(button->stopAllFadeTime());
2✔
169
    setAction(button->action());
2✔
170
    m_state = button->m_state;
2✔
171

172
    m_flashForceLTP = button->flashForceLTP();
2✔
173
    m_flashOverrides = button->flashOverrides();
2✔
174

175
    /* Copy common stuff */
176
    return VCWidget::copyFrom(widget);
2✔
177
}
178

179
/*****************************************************************************
180
 * Properties
181
 *****************************************************************************/
182

183
void VCButton::editProperties()
×
184
{
185
    VCButtonProperties prop(this, m_doc);
×
186
    if (prop.exec() == QDialog::Accepted)
×
187
        m_doc->setModified();
×
188
}
×
189

190
/*****************************************************************************
191
 * Background color
192
 *****************************************************************************/
193

194
void VCButton::setBackgroundImage(const QString& path)
3✔
195
{
196
    m_bgPixmap = QPixmap(path);
3✔
197
    m_backgroundImage = path;
3✔
198
    m_doc->setModified();
3✔
199
    update();
3✔
200
}
3✔
201

202
void VCButton::setBackgroundColor(const QColor& color)
5✔
203
{
204
    QPalette pal = palette();
5✔
205

206
    m_hasCustomBackgroundColor = true;
5✔
207
    m_backgroundImage = QString();
5✔
208
    pal.setColor(QPalette::Button, color);
5✔
209
    setPalette(pal);
5✔
210

211
    m_doc->setModified();
5✔
212
}
5✔
213

214
void VCButton::resetBackgroundColor()
2✔
215
{
216
    QColor fg;
2✔
217

218
    m_hasCustomBackgroundColor = false;
2✔
219
    m_backgroundImage = QString();
2✔
220

221
    /* Store foreground color */
222
    if (m_hasCustomForegroundColor == true)
2✔
223
        fg = palette().color(QPalette::ButtonText);
2✔
224

225
    /* Reset the whole palette to application palette */
226
    setPalette(QApplication::palette());
2✔
227

228
    /* Restore foreground color */
229
    if (fg.isValid() == true)
2✔
230
    {
231
        QPalette pal = palette();
2✔
232
        pal.setColor(QPalette::ButtonText, fg);
2✔
233
        setPalette(pal);
2✔
234
    }
2✔
235

236
    m_doc->setModified();
2✔
237
}
2✔
238

239
QColor VCButton::backgroundColor() const
6✔
240
{
241
    return palette().color(QPalette::Button);
6✔
242
}
243

244
/*****************************************************************************
245
 * Foreground color
246
 *****************************************************************************/
247

248
void VCButton::setForegroundColor(const QColor& color)
3✔
249
{
250
    QPalette pal = palette();
3✔
251

252
    m_hasCustomForegroundColor = true;
3✔
253

254
    pal.setColor(QPalette::WindowText, color);
3✔
255
    pal.setColor(QPalette::ButtonText, color);
3✔
256
    setPalette(pal);
3✔
257

258
    m_doc->setModified();
3✔
259
}
3✔
260

261
void VCButton::resetForegroundColor()
2✔
262
{
263
    QColor bg;
2✔
264

265
    m_hasCustomForegroundColor = false;
2✔
266

267
    /* Store background color */
268
    if (m_hasCustomBackgroundColor == true)
2✔
269
        bg = palette().color(QPalette::Button);
2✔
270

271
    /* Reset the whole palette to application palette */
272
    setPalette(QApplication::palette());
2✔
273

274
    /* Restore background color */
275
    if (bg.isValid() == true)
2✔
276
        setBackgroundColor(bg);
2✔
277

278
    m_doc->setModified();
2✔
279
}
2✔
280

281
QColor VCButton::foregroundColor() const
6✔
282
{
283
    return palette().color(QPalette::ButtonText);
6✔
284
}
285

286
/*****************************************************************************
287
 * Button icon
288
 *****************************************************************************/
289

290
QString VCButton::iconPath() const
72✔
291
{
292
    return m_iconPath;
72✔
293
}
294

295
void VCButton::setIconPath(const QString& iconPath)
10✔
296
{
297
    m_iconPath = iconPath;
10✔
298

299
    updateIcon();
10✔
300
    m_doc->setModified();
10✔
301
    update();
10✔
302
}
10✔
303

304
void VCButton::slotChooseIcon()
×
305
{
306
    /* No point coming here if there is no VC */
307
    VirtualConsole *vc = VirtualConsole::instance();
×
308
    if (vc == NULL)
×
309
        return;
×
310

311
    QString formats;
×
312
    QListIterator <QByteArray> it(QImageReader::supportedImageFormats());
×
313
    while (it.hasNext() == true)
×
314
        formats += QString("*.%1 ").arg(QString(it.next()).toLower());
×
315

316
    QString path;
×
317
    path = QFileDialog::getOpenFileName(this, tr("Select button icon"),
×
318
                                        iconPath(), tr("Images (%1)").arg(formats));
×
319
    if (path.isEmpty() == false)
×
320
    {
321
        foreach (VCWidget *widget, vc->selectedWidgets())
×
322
        {
323
            VCButton *button = qobject_cast<VCButton*> (widget);
×
324
            if (button != NULL)
×
325
                button->setIconPath(path);
×
326
        }
×
327
    }
328
}
×
329

330
void VCButton::updateIcon()
52✔
331
{
332
    if (m_action == Blackout)
52✔
333
    {
334
        m_icon = QIcon(":/blackout.png");
1✔
335
        m_iconSize = QSize(26, 26);
1✔
336
    }
337
    else if (m_action == StopAll)
51✔
338
    {
339
        m_icon = QIcon(":/panic.png");
1✔
340
        m_iconSize = QSize(26, 26);
1✔
341
    }
342
    else if (iconPath().isEmpty() == false)
50✔
343
    {
344
        m_icon = QIcon(iconPath());
13✔
345
        m_iconSize = QSize(26, 26);
13✔
346
    }
347
    else
348
    {
349
        m_icon = QIcon();
37✔
350
        m_iconSize = QSize(-1, -1);
37✔
351
    }
352
}
52✔
353

354
void VCButton::slotResetIcon()
1✔
355
{
356
    setIconPath(QString());
1✔
357
    update();
1✔
358
}
1✔
359

360
/*****************************************************************************
361
 * Function attachment
362
 *****************************************************************************/
363

364
void VCButton::setFunction(quint32 fid)
17✔
365
{
366
    Function* old = m_doc->function(m_function);
17✔
367
    if (old != NULL)
17✔
368
    {
369
        /* Get rid of old function connections */
370
        disconnect(old, SIGNAL(running(quint32)),
3✔
371
                this, SLOT(slotFunctionRunning(quint32)));
372
        disconnect(old, SIGNAL(stopped(quint32)),
3✔
373
                this, SLOT(slotFunctionStopped(quint32)));
374
        disconnect(old, SIGNAL(flashing(quint32,bool)),
3✔
375
                this, SLOT(slotFunctionFlashing(quint32,bool)));
376
    }
377

378
    Function* function = m_doc->function(fid);
17✔
379
    if (function != NULL)
17✔
380
    {
381
        /* Connect to the new function */
382
        connect(function, SIGNAL(running(quint32)),
13✔
383
                this, SLOT(slotFunctionRunning(quint32)));
384
        connect(function, SIGNAL(stopped(quint32)),
13✔
385
                this, SLOT(slotFunctionStopped(quint32)));
386
        connect(function, SIGNAL(flashing(quint32,bool)),
13✔
387
                this, SLOT(slotFunctionFlashing(quint32,bool)));
388

389
        m_function = fid;
13✔
390
        setToolTip(function->name());
13✔
391
    }
392
    else
393
    {
394
        /* No function attachment */
395
        m_function = Function::invalidId();
4✔
396
        setToolTip(QString());
4✔
397
    }
398
}
17✔
399

400
quint32 VCButton::function() const
14✔
401
{
402
    return m_function;
14✔
403
}
404

405
void VCButton::adjustFunctionIntensity(Function *f, qreal value)
7✔
406
{
407
    qreal finalValue = isStartupIntensityEnabled() ? startupIntensity() * value : value;
7✔
408

409
    VCWidget::adjustFunctionIntensity(f, finalValue);
7✔
410
}
7✔
411

412
void VCButton::notifyFunctionStarting(quint32 fid, qreal intensity)
×
413
{
414
    Q_UNUSED(intensity);
415

416
    if (mode() == Doc::Design)
×
417
        return;
×
418

419
    if (fid == m_function || m_function == Function::invalidId())
×
420
        return;
×
421

422
    // stop the controlled Function only
423
    // if actively started by this Button
424
    if (m_state != Active)
×
425
        return;
×
426

427
    if (action() == VCButton::Toggle)
×
428
    {
429
        Function *f = m_doc->function(m_function);
×
430
        if (f != NULL)
×
431
        {
432
            f->stop(functionParent());
×
433
            resetIntensityOverrideAttribute();
×
434
        }
435
    }
436
}
437

438
void VCButton::slotFunctionRemoved(quint32 fid)
2✔
439
{
440
    /* Invalidate the button's function if it's the one that was removed */
441
    if (fid == m_function)
2✔
442
    {
443
        setFunction(Function::invalidId());
1✔
444
        resetIntensityOverrideAttribute();
1✔
445
    }
446
}
2✔
447

448
/*****************************************************************************
449
 * Button state
450
 *****************************************************************************/
451

452
VCButton::ButtonState VCButton::state() const
72✔
453
{
454
    return m_state;
72✔
455
}
456

457
void VCButton::setState(ButtonState state)
59✔
458
{
459
    if (state == m_state)
59✔
460
        return;
19✔
461

462
    m_state = state;
40✔
463
    QSharedPointer<QLCInputSource> src = inputSource();
40✔
464
    if (!src.isNull() && src->isValid() == true)
40✔
465
    {
466
        if (m_state == Inactive)
8✔
467
            setBackgroundColor(src->feedbackValue(QLCInputFeedback::LowerValue), src);
4✔
468
        else if (m_state == Monitoring)
4✔
NEW
469
            setBackgroundColor(src->feedbackValue(QLCInputFeedback::MonitorValue), src);
×
470
        else
471
            setBackgroundColor(src->feedbackValue(QLCInputFeedback::UpperValue), src);
4✔
472
    }
473

474
    emit stateChanged(m_state);
40✔
475

476
    updateFeedback();
40✔
477

478
    update();
40✔
479
}
40✔
480

481
void VCButton::updateState()
×
482
{
483
    ButtonState state = Inactive;
×
484

485
    if (m_action == Blackout)
×
486
    {
487
        if (m_doc->inputOutputMap()->blackout())
×
488
            state = Active;
×
489
    }
490
    else if (m_action == Toggle)
×
491
    {
492
        Function* function = m_doc->function(m_function);
×
493
        if (function != NULL && function->isRunning())
×
494
            state = Active;
×
495
    }
496

497
    if (m_state != state)
×
498
        setState(state);
×
499
}
×
500

501
/*****************************************************************************
502
 * Key sequence handler
503
 *****************************************************************************/
504

505
void VCButton::setKeySequence(const QKeySequence& keySequence)
12✔
506
{
507
    m_keySequence = QKeySequence(keySequence);
12✔
508
}
12✔
509

510
QKeySequence VCButton::keySequence() const
9✔
511
{
512
    return m_keySequence;
9✔
513
}
514

515
void VCButton::slotKeyPressed(const QKeySequence& keySequence)
5✔
516
{
517
    if (acceptsInput() == false)
5✔
518
        return;
×
519

520
    if (m_keySequence == keySequence)
5✔
521
        pressFunction();
5✔
522
}
523

524
void VCButton::slotKeyReleased(const QKeySequence& keySequence)
3✔
525
{
526
    if (acceptsInput() == false)
3✔
527
        return;
×
528

529
    if (m_keySequence == keySequence)
3✔
530
        releaseFunction();
3✔
531
}
532

533
void VCButton::updateFeedback()
43✔
534
{
535
    //if (m_state == Monitoring)
536
    //    return;
537

538
    QSharedPointer<QLCInputSource> src = inputSource();
43✔
539
    if (!src.isNull() && src->isValid() == true)
43✔
540
    {
541
        if (m_state == Inactive)
11✔
542
            sendFeedback(src->feedbackValue(QLCInputFeedback::LowerValue), src, src->feedbackExtraParams(QLCInputFeedback::LowerValue));
5✔
543
        else if (m_state == Monitoring)
6✔
544
            sendFeedback(src->feedbackValue(QLCInputFeedback::MonitorValue), src, src->feedbackExtraParams(QLCInputFeedback::MonitorValue));
×
545
        else
546
            sendFeedback(src->feedbackValue(QLCInputFeedback::UpperValue), src, src->feedbackExtraParams(QLCInputFeedback::UpperValue));
6✔
547
    }
548
}
43✔
549

550
void VCButton::setBackgroundColor(int value, QSharedPointer<QLCInputSource> src)
8✔
551
{
552
    InputPatch *ip = m_doc->inputOutputMap()->inputPatch(src->universe());
8✔
553
    if (ip != NULL && ip->profile() != NULL)
8✔
554
    {
NEW
555
        QLCInputProfile *m_profile = ip->profile();
×
NEW
556
        if (m_profile->hasColorTable())
×
557
        {
NEW
558
            QMapIterator <uchar, QPair<QString, QColor>> it(m_profile->colorTable());
×
NEW
559
            while (it.hasNext() == true)
×
560
            {
NEW
561
                it.next();
×
NEW
562
                QPair<QString, QColor> lc = it.value();
×
563

NEW
564
                if (it.key() == value)
×
NEW
565
                    setBackgroundColor(lc.second);
×
NEW
566
            }
×
NEW
567
        }
×
568
    }
569
}
8✔
570

571
/*****************************************************************************
572
 * External input
573
 *****************************************************************************/
574

575
void VCButton::slotInputValueChanged(quint32 universe, quint32 channel, uchar value)
15✔
576
{
577
    /* Don't let input data through in design mode or if disabled */
578
    if (acceptsInput() == false)
15✔
579
        return;
1✔
580

581
    if (checkInputSource(universe, (page() << 16) | channel, value, sender()))
14✔
582
    {
583
        if (m_action == Flash)
14✔
584
        {
585
            // Keep the button depressed only while the external button is kept down.
586
            // Raise the button when the external button is raised.
587
            if (state() == Inactive && value > 0)
6✔
588
                pressFunction();
2✔
589
            else if (state() == Active && value == 0)
4✔
590
                releaseFunction();
2✔
591
        }
592
        else
593
        {
594
            if (value > 0)
8✔
595
            {
596
                // Only toggle when the external button is pressed.
597
                pressFunction();
5✔
598
            }
599
            else
600
            {
601
                // Work around the "internal" feedback of some controllers
602
                // by updating feedback state after button release.
603
                updateFeedback();
3✔
604
            }
605
        }
606
    }
607
}
608

609
/*****************************************************************************
610
 * Button action
611
 *****************************************************************************/
612

613
void VCButton::setAction(Action action)
42✔
614
{
615
    // Blackout signal connection
616
    if (m_action == Blackout && action != Blackout)
42✔
617
        disconnect(m_doc->inputOutputMap(), SIGNAL(blackoutChanged(bool)),
1✔
618
                this, SLOT(slotBlackoutChanged(bool)));
619
    else if (m_action != Blackout && action == Blackout)
41✔
620
        connect(m_doc->inputOutputMap(), SIGNAL(blackoutChanged(bool)),
1✔
621
                this, SLOT(slotBlackoutChanged(bool)));
622

623
    // Action update
624
    m_action = action;
42✔
625
    updateIcon();
42✔
626

627
    // Update tooltip
628
    if (m_action == Blackout)
42✔
629
        setToolTip(tr("Toggle Blackout"));
1✔
630
    else if (m_action == StopAll)
41✔
631
        setToolTip(tr("Stop ALL functions!"));
1✔
632
}
42✔
633

634
VCButton::Action VCButton::action() const
11✔
635
{
636
    return m_action;
11✔
637
}
638

639
QString VCButton::actionToString(VCButton::Action action)
4✔
640
{
641
    if (action == Flash)
4✔
642
        return QString(KXMLQLCVCButtonActionFlash);
2✔
643
    else if (action == Blackout)
2✔
644
        return QString(KXMLQLCVCButtonActionBlackout);
×
645
    else if (action == StopAll)
2✔
646
        return QString(KXMLQLCVCButtonActionStopAll);
×
647
    else
648
        return QString(KXMLQLCVCButtonActionToggle);
2✔
649
}
650

651
VCButton::Action VCButton::stringToAction(const QString& str)
5✔
652
{
653
    if (str == KXMLQLCVCButtonActionFlash)
5✔
654
        return Flash;
3✔
655
    else if (str == KXMLQLCVCButtonActionBlackout)
2✔
656
        return Blackout;
×
657
    else if (str == KXMLQLCVCButtonActionStopAll)
2✔
658
        return StopAll;
×
659
    else
660
        return Toggle;
2✔
661
}
662

663
void VCButton::setStopAllFadeOutTime(int ms)
2✔
664
{
665
    m_blackoutFadeOutTime = ms;
2✔
666
}
2✔
667

668
int VCButton::stopAllFadeTime() const
3✔
669
{
670
    return m_blackoutFadeOutTime;
3✔
671
}
672

673
/*****************************************************************************
674
 * Intensity adjustment
675
 *****************************************************************************/
676

677
void VCButton::enableStartupIntensity(bool enable)
11✔
678
{
679
    m_startupIntensityEnabled = enable;
11✔
680
}
11✔
681

682
bool VCButton::isStartupIntensityEnabled() const
16✔
683
{
684
    return m_startupIntensityEnabled;
16✔
685
}
686

687
void VCButton::setStartupIntensity(qreal fraction)
179✔
688
{
689
    m_startupIntensity = CLAMP(fraction, qreal(0), qreal(1));
179✔
690
}
179✔
691

692
qreal VCButton::startupIntensity() const
183✔
693
{
694
    return m_startupIntensity;
183✔
695
}
696

697
void VCButton::slotAttributeChanged(int value)
×
698
{
699
#if 0
700
    ClickAndGoSlider *slider = (ClickAndGoSlider *)sender();
701
    int idx = slider->property("attrIdx").toInt();
702

703
    Function* func = m_doc->function(m_function);
704
    if (func != NULL)
705
        func->adjustAttribute((qreal)value / 100, idx);
706
#else
707
    Q_UNUSED(value)
708
#endif
709
}
×
710

711

712

713
/*****************************************************************************
714
 * Flash Properties
715
 *****************************************************************************/
716

717
bool VCButton::flashOverrides() const
7✔
718
{
719
    return m_flashOverrides;
7✔
720
}
721

722
void VCButton::setFlashOverride(bool shouldOverride)
1✔
723
{
724
    m_flashOverrides = shouldOverride;
1✔
725
}
1✔
726

727
bool VCButton::flashForceLTP() const
7✔
728
{
729
    return m_flashForceLTP;
7✔
730
}
731

732
void VCButton::setFlashForceLTP(bool forceLTP)
1✔
733
{
734
    m_flashForceLTP = forceLTP;
1✔
735
}
1✔
736

737

738

739
/*****************************************************************************
740
 * Button press / release handlers
741
 *****************************************************************************/
742

743
void VCButton::pressFunction()
13✔
744
{
745
    /* Don't allow pressing during design mode */
746
    if (mode() == Doc::Design)
13✔
747
        return;
×
748

749
    Function* f = NULL;
13✔
750
    if (m_action == Toggle)
13✔
751
    {
752
        f = m_doc->function(m_function);
6✔
753
        if (f == NULL)
6✔
754
            return;
×
755

756
        // if the button is in a SoloFrame and the function is running but was
757
        // started by a different function (a chaser or collection), turn other
758
        // functions off and start this one.
759
        if (state() == Active && !(isChildOfSoloFrame() && f->startedAsChild()))
6✔
760
        {
761
            f->stop(functionParent());
3✔
762
            resetIntensityOverrideAttribute();
3✔
763
        }
764
        else
765
        {
766
            adjustFunctionIntensity(f, intensity());
3✔
767

768
            // starting a Chaser is a special case, since it is necessary
769
            // to use Chaser Actions to properly start the first
770
            // Chaser step with the right intensity
771
            if (f->type() == Function::ChaserType || f->type() == Function::SequenceType)
3✔
772
            {
773
                ChaserAction action;
774
                action.m_action = ChaserSetStepIndex;
×
775
                action.m_stepIndex = 0;
×
776
                action.m_masterIntensity = intensity();
×
777
                action.m_stepIntensity = 1.0;
×
778
                action.m_fadeMode = Chaser::FromFunction;
×
779

780
                Chaser *chaser = qobject_cast<Chaser*>(f);
×
781
                chaser->setAction(action);
×
782
            }
783

784
            f->start(m_doc->masterTimer(), functionParent());
3✔
785
            setState(Active);
3✔
786
            emit functionStarting(m_function);
3✔
787
        }
788
    }
789
    else if (m_action == Flash && state() == Inactive)
7✔
790
    {
791
        f = m_doc->function(m_function);
4✔
792
        if (f != NULL)
4✔
793
        {
794
            adjustFunctionIntensity(f, intensity());
4✔
795
            f->flash(m_doc->masterTimer(), flashOverrides(), flashForceLTP());
4✔
796
            setState(Active);
4✔
797
        }
798
    }
799
    else if (m_action == Blackout)
3✔
800
    {
801
        m_doc->inputOutputMap()->toggleBlackout();
2✔
802
    }
803
    else if (m_action == StopAll)
1✔
804
    {
805
        if (stopAllFadeTime() == 0)
1✔
806
            m_doc->masterTimer()->stopAllFunctions();
1✔
807
        else
808
            m_doc->masterTimer()->fadeAndStopAll(stopAllFadeTime());
×
809
    }
810
}
811

812
FunctionParent VCButton::functionParent() const
6✔
813
{
814
    return FunctionParent(FunctionParent::ManualVCWidget, id());
6✔
815
}
816

817
void VCButton::releaseFunction()
6✔
818
{
819
    /* Don't allow operation during design mode */
820
    if (mode() == Doc::Design)
6✔
821
        return;
×
822

823
    if (m_action == Flash && state() == Active)
6✔
824
    {
825
        Function* f = m_doc->function(m_function);
4✔
826
        if (f != NULL)
4✔
827
        {
828
            f->unFlash(m_doc->masterTimer());
4✔
829
            resetIntensityOverrideAttribute();
4✔
830
            setState(Inactive);
4✔
831
        }
832
    }
833
}
834

835
void VCButton::slotFunctionRunning(quint32 fid)
5✔
836
{
837
    if (fid == m_function && m_action == Toggle)
5✔
838
    {
839
        if (state() == Inactive)
3✔
840
            setState(Monitoring);
×
841
        emit functionStarting(m_function);
3✔
842
    }
843
}
5✔
844

845
void VCButton::slotFunctionStopped(quint32 fid)
6✔
846
{
847
    if (fid == m_function && m_action == Toggle)
6✔
848
    {
849
        resetIntensityOverrideAttribute();
3✔
850
        setState(Inactive);
3✔
851
        blink(250);
3✔
852
    }
853
}
6✔
854

855
void VCButton::slotFunctionFlashing(quint32 fid, bool state)
11✔
856
{
857
    // Do not change the state of the button for Blackout or Stop All Functions buttons
858
    if (m_action != Toggle && m_action != Flash)
11✔
859
        return;
×
860

861
    if (fid != m_function)
11✔
862
        return;
1✔
863

864
    // if the function was flashed by another button, and the function is still running, keep the button pushed
865
    Function* f = m_doc->function(m_function);
10✔
866
    if (state == false && m_action == Toggle && f != NULL && f->isRunning())
10✔
867
    {
868
        return;
1✔
869
    }
870

871
    setState(state ? Active : Inactive);
9✔
872
}
873

874
void VCButton::blink(int ms)
3✔
875
{
876
    slotBlink();
3✔
877
    QTimer::singleShot(ms, this, SLOT(slotBlink()));
3✔
878
}
3✔
879

880
void VCButton::slotBlink()
4✔
881
{
882
    // This function is called twice with same XOR mask,
883
    // thus creating a brief opposite-color -- normal-color blink
884
    // QPalette pal = palette();
885
    // QColor color(pal.color(QPalette::Button));
886
    // color.setRgb(color.red()^0xff, color.green()^0xff, color.blue()^0xff);
887
    // pal.setColor(QPalette::Button, color);
888
    // setPalette(pal);
889
}
4✔
890

891
void VCButton::slotBlackoutChanged(bool state)
2✔
892
{
893
    setState(state ? Active : Inactive);
2✔
894
}
2✔
895

896
bool VCButton::isChildOfSoloFrame() const
3✔
897
{
898
    QWidget* parent = parentWidget();
3✔
899
    while (parent != NULL)
6✔
900
    {
901
        if (qobject_cast<VCSoloFrame*>(parent) != NULL)
3✔
902
            return true;
×
903
        parent = parent->parentWidget();
3✔
904
    }
905
    return false;
3✔
906
}
907

908
/*****************************************************************************
909
 * Custom menu
910
 *****************************************************************************/
911

912
QMenu* VCButton::customMenu(QMenu* parentMenu)
2✔
913
{
914
    QMenu* menu = new QMenu(parentMenu);
2✔
915
    menu->setTitle(tr("Icon"));
2✔
916
    menu->addAction(m_chooseIconAction);
2✔
917
    menu->addAction(m_resetIconAction);
2✔
918

919
    return menu;
2✔
920
}
921

922
void VCButton::adjustIntensity(qreal val)
×
923
{
924
    if (state() == Active)
×
925
    {
926
        Function* func = m_doc->function(m_function);
×
927
        if (func != NULL)
×
928
            adjustFunctionIntensity(func, val);
×
929
    }
930

931
    VCWidget::adjustIntensity(val);
×
932
}
×
933

934
/*****************************************************************************
935
 * Load & Save
936
 *****************************************************************************/
937

938
bool VCButton::loadXML(QXmlStreamReader &root)
4✔
939
{
940
    bool visible = false;
4✔
941
    int x = 0;
4✔
942
    int y = 0;
4✔
943
    int w = 0;
4✔
944
    int h = 0;
4✔
945

946
    if (root.name() != KXMLQLCVCButton)
4✔
947
    {
948
        qWarning() << Q_FUNC_INFO << "Button node not found";
1✔
949
        return false;
1✔
950
    }
951

952
    /* Widget commons */
953
    loadXMLCommon(root);
3✔
954

955
    /* Icon */
956
    setIconPath(m_doc->denormalizeComponentPath(root.attributes().value(KXMLQLCVCButtonIcon).toString()));
6✔
957

958
    /* Children */
959
    while (root.readNextStartElement())
19✔
960
    {
961
        //qDebug() << "VC Button tag:" << root.name();
962
        if (root.name() == KXMLQLCWindowState)
16✔
963
        {
964
            loadXMLWindowState(root, &x, &y, &w, &h, &visible);
2✔
965
            setGeometry(x, y, w, h);
2✔
966
        }
967
        else if (root.name() == KXMLQLCVCWidgetAppearance)
14✔
968
        {
969
            loadXMLAppearance(root);
2✔
970
        }
971
        else if (root.name() == KXMLQLCVCButtonFunction)
12✔
972
        {
973
            QString str = root.attributes().value(KXMLQLCVCButtonFunctionID).toString();
4✔
974
            setFunction(str.toUInt());
2✔
975
            root.skipCurrentElement();
2✔
976
        }
2✔
977
        else if (root.name() == KXMLQLCVCWidgetInput)
10✔
978
        {
979
            loadXMLInput(root);
2✔
980
        }
981
        else if (root.name() == KXMLQLCVCButtonAction)
8✔
982
        {
983
            QXmlStreamAttributes attrs = root.attributes();
2✔
984
            setAction(stringToAction(root.readElementText()));
2✔
985
            if (attrs.hasAttribute(KXMLQLCVCButtonStopAllFadeTime))
2✔
986
                setStopAllFadeOutTime(attrs.value(KXMLQLCVCButtonStopAllFadeTime).toString().toInt());
×
987

988
            if (attrs.hasAttribute(KXMLQLCVCButtonFlashOverride))
2✔
989
                setFlashOverride(attrs.value(KXMLQLCVCButtonFlashOverride).toInt());
×
990

991
            if (attrs.hasAttribute(KXMLQLCVCButtonFlashForceLTP))
2✔
992
                setFlashForceLTP(attrs.value(KXMLQLCVCButtonFlashForceLTP).toInt());
×
993
        }
2✔
994
        else if (root.name() == KXMLQLCVCButtonKey)
6✔
995
        {
996
            setKeySequence(stripKeySequence(QKeySequence(root.readElementText())));
2✔
997
        }
998
        else if (root.name() == KXMLQLCVCButtonIntensity)
4✔
999
        {
1000
            bool adjust;
1001
            if (root.attributes().value(KXMLQLCVCButtonIntensityAdjust).toString() == KXMLQLCTrue)
4✔
1002
                adjust = true;
1✔
1003
            else
1004
                adjust = false;
1✔
1005
            setStartupIntensity(qreal(root.readElementText().toInt()) / qreal(100));
2✔
1006
            enableStartupIntensity(adjust);
2✔
1007
        }
1008
        else
1009
        {
1010
            qWarning() << Q_FUNC_INFO << "Unknown button tag:" << root.name().toString();
2✔
1011
            root.skipCurrentElement();
2✔
1012
        }
1013
    }
1014

1015
    /* All buttons start raised... */
1016
    setState(Inactive);
3✔
1017

1018
    return true;
3✔
1019
}
1020

1021
bool VCButton::saveXML(QXmlStreamWriter *doc)
1✔
1022
{
1023
    Q_ASSERT(doc != NULL);
1✔
1024

1025
    /* VC button entry */
1026
    doc->writeStartElement(KXMLQLCVCButton);
2✔
1027

1028
    saveXMLCommon(doc);
1✔
1029

1030
    /* Icon */
1031
    doc->writeAttribute(KXMLQLCVCButtonIcon, m_doc->normalizeComponentPath(iconPath()));
2✔
1032

1033
    /* Window state */
1034
    saveXMLWindowState(doc);
1✔
1035

1036
    /* Appearance */
1037
    saveXMLAppearance(doc);
1✔
1038

1039
    /* Function */
1040
    doc->writeStartElement(KXMLQLCVCButtonFunction);
2✔
1041
    doc->writeAttribute(KXMLQLCVCButtonFunctionID, QString::number(function()));
2✔
1042
    doc->writeEndElement();
1✔
1043

1044
    /* Action */
1045
    doc->writeStartElement(KXMLQLCVCButtonAction);
2✔
1046

1047
    if (action() == StopAll && stopAllFadeTime() != 0)
1✔
1048
    {
1049
        doc->writeAttribute(KXMLQLCVCButtonStopAllFadeTime, QString::number(stopAllFadeTime()));
×
1050
    }
1051
    else if (action() == Flash)
1✔
1052
    {
1053
        doc->writeAttribute(KXMLQLCVCButtonFlashOverride, QString::number(flashOverrides()));
2✔
1054
        doc->writeAttribute(KXMLQLCVCButtonFlashForceLTP, QString::number(flashForceLTP()));
2✔
1055
    }
1056
    doc->writeCharacters(actionToString(action()));
1✔
1057
    doc->writeEndElement();
1✔
1058

1059
    /* Key sequence */
1060
    if (m_keySequence.isEmpty() == false)
1✔
1061
        doc->writeTextElement(KXMLQLCVCButtonKey, m_keySequence.toString());
2✔
1062

1063
    /* Intensity adjustment */
1064
    doc->writeStartElement(KXMLQLCVCButtonIntensity);
2✔
1065
    doc->writeAttribute(KXMLQLCVCButtonIntensityAdjust,
1✔
1066
                     isStartupIntensityEnabled() ? KXMLQLCTrue : KXMLQLCFalse);
1✔
1067
    doc->writeCharacters(QString::number(int(startupIntensity() * 100)));
1✔
1068
    doc->writeEndElement();
1✔
1069

1070
    /* External input */
1071
    saveXMLInput(doc);
1✔
1072

1073
    /* End the <Button> tag */
1074
    doc->writeEndElement();
1✔
1075

1076
    return true;
1✔
1077
}
1078

1079
/*****************************************************************************
1080
 * Event handlers
1081
 *****************************************************************************/
1082

1083
void VCButton::paintEvent(QPaintEvent* e)
8✔
1084
{
1085
    QStyleOptionButton option;
8✔
1086
    option.initFrom(this);
8✔
1087

1088
    /* This should look like a normal button */
1089
    option.features = QStyleOptionButton::None;
8✔
1090

1091
    /* Sunken or raised based on state() status */
1092
    if (state() == Inactive)
8✔
1093
        option.state = QStyle::State_Raised;
7✔
1094
    else
1095
        option.state = QStyle::State_Sunken;
1✔
1096

1097
    /* Custom icons are always enabled, to see them in full color also in design mode */
1098
    if (m_action == Toggle || m_action == Flash)
8✔
1099
        option.state |= QStyle::State_Enabled;
8✔
1100

1101
    /* Icon */
1102
    option.icon = m_icon;
8✔
1103
    option.iconSize = m_iconSize;
8✔
1104

1105
    /* Paint the button */
1106
    QPainter painter(this);
8✔
1107
    painter.setRenderHint(QPainter::Antialiasing);
8✔
1108

1109
    style()->drawControl(QStyle::CE_PushButton, &option, &painter, this);
8✔
1110

1111
    if (m_backgroundImage.isEmpty() == false)
8✔
1112
    {
1113
        QRect pxRect = m_bgPixmap.rect();
×
1114
        // if the pixmap is bigger than the button, then paint a scaled version of it
1115
        // covering the whole button surface
1116
        // if the pixmap is smaller than the button, draw a centered pixmap
1117
        if (pxRect.contains(rect()))
×
1118
        {
1119
            if (m_ledStyle == true)
×
1120
                painter.drawPixmap(rect(), m_bgPixmap);
×
1121
            else
1122
                painter.drawPixmap(3, 3, width() - 6, height() - 6, m_bgPixmap);
×
1123
        }
1124
        else
1125
        {
1126
            painter.drawPixmap((width() - pxRect.width()) / 2,
×
1127
                               (height() - pxRect.height()) / 2,
×
1128
                               m_bgPixmap);
×
1129
        }
1130
    }
1131

1132
    /* Paint caption with text wrapping */
1133
    if (caption().isEmpty() == false)
8✔
1134
    {
1135
        style()->drawItemText(&painter,
9✔
1136
                              rect(),
3✔
1137
                              Qt::AlignCenter | Qt::TextWordWrap,
1138
                              palette(),
1139
                              (mode() == Doc::Operate),
3✔
1140
                              caption());
6✔
1141
    }
1142

1143
    /* Flash emblem */
1144
    if (m_action == Flash)
8✔
1145
    {
1146
        QIcon icon(":/flash.png");
2✔
1147
        painter.drawPixmap(rect().width() - 18, 2,
2✔
1148
                           icon.pixmap(QSize(16, 16), QIcon::Normal, QIcon::On));
4✔
1149
    }
2✔
1150

1151
    if (m_ledStyle == true)
8✔
1152
    {
1153
        painter.setPen(QPen(QColor(160, 160, 160, 255), 2));
×
1154

1155
        if (state() == Active)
×
1156
        {
1157
            if (m_flashForceLTP || m_flashOverrides)
×
1158
                painter.setBrush(QBrush(QColor(230, 0, 0, 255)));
×
1159
            else
1160
                painter.setBrush(QBrush(QColor(0, 230, 0, 255)));
×
1161
        }
1162
        else if (state() == Monitoring)
×
1163
            painter.setBrush(QBrush(QColor(255, 170, 0, 255)));
×
1164
        else
1165
            painter.setBrush(QBrush(QColor(110, 110, 110, 255)));
×
1166

1167
        int dim = rect().width() / 6;
×
1168
        if (dim > 14) dim = 14;
×
1169

1170
        painter.drawEllipse(6, 6, dim, dim);      // Style #1
×
1171
        //painter.drawRoundedRect(-1, -1, dim, dim, 3, 3);   // Style #2
1172
    }
1173
    else
1174
    {
1175
        // Style #3
1176
        painter.setBrush(Qt::NoBrush);
8✔
1177

1178
        if (state() != Inactive)
8✔
1179
        {
1180
            int borderWidth = (rect().width() > 80)?3:2;
1✔
1181
            painter.setPen(QPen(QColor(20, 20, 20, 255), borderWidth * 2));
1✔
1182
            painter.drawRoundedRect(borderWidth, borderWidth,
3✔
1183
                                    rect().width() - borderWidth * 2, rect().height() - (borderWidth * 2),
1✔
1184
                                    borderWidth + 1,  borderWidth + 1);
1✔
1185
            if (state() == Monitoring)
1✔
1186
                painter.setPen(QPen(QColor(255, 170, 0, 255), borderWidth));
×
1187
            else
1188
            {
1189
                if (m_flashForceLTP || m_flashOverrides)
1✔
1190
                    painter.setPen(QPen(QColor(230, 0, 0, 255), borderWidth));
×
1191
                else
1192
                    painter.setPen(QPen(QColor(0, 230, 0, 255), borderWidth));
1✔
1193
            }
1194
            painter.drawRoundedRect(borderWidth, borderWidth,
3✔
1195
                                    rect().width() - borderWidth * 2, rect().height() - (borderWidth * 2),
1✔
1196
                                    borderWidth, borderWidth);
1197
        }
1198
        else
1199
        {
1200
            painter.setPen(QPen(QColor(160, 160, 160, 255), 3));
7✔
1201
            painter.drawRoundedRect(1, 1, rect().width() - 2, rect().height() - 2, 3, 3);
7✔
1202
        }
1203
    }
1204

1205
    /* Stop painting here */
1206
    painter.end();
8✔
1207

1208
    /* Draw a selection frame if appropriate */
1209
    VCWidget::paintEvent(e);
8✔
1210
}
8✔
1211

1212
void VCButton::mousePressEvent(QMouseEvent* e)
2✔
1213
{
1214
    if (mode() == Doc::Design)
2✔
1215
        VCWidget::mousePressEvent(e);
1✔
1216
    else if (e->button() == Qt::LeftButton)
1✔
1217
        pressFunction();
1✔
1218
#if 0
1219
    else if (e->button() == Qt::RightButton)
1220
    {
1221
        Function* func = m_doc->function(m_function);
1222
        if (func != NULL)
1223
        {
1224
            QString menuStyle = "QMenu { background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #B9D9E8, stop:1 #A4C0CE);"
1225
                            "border: 1px solid black; border-radius: 4px; font:bold; }";
1226
            QMenu *menu = new QMenu();
1227
            menu->setStyleSheet(menuStyle);
1228
            int idx = 0;
1229
            foreach (Attribute attr, func->attributes())
1230
            {
1231
                QString slStyle = "QSlider::groove:horizontal { border: 1px solid #999999; margin: 0; border-radius: 2px;"
1232
                        "height: 15px; background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #B1B1B1, stop:1 #c4c4c4); }"
1233

1234
                        "QSlider::handle:horizontal {"
1235
                        "background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f);"
1236
                        "border: 1px solid #5c5c5c; width: 15px; border-radius: 2px; margin: -1px 0; }"
1237

1238
                        "QSlider::sub-page:horizontal { background: #114EA2; border-radius: 2px; }";
1239

1240
                QWidget *entryWidget = new QWidget();
1241
                QHBoxLayout *hbox = new QHBoxLayout(menu);
1242
                hbox->setMargin(3);
1243
                QLabel *label = new QLabel(attr.m_name);
1244
                label->setAlignment(Qt::AlignLeft);
1245
                label->setFixedWidth(100);
1246
                ClickAndGoSlider *slider = new ClickAndGoSlider(menu);
1247
                slider->setOrientation(Qt::Horizontal);
1248
                slider->setSliderStyleSheet(slStyle);
1249
                slider->setFixedSize(QSize(100, 18));
1250
                slider->setMinimum(0);
1251
                slider->setMaximum(100);
1252
                slider->setValue(attr.m_value * 100);
1253
                slider->setProperty("attrIdx", QVariant(idx));
1254
                connect(slider, SIGNAL(valueChanged(int)), this, SLOT(slotAttributeChanged(int)));
1255
                hbox->addWidget(label);
1256
                hbox->addWidget(slider);
1257
                entryWidget->setLayout(hbox);
1258
                QWidgetAction *sliderBoxAction = new QWidgetAction(menu);
1259
                sliderBoxAction->setDefaultWidget(entryWidget);
1260
                menu->addAction(sliderBoxAction);
1261
                idx++;
1262
            }
1263
            menu->exec(QCursor::pos());
1264
        }
1265
    }
1266
#endif
1267
}
2✔
1268

1269
void VCButton::mouseReleaseEvent(QMouseEvent* e)
2✔
1270
{
1271
    if (mode() == Doc::Design)
2✔
1272
        VCWidget::mouseReleaseEvent(e);
1✔
1273
    else
1274
        releaseFunction();
1✔
1275
}
2✔
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