Page MenuHomec4science

mainwindow.cpp
No OneTemporary

File Metadata

Created
Thu, Nov 7, 09:18

mainwindow.cpp

/*
* Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QDebug>
#include <QInputDialog>
#include <QFileDialog>
#include <QList>
#include <QDateTime>
#include <limits>
#define GRAPH_UPDATE_PERIOD 50 ///< Plot window refresh period [ms].
#define LOG_CHECKBOX_BASE_LABEL QString("Log to CSV file")
#define SETTING_WINDOW_SIZE "window_size"
#define SETTING_LOGS_DIR "logs_dir"
/**
* @brief Constructor
* Setups the GUI, and try to connect to the HRI board.
* @param parent parent of this widget.
*/
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
//
ui->setupUi(this);
variablesListLayout = new QGridLayout();
variablesListLayout->setAlignment(Qt::AlignTop);
delete ui->scrollWidget->layout();
ui->scrollWidget->setLayout(variablesListLayout);
chart = new QtCharts::QChart();
ui->graphicsView->setChart(chart);
ui->graphicsView->setRenderHint(QPainter::Antialiasing);
graphUpdateTimer.setInterval(GRAPH_UPDATE_PERIOD);
graphUpdateTimer.setSingleShot(false);
connect(&graphUpdateTimer, SIGNAL(timeout()), this, SLOT(updateGraph()));
//
syncVars = nullptr;
// Recover the previous logs save location, or define if this is the first
// time the app is started.
if(settings.contains(SETTING_LOGS_DIR))
logsDirectory = settings.value(SETTING_LOGS_DIR).toString();
else
{
logsDirectory = qApp->applicationDirPath();
#if defined(Q_OS_MACX) || defined(Q_OS_MAC64)
// For MacOSX, go to out of the app bundle.
logsDirectory.remove(QRegExp("/[^/]*.app/Contents/MacOS/?$"));
#endif
}
ui->logToFileCheckbox->setText(LOG_CHECKBOX_BASE_LABEL +
" (in " + logsDirectory + ")");
// Resize the window to the previous size.
if(settings.contains(SETTING_WINDOW_SIZE))
resize(settings.value(SETTING_WINDOW_SIZE).toSize());
// Ask for the COM port.
QString comPortName;
QStringList ports = HriBoard::getComPorts();
if(ports.size() == 0)
{
QMessageBox::critical(this, qApp->applicationName(), "No COM port.");
exit(0);
}
else if(ports.size() == 1)
comPortName = ports.first();
else
{
bool ok;
QString portChoice = QInputDialog::getItem(this,
qApp->applicationName(),
"Serial port:", ports, 0,
false, &ok);
int portIndex = ports.indexOf(portChoice);
if(portIndex == -1 || !ok)
exit(0);
comPortName = ports[portIndex];
}
//
connect(ui->clearButton, SIGNAL(clicked(bool)), this, SLOT(clearPlot()));
connect(ui->logToFileCheckbox, SIGNAL(toggled(bool)),
this, SLOT(onLogToFileCheckboxToggled()));
connect(ui->setLogLocationButton, SIGNAL(clicked(bool)),
this, SLOT(setLogfilesDirectory()));
// Establish the link with the HRI board.
try
{
hriBoard.openLink(comPortName);
}
catch(std::runtime_error)
{
QMessageBox::critical(nullptr, qApp->applicationName(),
"Could not open the serial port " + comPortName);
exit(0);
}
connect(&hriBoard, SIGNAL(syncVarsListReceived(const QList<SyncVarBase*>&)),
this, SLOT(onVarsListReceived(const QList<SyncVarBase*>&)));
connect(&hriBoard, SIGNAL(syncVarUpdated(SyncVarBase*)),
this, SLOT(onVarUpdated(SyncVarBase*)));
}
/**
* @brief Destructor.
*/
MainWindow::~MainWindow()
{
// Store the settings.
settings.setValue(SETTING_WINDOW_SIZE, size());
settings.setValue(SETTING_LOGS_DIR, logsDirectory);
//
delete ui;
}
/**
* @brief Displays the SyncVars list, and starts the streaming.
* @param syncVars SyncVars list.
* @remark This slot function is called automatically by the HriBoard object, as
* soon as the list is received from the board.
*/
void MainWindow::onVarsListReceived(const QList<SyncVarBase *> &syncVars)
{
this->syncVars = &syncVars;
QLayoutItem *li;
while((li = variablesListLayout->takeAt(0)) != nullptr)
li->widget()->deleteLater();
syncVarsWidgets.clear();
for(int i=0; i<syncVars.size(); i++)
{
SyncVarWidgetsLine varsWidgets;
// Name label.
varsWidgets.name = new QLabel(syncVars[i]->getName());
variablesListLayout->addWidget(varsWidgets.name, i, 0);
// Get button.
if(syncVars[i]->getAccess() != WRITEONLY)
{
varsWidgets.getButton = new QPushButton("Get");
variablesListLayout->addWidget(varsWidgets.getButton, i, 1);
connect(varsWidgets.getButton, SIGNAL(clicked(bool)),
this, SLOT(onGetButtonPressed()));
}
// Value field.
varsWidgets.valueSpinBox = new QDoubleSpinBox();
varsWidgets.valueSpinBox->setDecimals(4);
varsWidgets.valueSpinBox->setMinimum(std::numeric_limits<double>::lowest());
varsWidgets.valueSpinBox->setMaximum(std::numeric_limits<double>::max());
variablesListLayout->addWidget(varsWidgets.valueSpinBox, i, 2);
if(syncVars[i]->getAccess() == READONLY)
{
varsWidgets.valueSpinBox->setReadOnly(true);
varsWidgets.valueSpinBox->setButtonSymbols(QAbstractSpinBox::NoButtons);
}
else
{
connect(varsWidgets.valueSpinBox, SIGNAL(editingFinished()),
this, SLOT(onSetButtonPressed()));
}
// Set button.
if(syncVars[i]->getAccess() != READONLY)
{
varsWidgets.setButton = new QPushButton("Set");
variablesListLayout->addWidget(varsWidgets.setButton, i, 3);
connect(varsWidgets.setButton, SIGNAL(clicked(bool)),
this, SLOT(onSetButtonPressed()));
}
// Stream checkbox.
if(syncVars[i]->getAccess() != WRITEONLY)
{
varsWidgets.streamCheckbox = new QCheckBox("Stream");
variablesListLayout->addWidget(varsWidgets.streamCheckbox, i, 4);
connect(varsWidgets.streamCheckbox, SIGNAL(toggled(bool)),
this, SLOT(onStreamCheckboxToggled()));
}
if(syncVars[i]->getAccess() != WRITEONLY)
{
varsWidgets.scaleSpinbox = new QDoubleSpinBox();
variablesListLayout->addWidget(varsWidgets.scaleSpinbox, i, 5);
varsWidgets.scaleSpinbox->setPrefix("x");
varsWidgets.scaleSpinbox->setValue(1.0);
varsWidgets.scaleSpinbox->setRange(-1000000.0, 1000000.0);
}
syncVarsWidgets.append(varsWidgets);
}
//
clearPlot();
}
/**
* @brief Updates the "value" field of a SyncVar.
* @param var the SyncVar corresponding to the "value" field to update.
*/
void MainWindow::onVarUpdated(SyncVarBase *var)
{
int varIndex = syncVars->indexOf(var);
if(varIndex >= 0 && varIndex < syncVarsWidgets.size())
syncVarsWidgets[varIndex].valueSpinBox->setValue(var->toDouble());
}
/**
* @brief Gets the value of a SyncVar whose the "Get" button was pressed.
* @warning This function should only be called by the "Get" button signal.
*/
void MainWindow::onGetButtonPressed()
{
int varIndex = getVarIndexFromWidget((QWidget*)sender());
hriBoard.readRemoteVar(syncVars->at(varIndex));
}
/**
* @brief Sets the value of a SyncVar whose the "Set" button was pressed.
* @warning This function should only be called by the "Set" button signal.
*/
void MainWindow::onSetButtonPressed()
{
int varIndex = getVarIndexFromWidget((QWidget*)sender());
SyncVarBase *sv = syncVars->at(varIndex);
sv->fromDouble(syncVarsWidgets[varIndex].valueSpinBox->value());
hriBoard.writeRemoteVar(sv);
}
/**
* @brief Updates the list of SyncVars to stream from the checkboxes' states.
*/
void MainWindow::onStreamCheckboxToggled()
{
// Setup the streaming.
QList<SyncVarBase*> varsToStream;
for(int i=0; i<syncVarsWidgets.size(); i++)
{
if(syncVarsWidgets[i].streamCheckbox->isChecked())
varsToStream.append(syncVars->at(i));
}
hriBoard.setStreamedVars(varsToStream, &streamedVarsValuesBuffer);
// Setup the graph.
streamedVarsValuesBuffer.clear();
chart->removeAllSeries();
linesSeries.clear();
for(SyncVarBase *sv : varsToStream)
{
QtCharts::QLineSeries *series = new QtCharts::QLineSeries();
series->setName(sv->getName());
chart->addSeries(series);
linesSeries.append(series);
}
chart->createDefaultAxes();
chart->axisX()->setTitleText("Time [s]");
// Setup the update timer.
if(varsToStream.isEmpty())
graphUpdateTimer.stop();
else
graphUpdateTimer.start();
}
/**
* @brief Clears the plot window.
*/
void MainWindow::clearPlot()
{
for(QtCharts::QLineSeries *ls : linesSeries)
ls->clear();
}
/**
* @brief Starts or stops the streamed variables logging to file.
*/
void MainWindow::onLogToFileCheckboxToggled()
{
if(ui->logToFileCheckbox->isChecked())
{
if(!hriBoard.startLoggingToFile(logsDirectory))
{
// If the logging cannot start, uncheck the box.
QSignalBlocker signalBlocker(ui->logToFileCheckbox);
ui->logToFileCheckbox->setChecked(false);
}
}
else
hriBoard.stopLoggingToFile();
}
/**
* @brief Sets the directory to save the logfiles.
*/
void MainWindow::setLogfilesDirectory()
{
QString newDir = QFileDialog::getExistingDirectory(this,
"Select the directory to save the logfiles", logsDirectory);
if(!newDir.isEmpty())
{
logsDirectory = newDir;
ui->logToFileCheckbox->setText(LOG_CHECKBOX_BASE_LABEL +
" (in " + logsDirectory + ")");
}
}
/**
* @brief Refreshes the plot frame.
*/
void MainWindow::updateGraph()
{
// Get the variables scales.
const QList<SyncVarBase*> &streamedVars = hriBoard.getStreamedVars();
QList<double> plotScales;
for(SyncVarBase* sv : streamedVars)
{
int varIndex = syncVars->indexOf(sv);
if(varIndex != -1)
plotScales.append(syncVarsWidgets[varIndex].scaleSpinbox->value());
}
// Add the new points to the graph series.
int decimationCounter = 0;
while(!streamedVarsValuesBuffer.isEmpty())
{
if(decimationCounter <= 0)
{
decimationCounter = ui->plotDecimSpinbox->value();
QList<double> &values = streamedVarsValuesBuffer.first();
for(int i=0; i<linesSeries.size(); i++)
linesSeries[i]->append(values[0], values[i+1] * plotScales[i]);
}
else
decimationCounter--;
streamedVarsValuesBuffer.removeFirst();
}
// Remove the oldest points, to limit the number of points displayed.
for(QtCharts::QLineSeries *ls : linesSeries)
{
if(ls->count() > ui->nPointsSpinbox->value())
ls->removePoints(0, ls->count() - ui->nPointsSpinbox->value());
}
// Compute the display range.
if(linesSeries.size() > 0 && linesSeries.first()->count() > 0)
{
// Compute the horizontal (time) range.
double firstTime = linesSeries.first()->points().first().x();
double lastTime = linesSeries.first()->points().last().x();
chart->axisX()->setRange(firstTime, lastTime);
// Compute the vertical range.
double min = std::numeric_limits<double>::max();
double max = std::numeric_limits<double>::lowest();
for(QtCharts::QLineSeries *ls : linesSeries)
{
QVector<QPointF> points = ls->pointsVector();
for(QPointF p : points)
{
if(p.y() < min)
min = p.y();
if(p.y() > max)
max = p.y();
}
}
if(min == max)
{
min -= 1.0;
max += 1.0;
}
else
{
min -= (max-min) * 0.05; // Add a 5% margin at the top and the
max += (max-min) * 0.05; // bottom.
}
chart->axisY()->setRange(min, max);
}
}
/**
* @brief Gets the variable index from a widget of the SyncVars list.
* @param widget the widget belonging to the variables list.
* @return The index of the SyncVar corresponding to the given widget.
*/
int MainWindow::getVarIndexFromWidget(QWidget *widget)
{
int index = variablesListLayout->indexOf(widget);
int row, column, rowSpan, columnSpan;
variablesListLayout->getItemPosition(index, &row, &column,
&rowSpan, &columnSpan);
return row;
}

Event Timeline