First pass of adding direct DBC signal support to the graphing window.

It mostly works now but there are several little pieces still to do.
This commit is contained in:
Collin Kidder
2016-01-11 20:45:30 -05:00
parent 62e168905a
commit cdd4f23609
7 changed files with 370 additions and 233 deletions
+1 -1
View File
@@ -739,7 +739,7 @@ bool DBCHandler::processSignalDouble(const CANFrame &frame, const DBC_SIGNAL &si
{
result = Utility::processIntegerSignal(frame.data, sig.startBit, sig.signalSize, sig.intelByteOrder, isSigned);
endResult = ((double)result * sig.factor) + sig.bias;
result = (int64_t)endResult;
result = (int64_t)endResult;
}
else if (sig.valType == SP_FLOAT)
{
+225 -186
View File
@@ -799,178 +799,43 @@ void GraphingWindow::addNewGraph()
void GraphingWindow::appendToGraph(GraphParams &params, CANFrame &frame)
{
int64_t tempVal; //64 bit temp value.
if (params.endByte == -1 || params.startByte == params.endByte)
if (params.isDBCSignal)
{
tempVal = (frame.data[params.startByte] & params.mask);
if (params.isSigned && tempVal > 127)
{
tempVal = tempVal - 256;
}
if (secondsMode)
{
params.x.append((double)(frame.timestamp) / 1000000.0 - params.xbias);
}
else
{
params.x.append(frame.timestamp - params.xbias);
}
params.y.append((tempVal * params.scale) + params.bias);
}
else if (params.endByte > params.startByte) //big endian
else
{
float tempValue;
int64_t tempValInt;
int numBytes = (params.endByte - params.startByte) + 1;
int64_t shiftRef = 1 << (numBytes * 8);
uint64_t maskShifter;
uint8_t tempByte;
tempValInt = 0;
int64_t expon = 1;
maskShifter = params.mask;
for (int c = 0; c < numBytes; c++)
if (params.endByte == -1 || params.startByte == params.endByte)
{
tempByte = frame.data[params.endByte - c];
tempByte &= maskShifter;
tempValInt += (tempByte * expon);
expon *= 256;
maskShifter = maskShifter >> 8;
}
tempValInt &= params.mask;
int64_t twocompPoint = params.mask;
if (shiftRef < twocompPoint || twocompPoint == -1) twocompPoint = shiftRef;
//qDebug() << "two comp point: " << twocompPoint;
if (params.isSigned && tempValInt > ((twocompPoint / 2)))
{
tempValInt = tempValInt - twocompPoint;
}
tempValue = (float)tempValInt;
if (secondsMode)
{
params.x.append((double)(frame.timestamp) / 1000000.0 - params.xbias);
}
else
{
params.x.append(frame.timestamp - params.xbias);
}
params.y.append((tempValue * params.scale) + params.bias);
}
else //little endian
{
float tempValue;
int64_t tempValInt;
int numBytes = (params.startByte - params.endByte) + 1;
int64_t shiftRef = 1 << (numBytes * 8);
uint64_t maskShifter;
uint8_t tempByte;
tempValInt = 0;
int64_t expon = 1;
maskShifter = params.mask;
for (int c = 0; c < numBytes; c++)
{
tempByte = frame.data[params.endByte + c];
tempByte &= maskShifter;
tempValInt += tempByte * expon;
expon *= 256;
maskShifter = maskShifter >> 8;
}
tempValInt &= params.mask;
int64_t twocompPoint = params.mask;
if (shiftRef < twocompPoint || twocompPoint == -1) twocompPoint = shiftRef;
//qDebug() << "two comp point: " << twocompPoint;
if (params.isSigned && tempValInt > ((twocompPoint / 2)))
{
tempValInt = tempValInt - twocompPoint;
}
tempValue = (float)tempValInt;
if (secondsMode)
{
params.x.append((double)(frame.timestamp) / 1000000.0 - params.xbias);
}
else
{
params.x.append(frame.timestamp - params.xbias);
}
params.y.append((tempValue * params.scale) + params.bias);
}
params.ref->setData(params.x,params.y);
}
void GraphingWindow::createGraph(GraphParams &params, bool createGraphParam)
{
int64_t tempVal; //64 bit temp value.
float yminval=10000000.0, ymaxval = -1000000.0;
float xminval=10000000000.0, xmaxval = -10000000000.0;
GraphParams *refParam = &params;
qDebug() << "New Graph ID: " << params.ID;
qDebug() << "Start byte: " << params.startByte;
qDebug() << "End Byte: " << params.endByte;
qDebug() << "Mask: " << params.mask;
frameCache.clear();
for (int i = 0; i < modelFrames->count(); i++)
{
CANFrame thisFrame = modelFrames->at(i);
if (thisFrame.ID == params.ID) frameCache.append(thisFrame);
}
int numEntries = frameCache.count() / params.stride;
params.x.reserve(numEntries);
params.y.reserve(numEntries);
params.x.fill(0, numEntries);
params.y.fill(0, numEntries);
if (params.endByte == -1 || params.startByte == params.endByte)
{
for (int j = 0; j < numEntries; j++)
{
tempVal = (frameCache[j * params.stride].data[params.startByte] & params.mask);
tempVal = (frame.data[params.startByte] & params.mask);
if (params.isSigned && tempVal > 127)
{
tempVal = tempVal - 256;
}
if (secondsMode)
{
params.x[j] = (double)(frameCache[j].timestamp) / 1000000.0;
params.x.append((double)(frame.timestamp) / 1000000.0 - params.xbias);
}
else
{
params.x[j] = frameCache[j].timestamp;
params.x.append(frame.timestamp - params.xbias);
}
params.y[j] = (tempVal * params.scale) + params.bias;
if (params.y[j] < yminval) yminval = params.y[j];
if (params.y[j] > ymaxval) ymaxval = params.y[j];
if (params.x[j] < xminval) xminval = params.x[j];
if (params.x[j] > xmaxval) xmaxval = params.x[j];
params.y.append((tempVal * params.scale) + params.bias);
}
}
else if (params.endByte > params.startByte) //big endian
{
float tempValue;
int64_t tempValInt;
int numBytes = (params.endByte - params.startByte) + 1;
int64_t shiftRef = (uint64_t)1 << (numBytes * 8);
uint64_t maskShifter;
uint8_t tempByte;
for (int j = 0; j < numEntries; j++)
else if (params.endByte > params.startByte) //big endian
{
float tempValue;
int64_t tempValInt;
int numBytes = (params.endByte - params.startByte) + 1;
int64_t shiftRef = 1 << (numBytes * 8);
uint64_t maskShifter;
uint8_t tempByte;
tempValInt = 0;
int64_t expon = 1;
maskShifter = params.mask;
for (int c = 0; c < numBytes; c++)
{
tempByte = frameCache[j * params.stride].data[params.endByte - c];
tempByte = frame.data[params.endByte - c];
tempByte &= maskShifter;
tempValInt += (tempByte * expon);
expon *= 256;
@@ -991,36 +856,29 @@ void GraphingWindow::createGraph(GraphParams &params, bool createGraphParam)
if (secondsMode)
{
params.x[j] = (double)(frameCache[j].timestamp) / 1000000.0;
params.x.append((double)(frame.timestamp) / 1000000.0 - params.xbias);
}
else
{
params.x[j] = frameCache[j].timestamp;
params.x.append(frame.timestamp - params.xbias);
}
params.y[j] = (tempValue * params.scale) + params.bias;
if (params.y[j] < yminval) yminval = params.y[j];
if (params.y[j] > ymaxval) ymaxval = params.y[j];
if (params.x[j] < xminval) xminval = params.x[j];
if (params.x[j] > xmaxval) xmaxval = params.x[j];
params.y.append((tempValue * params.scale) + params.bias);
}
}
else //little endian
{
float tempValue;
int64_t tempValInt;
int numBytes = (params.startByte - params.endByte) + 1;
int64_t shiftRef = (uint64_t)1 << (numBytes * 8);
uint64_t maskShifter;
uint8_t tempByte;
for (int j = 0; j < numEntries; j++)
else //little endian
{
float tempValue;
int64_t tempValInt;
int numBytes = (params.startByte - params.endByte) + 1;
int64_t shiftRef = 1 << (numBytes * 8);
uint64_t maskShifter;
uint8_t tempByte;
tempValInt = 0;
int64_t expon = 1;
maskShifter = params.mask;
for (int c = 0; c < numBytes; c++)
{
tempByte = frameCache[j * params.stride].data[params.endByte + c];
tempByte = frame.data[params.endByte + c];
tempByte &= maskShifter;
tempValInt += tempByte * expon;
expon *= 256;
@@ -1040,33 +898,214 @@ void GraphingWindow::createGraph(GraphParams &params, bool createGraphParam)
if (secondsMode)
{
params.x[j] = (double)(frameCache[j].timestamp) / 1000000.0;
params.x.append((double)(frame.timestamp) / 1000000.0 - params.xbias);
}
else
{
params.x[j] = frameCache[j].timestamp;
params.x.append(frame.timestamp - params.xbias);
}
params.y[j] = (tempValue * params.scale) + params.bias;
if (params.y[j] < yminval) yminval = params.y[j];
if (params.y[j] > ymaxval) ymaxval = params.y[j];
if (params.x[j] < xminval) xminval = params.x[j];
if (params.x[j] > xmaxval) xmaxval = params.x[j];
params.y.append((tempValue * params.scale) + params.bias);
}
}
params.ref->setData(params.x,params.y);
}
void GraphingWindow::createGraph(GraphParams &params, bool createGraphParam)
{
int64_t tempVal; //64 bit temp value.
float yminval=10000000.0, ymaxval = -1000000.0;
float xminval=10000000000.0, xmaxval = -10000000000.0;
GraphParams *refParam = &params;
if (params.isDBCSignal)
{
qDebug() << "New signal graph: " << params.signal->name <<" in ID:" << params.ID;
}
else
{
qDebug() << "New Graph ID: " << params.ID;
qDebug() << "Start byte: " << params.startByte;
qDebug() << "End Byte: " << params.endByte;
qDebug() << "Mask: " << params.mask;
}
frameCache.clear();
for (int i = 0; i < modelFrames->count(); i++)
{
CANFrame thisFrame = modelFrames->at(i);
if (thisFrame.ID == params.ID) frameCache.append(thisFrame);
}
int numEntries = frameCache.count() / params.stride;
params.x.reserve(numEntries);
params.y.reserve(numEntries);
params.x.fill(0, numEntries);
params.y.fill(0, numEntries);
if (params.isDBCSignal)
{
double tempValue;
int l = 0;
for (int j = 0; j < numEntries; j++)
{
//if the given signal was found and successfully processed in this frame then add it to the graph
if (dbcHandler->processSignalDouble(frameCache[j], *params.signal, tempValue))
{
//qDebug() << "tempValue: " << tempValue;
if (secondsMode)
{
params.x[l] = (double)(frameCache[j].timestamp) / 1000000.0;
}
else
{
params.x[l] = frameCache[j].timestamp;
}
params.y[l] = tempValue;
if (params.y[l] < yminval) yminval = params.y[l];
if (params.y[l] > ymaxval) ymaxval = params.y[l];
if (params.x[l] < xminval) xminval = params.x[l];
if (params.x[l] > xmaxval) xmaxval = params.x[l];
l++;
}
}
params.x.resize(l);
params.y.resize(l);
params.x.squeeze();
params.y.squeeze();
}
else
{
if (params.endByte == -1 || params.startByte == params.endByte)
{
for (int j = 0; j < numEntries; j++)
{
tempVal = (frameCache[j * params.stride].data[params.startByte] & params.mask);
if (params.isSigned && tempVal > 127)
{
tempVal = tempVal - 256;
}
if (secondsMode)
{
params.x[j] = (double)(frameCache[j].timestamp) / 1000000.0;
}
else
{
params.x[j] = frameCache[j].timestamp;
}
params.y[j] = (tempVal * params.scale) + params.bias;
if (params.y[j] < yminval) yminval = params.y[j];
if (params.y[j] > ymaxval) ymaxval = params.y[j];
if (params.x[j] < xminval) xminval = params.x[j];
if (params.x[j] > xmaxval) xmaxval = params.x[j];
}
}
else if (params.endByte > params.startByte) //big endian
{
float tempValue;
int64_t tempValInt;
int numBytes = (params.endByte - params.startByte) + 1;
int64_t shiftRef = (uint64_t)1 << (numBytes * 8);
uint64_t maskShifter;
uint8_t tempByte;
for (int j = 0; j < numEntries; j++)
{
tempValInt = 0;
int64_t expon = 1;
maskShifter = params.mask;
for (int c = 0; c < numBytes; c++)
{
tempByte = frameCache[j * params.stride].data[params.endByte - c];
tempByte &= maskShifter;
tempValInt += (tempByte * expon);
expon *= 256;
maskShifter = maskShifter >> 8;
}
tempValInt &= params.mask;
int64_t twocompPoint = params.mask;
if (shiftRef < twocompPoint || twocompPoint == -1) twocompPoint = shiftRef;
//qDebug() << "two comp point: " << twocompPoint;
if (params.isSigned && tempValInt > ((twocompPoint / 2)))
{
tempValInt = tempValInt - twocompPoint;
}
tempValue = (float)tempValInt;
if (secondsMode)
{
params.x[j] = (double)(frameCache[j].timestamp) / 1000000.0;
}
else
{
params.x[j] = frameCache[j].timestamp;
}
params.y[j] = (tempValue * params.scale) + params.bias;
if (params.y[j] < yminval) yminval = params.y[j];
if (params.y[j] > ymaxval) ymaxval = params.y[j];
if (params.x[j] < xminval) xminval = params.x[j];
if (params.x[j] > xmaxval) xmaxval = params.x[j];
}
}
else //little endian
{
float tempValue;
int64_t tempValInt;
int numBytes = (params.startByte - params.endByte) + 1;
int64_t shiftRef = (uint64_t)1 << (numBytes * 8);
uint64_t maskShifter;
uint8_t tempByte;
for (int j = 0; j < numEntries; j++)
{
tempValInt = 0;
int64_t expon = 1;
maskShifter = params.mask;
for (int c = 0; c < numBytes; c++)
{
tempByte = frameCache[j * params.stride].data[params.endByte + c];
tempByte &= maskShifter;
tempValInt += tempByte * expon;
expon *= 256;
maskShifter = maskShifter >> 8;
}
tempValInt &= params.mask;
int64_t twocompPoint = params.mask;
if (shiftRef < twocompPoint || twocompPoint == -1) twocompPoint = shiftRef;
//qDebug() << "two comp point: " << twocompPoint;
if (params.isSigned && tempValInt > ((twocompPoint / 2)))
{
tempValInt = tempValInt - twocompPoint;
}
tempValue = (float)tempValInt;
if (secondsMode)
{
params.x[j] = (double)(frameCache[j].timestamp) / 1000000.0;
}
else
{
params.x[j] = frameCache[j].timestamp;
}
params.y[j] = (tempValue * params.scale) + params.bias;
if (params.y[j] < yminval) yminval = params.y[j];
if (params.y[j] > ymaxval) ymaxval = params.y[j];
if (params.x[j] < xminval) xminval = params.x[j];
if (params.x[j] > xmaxval) xmaxval = params.x[j];
}
}
}
params.xbias = 0;
/*
for (int ct = 0; ct < params.x.count(); ct++)
{
params.x[ct] -= xminval;
}
params.xbias = xminval;
xmaxval -= xminval;
xminval = 0;
*/
ui->graphingView->addGraph();
params.ref = ui->graphingView->graph();
if (createGraphParam)
+3 -1
View File
@@ -24,7 +24,9 @@ public:
QColor color;
QCPGraph *ref;
QString graphName;
//the below stuff is used for internal purposes only
bool isDBCSignal; //is this graph actually a DBC signal? If so the below pointer should be filled out
DBC_SIGNAL *signal;
//the below stuff is used for internal purposes only - code should be refactored so these can be private
QVector<double> x, y;
double xbias;
};
+5 -2
View File
@@ -9,14 +9,17 @@
#include "serialworker.h"
/*
Things that were planned for the GVRET-PC project but never completed.
Some notes on things I'd like to put into the program but haven't put on github (yet)
Single / Multi state - The goal is to find bits that change based on toggles or discrete state items (shifters, etc)
fuzzy scope - Try to find potential places where a given value might be stored - offer guesses and the program tries to find candidates for you
or, try to find things that appear to be multi-byte integers
Change dbc editor to be able to do multiplexed signals
Make the graphing window use signals either exclusively or as an option for each signal graphed
Allow multiple DBC files to be loaded in the program and also allow them to be associated with a bus
Allow scripts to read/write signals from DBC files
allow scripts to load DBC files in support of the script
*/
QString MainWindow::loadedFileName = "";
+109 -42
View File
@@ -25,6 +25,8 @@ NewGraphDialog::NewGraphDialog(DBCHandler *handler, QWidget *parent) :
connect(ui->cbMessages, SIGNAL(currentIndexChanged(int)), this, SLOT(loadSignals(int)));
connect(ui->cbSignals, SIGNAL(currentIndexChanged(int)), this, SLOT(fillFormFromSignal(int)));
connect(ui->rbSignalGraph, SIGNAL(toggled(bool)), this, SLOT(setSignalActive(bool)));
connect(ui->rbStandardGraph, SIGNAL(toggled(bool)), this, SLOT(setStandardActive(bool)));
loadMessages();
}
@@ -58,28 +60,41 @@ void NewGraphDialog::clearParams()
ui->txtStride->clear();
ui->txtName->clear();
ui->txtData->clear();
ui->rbStandardGraph->setChecked(true);
setStandardActive(true);
}
void NewGraphDialog::setParams(GraphParams &params)
{
ui->txtID->setText(Utility::formatNumber(params.ID));
ui->txtBias->setText(QString::number(params.bias));
ui->txtMask->setText(Utility::formatNumber(params.mask));
ui->txtScale->setText(QString::number(params.scale));
ui->txtStride->setText(QString::number(params.stride));
ui->cbSigned->setChecked(params.isSigned);
ui->txtName->setText(params.graphName);
if (params.endByte > -1)
if (params.isDBCSignal)
{
ui->txtData->setText(QString::number(params.startByte) + "-" + QString::number(params.endByte));
clearParams();
setSignalActive(true);
//loadMessages();
}
else
{
ui->txtData->setText(QString::number(params.startByte));
setStandardActive(true);
ui->txtID->setText(Utility::formatNumber(params.ID));
ui->txtBias->setText(QString::number(params.bias));
ui->txtMask->setText(Utility::formatNumber(params.mask));
ui->txtScale->setText(QString::number(params.scale));
ui->txtStride->setText(QString::number(params.stride));
ui->cbSigned->setChecked(params.isSigned);
if (params.endByte > -1)
{
ui->txtData->setText(QString::number(params.startByte) + "-" + QString::number(params.endByte));
}
else
{
ui->txtData->setText(QString::number(params.startByte));
}
}
ui->txtName->setText(params.graphName);
QPalette p = ui->colorSwatch->palette();
p.setColor(QPalette::Button, params.color);
ui->colorSwatch->setPalette(p);
@@ -87,31 +102,50 @@ void NewGraphDialog::setParams(GraphParams &params)
void NewGraphDialog::getParams(GraphParams &params)
{
params.ID = Utility::ParseStringToNum(ui->txtID->text());
params.bias = ui->txtBias->text().toFloat();
params.isDBCSignal = ui->rbSignalGraph->isChecked();
params.color = ui->colorSwatch->palette().button().color();
params.isSigned = ui->cbSigned->isChecked();
params.mask = Utility::ParseStringToNum(ui->txtMask->text());
params.scale = ui->txtScale->text().toFloat();
params.stride = Utility::ParseStringToNum(ui->txtStride->text());
QStringList values = ui->txtData->text().split('-');
params.startByte = -1;
params.endByte = -1;
if (values.count() > 0)
{
params.startByte = values[0].toInt();
if (values.count() > 1)
{
params.endByte = values[1].toInt();
}
}
//now catch stupidity and bring it to defaults
if (params.mask == 0) params.mask = 0xFFFFFFFF;
if (fabs(params.scale) < 0.00000001) params.scale = 1.0f;
if (params.stride < 1) params.stride = 1;
params.graphName = ui->txtName->text();
if (params.isDBCSignal)
{
DBC_MESSAGE *msg = dbcHandler->findMsgByName(ui->cbMessages->currentText());
if (msg != NULL)
params.signal = dbcHandler->findSignalByName(msg, ui->cbSignals->currentText());
params.ID = params.signal->parentMessage->ID;
params.bias = 0;
params.isSigned = false;
params.mask = 0;
params.scale = 1;
params.bias = 0;
params.stride = 1;
}
else {
params.ID = Utility::ParseStringToNum(ui->txtID->text());
params.bias = ui->txtBias->text().toFloat();
params.isSigned = ui->cbSigned->isChecked();
params.mask = Utility::ParseStringToNum(ui->txtMask->text());
params.scale = ui->txtScale->text().toFloat();
params.stride = Utility::ParseStringToNum(ui->txtStride->text());
params.signal = NULL;
QStringList values = ui->txtData->text().split('-');
params.startByte = -1;
params.endByte = -1;
if (values.count() > 0)
{
params.startByte = values[0].toInt();
if (values.count() > 1)
{
params.endByte = values[1].toInt();
}
}
//now catch stupidity and bring it to defaults
if (params.mask == 0) params.mask = 0xFFFFFFFF;
if (fabs(params.scale) < 0.00000001) params.scale = 1.0f;
if (params.stride < 1) params.stride = 1;
}
}
void NewGraphDialog::loadMessages()
@@ -154,14 +188,15 @@ void NewGraphDialog::fillFormFromSignal(int idx)
if (sig == NULL) return;
params.graphName = sig->name;
params.ID = msg->ID;
params.bias = sig->bias;
params.scale = sig->factor;
params.stride = 1;
params.mask = (1 << (sig->signalSize)) - 1;
if (sig->valType == SIGNED_INT) params.isSigned = true;
else params.isSigned = false;
//params.ID = msg->ID;
//params.bias = sig->bias;
//params.scale = sig->factor;
//params.stride = 1;
//params.mask = (1 << (sig->signalSize)) - 1;
//if (sig->valType == SIGNED_INT) params.isSigned = true;
//else params.isSigned = false;
params.color = ui->colorSwatch->palette().color(QPalette::Button);
/*
if (sig->intelByteOrder)
{
//for this ordering the byte order is reserved and starting byte
@@ -175,6 +210,38 @@ void NewGraphDialog::fillFormFromSignal(int idx)
params.startByte = sig->startBit / 8;
params.endByte = (sig->startBit + sig->signalSize - 1) / 8;
}
*/
setParams(params);
}
void NewGraphDialog::setSignalActive(bool state)
{
if (!state) return;
ui->rbSignalGraph->setChecked(true);
ui->rbStandardGraph->setChecked(false);
ui->cbMessages->setEnabled(true);
ui->cbSignals->setEnabled(true);
ui->cbSigned->setEnabled(false);
ui->txtBias->setEnabled(false);
ui->txtData->setEnabled(false);
ui->txtID->setEnabled(false);
ui->txtMask->setEnabled(false);
ui->txtScale->setEnabled(false);
ui->txtStride->setEnabled(false);
}
void NewGraphDialog::setStandardActive(bool state)
{
if (!state) return;
ui->rbStandardGraph->setChecked(true);
ui->rbSignalGraph->setChecked(false);
ui->cbMessages->setEnabled(false);
ui->cbSignals->setEnabled(false);
ui->cbSigned->setEnabled(true);
ui->txtBias->setEnabled(true);
ui->txtData->setEnabled(true);
ui->txtID->setEnabled(true);
ui->txtMask->setEnabled(true);
ui->txtScale->setEnabled(true);
ui->txtStride->setEnabled(true);
}
+2
View File
@@ -26,6 +26,8 @@ private slots:
void loadMessages();
void loadSignals(int idx);
void fillFormFromSignal(int idx);
void setSignalActive(bool);
void setStandardActive(bool);
private:
Ui::NewGraphDialog *ui;
+25 -1
View File
@@ -7,13 +7,37 @@
<x>0</x>
<y>0</y>
<width>492</width>
<height>309</height>
<height>340</height>
</rect>
</property>
<property name="windowTitle">
<string>Graph Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QRadioButton" name="rbStandardGraph">
<property name="text">
<string>Standard Graph</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rbSignalGraph">
<property name="text">
<string>DBC Signal Graph</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,1">
<item>