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

mcallegari / qlcplus / 21717649520

05 Feb 2026 03:33PM UTC coverage: 33.987% (-0.007%) from 33.994%
21717649520

Pull #1947

github

web-flow
Merge aa4b53ffb into f54ff36b5
Pull Request #1947: ui/clickandgowidget: fix scaling issues on high-DPI screens

14 of 49 new or added lines in 1 file covered. (28.57%)

17634 of 51885 relevant lines covered (33.99%)

19806.73 hits per line

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

5.59
/ui/src/clickandgowidget.cpp
1
/*
2
  Q Light Controller Plus
3
  clickandgowidget.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 <QApplication>
21
#include <QMouseEvent>
22
#include <QPainter>
23
#include <QScreen>
24
#include <qmath.h>
25
#include <QDebug>
26
#include <QImage>
27

28
#include "clickandgowidget.h"
29
#include "qlccapability.h"
30
#include "qlcmacros.h"
31
#include "gradient.h"
32

33
#define CELL_W  150
34
#define CELL_H  45
35
#define TITLE_H  18
36

37
ClickAndGoWidget::ClickAndGoWidget(QWidget *parent)
1✔
38
    : QWidget(parent)
39
    , m_type(ClickAndGo::None)
1✔
40
    , m_width(10)
1✔
41
    , m_height(10)
1✔
42
    , m_cols(0)
1✔
43
    , m_rows(0)
1✔
44
    , m_cellWidth(CELL_W)
1✔
45
    , m_hoverCellIdx(-1)
1✔
46
    , m_cellBarXpos(1)
1✔
47
    , m_cellBarYpos(1)
1✔
48
    , m_cellBarWidth(0)
1✔
49
    , m_levelLowLimit(0)
1✔
50
    , m_levelHighLimit(255)
1✔
51
    , m_linearColor(false)
1✔
52
{
53
    // This makes the application crash when a clickAndGoWidget
54
    // is created in a QDialog.
55
    //    setAttribute(Qt::WA_StaticContents);
56

57
    setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
1✔
58
    setMouseTracking(true);
1✔
59
}
1✔
60

61
void ClickAndGoWidget::setupGradient(QColor begin, QColor end)
×
62
{
63
    QLinearGradient linearGrad(QPointF(10,0), QPointF(266, 0));
×
64
    linearGrad.setColorAt(0, begin);
×
65
    linearGrad.setColorAt(1, end);
×
66

67
    // create image and fill it with gradient
68
    m_width = 276;
×
69
    m_height = 40;
×
NEW
70
    m_image = getDPIAwareImage(m_width, m_height);
×
71
    QPainter painter(&m_image);
×
72
    painter.fillRect(m_image.rect(), linearGrad);
×
73

74
    m_linearColor = true;
×
75
}
×
76

77
void ClickAndGoWidget::setupColorPicker()
×
78
{
79
    int cw = 15;
×
80

81
    m_width = 256 + 30;
×
82
    m_height = 256;
×
NEW
83
    m_image = getDPIAwareImage(m_width, m_height);
×
84
    QPainter painter(&m_image);
×
85

86
    // Draw 16 default color squares
87
    painter.fillRect(0, 0, cw, 32, QColor(Qt::white));
×
88
    painter.fillRect(cw, 0, cw, 32, QColor(Qt::black));
×
89
    painter.fillRect(0, 32, cw, 64, QColor(Qt::red));
×
90
    painter.fillRect(cw, 32, cw, 64, QColor(Qt::darkRed));
×
91
    painter.fillRect(0, 64, cw, 96, QColor(Qt::green));
×
92
    painter.fillRect(cw, 64, cw, 96, QColor(Qt::darkGreen));
×
93
    painter.fillRect(0, 96, cw, 128, QColor(Qt::blue));
×
94
    painter.fillRect(cw, 96, cw, 128, QColor(Qt::darkBlue));
×
95
    painter.fillRect(0, 128, cw, 160, QColor(Qt::cyan));
×
96
    painter.fillRect(cw, 128, cw, 160, QColor(Qt::darkCyan));
×
97
    painter.fillRect(0, 160, cw, 192, QColor(Qt::magenta));
×
98
    painter.fillRect(cw, 160, cw, 192, QColor(Qt::darkMagenta));
×
99
    painter.fillRect(0, 192, cw, 224, QColor(Qt::yellow));
×
100
    painter.fillRect(cw, 192, cw, 224, QColor(Qt::darkYellow));
×
101
    painter.fillRect(0, 224, cw, 256, QColor(Qt::gray));
×
102
    painter.fillRect(cw, 224, cw, 256, QColor(Qt::darkGray));
×
103

104
    painter.drawImage(cw * 2, 0, Gradient::getRGBGradient());
×
105
}
×
106

107
void ClickAndGoWidget::setType(int type, const QLCChannel *chan)
×
108
{
109
    m_linearColor = false;
×
110
    //qDebug() << Q_FUNC_INFO << "Type: " << type;
111
    if (type == None)
×
112
    {
NEW
113
        m_image = getDPIAwareImage(0, 0);
×
114
    }
115
    else if (type == Red)
×
116
        setupGradient(Qt::black, Qt::red);
×
117
    else if (type == Green)
×
118
        setupGradient(Qt::black, Qt::green);
×
119
    else if (type == Blue)
×
120
        setupGradient(Qt::black, Qt::blue);
×
121
    else if (type == Cyan)
×
122
        setupGradient(Qt::white, Qt::cyan);
×
123
    else if (type == Magenta)
×
124
        setupGradient(Qt::white, Qt::magenta);
×
125
    else if (type == Yellow)
×
126
        setupGradient(Qt::white, Qt::yellow);
×
127
    else if (type == Amber)
×
128
        setupGradient(Qt::black, 0xFFFF7E00);
×
129
    else if (type == White)
×
130
        setupGradient(Qt::black, Qt::white);
×
131
    else if (type == UV)
×
132
        setupGradient(Qt::black, 0xFF9400D3);
×
133
    else if (type == Lime)
×
134
        setupGradient(Qt::black, 0xFFADFF2F);
×
135
    else if (type == Indigo)
×
136
        setupGradient(Qt::black, 0xFF4B0082);
×
137
    else if (type == RGB || type == CMY)
×
138
    {
139
        setupColorPicker();
×
140
    }
141
    else if (type == Preset)
×
142
    {
143
        createPresetList(chan);
×
144
        setupPresetPicker();
×
145
    }
146

147
    m_type = type;
×
148
}
×
149

150
void ClickAndGoWidget::setLevelLowLimit(int min)
×
151
{
152
    this->m_levelLowLimit = min;
×
153
}
×
154

155
void ClickAndGoWidget::setLevelHighLimit(int max)
×
156
{
157
    this->m_levelHighLimit = max;
×
158
}
×
159

NEW
160
int ClickAndGoWidget::getType() const
×
161
{
162
    return m_type;
×
163
}
164

165
QString ClickAndGoWidget::clickAndGoTypeToString(ClickAndGoWidget::ClickAndGo type)
×
166
{
167
    switch (type)
×
168
    {
169
        default:
×
170
        case None: return "None"; break;
×
171
        case Red: return "Red"; break;
×
172
        case Green: return "Green"; break;
×
173
        case Blue: return "Blue"; break;
×
174
        case Cyan: return "Cyan"; break;
×
175
        case Magenta: return "Magenta"; break;
×
176
        case Yellow: return "Yellow"; break;
×
177
        case Amber: return "Amber"; break;
×
178
        case White: return "White"; break;
×
179
        case UV: return "UV"; break;
×
180
        case Lime: return "Lime"; break;
×
181
        case Indigo: return "Indigo"; break;
×
182
        case RGB: return "RGB"; break;
×
183
        case CMY: return "CMY"; break;
×
184
        case Preset: return "Preset"; break;
×
185
    }
186
}
187

188
ClickAndGoWidget::ClickAndGo ClickAndGoWidget::stringToClickAndGoType(QString str)
×
189
{
190
    if (str == "Red") return Red;
×
191
    else if (str == "Green") return Green;
×
192
    else if (str == "Blue") return Blue;
×
193
    else if (str == "Cyan") return Cyan;
×
194
    else if (str == "Magenta") return Magenta;
×
195
    else if (str == "Yellow") return Yellow;
×
196
    else if (str == "Amber") return Amber;
×
197
    else if (str == "White") return White;
×
198
    else if (str == "UV") return UV;
×
199
    else if (str == "Lime") return Lime;
×
200
    else if (str == "Indigo") return Indigo;
×
201
    else if (str == "RGB") return RGB;
×
202
    else if (str == "CMY") return CMY;
×
203
    else if (str == "Preset") return Preset;
×
204

205
    return None;
×
206
}
207

NEW
208
QColor ClickAndGoWidget::getColorAt(uchar pos) const
×
209
{
210
    if (m_linearColor == true)
×
211
    {
212
        QRgb col = m_image.pixel(10 + pos, 10);
×
213
        return QColor(col);
×
214
    }
215
    return QColor(0,0,0);
×
216
}
217

NEW
218
QImage ClickAndGoWidget::getImageFromValue(uchar value) const
×
219
{
220
    /** If the widget type is a Preset, return directly
221
     *  the pre-loaded resource */
