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

mcallegari / qlcplus / 19144422256

06 Nov 2025 05:33PM UTC coverage: 34.256% (-0.1%) from 34.358%
19144422256

push

github

mcallegari
Back to 5.1.0 debug

17718 of 51723 relevant lines covered (34.26%)

19528.23 hits per line

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

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

5
  Copyright (c) Massimo Callegari
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 <QXmlStreamReader>
21
#include <QXmlStreamWriter>
22
#include <QDateTime>
23
#include <QStyle>
24
#include <QtGui>
25

26
#include "vcclockproperties.h"
27
#include "vcclock.h"
28
#include "doc.h"
29

30
#define HYSTERESIS 3 // Hysteresis for pause/reset external input
31

32
#define KXMLQLCVCClockType      QStringLiteral("Type")
33
#define KXMLQLCVCClockHours     QStringLiteral("Hours")
34
#define KXMLQLCVCClockMinutes   QStringLiteral("Minutes")
35
#define KXMLQLCVCClockSeconds   QStringLiteral("Seconds")
36

37
#define KXMLQLCVCClockSchedule      QStringLiteral("Schedule")
38
#define KXMLQLCVCClockScheduleFunc  QStringLiteral("Function")
39
#define KXMLQLCVCClockScheduleTime  QStringLiteral("Time")
40

41
#define KXMLQLCVCClockPlay  QStringLiteral("PlayPause")
42
#define KXMLQLCVCClockReset QStringLiteral("Reset")
43

44
const quint8 VCClock::playInputSourceId = 0;
45
const quint8 VCClock::resetInputSourceId = 1;
46

47
VCClock::VCClock(QWidget* parent, Doc* doc)
×
48
    : VCWidget(parent, doc)
49
    , m_clocktype(Clock)
×
50
    , m_scheduleIndex(-1)
×
51
    , m_hh(0)
×
52
    , m_mm(0)
×
53
    , m_ss(0)
×
54
    , m_targetTime(0)
×
55
    , m_currentTime(0)
×
56
    , m_isPaused(true)
×
57
{
58
    /* Set the class name "VCClock" as the object name as well */
59
    setObjectName(VCClock::staticMetaObject.className());
×
60

61
    setType(VCWidget::ClockWidget);
×
62
    setCaption("");
×
63
    resize(QSize(150, 50));
×
64
    QFont font = qApp->font();
×
65
    font.setBold(true);
×
66
    font.setPixelSize(28);
×
67
    setFont(font);
×
68

69
    QTimer *timer = new QTimer(this);
×
70
    connect(timer, SIGNAL(timeout()), this, SLOT(slotUpdateTime()));
×
71
    timer->start(1000);
×
72
}
×
73

74
VCClock::~VCClock()
×
75
{
76
}
×
77

78
void VCClock::slotModeChanged(Doc::Mode mode)
×
79
{
80
    qDebug() << Q_FUNC_INFO;
×
81

82
    if (mode == Doc::Operate)
×
83
    {
84
        m_scheduleIndex = -1;
×
85

86
        if (m_scheduleList.count() > 0)
×
87
        {
88
            QTime currTime = QDateTime::currentDateTime().time();
×
89

90
            // find the index of the next scheduled event to run
91
            for (int i = 0; i < m_scheduleList.count(); i++)
×
92
            {
93
                VCClockSchedule sch = m_scheduleList.at(i);
×
94
                if (sch.time().time() >= currTime)
×
95
                {
96
                    m_scheduleIndex = i;
×
97
                    qDebug() << "VC Clock set to play index:" << i;
×
98
                    break;
×
99
                }
100
            }
×
101
            // if no event is found after the current time, it means the next schedule 
102
            // will happen the day after so it's the first in the list
103
            if (m_scheduleIndex == -1)
×
104
                m_scheduleIndex = 0;
×
105
        }
106
    }
107
    VCWidget::slotModeChanged(mode);
×
108
}
×
109

