Implement LED panel #47

This commit is contained in:
Alex Spataru 2021-10-21 22:37:23 -05:00
parent 667380d75c
commit b323daca93
18 changed files with 1054 additions and 16 deletions

View File

@ -180,12 +180,14 @@ HEADERS += \
src/Widgets/Common/AnalogGauge.h \
src/Widgets/Common/AttitudeIndicator.h \
src/Widgets/Common/BaseWidget.h \
src/Widgets/Common/KLed.h \
src/Widgets/Compass.h \
src/Widgets/DataGroup.h \
src/Widgets/FFTPlot.h \
src/Widgets/GPS.h \
src/Widgets/Gauge.h \
src/Widgets/Gyroscope.h \
src/Widgets/LEDPanel.h \
src/Widgets/MultiPlot.h \
src/Widgets/Plot.h \
src/Widgets/Terminal.h
@ -219,12 +221,14 @@ SOURCES += \
src/Widgets/Common/AnalogGauge.cpp \
src/Widgets/Common/AttitudeIndicator.cpp \
src/Widgets/Common/BaseWidget.cpp \
src/Widgets/Common/KLed.cpp \
src/Widgets/Compass.cpp \
src/Widgets/DataGroup.cpp \
src/Widgets/FFTPlot.cpp \
src/Widgets/GPS.cpp \
src/Widgets/Gauge.cpp \
src/Widgets/Gyroscope.cpp \
src/Widgets/LEDPanel.cpp \
src/Widgets/MultiPlot.cpp \
src/Widgets/Plot.cpp \
src/Widgets/Terminal.cpp \

View File

@ -140,5 +140,6 @@
<file>qml/JsonEditor/TreeView.qml</file>
<file>icons/gps.svg</file>
<file>qml/Widgets/Terminal.qml</file>
<file>icons/led.svg</file>
</qresource>
</RCC>

1
assets/icons/led.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6C7.8 12.16 7 10.63 7 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z"/></svg>

After

Width:  |  Height:  |  Size: 447 B

View File

@ -173,6 +173,17 @@ Widgets.Window {
onCheckedChanged: (index, checked) => Cpp_UI_Dashboard.setMultiplotVisible(index, checked)
}
//
// LEDs
//
ViewOptionsDelegate {
title: qsTr("LED Panels")
icon: "qrc:/icons/led.svg"
count: Cpp_UI_Dashboard.ledCount
titles: Cpp_UI_Dashboard.ledTitles
onCheckedChanged: (index, checked) => Cpp_UI_Dashboard.setLedVisible(index, checked)
}
//
// FFT
//

View File

@ -112,6 +112,18 @@ Widgets.Window {
}
}
//
// Dataset LED
//
Label {
text: qsTr("Display LED:")
} Switch {
id: led
Layout.leftMargin: -app.spacing
checked: Cpp_JSON_Editor.datasetLED(group, dataset)
onCheckedChanged: Cpp_JSON_Editor.setDatasetLED(group, dataset, checked)
}
//
// Dataset graph
//

View File

