1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-09-08 06:06:33 -04:00
sdrangel/plugins/feature/remotecontrol/remotecontrolgui.cpp
Jon Beniston c966f1cb5a dd maximize button to MainSpectrum and expandible Channels and Features.
Add sizeToContents in ChannelGUI and FeatureGUI, called when widget is
rolled, so we can remove resizing code from all of the individual
channels and features.

In RollupContents, use minimumSizeHint for calculated size, so that
minimumWidth can come from .ui file.

In DeviceGUI::sizeToContents(), call adjustSize(), so Device GUIs start
out at minimum needed size (which should restore appearance prior to
last patch).

In stackSubWindows, use available space for channels if no
spectrum/features present.
In stackSubWindows, fix spectrum from being sized too big, resulting in
scroll bars appearing.
Reset user-defined channel width in stackSubWindows, when channels are
removed.
Don't stack maximized windows.

There's one hack in Channel/FeatureGUI::maximizeWindow(). It seems that
when maximimzing a window, QOpenGLWidgets aren't always paint properly
immediately afterwards, so the code forces an additional update. I can't
see why the first call to paintGL doesn't work.
2022-11-11 12:24:27 +00:00

1174 lines
44 KiB
C++

///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "feature/featureuiset.h"
#include "gui/basicfeaturesettingsdialog.h"
#include "gui/flowlayout.h"
#include "gui/scidoublespinbox.h"
#include "ui_remotecontrolgui.h"
#include "remotecontrol.h"
#include "remotecontrolgui.h"
#include "remotecontrolsettingsdialog.h"
RemoteControlGUI* RemoteControlGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature)
{
RemoteControlGUI* gui = new RemoteControlGUI(pluginAPI, featureUISet, feature);
return gui;
}
void RemoteControlGUI::destroy()
{
delete this;
}
void RemoteControlGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray RemoteControlGUI::serialize() const
{
return m_settings.serialize();
}
bool RemoteControlGUI::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
m_feature->setWorkspaceIndex(m_settings.m_workspaceIndex);
displaySettings();
applySettings(true);
on_update_clicked();
return true;
}
else
{
resetToDefaults();
return false;
}
}
bool RemoteControlGUI::handleMessage(const Message& message)
{
if (RemoteControl::MsgConfigureRemoteControl::match(message))
{
qDebug("RemoteControlGUI::handleMessage: RemoteControl::MsgConfigureRemoteControl");
const RemoteControl::MsgConfigureRemoteControl& cfg = (RemoteControl::MsgConfigureRemoteControl&) message;
m_settings = cfg.getSettings();
blockApplySettings(true);
displaySettings();
blockApplySettings(false);
return true;
}
else if (RemoteControl::MsgDeviceStatus::match(message))
{
const RemoteControl::MsgDeviceStatus& msg = (RemoteControl::MsgDeviceStatus&) message;
deviceUpdated(msg.getProtocol(), msg.getDeviceId(), msg.getStatus());
return true;
}
else if (RemoteControl::MsgDeviceError::match(message))
{
const RemoteControl::MsgDeviceError& msg = (RemoteControl::MsgDeviceError&) message;
QMessageBox::critical(this, "Remote Control Error", msg.getErrorMessage());
return true;
}
else if (RemoteControl::MsgDeviceUnavailable::match(message))
{
const RemoteControl::MsgDeviceUnavailable& msg = (RemoteControl::MsgDeviceUnavailable&) message;
deviceUnavailable(msg.getProtocol(), msg.getDeviceId());
return true;
}
return false;
}
void RemoteControlGUI::handleInputMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()))
{
if (handleMessage(*message)) {
delete message;
}
}
}
void RemoteControlGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
getRollupContents()->saveState(m_rollupState);
applySettings();
}
RemoteControlGUI::RemoteControlGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent) :
FeatureGUI(parent),
ui(new Ui::RemoteControlGUI),
m_pluginAPI(pluginAPI),
m_featureUISet(featureUISet),
m_doApplySettings(true)
{
m_feature = feature;
setAttribute(Qt::WA_DeleteOnClose, true);
m_helpURL = "plugins/feature/remotecontrol/readme.md";
RollupContents *rollupContents = getRollupContents();
ui->setupUi(rollupContents);
rollupContents->arrangeRollups();
connect(rollupContents, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"
"QToolButton:checked { background-color : green; }"
"QToolButton:disabled { background-color : gray; }");
m_startStopIcon.addFile(":/play.png", QSize(16, 16), QIcon::Normal, QIcon::Off);
m_startStopIcon.addFile(":/stop.png", QSize(16, 16), QIcon::Normal, QIcon::On);
m_remoteControl = reinterpret_cast<RemoteControl*>(feature);
m_remoteControl->setMessageQueueToGUI(&m_inputMessageQueue);
m_settings.setRollupState(&m_rollupState);
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
displaySettings();
applySettings(true);
makeUIConnections();
}
RemoteControlGUI::~RemoteControlGUI()
{
qDeleteAll(m_deviceGUIs);
m_deviceGUIs.clear();
delete ui;
}
void RemoteControlGUI::setWorkspaceIndex(int index)
{
m_settings.m_workspaceIndex = index;
m_feature->setWorkspaceIndex(index);
}
void RemoteControlGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void RemoteControlGUI::displaySettings()
{
setTitleColor(m_settings.m_rgbColor);
setWindowTitle(m_settings.m_title);
setTitle(m_settings.m_title);
createGUI();
blockApplySettings(true);
getRollupContents()->restoreState(m_rollupState);
blockApplySettings(false);
getRollupContents()->arrangeRollups();
}
void RemoteControlGUI::onMenuDialogCalled(const QPoint &p)
{
if (m_contextMenuType == ContextMenuChannelSettings)
{
BasicFeatureSettingsDialog dialog(this);
dialog.setTitle(m_settings.m_title);
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
dialog.setReverseAPIFeatureSetIndex(m_settings.m_reverseAPIFeatureSetIndex);
dialog.setReverseAPIFeatureIndex(m_settings.m_reverseAPIFeatureIndex);
dialog.setDefaultTitle(m_displayedName);
dialog.move(p);
dialog.exec();
m_settings.m_title = dialog.getTitle();
m_settings.m_useReverseAPI = dialog.useReverseAPI();
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
m_settings.m_reverseAPIFeatureSetIndex = dialog.getReverseAPIFeatureSetIndex();
m_settings.m_reverseAPIFeatureIndex = dialog.getReverseAPIFeatureIndex();
setTitle(m_settings.m_title);
setTitleColor(m_settings.m_rgbColor);
applySettings();
}
resetContextMenuType();
}
void RemoteControlGUI::createControls(RemoteControlDeviceGUI *gui, QBoxLayout *vBox, FlowLayout *flow, int &widgetCnt)
{
// Create buttons to control the device
QGridLayout *controlsGrid = nullptr;
if (gui->m_rcDevice->m_verticalControls)
{
controlsGrid = new QGridLayout();
vBox->addLayout(controlsGrid);
}
else if (!flow)
{
flow = new FlowLayout(2, 6, 6);
vBox->addItem(flow);
}
int row = 0;
for (auto const &control : gui->m_rcDevice->m_controls)
{
if (!gui->m_rcDevice->m_verticalControls && (widgetCnt > 0))
{
QFrame *line = new QFrame();
line->setFrameShape(QFrame::VLine);
line->setFrameShadow(QFrame::Sunken);
flow->addWidget(line);
}
DeviceDiscoverer::ControlInfo *info = gui->m_rcDevice->m_info.getControl(control.m_id);
if (!info)
{
qDebug() << "RemoteControlGUI::createControls: Info missing for " << control.m_id;
continue;
}
if (!control.m_labelLeft.isEmpty())
{
QLabel *controlLabelLeft = new QLabel(control.m_labelLeft);
if (gui->m_rcDevice->m_verticalControls)
{
controlsGrid->addWidget(controlLabelLeft, row, 0);
controlsGrid->setColumnStretch(row, 0);
}
else
{
flow->addWidget(controlLabelLeft);
}
}
QList<QWidget *> widgets;
QWidget *widget = nullptr;
switch (info->m_type)
{
case DeviceDiscoverer::BOOL:
{
ButtonSwitch *button = new ButtonSwitch();
button->setToolTip("Start/stop " + info->m_name);
button->setIcon(m_startStopIcon);
button->setStyleSheet("QToolButton { background-color : blue; }"
"QToolButton:checked { background-color : green; }"
"QToolButton:disabled { background-color : gray; }");
connect(button, &ButtonSwitch::toggled,
[=] (bool toggled)
{
RemoteControl::MsgDeviceSetState *message = RemoteControl::MsgDeviceSetState::create(gui->m_rcDevice->m_protocol,
gui->m_rcDevice->m_info.m_id,
control.m_id,
toggled);
m_remoteControl->getInputMessageQueue()->push(message);
}
);
widgets.append(button);
widget = button;
}
break;
case DeviceDiscoverer::INT:
{
QSpinBox *spinBox = new QSpinBox();
spinBox->setToolTip("Set value for " + info->m_name);
spinBox->setMinimum((int)info->m_min);
spinBox->setMaximum((int)info->m_max);
connect(spinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
[=] (int value)
{
RemoteControl::MsgDeviceSetState *message = RemoteControl::MsgDeviceSetState::create(gui->m_rcDevice->m_protocol,
gui->m_rcDevice->m_info.m_id,
control.m_id,
value);
m_remoteControl->getInputMessageQueue()->push(message);
}
);
widgets.append(spinBox);
widget = spinBox;
}
break;
case DeviceDiscoverer::FLOAT:
{
switch (info->m_widgetType)
{
case DeviceDiscoverer::SPIN_BOX:
{
QDoubleSpinBox *spinBox = new SciDoubleSpinBox();
spinBox->setToolTip("Set value for " + info->m_name);
spinBox->setMinimum(info->m_min);
spinBox->setMaximum(info->m_max);
spinBox->setDecimals(info->m_precision);
connect(spinBox, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
[=] (double value)
{
RemoteControl::MsgDeviceSetState *message = RemoteControl::MsgDeviceSetState::create(gui->m_rcDevice->m_protocol,
gui->m_rcDevice->m_info.m_id,
control.m_id,
(float)value * info->m_scale);
m_remoteControl->getInputMessageQueue()->push(message);
}
);
widgets.append(spinBox);
widget = spinBox;
}
break;
case DeviceDiscoverer::DIAL:
{
widget = new QWidget();
QHBoxLayout *layout = new QHBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
widget->setLayout(layout);
QDial *dial = new QDial();
dial->setMaximumSize(24, 24);
dial->setToolTip("Set value for " + info->m_name);
dial->setMinimum(info->m_min);
dial->setMaximum(info->m_max);
connect(dial, static_cast<void (QDial::*)(int)>(&QDial::valueChanged),
[=] (int value)
{
RemoteControl::MsgDeviceSetState *message = RemoteControl::MsgDeviceSetState::create(gui->m_rcDevice->m_protocol,
gui->m_rcDevice->m_info.m_id,
control.m_id,
((float)value) * info->m_scale);
m_remoteControl->getInputMessageQueue()->push(message);
}
);
widgets.append(dial);
layout->addWidget(dial);
QLabel *label = new QLabel(QString::number(dial->value()));
label->setToolTip("Value for " + info->m_name);
widgets.append(label);
layout->addWidget(label);
}
break;
case DeviceDiscoverer::SLIDER:
{
widget = new QWidget();
QHBoxLayout *layout = new QHBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
widget->setLayout(layout);
QSlider *slider = new QSlider(Qt::Horizontal);
slider->setToolTip("Set value for " + info->m_name);
slider->setMinimum(info->m_min);
slider->setMaximum(info->m_max);
connect(slider, static_cast<void (QSlider::*)(int)>(&QSlider::valueChanged),
[=] (int value)
{
RemoteControl::MsgDeviceSetState *message = RemoteControl::MsgDeviceSetState::create(gui->m_rcDevice->m_protocol,
gui->m_rcDevice->m_info.m_id,
control.m_id,
((float)value) * info->m_scale);
m_remoteControl->getInputMessageQueue()->push(message);
}
);
widgets.append(slider);
layout->addWidget(slider);
QLabel *label = new QLabel(QString::number(slider->value()));
label->setToolTip("Value for " + info->m_name);
widgets.append(label);
layout->addWidget(label);
}
break;
}
}
break;
case DeviceDiscoverer::STRING:
{
QLineEdit *lineEdit = new QLineEdit();
lineEdit->setToolTip("Set value for " + info->m_name);
connect(lineEdit, &QLineEdit::editingFinished,
[=] ()
{
QString text = lineEdit->text();
RemoteControl::MsgDeviceSetState *message = RemoteControl::MsgDeviceSetState::create(gui->m_rcDevice->m_protocol,
gui->m_rcDevice->m_info.m_id,
control.m_id,
text);
m_remoteControl->getInputMessageQueue()->push(message);
}
);
widgets.append(lineEdit);
widget = lineEdit;
}
break;
case DeviceDiscoverer::LIST:
{
QComboBox *combo = new QComboBox();
combo->setToolTip("Set value for " + info->m_name);
combo->insertItems(0, info->m_values);
connect(combo, &QComboBox::currentTextChanged,
[=] (const QString &text)
{
RemoteControl::MsgDeviceSetState *message = RemoteControl::MsgDeviceSetState::create(gui->m_rcDevice->m_protocol,
gui->m_rcDevice->m_info.m_id,
control.m_id,
text);
m_remoteControl->getInputMessageQueue()->push(message);
}
);
widgets.append(combo);
widget = combo;
}
break;
case DeviceDiscoverer::BUTTON:
{
QString label = info->m_name;
if (info->m_values.size() > 0) {
label = info->m_values[0];
}
QToolButton *button = new QToolButton();
button->setText(label);
button->setToolTip("Trigger " + info->m_name);
connect(button, &QToolButton::clicked,
[=] (bool checked)
{
(void) checked;
RemoteControl::MsgDeviceSetState *message = RemoteControl::MsgDeviceSetState::create(gui->m_rcDevice->m_protocol,
gui->m_rcDevice->m_info.m_id,
control.m_id,
1);
m_remoteControl->getInputMessageQueue()->push(message);
}
);
widgets.append(button);
widget = button;
}
break;
default:
qDebug() << "RemoteControlGUI::createControls: Unexpected type for control.";
break;
}
gui->m_controls.insert(control.m_id, widgets);
if (gui->m_rcDevice->m_verticalControls) {
controlsGrid->addWidget(widget, row, 1);
} else {
flow->addWidget(widget);
}
if (!control.m_labelRight.isEmpty())
{
QLabel *controlLabelRight = new QLabel(control.m_labelRight);
if (gui->m_rcDevice->m_verticalControls)
{
controlsGrid->addWidget(controlLabelRight, row, 2);
controlsGrid->setColumnStretch(row, 2);
}
else
{
flow->addWidget(controlLabelRight);
}
}
widgetCnt++;
row++;
}
}
void RemoteControlGUI::createChart(RemoteControlDeviceGUI *gui, QVBoxLayout *vBox, const QString &id, const QString &units)
{
if (gui->m_chart == nullptr)
{
// Create a chart to plot the sensor data
gui->m_chart = new QChart();
gui->m_chart->setTitle("");
gui->m_chart->legend()->hide();
gui->m_chart->layout()->setContentsMargins(0, 0, 0, 0);
gui->m_chart->setMargins(QMargins(1, 1, 1, 1));
gui->m_chart->setTheme(QChart::ChartThemeDark);
QLineSeries *series = new QLineSeries();
gui->m_series.insert(id, series);
QLineSeries *onePointSeries = new QLineSeries();
gui->m_onePointSeries.insert(id, onePointSeries);
gui->m_chart->addSeries(series);
QValueAxis *yAxis = new QValueAxis();
QDateTimeAxis *xAxis = new QDateTimeAxis();
xAxis->setFormat(QString("hh:mm:ss"));
yAxis->setTitleText(units);
gui->m_chart->addAxis(xAxis, Qt::AlignBottom);
gui->m_chart->addAxis(yAxis, Qt::AlignLeft);
series->attachAxis(xAxis);
series->attachAxis(yAxis);
gui->m_chartView = new QChartView();
gui->m_chartView->setChart(gui->m_chart);
if (m_settings.m_chartHeightFixed)
{
gui->m_chartView->setMinimumSize(300, m_settings.m_chartHeightPixels);
gui->m_chartView->setMaximumSize(16777215, m_settings.m_chartHeightPixels);
gui->m_chartView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
}
else
{
gui->m_chartView->setMinimumSize(300, 130); // 130 is enough to display axis labels
gui->m_chartView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
gui->m_chartView->setSceneRect(0, 0, 300, 130); // This determines m_chartView->sizeHint() - default is 640x480, which is a bit big
}
QBoxLayout *chartLayout = new QVBoxLayout();
gui->m_chartView->setLayout(chartLayout);
vBox->addWidget(gui->m_chartView);
}
else
{
// Add new series
QLineSeries *series = new QLineSeries();
gui->m_series.insert(id, series);
QLineSeries *onePointSeries = new QLineSeries();
gui->m_onePointSeries.insert(id, onePointSeries);
gui->m_chart->addSeries(series);
if (!gui->m_rcDevice->m_commonYAxis)
{
// Use per series Y axis
QValueAxis *yAxis = new QValueAxis();
yAxis->setTitleText(units);
gui->m_chart->addAxis(yAxis, Qt::AlignRight);
series->attachAxis(yAxis);
}
else
{
// Use common y axis
QAbstractAxis *yAxis = gui->m_chart->axes(Qt::Vertical)[0];
// Only display units if all the same
if (yAxis->titleText() != units) {
yAxis->setTitleText("");
}
series->attachAxis(yAxis);
}
series->attachAxis(gui->m_chart->axes(Qt::Horizontal)[0]);
}
}
void RemoteControlGUI::createSensors(RemoteControlDeviceGUI *gui, QVBoxLayout *vBox, FlowLayout *flow, int &widgetCnt, bool &hasCharts)
{
// Table doesn't seem to expand in a QHBoxLayout, so we have to use a GridLayout
QGridLayout *grid = nullptr;
QTableWidget *table = nullptr;
if (gui->m_rcDevice->m_verticalSensors)
{
grid = new QGridLayout();
grid->setColumnStretch(0, 1);
vBox->addLayout(grid);
table = new QTableWidget(gui->m_rcDevice->m_sensors.size(), 3);
table->verticalHeader()->setVisible(false);
table->horizontalHeader()->setVisible(false);
table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
table->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
table->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
table->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
table->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); // Needed so table->sizeHint matches minimumSize set below
}
else if (!flow)
{
flow = new FlowLayout(2, 6, 6);
vBox->addItem(flow);
}
int row = 0;
bool hasUnits = false;
for (auto const &sensor : gui->m_rcDevice->m_sensors)
{
// For vertical layout, we use a table
// For horizontal, we use HBox of labels separated with bars
if (gui->m_rcDevice->m_verticalSensors)
{
if (!sensor.m_labelLeft.isEmpty())
{
QTableWidgetItem *sensorLabel = new QTableWidgetItem(sensor.m_labelLeft);
sensorLabel->setFlags(Qt::ItemIsEnabled);
table->setItem(row, COL_LABEL, sensorLabel);
}
QTableWidgetItem *valueItem = new QTableWidgetItem("-");
table->setItem(row, COL_VALUE, valueItem);
valueItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
valueItem->setFlags(Qt::ItemIsEnabled);
if (!sensor.m_labelRight.isEmpty())
{
QTableWidgetItem *unitsItem = new QTableWidgetItem(sensor.m_labelRight);
unitsItem->setFlags(Qt::ItemIsEnabled);
table->setItem(row, COL_UNITS, unitsItem);
hasUnits = true;
}
gui->m_sensorValueItems.insert(sensor.m_id, valueItem);
grid->addWidget(table, 0, 0);
}
else
{
if (widgetCnt > 0)
{
QFrame *line = new QFrame();
line->setFrameShape(QFrame::VLine);
line->setFrameShadow(QFrame::Sunken);
flow->addWidget(line);
}
if (!sensor.m_labelLeft.isEmpty())
{
QLabel *sensorLabel = new QLabel(sensor.m_labelLeft);
flow->addWidget(sensorLabel);
}
QLabel *sensorValue = new QLabel("-");
flow->addWidget(sensorValue);
if (!sensor.m_labelRight.isEmpty())
{
QLabel *sensorUnits = new QLabel(sensor.m_labelRight);
flow->addWidget(sensorUnits);
}
gui->m_sensorValueLabels.insert(sensor.m_id, sensorValue);
}
if (sensor.m_plot)
{
createChart(gui, vBox, sensor.m_id, sensor.m_labelRight);
hasCharts = true;
}
widgetCnt++;
row++;
}
if (table)
{
table->resizeColumnToContents(COL_LABEL);
if (hasUnits) {
table->resizeColumnToContents(COL_UNITS);
} else {
table->hideColumn(COL_UNITS);
}
int tableWidth = 0;
for (int i = 0; i < table->columnCount(); i++){
tableWidth += table->columnWidth(i);
}
int tableHeight = 0;
for (int i = 0; i < table->rowCount(); i++){
tableHeight += table->rowHeight(i);
}
table->setMinimumWidth(tableWidth);
table->setMinimumHeight(tableHeight+2);
}
}
RemoteControlGUI::RemoteControlDeviceGUI *RemoteControlGUI::createDeviceGUI(RemoteControlDevice *rcDevice)
{
// Create the UI for the device
RemoteControlDeviceGUI *gui = new RemoteControlDeviceGUI(rcDevice);
bool hasCharts = false;
gui->m_container = new QWidget(getRollupContents());
gui->m_container->setWindowTitle(gui->m_rcDevice->m_label);
bool vertical = gui->m_rcDevice->m_verticalControls || gui->m_rcDevice->m_verticalSensors;
QVBoxLayout *vBox = new QVBoxLayout();
vBox->setContentsMargins(2, 2, 2, 2);
FlowLayout *flow = nullptr;
if (!vertical)
{
flow = new FlowLayout(2, 6, 6);
vBox->addItem(flow);
}
int widgetCnt = 0;
// Create buttons to control the device
createControls(gui, vBox, flow, widgetCnt);
if (gui->m_rcDevice->m_verticalControls) {
widgetCnt = 0;
}
// Create widgets to display the sensor label and its value
createSensors(gui, vBox, flow, widgetCnt, hasCharts);
gui->m_container->setLayout(vBox);
if (hasCharts && !m_settings.m_chartHeightFixed) {
gui->m_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
}
gui->m_container->show();
return gui;
}
void RemoteControlGUI::createGUI()
{
// Delete existing elements
for (auto gui : m_deviceGUIs)
{
delete gui->m_container;
gui->m_container = nullptr;
}
qDeleteAll(m_deviceGUIs);
m_deviceGUIs.clear();
// Create new GUIs for each device
bool expanding = false;
for (auto device : m_settings.m_devices)
{
RemoteControlDeviceGUI *gui = createDeviceGUI(device);
m_deviceGUIs.append(gui);
if (gui->m_container->sizePolicy().verticalPolicy() == QSizePolicy::Expanding) {
expanding = true;
}
}
if (expanding)
{
getRollupContents()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
}
else
{
getRollupContents()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
}
// FIXME: Why are these three steps needed to get the window
// to resize to the newly added widgets?
getRollupContents()->arrangeRollups(); // Recalc rollup size
layout()->activate(); // Get QMdiSubWindow to recalc its sizeHint
resize(sizeHint());
// Need to do it twice when FlowLayout is used!
getRollupContents()->arrangeRollups();
layout()->activate();
resize(sizeHint());
}
void RemoteControlGUI::on_startStop_toggled(bool checked)
{
if (m_doApplySettings)
{
RemoteControl::MsgStartStop *message = RemoteControl::MsgStartStop::create(checked);
m_remoteControl->getInputMessageQueue()->push(message);
}
}
void RemoteControlGUI::on_update_clicked()
{
RemoteControl::MsgDeviceGetState *message = RemoteControl::MsgDeviceGetState::create();
m_remoteControl->getInputMessageQueue()->push(message);
}
void RemoteControlGUI::on_settings_clicked()
{
// Display settings dialog
RemoteControlSettingsDialog dialog(&m_settings);
if (dialog.exec() == QDialog::Accepted)
{
createGUI();
applySettings();
on_update_clicked();
}
}
void RemoteControlGUI::on_clearData_clicked()
{
// Clear data in all charts
for (auto deviceGUI : m_deviceGUIs)
{
for (auto series : deviceGUI->m_series) {
series->clear();
}
for (auto series : deviceGUI->m_onePointSeries) {
series->clear();
}
}
}
// Update a control widget with latest state value
void RemoteControlGUI::updateControl(QWidget *widget, const DeviceDiscoverer::ControlInfo *controlInfo, const QString &key, const QVariant &value)
{
if (ButtonSwitch *button = qobject_cast<ButtonSwitch *>(widget))
{
if ((QMetaType::Type)value.type() == QMetaType::QString)
{
if (value.toString() == "unavailable")
{
button->setStyleSheet("QToolButton { background-color : gray; }"
"QToolButton:checked { background-color : gray; }"
"QToolButton:disabled { background-color : gray; }");
}
else if (value.toString() == "error")
{
button->setStyleSheet("QToolButton { background-color : red; }"
"QToolButton:checked { background-color : red; }"
"QToolButton:disabled { background-color : red; }");
}
else
{
qDebug() << "RemoteControlGUI::updateControl: String value for button " << key << value;
}
}
else
{
int state = value.toInt();
int prev = button->blockSignals(true);
button->setChecked(state != 0);
button->blockSignals(prev);
button->setStyleSheet("QToolButton { background-color : blue; }"
"QToolButton:checked { background-color : green; }"
"QToolButton:disabled { background-color : gray; }");
}
}
else if (QSpinBox *spinBox = qobject_cast<QSpinBox *>(widget))
{
int prev = spinBox->blockSignals(true);
if (value.toString() == "unavailable")
{
spinBox->setStyleSheet("QSpinBox { background-color : gray; }");
}
else if (value.toString() == "error")
{
spinBox->setStyleSheet("QSpinBox { background-color : red; }");
}
else
{
int state = value.toInt();
bool outOfRange = (state < spinBox->minimum()) || (state > spinBox->maximum());
spinBox->setValue(state);
if (outOfRange) {
spinBox->setStyleSheet("QSpinBox { background-color : red; }");
} else {
spinBox->setStyleSheet("");
}
}
spinBox->blockSignals(prev);
}
else if (QDoubleSpinBox *spinBox = qobject_cast<QDoubleSpinBox *>(widget))
{
int prev = spinBox->blockSignals(true);
if (value.toString() == "unavailable")
{
spinBox->setStyleSheet("QDoubleSpinBox { background-color : gray; }");
}
else if (value.toString() == "error")
{
spinBox->setStyleSheet("QDoubleSpinBox { background-color : red; }");
}
else
{
double state = value.toDouble();
if (controlInfo) {
state = state / controlInfo->m_scale;
}
bool outOfRange = (state < spinBox->minimum()) || (state > spinBox->maximum());
spinBox->setValue(state);
if (outOfRange) {
spinBox->setStyleSheet("QDoubleSpinBox { background-color : red; }");
} else {
spinBox->setStyleSheet("");
}
}
spinBox->blockSignals(prev);
}
else if (QDial *dial = qobject_cast<QDial *>(widget))
{
int prev = dial->blockSignals(true);
if (value.toString() == "unavailable")
{
dial->setStyleSheet("QDial { background-color : gray; }");
}
else if (value.toString() == "error")
{
dial->setStyleSheet("QDial { background-color : red; }");
}
else
{
double state = value.toDouble();
if (controlInfo) {
state = state / controlInfo->m_scale;
}
bool outOfRange = (state < dial->minimum()) || (state > dial->maximum());
dial->setValue(state);
if (outOfRange) {
dial->setStyleSheet("QDial { background-color : red; }");
} else {
dial->setStyleSheet("");
}
}
dial->blockSignals(prev);
}
else if (QSlider *slider = qobject_cast<QSlider *>(widget))
{
int prev = slider->blockSignals(true);
if (value.toString() == "unavailable")
{
slider->setStyleSheet("QSlider { background-color : gray; }");
}
else if (value.toString() == "error")
{
slider->setStyleSheet("QSlider { background-color : red; }");
}
else
{
double state = value.toDouble();
if (controlInfo) {
state = state / controlInfo->m_scale;
}
bool outOfRange = (state < slider->minimum()) || (state > slider->maximum());
slider->setValue(state);
if (outOfRange) {
slider->setStyleSheet("QSlider { background-color : red; }");
} else {
slider->setStyleSheet("");
}
}
slider->blockSignals(prev);
}
else if (QComboBox *comboBox = qobject_cast<QComboBox *>(widget))
{
int prev = comboBox->blockSignals(true);
QString string = value.toString();
int index = comboBox->findText(string);
if (index != -1)
{
comboBox->setCurrentIndex(index);
comboBox->setStyleSheet("");
}
else
{
comboBox->setStyleSheet("QComboBox { background-color : red; }");
}
comboBox->blockSignals(prev);
}
else if (QLineEdit *lineEdit = qobject_cast<QLineEdit *>(widget))
{
lineEdit->setText(value.toString());
}
else if (QLabel *label = qobject_cast<QLabel *>(widget))
{
label->setText(value.toString());
}
else
{
qDebug() << "RemoteControlGUI::updateControl: Unexpected widget type";
}
}
void RemoteControlGUI::updateChart(RemoteControlDeviceGUI *deviceGUI, const QString &key, const QVariant &value)
{
// Format the value for display
bool ok = false;
double d = value.toDouble(&ok);
bool iOk = false;
int iValue = value.toInt(&iOk);
QString formattedValue;
RemoteControlSensor *sensor = deviceGUI->m_rcDevice->getSensor(key);
QString format = sensor->m_format.trimmed();
if (format.contains("%s"))
{
formattedValue = QString::asprintf(format.toUtf8(), value.toString().toUtf8().data());
}
else if (format.contains("%d") || format.contains("%u") || format.contains("%x") || format.contains("%X"))
{
formattedValue = QString::asprintf(format.toUtf8(), value.toInt());
}
else if (((QMetaType::Type)value.type() == QMetaType::Double) || ((QMetaType::Type)value.type() == QMetaType::Float))
{
if (format.isEmpty()) {
format = "%.1f";
}
formattedValue = QString::asprintf(format.toUtf8(), value.toDouble());
}
else if (iOk)
{
formattedValue = QString::asprintf("%d", iValue);
}
else
{
formattedValue = value.toString();
}
// Update sensor value widget to display the latest value
if (deviceGUI->m_sensorValueLabels.contains(key)) {
deviceGUI->m_sensorValueLabels.value(key)->setText(formattedValue);
} else {
deviceGUI->m_sensorValueItems.value(key)->setText(formattedValue);
}
// Plot value on chart
if (deviceGUI->m_series.contains(key))
{
QLineSeries *onePointSeries = deviceGUI->m_onePointSeries.value(key);
QLineSeries *series = deviceGUI->m_series.value(key);
QDateTime dt = QDateTime::currentDateTime();
if (ok)
{
// Charts aren't displayed properly if series has only one point,
// so we save the first point in an additional series: onePointSeries
if (onePointSeries->count() == 0)
{
onePointSeries->append(dt.toMSecsSinceEpoch(), d);
}
else
{
if (series->count() == 0) {
series->append(onePointSeries->at(0));
}
series->append(dt.toMSecsSinceEpoch(), d);
QList<QAbstractAxis *> axes = deviceGUI->m_chart->axes(Qt::Horizontal, series);
QDateTimeAxis *dtAxis = (QDateTimeAxis *)axes[0];
QDateTime start = QDateTime::fromMSecsSinceEpoch(series->at(0).x());
QDateTime end = QDateTime::fromMSecsSinceEpoch(series->at(series->count() - 1).x());
if (start.date() == end.date())
{
if (start.secsTo(end) < 60*5) {
dtAxis->setFormat(QString("hh:mm:ss"));
} else {
dtAxis->setFormat(QString("hh:mm"));
}
}
else
{
dtAxis->setFormat(QString("%1 hh:mm").arg(QLocale::system().dateFormat(QLocale::ShortFormat)));
}
dtAxis->setRange(start, end);
axes = deviceGUI->m_chart->axes(Qt::Vertical, series);
QValueAxis *yAxis = (QValueAxis *)axes[0];
if (series->count() == 2)
{
double y1 = series->at(0).y();
double y2 = series->at(1).y();
double yMin = std::min(y1, y2);
double yMax = std::max(y1, y2);
double min = (yMin >= 0.0) ? yMin * 0.9 : yMin * 1.1;
double max = (yMax >= 0.0) ? yMax * 1.1 : yMax * 0.9;
yAxis->setRange(min, max);
}
else
{
double min = (d >= 0.0) ? d * 0.9 : d * 1.1;
double max = (d >= 0.0) ? d * 1.1 : d * 0.9;
if (min < yAxis->min()) {
yAxis->setMin(min);
}
if (max > yAxis->max()) {
yAxis->setMax(max);
}
}
}
}
else
{
qDebug() << "RemoteControlGUI::deviceUpdated: Error converting " << key << value;
}
}
}
void RemoteControlGUI::deviceUpdated(const QString &protocol, const QString &deviceId, const QHash<QString, QVariant> &status)
{
for (auto deviceGUI : m_deviceGUIs)
{
if ( (protocol == deviceGUI->m_rcDevice->m_protocol)
&& (deviceId == deviceGUI->m_rcDevice->m_info.m_id))
{
deviceGUI->m_container->setEnabled(true);
QHashIterator<QString, QVariant> itr(status);
while (itr.hasNext())
{
itr.next();
QString key = itr.key();
QVariant value = itr.value();
if (deviceGUI->m_controls.contains(key))
{
// Update control(s) to display latest state
QList<QWidget *> widgets = deviceGUI->m_controls.value(key);
DeviceDiscoverer::ControlInfo *control = deviceGUI->m_rcDevice->m_info.getControl(key);
for (auto widget : widgets) {
updateControl(widget, control, key, value);
}
}
else if (deviceGUI->m_sensorValueLabels.contains(key) || deviceGUI->m_sensorValueItems.contains(key))
{
// Plot on chart
updateChart(deviceGUI, key, value);
}
else
{
qDebug() << "RemoteControlGUI::deviceUpdated: Unexpected status key " << key << value;
}
}
}
}
}
void RemoteControlGUI::deviceUnavailable(const QString &protocol, const QString &deviceId)
{
for (auto deviceGUI : m_deviceGUIs)
{
if ( (protocol == deviceGUI->m_rcDevice->m_protocol)
&& (deviceId == deviceGUI->m_rcDevice->m_info.m_id))
{
deviceGUI->m_container->setEnabled(false);
}
}
}
void RemoteControlGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
RemoteControl::MsgConfigureRemoteControl* message = RemoteControl::MsgConfigureRemoteControl::create(m_settings, force);
m_remoteControl->getInputMessageQueue()->push(message);
}
}
void RemoteControlGUI::makeUIConnections()
{
QObject::connect(ui->startStop, &ButtonSwitch::toggled, this, &RemoteControlGUI::on_startStop_toggled);
QObject::connect(ui->update, &QToolButton::clicked, this, &RemoteControlGUI::on_update_clicked);
QObject::connect(ui->settings, &QToolButton::clicked, this, &RemoteControlGUI::on_settings_clicked);
QObject::connect(ui->clearData, &QToolButton::clicked, this, &RemoteControlGUI::on_clearData_clicked);
}