110
/*********************************************************************
111
 * Type
112
 *********************************************************************/
113

114
void VCClock::setClockType(VCClock::ClockType type)
×
115
{
116
    m_clocktype = type;
×
117
    updateFeedback();
×
118
    update();
×
119
}
×
120

121
VCClock::ClockType VCClock::clockType() const
×
122
{
123
    return m_clocktype;
×
124
}
125

126
QString VCClock::typeToString(VCClock::ClockType type) const
×
127
{
128
    if (type == Stopwatch)
×
129
        return "Stopwatch";
×
130
    else if (type == Countdown)
×
131
        return "Countdown";
×
132
    else
133
        return "Clock";
×
134
}
135

136
VCClock::ClockType VCClock::stringToType(QString str) const
×
137
{
138
    if (str == "Stopwatch")
×
139
        return Stopwatch;
×
140
    else if (str == "Countdown")
×
141
        return Countdown;
×
142
    else
143
        return Clock;
×
144
}
145

146
void VCClock::addSchedule(VCClockSchedule schedule)
×
147
{
148
    qDebug() << Q_FUNC_INFO << "--- ID:" << schedule.function() << ", time:" << schedule.time().time().toString();
×
149
    if (schedule.function() != Function::invalidId())
×
150
        m_scheduleList.append(schedule);
×
151
    std::sort(m_scheduleList.begin(), m_scheduleList.end());
×
152
}
×
153

154
void VCClock::removeSchedule(int index)
×
155
{
156
    if (index < 0 || index > m_scheduleList.count())
×
157
        return;
×
158

159
    m_scheduleList.removeAt(index);
×
160
}
161

162
void VCClock::removeAllSchedule()
×
163
{
164
    m_scheduleList.clear();
×
165
}
×
166

167
QList<VCClockSchedule> VCClock::schedules() const
×
168
{
169
    return m_scheduleList;
×
170
}
171

172
FunctionParent VCClock::functionParent() const
×
173
{
174
    return FunctionParent(FunctionParent::AutoVCWidget, id());
×
175
}
176

177
void VCClock::setCountdown(int h, int m, int s)
×
178
{
179
    m_hh = h;
×
180
    m_mm = m;
×
181
    m_ss = s;
×
182
    m_targetTime = (m_hh * 3600) + (m_mm * 60) + m_ss;
×
183
    m_currentTime = m_targetTime;
×
184
}
×
185

186
void VCClock::slotUpdateTime()
×
187
{
188
    if (mode() == Doc::Operate)
×
189
    {
190
        if (m_isPaused == false)
×
191
        {
192
            if (m_clocktype == Stopwatch)
×
193
            {
194
                m_currentTime++;
×
195
            }
196
            else if (m_clocktype == Countdown && m_currentTime > 0)
×
197
            {
198
                m_currentTime--;
×
199

200
                if (m_currentTime == 0)
×
201
                {
202
                    for (int i = 0; i < m_scheduleList.count(); i++)
×
203
                    {
204
                        VCClockSchedule sch = m_scheduleList.at(i);
×
205
                        quint32 fid = sch.function();
×
206
                        Function *func = m_doc->function(fid);
×
207
                        if (func != NULL)
×
208
                        {
209
                            func->start(m_doc->masterTimer(), functionParent());
×
210
                            qDebug() << "VC Clock starting function:" << func->name();
×
211
                        }
212
                    }
×
213
                }
214
            }
215

216
            emit timeChanged(m_currentTime);
×
217
        }
218
        else
219
        {
220
            if (m_clocktype == Clock)
×
221
            {
222
                if (m_scheduleIndex != -1 && m_scheduleIndex < m_scheduleList.count())
×
223
                {
224
                    QTime currTime = QDateTime::currentDateTime().time();
×
225
                    VCClockSchedule sch = m_scheduleList.at(m_scheduleIndex);
×
226
                    //qDebug() << "--- > currTime:" << currTime.toString() << ", schTime:" << sch.time().time().toString();
227
                    if (sch.time().time().toString() == currTime.toString())
×
228
                    {
229
                        quint32 fid = sch.function();
×
230
                        Function *func = m_doc->function(fid);
×
231
                        if (func != NULL)
×
232
                        {
233
                            func->start(m_doc->masterTimer(), functionParent());
×
234
                            qDebug() << "VC Clock starting function:" << func->name();
×
235
                        }
236
                        m_scheduleIndex++;
×
237
                        if (m_scheduleIndex == m_scheduleList.count())
×
238
                            m_scheduleIndex = 0;
×
239
                    }
240
                }
×
241
            }
242
        }
243
    }
244
    updateFeedback();
×
245
    update();
×
246
}
×
247