@ -204,7 +204,7 @@ Widgets.Window {
delegate: Item {
Layout.fillWidth: true
Layout.minimumWidth: 320
Layout.minimumHeight: 400 + 2 * app.spacing
Layout.minimumHeight: 420 + 2 * app.spacing
JsonDatasetDelegate {
id: datasetDelegate

View File

@ -28,8 +28,9 @@ using namespace JSON;
Dataset::Dataset(QObject *parent)
: QObject(parent)
, m_fft(false)
, m_graph(false)
, m_led(false)
, m_log(false)
, m_graph(false)
, m_title("")
, m_value("")
, m_units("")
@ -50,6 +51,14 @@ bool Dataset::fft() const
return m_fft;
}
/**
* @return @c true if the UI should generate a LED of this dataset
*/
bool Dataset::led() const
{
return m_led;
}
/**
* @return @c true if the UI should generate a logarithmic plot of this dataset
*/
@ -151,6 +160,7 @@ bool Dataset::read(const QJsonObject &object)
if (!object.isEmpty())
{
auto fft = object.value("fft").toVariant().toBool();
auto led = object.value("led").toVariant().toBool();
auto log = object.value("log").toVariant().toBool();
auto graph = object.value("g").toVariant().toBool();
auto title = object.value("t").toVariant().toString();
@ -182,6 +192,7 @@ bool Dataset::read(const QJsonObject &object)
m_min = min;
m_max = max;
m_fft = fft;
m_led = led;
m_log = log;
m_graph = graph;
m_title = title;

View File

@ -91,6 +91,7 @@ public:
Dataset(QObject *parent = nullptr);
bool fft() const;
bool led() const;
bool log() const;
bool graph() const;
double min() const;
@ -107,8 +108,9 @@ public:
private:
bool m_fft;
bool m_graph;
bool m_led;
bool m_log;
bool m_graph;
QString m_title;
QString m_value;

View File

@ -324,6 +324,7 @@ bool Editor::saveJsonFile()
dataset.insert("t", datasetTitle(i, j));
dataset.insert("u", datasetUnits(i, j));
dataset.insert("g", datasetGraph(i, j));
dataset.insert("led", datasetLED(i, j));
dataset.insert("w", datasetWidget(i, j));
dataset.insert("fft", datasetFftPlot(i, j));
dataset.insert("log", datasetLogPlot(i, j));
@ -458,6 +459,22 @@ int Editor::datasetIndex(const int group, const int dataset)
return 0;
}
/**
* Returns @c true if Serial Studio should generate a LED with the given
* @a dataset (which is contained by the specified @a group).
*
* @param group index of the group in which the dataset belongs
* @param dataset index of the dataset
*/
bool Editor::datasetLED(const int group, const int dataset)
{
auto set = getDataset(group, dataset);
if (set)
return set->led();
return false;
}
/**
* Returns @c true if Serial Studio should graph the data of the given
* @a dataset (which is contained by the specified @a group).
@ -758,6 +775,7 @@ void Editor::openJsonFile(const QString &path)
// Register dataset with C++ model
addDataset(group);
setDatasetGraph(group, dataset, jsonDataset.value("g").toBool());
setDatasetLED(group, dataset, jsonDataset.value("led").toBool());
setDatasetTitle(group, dataset, jsonDataset.value("t").toString());
setDatasetUnits(group, dataset, jsonDataset.value("u").toString());
setDatasetFftPlot(group, dataset, jsonDataset.value("fft").toBool());
@ -1153,6 +1171,22 @@ void Editor::setDatasetIndex(const int group, const int dataset, const int frame
}
}
/**
* Updates the @a generateLED flag of the given @a dataset.
*
* @param group index of the group in which the dataset belongs
* @param dataset index of the dataset
*/
void Editor::setDatasetLED(const int group, const int dataset, const bool generateLED)
{
auto set = getDataset(group, dataset);
if (set)
{
set->m_led = generateLED;
emit datasetChanged(group, dataset);
}
}
/**
* Updates the @a generateGraph flag of the given @a dataset.
*

View File

@ -115,6 +115,7 @@ public:
Q_INVOKABLE int groupWidgetIndex(const int group);
Q_INVOKABLE int datasetIndex(const int group, const int dataset);
Q_INVOKABLE bool datasetLED(const int group, const int dataset);
Q_INVOKABLE bool datasetGraph(const int group, const int dataset);
Q_INVOKABLE bool datasetFftPlot(const int group, const int dataset);
Q_INVOKABLE bool datasetLogPlot(const int group, const int dataset);
@ -151,6 +152,7 @@ public slots:
void setDatasetTitle(const int group, const int dataset, const QString &title);
void setDatasetUnits(const int group, const int dataset, const QString &units);
void setDatasetIndex(const int group, const int dataset, const int frameIndex);
void setDatasetLED(const int group, const int dataset, const bool generateLED);
void setDatasetGraph(const int group, const int dataset, const bool generateGraph);
void setDatasetFftPlot(const int group, const int dataset, const bool generateFft);
void setDatasetLogPlot(const int group, const int dataset, const bool generateLog);

View File

@ -31,6 +31,11 @@
#include "Dataset.h"
namespace UI
{
class Dashboard;
}
namespace JSON
{
/**
@ -89,6 +94,7 @@ private:
QVector<Dataset *> m_datasets;
friend class Editor;
friend class UI::Dashboard;
};
}

View File

@ -77,6 +77,7 @@ QFont Dashboard::monoFont() const
}
// clang-format off
JSON::Group *Dashboard::getLED(const int index) { return getGroupWidget(m_ledWidgets, index); }
JSON::Group *Dashboard::getGPS(const int index) { return getGroupWidget(m_gpsWidgets, index); }
JSON::Dataset *Dashboard::getBar(const int index) { return getDatasetWidget(m_barWidgets, index); }
JSON::Dataset *Dashboard::getFFT(const int index) { return getDatasetWidget(m_fftWidgets, index); }
@ -143,7 +144,9 @@ bool Dashboard::frameValid() const
int Dashboard::totalWidgetCount() const
{
// clang-format off
const int count = gpsCount() +
const int count =
gpsCount() +
ledCount() +
barCount() +
fftCount() +
plotCount() +
@ -160,6 +163,7 @@ int Dashboard::totalWidgetCount() const
// clang-format off
int Dashboard::gpsCount() const { return m_gpsWidgets.count(); }
int Dashboard::ledCount() const { return m_ledWidgets.count(); }
int Dashboard::barCount() const { return m_barWidgets.count(); }
int Dashboard::fftCount() const { return m_fftWidgets.count(); }
int Dashboard::plotCount() const { return m_plotWidgets.count(); }
@ -188,15 +192,16 @@ QVector<QString> Dashboard::widgetTitles() const
// clang-format off
return groupTitles() +
multiPlotTitles() +
fftTitles() +
plotTitles() +
barTitles() +
gaugeTitles() +
compassTitles() +
gyroscopeTitles() +
accelerometerTitles() +
gpsTitles();
multiPlotTitles() +
ledTitles() +
fftTitles() +
plotTitles() +
barTitles() +
gaugeTitles() +
compassTitles() +
gyroscopeTitles() +
accelerometerTitles() +
gpsTitles();
// clang-format on
}
@ -235,8 +240,13 @@ int Dashboard::relativeIndex(const int globalIndex) const
if (index < multiPlotCount())
return index;
// Check if we should return FFT widget
// Check if we should return LED widget
index -= multiPlotCount();
if (index < ledCount())
return index;
// Check if we should return FFT widget
index -= ledCount();
if (index < fftCount())
return index;
@ -333,6 +343,9 @@ bool Dashboard::widgetVisible(const int globalIndex) const
case WidgetType::GPS:
visible = gpsVisible(index);
break;
case WidgetType::LED:
visible = ledVisible(index);
break;
default:
visible = false;
break;
@ -391,6 +404,9 @@ QString Dashboard::widgetIcon(const int globalIndex) const
case WidgetType::GPS:
return "qrc:/icons/gps.svg";
break;
case WidgetType::LED:
return "qrc:/icons/led.svg";
break;
default:
return "qrc:/icons/close.svg";
break;
@ -411,7 +427,8 @@ QString Dashboard::widgetIcon(const int globalIndex) const
* - @c WidgetType::Compass
* - @c WidgetType::Gyroscope
* - @c WidgetType::Accelerometer
* - @c WidgetType::Map
* - @c WidgetType::GPS
* - @c WidgetType::LED
*
* To simplify operations and user interface generation in QML, this class represents
* the widgets in two manners:
@ -447,8 +464,13 @@ UI::Dashboard::WidgetType Dashboard::widgetType(const int globalIndex) const
if (index < multiPlotCount())
return WidgetType::MultiPlot;
// Check if we should return FFT widget
// Check if we should return LED widget
index -= multiPlotCount();
if (index < ledCount())
return WidgetType::LED;
// Check if we should return FFT widget
index -= ledCount();
if (index < fftCount())
return WidgetType::FFT;
@ -499,6 +521,7 @@ UI::Dashboard::WidgetType Dashboard::widgetType(const int globalIndex) const
bool Dashboard::barVisible(const int index) const { return getVisibility(m_barVisibility, index); }
bool Dashboard::fftVisible(const int index) const { return getVisibility(m_fftVisibility, index); }
bool Dashboard::gpsVisible(const int index) const { return getVisibility(m_gpsVisibility, index); }
bool Dashboard::ledVisible(const int index) const { return getVisibility(m_ledVisibility, index); }
bool Dashboard::plotVisible(const int index) const { return getVisibility(m_plotVisibility, index); }
bool Dashboard::groupVisible(const int index) const { return getVisibility(m_groupVisibility, index); }
bool Dashboard::gaugeVisible(const int index) const { return getVisibility(m_gaugeVisibility, index); }
@ -514,6 +537,7 @@ bool Dashboard::accelerometerVisible(const int index) const { return getVisibili
// clang-format off
QVector<QString> Dashboard::gpsTitles() const { return groupTitles(m_gpsWidgets); }
QVector<QString> Dashboard::ledTitles() const { return groupTitles(m_ledWidgets); }
QVector<QString> Dashboard::groupTitles() const { return groupTitles(m_groupWidgets); }
QVector<QString> Dashboard::barTitles() const { return datasetTitles(m_barWidgets); }
QVector<QString> Dashboard::fftTitles() const { return datasetTitles(m_fftWidgets); }
@ -559,6 +583,7 @@ void Dashboard::setPoints(const int points)
void Dashboard::setBarVisible(const int i, const bool v) { setVisibility(m_barVisibility, i, v); }
void Dashboard::setFFTVisible(const int i, const bool v) { setVisibility(m_fftVisibility, i, v); }
void Dashboard::setGpsVisible(const int i, const bool v) { setVisibility(m_gpsVisibility, i, v); }
void Dashboard::setLedVisible(const int i, const bool v) { setVisibility(m_ledVisibility, i, v); }
void Dashboard::setPlotVisible(const int i, const bool v) { setVisibility(m_plotVisibility, i, v); }
void Dashboard::setGroupVisible(const int i, const bool v) { setVisibility(m_groupVisibility, i, v); }
void Dashboard::setGaugeVisible(const int i, const bool v) { setVisibility(m_gaugeVisibility, i, v); }
@ -590,6 +615,7 @@ void Dashboard::resetData()
m_barWidgets.clear();
m_fftWidgets.clear();
m_gpsWidgets.clear();
m_ledWidgets.clear();
m_plotWidgets.clear();
m_gaugeWidgets.clear();
m_groupWidgets.clear();
@ -602,6 +628,7 @@ void Dashboard::resetData()
m_barVisibility.clear();
m_fftVisibility.clear();
m_gpsVisibility.clear();
m_ledVisibility.clear();
m_plotVisibility.clear();
m_gaugeVisibility.clear();
m_groupVisibility.clear();
@ -634,6 +661,7 @@ void Dashboard::updateData()
int barC = barCount();
int fftC = fftCount();
int mapC = gpsCount();
int ledC = ledCount();
int plotC = plotCount();
int groupC = groupCount();
int gaugeC = gaugeCount();
@ -654,6 +682,7 @@ void Dashboard::updateData()
m_barWidgets.clear();
m_fftWidgets.clear();
m_gpsWidgets.clear();
m_ledWidgets.clear();
m_plotWidgets.clear();
m_gaugeWidgets.clear();
m_groupWidgets.clear();
@ -670,6 +699,7 @@ void Dashboard::updateData()
// Update widget vectors
m_fftWidgets = getFFTWidgets();
m_ledWidgets = getLEDWidgets();
m_plotWidgets = getPlotWidgets();
m_groupWidgets = getWidgetGroups("");
m_gpsWidgets = getWidgetGroups("map");
@ -697,6 +727,7 @@ void Dashboard::updateData()
regenerateWidgets |= (barC != barCount());
regenerateWidgets |= (fftC != fftCount());
regenerateWidgets |= (mapC != gpsCount());
regenerateWidgets |= (ledC != ledCount());
regenerateWidgets |= (plotC != plotCount());
regenerateWidgets |= (gaugeC != gaugeCount());
regenerateWidgets |= (groupC != groupCount());
@ -711,6 +742,7 @@ void Dashboard::updateData()
m_barVisibility.clear();
m_fftVisibility.clear();
m_gpsVisibility.clear();
m_ledVisibility.clear();
m_plotVisibility.clear();
m_gaugeVisibility.clear();
m_groupVisibility.clear();
@ -726,6 +758,8 @@ void Dashboard::updateData()
m_fftVisibility.append(true);
for (i = 0; i < gpsCount(); ++i)
m_gpsVisibility.append(true);
for (i = 0; i < ledCount(); ++i)
m_ledVisibility.append(true);
for (i = 0; i < plotCount(); ++i)
m_plotVisibility.append(true);
for (i = 0; i < gaugeCount(); ++i)
@ -843,6 +877,33 @@ void Dashboard::processLatestJSON(const JFI_Object &frameInfo)
// Widget utility functions
//--------------------------------------------------------------------------------------------------
/**
* Returns a group with all the datasets that need to be shown in the LED status panel.
*
* @note We return a vector with a single group item because we want to display a title on
* the window without breaking the current software architecture.
*/
QVector<JSON::Group *> Dashboard::getLEDWidgets() const
{
QVector<JSON::Dataset *> widgets;
foreach (auto group, m_latestFrame.groups())
{
foreach (auto dataset, group->datasets())
{
if (dataset->led())
widgets.append(dataset);
}
}
JSON::Group *group = new JSON::Group();
group->m_title = tr("Status Panel");
group->m_datasets = widgets;
QVector<JSON::Group *> groups;
groups.append(group);
return groups;
}
/**
* Returns a vector with all the datasets that need to be shown in the FFT widgets.
*/

View File

@ -78,6 +78,9 @@ class Dashboard : public QObject
Q_PROPERTY(int gpsCount
READ gpsCount
NOTIFY widgetCountChanged)
Q_PROPERTY(int ledCount
READ ledCount
NOTIFY widgetCountChanged)
Q_PROPERTY(int barCount
READ barCount
NOTIFY widgetCountChanged)
@ -108,6 +111,9 @@ class Dashboard : public QObject
Q_PROPERTY(QVector<QString> gpsTitles
READ gpsTitles
NOTIFY widgetCountChanged)
Q_PROPERTY(QVector<QString> ledTitles
READ ledTitles
NOTIFY widgetCountChanged)
Q_PROPERTY(QVector<QString> barTitles
READ barTitles
NOTIFY widgetCountChanged)
@ -158,6 +164,7 @@ public:
Gyroscope,
Accelerometer,
GPS,
LED,
Unknown
};
Q_ENUM(WidgetType)
@ -165,6 +172,7 @@ public:
static Dashboard *getInstance();
QFont monoFont() const;
JSON::Group *getLED(const int index);
JSON::Group *getGPS(const int index);
JSON::Dataset *getFFT(const int index);
JSON::Dataset *getBar(const int index);
@ -182,6 +190,7 @@ public:
int totalWidgetCount() const;
int gpsCount() const;
int ledCount() const;
int fftCount() const;
int barCount() const;
int plotCount() const;
@ -202,6 +211,7 @@ public:
Q_INVOKABLE bool barVisible(const int index) const;
Q_INVOKABLE bool fftVisible(const int index) const;
Q_INVOKABLE bool gpsVisible(const int index) const;
Q_INVOKABLE bool ledVisible(const int index) const;
Q_INVOKABLE bool plotVisible(const int index) const;
Q_INVOKABLE bool groupVisible(const int index) const;
Q_INVOKABLE bool gaugeVisible(const int index) const;
@ -213,6 +223,7 @@ public:
QVector<QString> barTitles() const;
QVector<QString> fftTitles() const;
QVector<QString> gpsTitles() const;
QVector<QString> ledTitles() const;
QVector<QString> plotTitles() const;
QVector<QString> groupTitles() const;
QVector<QString> gaugeTitles() const;
@ -230,6 +241,7 @@ public slots:
void setBarVisible(const int index, const bool visible);
void setFFTVisible(const int index, const bool visible);
void setGpsVisible(const int index, const bool visible);
void setLedVisible(const int index, const bool visible);
void setPlotVisible(const int index, const bool visible);
void setGroupVisible(const int index, const bool visible);
void setGaugeVisible(const int index, const bool visible);
@ -247,6 +259,7 @@ private slots:
private:
Dashboard();
QVector<JSON::Group *> getLEDWidgets() const;
QVector<JSON::Dataset *> getFFTWidgets() const;
QVector<JSON::Dataset *> getPlotWidgets() const;
QVector<JSON::Group *> getWidgetGroups(const QString &handle) const;
@ -272,6 +285,7 @@ private:
QVector<bool> m_barVisibility;
QVector<bool> m_fftVisibility;
QVector<bool> m_gpsVisibility;
QVector<bool> m_ledVisibility;
QVector<bool> m_plotVisibility;
QVector<bool> m_groupVisibility;
QVector<bool> m_gaugeVisibility;
@ -286,6 +300,7 @@ private:
QVector<JSON::Dataset *> m_gaugeWidgets;
QVector<JSON::Dataset *> m_compassWidgets;
QVector<JSON::Group *> m_ledWidgets;
QVector<JSON::Group *> m_gpsWidgets;
QVector<JSON::Group *> m_groupWidgets;
QVector<JSON::Group *> m_multiPlotWidgets;

View File

@ -31,6 +31,7 @@
#include <Widgets/Gauge.h>
#include <Widgets/Compass.h>
#include <Widgets/FFTPlot.h>
#include <Widgets/LEDPanel.h>
#include <Widgets/DataGroup.h>
#include <Widgets/Gyroscope.h>
#include <Widgets/MultiPlot.h>
@ -276,6 +277,9 @@ void WidgetLoader::setWidgetIndex(const int index)
case UI::Dashboard::WidgetType::GPS:
m_widget = new GPS(relativeIndex());
break;
case UI::Dashboard::WidgetType::LED:
m_widget = new LEDPanel(relativeIndex());
break;
default:
break;
}

