diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui
index 21968010230..97c9ebe3f9d 100644
--- a/src/qt/forms/debugwindow.ui
+++ b/src/qt/forms/debugwindow.ui
@@ -630,20 +630,26 @@
-
- 1
+ 0
- 288
+ 2400
- 12
+ 200
- 6
+ 0
Qt::Horizontal
+
+ QSlider::TicksBelow
+
+
+ 200
+
-
@@ -659,16 +665,6 @@
- -
-
-
- &Reset
-
-
- false
-
-
-
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp
index 9565fa508f1..783c45f3577 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -799,14 +799,56 @@ QString formatBytes(uint64_t bytes)
{
if (bytes < 1'000)
return QObject::tr("%1 B").arg(bytes);
+ if (bytes < 10'000)
+ return QObject::tr("%1 kB").arg(0.01 * (bytes / 10));
+ if (bytes < 100'000)
+ return QObject::tr("%1 kB").arg(0.1 * (bytes / 100));
if (bytes < 1'000'000)
return QObject::tr("%1 kB").arg(bytes / 1'000);
+ if (bytes < 10'000'000)
+ return QObject::tr("%1 MB").arg(0.01 * (bytes / 10'000));
+ if (bytes < 100'000'000)
+ return QObject::tr("%1 MB").arg(0.1 * (bytes / 100'000));
if (bytes < 1'000'000'000)
return QObject::tr("%1 MB").arg(bytes / 1'000'000);
return QObject::tr("%1 GB").arg(bytes / 1'000'000'000);
}
+QString formatBytesps(float val)
+{
+ if (val < 10)
+ //: "Bytes per second"
+ return QObject::tr("%1 B/s").arg(0.01 * int(val * 100 + 0.5));
+ if (val < 100)
+ //: "Bytes per second"
+ return QObject::tr("%1 B/s").arg(0.1 * int(val * 10 + 0.5));
+ if (val < 1'000)
+ //: "Bytes per second"
+ return QObject::tr("%1 B/s").arg(int(val + 0.5));
+ if (val < 10'000)
+ //: "Kilobytes per second"
+ return QObject::tr("%1 kB/s").arg(0.01 * int(val / 10 + 0.5));
+ if (val < 100'000)
+ //: "Kilobytes per second"
+ return QObject::tr("%1 kB/s").arg(0.1 * int(val / 100 + 0.5));
+ if (val < 1'000'000)
+ //: "Kilobytes per second"
+ return QObject::tr("%1 kB/s").arg(int(val / 1'000 + 0.5));
+ if (val < 10'000'000)
+ //: "Megabytes per second"
+ return QObject::tr("%1 MB/s").arg(0.01 * int(val / 10'000 + 0.5));
+ if (val < 100'000'000)
+ //: "Megabytes per second"
+ return QObject::tr("%1 MB/s").arg(0.1 * int(val / 100'000 + 0.5));
+ if (val < 10'000'000'000)
+ //: "Megabytes per second"
+ return QObject::tr("%1 MB/s").arg(long(val / 1'000'000 + 0.5));
+
+ //: "Gigabytes per second"
+ return QObject::tr("%1 GB/s").arg(long(val / 1'000'000'000 + 0.5));
+}
+
qreal calculateIdealFontSize(int width, const QString& text, QFont font, qreal minPointSize, qreal font_size) {
while(font_size >= minPointSize) {
font.setPointSizeF(font_size);
diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h
index 0224b18b4ee..3e015f8085c 100644
--- a/src/qt/guiutil.h
+++ b/src/qt/guiutil.h
@@ -235,6 +235,7 @@ namespace GUIUtil
QString formatNiceTimeOffset(qint64 secs);
QString formatBytes(uint64_t bytes);
+ QString formatBytesps(float bytes);
qreal calculateIdealFontSize(int width, const QString& text, QFont font, qreal minPointSize = 4, qreal startPointSize = 14);
diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp
index c5e5e69df6a..b8b0c688128 100644
--- a/src/qt/rpcconsole.cpp
+++ b/src/qt/rpcconsole.cpp
@@ -57,7 +57,6 @@
#include
const int CONSOLE_HISTORY = 50;
-const int INITIAL_TRAFFIC_GRAPH_MINS = 30;
const QSize FONT_RANGE(4, 40);
const char fontSizeSettingsKey[] = "consoleFontSize";
@@ -560,7 +559,6 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty
connect(ui->clearButton, &QAbstractButton::clicked, [this] { clear(); });
connect(ui->fontBiggerButton, &QAbstractButton::clicked, this, &RPCConsole::fontBigger);
connect(ui->fontSmallerButton, &QAbstractButton::clicked, this, &RPCConsole::fontSmaller);
- connect(ui->btnClearTrafficGraph, &QPushButton::clicked, ui->trafficGraph, &TrafficGraphWidget::clear);
// disable the wallet selector by default
ui->WalletSelector->setVisible(false);
@@ -572,7 +570,7 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty
// based timer interface
m_node.rpcSetTimerInterfaceIfUnset(rpcTimerInterface);
- setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_MINS);
+ setTrafficGraphRange(1); // 1 is the lowest setting (0 bumps up)
updateDetailWidget();
consoleFontSize = settings.value(fontSizeSettingsKey, QFont().pointSize()).toInt();
@@ -1133,21 +1131,63 @@ void RPCConsole::scrollToEnd()
scrollbar->setValue(scrollbar->maximum());
}
-void RPCConsole::on_sldGraphRange_valueChanged(int value)
+void RPCConsole::on_sldGraphRange_valueChanged(int slider_value)
{
- const int multiplier = 5; // each position on the slider represents 5 min
- int mins = value * multiplier;
- setTrafficGraphRange(mins);
+ static int64_t last_click_time = 0;
+ static bool last_click_was_up = false;
+ unsigned int value = (slider_value + 100) / 200 + 1; // minimum of 1, 0 reserve for scale bump
+ if (!slider_in_use) {
+ // Avoid accidental boucing of direction
+ int64_t now = GetTimeMillis();
+ bool this_click_is_up = false;
+ bool bouncing = false;
+ if (slider_value > set_slider_value)
+ this_click_is_up = true;
+ if (now - last_click_time < 250 && this_click_is_up != last_click_was_up) {
+ bouncing = true;
+ ui->sldGraphRange->blockSignals(true);
+ ui->sldGraphRange->setValue(set_slider_value);
+ ui->sldGraphRange->blockSignals(false);
+ }
+ last_click_time = now;
+ last_click_was_up = this_click_is_up;
+ set_slider_value = slider_value;
+ if (bouncing) return;
+ }
+ set_slider_value = slider_value;
+ setTrafficGraphRange(value);
+}
+
+void RPCConsole::setTrafficGraphRange(unsigned int value)
+{
+ std::chrono::minutes mins = ui->trafficGraph->setGraphRange(value);
+ if (value)
+ set_slider_value = (value - 1) * 200;
+ else {
+ set_slider_value += 200;
+ ui->sldGraphRange->blockSignals(true);
+ ui->sldGraphRange->setValue(set_slider_value);
+ ui->sldGraphRange->blockSignals(false);
+ }
+ ui->lblGraphRange->setText(GUIUtil::formatDurationStr(mins));
}
-void RPCConsole::setTrafficGraphRange(int mins)
+void RPCConsole::on_sldGraphRange_sliderReleased()
{
- ui->trafficGraph->setGraphRange(std::chrono::minutes{mins});
- ui->lblGraphRange->setText(GUIUtil::formatDurationStr(std::chrono::minutes{mins}));
+ ui->sldGraphRange->setValue(set_slider_value); // Snap the slider to where this value is
+ slider_in_use = false;
+}
+
+void RPCConsole::on_sldGraphRange_sliderPressed()
+{
+ slider_in_use = true;
}
void RPCConsole::updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut)
{
+ if (!slider_in_use && ui->trafficGraph->GraphRangeBump()) {
+ setTrafficGraphRange(0); // bump it up
+ }
ui->lblBytesIn->setText(GUIUtil::formatBytes(totalBytesIn));
ui->lblBytesOut->setText(GUIUtil::formatBytes(totalBytesOut));
}
diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h
index 528e2bef7d5..ffba9490165 100644
--- a/src/qt/rpcconsole.h
+++ b/src/qt/rpcconsole.h
@@ -84,6 +84,8 @@ private Q_SLOTS:
void on_openDebugLogfileButton_clicked();
/** change the time range of the network traffic graph */
void on_sldGraphRange_valueChanged(int value);
+ void on_sldGraphRange_sliderReleased();
+ void on_sldGraphRange_sliderPressed();
/** update traffic statistics */
void updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut);
void resizeEvent(QResizeEvent *event) override;
@@ -140,7 +142,7 @@ public Q_SLOTS:
} const ts;
void startExecutor();
- void setTrafficGraphRange(int mins);
+ void setTrafficGraphRange(unsigned int value);
enum ColumnWidths
{
@@ -170,6 +172,8 @@ public Q_SLOTS:
bool m_is_executing{false};
QByteArray m_peer_widget_header_state;
QByteArray m_banlist_widget_header_state;
+ bool slider_in_use{false};
+ int set_slider_value{0};
/** Update UI with latest network info from model. */
void updateNetworkState();
diff --git a/src/qt/trafficgraphwidget.cpp b/src/qt/trafficgraphwidget.cpp
index aebd44d5f7c..0af4a2a92d4 100644
--- a/src/qt/trafficgraphwidget.cpp
+++ b/src/qt/trafficgraphwidget.cpp
@@ -5,11 +5,14 @@
#include
#include
#include
+#include
#include
#include
#include
#include
+#include
+#include
#include
#include
@@ -22,44 +25,107 @@
TrafficGraphWidget::TrafficGraphWidget(QWidget *parent) :
QWidget(parent),
timer(nullptr),
- fMax(0.0f),
vSamplesIn(),
vSamplesOut(),
- nLastBytesIn(0),
- nLastBytesOut(0),
+ vTimeStamp(),
+ nLastBytesIn(),
+ nLastBytesOut(),
+ nLastTime(),
clientModel(nullptr)
{
timer = new QTimer(this);
- connect(timer, &QTimer::timeout, this, &TrafficGraphWidget::updateRates);
+ connect(timer, &QTimer::timeout, this, &TrafficGraphWidget::updateStuff);
+ timer->setInterval(75);
+ timer->start();
+ setMouseTracking(true);
}
void TrafficGraphWidget::setClientModel(ClientModel *model)
{
clientModel = model;
+ int64_t nTime = GetTimeMillis();
if(model) {
- nLastBytesIn = model->node().getTotalBytesRecv();
- nLastBytesOut = model->node().getTotalBytesSent();
+ for (int i = 0; i < VALUES_SIZE; i++) {
+ nLastBytesIn[i] = model->node().getTotalBytesRecv();
+ nLastBytesOut[i] = model->node().getTotalBytesSent();
+ nLastTime[i] = std::chrono::milliseconds{nTime};
+ vSamplesIn[i].push_front(nLastBytesIn[i]);
+ vSamplesOut[i].push_front(nLastBytesOut[i]);
+ vTimeStamp[i].push_front(nLastTime[i]);
+ }
}
}
-std::chrono::minutes TrafficGraphWidget::getGraphRange() const { return m_range; }
+bool TrafficGraphWidget::GraphRangeBump() const { return m_bump_value; }
+
+int TrafficGraphWidget::y_value(float value)
+{
+ int h = height() - YMARGIN * 2;
+ return YMARGIN + h - (h * 1.0 * (fToggle ? (pow(value, 0.30102) / pow(fMax, 0.30102)) : (value / fMax)));
+}
void TrafficGraphWidget::paintPath(QPainterPath &path, QQueue &samples)
{
- int sampleCount = samples.size();
+ int sampleCount = std::min(int(DESIRED_SAMPLES * m_range / values[m_value]), int(samples.size()));
if(sampleCount > 0) {
int h = height() - YMARGIN * 2, w = width() - XMARGIN * 2;
int x = XMARGIN + w;
path.moveTo(x, YMARGIN + h);
for(int i = 0; i < sampleCount; ++i) {
- x = XMARGIN + w - w * i / DESIRED_SAMPLES;
- int y = YMARGIN + h - (int)(h * samples.at(i) / fMax);
+ x = XMARGIN + w - w * i * values[m_value] / m_range / DESIRED_SAMPLES;
+ int y = y_value(samples.at(i));
path.lineTo(x, y);
}
path.lineTo(x, YMARGIN + h);
}
}
+float floatmax(float a, float b)
+{
+ if (a > b) return a;
+ else return b;
+}
+
+void TrafficGraphWidget::mouseMoveEvent(QMouseEvent *event)
+{
+ QWidget::mouseMoveEvent(event);
+ if (fMax <= 0.0f) return;
+ static int last_x = -1;
+ static int last_y = -1;
+ int x = event->x();
+ int y = event->y();
+ x_offset = event->globalX() - x;
+ y_offset = event->globalY() - y;
+ if (last_x == x && last_y == y) return; // Do nothing if mouse hasn't moved
+ int h = height() - YMARGIN * 2, w = width() - XMARGIN * 2;
+ int i = (w + XMARGIN - x) * DESIRED_SAMPLES / w;
+ int sampleSize = vTimeStamp[m_value].size();
+ unsigned int smallest_distance = 50; int closest_i = (i >= 0 && i < sampleSize) ? i : -1;
+ if (sampleSize && i >= -10 && i < sampleSize + 2 && y <= h + YMARGIN + 3) {
+ for (int test_i = std::max(i - 2, 0); test_i < std::min(i + 10, sampleSize); test_i++) {
+ float val = floatmax(vSamplesIn[m_value].at(test_i), vSamplesOut[m_value].at(test_i));
+ int y_data = y_value(val);
+ unsigned int distance = abs(y - y_data);
+ if (distance < smallest_distance) {
+ smallest_distance = distance;
+ closest_i = test_i;
+ }
+ }
+ }
+ if (ttpoint != closest_i) {
+ ttpoint = closest_i;
+ update(); // Calls paintEvent() to draw or delete the highlighted point
+ }
+ last_x = x; last_y = y;
+}
+
+void TrafficGraphWidget::mousePressEvent(QMouseEvent *event)
+{
+ QWidget::mousePressEvent(event);
+ fToggle = !fToggle;
+ update();
+}
+
void TrafficGraphWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
@@ -76,101 +142,227 @@ void TrafficGraphWidget::paintEvent(QPaintEvent *)
int base = floor(log10(fMax));
float val = pow(10.0f, base);
- const QString units = tr("kB/s");
const float yMarginText = 2.0;
- // draw lines
- painter.setPen(axisCol);
- painter.drawText(XMARGIN, YMARGIN + h - h * val / fMax-yMarginText, QString("%1 %2").arg(val).arg(units));
- for(float y = val; y < fMax; y += val) {
- int yy = YMARGIN + h - h * y / fMax;
- painter.drawLine(XMARGIN, yy, width() - XMARGIN, yy);
- }
- // if we drew 3 or fewer lines, break them up at the next lower order of magnitude
- if(fMax / val <= 3.0f) {
- axisCol = axisCol.darker();
+ // if we drew 10 or 3 fewer lines, break them up at the next lower order of magnitude
+ if(fMax / val <= (fToggle ? 10.0f : 3.0f)) {
+ float oldval = val;
val = pow(10.0f, base - 1);
- painter.setPen(axisCol);
- painter.drawText(XMARGIN, YMARGIN + h - h * val / fMax-yMarginText, QString("%1 %2").arg(val).arg(units));
+ painter.setPen(axisCol.darker());
+ painter.drawText(XMARGIN, y_value(val)-yMarginText, GUIUtil::formatBytesps(val*1000));
int count = 1;
- for(float y = val; y < fMax; y += val, count++) {
- // don't overwrite lines drawn above
+ for(float y = val; y < (!fToggle || fMax / val < 20 ? fMax : oldval); y += val, count++) {
if(count % 10 == 0)
continue;
- int yy = YMARGIN + h - h * y / fMax;
+ int yy = y_value(y);
painter.drawLine(XMARGIN, yy, width() - XMARGIN, yy);
}
+ if (fToggle) {
+ int yy = y_value(val*0.1);
+ painter.setPen(axisCol.darker().darker());
+ painter.drawText(XMARGIN, yy-yMarginText, GUIUtil::formatBytesps(val*100));
+ painter.drawLine(XMARGIN, yy, width() - XMARGIN, yy);
+ }
+ val = oldval;
+ }
+ // draw lines
+ painter.setPen(axisCol);
+ for(float y = val; y < fMax; y += val) {
+ int yy = y_value(y);
+ painter.drawLine(XMARGIN, yy, width() - XMARGIN, yy);
}
+ painter.drawText(XMARGIN, y_value(val)-yMarginText, GUIUtil::formatBytesps(val*1000));
painter.setRenderHint(QPainter::Antialiasing);
- if(!vSamplesIn.empty()) {
+ if(!vSamplesIn[m_value].empty()) {
QPainterPath p;
- paintPath(p, vSamplesIn);
+ paintPath(p, vSamplesIn[m_value]);
painter.fillPath(p, QColor(0, 255, 0, 128));
painter.setPen(Qt::green);
painter.drawPath(p);
}
- if(!vSamplesOut.empty()) {
+ if(!vSamplesOut[m_value].empty()) {
QPainterPath p;
- paintPath(p, vSamplesOut);
+ paintPath(p, vSamplesOut[m_value]);
painter.fillPath(p, QColor(255, 0, 0, 128));
painter.setPen(Qt::red);
painter.drawPath(p);
}
+ int sampleCount = vTimeStamp[m_value].size();
+ if (ttpoint >= 0 && ttpoint < sampleCount) {
+ painter.setPen(Qt::yellow);
+ int w = width() - XMARGIN * 2;
+ int x = XMARGIN + w - w * ttpoint / DESIRED_SAMPLES;
+ int y = y_value(floatmax(vSamplesIn[m_value].at(ttpoint), vSamplesOut[m_value].at(ttpoint)));
+ painter.drawEllipse(QPointF(x, y), 3, 3);
+ QString strTime;
+ std::chrono::milliseconds sampleTime{0};
+ if (ttpoint + 1 < sampleCount)
+ sampleTime = vTimeStamp[m_value].at(ttpoint+1);
+ else
+ sampleTime = vTimeStamp[m_value].at(ttpoint);
+ int age = GetTime() - sampleTime.count() / 1000;
+ if (age < 60*60*23)
+ strTime += QString::fromStdString(FormatISO8601Time(sampleTime.count() / 1000));
+ else
+ strTime += QString::fromStdString(FormatISO8601DateTime(sampleTime.count() / 1000));
+ int nDuration = (vTimeStamp[m_value].at(ttpoint) - sampleTime).count();
+ if (nDuration > 0) {
+ if (nDuration > 9999)
+ strTime += " +" + GUIUtil::formatDurationStr(std::chrono::seconds{nDuration/1000});
+ else
+ strTime += " +" + GUIUtil::formatPingTime(std::chrono::microseconds{nDuration*1000});
+ }
+ QString strData = tr("In") + " " + GUIUtil::formatBytesps(vSamplesIn[m_value].at(ttpoint)*1000) + "\n" + tr("Out") + " " + GUIUtil::formatBytesps(vSamplesOut[m_value].at(ttpoint)*1000);
+ // Line below allows ToolTip to move faster than the default ToolTip timeout (10 seconds).
+ QToolTip::showText(QPoint(x + x_offset, y + y_offset), strTime + "\n. " + strData);
+ QToolTip::showText(QPoint(x + x_offset, y + y_offset), strTime + "\n " + strData);
+ tt_time = GetTime();
+ } else
+ QToolTip::hideText();
}
-void TrafficGraphWidget::updateRates()
+void TrafficGraphWidget::update_fMax()
{
- if(!clientModel) return;
-
- quint64 bytesIn = clientModel->node().getTotalBytesRecv(),
- bytesOut = clientModel->node().getTotalBytesSent();
- float in_rate_kilobytes_per_sec = static_cast(bytesIn - nLastBytesIn) / timer->interval();
- float out_rate_kilobytes_per_sec = static_cast(bytesOut - nLastBytesOut) / timer->interval();
- vSamplesIn.push_front(in_rate_kilobytes_per_sec);
- vSamplesOut.push_front(out_rate_kilobytes_per_sec);
- nLastBytesIn = bytesIn;
- nLastBytesOut = bytesOut;
-
- while(vSamplesIn.size() > DESIRED_SAMPLES) {
- vSamplesIn.pop_back();
- }
- while(vSamplesOut.size() > DESIRED_SAMPLES) {
- vSamplesOut.pop_back();
- }
-
float tmax = 0.0f;
- for (const float f : vSamplesIn) {
+ for (const float f : vSamplesIn[m_new_value]) {
if(f > tmax) tmax = f;
}
- for (const float f : vSamplesOut) {
+ for (const float f : vSamplesOut[m_new_value]) {
if(f > tmax) tmax = f;
}
- fMax = tmax;
- update();
+ new_fMax = tmax;
}
-void TrafficGraphWidget::setGraphRange(std::chrono::minutes new_range)
+bool update_num(float new_val, float ¤t, float &increment, int length)
{
- m_range = new_range;
- const auto msecs_per_sample{std::chrono::duration_cast(m_range) / DESIRED_SAMPLES};
- timer->stop();
- timer->setInterval(msecs_per_sample);
+ if (new_val == 0 || current == new_val)
+ return false;
- clear();
+ if (abs(increment) <= abs(0.8 * current) / length) { // allow equal to as current and increment could be zero
+ if (new_val > current)
+ increment = 1.0 * (current+1) / length; // +1s are to get it started even if current is zero
+ else
+ increment = -1.0 * (current+1) / length;
+ if (abs(increment) > abs(new_val - current)) // Only check this when creating an increment
+ increment = 0; // Nothing to do!
+ } else {
+ if (((increment > 0) && (current + increment * 2 > new_val)) ||
+ ((increment < 0) && (current + increment * 2 < new_val))) {
+ increment = increment / 2; // Keep the momentum going even if new_val is elsewhere.
+ } else {
+ if (((increment > 0) && (current + increment * 4 < new_val)) ||
+ ((increment < 0) && (current + increment * 4 > new_val))) {
+ increment = increment * 2;
+ }
+ }
+ }
+ if (abs(increment) < 0.8 * current / length) {
+ if ((increment >= 0 && new_val > current) || (increment <= 0 && new_val < current))
+ current = new_val;
+ increment = 0;
+ } else
+ current += increment;
+
+ return true;
}
-void TrafficGraphWidget::clear()
+void TrafficGraphWidget::updateStuff()
{
- timer->stop();
+ if(!clientModel) return;
- vSamplesOut.clear();
- vSamplesIn.clear();
- fMax = 0.0f;
+ static int nInterval{timer->interval()};
+ int64_t nTime{GetTimeMillis()};
- if(clientModel) {
- nLastBytesIn = clientModel->node().getTotalBytesRecv();
- nLastBytesOut = clientModel->node().getTotalBytesSent();
+ bool fUpdate = false;
+ for (int i = 0; i < VALUES_SIZE; i++) {
+ int64_t msecs_per_sample = int64_t(values[i]) * int64_t(60000) / DESIRED_SAMPLES;
+ if (nTime > (nLastTime[i].count() + msecs_per_sample - nInterval/2)) { // REBTODO - fix bad timing
+ updateRates(i);
+ if (i == m_value) {
+ if (ttpoint >= 0 && ttpoint < DESIRED_SAMPLES) {
+ ttpoint++; // Move the selected point to the left
+ if (ttpoint >= DESIRED_SAMPLES) ttpoint = -1;
+ }
+ fUpdate = true;
+ }
+ if (i == m_new_value)
+ update_fMax();
+ }
}
- timer->start();
+
+ static float y_increment = 0;
+ static float x_increment = 0;
+ if (update_num(new_fMax, fMax, y_increment, height() - YMARGIN * 2))
+ fUpdate = true;
+ if (update_num(values[m_new_value], m_range, x_increment, width() - XMARGIN * 2)) {
+ if (values[m_new_value] > m_range && values[m_value] < m_range * 0.99) {
+ m_value++;
+ } else if (m_value > 0 && values[m_new_value] <= m_range && values[m_value-1] > m_range * 0.99)
+ m_value--;
+ fUpdate = true;
+ } else if (m_value != m_new_value) {
+ fUpdate = true;
+ m_value = m_new_value;
+ }
+
+ static bool last_fToggle = fToggle;
+ if (!QToolTip::isVisible()) {
+ if (ttpoint >= 0) { // Remove the yellow circle if the ToolTip has gone due to mouse moving elsewhere.
+ if (last_fToggle == fToggle) { // Not lost due to a toggle
+ ttpoint = -1;
+ } else
+ last_fToggle = fToggle;
+ fUpdate = true;
+ }
+ } else if (ttpoint >= 0 && GetTime() >= tt_time + 9) // ToolTip is about to expire so refresh it.
+ fUpdate = true; // TODO - technically it's only the ToolTip that needs to be refreshed
+
+ if (fUpdate)
+ update();
+}
+
+void TrafficGraphWidget::updateRates(int i)
+{
+ std::chrono::milliseconds nTime{GetTimeMillis()};
+ quint64 bytesIn = clientModel->node().getTotalBytesRecv(),
+ bytesOut = clientModel->node().getTotalBytesSent();
+ int nRealInterval = (nTime - nLastTime[i]).count();
+ float in_rate_kilobytes_per_sec = static_cast(bytesIn - nLastBytesIn[i]) / nRealInterval;
+ float out_rate_kilobytes_per_sec = static_cast(bytesOut - nLastBytesOut[i]) / nRealInterval;
+ vSamplesIn[i].push_front(in_rate_kilobytes_per_sec);
+ vSamplesOut[i].push_front(out_rate_kilobytes_per_sec);
+ vTimeStamp[i].push_front(nTime);
+ nLastTime[i] = nTime;
+ nLastBytesIn[i] = bytesIn;
+ nLastBytesOut[i] = bytesOut;
+ static bool fFull[VALUES_SIZE];
+ while(vTimeStamp[i].size() > DESIRED_SAMPLES) {
+ if (ttpoint < 0 && m_value == i && i < VALUES_SIZE - 1 && !fFull[i])
+ m_bump_value = true;
+
+ fFull[i] = true;
+
+ vSamplesIn[i].pop_back();
+ vSamplesOut[i].pop_back();
+ vTimeStamp[i].pop_back();
+ }
+}
+
+std::chrono::minutes TrafficGraphWidget::setGraphRange(unsigned int value)
+{
+ // value is the array marker plus 1 (as zero is reserved for bumping up)
+ if (!value) { // bump
+ m_bump_value = false;
+ value = m_value + 1;
+ } else
+ value--; // get the array marker
+ int old_value = m_new_value;
+ m_new_value = std::min((int)value, VALUES_SIZE - 1);
+ if (m_new_value != old_value) {
+ update_fMax();
+ update();
+ }
+
+ return std::chrono::minutes{values[m_new_value]};
}
diff --git a/src/qt/trafficgraphwidget.h b/src/qt/trafficgraphwidget.h
index a40b7345403..b3ea9bc4d48 100644
--- a/src/qt/trafficgraphwidget.h
+++ b/src/qt/trafficgraphwidget.h
@@ -17,6 +17,8 @@ class QPaintEvent;
class QTimer;
QT_END_NAMESPACE
+#define VALUES_SIZE 12
+
class TrafficGraphWidget : public QWidget
{
Q_OBJECT
@@ -24,26 +26,43 @@ class TrafficGraphWidget : public QWidget
public:
explicit TrafficGraphWidget(QWidget *parent = nullptr);
void setClientModel(ClientModel *model);
- std::chrono::minutes getGraphRange() const;
+ bool GraphRangeBump() const;
protected:
void paintEvent(QPaintEvent *) override;
+ int y_value(float value);
+ void mouseMoveEvent(QMouseEvent *event) override;
+ int ttpoint = -1;
+ int x_offset = 0;
+ int y_offset = 0;
+ int64_t tt_time = 0;
+ void mousePressEvent(QMouseEvent *event) override;
+ bool fToggle = true;
public Q_SLOTS:
- void updateRates();
- void setGraphRange(std::chrono::minutes new_range);
- void clear();
+ void updateStuff();
+ std::chrono::minutes setGraphRange(unsigned int value);
private:
+ void update_fMax();
void paintPath(QPainterPath &path, QQueue &samples);
+ void updateRates(int value);
QTimer *timer;
- float fMax;
- std::chrono::minutes m_range{0};
- QQueue vSamplesIn;
- QQueue vSamplesOut;
- quint64 nLastBytesIn;
- quint64 nLastBytesOut;
+ float fMax{0};
+ float new_fMax{0};
+ float m_range{0};
+ int m_value{0};
+ int m_new_value{0};
+ bool m_bump_value{false};
+ QQueue vSamplesIn[VALUES_SIZE];
+ QQueue vSamplesOut[VALUES_SIZE];
+ QQueue vTimeStamp[VALUES_SIZE];
+ quint64 nLastBytesIn[VALUES_SIZE];
+ quint64 nLastBytesOut[VALUES_SIZE];
+ std::chrono::milliseconds nLastTime[VALUES_SIZE];
+ unsigned int values[VALUES_SIZE] = {5, 10, 20, 45, 90, 3*60, 6*60, 12*60, 24*60, 3*24*60, 7*24*60, 28*24*60};
+ //unsigned int values[VALUES_SIZE] = {5, 15, 60, 3*60, 12*60, 2*24*60, 7*24*60, 28*24*60};
ClientModel *clientModel;
};
diff --git a/src/util/time.cpp b/src/util/time.cpp
index f7712f0dc82..5b7aea848e4 100644
--- a/src/util/time.cpp
+++ b/src/util/time.cpp
@@ -155,6 +155,17 @@ std::string FormatISO8601Date(int64_t nTime) {
return strprintf("%04i-%02i-%02i", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday);
}
+std::string FormatISO8601Time(int64_t nTime) {
+ struct tm ts;
+ time_t time_val = nTime;
+#ifdef HAVE_GMTIME_R
+ gmtime_r(&time_val, &ts);
+#else
+ gmtime_s(&ts, &time_val);
+#endif
+ return strprintf("%02i:%02i:%02iZ", ts.tm_hour, ts.tm_min, ts.tm_sec);
+}
+
int64_t ParseISO8601DateTime(const std::string& str)
{
static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0);
diff --git a/src/util/time.h b/src/util/time.h
index 9d92b237250..be244138bb4 100644
--- a/src/util/time.h
+++ b/src/util/time.h
@@ -74,6 +74,7 @@ T GetTime();
*/
std::string FormatISO8601DateTime(int64_t nTime);
std::string FormatISO8601Date(int64_t nTime);
+std::string FormatISO8601Time(int64_t nTime);
int64_t ParseISO8601DateTime(const std::string& str);
/**