Files
giesCANne/frameplaybackwindow.cpp
T
2017-10-24 21:27:55 -04:00

715 lines
23 KiB
C++

#include "frameplaybackwindow.h"
#include "ui_frameplaybackwindow.h"
#include <QDebug>
#include <QFileDialog>
#include <QMenu>
#include <QSettings>
#include "connections/canconmanager.h"
/*
* Notes about new functionality:
* The system has a sequence of files or captured data to play back. It starts at the first item in the list
* and goes through all of the frames. When it gets to the end it sees if there is a loop count for this file.
* If so it increments the loop count and goes back to the beginning. If not it goes to the next file in the list.
* At the end of the list it either stops (if looping is off) or goes back to the first item in the list.
* All items in the list have their own frame cache and filters
*
* So, to do this we'll have to track which item we're on and which frame within that item. As we go through
* items it should highlight the row we're on
*
*/
FramePlaybackWindow::FramePlaybackWindow(const QVector<CANFrame> *frames, QWidget *parent) :
QDialog(parent),
ui(new Ui::FramePlaybackWindow)
{
ui->setupUi(this);
int numBuses = CANConManager::getInstance()->getNumBuses();
for (int n = 0; n < numBuses; n++) ui->comboCANBus->addItem(QString::number(n));
ui->comboCANBus->addItem(tr("All"));
ui->comboCANBus->addItem(tr("From File"));
ui->comboCANBus->setCurrentIndex(0);
readSettings();
modelFrames = frames;
playbackTimer = new QTimer();
playbackTimer->setTimerType(Qt::PreciseTimer);
currentPosition = 0;
playbackActive = false;
playbackForward = true;
whichBusSend = 0; //0 = no bus, 1 = bus 0, 2 = bus 1, 4 = from file - Bitfield so you can 'or' them.
currentSeqItem = NULL;
currentSeqNum = -1;
updateFrameLabel();
connect(ui->btnStepBack, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnBackOneClick);
connect(ui->btnPause, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnPauseClick);
connect(ui->btnPlayReverse, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnReverseClick);
connect(ui->btnStop, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnStopClick);
connect(ui->btnPlay, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnPlayClick);
connect(ui->btnStepForward, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnFwdOneClick);
connect(ui->btnSelectAll, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnSelectAllClick);
connect(ui->btnSelectNone, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnSelectNoneClick);
connect(ui->btnDelete, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnDeleteCurrSeq);
connect(ui->spinPlaySpeed, SIGNAL(valueChanged(int)), this, SLOT(changePlaybackSpeed(int)));
//connect(ui->cbLoop, SIGNAL(clicked(bool)), this, SLOT(changeLooping(bool)));
connect(ui->comboCANBus, SIGNAL(currentIndexChanged(int)), this, SLOT(changeSendingBus(int)));
connect(ui->listID, &QListWidget::itemClicked, this, &FramePlaybackWindow::changeIDFiltering);
connect(playbackTimer, &QTimer::timeout, this, &FramePlaybackWindow::timerTriggered);
connect(ui->btnLoadFile, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnLoadFile);
connect(ui->btnLoadLive, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnLoadLive);
connect(ui->tblSequence, &QTableWidget::cellPressed, this, &FramePlaybackWindow::seqTableCellClicked);
connect(ui->tblSequence, &QTableWidget::cellChanged, this, &FramePlaybackWindow::seqTableCellChanged);
connect(ui->btnLoadFilters, &QAbstractButton::clicked, this, &FramePlaybackWindow::loadFilters);
connect(ui->btnSaveFilters, &QAbstractButton::clicked, this, &FramePlaybackWindow::saveFilters);
connect(ui->cbOriginalTiming, &QCheckBox::toggled, this, &FramePlaybackWindow::useOrigTimingClicked);
ui->listID->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->listID, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuFilters(QPoint)));
playbackTimer->setInterval(ui->spinPlaySpeed->value()); //set the timer to the default value of the control
QStringList headers;
headers << "Source" << "Loops";
ui->tblSequence->setColumnCount(2);
ui->tblSequence->setColumnWidth(0, 260);
ui->tblSequence->setColumnWidth(1, 80);
ui->tblSequence->setHorizontalHeaderLabels(headers);
}
FramePlaybackWindow::~FramePlaybackWindow()
{
delete ui;
playbackTimer->stop();
delete playbackTimer;
}
void FramePlaybackWindow::showEvent(QShowEvent *)
{
readSettings();
}
void FramePlaybackWindow::closeEvent(QCloseEvent *event)
{
Q_UNUSED(event);
writeSettings();
}
void FramePlaybackWindow::readSettings()
{
QSettings settings;
if (settings.value("Main/SaveRestorePositions", false).toBool())
{
resize(settings.value("Playback/WindowSize", QSize(742, 606)).toSize());
move(settings.value("Playback/WindowPos", QPoint(50, 50)).toPoint());
}
if (settings.value("Playback/AutoLoop", false).toBool())
{
ui->cbLoop->setChecked(true);
}
ui->spinPlaySpeed->setValue(settings.value("Playback/DefSpeed", 5).toInt());
ui->comboCANBus->setCurrentIndex(settings.value("Playback/SendingBus", 0).toInt());
whichBusSend = ui->comboCANBus->currentIndex();
}
void FramePlaybackWindow::writeSettings()
{
QSettings settings;
if (settings.value("Main/SaveRestorePositions", false).toBool())
{
settings.setValue("Playback/WindowSize", size());
settings.setValue("Playback/WindowPos", pos());
}
}
void FramePlaybackWindow::contextMenuFilters(QPoint pos)
{
QMenu *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->addAction(tr("Save filter definition to file"), this, SLOT(saveFilters()));
menu->addAction(tr("Load filter definition from file"), this, SLOT(loadFilters()));
menu->popup(ui->listID->mapToGlobal(pos));
}
void FramePlaybackWindow::saveFilters()
{
QString filename;
QFileDialog dialog(this);
QStringList filters;
filters.append(QString(tr("Filter list (*.ftl)")));
dialog.setFileMode(QFileDialog::AnyFile);
dialog.setNameFilters(filters);
dialog.setViewMode(QFileDialog::Detail);
dialog.setAcceptMode(QFileDialog::AcceptSave);
if (dialog.exec() == QDialog::Accepted)
{
filename = dialog.selectedFiles()[0];
if (!filename.contains('.')) filename += ".ftl";
if (dialog.selectedNameFilter() == filters[0])
{
QFile *outFile = new QFile(filename);
if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text))
return;
for (int c = 0; c < ui->listID->count(); c++)
{
outFile->write(QString::number(ui->listID->item(c)->text().toInt(NULL, 16), 16).toUtf8());
outFile->putChar(',');
if (ui->listID->item(c)->checkState() == Qt::Checked) outFile->putChar('T');
else outFile->putChar('F');
outFile->write("\n");
}
outFile->close();
}
}
}
void FramePlaybackWindow::loadFilters()
{
QString filename;
QFileDialog dialog(this);
QStringList filters;
filters.append(QString(tr("Filter List (*.ftl)")));
dialog.setFileMode(QFileDialog::ExistingFile);
dialog.setNameFilters(filters);
dialog.setViewMode(QFileDialog::Detail);
if (dialog.exec() == QDialog::Accepted)
{
filename = dialog.selectedFiles()[0];
//right now there is only one file type that can be loaded here so just do it.
QFile *inFile = new QFile(filename);
QByteArray line;
int ID;
bool checked = false;
if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text))
return;
btnSelectNoneClick();
while (!inFile->atEnd()) {
line = inFile->readLine().simplified();
if (line.length() > 2)
{
QList<QByteArray> tokens = line.split(',');
ID = tokens[0].toInt(NULL, 16);
if (tokens[1].toUpper() == "T") checked = true;
else checked = false;
if (checked)
{
QHash<int,bool>::iterator it;
for (it = currentSeqItem->idFilters.begin(); it != currentSeqItem->idFilters.end(); ++it)
{
if (it.key() == ID)
{
it.value() = true;
}
}
for (int c = 0; c < ui->listID->count(); c++)
{
QListWidgetItem *item = ui->listID->item(c);
if (item->text().toInt(NULL, 16) == ID)
{
item->setCheckState(Qt::Checked);
}
}
}
}
}
inFile->close();
}
}
void FramePlaybackWindow::refreshIDList()
{
if (currentSeqNum < 0 || currentSeqItem == NULL) return;
ui->tblSequence->setCurrentCell(currentSeqNum, 0);
ui->listID->clear();
QHash<int, bool>::Iterator filterIter;
for (filterIter = currentSeqItem->idFilters.begin(); filterIter != currentSeqItem->idFilters.end(); ++filterIter)
{
QListWidgetItem* listItem = new QListWidgetItem(Utility::formatNumber(filterIter.key()), ui->listID);
listItem->setFlags(listItem->flags() | Qt::ItemIsUserCheckable); // set checkable flag
if (filterIter.value()) listItem->setCheckState(Qt::Checked);
else listItem->setCheckState(Qt::Unchecked);
}
//default is to sort in ascending order
ui->listID->sortItems();
}
void FramePlaybackWindow::updateFrameLabel()
{
int row = currentSeqNum;
if (row != ui->tblSequence->currentRow())
{
ui->tblSequence->setCurrentCell(row, 0);
}
if (row == -1)
{
ui->lblCurrPlayback->setText("");
ui->lblPosition->setText("");
return;
}
ui->lblCurrPlayback->setText(currentSeqItem->filename);
ui->lblPosition->setText(QString::number(currentPosition) + tr(" of ") + QString::number(seqItems[row].data.count()));
}
void FramePlaybackWindow::seqTableCellClicked(int row, int col)
{
qDebug() << "Row: " << QString::number(row) << " Col: " << QString::number(col);
if (currentSeqNum != row)
{
currentSeqNum = row;
currentSeqItem = &seqItems[row];
refreshIDList();
}
}
void FramePlaybackWindow::seqTableCellChanged(int row,int col)
{
if (col == 1) //don't save any changes to the first column (0)
{
seqItems[row].maxLoops = Utility::ParseStringToNum(ui->tblSequence->item(row, col)->text());
if (seqItems[row].maxLoops < 1) seqItems[row].maxLoops = 1;
}
}
void FramePlaybackWindow::fillIDHash(SequenceItem &item)
{
int id;
item.idFilters.clear();
for (int i = 0; i < item.data.count(); i++)
{
id = item.data[i].ID;
if (!item.idFilters.contains(id))
{
item.idFilters.insert(id, true);
}
}
}
void FramePlaybackWindow::useOrigTimingClicked()
{
if (ui->cbOriginalTiming->isChecked())
{
ui->spinBurstSpeed->setEnabled(false);
ui->spinPlaySpeed->setEnabled(false);
}
else
{
ui->spinBurstSpeed->setEnabled(true);
ui->spinPlaySpeed->setEnabled(true);
}
}
void FramePlaybackWindow::btnDeleteCurrSeq()
{
if (currentSeqNum == -1) return;
if (playbackActive)
{
playbackActive = false;
playbackTimer->stop();
}
seqItems.removeAt(currentSeqNum);
ui->tblSequence->removeRow(currentSeqNum);
if (seqItems.count() > 0)
{
currentSeqNum = 0;
currentSeqItem = &seqItems[currentSeqNum];
}
else
{
currentSeqNum = -1;
currentSeqItem = NULL;
}
updateFrameLabel();
}
void FramePlaybackWindow::btnLoadFile()
{
QString filename;
SequenceItem item;
if (FrameFileIO::loadFrameFile(filename, &item.data))
{
qSort(item.data); //sort by timestamp to be sure it's in order
QStringList fileList = filename.split('/');
item.filename = fileList[fileList.length() - 1];
item.currentLoopCount = 0;
item.maxLoops = 1;
fillIDHash(item);
if (ui->tblSequence->currentRow() == -1)
{
ui->tblSequence->setCurrentCell(0,0);
}
seqItems.append(item);
int row = ui->tblSequence->rowCount();
ui->tblSequence->insertRow(row);
ui->tblSequence->setItem(row, 0, new QTableWidgetItem(item.filename));
ui->tblSequence->setItem(row, 1, new QTableWidgetItem(QString::number(item.maxLoops)));
qDebug() << currentSeqNum;
if (currentSeqNum == -1)
{
currentSeqNum = 0;
currentSeqItem = &seqItems[0];
}
refreshIDList();
updateFrameLabel();
}
}
void FramePlaybackWindow::btnLoadLive()
{
SequenceItem item;
item.filename = "<CAPTURED DATA>";
item.currentLoopCount = 0;
item.maxLoops = 1;
item.data = QVector<CANFrame>(*modelFrames); //create a copy of the current frames from the main view
qSort(item.data); //be sure it's all in time based order
fillIDHash(item);
if (ui->tblSequence->currentRow() == -1)
{
ui->tblSequence->setCurrentCell(0,0);
}
seqItems.append(item);
int row = ui->tblSequence->rowCount();
ui->tblSequence->insertRow(row);
ui->tblSequence->setItem(row, 0, new QTableWidgetItem(item.filename));
ui->tblSequence->setItem(row, 1, new QTableWidgetItem(QString::number(item.maxLoops)));
if (currentSeqNum == -1)
{
currentSeqNum = 0;
currentSeqItem = &seqItems[0];
}
refreshIDList();
updateFrameLabel();
btnStopClick();
}
void FramePlaybackWindow::btnBackOneClick()
{
sendingBuffer.clear();
playbackTimer->stop(); //pushing this button halts automatic playback
playbackActive = false;
updatePosition(false);
CANConManager::getInstance()->sendFrames(sendingBuffer);
}
void FramePlaybackWindow::btnPauseClick()
{
playbackActive = false;
playbackTimer->stop();
}
void FramePlaybackWindow::btnReverseClick()
{
playbackActive = true;
playbackForward = false;
if (ui->cbOriginalTiming->isChecked())
{
playbackElapsed.start();
playbackTimer->setInterval(1);
playbackLastTimeStamp = currentSeqItem->data[currentPosition].timestamp + 2000;
}
playbackTimer->start();
}
void FramePlaybackWindow::btnStopClick()
{
playbackTimer->stop(); //pushing this button halts automatic playback
playbackActive = false;
currentPosition = 0;
if (seqItems.count() > 0)
{
currentSeqNum = 0;
currentSeqItem = &seqItems[currentSeqNum];
}
else {
currentSeqNum = -1;
currentSeqItem = NULL;
}
if (ui->tblSequence->rowCount() > 0)
{
ui->tblSequence->setCurrentCell(0, 0);
refreshIDList();
}
updateFrameLabel();
}
void FramePlaybackWindow::btnPlayClick()
{
playbackActive = true;
playbackForward = true;
if (ui->cbOriginalTiming->isChecked())
{
playbackTimer->setInterval(1);
playbackElapsed.start();
if (currentSeqItem->data[currentPosition].timestamp > 1000)
playbackLastTimeStamp = currentSeqItem->data[currentPosition].timestamp - 2000;
else playbackLastTimeStamp = 0;
}
playbackTimer->start();
}
void FramePlaybackWindow::btnFwdOneClick()
{
sendingBuffer.clear();
playbackTimer->stop();
playbackActive = false;
updatePosition(true);
CANConManager::getInstance()->sendFrames(sendingBuffer);
}
void FramePlaybackWindow::changePlaybackSpeed(int newSpeed)
{
playbackTimer->setInterval(newSpeed);
}
void FramePlaybackWindow::changeLooping(bool check)
{
Q_UNUSED(check);
}
void FramePlaybackWindow::changeSendingBus(int newIdx)
{
whichBusSend = newIdx;
}
void FramePlaybackWindow::changeIDFiltering(QListWidgetItem *item)
{
qDebug() << "Changed ID filter";
int ID = Utility::ParseStringToNum(item->text());
currentSeqItem->idFilters[ID] = item->checkState();
}
void FramePlaybackWindow::btnSelectAllClick()
{
for (int i = 0; i < ui->listID->count(); i++)
{
QListWidgetItem *item = ui->listID->item(i);
item->setCheckState(Qt::Checked);
currentSeqItem->idFilters[Utility::ParseStringToNum(item->text())] = true;
}
}
void FramePlaybackWindow::btnSelectNoneClick()
{
for (int i = 0; i < ui->listID->count(); i++)
{
QListWidgetItem *item = ui->listID->item(i);
item->setCheckState(Qt::Unchecked);
currentSeqItem->idFilters[Utility::ParseStringToNum(item->text())] = false;
}
}
void FramePlaybackWindow::timerTriggered()
{
sendingBuffer.clear();
if (ui->cbOriginalTiming->isChecked())
{
//get elapsed microseconds since last tick (in case timer skips or is otherwise inaccurate, though there are no guarantees about elapsed timer either)
quint64 elapsed = playbackElapsed.nsecsElapsed() / 1000;
playbackElapsed.start();
if (playbackForward)
{
playbackLastTimeStamp += elapsed;
//qDebug() << playbackLastTimeStamp;
while (peekPosition(true) <= playbackLastTimeStamp)
{
updatePosition(true);
}
if (peekPosition(true) == 0xFFFFFFFFFFFFFFFFull)
{
updatePosition(true); //this'll go to the next log (if there is one)
if (currentSeqItem->data[currentPosition].timestamp > 1000)
playbackLastTimeStamp = currentSeqItem->data[currentPosition].timestamp - 1000;
else playbackLastTimeStamp = 0;
}
}
else
{
if (playbackLastTimeStamp > elapsed)
playbackLastTimeStamp -= elapsed;
else playbackLastTimeStamp = 0;
while (peekPosition(false) >= playbackLastTimeStamp && peekPosition(false) != 0xFFFFFFFFFFFFFFFFull)
{
updatePosition(false);
}
if (peekPosition(false) == 0xFFFFFFFFFFFFFFFFull)
{
updatePosition(false); //this'll go to the next log (if there is one)
playbackLastTimeStamp = currentSeqItem->data[currentPosition].timestamp + 1000;
}
}
}
else
{
for (int count = 0; count < ui->spinBurstSpeed->value(); count++)
{
if (!playbackActive)
{
playbackTimer->stop();
return;
}
if (playbackForward)
{
updatePosition(true);
}
else
{
updatePosition(false);
}
}
}
CANConManager::getInstance()->sendFrames(sendingBuffer);
}
quint64 FramePlaybackWindow::updatePosition(bool forward)
{
//qDebug() << "updatePosition";
if (forward)
{
if (currentPosition < (currentSeqItem->data.count() - 1)) currentPosition++; //still in same file so keep going
else //hit the end of the current file
{
qDebug() << "hit end of current sequence";
currentSeqItem->currentLoopCount++;
currentPosition = 0;
if (currentSeqItem->currentLoopCount == currentSeqItem->maxLoops) //have we looped enough times?
{
currentSeqNum++; //go forward in the sequence
if (currentSeqNum == seqItems.count()) //are we at the end of the sequence?
{
//reset the loop figures for each sequence entry
for (int i = 0; i < seqItems.count(); i++) seqItems[i].currentLoopCount = 0;
currentSeqNum = 0;
if (ui->cbLoop->isChecked()) //go back to beginning if we're looping the sequence
{
}
else //not looping so stop playback entirely
{
playbackActive = false;
playbackTimer->stop();
}
}
currentSeqItem = &seqItems[currentSeqNum];
ui->tblSequence->setCurrentCell(currentSeqNum, 0);
}
}
}
else
{
if (currentPosition > 0) currentPosition--;
else //hit the beginning of the current sequence
{
qDebug() << "hit start of current sequence";
currentSeqItem->currentLoopCount++;
currentPosition = currentSeqItem->data.count() - 1;
if (currentSeqItem->currentLoopCount == currentSeqItem->maxLoops) //have we looped enough times?
{
currentSeqNum--; //go backward in the sequence
if (currentSeqNum == -1) //are we trying to go past the beginning?
{
//reset the loop figures for each sequence entry
for (int i = 0; i < seqItems.count(); i++) seqItems[i].currentLoopCount = 0;
currentSeqNum = seqItems.count() - 1;
if (ui->cbLoop->isChecked()) //go back to the last sequence entry if we're looping
{
}
else //not looping so stop playback entirely
{
playbackActive = false;
playbackTimer->stop();
}
}
currentSeqItem = &seqItems[currentSeqNum];
ui->tblSequence->setCurrentCell(currentSeqNum, 0);
}
}
}
updateFrameLabel();
//only send frame out if its ID is checked in the list. Otherwise discard it.
CANFrame *thisFrame = &currentSeqItem->data[currentPosition];
uint32_t originalBus = thisFrame->bus;
if (currentSeqItem->idFilters.find(thisFrame->ID).value())
{
if (whichBusSend < ui->comboCANBus->count() - 2)
{
thisFrame->bus = whichBusSend;
sendingBuffer.append(*thisFrame);
}
else if (whichBusSend == (ui->comboCANBus->count() - 2)) //all
{
for (int c = 0; c < ui->comboCANBus->count() - 2; c++)
{
thisFrame->bus = c;
sendingBuffer.append(*thisFrame);
}
}
else //from file so retain original bus and send as-is
{
sendingBuffer.append(*thisFrame);
}
thisFrame->bus = originalBus;
}
return thisFrame->timestamp;
}
quint64 FramePlaybackWindow::peekPosition(bool forward)
{
int peekCurrentPosition = currentPosition;
if (forward)
{
if (peekCurrentPosition < (currentSeqItem->data.count() - 1)) peekCurrentPosition++; //still in same file so keep going
else //hit the end of the current file
{
return 0xFFFFFFFFFFFFFFFFull;
}
}
else
{
if (peekCurrentPosition > 0) peekCurrentPosition--;
else //hit the beginning of the current sequence
{
return 0xFFFFFFFFFFFFFFFFull;
}
}
CANFrame *thisFrame = &currentSeqItem->data[peekCurrentPosition];
return thisFrame->timestamp;
}