274
src/Widgets/Common/KLed.cpp Normal file
View File

@ -0,0 +1,274 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1998 Jörg Habenicht <j.habenicht@europemail.com>
SPDX-FileCopyrightText: 2010 Christoph Feck <cfeck@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kled.h"
#include <QImage>
#include <QPainter>
#include <QStyle>
#include <QStyleOption>
class KLedPrivate
{
public:
int darkFactor = 300;
QColor color;
KLed::State state = KLed::On;
KLed::Look look = KLed::Raised;
KLed::Shape shape = KLed::Circular;
QPixmap cachedPixmap[2]; // for both states
};
KLed::KLed(QWidget *parent)
: QWidget(parent)
, d(new KLedPrivate)
{
setColor(Qt::green);
updateAccessibleName();
}
KLed::KLed(const QColor &color, QWidget *parent)
: QWidget(parent)
, d(new KLedPrivate)
{
setColor(color);
updateAccessibleName();
}
KLed::KLed(const QColor &color, State state, Look look, Shape shape, QWidget *parent)
: QWidget(parent)
, d(new KLedPrivate)
{
d->state = (state == Off ? Off : On);
d->look = look;
d->shape = shape;
setColor(color);
updateAccessibleName();
}
KLed::~KLed() = default;
KLed::State KLed::state() const
{
return d->state;
}
KLed::Shape KLed::shape() const
{
return d->shape;
}
QColor KLed::color() const
{
return d->color;
}
KLed::Look KLed::look() const
{
return d->look;
}
void KLed::setState(State state)
{
if (d->state == state)
{
return;
}
d->state = (state == Off ? Off : On);
updateCachedPixmap();
updateAccessibleName();
}
void KLed::setShape(Shape shape)
{
if (d->shape == shape)
{
return;
}
d->shape = shape;
updateCachedPixmap();
}
void KLed::setColor(const QColor &color)
{
if (d->color == color)
{
return;
}
d->color = color;
updateCachedPixmap();
}
void KLed::setDarkFactor(int darkFactor)
{
if (d->darkFactor == darkFactor)
{
return;
}
d->darkFactor = darkFactor;
updateCachedPixmap();
}
int KLed::darkFactor() const
{
return d->darkFactor;
}
void KLed::setLook(Look look)
{
if (d->look == look)
{
return;
}
d->look = look;
updateCachedPixmap();
}
void KLed::toggle()
{
d->state = (d->state == On ? Off : On);
updateCachedPixmap();
updateAccessibleName();
}
void KLed::on()
{
setState(On);
}
void KLed::off()
{
setState(Off);
}
void KLed::resizeEvent(QResizeEvent *)
{
updateCachedPixmap();
}
QSize KLed::sizeHint() const
{
QStyleOption option;
option.initFrom(this);
int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, &option, this);
return QSize(iconSize, iconSize);
}
QSize KLed::minimumSizeHint() const
{
return QSize(32, 32);
}
void KLed::updateAccessibleName()
{
#ifndef QT_NO_ACCESSIBILITY
QString onName = tr("LED on", "Accessible name of a Led whose state is on");
QString offName = tr("LED off", "Accessible name of a Led whose state is off");
QString lastName = accessibleName();
if (lastName.isEmpty() || lastName == onName || lastName == offName)
{
// Accessible name has not been manually set.
setAccessibleName(d->state == On ? onName : offName);
}
#endif
}
void KLed::updateCachedPixmap()
{
d->cachedPixmap[Off] = QPixmap();
d->cachedPixmap[On] = QPixmap();
update();
}
void KLed::paintEvent(QPaintEvent *)
{
if (!d->cachedPixmap[d->state].isNull())
{
QPainter painter(this);
painter.drawPixmap(1, 1, d->cachedPixmap[d->state]);
return;
}
QSize size(width() - 2, height() - 2);
if (d->shape == Circular)
{
// Make sure the LED is round
const int dim = qMin(width(), height()) - 2;
size = QSize(dim, dim);
}
QPointF center(size.width() / 2.0, size.height() / 2.0);
const int smallestSize = qMin(size.width(), size.height());
QPainter painter;
QImage image(size, QImage::Format_ARGB32_Premultiplied);
image.fill(0);
QRadialGradient fillGradient(center, smallestSize / 2.0,
QPointF(center.x(), size.height() / 3.0));
const QColor fillColor = d->state != Off ? d->color : d->color.darker(d->darkFactor);
fillGradient.setColorAt(0.0, fillColor.lighter(250));
fillGradient.setColorAt(0.5, fillColor.lighter(130));
fillGradient.setColorAt(1.0, fillColor);
QConicalGradient borderGradient(center, d->look == Sunken ? 90 : -90);
QColor borderColor = palette().color(QPalette::Dark);
if (d->state == On)
{
QColor glowOverlay = fillColor;
glowOverlay.setAlpha(80);
// This isn't the fastest way, but should be "fast enough".
// It's also the only safe way to use QPainter::CompositionMode
QImage img(1, 1, QImage::Format_ARGB32_Premultiplied);
QPainter p(&img);
QColor start = borderColor;
start.setAlpha(255); // opaque
p.fillRect(0, 0, 1, 1, start);
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
p.fillRect(0, 0, 1, 1, glowOverlay);
p.end();
borderColor = img.pixel(0, 0);
}
borderGradient.setColorAt(0.2, borderColor);
borderGradient.setColorAt(0.5, palette().color(QPalette::Light));
borderGradient.setColorAt(0.8, borderColor);
painter.begin(&image);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(d->look == Flat ? QBrush(fillColor) : QBrush(fillGradient));
const QBrush penBrush
= (d->look == Flat) ? QBrush(borderColor) : QBrush(borderGradient);
const qreal penWidth = smallestSize / 8.0;
painter.setPen(QPen(penBrush, penWidth));
QRectF r(penWidth / 2.0, penWidth / 2.0, size.width() - penWidth,
size.height() - penWidth);
if (d->shape == Rectangular)
{
painter.drawRect(r);
}
else
{
painter.drawEllipse(r);
}
painter.end();
d->cachedPixmap[d->state] = QPixmap::fromImage(image);
painter.begin(this);
painter.drawPixmap(1, 1, d->cachedPixmap[d->state]);
painter.end();
}