248
void VCClock::resetTimer()
×
249
{
250
    if (clockType() == Stopwatch)
×
251
        m_currentTime = 0;
×
252
    else if (clockType() == Countdown)
×
253
        m_currentTime = m_targetTime;
×
254

255
    emit timeChanged(m_currentTime);
×
256

257
    updateFeedback();
×
258
    update();
×
259
}
×
260

261
void VCClock::playPauseTimer()
×
262
{
263
    if (clockType() == Stopwatch || clockType() == Countdown)
×
264
        m_isPaused = !m_isPaused;
×
265

266
    updateFeedback();
×
267
    update();
×
268
}
×
269

270
/*****************************************************************************
271
 * Key Sequences
272
 *****************************************************************************/
273

274
void VCClock::setPlayKeySequence(const QKeySequence& keySequence)
×
275
{
276
    m_playKeySequence = QKeySequence(keySequence);
×
277
}
×
278

279
QKeySequence VCClock::playKeySequence() const
×
280
{
281
    return m_playKeySequence;
×
282
}
283

284
void VCClock::setResetKeySequence(const QKeySequence& keySequence)
×
285
{
286
    m_resetKeySequence = QKeySequence(keySequence);
×
287
}
×
288

289
QKeySequence VCClock::resetKeySequence() const
×
290
{
291
    return m_resetKeySequence;
×
292
}
293

294
void VCClock::slotKeyPressed(const QKeySequence& keySequence)
×
295
{
296
    if (acceptsInput() == false)
×
297
        return;
×
298

299
    if (m_playKeySequence == keySequence)
×
300
        playPauseTimer();
×
301
    else if (m_resetKeySequence == keySequence)
×
302
        resetTimer();
×
303
}
304

305
void VCClock::updateFeedback()
×
306
{
307
    if (clockType() == Stopwatch)
×
308
    {
309
        sendFeedback(!m_isPaused ? UCHAR_MAX : 0, playInputSourceId);
×
310
        sendFeedback(m_currentTime == 0 ? UCHAR_MAX : 0, resetInputSourceId);
×
311
    }
312
    else if (clockType() == Countdown)
×
313
    {
314
        sendFeedback(!m_isPaused ? UCHAR_MAX : 0, playInputSourceId);
×
315
        sendFeedback(m_currentTime == m_targetTime ? UCHAR_MAX : 0, resetInputSourceId);
×
316
    }
317
    else
318
    {
319
        sendFeedback(0, playInputSourceId);
×
320
        sendFeedback(0, resetInputSourceId);
×
321
    }
322
}
×
323

324
/*****************************************************************************
325
 * External Input
326
 *****************************************************************************/
327

