Added a new dialog that helps to construct triggers for custom frame sender. Reworked custom sender window in anticipation of new functionality for triggers and modifiers

This commit is contained in:
Collin Kidder
2023-09-24 21:38:04 -04:00
parent aada0daf55
commit dda3109f60
7 changed files with 731 additions and 65 deletions
+3
View File
@@ -42,6 +42,7 @@ SOURCES += main.cpp\
mainwindow.cpp \ mainwindow.cpp \
canframemodel.cpp \ canframemodel.cpp \
simplecrypt.cpp \ simplecrypt.cpp \
triggerdialog.cpp \
utility.cpp \ utility.cpp \
qcustomplot.cpp \ qcustomplot.cpp \
frameplaybackwindow.cpp \ frameplaybackwindow.cpp \
@@ -132,6 +133,7 @@ HEADERS += mainwindow.h \
qcpaxistickerhex.h \ qcpaxistickerhex.h \
re/dbccomparatorwindow.h \ re/dbccomparatorwindow.h \
simplecrypt.h \ simplecrypt.h \
triggerdialog.h \
utility.h \ utility.h \
qcustomplot.h \ qcustomplot.h \
frameplaybackwindow.h \ frameplaybackwindow.h \
@@ -193,6 +195,7 @@ HEADERS += mainwindow.h \
pcaplite.h pcaplite.h
FORMS += ui/candatagrid.ui \ FORMS += ui/candatagrid.ui \
triggerdialog.ui \
ui/canbridgewindow.ui \ ui/canbridgewindow.ui \
ui/dbcnodeduplicateeditor.ui \ ui/dbcnodeduplicateeditor.ui \
ui/dbccomparatorwindow.ui \ ui/dbccomparatorwindow.ui \
+14
View File
@@ -5,6 +5,16 @@
#include <QList> #include <QList>
enum TriggerMask
{
TRG_ID = 1,
TRG_MS = 2,
TRG_COUNT = 4,
TRG_BUS = 8,
TRG_SIGNAL = 16,
TRG_SIGVAL = 32,
};
//Stores a single trigger. //Stores a single trigger.
class Trigger class Trigger
{ {
@@ -16,6 +26,10 @@ public:
int maxCount; //max # of these frames to trigger for int maxCount; //max # of these frames to trigger for
int currCount; //how many we have triggered for so far. int currCount; //how many we have triggered for so far.
int bus; //which bus to monitor (-1 if we aren't picky) int bus; //which bus to monitor (-1 if we aren't picky)
QString sigName; //which signal to trigger on
int64_t sigValueInt; //value to trigger on (integer)
double sigValueDbl; //value to trigger on (floating point)
uint32_t triggerMask;
}; };
//referece for a source location for a single modifier. //referece for a source location for a single modifier.
+120 -65
View File
@@ -7,6 +7,7 @@
#include "mainwindow.h" #include "mainwindow.h"
#include "helpwindow.h" #include "helpwindow.h"
#include "connections/canconmanager.h" #include "connections/canconmanager.h"
#include "triggerdialog.h"
/* /*
* notes: need to ensure that you grab pointers when modifying data structures and dont * notes: need to ensure that you grab pointers when modifying data structures and dont
@@ -24,6 +25,8 @@ FrameSenderWindow::FrameSenderWindow(const QVector<CANFrame> *frames, QWidget *p
modelFrames = frames; modelFrames = frames;
dbcHandler = DBCHandler::getReference();
intervalTimer = new QTimer(); intervalTimer = new QTimer();
intervalTimer->setTimerType(Qt::PreciseTimer); intervalTimer->setTimerType(Qt::PreciseTimer);
intervalTimer->setInterval(1); intervalTimer->setInterval(1);
@@ -32,6 +35,7 @@ FrameSenderWindow::FrameSenderWindow(const QVector<CANFrame> *frames, QWidget *p
createBlankRow(); createBlankRow();
connect(ui->tableSender, SIGNAL(cellChanged(int,int)), this, SLOT(onCellChanged(int,int))); connect(ui->tableSender, SIGNAL(cellChanged(int,int)), this, SLOT(onCellChanged(int,int)));
connect(ui->tableSender, SIGNAL(cellDoubleClicked(int, int)), SLOT(onCellDoubleTap(int, int)));
connect(intervalTimer, SIGNAL(timeout()), this, SLOT(handleTick())); connect(intervalTimer, SIGNAL(timeout()), this, SLOT(handleTick()));
connect(ui->btnClearGrid, SIGNAL(clicked(bool)), this, SLOT(clearGrid())); connect(ui->btnClearGrid, SIGNAL(clicked(bool)), this, SLOT(clearGrid()));
connect(ui->btnDisableAll, SIGNAL(clicked(bool)), this, SLOT(disableAll())); connect(ui->btnDisableAll, SIGNAL(clicked(bool)), this, SLOT(disableAll()));
@@ -48,19 +52,20 @@ FrameSenderWindow::FrameSenderWindow(const QVector<CANFrame> *frames, QWidget *p
void FrameSenderWindow::setupGrid() void FrameSenderWindow::setupGrid()
{ {
QStringList headers; QStringList headers;
headers << "En" << "Bus" << "ID" << "Len" << "Ext" << "Rem" << "Data" headers << "En" << "Bus" << "ID" << "MsgName" << "Len" << "Ext" << "Rem" << "Data"
<< "Trigger" << "Modifications" << "Count"; << "Trigger" << "Modifications" << "Count";
ui->tableSender->setColumnCount(10); ui->tableSender->setColumnCount(11);
ui->tableSender->setColumnWidth(0, 50); ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_EN, 50);
ui->tableSender->setColumnWidth(1, 50); ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_BUS, 50);
ui->tableSender->setColumnWidth(2, 50); ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_ID, 50);
ui->tableSender->setColumnWidth(3, 50); ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_MSGNAME, 150);
ui->tableSender->setColumnWidth(4, 50); ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_LEN, 50);
ui->tableSender->setColumnWidth(5, 50); ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_EXT, 50);
ui->tableSender->setColumnWidth(6, 220); ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_REM, 50);
ui->tableSender->setColumnWidth(7, 220); ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_DATA, 220);
ui->tableSender->setColumnWidth(8, 220); ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_TRIGGER, 220);
ui->tableSender->setColumnWidth(9, 80); ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_MODS, 220);
ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_COUNT, 80);
ui->tableSender->setHorizontalHeaderLabels(headers); ui->tableSender->setHorizontalHeaderLabels(headers);
} }
@@ -100,22 +105,26 @@ void FrameSenderWindow::createBlankRow()
item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(Qt::Unchecked); item->setCheckState(Qt::Unchecked);
inhibitChanged = true; inhibitChanged = true;
ui->tableSender->setItem(row, 0, item); ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_EN, item);
item = new QTableWidgetItem(); item = new QTableWidgetItem();
item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(Qt::Unchecked); item->setCheckState(Qt::Unchecked);
ui->tableSender->setItem(row, 4, item); ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_EXT, item);
item = new QTableWidgetItem(); item = new QTableWidgetItem();
item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(Qt::Unchecked); item->setCheckState(Qt::Unchecked);
ui->tableSender->setItem(row, 5, item); ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_REM, item);
for (int i = 1; i < 10; i++) for (int i = 1; i <= ST_COLS::SENDTAB_COL_COUNT; i++)
{ {
if (i != 4 && i != 5) { if (i != ST_COLS::SENDTAB_COL_EXT && i != ST_COLS::SENDTAB_COL_REM) {
item = new QTableWidgetItem(""); item = new QTableWidgetItem("");
//msgname is looked up via DBC interface and not able to be edited.
//Though, it might be perhaps interesting to allow renaming it to
//a shorter name in some cases? Jury is still out on this one...
if (i == ST_COLS::SENDTAB_COL_MSGNAME) item->setFlags(item->flags() & ~Qt::ItemIsEditable);
ui->tableSender->setItem(row, i, item); ui->tableSender->setItem(row, i, item);
} }
} }
@@ -212,7 +221,7 @@ void FrameSenderWindow::enableAll()
{ {
for (int i = 0; i < ui->tableSender->rowCount() - 1; i++) for (int i = 0; i < ui->tableSender->rowCount() - 1; i++)
{ {
ui->tableSender->item(i, 0)->setCheckState(Qt::Checked); ui->tableSender->item(i, ST_COLS::SENDTAB_COL_EN)->setCheckState(Qt::Checked);
sendingData[i].enabled = true; sendingData[i].enabled = true;
} }
} }
@@ -221,7 +230,7 @@ void FrameSenderWindow::disableAll()
{ {
for (int i = 0; i < ui->tableSender->rowCount() - 1; i++) for (int i = 0; i < ui->tableSender->rowCount() - 1; i++)
{ {
ui->tableSender->item(i, 0)->setCheckState(Qt::Unchecked); ui->tableSender->item(i, ST_COLS::SENDTAB_COL_EN)->setCheckState(Qt::Unchecked);
sendingData[i].enabled = false; sendingData[i].enabled = false;
} }
} }
@@ -309,14 +318,14 @@ void FrameSenderWindow::saveSenderFile(QString filename)
for (int c = 0; c < sendingData.count(); c++) for (int c = 0; c < sendingData.count(); c++)
{ {
outString.clear(); outString.clear();
if (ui->tableSender->item(c, 0)->checkState() == Qt::Checked) if (ui->tableSender->item(c, ST_COLS::SENDTAB_COL_EN)->checkState() == Qt::Checked)
{ {
outString = "T#"; outString = "T#";
} }
else outString = "F#"; else outString = "F#";
for (int i = 1; i < 9; i++) for (int i = 1; i < ST_COLS::SENDTAB_COL_COUNT; i++)
{ {
if (i == 4 || i == 5) { if (i == ST_COLS::SENDTAB_COL_EXT || i == ST_COLS::SENDTAB_COL_REM) {
if (ui->tableSender->item(c, i)->checkState() == Qt::Checked) { if (ui->tableSender->item(c, i)->checkState() == Qt::Checked) {
outString.append("T"); outString.append("T");
} else { } else {
@@ -359,28 +368,28 @@ void FrameSenderWindow::loadSenderFile(QString filename)
int row = ui->tableSender->rowCount(); int row = ui->tableSender->rowCount();
ui->tableSender->insertRow(row); ui->tableSender->insertRow(row);
QTableWidgetItem *item = new QTableWidgetItem(); QTableWidgetItem *item = new QTableWidgetItem();
ui->tableSender->setItem(row, 0, item); ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_EN, item);
ui->tableSender->item(row, 0)->setFlags(item->flags() | Qt::ItemIsUserCheckable); ui->tableSender->item(row, ST_COLS::SENDTAB_COL_EN)->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item = new QTableWidgetItem(); item = new QTableWidgetItem();
ui->tableSender->setItem(row, 4, item); ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_EXT, item);
ui->tableSender->item(row, 4)->setFlags(item->flags() | Qt::ItemIsUserCheckable); ui->tableSender->item(row, ST_COLS::SENDTAB_COL_EN)->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(Qt::Unchecked); item->setCheckState(Qt::Unchecked);
item = new QTableWidgetItem(); item = new QTableWidgetItem();
ui->tableSender->setItem(row, 5, item); ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_REM, item);
ui->tableSender->item(row, 5)->setFlags(item->flags() | Qt::ItemIsUserCheckable); ui->tableSender->item(row, ST_COLS::SENDTAB_COL_REM)->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(Qt::Unchecked); item->setCheckState(Qt::Unchecked);
if (tokens[0] == "T") if (tokens[0] == "T")
{ {
ui->tableSender->item(row, 0)->setCheckState(Qt::Checked); ui->tableSender->item(row, ST_COLS::SENDTAB_COL_EN)->setCheckState(Qt::Checked);
} }
else ui->tableSender->item(row, 0)->setCheckState(Qt::Unchecked); else ui->tableSender->item(row, ST_COLS::SENDTAB_COL_EN)->setCheckState(Qt::Unchecked);
if (tokens.length() >= 9) { if (tokens.length() >= 9) {
for (int i = 1; i < 9; i++) for (int i = 1; i < ST_COLS::SENDTAB_COL_COUNT; i++)
{ {
if (i != 4 && i != 5) { if (i != ST_COLS::SENDTAB_COL_EXT && i != ST_COLS::SENDTAB_COL_REM) {
ui->tableSender->setItem(row, i, new QTableWidgetItem(QString(tokens[i]))); ui->tableSender->setItem(row, i, new QTableWidgetItem(QString(tokens[i])));
} else { } else {
if (tokens[i] == "T") { if (tokens[i] == "T") {
@@ -389,15 +398,15 @@ void FrameSenderWindow::loadSenderFile(QString filename)
} }
} }
} else { } else {
ui->tableSender->setItem(row, 1, new QTableWidgetItem(QString(tokens[1]))); ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_BUS, new QTableWidgetItem(QString(tokens[1])));
ui->tableSender->setItem(row, 2, new QTableWidgetItem(QString(tokens[2]))); ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_ID, new QTableWidgetItem(QString(tokens[2])));
ui->tableSender->setItem(row, 3, new QTableWidgetItem(QString(tokens[3]))); ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_LEN, new QTableWidgetItem(QString(tokens[3])));
ui->tableSender->setItem(row, 6, new QTableWidgetItem(QString(tokens[4]))); ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_DATA, new QTableWidgetItem(QString(tokens[4])));
ui->tableSender->setItem(row, 7, new QTableWidgetItem(QString(tokens[5]))); ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_TRIGGER, new QTableWidgetItem(QString(tokens[5])));
ui->tableSender->setItem(row, 8, new QTableWidgetItem(QString(tokens[6]))); ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_MODS, new QTableWidgetItem(QString(tokens[6])));
} }
inhibitChanged = false; inhibitChanged = false;
for (int k = 0; k < 9; k++) processCellChange(row, k); for (int k = 0; k < ST_COLS::SENDTAB_COL_COUNT; k++) processCellChange(row, k);
} }
} }
@@ -405,6 +414,43 @@ void FrameSenderWindow::loadSenderFile(QString filename)
delete inFile; delete inFile;
} }
void FrameSenderWindow::onCellDoubleTap(int row, int column)
{
if (column == ST_COLS::SENDTAB_COL_TRIGGER)
{
if (row == ui->tableSender->rowCount() - 1)
{
createBlankRow();
}
if (row >= sendingData.count())
{
FrameSendData tempData;
tempData.enabled = false;
tempData.setFrameType(QCanBusFrame::DataFrame);
tempData.setExtendedFrameFormat(false);
sendingData.append(tempData);
}
sendData = nullptr;
sendData = &sendingData[row];
td = new TriggerDialog(sendData->triggers);
if (td->exec() == QDialog::Accepted)
{
sendData->triggers.clear();
sendData->triggers.append(td->getUpdatedTriggers());
//now have to generate the actual trigger text
QString output;
foreach (Trigger trig, sendData->triggers)
{
output += td->buildEntry(trig) + ",";
}
output.chop(1); //don't want the trailing ,
ui->tableSender->item(row, ST_COLS::SENDTAB_COL_TRIGGER)->setText(output);
}
delete td;
td = nullptr;
}
}
void FrameSenderWindow::onCellChanged(int row, int col) void FrameSenderWindow::onCellChanged(int row, int col)
{ {
if (inhibitChanged) return; if (inhibitChanged) return;
@@ -692,7 +738,7 @@ void FrameSenderWindow::processTriggerText(int line)
//trigger has two levels of syntactic parsing. First you split by comma to get each //trigger has two levels of syntactic parsing. First you split by comma to get each
//actual trigger. Then you split by spaces to get the tokens within each trigger //actual trigger. Then you split by spaces to get the tokens within each trigger
//trigger = ui->tableSender->item(line, 5)->text().toUpper().trimmed().replace(" ", ""); //trigger = ui->tableSender->item(line, 5)->text().toUpper().trimmed().replace(" ", "");
trigger = ui->tableSender->item(line, 7)->text().toUpper(); trigger = ui->tableSender->item(line, ST_COLS::SENDTAB_COL_TRIGGER)->text().toUpper();
if (trigger != "") if (trigger != "")
{ {
QStringList triggers = trigger.split(','); QStringList triggers = trigger.split(',');
@@ -717,7 +763,7 @@ void FrameSenderWindow::processTriggerText(int line)
if (tok.left(2) == "ID") if (tok.left(2) == "ID")
{ {
thisTrigger.ID = Utility::ParseStringToNum(tok.right(tok.length() - 2)); thisTrigger.ID = Utility::ParseStringToNum(tok.right(tok.length() - 2));
if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 10000000; //if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 0;
if (thisTrigger.milliseconds == -1) thisTrigger.milliseconds = 0; //by default don't count, just send it upon trigger if (thisTrigger.milliseconds == -1) thisTrigger.milliseconds = 0; //by default don't count, just send it upon trigger
thisTrigger.readyCount = false; //won't try counting until trigger hits thisTrigger.readyCount = false; //won't try counting until trigger hits
@@ -725,13 +771,13 @@ void FrameSenderWindow::processTriggerText(int line)
else if (tok.endsWith("MS")) else if (tok.endsWith("MS"))
{ {
thisTrigger.milliseconds = Utility::ParseStringToNum(tok.left(tok.length()-2)); thisTrigger.milliseconds = Utility::ParseStringToNum(tok.left(tok.length()-2));
if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 10000000; //if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 0;
if (thisTrigger.ID == -1) thisTrigger.ID = 0; //if (thisTrigger.ID == -1) thisTrigger.ID = 0;
} }
else if (tok.endsWith("X")) else if (tok.endsWith("X"))
{ {
thisTrigger.maxCount = Utility::ParseStringToNum(tok.left(tok.length() - 1)); thisTrigger.maxCount = Utility::ParseStringToNum(tok.left(tok.length() - 1));
if (thisTrigger.ID == -1) thisTrigger.ID = 0; //if (thisTrigger.ID == -1) thisTrigger.ID = 0;
if (thisTrigger.milliseconds == -1) thisTrigger.milliseconds = 10; if (thisTrigger.milliseconds == -1) thisTrigger.milliseconds = 10;
} }
else if (tok.startsWith("BUS")) else if (tok.startsWith("BUS"))
@@ -740,9 +786,9 @@ void FrameSenderWindow::processTriggerText(int line)
} }
} }
//now, find anything that wasn't set and set it to defaults //now, find anything that wasn't set and set it to defaults
if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 10000000; //if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 0;
if (thisTrigger.milliseconds == -1) thisTrigger.milliseconds = 100; //if (thisTrigger.milliseconds == -1) thisTrigger.milliseconds = 100;
if (thisTrigger.ID == -1) thisTrigger.ID = 0; //if (thisTrigger.ID == -1) thisTrigger.ID = 0;
sendingData[line].triggers.append(thisTrigger); sendingData[line].triggers.append(thisTrigger);
} }
} }
@@ -815,7 +861,7 @@ void FrameSenderWindow::updateGridRow(int idx)
FrameSendData *temp = &sendingData[idx]; FrameSendData *temp = &sendingData[idx];
int gridLine = idx; int gridLine = idx;
QString dataString; QString dataString;
QTableWidgetItem *item = ui->tableSender->item(gridLine, 9); QTableWidgetItem *item = ui->tableSender->item(gridLine, ST_COLS::SENDTAB_COL_COUNT);
const unsigned char *data = reinterpret_cast<const unsigned char *>(temp->payload().constData()); const unsigned char *data = reinterpret_cast<const unsigned char *>(temp->payload().constData());
int dataLen = temp->payload().length(); int dataLen = temp->payload().length();
@@ -823,7 +869,7 @@ void FrameSenderWindow::updateGridRow(int idx)
{ {
item = new QTableWidgetItem(); item = new QTableWidgetItem();
item->setText(QString::number(temp->count)); item->setText(QString::number(temp->count));
ui->tableSender->setItem(gridLine, 9, item); ui->tableSender->setItem(gridLine, ST_COLS::SENDTAB_COL_COUNT, item);
} }
else else
{ {
@@ -837,11 +883,11 @@ void FrameSenderWindow::updateGridRow(int idx)
dataString.append(Utility::formatNumber(data[i])); dataString.append(Utility::formatNumber(data[i]));
dataString.append(" "); dataString.append(" ");
} }
ui->tableSender->item(gridLine, 6)->setText(dataString); ui->tableSender->item(gridLine, ST_COLS::SENDTAB_COL_DATA)->setText(dataString);
} }
else else
{ {
ui->tableSender->item(gridLine, 6)->setText(""); ui->tableSender->item(gridLine, ST_COLS::SENDTAB_COL_DATA)->setText("");
} }
inhibitChanged = false; inhibitChanged = false;
} }
@@ -852,6 +898,7 @@ void FrameSenderWindow::processCellChange(int line, int col)
FrameSendData tempData; FrameSendData tempData;
QStringList tokens; QStringList tokens;
int tempVal; int tempVal;
DBC_MESSAGE *msg;
//if this is a new line then create the base object for the line //if this is a new line then create the base object for the line
if (line >= sendingData.count()) if (line >= sendingData.count())
@@ -870,7 +917,7 @@ void FrameSenderWindow::processCellChange(int line, int col)
switch (col) switch (col)
{ {
case 0: //Enable check box case ST_COLS::SENDTAB_COL_EN: //Enable check box
if (ui->tableSender->item(line, 0)->checkState() == Qt::Checked) if (ui->tableSender->item(line, 0)->checkState() == Qt::Checked)
{ {
sendingData[line].enabled = true; sendingData[line].enabled = true;
@@ -878,54 +925,62 @@ void FrameSenderWindow::processCellChange(int line, int col)
else sendingData[line].enabled = false; else sendingData[line].enabled = false;
qDebug() << "Setting enabled to " << sendingData[line].enabled; qDebug() << "Setting enabled to " << sendingData[line].enabled;
break; break;
case 1: //Bus designation case ST_COLS::SENDTAB_COL_BUS: //Bus designation
tempVal = Utility::ParseStringToNum(ui->tableSender->item(line, 1)->text()); tempVal = Utility::ParseStringToNum(ui->tableSender->item(line, ST_COLS::SENDTAB_COL_BUS)->text());
if (tempVal < -1) tempVal = -1; if (tempVal < -1) tempVal = -1;
if (tempVal >= numBuses) tempVal = numBuses - 1; if (tempVal >= numBuses) tempVal = numBuses - 1;
sendingData[line].bus = tempVal; sendingData[line].bus = tempVal;
qDebug() << "Setting bus to " << tempVal; qDebug() << "Setting bus to " << tempVal;
break; break;
case 2: //ID field case ST_COLS::SENDTAB_COL_ID: //ID field
tempVal = Utility::ParseStringToNum(ui->tableSender->item(line, 2)->text()); tempVal = Utility::ParseStringToNum(ui->tableSender->item(line, ST_COLS::SENDTAB_COL_ID)->text());
if (tempVal < 0) tempVal = 0; if (tempVal < 0) tempVal = 0;
if (tempVal > 0x7FFFFFFF) tempVal = 0x7FFFFFFF; if (tempVal > 0x7FFFFFFF) tempVal = 0x7FFFFFFF;
sendingData[line].setFrameId(tempVal); sendingData[line].setFrameId(tempVal);
if (sendingData[line].frameId() > 0x7FF) { if (sendingData[line].frameId() > 0x7FF) {
sendingData[line].setExtendedFrameFormat(true); sendingData[line].setExtendedFrameFormat(true);
ui->tableSender->blockSignals(true); ui->tableSender->blockSignals(true);
ui->tableSender->item(line, 4)->setCheckState(Qt::Checked); ui->tableSender->item(line, ST_COLS::SENDTAB_COL_EXT)->setCheckState(Qt::Checked);
ui->tableSender->blockSignals(false);
}
msg = dbcHandler->findMessage(sendingData[line].frameId());
if (msg)
{
ui->tableSender->blockSignals(true);
ui->tableSender->item(line, ST_COLS::SENDTAB_COL_MSGNAME)->setText(msg->name);
ui->tableSender->item(line, ST_COLS::SENDTAB_COL_LEN)->setText(QString::number(msg->len));
ui->tableSender->blockSignals(false); ui->tableSender->blockSignals(false);
} }
qDebug() << "setting ID to " << tempVal; qDebug() << "setting ID to " << tempVal;
break; break;
case 3: //length field case ST_COLS::SENDTAB_COL_LEN:
tempVal = Utility::ParseStringToNum(ui->tableSender->item(line, 3)->text()); tempVal = Utility::ParseStringToNum(ui->tableSender->item(line, 3)->text());
if (tempVal < 0) tempVal = 0; if (tempVal < 0) tempVal = 0;
if (tempVal > 8) tempVal = 8; if (tempVal > 8) tempVal = 8;
arr.resize(tempVal); arr.resize(tempVal);
sendingData[line].setPayload(arr); sendingData[line].setPayload(arr);
break; break;
case 4: // Ext case ST_COLS::SENDTAB_COL_EXT:
if (ui->tableSender->item(line, 4)->checkState() == Qt::Checked) { if (ui->tableSender->item(line, 4)->checkState() == Qt::Checked) {
sendingData[line].setExtendedFrameFormat(true); sendingData[line].setExtendedFrameFormat(true);
} else { } else {
sendingData[line].setExtendedFrameFormat(false); sendingData[line].setExtendedFrameFormat(false);
} }
break; break;
case 5: // Rem case ST_COLS::SENDTAB_COL_REM:
if (ui->tableSender->item(line, 5)->checkState() == Qt::Checked) { if (ui->tableSender->item(line, 5)->checkState() == Qt::Checked) {
sendingData[line].setFrameType(QCanBusFrame::RemoteRequestFrame); sendingData[line].setFrameType(QCanBusFrame::RemoteRequestFrame);
} else { } else {
sendingData[line].setFrameType(QCanBusFrame::DataFrame); sendingData[line].setFrameType(QCanBusFrame::DataFrame);
} }
break; break;
case 6: //Data bytes case ST_COLS::SENDTAB_COL_DATA: //Data bytes
for (int i = 0; i < 8; i++) sendingData[line].payload().data()[i] = 0; for (int i = 0; i < 8; i++) sendingData[line].payload().data()[i] = 0;
#if QT_VERSION >= QT_VERSION_CHECK( 5, 14, 0 ) #if QT_VERSION >= QT_VERSION_CHECK( 5, 14, 0 )
tokens = ui->tableSender->item(line, 6)->text().split(" ", Qt::SkipEmptyParts); tokens = ui->tableSender->item(line, ST_COLS::SENDTAB_COL_DATA)->text().split(" ", Qt::SkipEmptyParts);
#else #else
tokens = ui->tableSender->item(line, 6)->text().split(" ", QString::SkipEmptyParts); tokens = ui->tableSender->item(line, ST_COLS::SENDTAB_COL_DATA)->text().split(" ", QString::SkipEmptyParts);
#endif #endif
arr.clear(); arr.clear();
arr.reserve(tokens.count()); arr.reserve(tokens.count());
@@ -935,10 +990,10 @@ void FrameSenderWindow::processCellChange(int line, int col)
} }
sendingData[line].setPayload(arr); sendingData[line].setPayload(arr);
break; break;
case 7: //triggers case ST_COLS::SENDTAB_COL_TRIGGER: //triggers
processTriggerText(line); processTriggerText(line);
break; break;
case 8: //modifiers case ST_COLS::SENDTAB_COL_MODS: //modifiers
processModifierText(line); processModifierText(line);
break; break;
} }
+22
View File
@@ -8,11 +8,28 @@
#include <QMutex> #include <QMutex>
#include "can_structs.h" #include "can_structs.h"
#include "can_trigger_structs.h" #include "can_trigger_structs.h"
#include "dbc/dbchandler.h"
#include "triggerdialog.h"
namespace Ui { namespace Ui {
class FrameSenderWindow; class FrameSenderWindow;
} }
enum ST_COLS
{
SENDTAB_COL_EN = 0,
SENDTAB_COL_BUS = 1,
SENDTAB_COL_ID = 2,
SENDTAB_COL_MSGNAME = 3,
SENDTAB_COL_LEN = 4,
SENDTAB_COL_EXT = 5,
SENDTAB_COL_REM = 6,
SENDTAB_COL_DATA = 7,
SENDTAB_COL_TRIGGER = 8,
SENDTAB_COL_MODS = 9,
SENDTAB_COL_COUNT = 10,
};
class FrameSenderWindow : public QDialog class FrameSenderWindow : public QDialog
{ {
Q_OBJECT Q_OBJECT
@@ -23,6 +40,7 @@ public:
private slots: private slots:
void onCellChanged(int, int); void onCellChanged(int, int);
void onCellDoubleTap(int, int);
void handleTick(); void handleTick();
void enableAll(); void enableAll();
void disableAll(); void disableAll();
@@ -40,6 +58,9 @@ private:
QElapsedTimer elapsedTimer; QElapsedTimer elapsedTimer;
bool inhibitChanged = false; bool inhibitChanged = false;
QMutex mutex; QMutex mutex;
DBCHandler *dbcHandler;
TriggerDialog *td;
FrameSendData *sendData;
void createBlankRow(); void createBlankRow();
void doModifiers(int); void doModifiers(int);
@@ -57,6 +78,7 @@ private:
void processIncomingFrame(CANFrame *frame); void processIncomingFrame(CANFrame *frame);
bool eventFilter(QObject *obj, QEvent *event); bool eventFilter(QObject *obj, QEvent *event);
void setupGrid(); void setupGrid();
void finishedTriggerDialog(int result);
}; };
#endif // FRAMESENDERWINDOW_H #endif // FRAMESENDERWINDOW_H
+341
View File
@@ -0,0 +1,341 @@
#include "triggerdialog.h"
#include "ui_triggerdialog.h"
#include <QMessageBox>
TriggerDialog::TriggerDialog(QList<Trigger> trigs, QWidget *parent) :
QDialog(parent),
ui(new Ui::TriggerDialog)
{
ui->setupUi(this);
triggers.clear();
triggers.append(trigs); //make sure it's just a clone of the existing triggers
//if there are no triggers then create a default one
if (triggers.count() == 0)
{
Trigger trig;
trig.ID = 0;
trig.bus = 0;
trig.maxCount = 0;
trig.milliseconds = 100;
trig.sigName = "";
trig.sigValueDbl = 0.0;
trig.sigValueInt = 0;
trig.triggerMask = TriggerMask::TRG_MS;
triggers.append(trig);
}
lastMsgID = 0xFFFFFFFF;
inhibitUpdates = false;
dbcHandler = DBCHandler::getReference();
/*
getting the messages possible is complicated by the fact that multiple DBC files
can be loaded and each one can be associated with a different bus. So, this is
tough. At first I suppose we need to list all possible messages but if the user
sets a bus then we should recalculate and only show messages possible on that bus.
*/
ui->cmMsgID->clear();
QStringList entries;
int numFiles = dbcHandler->getFileCount();
for (int i = 0; i < numFiles; i++)
{
DBCFile *file = dbcHandler->getFileByIdx(i);
int numMsgs = file->messageHandler->getCount();
for (int j = 0; j < numMsgs; j++)
{
DBC_MESSAGE *msg = file->messageHandler->findMsgByIdx(j);
entries.append(("0x" + QString::number(msg->ID, 16) + " (" + msg->name + ")"));
}
}
entries.sort();
ui->cmMsgID->addItems(entries);
connect(ui->listTriggers, &QListWidget::currentRowChanged, this, &TriggerDialog::FillDetails);
connect(ui->btnDelete, &QPushButton::clicked, this, &TriggerDialog::deleteSelectedTrigger);
connect(ui->btnNew, &QPushButton::clicked, this, &TriggerDialog::addNewTrigger);
connect(ui->btnSave, &QPushButton::clicked, this, &TriggerDialog::saveAndExit);
connect(ui->cbBus, &QCheckBox::stateChanged, this, &TriggerDialog::handleCheckboxes);
connect(ui->cbMillis, &QCheckBox::stateChanged, this, &TriggerDialog::handleCheckboxes);
connect(ui->cbMaxCount, &QCheckBox::stateChanged, this, &TriggerDialog::handleCheckboxes);
connect(ui->cbMsgID, &QCheckBox::stateChanged, this, &TriggerDialog::handleCheckboxes);
connect(ui->cbSigValue, &QCheckBox::stateChanged, this, &TriggerDialog::handleCheckboxes);
connect(ui->cbSignal, &QCheckBox::stateChanged, this, &TriggerDialog::handleCheckboxes);
connect(ui->txtMaxCount, &QLineEdit::textEdited, this, &TriggerDialog::regenerateCurrentListItem);
connect(ui->cmMsgID, &QComboBox::currentTextChanged, this, &TriggerDialog::regenerateCurrentListItem);
connect(ui->cmSignal, &QComboBox::currentTextChanged, this, &TriggerDialog::regenerateCurrentListItem);
connect(ui->txtBus, &QLineEdit::textEdited, this, &TriggerDialog::regenerateCurrentListItem);
connect(ui->txtDelay, &QLineEdit::textEdited, this, &TriggerDialog::regenerateCurrentListItem);
connect(ui->txtSigValue, &QLineEdit::textEdited, this, &TriggerDialog::regenerateCurrentListItem);
}
TriggerDialog::~TriggerDialog()
{
delete ui;
}
QList<Trigger> TriggerDialog::getUpdatedTriggers()
{
return triggers;
}
void TriggerDialog::showEvent(QShowEvent* event)
{
QDialog::showEvent(event);
regenerateList();
}
void TriggerDialog::regenerateList()
{
ui->listTriggers->clear();
//iterate over the QList of triggers, show them in the list,
//load the first item in the list, and display it broken down on the right.
foreach(Trigger trig, triggers)
{
ui->listTriggers->addItem(buildEntry(trig));
}
ui->listTriggers->setCurrentRow(0);
FillDetails();
}
QString TriggerDialog::buildEntry(Trigger trig)
{
QString build;
if (trig.triggerMask & TriggerMask::TRG_BUS)
{
build = build + "BUS" + QString::number(trig.bus) + " ";
}
if (trig.triggerMask & TriggerMask::TRG_ID)
{
build = build + "ID0x" + QString::number(trig.ID, 16) + " ";
}
if (trig.triggerMask & TriggerMask::TRG_SIGNAL)
{
build = build + "SIG[" + trig.sigName;
if (trig.triggerMask & TriggerMask::TRG_SIGVAL)
{
build = build + ";" + QString::number(trig.sigValueDbl);
}
build = build + "] ";
}
if (trig.triggerMask & TriggerMask::TRG_COUNT)
{
build = build + QString::number(trig.maxCount) + "X ";
}
if (trig.triggerMask & TriggerMask::TRG_MS)
{
build = build + QString::number(trig.milliseconds) + "MS ";
}
return build;
}
void TriggerDialog::handleCheckboxes()
{
ui->txtBus->setEnabled(ui->cbBus->checkState());
ui->txtDelay->setEnabled(ui->cbMillis->checkState());
ui->txtMaxCount->setEnabled(ui->cbMaxCount->checkState());
ui->cmMsgID->setEnabled(ui->cbMsgID->checkState());
ui->cmSignal->setEnabled(ui->cbSignal->checkState());
ui->txtSigValue->setEnabled(ui->cbSigValue->checkState());
if (ui->cbBus->checkState() == Qt::Unchecked) ui->txtBus->setText("");
if (ui->cbMillis->checkState() == Qt::Unchecked) ui->txtDelay->setText("");
if (ui->cbMaxCount->checkState() == Qt::Unchecked) ui->txtMaxCount->setText("");
if (ui->cbMsgID->checkState() == Qt::Unchecked) ui->cmMsgID->setCurrentText("");
if (ui->cbSignal->checkState() == Qt::Unchecked) ui->cmSignal->setCurrentText("");
if (ui->cbSigValue->checkState() == Qt::Unchecked) ui->txtSigValue->setText("");
regenerateCurrentListItem();
}
//load all the values from the editing screen and place them into the structure
//also, redo the list entry so it shows the proper text
void TriggerDialog::regenerateCurrentListItem()
{
if (inhibitUpdates) return;
int row = ui->listTriggers->currentRow();
Trigger trig;
trig.ID = 0;
trig.bus = 0;
trig.maxCount = 0;
trig.milliseconds = 0;
trig.sigName = "";
trig.sigValueDbl = 0.0;
trig.triggerMask = 0;
if (ui->cbMsgID->isChecked() && ui->cmMsgID->currentText().length() > 0)
{
trig.triggerMask |= TriggerMask::TRG_ID;
trig.ID = ui->cmMsgID->currentText()
.replace("0x", "")
.split("(")[0]
.toInt(nullptr, 16);
if (trig.ID != lastMsgID)
{
lastMsgID = trig.ID;
//now, reload signals combobox
ui->cmSignal->clear();
QStringList entries;
//danger - this isn't doing a great job of making sure we're using
//the right message. Should at least search by message name instead
//if possible.
DBC_MESSAGE *msg = dbcHandler->findMessage(trig.ID);
int numSigs = msg->sigHandler->getCount();
for (int s = 0; s < numSigs; s++)
{
DBC_SIGNAL *sig = msg->sigHandler->findSignalByIdx(s);
entries.append(sig->name);
}
entries.sort();
ui->cmSignal->addItems(entries);
}
}
if (ui->cbBus->isChecked() && ui->txtBus ->text().length() > 0)
{
trig.triggerMask |= TriggerMask::TRG_BUS;
trig.bus = ui->txtBus->text().toInt();
}
if (ui->cbMillis->isChecked() && ui->txtDelay->text().length() > 0)
{
trig.triggerMask |= TriggerMask::TRG_MS;
trig.milliseconds = ui->txtDelay->text().toInt();
}
if (ui->cbMaxCount->isChecked() && ui->txtMaxCount->text().length() > 0)
{
trig.triggerMask |= TriggerMask::TRG_COUNT;
trig.maxCount = ui->txtMaxCount->text().toInt();
}
if (ui->cbSigValue->isChecked() && ui->txtSigValue->text().length() > 0)
{
trig.triggerMask |= TriggerMask::TRG_SIGVAL;
trig.sigValueDbl = ui->txtSigValue->text().toDouble();
}
if (ui->cbSignal->isChecked() && ui->cmSignal->currentText().length() > 0)
{
trig.triggerMask |= TriggerMask::TRG_SIGNAL;
trig.sigName = ui->cmSignal->currentText().trimmed();
}
triggers[row] = trig;
ui->listTriggers->item(row)->setText(buildEntry(trig));
}
void TriggerDialog::FillDetails()
{
int selectedRow = ui->listTriggers->currentRow();
if (selectedRow < 0) return;
inhibitUpdates = true; //this function makes a lot of changes and they should not trigger the normal updates
if (triggers[selectedRow].triggerMask & TriggerMask::TRG_BUS)
{
ui->txtBus->setEnabled(true);
ui->txtBus->setText(QString::number(triggers[selectedRow].bus));
ui->cbBus->setCheckState(Qt::Checked);
}
else
{
ui->txtBus->setEnabled(false);
ui->cbBus->setCheckState(Qt::Unchecked);
}
if (triggers[selectedRow].triggerMask & TriggerMask::TRG_ID)
{
ui->cmMsgID->setEnabled(true);
ui->cmMsgID->setCurrentText(QString::number(triggers[selectedRow].ID, 16));
ui->cbMsgID->setCheckState(Qt::Checked);
}
else
{
ui->cmMsgID->setEnabled(false);
ui->cbMsgID->setCheckState(Qt::Unchecked);
}
if (triggers[selectedRow].triggerMask & TriggerMask::TRG_COUNT)
{
ui->txtMaxCount->setEnabled(true);
ui->txtMaxCount->setText(QString::number(triggers[selectedRow].maxCount));
ui->cbMaxCount->setCheckState(Qt::Checked);
}
else
{
ui->txtMaxCount->setEnabled(false);
ui->cbMaxCount->setCheckState(Qt::Unchecked);
}
if (triggers[selectedRow].triggerMask & TriggerMask::TRG_MS)
{
ui->txtDelay->setEnabled(true);
ui->txtDelay->setText(QString::number(triggers[selectedRow].milliseconds));
ui->cbMillis->setCheckState(Qt::Checked);
}
else
{
ui->txtDelay->setEnabled(false);
ui->cbMillis->setCheckState(Qt::Unchecked);
}
if (triggers[selectedRow].triggerMask & TriggerMask::TRG_SIGNAL)
{
ui->cmSignal->setEnabled(true);
ui->cmSignal->setCurrentText(triggers[selectedRow].sigName);
ui->cbSignal->setCheckState(Qt::Checked);
}
else
{
ui->cmSignal->setEnabled(false);
ui->cbSignal->setCheckState(Qt::Unchecked);
}
if (triggers[selectedRow].triggerMask & TriggerMask::TRG_SIGVAL)
{
ui->txtSigValue->setEnabled(true);
ui->txtSigValue->setText(QString::number(triggers[selectedRow].sigValueDbl));
ui->cbSigValue->setCheckState(Qt::Checked);
}
else
{
ui->txtSigValue->setEnabled(false);
ui->cbSigValue->setCheckState(Qt::Unchecked);
}
inhibitUpdates = false;
}
void TriggerDialog::addNewTrigger()
{
Trigger newTrig;
newTrig.ID = 0;
newTrig.bus = 0;
newTrig.maxCount = 0;
newTrig.milliseconds = 100;
newTrig.sigName = "";
newTrig.sigValueDbl = 0.0;
newTrig.sigValueInt = 0;
newTrig.triggerMask = TriggerMask::TRG_MS;
triggers.append(newTrig);
ui->listTriggers->addItem(buildEntry(newTrig));
}
void TriggerDialog::deleteSelectedTrigger()
{
if (triggers.count() == 1)
{
QMessageBox::information(this, "Error", "Cannot delete the last trigger");
return;
}
}
void TriggerDialog::saveAndExit()
{
accept();
}
+40
View File
@@ -0,0 +1,40 @@
#ifndef TRIGGERDIALOG_H
#define TRIGGERDIALOG_H
#include <QDialog>
#include "can_trigger_structs.h"
#include "dbc/dbchandler.h"
namespace Ui {
class TriggerDialog;
}
class TriggerDialog : public QDialog
{
Q_OBJECT
public:
explicit TriggerDialog(QList<Trigger> triggers, QWidget *parent = nullptr);
QList<Trigger> getUpdatedTriggers();
void showEvent(QShowEvent*);
~TriggerDialog();
QString buildEntry(Trigger trig);
private:
Ui::TriggerDialog *ui;
DBCHandler *dbcHandler;
int lastMsgID;
bool inhibitUpdates;
QList<Trigger> triggers;
void FillDetails();
void addNewTrigger();
void deleteSelectedTrigger();
void saveAndExit();
void regenerateList();
void handleCheckboxes();
void regenerateCurrentListItem();
};
#endif // TRIGGERDIALOG_H
+191
View File
@@ -0,0 +1,191 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TriggerDialog</class>
<widget class="QDialog" name="TriggerDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>771</width>
<height>303</height>
</rect>
</property>
<property name="windowTitle">
<string>Trigger Builder</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="5,1,10">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QListWidget" name="listTriggers"/>
</item>
<item>
<widget class="QPushButton" name="btnNew">
<property name="text">
<string>Add New</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnDelete">
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<property name="lineWidth">
<number>7</number>
</property>
<property name="midLineWidth">
<number>3</number>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<property name="formAlignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<item row="0" column="0">
<widget class="QCheckBox" name="cbBus">
<property name="text">
<string>Bus</string>
</property>
</widget>
</item>
<item row="10" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="txtBus">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="cbMsgID">
<property name="text">
<string>Msg ID</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="cbSignal">
<property name="text">
<string>Signal</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="cmSignal">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="cbMillis">
<property name="text">
<string>MS Delay</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="txtDelay">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="cbMaxCount">
<property name="text">
<string>Max Count</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="txtMaxCount">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="cmMsgID">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="cbSigValue">
<property name="text">
<string>Signal Value</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="txtSigValue">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="btnSave">
<property name="text">
<string>Save And Update</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>