258
src/Widgets/Common/KLed.h Normal file
View File

@ -0,0 +1,258 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1998 Jörg Habenicht <j.habenicht@europemail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KLED_H
#define KLED_H
#include <QWidget>
#include <memory>
class QColor;
/**
* @class KLed kled.h KLed
*
* @short An LED widget.
*
* Displays a round or rectangular light emitting diode.
*
* It is configurable to arbitrary colors, the two on/off states and three
* styles (or "looks");
*
* It may display itself in a performant flat view, a round view with
* light spot or a round view sunken in the screen.
*
* \image html kled.png "KLed Widget"
*
* @author Joerg Habenicht, Richard J. Moore (rich@kde.org) 1998, 1999
*/
class KLed : public QWidget
{
Q_OBJECT
Q_PROPERTY(State state READ state WRITE setState)
Q_PROPERTY(Shape shape READ shape WRITE setShape)
Q_PROPERTY(Look look READ look WRITE setLook)
Q_PROPERTY(QColor color READ color WRITE setColor)
Q_PROPERTY(int darkFactor READ darkFactor WRITE setDarkFactor)
public:
/**
* Status of the light is on/off.
* @short LED on/off.
*/
enum State
{
Off,
On
};
Q_ENUM(State)
/**
* Shades of the lamp.
* @short LED shape
*/
enum Shape
{
Rectangular,
Circular
};
Q_ENUM(Shape)
/**
* Displays a flat, round or sunken LED.
*
* @short LED look.
*/
enum Look
{
Flat,
Raised,
Sunken,
};
Q_ENUM(Look)
/**
* Constructs a green, round LED widget which will initially
* be turned on.
*
* @param parent The parent widget.
*/
explicit KLed(QWidget *parent = nullptr);
/**
* Constructs a round LED widget with the supplied color which will
* initially be turned on.
*
* @param color Initial color of the LED.
* @param parent The parent widget.
* @short Constructor
*/
explicit KLed(const QColor &color, QWidget *parent = nullptr);
/**
* Constructor with the color, state and look.
*
* Differs from above only in the parameters, which configure all settings.
*
* @param color Initial color of the LED.
* @param state Sets the State.
* @param look Sets the Look.
* @param shape Sets the Shape (rectangular or circular).
* @param parent The parent widget.
* @short Constructor
*/
KLed(const QColor &color, KLed::State state, KLed::Look look, KLed::Shape shape,
QWidget *parent = nullptr);
/**
* Destroys the LED widget.
* @short Destructor
*/
~KLed() override;
/**
* Returns the current color of the widget.
*
* @returns LED color
* @see setColor()
*/
QColor color() const;
/**
* Returns the current state of the widget (on/off).
* @returns LED state
*
* @see State
*/
State state() const;
/**
* Returns the current look of the widget.
* @returns LED look
*
* @see Look
*/
Look look() const;
/**
* Returns the current shape of the widget.
* @returns LED shape
*
* @see Shape
*/
Shape shape() const;
/**
* Returns the factor to darken the LED.
* @returns dark factor
*
* @see setDarkFactor()
*/
int darkFactor() const;
/**
* Set the color of the widget.
*
* The LED is shown with @p color when in the KLed::On state
* or with the darken color in KLed::Off state.
*
* The widget calls the update() method, so it will
* be updated when entering the main event loop.
*
* @param color New color of the LED.
*
* @see color() darkFactor()
*/
void setColor(const QColor &color);
/**
* Sets the state of the widget to On or Off.
*
* @param state The LED state: on or off.
*
* @see on() off() toggle()
*/
void setState(State state);
/**
* Sets the look of the widget.
*
* The look may be Flat, Raised or Sunken.
*
* The widget calls the update() method, so it will
* be updated when entering the main event loop.
*
* @param look New look of the LED.
*
* @see Look
*/
void setLook(Look look);
/**
* Set the shape of the LED.
*
* @param shape The LED shape.
* @short Set LED shape.
*/
void setShape(Shape shape);
/**
* Sets the factor to darken the LED in KLed::Off state.
*
* The @p darkFactor should be greater than 100, otherwise the LED
* becomes lighter in KLed::Off state.
*
* Defaults to 300.
*
* @param darkFactor Sets the factor to darken the LED.
*
* @see setColor
*/
void setDarkFactor(int darkFactor);
QSize sizeHint() const override;
QSize minimumSizeHint() const override;
public Q_SLOTS:
/**
* Toggles the state of the led from Off to On or vice versa.
*/
void toggle();
/**
* Sets the state of the widget to On.
*
* @see off() toggle() setState()
*/
void on();
/**
* Sets the state of the widget to Off.
*
* @see on() toggle() setState()
*/
void off();
protected:
void paintEvent(QPaintEvent *) override;
void resizeEvent(QResizeEvent *) override;
/**
* @internal
* invalidates caches after property changes and calls update()
*/
void updateCachedPixmap();
private:
void updateAccessibleName();
private:
std::unique_ptr<class KLedPrivate> const d;
};
#endif