328
void VCClock::slotInputValueChanged(quint32 universe, quint32 channel, uchar value)
×
329
{
330
    /* Don't let input data through in design mode or if disabled */
331
    if (acceptsInput() == false)
×
332
        return;
×
333

334
    quint32 pagedCh = (page() << 16) | channel;
×
335

336
    if (checkInputSource(universe, pagedCh, value, sender(), playInputSourceId))
×
337
    {
338
        // Use hysteresis for values, in case the timer is being controlled
339
        // by a slider. The value has to go to zero before the next non-zero
340
        // value is accepted as input. And the non-zero values have to visit
341
        // above $HYSTERESIS before a zero is accepted again.
342
        if (m_playLatestValue == 0 && value > 0)
×
343
        {
344
            playPauseTimer();
×
345
            m_playLatestValue = value;
×
346
        }
347
        else if (m_playLatestValue > HYSTERESIS && value == 0)
×
348
        {
349
            m_playLatestValue = 0;
×
350
        }
351

352
        if (value > HYSTERESIS)
×
353
            m_playLatestValue = value;
×
354
    }
355
    else if (checkInputSource(universe, pagedCh, value, sender(), resetInputSourceId))
×
356
    {
357
        // Use hysteresis for values, in case the timer is being controlled
358
        // by a slider. The value has to go to zero before the next non-zero
359
        // value is accepted as input. And the non-zero values have to visit
360
        // above $HYSTERESIS before a zero is accepted again.
361
        if (m_resetLatestValue == 0 && value > 0)
×
362
        {
363
            resetTimer();
×
364
            m_resetLatestValue = value;
×
365
        }
366
        else if (m_resetLatestValue > HYSTERESIS && value == 0)
×
367
        {
368
            m_resetLatestValue = 0;
×
369
        }
370

371
        if (value > HYSTERESIS)
×
372
            m_resetLatestValue = value;
×
373
    }
374
}
375

376
/*****************************************************************************
377
 * Clipboard
378
 *****************************************************************************/
379

380
VCWidget* VCClock::createCopy(VCWidget* parent)
×
381
{
382
    Q_ASSERT(parent != NULL);
×
383

384
    VCClock* clock = new VCClock(parent, m_doc);
×
385
    if (clock->copyFrom(this) == false)
×
386
    {
387
        delete clock;
×
388
        clock = NULL;
×
389
    }
390

391
    return clock;
×
392
}
393

394
bool VCClock::copyFrom(const VCWidget* widget)
×
395
{
396
    const VCClock* clock = qobject_cast<const VCClock*> (widget);
×
397
    if (clock == NULL)
×
398
        return false;
×
399

400
    // TODO: copy schedules
401

402
    /* Clock type */
403
    setClockType(clock->clockType());
×
404

405
    /* Key sequence */
406
    setPlayKeySequence(clock->playKeySequence());
×
407
    setResetKeySequence(clock->resetKeySequence());
×
408

409
    /* Common stuff */
410
    return VCWidget::copyFrom(widget);
×
411
}
412

413
/*****************************************************************************
414
 * Properties
415
 *****************************************************************************/
416

417
void VCClock::editProperties()
×
418
{
419
    VCClockProperties vccp(this, m_doc);
×
420
    if (vccp.exec() == QDialog::Rejected)
×
421
        return;
×
422

423
    m_doc->setModified();
×
424
}
×
425

426
/*****************************************************************************
427
 * Load & Save
428
 *****************************************************************************/
429

430
bool VCClock::loadXML(QXmlStreamReader &root)
×
431
{
432
    if (root.name() != KXMLQLCVCClock)
×
433
    {
434
        qWarning() << Q_FUNC_INFO << "Clock node not found";
×
435
        return false;
×
436
    }
437

438
    QXmlStreamAttributes attrs = root.attributes();
×
439

440
    if (attrs.hasAttribute(KXMLQLCVCClockType))
×
441
    {
442
        setClockType(stringToType(attrs.value(KXMLQLCVCClockType).toString()));
×
443
        if (clockType() == Countdown)
×
444
        {
445
            int h = 0, m = 0, s = 0;
×
446
            if (attrs.hasAttribute(KXMLQLCVCClockHours))
×
447
                h = attrs.value(KXMLQLCVCClockHours).toString().toInt();
×
448
            if (attrs.hasAttribute(KXMLQLCVCClockMinutes))
×
449
                m = attrs.value(KXMLQLCVCClockMinutes).toString().toInt();
×
450
            if (attrs.hasAttribute(KXMLQLCVCClockSeconds))
×
451
                s = attrs.value(KXMLQLCVCClockSeconds).toString().toInt();
×
452
            setCountdown(h, m ,s);
×
453
        }
454
    }
455

456
    /* Widget commons */
457
    loadXMLCommon(root);
×
458

459
    /* Children */
460
    while (root.readNextStartElement())
×
461
    {
462
        if (root.name() == KXMLQLCWindowState)
×
463
        {
464
            int x = 0, y = 0, w = 0, h = 0;
×
465
            bool visible = false;
×
466
            loadXMLWindowState(root, &x, &y, &w, &h, &visible);
×
467
            setGeometry(x, y, w, h);
×
468
        }
469
        else if (root.name() == KXMLQLCVCWidgetAppearance)
×
470
        {
471
            loadXMLAppearance(root);
×
472
        }
473
        else if (root.name() == KXMLQLCVCClockSchedule)
×
474
        {
475
            VCClockSchedule sch;
×
476
            if (sch.loadXML(root) == true)
×
477
                addSchedule(sch);
×
478
        }
×
479
        else if (root.name() == KXMLQLCVCClockPlay)
×
480
        {
481
            QString str = loadXMLSources(root, playInputSourceId);
×
482
            if (str.isEmpty() == false)
×
483
                m_playKeySequence = stripKeySequence(QKeySequence(str));
×
484
        }else if (root.name() == KXMLQLCVCClockReset)
×
485
        {
486
            QString str = loadXMLSources(root, resetInputSourceId);
×
487
            if (str.isEmpty() == false)
×
488
                m_resetKeySequence = stripKeySequence(QKeySequence(str));
×
489
        }
×
490
        else
491
        {
492
            qWarning() << Q_FUNC_INFO << "Unknown clock tag:" << root.name().toString();
×
493
            root.skipCurrentElement();
×
494
        }
495
    }
496

497
    return true;
×
498
}
×
499

500
bool VCClock::saveXML(QXmlStreamWriter *doc)
×
501
{
502
    Q_ASSERT(doc != NULL);
×
503

504
    /* VC Clock entry */
505
    doc->writeStartElement(KXMLQLCVCClock);
×
506

507
    /* Type */
508
    ClockType type = clockType();
×
509
    doc->writeAttribute(KXMLQLCVCClockType, typeToString(type));
×
510
    if (type == Countdown)
×
511
    {
512
        doc->writeAttribute(KXMLQLCVCClockHours, QString::number(getHours()));
×
513
        doc->writeAttribute(KXMLQLCVCClockMinutes, QString::number(getMinutes()));
×
514
        doc->writeAttribute(KXMLQLCVCClockSeconds, QString::number(getSeconds()));
×
515
    }
516

517
    saveXMLCommon(doc);
×
518

519
    /* Window state */
520
    saveXMLWindowState(doc);
×
521

522
    /* Appearance */
523
    saveXMLAppearance(doc);
×
524

525
    foreach (VCClockSchedule sch, schedules())
×
526
        sch.saveXML(doc);
×
527

528
    if (type != Clock)
×
529
    {
530
        /* Play/Pause */
531
        doc->writeStartElement(KXMLQLCVCClockPlay);
×
532
        if (m_playKeySequence.toString().isEmpty() == false)
×
533
            doc->writeTextElement(KXMLQLCVCWidgetKey, m_playKeySequence.toString());
×
534
        saveXMLInput(doc, inputSource(playInputSourceId));
×
535
        doc->writeEndElement();
×
536

537
        /* Reset */
538
        doc->writeStartElement(KXMLQLCVCClockReset);
×
539
        if (m_resetKeySequence.toString().isEmpty() == false)
×
540
            doc->writeTextElement(KXMLQLCVCWidgetKey, m_resetKeySequence.toString());
×
541
        saveXMLInput(doc, inputSource(resetInputSourceId));
×
542
        doc->writeEndElement();
×
543
    }
544

545
    /* End the <Clock> tag */
546
    doc->writeEndElement();
×
547

548
    return true;
×
549
}
550

551
/****************************************************************************
552
 * Drawing
553
 ****************************************************************************/
554

555
void VCClock::paintEvent(QPaintEvent* e)
×
556
{
557
    QPainter painter(this);
×
558

559
    if (clockType() == Clock)
×
560
    {
561
        QDateTime currTime = QDateTime::currentDateTime();
×
562
        style()->drawItemText(&painter, rect(), Qt::AlignCenter | Qt::TextWordWrap, palette(),
×
563
                              true, currTime.time().toString(), foregroundRole());
×
564
    }
×
565
    else
566
    {
567
        quint32 secTime = m_currentTime;
×
568
        uint h, m;
569

570
        h = secTime / 3600;
×
571
        secTime -= (h * 3600);
×
572

573
        m = secTime / 60;
×
574
        secTime -= (m * 60);
×
575
        style()->drawItemText(&painter, rect(), Qt::AlignCenter | Qt::TextWordWrap, palette(),
×
576
                              true, QString("%1:%2:%3").arg(h, 2, 10, QChar('0'))
×
577
                              .arg(m, 2, 10, QChar('0')).arg(secTime, 2, 10, QChar('0')), foregroundRole());
×
578
    }
579
    painter.end();
×
580

581
    VCWidget::paintEvent(e);
×
582
}
×
583

584
void VCClock::mousePressEvent(QMouseEvent *e)
×
585
{
586
    if (mode() == Doc::Design)
×
587
    {
588
        VCWidget::mousePressEvent(e);
×
589
        return;
×
590
    }
591

592
    if (e->button() == Qt::RightButton)
×
593
    {
594
        resetTimer();
×
595
    }
596
    else if (e->button() == Qt::LeftButton)
×
597
    {
598
        playPauseTimer();
×
599
    }
600
    VCWidget::mousePressEvent(e);
×
601
}
602

603
/*********************************************************************
604
 * VCClockSchedule Class methods
605
 *********************************************************************/
606

607
bool VCClockSchedule::operator <(const VCClockSchedule &sch) const
×
608
{
609
    if (sch.time() < time())
×
610
        return false;
×
611
    return true;
×
612
}
613

614
bool VCClockSchedule::loadXML(QXmlStreamReader &root)
×
615
{
616
    if (root.name() != KXMLQLCVCClockSchedule)
×
617
    {
618
        qWarning() << Q_FUNC_INFO << "Clock Schedule node not found";
×
619
        return false;
×
620
    }
621

622
    QXmlStreamAttributes attrs = root.attributes();
×
623

624
    if (attrs.hasAttribute(KXMLQLCVCClockScheduleFunc))
×
625
    {
626
        setFunction(attrs.value(KXMLQLCVCClockScheduleFunc).toString().toUInt());
×
627
        if (attrs.hasAttribute(KXMLQLCVCClockScheduleTime))
×
628
        {
629
            QDateTime dt;
×
630
            dt.setTime(QTime::fromString(attrs.value(KXMLQLCVCClockScheduleTime).toString(), "HH:mm:ss"));
×
631
            setTime(dt);
×
632
        }
×
633
    }
634
    root.skipCurrentElement();
×
635

636
    return true;
×
637
}
×
638

639
bool VCClockSchedule::saveXML(QXmlStreamWriter *doc)
×
640
{
641
    /* Schedule tag */
642
    doc->writeStartElement(KXMLQLCVCClockSchedule);
×
643

644
    /* Schedule function */
645
    doc->writeAttribute(KXMLQLCVCClockScheduleFunc, QString::number(function()));
×
646
    /* Schedule time */
647
    doc->writeAttribute(KXMLQLCVCClockScheduleTime, time().time().toString());
×
648

649
    doc->writeEndElement();
×
650

651
    return true;
×
652
}
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