mirror of
https://bitbucket.org/librepilot/librepilot.git
synced 2025-03-02 19:29:15 +01:00
OP-42 GCS/Scope: Plot can now show high frequency data effectively
git-svn-id: svn://svn.openpilot.org/OpenPilot/trunk@1286 ebee16cc-31ac-478f-84a7-5cbb03baadba
This commit is contained in:
parent
a075eafb98
commit
fdfd004634
@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
#include "plotdata.h"
|
#include "plotdata.h"
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
PlotData::PlotData(QString p_uavObject, QString p_uavField)
|
PlotData::PlotData(QString p_uavObject, QString p_uavField)
|
||||||
{
|
{
|
||||||
@ -67,7 +68,7 @@ bool SequencialPlotData::append(UAVObject* obj)
|
|||||||
xData->insert(xData->size(), xData->size());
|
xData->insert(xData->size(), xData->size());
|
||||||
|
|
||||||
//notify the gui of changes in the data
|
//notify the gui of changes in the data
|
||||||
dataChanged();
|
//dataChanged();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,7 +94,7 @@ bool ChronoPlotData::append(UAVObject* obj)
|
|||||||
removeStaleData();
|
removeStaleData();
|
||||||
|
|
||||||
//notify the gui of chages in the data
|
//notify the gui of chages in the data
|
||||||
dataChanged();
|
//dataChanged();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,12 +120,15 @@ void ChronoPlotData::removeStaleData()
|
|||||||
} else
|
} else
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//qDebug() << "removeStaleData ";
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChronoPlotData::removeStaleDataTimeout()
|
void ChronoPlotData::removeStaleDataTimeout()
|
||||||
{
|
{
|
||||||
removeStaleData();
|
removeStaleData();
|
||||||
dataChanged();
|
//dataChanged();
|
||||||
|
//qDebug() << "removeStaleDataTimeout";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UAVObjectPlotData::append(UAVObject* obj)
|
bool UAVObjectPlotData::append(UAVObject* obj)
|
||||||
|
@ -77,6 +77,9 @@ public:
|
|||||||
|
|
||||||
virtual bool append(UAVObject* obj) = 0;
|
virtual bool append(UAVObject* obj) = 0;
|
||||||
virtual PlotType plotType() = 0;
|
virtual PlotType plotType() = 0;
|
||||||
|
virtual void removeStaleData() = 0;
|
||||||
|
|
||||||
|
void updatePlotCurveData();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void dataChanged();
|
void dataChanged();
|
||||||
@ -105,6 +108,11 @@ public:
|
|||||||
virtual PlotType plotType() {
|
virtual PlotType plotType() {
|
||||||
return SequencialPlot;
|
return SequencialPlot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Removes the old data from the buffer
|
||||||
|
*/
|
||||||
|
virtual void removeStaleData(){}
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -114,16 +122,11 @@ class ChronoPlotData : public PlotData
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
ChronoPlotData(QString uavObject, QString uavField, double refreshInterval)
|
ChronoPlotData(QString uavObject, QString uavField)
|
||||||
: PlotData(uavObject, uavField) {
|
: PlotData(uavObject, uavField) {
|
||||||
scalePower = 1;
|
scalePower = 1;
|
||||||
//Setup timer that removes stale data
|
|
||||||
timer = new QTimer();
|
|
||||||
connect(timer, SIGNAL(timeout()), this, SLOT(removeStaleDataTimeout()));
|
|
||||||
timer->start(refreshInterval * 1000);
|
|
||||||
}
|
}
|
||||||
~ChronoPlotData() {
|
~ChronoPlotData() {
|
||||||
delete timer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool append(UAVObject* obj);
|
bool append(UAVObject* obj);
|
||||||
@ -131,10 +134,10 @@ public:
|
|||||||
virtual PlotType plotType() {
|
virtual PlotType plotType() {
|
||||||
return ChronoPlot;
|
return ChronoPlot;
|
||||||
}
|
}
|
||||||
private:
|
|
||||||
void removeStaleData();
|
|
||||||
|
|
||||||
QTimer *timer;
|
virtual void removeStaleData();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void removeStaleDataTimeout();
|
void removeStaleDataTimeout();
|
||||||
@ -157,6 +160,8 @@ public:
|
|||||||
virtual PlotType plotType() {
|
virtual PlotType plotType() {
|
||||||
return UAVObjectPlot;
|
return UAVObjectPlot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual void removeStaleData(){}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PLOTDATA_H
|
#endif // PLOTDATA_H
|
||||||
|
@ -33,12 +33,14 @@
|
|||||||
|
|
||||||
ScopeGadget::ScopeGadget(QString classId, ScopeGadgetWidget *widget, QWidget *parent) :
|
ScopeGadget::ScopeGadget(QString classId, ScopeGadgetWidget *widget, QWidget *parent) :
|
||||||
IUAVGadget(classId, parent),
|
IUAVGadget(classId, parent),
|
||||||
m_widget(widget)
|
m_widget(widget),
|
||||||
|
configLoaded(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScopeGadget::loadConfiguration(IUAVGadgetConfiguration* config)
|
void ScopeGadget::loadConfiguration(IUAVGadgetConfiguration* config)
|
||||||
{
|
{
|
||||||
|
|
||||||
ScopeGadgetConfiguration *sgConfig = qobject_cast<ScopeGadgetConfiguration*>(config);
|
ScopeGadgetConfiguration *sgConfig = qobject_cast<ScopeGadgetConfiguration*>(config);
|
||||||
ScopeGadgetWidget* widget = qobject_cast<ScopeGadgetWidget*>(m_widget);
|
ScopeGadgetWidget* widget = qobject_cast<ScopeGadgetWidget*>(m_widget);
|
||||||
|
|
||||||
@ -63,8 +65,7 @@ void ScopeGadget::loadConfiguration(IUAVGadgetConfiguration* config)
|
|||||||
uavObject,
|
uavObject,
|
||||||
uavField,
|
uavField,
|
||||||
scale,
|
scale,
|
||||||
QPen(
|
QPen( QBrush(QColor(color),Qt::SolidPattern),
|
||||||
QBrush(QColor(color),Qt::SolidPattern),
|
|
||||||
(qreal)2,
|
(qreal)2,
|
||||||
Qt::SolidLine,
|
Qt::SolidLine,
|
||||||
Qt::SquareCap,
|
Qt::SquareCap,
|
||||||
|
@ -61,6 +61,8 @@ public:
|
|||||||
private:
|
private:
|
||||||
QWidget *m_widget;
|
QWidget *m_widget;
|
||||||
QList<int> m_context;
|
QList<int> m_context;
|
||||||
|
|
||||||
|
bool configLoaded;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ ScopeGadgetConfiguration::ScopeGadgetConfiguration(QString classId, const QByteA
|
|||||||
IUAVGadgetConfiguration(classId, parent),
|
IUAVGadgetConfiguration(classId, parent),
|
||||||
m_plotType((int)ChronoPlot),
|
m_plotType((int)ChronoPlot),
|
||||||
m_dataSize(60),
|
m_dataSize(60),
|
||||||
m_refreshInterval(1)
|
m_refreshInterval(1000)
|
||||||
{
|
{
|
||||||
uint currentStreamVersion = 0;
|
uint currentStreamVersion = 0;
|
||||||
int plotCurveCount = 0;
|
int plotCurveCount = 0;
|
||||||
@ -71,6 +71,9 @@ ScopeGadgetConfiguration::ScopeGadgetConfiguration(QString classId, const QByteA
|
|||||||
m_PlotCurveConfigs.append(plotCurveConf);
|
m_PlotCurveConfigs.append(plotCurveConf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//The value is converted to milliseconds, so if it is < 100, it is still seconds
|
||||||
|
if(m_refreshInterval < 100)
|
||||||
|
m_refreshInterval *= 1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>551</width>
|
<width>551</width>
|
||||||
<height>231</height>
|
<height>275</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
@ -97,16 +97,19 @@
|
|||||||
<item row="3" column="1">
|
<item row="3" column="1">
|
||||||
<widget class="QSpinBox" name="spnRefreshInterval">
|
<widget class="QSpinBox" name="spnRefreshInterval">
|
||||||
<property name="suffix">
|
<property name="suffix">
|
||||||
<string> seconds</string>
|
<string>ms</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="minimum">
|
<property name="minimum">
|
||||||
<number>1</number>
|
<number>100</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="maximum">
|
<property name="maximum">
|
||||||
<number>30</number>
|
<number>30000</number>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<number>500</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="value">
|
<property name="value">
|
||||||
<number>5</number>
|
<number>1000</number>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -49,15 +49,12 @@ TestDataGen* ScopeGadgetWidget::testDataGen;
|
|||||||
|
|
||||||
ScopeGadgetWidget::ScopeGadgetWidget(QWidget *parent) : QwtPlot(parent)
|
ScopeGadgetWidget::ScopeGadgetWidget(QWidget *parent) : QwtPlot(parent)
|
||||||
{
|
{
|
||||||
// if(testDataGen == 0)
|
// if(testDataGen == 0)
|
||||||
// testDataGen = new TestDataGen();
|
// testDataGen = new TestDataGen();
|
||||||
|
|
||||||
setCanvasBackground(Qt::darkBlue);
|
//Setup the timer that replots data
|
||||||
|
replotTimer = new QTimer(this);
|
||||||
QwtPlotGrid *grid = new QwtPlotGrid;
|
connect(replotTimer, SIGNAL(timeout()), this, SLOT(replotNewData()));
|
||||||
grid->setMajPen(QPen(Qt::gray, 0, Qt::DashLine));
|
|
||||||
grid->setMinPen(QPen(Qt::lightGray, 0 , Qt::DotLine));
|
|
||||||
grid->attach(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScopeGadgetWidget::preparePlot(PlotType plotType)
|
void ScopeGadgetWidget::preparePlot(PlotType plotType)
|
||||||
@ -71,6 +68,14 @@ void ScopeGadgetWidget::preparePlot(PlotType plotType)
|
|||||||
// Show a title
|
// Show a title
|
||||||
setTitle("Scope");
|
setTitle("Scope");
|
||||||
|
|
||||||
|
setCanvasBackground(Qt::darkBlue);
|
||||||
|
|
||||||
|
//Add grid lines
|
||||||
|
QwtPlotGrid *grid = new QwtPlotGrid;
|
||||||
|
grid->setMajPen(QPen(Qt::gray, 0, Qt::DashLine));
|
||||||
|
grid->setMinPen(QPen(Qt::lightGray, 0 , Qt::DotLine));
|
||||||
|
grid->attach(this);
|
||||||
|
|
||||||
// Show a legend at the bottom
|
// Show a legend at the bottom
|
||||||
if (legend() == 0) {
|
if (legend() == 0) {
|
||||||
QwtLegend *legend = new QwtLegend();
|
QwtLegend *legend = new QwtLegend();
|
||||||
@ -80,6 +85,14 @@ void ScopeGadgetWidget::preparePlot(PlotType plotType)
|
|||||||
}
|
}
|
||||||
|
|
||||||
connect(this, SIGNAL(legendChecked(QwtPlotItem *, bool)),this, SLOT(showCurve(QwtPlotItem *, bool)));
|
connect(this, SIGNAL(legendChecked(QwtPlotItem *, bool)),this, SLOT(showCurve(QwtPlotItem *, bool)));
|
||||||
|
|
||||||
|
if(!replotTimer->isActive())
|
||||||
|
replotTimer->start(m_refreshInterval);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
replotTimer->setInterval(m_refreshInterval);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScopeGadgetWidget::showCurve(QwtPlotItem *item, bool on)
|
void ScopeGadgetWidget::showCurve(QwtPlotItem *item, bool on)
|
||||||
@ -110,8 +123,8 @@ void ScopeGadgetWidget::setupChronoPlot()
|
|||||||
setAxisTitle(QwtPlot::xBottom, "Time [h:m:s]");
|
setAxisTitle(QwtPlot::xBottom, "Time [h:m:s]");
|
||||||
setAxisScaleDraw(QwtPlot::xBottom, new TimeScaleDraw());
|
setAxisScaleDraw(QwtPlot::xBottom, new TimeScaleDraw());
|
||||||
uint NOW = QDateTime::currentDateTime().toTime_t();
|
uint NOW = QDateTime::currentDateTime().toTime_t();
|
||||||
setAxisScale(QwtPlot::xBottom, NOW - m_xWindowSize, NOW);
|
setAxisScale(QwtPlot::xBottom, NOW - m_xWindowSize / 1000, NOW);
|
||||||
setAxisLabelRotation(QwtPlot::xBottom, -50.0);
|
setAxisLabelRotation(QwtPlot::xBottom, -15.0);
|
||||||
setAxisLabelAlignment(QwtPlot::xBottom, Qt::AlignLeft | Qt::AlignBottom);
|
setAxisLabelAlignment(QwtPlot::xBottom, Qt::AlignLeft | Qt::AlignBottom);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -135,7 +148,7 @@ void ScopeGadgetWidget::addCurvePlot(QString uavObject, QString uavField, int sc
|
|||||||
if (m_plotType == SequencialPlot)
|
if (m_plotType == SequencialPlot)
|
||||||
plotData = new SequencialPlotData(uavObject, uavField);
|
plotData = new SequencialPlotData(uavObject, uavField);
|
||||||
else if (m_plotType == ChronoPlot)
|
else if (m_plotType == ChronoPlot)
|
||||||
plotData = new ChronoPlotData(uavObject, uavField, m_refreshInterval);
|
plotData = new ChronoPlotData(uavObject, uavField);
|
||||||
//else if (m_plotType == UAVObjectPlot)
|
//else if (m_plotType == UAVObjectPlot)
|
||||||
// plotData = new UAVObjectPlotData(uavObject, uavField);
|
// plotData = new UAVObjectPlotData(uavObject, uavField);
|
||||||
|
|
||||||
@ -169,8 +182,6 @@ void ScopeGadgetWidget::addCurvePlot(QString uavObject, QString uavField, int sc
|
|||||||
connect(obj, SIGNAL(objectUpdated(UAVObject*)), this, SLOT(uavObjectReceived(UAVObject*)));
|
connect(obj, SIGNAL(objectUpdated(UAVObject*)), this, SLOT(uavObjectReceived(UAVObject*)));
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(plotData, SIGNAL(dataChanged()), this, SLOT(replotNewData()));
|
|
||||||
|
|
||||||
replot();
|
replot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,16 +203,24 @@ void ScopeGadgetWidget::uavObjectReceived(UAVObject* obj)
|
|||||||
{
|
{
|
||||||
foreach(PlotData* plotData, m_curvesData.values()) {
|
foreach(PlotData* plotData, m_curvesData.values()) {
|
||||||
plotData->append(obj);
|
plotData->append(obj);
|
||||||
plotData->curve->setData(*plotData->xData, *plotData->yData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScopeGadgetWidget::replotNewData()
|
void ScopeGadgetWidget::replotNewData()
|
||||||
{
|
{
|
||||||
if (m_plotType == ChronoPlot) {
|
foreach(PlotData* plotData, m_curvesData.values()) {
|
||||||
uint NOW = QDateTime::currentDateTime().toTime_t();
|
plotData->removeStaleData();
|
||||||
setAxisScale(QwtPlot::xBottom, NOW - m_xWindowSize, NOW);
|
plotData->curve->setData(*plotData->xData, *plotData->yData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QDateTime NOW = QDateTime::currentDateTime();
|
||||||
|
double toTime = NOW.toTime_t();
|
||||||
|
toTime += NOW.time().msec() / 1000.0;
|
||||||
|
if (m_plotType == ChronoPlot) {
|
||||||
|
setAxisScale(QwtPlot::xBottom, toTime - m_xWindowSize, toTime);
|
||||||
|
}
|
||||||
|
//qDebug() << "replotNewData from " << NOW.addSecs(- m_xWindowSize) << " to " << NOW;
|
||||||
|
|
||||||
replot();
|
replot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,6 +276,11 @@ void ScopeGadgetWidget::setupExamplePlot()
|
|||||||
|
|
||||||
ScopeGadgetWidget::~ScopeGadgetWidget()
|
ScopeGadgetWidget::~ScopeGadgetWidget()
|
||||||
{
|
{
|
||||||
|
if (replotTimer)
|
||||||
|
replotTimer->stop();
|
||||||
|
delete replotTimer;
|
||||||
|
replotTimer = 0;
|
||||||
|
|
||||||
//Get the object to de-monitor
|
//Get the object to de-monitor
|
||||||
ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
|
ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
|
||||||
UAVObjectManager *objManager = pm->getObject<UAVObjectManager>();
|
UAVObjectManager *objManager = pm->getObject<UAVObjectManager>();
|
||||||
@ -281,6 +305,7 @@ void ScopeGadgetWidget::clearCurvePlots()
|
|||||||
m_curvesData.clear();
|
m_curvesData.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TestDataGen::TestDataGen()
|
TestDataGen::TestDataGen()
|
||||||
{
|
{
|
||||||
// Get required UAVObjects
|
// Get required UAVObjects
|
||||||
@ -291,17 +316,20 @@ TestDataGen::TestDataGen()
|
|||||||
gps = PositionActual::GetInstance(objManager);
|
gps = PositionActual::GetInstance(objManager);
|
||||||
|
|
||||||
//Setup timer
|
//Setup timer
|
||||||
|
periodMs = 5;
|
||||||
timer = new QTimer(this);
|
timer = new QTimer(this);
|
||||||
connect(timer, SIGNAL(timeout()), this, SLOT(genTestData()));
|
connect(timer, SIGNAL(timeout()), this, SLOT(genTestData()));
|
||||||
timer->start(2);
|
timer->start(periodMs);
|
||||||
|
|
||||||
|
debugCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestDataGen::genTestData()
|
void TestDataGen::genTestData()
|
||||||
{
|
{
|
||||||
// Update AltitudeActual object
|
// Update AltitudeActual object
|
||||||
AltitudeActual::DataFields altActualData;
|
AltitudeActual::DataFields altActualData;
|
||||||
altActualData.Altitude = 500 * sin(0.1 * testTime) + 200 * cos(0.4 * testTime) + 800;
|
altActualData.Altitude = 500 * sin(1 * testTime) + 200 * cos(4 * testTime) + 800;
|
||||||
altActualData.Temperature = 30 * sin(0.05 * testTime);
|
altActualData.Temperature = 30 * sin(0.5 * testTime);
|
||||||
altActualData.Pressure = 100;
|
altActualData.Pressure = 100;
|
||||||
altActual->setData(altActualData);
|
altActual->setData(altActualData);
|
||||||
|
|
||||||
@ -316,7 +344,11 @@ void TestDataGen::genTestData()
|
|||||||
gpsData.Satellites = 10;
|
gpsData.Satellites = 10;
|
||||||
gps->setData(gpsData);
|
gps->setData(gpsData);
|
||||||
|
|
||||||
testTime += 0.02;
|
testTime += (periodMs / 1000.0);
|
||||||
|
|
||||||
|
debugCounter++;
|
||||||
|
if (debugCounter % 100 == 0 )
|
||||||
|
qDebug() << "Test Time = " << testTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
TestDataGen::~TestDataGen()
|
TestDataGen::~TestDataGen()
|
||||||
|
@ -52,14 +52,17 @@ class TimeScaleDraw : public QwtScaleDraw
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TimeScaleDraw() {
|
TimeScaleDraw() {
|
||||||
baseTime = QDateTime::currentDateTime().toTime_t();
|
//baseTime = QDateTime::currentDateTime().toTime_t();
|
||||||
}
|
}
|
||||||
virtual QwtText label(double v) const {
|
virtual QwtText label(double v) const {
|
||||||
QDateTime upTime = QDateTime::fromTime_t((uint)v);
|
uint seconds = (uint)(v);
|
||||||
|
QDateTime upTime = QDateTime::fromTime_t(seconds);
|
||||||
|
QTime timePart = upTime.time().addMSecs((v - seconds )* 1000);
|
||||||
|
upTime.setTime(timePart);
|
||||||
return upTime.toLocalTime().toString("hh:mm:ss");
|
return upTime.toLocalTime().toString("hh:mm:ss");
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
double baseTime;
|
// double baseTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -80,6 +83,9 @@ private:
|
|||||||
|
|
||||||
QTimer *timer;
|
QTimer *timer;
|
||||||
double testTime;
|
double testTime;
|
||||||
|
int periodMs;
|
||||||
|
|
||||||
|
int debugCounter;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void genTestData();
|
void genTestData();
|
||||||
@ -128,6 +134,8 @@ private:
|
|||||||
QMap<QString, PlotData*> m_curvesData;
|
QMap<QString, PlotData*> m_curvesData;
|
||||||
|
|
||||||
static TestDataGen* testDataGen;
|
static TestDataGen* testDataGen;
|
||||||
|
|
||||||
|
QTimer *replotTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user