274
src/Widgets/LEDPanel.cpp Normal file
View File

@ -0,0 +1,274 @@
/*
* Copyright (c) 2020-2021 Alex Spataru <https://github.com/alex-spataru>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "LEDPanel.h"
#include "UI/Dashboard.h"
#include "Misc/ThemeManager.h"
#include <QMouseEvent>
#include <QWheelEvent>
#include <QResizeEvent>
using namespace Widgets;
/**
* Generates the user interface elements & layout
*/
LEDPanel::LEDPanel(const int index)
: m_index(index)
{
// Get pointers to serial studio modules
auto dash = UI::Dashboard::getInstance();
auto theme = Misc::ThemeManager::getInstance();
// Invalid index, abort initialization
if (m_index < 0 || m_index >= dash->ledCount())
return;
// Get group pointer
auto group = dash->getLED(m_index);
if (!group)
return;
// Generate widget stylesheets
auto titleQSS = QSS("color:%1", theme->widgetTextPrimary());
// Set window palette
QPalette windowPalette;
windowPalette.setColor(QPalette::Base, theme->widgetWindowBackground());
windowPalette.setColor(QPalette::Window, theme->widgetWindowBackground());
setPalette(windowPalette);
// Configure scroll area container
m_dataContainer = new QWidget(this);
// Make the value label larger
auto valueFont = dash->monoFont();
valueFont.setPixelSize(dash->monoFont().pixelSize() * 1.3);
// Get LED color list
QString color;
auto colors = QStringList { "#ff3333", "#ff9933", "#ffff33", "#99ff33",
"#33ff33", "#33ff99", "#33ffff", "#3399ff",
"#3333ff", "#9933ff", "#ff33ff", "#ff3399" };
// Configure grid layout
m_gridLayout = new QGridLayout(m_dataContainer);
m_gridLayout->setSpacing(8);
for (int dataset = 0; dataset < group->datasetCount(); ++dataset)
{
// Create labels
m_leds.append(new KLed(m_dataContainer));
m_titles.append(new QLabel(m_dataContainer));
// Get pointers to labels
auto led = m_leds.last();
auto title = m_titles.last();
// Set label styles & fonts
title->setStyleSheet(titleQSS);
title->setFont(dash->monoFont());
title->setText(group->getDataset(dataset)->title());
// Get LED color
if (colors.count() > dataset)
color = colors.at(dataset);
else
color = colors.at(colors.count() % dataset);
// Set LED color & style
led->setLook(KLed::Flat);
led->setShape(KLed::Rectangular);
led->setColor(QColor(color));
// Calculate column and row
int column = 0;
int row = dataset;
int count = dataset + 1;
while (count > 3)
{
count -= 3;
row -= 3;
column += 2;
}
// Add label and LED to grid layout
m_gridLayout->addWidget(led, row, column);
m_gridLayout->addWidget(title, row, column + 1);
m_gridLayout->setAlignment(title, Qt::AlignLeft | Qt::AlignVCenter);
m_gridLayout->setAlignment(led, Qt::AlignHCenter | Qt::AlignVCenter);
}
// Load layout into container widget
m_dataContainer->setLayout(m_gridLayout);
// Configure scroll area
m_scrollArea = new QScrollArea(this);
m_scrollArea->setWidgetResizable(true);
m_scrollArea->setWidget(m_dataContainer);
m_scrollArea->setFrameShape(QFrame::NoFrame);
m_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// Configure main layout
m_mainLayout = new QVBoxLayout(this);
m_mainLayout->addWidget(m_scrollArea);
m_mainLayout->setContentsMargins(0, 0, 0, 0);
setLayout(m_mainLayout);
// React to dashboard events
connect(dash, SIGNAL(updated()), this, SLOT(updateData()));
}
/**
* Frees the memory allocated for each label and LED that represents a dataset
*/
LEDPanel::~LEDPanel()
{
foreach (auto led, m_leds)
delete led;
foreach (auto title, m_titles)
delete title;
delete m_gridLayout;
delete m_scrollArea;
delete m_mainLayout;
}
/**
* Checks if the widget is enabled, if so, the widget shall be updated
* to display the latest data frame.
*
* If the widget is disabled (e.g. the user hides it, or the external
* window is hidden), then the widget shall ignore the update request.
*/
void LEDPanel::updateData()
{
// Widget not enabled, do nothing
if (!isEnabled())
return;
// Get group pointer
auto dash = UI::Dashboard::getInstance();
auto group = dash->getLED(m_index);
if (!group)
return;
// Update labels
JSON::Dataset *dataset;
for (int i = 0; i < group->datasetCount(); ++i)
{
dataset = group->getDataset(i);
if (dataset)
{
#ifndef LAZY_WIDGETS
m_titles.at(i)->setText(set->title());
#endif
// Get dataset value (we compare with 0.1 for low voltages)
auto value = dataset->value().toDouble();
if (qAbs(value) < 0.10)
m_leds.at(i)->off();
else
m_leds.at(i)->on();
}
}
}
/**
* Changes the size of the labels when the widget is resized
*/
void LEDPanel::resizeEvent(QResizeEvent *event)
{
auto width = event->size().width();
QFont font = UI::Dashboard::getInstance()->monoFont();
font.setPixelSize(qMax(8, width / 24));
auto fHeight = QFontMetrics(font).height() * 1.5;
for (int i = 0; i < m_titles.count(); ++i)
{
m_titles.at(i)->setFont(font);
m_leds.at(i)->setMinimumSize(fHeight, fHeight);
}
event->accept();
}
void LEDPanel::wheelEvent(QWheelEvent *event)
{
class Hack : public QScrollArea
{
public:
using QWidget::wheelEvent;
};
auto hack = static_cast<Hack *>(m_scrollArea);
hack->wheelEvent(event);
}
void LEDPanel::mouseMoveEvent(QMouseEvent *event)
{
class Hack : public QScrollArea
{
public:
using QWidget::mouseMoveEvent;
};
auto hack = static_cast<Hack *>(m_scrollArea);
hack->mouseMoveEvent(event);
}
void LEDPanel::mousePressEvent(QMouseEvent *event)
{
class Hack : public QScrollArea
{
public:
using QWidget::mousePressEvent;
};
auto hack = static_cast<Hack *>(m_scrollArea);
hack->mousePressEvent(event);
}
void LEDPanel::mouseReleaseEvent(QMouseEvent *event)
{
class Hack : public QScrollArea
{
public:
using QWidget::mouseReleaseEvent;
};
auto hack = static_cast<Hack *>(m_scrollArea);
hack->mouseReleaseEvent(event);
}
void LEDPanel::mouseDoubleClickEvent(QMouseEvent *event)
{
class Hack : public QScrollArea
{
public:
using QWidget::mouseDoubleClickEvent;
};
auto hack = static_cast<Hack *>(m_scrollArea);
hack->mouseDoubleClickEvent(event);
}

68
src/Widgets/LEDPanel.h Normal file
View File

@ -0,0 +1,68 @@
/*
* Copyright (c) 2020-2021 Alex Spataru <https://github.com/alex-spataru>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef WIDGETS_LEDPANEL_H
#define WIDGETS_LEDPANEL_H
#include <QLabel>
#include <QWidget>
#include <QGridLayout>
#include <QVBoxLayout>
#include <QScrollArea>
#include "Common/KLed.h"
namespace Widgets
{
class LEDPanel : public QWidget
{
Q_OBJECT
public:
LEDPanel(const int index = -1);
~LEDPanel();
private slots:
void updateData();
protected:
void resizeEvent(QResizeEvent *event);
void wheelEvent(QWheelEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void mouseDoubleClickEvent(QMouseEvent *event);
private:
int m_index;
QVector<KLed *> m_leds;
QVector<QLabel *> m_titles;
QWidget *m_dataContainer;
QVBoxLayout *m_mainLayout;
QGridLayout *m_gridLayout;
QScrollArea *m_scrollArea;
};
}
#endif