From e38dab01a0d65a8d460f1cb246ae134c934d3d93 Mon Sep 17 00:00:00 2001 From: parhelia512 <0011d3@gmail.com> Date: Tue, 22 Apr 2025 21:35:14 -0400 Subject: [PATCH] Replace QtCharts with SpeedWidget (#379) * Replace QtCharts with SpeedWidget * Replace QtCharts with SpeedWidget --- .../v2/ui/widgets/speedchart/SpeedWidget.cpp | 330 ++++++++++++++++++ .../v2/ui/widgets/speedchart/SpeedWidget.hpp | 83 +++++ CMakeLists.txt | 8 +- include/ui/mainwindow.h | 4 +- include/ui/utils/CustomChartView.h | 39 --- include/ui/utils/TrafficChart.h | 315 ----------------- res/translations/fa_IR.ts | 11 + res/translations/ru_RU.ts | 11 + res/translations/zh_CN.ts | 13 +- src/ui/mainwindow.cpp | 18 +- 10 files changed, 465 insertions(+), 367 deletions(-) create mode 100644 3rdparty/qv2ray/v2/ui/widgets/speedchart/SpeedWidget.cpp create mode 100644 3rdparty/qv2ray/v2/ui/widgets/speedchart/SpeedWidget.hpp delete mode 100644 include/ui/utils/CustomChartView.h delete mode 100644 include/ui/utils/TrafficChart.h diff --git a/3rdparty/qv2ray/v2/ui/widgets/speedchart/SpeedWidget.cpp b/3rdparty/qv2ray/v2/ui/widgets/speedchart/SpeedWidget.cpp new file mode 100644 index 0000000..21a4eca --- /dev/null +++ b/3rdparty/qv2ray/v2/ui/widgets/speedchart/SpeedWidget.cpp @@ -0,0 +1,330 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015 Anton Lashkov + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If + * you modify file(s), you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. + */ + +#include "SpeedWidget.hpp" + +#include +#include + +#define VIEWABLE 120 + +// table of supposed nice steps for grid marks to get nice looking quarters of scale +const static double roundingTable[] = { 1.2, 1.6, 2, 2.4, 2.8, 3.2, 4, 6, 8 }; + +// use binary prefix standards from IEC 60027-2 +// see http://en.wikipedia.org/wiki/Kilobyte +enum SizeUnit : int +{ + Byte, // 1000^0, + KByte, // 1000^1, + MByte, // 1000^2, + GByte, // 1000^3, + TByte, // 1000^4, + PByte, // 1000^5, + EByte // 1000^6 +}; + +struct SplittedValue +{ + double arg; + SizeUnit unit; + qint64 sizeInBytes() const + { + auto size = arg; + for (int i = 0; i < static_cast(unit); ++i) + { + size *= 1024; + } + return size; + } +}; + +SpeedWidget::SpeedWidget(QWidget *parent) : QGraphicsView(parent) +{ + UpdateSpeedPlotSettings(); +} + +void SpeedWidget::AddPointData(QMap data) +{ + SpeedWidget::PointData point; + point.x = QDateTime::currentMSecsSinceEpoch() / 1000; + for (const auto &[id, data] : data.toStdMap()) + { + if (m_properties.contains(id)) + point.y[id] = data; + } + + dataCollection.push_back(point); + + while (dataCollection.length() > VIEWABLE) + { + dataCollection.removeFirst(); + } + replot(); +} + +QString unitString(const SizeUnit unit, const bool isSpeed) +{ + const static QStringList units{ "B", "KB", "MB", "GB", "TB", "PB", "EB" }; + auto unitString = units[unit]; + if (isSpeed) + unitString += "/s"; + return unitString; +} + +int friendlyUnitPrecision(const SizeUnit unit) +{ + // friendlyUnit's number of digits after the decimal point + switch (unit) + { + case SizeUnit::Byte: return 0; + case SizeUnit::KByte: + case SizeUnit::MByte: return 1; + case SizeUnit::GByte: return 2; + default: return 3; + } +} + +SplittedValue getRoundedYScale(double value) +{ + if (value == 0.0) + return { 0, SizeUnit::Byte }; + + if (value <= 12.0) + return { 12, SizeUnit::Byte }; + + auto calculatedUnit = SizeUnit::Byte; + + while (value > 1000) + { + value /= 1000; + calculatedUnit = static_cast(static_cast(calculatedUnit) + 1); + } + + if (value > 100.0) + { + int roundedValue = static_cast(value / 40) * 40; + while (roundedValue < value) + roundedValue += 40; + return { static_cast(roundedValue), calculatedUnit }; + } + + if (value > 10.0) + { + int roundedValue = static_cast(value / 4) * 4; + while (roundedValue < value) + roundedValue += 4; + return { static_cast(roundedValue), calculatedUnit }; + } + + for (const auto &roundedValue : roundingTable) + { + if (value <= roundedValue) + return { roundedValue, calculatedUnit }; + } + + return { 10.0, calculatedUnit }; +} + +QString formatLabel(const double argValue, const SizeUnit unit) +{ + // check is there need for digits after decimal separator + const int precision = (argValue < 10) ? friendlyUnitPrecision(unit) : 0; + return QLocale::system().toString(argValue, 'f', precision) + " " + unitString(unit, true); +} + +struct QvGraphPenConfig +{ + int R = 150; + int G = 150; + int B = 150; + float width = 1.5f; + Qt::PenStyle style = Qt::SolidLine; +}; + +void SpeedWidget::UpdateSpeedPlotSettings() +{ + const static std::pair DefaultPen{ { 134, 196, 63, 1.5f, Qt::SolidLine }, { 50, 153, 255, 1.5f, Qt::SolidLine } }; + const static std::pair DirectPen{ { 0, 210, 240, 1.5f, Qt::DotLine }, { 235, 220, 42, 1.5f, Qt::DotLine } }; + + const auto getPen = [](const QvGraphPenConfig &conf) + { + QPen p{ { conf.R, conf.G, conf.B } }; + p.setStyle(conf.style); + p.setWidthF(conf.width); + return p; + }; + + m_properties.clear(); + + m_properties[OUTBOUND_PROXY_UP] = { tr("Proxy") + " ↑", getPen(DefaultPen.first) }; + m_properties[OUTBOUND_PROXY_DOWN] = { tr("Proxy") + " ↓", getPen(DefaultPen.second) }; + + m_properties[OUTBOUND_DIRECT_UP] = { tr("Direct") + " ↑", getPen(DirectPen.first) }; + m_properties[OUTBOUND_DIRECT_DOWN] = { tr("Direct") + " ↓", getPen(DirectPen.second) }; + + // m_properties[INBOUND_UP] = { tr("Total") + " ↑", getPen((*Graph->colorConfig)[API_INBOUND].value1) }; + // m_properties[INBOUND_DOWN] = { tr("Total") + " ↓", getPen((*Graph->colorConfig)[API_INBOUND].value2) }; +} + +void SpeedWidget::Clear() +{ + dataCollection.clear(); + m_properties.clear(); + UpdateSpeedPlotSettings(); + replot(); +} +void SpeedWidget::replot() +{ + viewport()->update(); +} + +quint64 SpeedWidget::maxYValue() +{ + quint64 maxYValue = 0; + + for (int id = 0; id < NB_GRAPHS; ++id) + for (int i = dataCollection.size() - 1, j = 0; (i >= 0) && (j <= VIEWABLE); --i, ++j) + if (dataCollection[i].y[id] > maxYValue) + maxYValue = dataCollection[i].y[id]; + + return maxYValue; +} + +void SpeedWidget::paintEvent(QPaintEvent *) +{ + const auto fullRect = viewport()->rect(); + auto rect = viewport()->rect(); + + // Add padding + rect.adjust(4, 4, 0, -4); + + const auto niceScale = getRoundedYScale(maxYValue()); + + // draw Y axis speed labels + const QVector speedLabels = { + formatLabel(niceScale.arg, niceScale.unit), + formatLabel((0.75 * niceScale.arg), niceScale.unit), + formatLabel((0.50 * niceScale.arg), niceScale.unit), + formatLabel((0.25 * niceScale.arg), niceScale.unit), + formatLabel(0.0, niceScale.unit), + }; + + QPainter painter(viewport()); + painter.setRenderHints(QPainter::Antialiasing); + + const auto fontMetrics = painter.fontMetrics(); + rect.adjust(0, fontMetrics.height(), 0, 0); // Add top padding for top speed text + + int yAxisWidth = 0; + for (const auto &label : speedLabels) + if (fontMetrics.horizontalAdvance(label) > yAxisWidth) + yAxisWidth = fontMetrics.horizontalAdvance(label); + + int i = 0; + for (const auto &label : speedLabels) + { + QRectF labelRect(rect.topLeft() + QPointF(-yAxisWidth, (i++) * 0.25 * rect.height() - fontMetrics.height()), QSizeF(2 * yAxisWidth, fontMetrics.height())); + painter.drawText(labelRect, label, Qt::AlignRight | Qt::AlignTop); + } + + // draw grid lines + rect.adjust(yAxisWidth + 4, 0, 0, 0); + QPen gridPen; + gridPen.setStyle(Qt::DashLine); + gridPen.setWidthF(1); + gridPen.setColor(QColor(128, 128, 128, 128)); + + // Set antialiasing for graphs + painter.setPen(gridPen); + painter.drawLine(fullRect.left(), rect.top(), rect.right(), rect.top()); + painter.drawLine(fullRect.left(), rect.top() + 0.25 * rect.height(), rect.right(), rect.top() + 0.25 * rect.height()); + painter.drawLine(fullRect.left(), rect.top() + 0.50 * rect.height(), rect.right(), rect.top() + 0.50 * rect.height()); + painter.drawLine(fullRect.left(), rect.top() + 0.75 * rect.height(), rect.right(), rect.top() + 0.75 * rect.height()); + painter.drawLine(fullRect.left(), rect.bottom(), rect.right(), rect.bottom()); + + constexpr auto TIME_AXIS_DIVISIONS = 6; + for (int i = 0; i < TIME_AXIS_DIVISIONS; ++i) + { + const int x = rect.left() + (i * rect.width()) / TIME_AXIS_DIVISIONS; + painter.drawLine(x, fullRect.top(), x, fullRect.bottom()); + } + + // draw graphs + // Need, else graphs cross left gridline + rect.adjust(3, 0, 0, 0); + // + const double yMultiplier = (niceScale.arg == 0.0) ? 0.0 : (static_cast(rect.height()) / niceScale.sizeInBytes()); + const double xTickSize = static_cast(rect.width()) / VIEWABLE; + + for (auto it = m_properties.constKeyValueBegin(); it != m_properties.constKeyValueEnd(); it++) + { + QVector points; + + for (int i = static_cast(dataCollection.size()) - 1, j = 0; (i >= 0) && (j <= VIEWABLE); --i, ++j) + { + const int newX = rect.right() - j * xTickSize; + const int newY = rect.bottom() - dataCollection[i].y[it->first] * yMultiplier; + points.push_back({ newX, newY }); + } + + painter.setPen(it->second.pen); + painter.drawPolyline(points.data(), points.size()); + } + + // draw legend + double legendHeight = 0; + int legendWidth = 0; + + for (const auto &property : qAsConst(m_properties)) + { + if (fontMetrics.horizontalAdvance(property.name) > legendWidth) + legendWidth = fontMetrics.horizontalAdvance(property.name); + + legendHeight += 1.5 * fontMetrics.height(); + } + + { + QPoint legendTopLeft(rect.left() + 4, fullRect.top() + 4); + QRectF legendBackgroundRect(QPoint(legendTopLeft.x() - 4, legendTopLeft.y() - 4), QSizeF(legendWidth + 8, legendHeight + 8)); + auto legendBackgroundColor = QWidget::palette().color(QWidget::backgroundRole()); + legendBackgroundColor.setAlpha(128); // 50% transparent + painter.fillRect(legendBackgroundRect, legendBackgroundColor); + + int i = 0; + for (const auto &property : qAsConst(m_properties)) + { + int nameSize = fontMetrics.horizontalAdvance(property.name); + double indent = 1.5 * (i++) * fontMetrics.height(); + painter.setPen(property.pen); + painter.drawLine(legendTopLeft + QPointF(0, indent + fontMetrics.height()), legendTopLeft + QPointF(nameSize, indent + fontMetrics.height())); + painter.drawText(QRectF(legendTopLeft + QPointF(0, indent), QSizeF(2 * nameSize, fontMetrics.height())), property.name, QTextOption(Qt::AlignVCenter)); + } + } +} diff --git a/3rdparty/qv2ray/v2/ui/widgets/speedchart/SpeedWidget.hpp b/3rdparty/qv2ray/v2/ui/widgets/speedchart/SpeedWidget.hpp new file mode 100644 index 0000000..3eeae4e --- /dev/null +++ b/3rdparty/qv2ray/v2/ui/widgets/speedchart/SpeedWidget.hpp @@ -0,0 +1,83 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015 Anton Lashkov + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ +#pragma once + +#include +#include +#include + +class SpeedWidget : public QGraphicsView +{ + Q_OBJECT + public: + enum GraphType + { + INBOUND_UP, + INBOUND_DOWN, + OUTBOUND_PROXY_UP, + OUTBOUND_PROXY_DOWN, + OUTBOUND_DIRECT_UP, + OUTBOUND_DIRECT_DOWN, + OUTBOUND_BLOCK_UP, + OUTBOUND_BLOCK_DOWN, + NB_GRAPHS, + }; + struct PointData + { + qint64 x; + quint64 y[NB_GRAPHS]; + PointData() + { + for (auto i = 0; i < NB_GRAPHS; i++) + y[i] = 0; + } + }; + + explicit SpeedWidget(QWidget *parent = nullptr); + void UpdateSpeedPlotSettings(); + void AddPointData(QMap data); + void Clear(); + void replot(); + + protected: + void paintEvent(QPaintEvent *event) override; + + private: + struct GraphProperties + { + GraphProperties(){}; + GraphProperties(const QString &name, const QPen &pen) : name(name), pen(pen){}; + QString name; + QPen pen; + }; + + quint64 maxYValue(); + QList dataCollection; + + QMap m_properties; +}; diff --git a/CMakeLists.txt b/CMakeLists.txt index efad33d..6b56354 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND WIN32) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) endif () -find_package(Qt6 REQUIRED COMPONENTS Widgets Network LinguistTools Charts DBus) +find_package(Qt6 REQUIRED COMPONENTS Widgets Network LinguistTools DBus) if (NKR_CROSS) set_property(TARGET Qt6::moc PROPERTY IMPORTED_LOCATION /usr/bin/moc) @@ -87,6 +87,8 @@ set(PROJECT_SOURCES 3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.cpp 3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp 3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.ui + 3rdparty/qv2ray/v2/ui/widgets/speedchart/SpeedWidget.cpp + 3rdparty/qv2ray/v2/ui/widgets/speedchart/SpeedWidget.hpp 3rdparty/qv2ray/v2/proxy/QvProxyConfigurator.cpp src/api/gRPC.cpp @@ -204,8 +206,6 @@ set(PROJECT_SOURCES include/stats/connections/connectionLister.hpp src/stats/connectionLister/connectionLister.cpp src/configs/proxy/Json2Bean.cpp - include/ui/utils/TrafficChart.h - include/ui/utils/CustomChartView.h include/sys/windows/eventHandler.h ) @@ -258,7 +258,7 @@ target_sources(nekoray PRIVATE ${CMAKE_BINARY_DIR}/translations.qrc) # Target Link target_link_libraries(nekoray PRIVATE - Qt6::Widgets Qt6::Network Qt6::Charts Qt6::DBus + Qt6::Widgets Qt6::Network Qt6::DBus Threads::Threads ${NKR_EXTERNAL_TARGETS} ${PLATFORM_LIBRARIES} diff --git a/include/ui/mainwindow.h b/include/ui/mainwindow.h index 03bc687..86a8769 100644 --- a/include/ui/mainwindow.h +++ b/include/ui/mainwindow.h @@ -4,7 +4,7 @@ #include "include/global/NekoGui.hpp" #include "include/stats/connections/connectionLister.hpp" -#include "utils/TrafficChart.h" +#include "3rdparty/qv2ray/v2/ui/widgets/speedchart/SpeedWidget.hpp" #ifdef Q_OS_LINUX #include #endif @@ -191,7 +191,7 @@ private: // int toolTipID; // - TrafficChart* trafficGraph; + SpeedWidget *speedChartWidget; QList> get_now_selected_list(); diff --git a/include/ui/utils/CustomChartView.h b/include/ui/utils/CustomChartView.h deleted file mode 100644 index fd41873..0000000 --- a/include/ui/utils/CustomChartView.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include - -class CustomChartView : public QChartView -{ - Q_OBJECT - -public: - explicit CustomChartView(QChart *chart, QWidget *parent = nullptr) - : QChartView(chart, parent), - tooltipTimer(new QTimer(this)), - lastMousePos(QPoint(-1, -1)) - { - setMouseTracking(true); - tooltipTimer->setInterval(200); - tooltipTimer->setSingleShot(true); - connect(tooltipTimer, &QTimer::timeout, this, [=]{emit mouseStopEvent(lastMousePos);}); - } - - signals: - void mouseStopEvent(QPoint pos); - - void mouseStartMoving(); - -protected: - void mouseMoveEvent(QMouseEvent *event) override - { - lastMousePos = event->pos(); - event->ignore(); - emit mouseStartMoving(); - tooltipTimer->start(); - } - -private: - QTimer *tooltipTimer; - QPoint lastMousePos; -}; \ No newline at end of file diff --git a/include/ui/utils/TrafficChart.h b/include/ui/utils/TrafficChart.h deleted file mode 100644 index 0ce9093..0000000 --- a/include/ui/utils/TrafficChart.h +++ /dev/null @@ -1,315 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include - -#include "CustomChartView.h" - -class TrafficChart -{ - QChart *chart; - CustomChartView *chartView; - - QVector proxyDlRaw; - QVector proxyUlRaw; - QVector directDlRaw; - QVector directUlRaw; - - QSplineSeries *proxyDlLine; - QSplineSeries *proxyUpLine; - QSplineSeries *directDlLine; - QSplineSeries *directUpLine; - std::multiset dataSet; - - QDateTimeAxis *timeAxis; - QValueAxis *valueAxis; - - QMutex tooltipMu; - - int scaleFactor = 1; - int intervalRange = 90; - - void updateMagnitude() - { - auto currMax = dataSet.rbegin().operator*(); - if (currMax == 0) return; - auto prevScale = scaleFactor; - // calc new magnitude - if (currMax <= 1000) - { - scaleFactor = 1; - } - else if (currMax <= 1000000) - { - scaleFactor = 1000; - } - else if (currMax <= 1000000000) - { - scaleFactor = 1000000; - } - else - { - scaleFactor = 1000000000; - } - valueAxis->setMax(static_cast(currMax) / static_cast(scaleFactor) * 1.1); - if (prevScale == scaleFactor) return; - valueAxis->setLabelFormat(getRateLabel()); - scaleData(); - } - - [[nodiscard]] QString getRateLabel() const - { - if (scaleFactor == 1) return "%.0f B/s"; - if (scaleFactor == 1000) return "%.2f KB/s"; - if (scaleFactor == 1000000) return "%.2f MB/s"; - if (scaleFactor == 1000000000) return "%.2f GB/s"; - return "%.0f ?/s"; - } - - void scaleData() const - { - auto data = proxyDlLine->points(); - for (int i = 0; i < data.size(); i++) - { - data[i] = {data[i].x(), static_cast(proxyDlRaw[i]) / scaleFactor}; - } - proxyDlLine->replace(data); - - data = proxyUpLine->points(); - for (int i = 0; i < data.size(); i++) - { - data[i] = {data[i].x(), static_cast(proxyUlRaw[i]) / scaleFactor}; - } - proxyUpLine->replace(data); - - data = directDlLine->points(); - for (int i = 0; i < data.size(); i++) - { - data[i] = {data[i].x(), static_cast(directDlRaw[i]) / scaleFactor}; - } - directDlLine->replace(data); - - data = directUpLine->points(); - for (int i = 0; i < data.size(); i++) - { - data[i] = {data[i].x(), static_cast(directUlRaw[i]) / scaleFactor}; - } - directUpLine->replace(data); - } - -public: - explicit TrafficChart() - { - // init - chart = new QChart; - updateTheme(); - chart->setTitle(QObject::tr("Traffic Chart")); - chart->legend()->setVisible(true); - chart->legend()->setAlignment(Qt::AlignBottom); - chart->setMargins(QMargins(0, 0, 0, 0)); - - proxyDlLine = new QSplineSeries; - proxyDlLine->setName(QObject::tr("Proxy Dl")); - proxyDlLine->setColor(Qt::darkMagenta); - auto pen = proxyDlLine->pen(); - pen.setWidth(3); - proxyDlLine->setPen(pen); - chart->addSeries(proxyDlLine); - - proxyUpLine = new QSplineSeries; - proxyUpLine->setName(QObject::tr("Proxy Ul")); - proxyUpLine->setColor(Qt::darkRed); - pen = proxyUpLine->pen(); - pen.setWidth(3); - proxyUpLine->setPen(pen); - chart->addSeries(proxyUpLine); - - directDlLine = new QSplineSeries; - directDlLine->setName(QObject::tr("Direct Dl")); - directDlLine->setColor(Qt::darkGreen); - pen = directDlLine->pen(); - pen.setWidth(3); - directDlLine->setPen(pen); - chart->addSeries(directDlLine); - - directUpLine = new QSplineSeries; - directUpLine->setName(QObject::tr("Direct Ul")); - directUpLine->setColor(Qt::darkYellow); - pen = directUpLine->pen(); - pen.setWidth(3); - directUpLine->setPen(pen); - chart->addSeries(directUpLine); - - timeAxis = new QDateTimeAxis; - timeAxis->setFormat("hh:mm:ss"); - timeAxis->setTickCount(10); - auto gridPen = timeAxis->gridLinePen(); - gridPen.setWidth(1); - gridPen.setDashPattern({1,3}); - gridPen.setColor(Qt::darkGray); - timeAxis->setGridLinePen(gridPen); - chart->addAxis(timeAxis, Qt::AlignBottom); - proxyDlLine->attachAxis(timeAxis); - proxyUpLine->attachAxis(timeAxis); - directDlLine->attachAxis(timeAxis); - directUpLine->attachAxis(timeAxis); - - valueAxis = new QValueAxis; - valueAxis->setLabelFormat("%.0f B/s"); - valueAxis->setMin(0); - valueAxis->setMax(1000); - valueAxis->setGridLinePen(gridPen); - chart->addAxis(valueAxis, Qt::AlignLeft); - proxyDlLine->attachAxis(valueAxis); - proxyUpLine->attachAxis(valueAxis); - directDlLine->attachAxis(valueAxis); - directUpLine->attachAxis(valueAxis); - - // initial values - auto now = QDateTime::currentDateTime(); - timeAxis->setRange(now.addSecs(-intervalRange + 1), now); - for (int i = 0; i < intervalRange; ++i) - { - proxyDlLine->append(now.addSecs(-intervalRange+i+1).toMSecsSinceEpoch(), 0); - proxyUpLine->append(now.addSecs(-intervalRange+i+1).toMSecsSinceEpoch(), 0); - directDlLine->append(now.addSecs(-intervalRange+i+1).toMSecsSinceEpoch(), 0); - directUpLine->append(now.addSecs(-intervalRange+i+1).toMSecsSinceEpoch(), 0); - - proxyDlRaw << 0; - proxyUlRaw << 0; - directDlRaw << 0; - directUlRaw << 0; - - dataSet.insert(0); - dataSet.insert(0); - dataSet.insert(0); - dataSet.insert(0); - } - - chartView = new CustomChartView(chart); - chartView->setRenderHint(QPainter::Antialiasing); - - QObject::connect(chartView, &CustomChartView::mouseStopEvent, chartView, [=](const QPoint pos) - { - if (!chartView->rect().contains(chartView->mapFromGlobal(QCursor::pos()))) return; - auto x = chart->mapToValue(pos).x(); - int idx = -1; - int mn=5000000; - for (int i=0;icount();i++) - { - if (auto dif = abs(proxyDlLine->at(i).x()-x); dif <= 500000) - { - if (dif < mn) - { - mn = dif; - idx = i; - } - else if (idx > 0) break; - } - } - if (idx == -1) return; - const auto format = getRateLabel(); - const auto data = QString::asprintf( - QString("Proxy Dl: " + format + "\nProxy Ul: " + format + "\nDirect Dl: " + format + "\nDirect Ul: " + format).toStdString().c_str(), - proxyDlLine->at(idx).y(), proxyUpLine->at(idx).y(), directDlLine->at(idx).y(), directUpLine->at(idx).y()); - QToolTip::showText(chartView->mapToGlobal(pos), data); - }); - - QObject::connect(chartView, &CustomChartView::mouseStartMoving, chartView, [=] - { - if (QToolTip::isVisible()) - { - QToolTip::hideText(); - } - }); - - for (QLegendMarker* marker : chart->legend()->markers()) - { - QObject::connect(marker, &QLegendMarker::clicked, chartView, [=] - { - auto series = static_cast(marker->series()); - double alpha; - if (series->isVisible()) - { - series->hide(); - marker->setVisible(true); - alpha = 0.5; - } else - { - series->show(); - alpha = 1.0; - } - QBrush brush = marker->labelBrush(); - QColor color = brush.color(); - color.setAlphaF(alpha); - brush.setColor(color); - marker->setLabelBrush(brush); - - brush = marker->brush(); - color = brush.color(); - color.setAlphaF(alpha); - brush.setColor(color); - marker->setBrush(brush); - - QPen markerPen = marker->pen(); - color = markerPen.color(); - color.setAlphaF(alpha); - markerPen.setColor(color); - marker->setPen(markerPen); - }); - } - }; - - [[nodiscard]] QChartView* getChartView() const - { - return chartView; - } - - void updateChart(int pDl, int pUl, int dDl, int dUl) - { - auto now = QDateTime::currentDateTime(); - dataSet.insert(pDl); - dataSet.insert(pUl); - dataSet.insert(dDl); - dataSet.insert(dUl); - - dataSet.erase(dataSet.find(proxyDlRaw.first())); - dataSet.erase(dataSet.find(proxyUlRaw.first())); - dataSet.erase(dataSet.find(directDlRaw.first())); - dataSet.erase(dataSet.find(directUlRaw.first())); - - proxyDlLine->remove(0); - proxyUpLine->remove(0); - directDlLine->remove(0); - directUpLine->remove(0); - - proxyDlRaw.removeFirst(); - proxyUlRaw.removeFirst(); - directDlRaw.removeFirst(); - directUlRaw.removeFirst(); - - proxyDlLine->append(now.toMSecsSinceEpoch(), static_cast(pDl) / static_cast(scaleFactor)); - proxyUpLine->append(now.toMSecsSinceEpoch(), static_cast(pUl) / static_cast(scaleFactor)); - directDlLine->append(now.toMSecsSinceEpoch(), static_cast(dDl) / static_cast(scaleFactor)); - directUpLine->append(now.toMSecsSinceEpoch(), static_cast(dUl) / static_cast(scaleFactor)); - - proxyDlRaw << pDl; - proxyUlRaw << pUl; - directDlRaw << dDl; - directUlRaw << dUl; - - timeAxis->setRange(now.addSecs(-intervalRange + 1), now); - - updateMagnitude(); - } - - void updateTheme() - { - chart->setTheme(qApp->styleHints()->colorScheme() == Qt::ColorScheme::Dark ? QChart::ChartThemeDark : QChart::ChartThemeLight); - } -}; diff --git a/res/translations/fa_IR.ts b/res/translations/fa_IR.ts index 3dc9ae5..07943bb 100644 --- a/res/translations/fa_IR.ts +++ b/res/translations/fa_IR.ts @@ -2127,4 +2127,15 @@ Direct: %2 + + SpeedWidget + + Proxy + + + + Direct + + + diff --git a/res/translations/ru_RU.ts b/res/translations/ru_RU.ts index b81f65d..9cb63ff 100644 --- a/res/translations/ru_RU.ts +++ b/res/translations/ru_RU.ts @@ -2153,4 +2153,15 @@ Release note: Некоторые правила не удалось добавить, исправьте их перед сохранением: + + SpeedWidget + + Proxy + Прокси + + + Direct + Напрямую + + diff --git a/res/translations/zh_CN.ts b/res/translations/zh_CN.ts index 91206a7..3cb4596 100644 --- a/res/translations/zh_CN.ts +++ b/res/translations/zh_CN.ts @@ -2083,7 +2083,7 @@ Release note: Direct - 直接 + 直连 Proxy @@ -2141,4 +2141,15 @@ Release note: 某些规则无法添加,请在保存之前修复它们: + + SpeedWidget + + Proxy + 代理 + + + Direct + 直连 + + diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp index d41b891..66eb7cd 100644 --- a/src/ui/mainwindow.cpp +++ b/src/ui/mainwindow.cpp @@ -47,7 +47,6 @@ #include #include #include -#include #include #include <3rdparty/QHotkey/qhotkey.h> #include @@ -89,7 +88,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi connect(qApp->styleHints(), &QStyleHints::colorSchemeChanged, this, [=](const Qt::ColorScheme& scheme) { new SyntaxHighlighter(scheme == Qt::ColorScheme::Dark, qvLogDocument); themeManager->ApplyTheme(NekoGui::dataStore->theme, true); - if (trafficGraph) trafficGraph->updateTheme(); }); connect(themeManager, &ThemeManager::themeChanged, this, [=](const QString& theme){ if (theme.toLower().contains("vista")) { @@ -256,9 +254,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi } }); - // setup Traffic Graph - trafficGraph = new TrafficChart(); - ui->graph_tab->layout()->addWidget(trafficGraph->getChartView()); + // setup Speed Chart + speedChartWidget = new SpeedWidget(this); + ui->graph_tab->layout()->addWidget(speedChartWidget); // table UI ui->proxyListTable->callback_save_order = [=] { @@ -1165,7 +1163,15 @@ void MainWindow::refresh_status(const QString &traffic_update) { void MainWindow::update_traffic_graph(int proxyDl, int proxyUp, int directDl, int directUp) { - if (trafficGraph) trafficGraph->updateChart(proxyDl, proxyUp, directDl, directUp);; + if (speedChartWidget) { + QMap pointData; + pointData[SpeedWidget::OUTBOUND_PROXY_UP] = proxyUp; + pointData[SpeedWidget::OUTBOUND_PROXY_DOWN] = proxyDl; + pointData[SpeedWidget::OUTBOUND_DIRECT_UP] = directUp; + pointData[SpeedWidget::OUTBOUND_DIRECT_DOWN] = directDl; + + speedChartWidget->AddPointData(pointData); + } } // table显示