222
    if (m_type == Preset)
×
223
    {
224
        foreach (PresetResource res, m_resources)
×
225
        {
226
            if (value >= res.m_resLowLimit && value <= res.m_resHighLimit)
×
227
                return res.m_thumbnail;
×
228
        }
×
229
    }
230

231
    QImage img(42, 42, QImage::Format_RGB32);
×
232
    if (m_type == None)
×
233
    {
234
        img.fill(Qt::black);
×
235
    }
236
    else if (m_linearColor == true)
×
237
    {
238
        QRgb col = m_image.pixel(10 + value, 10);
×
239
        img.fill(QColor(col).rgb());
×
240
    }
241

242
    return img;
×
243
}
×
244

245
void ClickAndGoWidget::createPresetList(const QLCChannel *chan)
×
246
{
247
    int i = 1;
×
248
    if (chan == NULL)
×
249
        return;
×
250

251
    m_title = chan->name();
×
252
    m_resources.clear();
×
253

254
    //qDebug() << Q_FUNC_INFO << "cap #" << chan->capabilities().size();
255

256
    foreach (QLCCapability* cap, chan->capabilities())
×
257
    {
258
        if (cap->presetType() == QLCCapability::Picture)
×
259
        {
NEW
260
            m_resources.append(PresetResource(this, cap->resource(0).toString(), cap->name(),
×
261
                                              cap->min(), cap->max()));
×
262
        }
263
        else if (cap->presetType() == QLCCapability::SingleColor)
×
264
        {
265
            QColor col1 = cap->resource(0).value<QColor>();
×
NEW
266
            m_resources.append(PresetResource(this, col1, QColor(), cap->name(), cap->min(), cap->max()));
×
267
        }
268
        else if (cap->presetType() == QLCCapability::DoubleColor)
×
269
        {
270
            QColor col1 = cap->resource(0).value<QColor>();
×
271
            QColor col2 = cap->resource(1).value<QColor>();
×
NEW
272
            m_resources.append(PresetResource(this, col1, col2, cap->name(), cap->min(), cap->max()));
×
273
        }
274
        else
275
        {
NEW
276
            m_resources.append(PresetResource(this, i, cap->name(), cap->min(), cap->max()));
×
277
        }
278
        i++;
×
279
    }
×
280
}
281

282
void ClickAndGoWidget::setupPresetPicker()
×
283
{
284
    if (m_resources.size() == 0)
×
285
        return;
×
286

287
    QScreen *scr = QGuiApplication::screens().first();
×
288
    QRect screen = scr->availableGeometry();
×
289

290
    m_cols = 2;
×
291
    m_rows = qCeil((qreal)m_resources.size() / 2);
×
292
    m_width = m_cellWidth * m_cols;
×
293
    m_height = CELL_H * m_rows + TITLE_H;
×
294

295
    // first check if the menu fits vertically
296
    if (m_height > screen.height())
×
297
    {
298
        m_rows = qFloor((qreal)screen.height() / CELL_H);
×
299
        m_cols = qCeil((qreal)m_resources.size() / m_rows);
×
300
        m_width = m_cellWidth * m_cols;
×
301
        m_height = CELL_H * m_rows + TITLE_H;
×
302
    }
303

304
    // then check if it has to be rescaled horizontally
305
    if (m_width > screen.width())
×
306
    {
307
        m_cellWidth = screen.width() / m_cols;
×
308
        m_width = m_cellWidth * m_cols;
×
309
    }
310

311
    int x = 0;
×
312
    int y = 0;
×
NEW
313
    m_image = getDPIAwareImage(m_width, m_height);
×
314
    QPainter painter(&m_image);
×
315
    QPalette p = palette();
×
316
    painter.setRenderHint(QPainter::Antialiasing);
×
317
    painter.fillRect(0, 0, m_width, m_height, p.color(QPalette::Window));
×
318

319
    // title
320
    painter.setPen(p.color(QPalette::Text));
×
321
    painter.drawText(x + 3, y, m_width - 3, TITLE_H, Qt::AlignVCenter | Qt::TextSingleLine, m_title);
×
322
    y += TITLE_H;
×
323

324
    for (int i = 0; i < m_resources.size(); i++)
×
325
    {
326
        PresetResource res = m_resources.at(i);
×
327
        if (res.m_resLowLimit > m_levelHighLimit || res.m_resHighLimit < m_levelLowLimit)
×
328
            continue;
×
329
        painter.setPen(p.color(QPalette::Text));
×
330
        painter.drawRect(x, y, m_cellWidth, CELL_H);
×
331
        painter.drawImage(x + 1, y + 4, res.m_thumbnail);
×
332
        painter.drawText(x + 43, y + 4, m_cellWidth - 42, CELL_H - 5, Qt::TextWordWrap|Qt::AlignVCenter, res.m_descr);
×
333
        if (i % m_cols == m_cols - 1)
×
334
        {
335
            y += CELL_H;
×
336
            x = 0;
×
337
        }
338
        else
339
            x += m_cellWidth;
×
340

341
    }
×
342
}
×
343

344
QSize ClickAndGoWidget::sizeHint() const
×
345
{
346
    return QSize(m_width, m_height);
×
347
}
348

349
void ClickAndGoWidget::mousePressEvent(QMouseEvent *event)
×
350
{
351
    if (m_linearColor == true)
×
352
    {
353
        if (event->pos().x() <= 10)
×
354
            emit levelChanged(0);
×
355
        else if (event->pos().x() > 10 && event->pos().x() < 256)
×
356
            emit levelChanged((uchar)(event->pos().x() - 10));
×
357
        else
358
            emit levelChanged(255);
×
359
    }
360
    else if (m_type == RGB || m_type == CMY)
×
361
    {
362
        emit colorChanged(m_image.pixel(event->pos().x(), event->pos().y()));
×
363
    }
364
    else if (m_type == Preset)
×
365
    {
366
        if (m_hoverCellIdx >= 0 && m_hoverCellIdx < m_resources.length())
×
367
        {
368
            PresetResource res = m_resources.at(m_hoverCellIdx);
×
369
            qDebug() << "Mouse press. cellW: " << m_cellBarWidth << "min: " << res.m_resLowLimit << "max:" << res.m_resHighLimit;
×
370

371
            float f = SCALE(float(m_cellBarWidth),
×
372
                        float(0),
373
                        float(m_cellWidth),
374
                        float(0), float(res.m_resHighLimit - res.m_resLowLimit));
375
            emit levelAndPresetChanged((uchar)f + res.m_resLowLimit, res.m_thumbnail);
×
376
        }
×
377
    }
378
    QWidget::mousePressEvent(event);
×
379
}
×
380

381
void ClickAndGoWidget::mouseMoveEvent(QMouseEvent *event)
×
382
{
383
    if (m_linearColor == true && event->buttons() == Qt::LeftButton)
×
384
    {
385
        if (event->pos().x() <= 10)
×
386
            emit levelChanged(0);
×
387
        else if (event->pos().x() > 10 && event->pos().x() < 256)
×
388
            emit levelChanged((uchar)(event->pos().x() - 10));
×
389
        else
390
            emit levelChanged(255);
×
391
    }
392
    else if ((m_type == RGB || m_type == CMY) && event->buttons() == Qt::LeftButton)
×
393
    {
394
        emit colorChanged(m_image.pixel(event->pos().x(), event->pos().y()));
×
395
    }
396
    else if (m_type == Preset)
×
397
    {
398
        // calculate the index of the resource where the cursor is
399
        int floorX = qFloor(event->pos().x() / m_cellWidth);
×
400
        int floorY = qFloor((event->pos().y() - TITLE_H) / CELL_H);
×
401
        int tmpCellIDx = (floorY * m_cols) + floorX;
×
402
        if (event->pos().y() < TITLE_H || tmpCellIDx < 0 || tmpCellIDx >= m_resources.length())
×
403
        {
404
            m_hoverCellIdx = -1;
×
405
            update();
×
406
            return;
×
407
        }
408
        m_cellBarXpos = floorX * m_cellWidth;
×
409
        m_cellBarYpos = floorY * CELL_H + TITLE_H;
×
410
        m_cellBarWidth = event->pos().x() - m_cellBarXpos;
×
411
        m_hoverCellIdx = tmpCellIDx;
×
412
        update();
×
413
        qDebug() << "Idx:" << m_hoverCellIdx << "X:" << m_cellBarXpos << "mX:" << event->pos().x();
×
414
    }
415
}
416

417
void ClickAndGoWidget::paintEvent(QPaintEvent *event)
×
418
{
419
    Q_UNUSED(event)
420

421
    QPainter painter(this);
×
422
    painter.drawImage(QPoint(0, 0), m_image);
×
423
    if (m_type == Preset && m_hoverCellIdx >= 0)
×
424
    {
425
        painter.setPen(Qt::NoPen);
×
426
        painter.setBrush(QBrush(QColor(76, 136, 255, 255)));
×
427
        painter.drawRect(m_cellBarXpos, m_cellBarYpos + 1, m_cellBarWidth, 3);
×
428
    }
429
}
×
430

NEW
431
QImage ClickAndGoWidget::getDPIAwareImage(int width, int height) const
×
432
{
NEW
433
    qreal devicePixelRatio = 1;
×
434
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
NEW
435
    devicePixelRatio = devicePixelRatioF();
×
436
#else
437
    const QScreen* currentScreen = QGuiApplication::screens().first();
438
    if (currentScreen != NULL)
439
    {
440
        devicePixelRatio = currentScreen->devicePixelRatio();
441
    }
442
#endif
443

NEW
444
    QImage image(width*devicePixelRatio, height*devicePixelRatio, QImage::Format_RGB32);
×
NEW
445
    image.setDevicePixelRatio(devicePixelRatio);
×
446

NEW
447
    return image;
×
NEW
448
}
×
449

NEW
450
QImage ClickAndGoWidget::getDPIAwareImageStatic(const ClickAndGoWidget* parent, int width, int height)
×
451
{
NEW
452
    return (parent != NULL) ? parent->getDPIAwareImage(width, height) : QImage(width, height, QImage::Format_RGB32);
×
453
}
454

NEW
455
ClickAndGoWidget::PresetResource::PresetResource(const ClickAndGoWidget* parent, QString path, QString text, uchar min, uchar max)
×
NEW
456
    : m_descr(text)
×
NEW
457
    , m_resLowLimit(min)
×
NEW
458
    , m_resHighLimit(max)
×
459
{
460
    QImage px(path);
×
NEW
461
    m_thumbnail = getDPIAwareImageStatic(parent, 40, 40);
×
462
    m_thumbnail.fill(Qt::white);
×
463
    QPainter painter(&m_thumbnail);
×
464
    painter.setRenderHint(QPainter::SmoothPixmapTransform);
×
465
    painter.drawImage(QRect(0,0,40,40), px);
×
466
    //qDebug() << "PATH: adding " << path << ", descr: " << text;
467
}
×
468

NEW
469
ClickAndGoWidget::PresetResource::PresetResource(const ClickAndGoWidget* parent, QColor color1, QColor color2,
×
470
                                                 QString text, uchar min, uchar max)
×
NEW
471
    : m_descr(text)
×
NEW
472
    , m_resLowLimit(min)
×
NEW
473
    , m_resHighLimit(max)
×
474
{
NEW
475
    m_thumbnail = getDPIAwareImageStatic(parent, 40, 40);
×
476
    if (color2.isValid() == false)
×
477
        m_thumbnail.fill(color1.rgb());
×
478
    else
479
    {
480
        QPainter painter(&m_thumbnail);
×
481
        painter.fillRect(0, 0, 20, 40, color1);
×
482
        painter.fillRect(20, 0, 40, 40, color2);
×
483
    }
×
484
    //qDebug() << "COLOR: adding " << color1.name() << ", descr: " << text;
485
}
×
486

NEW
487
ClickAndGoWidget::PresetResource::PresetResource(const ClickAndGoWidget* parent, int index, QString text, uchar min, uchar max)
×
NEW
488
    : m_descr(text)
×
NEW
489
    , m_resLowLimit(min)
×
NEW
490
    , m_resHighLimit(max)
×
491
{
NEW
492
    m_thumbnail = getDPIAwareImageStatic(parent, 40, 40);
×
493
    m_thumbnail.fill(Qt::white);
×
494
    QFont tfont = QApplication::font();
×
495
    tfont.setBold(true);
×
496
    tfont.setPixelSize(20);
×
497
    QPainter painter(&m_thumbnail);
×
498
    painter.setFont(tfont);
×
499
    painter.drawText(0, 0, 40, 40, Qt::AlignHCenter|Qt::AlignVCenter, QString("%1").arg(index));
×
500
    //qDebug() << "GENERIC: adding " << index << ", descr: " << text;
501
}
×
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