552 lines
18 KiB
C++
552 lines
18 KiB
C++
/*
|
|
* Copyright (C) 2012 Open Source Robotics Foundation
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#ifdef _WIN32
|
|
// Ensure that Winsock2.h is included before Windows.h, which can get
|
|
// pulled in by anybody (e.g., Boost).
|
|
#include <Winsock2.h>
|
|
// For _access()
|
|
#include <io.h>
|
|
#endif
|
|
|
|
#include <boost/filesystem.hpp>
|
|
#include <stdio.h>
|
|
|
|
#include "gazebo/common/CommonIface.hh"
|
|
#include "gazebo/common/SystemPaths.hh"
|
|
#include "gazebo/gui/DataLogger.hh"
|
|
#include "gazebo/gui/DataLoggerPrivate.hh"
|
|
#include "gazebo/msgs/msgs.hh"
|
|
#include "gazebo/transport/transport.hh"
|
|
|
|
using namespace gazebo;
|
|
using namespace gui;
|
|
|
|
/////////////////////////////////////////////////
|
|
DataLogger::DataLogger(QWidget *_parent)
|
|
: QDialog(_parent),
|
|
dataPtr(new DataLoggerPrivate())
|
|
{
|
|
this->setWindowFlags(Qt::Window | Qt::WindowTitleHint |
|
|
Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint);
|
|
|
|
// This name is used in the qt style sheet
|
|
this->setObjectName("dataLogger");
|
|
this->setWindowIcon(QIcon(":/images/gazebo.svg"));
|
|
this->setWindowTitle(tr("Gazebo: Data Logger"));
|
|
this->setWindowFlags(Qt::Window | Qt::WindowCloseButtonHint |
|
|
Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint);
|
|
|
|
// The record button allows the user to start and pause data recording
|
|
this->dataPtr->recordButton = new QToolButton(this);
|
|
this->dataPtr->recordButton->setIcon(QPixmap(":/images/record.png"));
|
|
this->dataPtr->recordButton->setStatusTip(tr("Record a log file"));
|
|
this->dataPtr->recordButton->setCheckable(true);
|
|
this->dataPtr->recordButton->setChecked(false);
|
|
this->dataPtr->recordButton->setIconSize(QSize(30, 30));
|
|
this->dataPtr->recordButton->setObjectName("dataLoggerRecordButton");
|
|
connect(this->dataPtr->recordButton, SIGNAL(toggled(bool)),
|
|
this, SLOT(OnRecord(bool)));
|
|
|
|
// Textual status information
|
|
this->dataPtr->statusLabel = new QLabel("Ready");
|
|
this->dataPtr->statusLabel->setObjectName("dataLoggerStatusLabel");
|
|
this->dataPtr->statusLabel->setStyleSheet(
|
|
"QLabel{padding-left: 14px; padding-top: 9px}");
|
|
|
|
// Duration of logging
|
|
this->dataPtr->timeLabel = new QLabel("00:00:00.000");
|
|
this->dataPtr->timeLabel->setObjectName("dataLoggerTimeLabel");
|
|
this->dataPtr->timeLabel->setFixedWidth(90);
|
|
|
|
// Size of log file
|
|
this->dataPtr->sizeLabel = new QLabel("(0.00 B)");
|
|
this->dataPtr->sizeLabel->setObjectName("dataLoggerSizeLabel");
|
|
|
|
// Horizontal separator
|
|
QFrame *separator = new QFrame();
|
|
separator->setFrameShape(QFrame::HLine);
|
|
separator->setLineWidth(1);
|
|
separator->setFixedHeight(30);
|
|
|
|
// Address label
|
|
QLabel *uriLabel = new QLabel("Address: ");
|
|
uriLabel->setStyleSheet(
|
|
"QLabel {color: #aeaeae; font-size: 11px; background: transparent}");
|
|
|
|
// Address URI Line Edit
|
|
this->dataPtr->destURI = new QLineEdit;
|
|
this->dataPtr->destURI->setReadOnly(true);
|
|
this->dataPtr->destURI->setObjectName("dataLoggerDestnationURILabel");
|
|
this->dataPtr->destURI->setStyleSheet(
|
|
"QLineEdit {color: #aeaeae; font-size: 11px; background: transparent}");
|
|
|
|
// "Save to" label
|
|
QLabel *pathLabel = new QLabel("Save to: ");
|
|
pathLabel->setStyleSheet(
|
|
"QLabel {\
|
|
color: #aeaeae;\
|
|
font-size: 11px;\
|
|
background: transparent;\
|
|
padding-top: 4px;\
|
|
}");
|
|
|
|
// Destination path Text Edit
|
|
this->dataPtr->destPath = new QPlainTextEdit();
|
|
this->dataPtr->destPath->setObjectName("dataLoggerDestnationPathLabel");
|
|
this->dataPtr->destPath->setMinimumWidth(200);
|
|
this->dataPtr->destPath->setMaximumHeight(50);
|
|
this->dataPtr->destPath->setReadOnly(true);
|
|
this->dataPtr->destPath->setFrameStyle(QFrame::NoFrame);
|
|
this->dataPtr->destPath->setLineWrapMode(QPlainTextEdit::WidgetWidth);
|
|
this->dataPtr->destPath->setWordWrapMode(QTextOption::WrapAnywhere);
|
|
this->dataPtr->destPath->setStyleSheet(
|
|
"QPlainTextEdit {\
|
|
color: #aeaeae;\
|
|
font-size: 11px;\
|
|
background: transparent;\
|
|
}");
|
|
|
|
// Browser button
|
|
QPushButton *browseButton = new QPushButton("Browse");
|
|
browseButton->setFixedWidth(100);
|
|
browseButton->setFocusPolicy(Qt::NoFocus);
|
|
connect(browseButton, SIGNAL(clicked()), this, SLOT(OnBrowse()));
|
|
|
|
// Button which toggles recordings
|
|
QRadioButton *recordingsButton = new QRadioButton();
|
|
recordingsButton->setChecked(false);
|
|
recordingsButton->setFocusPolicy(Qt::NoFocus);
|
|
recordingsButton->setText("Recordings");
|
|
recordingsButton->setStyleSheet(
|
|
"QRadioButton {\
|
|
color: #d0d0d0;\
|
|
}\
|
|
QRadioButton::indicator::unchecked {\
|
|
image: url(:/images/right_arrow.png);\
|
|
}\
|
|
QRadioButton::indicator::checked {\
|
|
image: url(:/images/down_arrow.png);\
|
|
}");
|
|
connect(recordingsButton, SIGNAL(toggled(bool)), this,
|
|
SLOT(OnToggleSettings(bool)));
|
|
|
|
// Insert widgets in the top layout
|
|
QGridLayout *topLayout = new QGridLayout();
|
|
topLayout->addWidget(this->dataPtr->recordButton, 0, 0, 2, 1);
|
|
topLayout->addWidget(this->dataPtr->statusLabel, 0, 1, 2, 2);
|
|
topLayout->addWidget(this->dataPtr->timeLabel, 0, 3);
|
|
topLayout->addWidget(this->dataPtr->sizeLabel, 1, 3);
|
|
topLayout->addWidget(separator, 2, 0, 1, 4);
|
|
topLayout->addWidget(uriLabel, 3, 0);
|
|
topLayout->addWidget(this->dataPtr->destURI, 3, 1, 1, 3);
|
|
topLayout->addWidget(pathLabel, 4, 0);
|
|
topLayout->addWidget(this->dataPtr->destPath, 4, 1, 1, 2);
|
|
topLayout->addWidget(browseButton, 4, 3);
|
|
topLayout->addWidget(recordingsButton, 5, 0, 1, 4);
|
|
|
|
// Align widgets within layout
|
|
topLayout->setAlignment(pathLabel, Qt::AlignTop | Qt::AlignRight);
|
|
topLayout->setAlignment(browseButton, Qt::AlignTop);
|
|
topLayout->setAlignment(this->dataPtr->statusLabel, Qt::AlignLeft);
|
|
topLayout->setAlignment(this->dataPtr->timeLabel, Qt::AlignRight);
|
|
topLayout->setAlignment(this->dataPtr->sizeLabel,
|
|
Qt::AlignRight | Qt::AlignTop);
|
|
topLayout->setAlignment(uriLabel, Qt::AlignRight);
|
|
|
|
// Put the layout in a widget to be able to control size
|
|
QWidget *topWidget = new QWidget();
|
|
topWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
|
topWidget->setLayout(topLayout);
|
|
|
|
// List of recorded logs
|
|
this->dataPtr->logList = new QTextBrowser(this);
|
|
this->dataPtr->logList->setObjectName("dataLoggerRecordingsList");
|
|
|
|
// Layout to hold the list
|
|
QVBoxLayout *settingsLayout = new QVBoxLayout;
|
|
settingsLayout->setContentsMargins(2, 2, 2, 2);
|
|
settingsLayout->addWidget(this->dataPtr->logList);
|
|
|
|
// Frame that can be hidden by toggling the expand button
|
|
this->dataPtr->settingsFrame = new QFrame();
|
|
this->dataPtr->settingsFrame->setObjectName("dataLoggerSettingFrame");
|
|
this->dataPtr->settingsFrame->setLayout(settingsLayout);
|
|
this->dataPtr->settingsFrame->setSizePolicy(QSizePolicy::Expanding,
|
|
QSizePolicy::Expanding);
|
|
this->dataPtr->settingsFrame->hide();
|
|
|
|
// Mainlayout for the whole widget
|
|
QVBoxLayout *mainLayout = new QVBoxLayout;
|
|
mainLayout->setSizeConstraint(QLayout::SetMinAndMaxSize);
|
|
mainLayout->addWidget(topWidget);
|
|
mainLayout->addWidget(this->dataPtr->settingsFrame);
|
|
|
|
// Assign the mainlayout to this widget
|
|
this->setLayout(mainLayout);
|
|
|
|
// Create a QueuedConnection to set time. This is used for thread safety.
|
|
connect(this, SIGNAL(SetTime(QString)), this->dataPtr->timeLabel,
|
|
SLOT(setText(QString)), Qt::QueuedConnection);
|
|
|
|
// Create a QueuedConnection to set size. This is used for thread safety.
|
|
connect(this, SIGNAL(SetSize(QString)), this->dataPtr->sizeLabel,
|
|
SLOT(setText(QString)), Qt::QueuedConnection);
|
|
|
|
// Create a QueuedConnection to set destination path.
|
|
// This is used for thread safety.
|
|
connect(this, SIGNAL(SetDestinationPath(QString)),
|
|
this, SLOT(OnSetDestinationPath(QString)), Qt::QueuedConnection);
|
|
|
|
// Create a QueuedConnection to set destination URI.
|
|
// This is used for thread safety.
|
|
connect(this, SIGNAL(SetDestinationURI(QString)),
|
|
this, SLOT(OnSetDestinationURI(QString)), Qt::QueuedConnection);
|
|
|
|
connect(this, SIGNAL(rejected()), this, SLOT(OnCancel()));
|
|
|
|
// Timer used to blink the status label
|
|
this->dataPtr->statusTimer = new QTimer();
|
|
connect(this->dataPtr->statusTimer,
|
|
SIGNAL(timeout()), this, SLOT(OnBlinkStatus()));
|
|
this->dataPtr->statusTime = 0;
|
|
|
|
// Timer used to hide the confirmation dialog
|
|
this->dataPtr->confirmationDialog = NULL;
|
|
this->dataPtr->confirmationTimer = new QTimer(this);
|
|
connect(this->dataPtr->confirmationTimer, SIGNAL(timeout()), this,
|
|
SLOT(OnConfirmationTimeout()));
|
|
|
|
// Create a node from communication.
|
|
this->dataPtr->node = transport::NodePtr(new transport::Node());
|
|
this->dataPtr->node->TryInit(common::Time::Maximum());
|
|
|
|
// Advertise on the log control topic. The server listens to log control
|
|
// messages.
|
|
this->dataPtr->pub =
|
|
this->dataPtr->node->Advertise<msgs::LogControl>("~/log/control");
|
|
|
|
// Subscribe to the log status topic. The server publishes log status
|
|
// messages.
|
|
this->dataPtr->sub = this->dataPtr->node->Subscribe<msgs::LogStatus>(
|
|
"~/log/status", &DataLogger::OnStatus, this);
|
|
|
|
// Fill the path with the home folder - duplicated from util/LogRecord
|
|
#ifndef _WIN32
|
|
const char *homePath = common::getEnv("HOME");
|
|
#else
|
|
const char *homePath = common::getEnv("HOMEPATH");
|
|
#endif
|
|
|
|
GZ_ASSERT(homePath, "HOME environment variable is missing");
|
|
|
|
if (!homePath)
|
|
{
|
|
common::SystemPaths *paths = common::SystemPaths::Instance();
|
|
this->dataPtr->basePath =
|
|
QString::fromStdString(paths->GetTmpPath() + "/gazebo");
|
|
}
|
|
else
|
|
{
|
|
this->dataPtr->basePath =
|
|
QString::fromStdString(boost::filesystem::path(homePath).string());
|
|
}
|
|
|
|
this->dataPtr->basePath = this->dataPtr->basePath + "/.gazebo/log/";
|
|
this->SetDestinationPath(this->dataPtr->basePath);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
DataLogger::~DataLogger()
|
|
{
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void DataLogger::OnRecord(bool _toggle)
|
|
{
|
|
// If _toggle, then we should start logging data.
|
|
if (_toggle)
|
|
{
|
|
// Switch the icon
|
|
this->dataPtr->recordButton->setIcon(QPixmap(":/images/record_stop.png"));
|
|
|
|
this->dataPtr->statusLabel->setText("Recording...");
|
|
this->dataPtr->statusTimer->start(100);
|
|
|
|
// Tell the server to start data logging
|
|
msgs::LogControl msg;
|
|
msg.set_start(true);
|
|
this->dataPtr->pub->Publish(msg);
|
|
}
|
|
// Otherwise pause data logging
|
|
else
|
|
{
|
|
// Switch the icon
|
|
this->dataPtr->recordButton->setIcon(QPixmap(":/images/record.png"));
|
|
|
|
// Display confirmation
|
|
this->dataPtr->confirmationTimer->start(2000);
|
|
QLabel *confirmationLabel = new QLabel("Saved to \n" +
|
|
this->dataPtr->destPath->toPlainText());
|
|
confirmationLabel->setObjectName("dataLoggerConfirmationLabel");
|
|
QHBoxLayout *confirmationLayout = new QHBoxLayout();
|
|
confirmationLayout->addWidget(confirmationLabel);
|
|
|
|
if (this->dataPtr->confirmationDialog)
|
|
this->dataPtr->confirmationDialog->close();
|
|
this->dataPtr->confirmationDialog =
|
|
new QDialog(this, Qt::FramelessWindowHint);
|
|
this->dataPtr->confirmationDialog->setObjectName(
|
|
"dataLoggerConfirmationDialog");
|
|
this->dataPtr->confirmationDialog->setLayout(confirmationLayout);
|
|
this->dataPtr->confirmationDialog->setStyleSheet(
|
|
"QDialog {background-color: #eee}\
|
|
QLabel {color: #111}");
|
|
this->dataPtr->confirmationDialog->setModal(false);
|
|
this->dataPtr->confirmationDialog->show();
|
|
this->dataPtr->confirmationDialog->move(this->mapToGlobal(
|
|
QPoint((this->width()-this->dataPtr->confirmationDialog->width())*0.5,
|
|
this->height() + 10)));
|
|
|
|
// Change the status
|
|
this->dataPtr->statusLabel->setText("Ready");
|
|
this->dataPtr->statusLabel->setStyleSheet(
|
|
"QLabel {\
|
|
color: #aeaeae;\
|
|
padding-left: 14px;\
|
|
padding-top: 9px;\
|
|
}");
|
|
this->dataPtr->statusTimer->stop();
|
|
|
|
// Change the Save to box
|
|
this->SetDestinationPath(this->dataPtr->basePath);
|
|
|
|
// Tell the server to stop data logging
|
|
msgs::LogControl msg;
|
|
msg.set_stop(true);
|
|
this->dataPtr->pub->Publish(msg);
|
|
|
|
this->dataPtr->logList->append(this->dataPtr->destPath->toPlainText());
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void DataLogger::OnStatus(ConstLogStatusPtr &_msg)
|
|
{
|
|
// A new log status message has arrived, let's display the contents.
|
|
common::Time time = msgs::Convert(_msg->sim_time());
|
|
std::ostringstream stream;
|
|
|
|
// Compute the hours, minutes, seconds, and milliseconds that logging has
|
|
// been running.
|
|
unsigned int hours = time.sec / 3600;
|
|
unsigned int min = (time.sec - hours * 3600) / 60;
|
|
unsigned int sec = (time.sec - hours * 3600 - min * 60);
|
|
unsigned int msec = rint(time.nsec * 1e-6);
|
|
|
|
// Display the time.
|
|
stream << std::setw(2) << std::setfill('0') << hours << ":";
|
|
stream << std::setw(2) << std::setfill('0') << min << ":";
|
|
stream << std::setw(2) << std::setfill('0') << sec << ".";
|
|
stream << std::setw(3) << std::setfill('0') << msec;
|
|
|
|
this->SetTime(QString::fromStdString(stream.str()));
|
|
|
|
// Reset the stream
|
|
stream.str("");
|
|
|
|
// If there is log file information in the message...
|
|
if (_msg->has_log_file())
|
|
{
|
|
// If there is file name information and we're recording...
|
|
if (_msg->log_file().has_base_path() &&
|
|
this->dataPtr->recordButton->isChecked())
|
|
{
|
|
std::string logBasePath = _msg->log_file().base_path();
|
|
|
|
// Display the log path
|
|
if (_msg->log_file().has_full_path() && !logBasePath.empty())
|
|
{
|
|
std::string fullPath = _msg->log_file().full_path();
|
|
if (!fullPath.empty())
|
|
this->SetDestinationPath(QString::fromStdString(fullPath));
|
|
}
|
|
}
|
|
|
|
// Display the URI
|
|
if (_msg->log_file().has_uri())
|
|
this->SetDestinationURI(QString::fromStdString(_msg->log_file().uri()));
|
|
else
|
|
this->SetDestinationURI(tr(""));
|
|
|
|
// If there is log file size information...
|
|
if (_msg->log_file().has_size() && _msg->log_file().has_size_units())
|
|
{
|
|
// Get the size of the log file.
|
|
stream << std::fixed << std::setprecision(2) << "(" <<
|
|
_msg->log_file().size();
|
|
|
|
// Get the size units.
|
|
switch (_msg->log_file().size_units())
|
|
{
|
|
case msgs::LogStatus::LogFile::BYTES:
|
|
stream << "B)";
|
|
break;
|
|
case msgs::LogStatus::LogFile::K_BYTES:
|
|
stream << "KB)";
|
|
break;
|
|
case msgs::LogStatus::LogFile::M_BYTES:
|
|
stream << "MB)";
|
|
break;
|
|
default:
|
|
stream << "GB)";
|
|
break;
|
|
}
|
|
|
|
this->SetSize(QString::fromStdString(stream.str()));
|
|
}
|
|
else
|
|
this->SetSize("(0.00 B)");
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void DataLogger::OnSetDestinationPath(QString _filename)
|
|
{
|
|
if (!_filename.isEmpty())
|
|
this->dataPtr->destPath->setPlainText(_filename);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void DataLogger::OnSetDestinationURI(QString _uri)
|
|
{
|
|
if (!_uri.isEmpty())
|
|
this->dataPtr->destURI->setText(_uri);
|
|
else
|
|
this->dataPtr->destURI->setText("");
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void DataLogger::OnToggleSettings(bool _checked)
|
|
{
|
|
if (_checked)
|
|
this->dataPtr->settingsFrame->show();
|
|
else
|
|
this->dataPtr->settingsFrame->hide();
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void DataLogger::OnBrowse()
|
|
{
|
|
QFileDialog fileDialog(this, tr("Set log directory"), QDir::homePath());
|
|
fileDialog.setFileMode(QFileDialog::Directory);
|
|
fileDialog.setFilter(QDir::AllDirs | QDir::Hidden);
|
|
fileDialog.setOptions(QFileDialog::ShowDirsOnly
|
|
| QFileDialog::DontResolveSymlinks);
|
|
fileDialog.setWindowFlags(Qt::Window | Qt::WindowCloseButtonHint |
|
|
Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint);
|
|
|
|
if (fileDialog.exec() != QDialog::Accepted)
|
|
return;
|
|
|
|
QStringList selected = fileDialog.selectedFiles();
|
|
if (selected.empty())
|
|
return;
|
|
|
|
boost::filesystem::path path = selected[0].toStdString();
|
|
|
|
// Make sure the directory exists
|
|
if (!boost::filesystem::exists(path))
|
|
{
|
|
QMessageBox msgBox(this);
|
|
std::ostringstream stream;
|
|
stream << "Directory " << path << " does not exist.";
|
|
msgBox.setText(stream.str().c_str());
|
|
msgBox.exec();
|
|
return;
|
|
}
|
|
|
|
// Make sure we have a directory
|
|
if (!boost::filesystem::is_directory(path))
|
|
{
|
|
QMessageBox msgBox(this);
|
|
std::ostringstream stream;
|
|
stream << "Path " << path << " is not a directory. Please only specify a "
|
|
<< "directory for data logging.";
|
|
msgBox.setText(stream.str().c_str());
|
|
msgBox.exec();
|
|
return;
|
|
}
|
|
|
|
// Make sure the path is writable.
|
|
// Note: This is not cross-platform compatible.
|
|
#ifdef _WIN32
|
|
// Check for write-only (2) and read-write (6)
|
|
if ((_access(path.string().c_str(), 2) != 0) &&
|
|
(_access(path.string().c_str(), 6) != 0))
|
|
#else
|
|
if (access(path.string().c_str(), W_OK) != 0)
|
|
#endif
|
|
{
|
|
QMessageBox msgBox(this);
|
|
std::ostringstream stream;
|
|
stream << "You do no have permission to write into " << path;
|
|
msgBox.setText(stream.str().c_str());
|
|
msgBox.exec();
|
|
return;
|
|
}
|
|
|
|
// Set the new base path
|
|
msgs::LogControl msg;
|
|
msg.set_base_path(path.string());
|
|
this->dataPtr->pub->Publish(msg);
|
|
|
|
this->dataPtr->basePath = QString::fromStdString(path.string());
|
|
this->SetDestinationPath(this->dataPtr->basePath);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void DataLogger::OnBlinkStatus()
|
|
{
|
|
this->dataPtr->statusTime += 1.0/10;
|
|
|
|
if (this->dataPtr->statusTime >= 1)
|
|
this->dataPtr->statusTime = 0;
|
|
|
|
this->dataPtr->statusLabel->setStyleSheet(QString::fromStdString(
|
|
"QLabel{color: rgb("+
|
|
std::to_string(255+(128*(this->dataPtr->statusTime-1)))+", "+
|
|
std::to_string(255+(128*(this->dataPtr->statusTime-1)))+", "+
|
|
std::to_string(255+(128*(this->dataPtr->statusTime-1)))+
|
|
"); "+
|
|
"padding-left: 15px; padding-top: 9px}"));
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void DataLogger::OnConfirmationTimeout()
|
|
{
|
|
this->dataPtr->confirmationDialog->close();
|
|
this->dataPtr->confirmationTimer->stop();
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void DataLogger::OnCancel()
|
|
{
|
|
if (this->dataPtr->recordButton->isChecked())
|
|
this->dataPtr->recordButton->click();
|
|
}
|