mirror of https://github.com/qt/qtgraphs.git
Add readonly visual min and max properties to QValueAxis and QDateTimeAxis
Task-number: QTBUG-142046 Change-Id: I25ab89809d28f3efa27bc83a7a455d3ab2a82a90 Reviewed-by: Sami Varanka <sami.varanka@qt.io> Reviewed-by: Tomi Korpipää <tomi.korpipaa@qt.io>
This commit is contained in:
parent
8e44b5cc66
commit
12e2be4741
|
|
@ -332,6 +332,7 @@ void QDateTimeAxis::setZoom(qreal zoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
d->m_zoom = zoom;
|
d->m_zoom = zoom;
|
||||||
|
d->calculateVisualRange();
|
||||||
emit zoomChanged(zoom);
|
emit zoomChanged(zoom);
|
||||||
emit update();
|
emit update();
|
||||||
}
|
}
|
||||||
|
|
@ -352,6 +353,7 @@ void QDateTimeAxis::setPan(qreal pan)
|
||||||
}
|
}
|
||||||
|
|
||||||
d->m_pan = pan;
|
d->m_pan = pan;
|
||||||
|
d->calculateVisualRange();
|
||||||
emit panChanged(pan);
|
emit panChanged(pan);
|
||||||
emit update();
|
emit update();
|
||||||
}
|
}
|
||||||
|
|
@ -362,6 +364,18 @@ qreal QDateTimeAxis::pan() const
|
||||||
return d->m_pan;
|
return d->m_pan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QDateTime QDateTimeAxis::visualMin() const
|
||||||
|
{
|
||||||
|
Q_D(const QDateTimeAxis);
|
||||||
|
return QDateTime::fromMSecsSinceEpoch(d->m_visualMin, d->m_timeZone);
|
||||||
|
}
|
||||||
|
|
||||||
|
QDateTime QDateTimeAxis::visualMax() const
|
||||||
|
{
|
||||||
|
Q_D(const QDateTimeAxis);
|
||||||
|
return QDateTime::fromMSecsSinceEpoch(d->m_visualMax, d->m_timeZone);
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
QDateTimeAxisPrivate::QDateTimeAxisPrivate() {}
|
QDateTimeAxisPrivate::QDateTimeAxisPrivate() {}
|
||||||
|
|
@ -407,8 +421,30 @@ void QDateTimeAxisPrivate::setRange(qreal min, qreal max)
|
||||||
emit q->maxChanged(QDateTime::fromMSecsSinceEpoch(max, QTimeZone::UTC));
|
emit q->maxChanged(QDateTime::fromMSecsSinceEpoch(max, QTimeZone::UTC));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changed)
|
if (changed) {
|
||||||
|
calculateVisualRange();
|
||||||
emit q->rangeChanged(min, max);
|
emit q->rangeChanged(min, max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QDateTimeAxisPrivate::calculateVisualRange()
|
||||||
|
{
|
||||||
|
Q_Q(QDateTimeAxis);
|
||||||
|
qreal diff = m_max - m_min;
|
||||||
|
qreal center = diff / 2.0f + m_min + m_pan;
|
||||||
|
diff /= m_zoom;
|
||||||
|
qreal max = center + diff / 2.0f;
|
||||||
|
qreal min = center - diff / 2.0f;
|
||||||
|
|
||||||
|
if (!qFuzzyCompare(m_visualMin, min)) {
|
||||||
|
m_visualMin = min;
|
||||||
|
emit q->visualMinChanged(min);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!qFuzzyCompare(m_visualMax, max)) {
|
||||||
|
m_visualMax = max;
|
||||||
|
emit q->visualMaxChanged(max);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ class Q_GRAPHS_EXPORT QDateTimeAxis : public QAbstractAxis
|
||||||
QString timeZone READ timeZone WRITE setTimeZone NOTIFY timeZoneChanged REVISION(6, 11))
|
QString timeZone READ timeZone WRITE setTimeZone NOTIFY timeZoneChanged REVISION(6, 11))
|
||||||
Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged REVISION(6, 11))
|
Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged REVISION(6, 11))
|
||||||
Q_PROPERTY(qreal pan READ pan WRITE setPan NOTIFY panChanged REVISION(6, 11))
|
Q_PROPERTY(qreal pan READ pan WRITE setPan NOTIFY panChanged REVISION(6, 11))
|
||||||
|
Q_PROPERTY(QDateTime visualMin READ visualMin NOTIFY visualMinChanged REVISION(6, 11))
|
||||||
|
Q_PROPERTY(QDateTime visualMax READ visualMax NOTIFY visualMaxChanged REVISION(6, 11))
|
||||||
|
|
||||||
QML_NAMED_ELEMENT(DateTimeAxis)
|
QML_NAMED_ELEMENT(DateTimeAxis)
|
||||||
|
|
||||||
|
|
@ -62,6 +64,9 @@ public:
|
||||||
qreal pan() const;
|
qreal pan() const;
|
||||||
void setPan(qreal pan);
|
void setPan(qreal pan);
|
||||||
|
|
||||||
|
QDateTime visualMin() const;
|
||||||
|
QDateTime visualMax() const;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void minChanged(const QDateTime &min);
|
void minChanged(const QDateTime &min);
|
||||||
void maxChanged(const QDateTime &max);
|
void maxChanged(const QDateTime &max);
|
||||||
|
|
@ -71,6 +76,8 @@ Q_SIGNALS:
|
||||||
Q_REVISION(6, 11) void timeZoneChanged(const QString &timeZone);
|
Q_REVISION(6, 11) void timeZoneChanged(const QString &timeZone);
|
||||||
Q_REVISION(6, 11) void zoomChanged(qreal zoom);
|
Q_REVISION(6, 11) void zoomChanged(qreal zoom);
|
||||||
Q_REVISION(6, 11) void panChanged(qreal pan);
|
Q_REVISION(6, 11) void panChanged(qreal pan);
|
||||||
|
Q_REVISION(6, 11) void visualMinChanged(qreal min);
|
||||||
|
Q_REVISION(6, 11) void visualMaxChanged(qreal max);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Q_DECLARE_PRIVATE(QDateTimeAxis)
|
Q_DECLARE_PRIVATE(QDateTimeAxis)
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ protected:
|
||||||
QString m_format = QStringLiteral("dd-MMMM-yy");
|
QString m_format = QStringLiteral("dd-MMMM-yy");
|
||||||
qreal m_zoom = 1.0;
|
qreal m_zoom = 1.0;
|
||||||
qreal m_pan = 0.0;
|
qreal m_pan = 0.0;
|
||||||
|
qreal m_visualMin;
|
||||||
|
qreal m_visualMax;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void setMin(const QVariant &min) override;
|
void setMin(const QVariant &min) override;
|
||||||
|
|
@ -41,6 +43,7 @@ public:
|
||||||
void setRange(qreal min, qreal max) override;
|
void setRange(qreal min, qreal max) override;
|
||||||
qreal min() override { return m_min; }
|
qreal min() override { return m_min; }
|
||||||
qreal max() override { return m_max; }
|
qreal max() override { return m_max; }
|
||||||
|
void calculateVisualRange();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Q_DECLARE_PUBLIC(QDateTimeAxis)
|
Q_DECLARE_PUBLIC(QDateTimeAxis)
|
||||||
|
|
|
||||||
|
|
@ -366,6 +366,7 @@ void QValueAxis::setZoom(qreal zoom)
|
||||||
Q_D(QValueAxis);
|
Q_D(QValueAxis);
|
||||||
if (d->m_zoom != zoom) {
|
if (d->m_zoom != zoom) {
|
||||||
d->m_zoom = zoom;
|
d->m_zoom = zoom;
|
||||||
|
d->calculateVisualRange();
|
||||||
emit update();
|
emit update();
|
||||||
emit zoomChanged(zoom);
|
emit zoomChanged(zoom);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -385,6 +386,7 @@ void QValueAxis::setPan(qreal pan)
|
||||||
Q_D(QValueAxis);
|
Q_D(QValueAxis);
|
||||||
if (d->m_pan != pan) {
|
if (d->m_pan != pan) {
|
||||||
d->m_pan = pan;
|
d->m_pan = pan;
|
||||||
|
d->calculateVisualRange();
|
||||||
emit update();
|
emit update();
|
||||||
emit panChanged(pan);
|
emit panChanged(pan);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -399,6 +401,18 @@ qreal QValueAxis::pan() const
|
||||||
return d->m_pan;
|
return d->m_pan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qreal QValueAxis::visualMin() const
|
||||||
|
{
|
||||||
|
Q_D(const QValueAxis);
|
||||||
|
return d->m_visualMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal QValueAxis::visualMax() const
|
||||||
|
{
|
||||||
|
Q_D(const QValueAxis);
|
||||||
|
return d->m_visualMax;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Returns the type of the axis.
|
Returns the type of the axis.
|
||||||
*/
|
*/
|
||||||
|
|
@ -477,8 +491,28 @@ void QValueAxisPrivate::setRange(qreal min, qreal max)
|
||||||
emit q->maxChanged(max);
|
emit q->maxChanged(max);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changed)
|
if (changed) {
|
||||||
|
calculateVisualRange();
|
||||||
emit q->rangeChanged(min, max);
|
emit q->rangeChanged(min, max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QValueAxisPrivate::calculateVisualRange()
|
||||||
|
{
|
||||||
|
Q_Q(QValueAxis);
|
||||||
|
qreal diff = m_max - m_min;
|
||||||
|
qreal center = diff / 2.0f + m_min + m_pan;
|
||||||
|
diff /= m_zoom;
|
||||||
|
qreal min = center - diff / 2.0f;
|
||||||
|
qreal max = center + diff / 2.0f;
|
||||||
|
if (!qFuzzyCompare(m_visualMin, min)) {
|
||||||
|
m_visualMin = min;
|
||||||
|
emit q->visualMinChanged(min);
|
||||||
|
}
|
||||||
|
if (!qFuzzyCompare(m_visualMax, max)) {
|
||||||
|
m_visualMax = max;
|
||||||
|
emit q->visualMaxChanged(max);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ class Q_GRAPHS_EXPORT QValueAxis : public QAbstractAxis
|
||||||
qreal tickInterval READ tickInterval WRITE setTickInterval NOTIFY tickIntervalChanged FINAL)
|
qreal tickInterval READ tickInterval WRITE setTickInterval NOTIFY tickIntervalChanged FINAL)
|
||||||
Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged REVISION(6, 9))
|
Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged REVISION(6, 9))
|
||||||
Q_PROPERTY(qreal pan READ pan WRITE setPan NOTIFY panChanged REVISION(6, 9))
|
Q_PROPERTY(qreal pan READ pan WRITE setPan NOTIFY panChanged REVISION(6, 9))
|
||||||
|
Q_PROPERTY(qreal visualMin READ visualMin NOTIFY visualMinChanged REVISION(6, 11))
|
||||||
|
Q_PROPERTY(qreal visualMax READ visualMax NOTIFY visualMaxChanged REVISION(6, 11))
|
||||||
QML_NAMED_ELEMENT(ValueAxis)
|
QML_NAMED_ELEMENT(ValueAxis)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
@ -67,6 +69,9 @@ public:
|
||||||
void setPan(qreal pan);
|
void setPan(qreal pan);
|
||||||
qreal pan() const;
|
qreal pan() const;
|
||||||
|
|
||||||
|
qreal visualMin() const;
|
||||||
|
qreal visualMax() const;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void minChanged(qreal min);
|
void minChanged(qreal min);
|
||||||
void maxChanged(qreal max);
|
void maxChanged(qreal max);
|
||||||
|
|
@ -78,6 +83,8 @@ Q_SIGNALS:
|
||||||
void tickIntervalChanged(qreal tickInterval);
|
void tickIntervalChanged(qreal tickInterval);
|
||||||
Q_REVISION(6, 9) void zoomChanged(qreal zoom);
|
Q_REVISION(6, 9) void zoomChanged(qreal zoom);
|
||||||
Q_REVISION(6, 9) void panChanged(qreal pan);
|
Q_REVISION(6, 9) void panChanged(qreal pan);
|
||||||
|
Q_REVISION(6, 11) void visualMinChanged(qreal visualMin);
|
||||||
|
Q_REVISION(6, 11) void visualMaxChanged(qreal visualMax);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Q_DECLARE_PRIVATE(QValueAxis)
|
Q_DECLARE_PRIVATE(QValueAxis)
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ public:
|
||||||
qreal min() override { return m_min; }
|
qreal min() override { return m_min; }
|
||||||
qreal max() override { return m_max; }
|
qreal max() override { return m_max; }
|
||||||
void setRange(qreal min,qreal max) override;
|
void setRange(qreal min,qreal max) override;
|
||||||
|
void calculateVisualRange();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void setMin(const QVariant &min) override;
|
void setMin(const QVariant &min) override;
|
||||||
|
|
@ -44,6 +45,8 @@ private:
|
||||||
qreal m_tickInterval;
|
qreal m_tickInterval;
|
||||||
qreal m_zoom = 1.0;
|
qreal m_zoom = 1.0;
|
||||||
qreal m_pan = 0.0;
|
qreal m_pan = 0.0;
|
||||||
|
qreal m_visualMin = m_min;
|
||||||
|
qreal m_visualMax = m_max;
|
||||||
Q_DECLARE_PUBLIC(QValueAxis)
|
Q_DECLARE_PUBLIC(QValueAxis)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,80 +12,109 @@ Rectangle {
|
||||||
width: 800
|
width: 800
|
||||||
height: 600
|
height: 600
|
||||||
color: "#202020"
|
color: "#202020"
|
||||||
|
ColumnLayout {
|
||||||
RowLayout {
|
id: infoLayout
|
||||||
id:bar
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
leftMargin: 25
|
||||||
|
}
|
||||||
height: 100
|
height: 100
|
||||||
|
spacing: 0
|
||||||
Text {
|
RowLayout {
|
||||||
Layout.leftMargin: 20
|
Layout.fillHeight: true
|
||||||
font.pixelSize: 24
|
Layout.fillWidth: true
|
||||||
color: "#ffffff"
|
Text {
|
||||||
text: "X:"
|
Layout.leftMargin: 20
|
||||||
}
|
font.pixelSize: 24
|
||||||
|
|
||||||
Slider {
|
|
||||||
id: sliderX
|
|
||||||
|
|
||||||
value: (new Date(1950,1,1)).getTime()
|
|
||||||
from: (new Date(1900,1,1)).getTime()
|
|
||||||
to: (new Date(2000,1,1)).getTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
font.pixelSize: 24
|
|
||||||
color: "#ffffff"
|
|
||||||
text: "Y:"
|
|
||||||
}
|
|
||||||
|
|
||||||
Slider {
|
|
||||||
id: sliderY
|
|
||||||
|
|
||||||
value: (new Date(1950,1,1)).getTime()
|
|
||||||
from: (new Date(1900,1,1)).getTime()
|
|
||||||
to: (new Date(2000,1,1)).getTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
Layout.leftMargin: 20
|
|
||||||
font.pixelSize: 24
|
|
||||||
color: "#ffffff"
|
|
||||||
text: "X Ticks:"
|
|
||||||
}
|
|
||||||
|
|
||||||
SpinBox {
|
|
||||||
onValueChanged: xAxis.tickInterval = value
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
Layout.leftMargin: 20
|
|
||||||
font.pixelSize: 24
|
|
||||||
color: "#ffffff"
|
|
||||||
text: "X Format:"
|
|
||||||
}
|
|
||||||
TextField {
|
|
||||||
placeholderText: "MMMM-yyyy"
|
|
||||||
onAccepted: xAxis.labelFormat = text
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckBox {
|
|
||||||
id: zoomAreaEnabled
|
|
||||||
text: "Zoom area enabled"
|
|
||||||
contentItem: Text {
|
|
||||||
text: zoomAreaEnabled.text
|
|
||||||
font: zoomAreaEnabled.font
|
|
||||||
color: "#ffffff"
|
color: "#ffffff"
|
||||||
verticalAlignment: Text.AlignVCenter
|
text: "X:"
|
||||||
leftPadding: zoomAreaEnabled.indicator.width + zoomAreaEnabled.spacing
|
}
|
||||||
|
Slider {
|
||||||
|
id: sliderX
|
||||||
|
value: (new Date(1950,1,1)).getTime()
|
||||||
|
from: (new Date(1900,1,1)).getTime()
|
||||||
|
to: (new Date(2000,1,1)).getTime()
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
font.pixelSize: 24
|
||||||
|
color: "#ffffff"
|
||||||
|
text: "Y:"
|
||||||
|
}
|
||||||
|
Slider {
|
||||||
|
id: sliderY
|
||||||
|
value: (new Date(1950,1,1)).getTime()
|
||||||
|
from: (new Date(1900,1,1)).getTime()
|
||||||
|
to: (new Date(2000,1,1)).getTime()
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
Layout.leftMargin: 20
|
||||||
|
font.pixelSize: 24
|
||||||
|
color: "#ffffff"
|
||||||
|
text: "X Ticks:"
|
||||||
|
}
|
||||||
|
SpinBox {
|
||||||
|
onValueChanged: xAxis.tickInterval = value
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
Layout.leftMargin: 20
|
||||||
|
font.pixelSize: 24
|
||||||
|
color: "#ffffff"
|
||||||
|
text: "X Format:"
|
||||||
|
}
|
||||||
|
TextField {
|
||||||
|
placeholderText: "MMMM-yyyy"
|
||||||
|
onAccepted: xAxis.labelFormat = text
|
||||||
|
}
|
||||||
|
CheckBox {
|
||||||
|
id: zoomAreaEnabled
|
||||||
|
text: "Zoom area enabled"
|
||||||
|
contentItem: Text {
|
||||||
|
text: zoomAreaEnabled.text
|
||||||
|
font: zoomAreaEnabled.font
|
||||||
|
color: "#ffffff"
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
leftPadding: zoomAreaEnabled.indicator.width + zoomAreaEnabled.spacing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
spacing: 50
|
||||||
|
Column {
|
||||||
|
Label {
|
||||||
|
id: visualXMin
|
||||||
|
text: "X Axis visualMin: " + xAxis.visualMin
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
id: visualXMax
|
||||||
|
text: "X Axis visualMax: " + xAxis.visualMax
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column {
|
||||||
|
Label {
|
||||||
|
id: visualYMin
|
||||||
|
text: "Y Axis visualMin: " + yAxis.visualMin
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
id: visualYMax
|
||||||
|
text: "Y Axis visualMax: " + yAxis.visualMax
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
GraphsView {
|
GraphsView {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.top: bar.bottom
|
anchors.top: infoLayout.bottom
|
||||||
anchors.margins: 10
|
anchors.margins: 10
|
||||||
zoomStyle: GraphsView.ZoomStyle.Center;
|
zoomStyle: GraphsView.ZoomStyle.Center;
|
||||||
panStyle: zoomAreaEnabled.checked ? GraphsView.PanStyle.None : GraphsView.PanStyle.Drag
|
panStyle: zoomAreaEnabled.checked ? GraphsView.PanStyle.None : GraphsView.PanStyle.Drag
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,22 @@ Rectangle {
|
||||||
toValue: 40
|
toValue: 40
|
||||||
onSliderValueChanged: yAxis.max = sliderValue;
|
onSliderValueChanged: yAxis.max = sliderValue;
|
||||||
}
|
}
|
||||||
|
CustomLabel {
|
||||||
|
id: visualRangeMinXLabel
|
||||||
|
text: qsTr("X Axis visualMin: %1").arg(Number(xAxis.visualMin).toLocaleString())
|
||||||
|
}
|
||||||
|
CustomLabel {
|
||||||
|
id: visualRangeMaxXLabel
|
||||||
|
text: qsTr("X Axis visualMax: %1").arg(Number(xAxis.visualMax).toLocaleString())
|
||||||
|
}
|
||||||
|
CustomLabel {
|
||||||
|
id: visualRangeMinYLabel
|
||||||
|
text: qsTr("Y Axis visualMin: %1").arg(Number(yAxis.visualMin).toLocaleString())
|
||||||
|
}
|
||||||
|
CustomLabel {
|
||||||
|
id: visualRangeMaxYLabel
|
||||||
|
text: qsTr("Y Axis visualMax: %1").arg(Number(yAxis.visualMax).toLocaleString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphsView {
|
GraphsView {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue