diff --git a/Furious/Externals/GenTranslation.py b/Furious/Externals/GenTranslation.py
index 3375a73..46ec9c8 100644
--- a/Furious/Externals/GenTranslation.py
+++ b/Furious/Externals/GenTranslation.py
@@ -3072,30 +3072,6 @@ TRANSLATION = {
"ZH": "正在检测...",
"isReviewed": "True"
},
- "Connect to a proxy to view endpoint information.": {
- "source": [
- "Furious.Widget.EndpointInfoWidget"
- ],
- "RU": "Подключитесь к прокси, чтобы просмотреть информацию о выходном узле.",
- "ZH": "连接代理后可查看出口信息。",
- "isReviewed": "True"
- },
- "Connecting...": {
- "source": [
- "Furious.Widget.EndpointInfoWidget"
- ],
- "RU": "Подключение...",
- "ZH": "正在连接...",
- "isReviewed": "True"
- },
- "Proxy endpoint information is unavailable.": {
- "source": [
- "Furious.Widget.EndpointInfoWidget"
- ],
- "RU": "Информация о выходном узле прокси недоступна.",
- "ZH": "代理出口信息不可用。",
- "isReviewed": "True"
- },
"Approximate location unavailable": {
"source": [
"Furious.Widget.EndpointInfoWidget"
@@ -3141,7 +3117,7 @@ TRANSLATION = {
"Furious.Widget.EndpointInfoWidget"
],
"RU": "Местоположение приблизительно определено по публичному IP и может быть неточным.",
- "ZH": "位置信息根据公网 IP 估算,可能不准确。",
+ "ZH": "位置信息根据公网IP估算,可能不准确。",
"isReviewed": "True"
},
"Not available": {
@@ -3160,14 +3136,6 @@ TRANSLATION = {
"ZH": "未知",
"isReviewed": "True"
},
- "Endpoint inspection is disabled.": {
- "source": [
- "Furious.Widget.EndpointInfoWidget"
- ],
- "RU": "Проверка конечной точки отключена.",
- "ZH": "代理端点检测已禁用。",
- "isReviewed": "True"
- },
"Enable Proxy Endpoint Information": {
"source": [
"Furious.Window.SettingsPage"
@@ -3213,7 +3181,7 @@ TRANSLATION = {
"Furious.Window.SettingsPage"
],
"RU": "Публичный IP-адрес
\nПубличные IPv4- и IPv6-адреса прокси проверяются
\nчерез активное прокси-соединение с помощью Cloudflare,
\nа ipify используется как резервный сервис.
\nЭти сервисы могут видеть публичный IP-адрес прокси.",
- "ZH": "公网 IP
\n系统通过当前代理连接
\n使用 Cloudflare 检查代理的公网 IPv4 和 IPv6 地址,
\n并以 ipify 作为备用服务。
\n这些服务可以看到代理的公网 IP。",
+ "ZH": "公网 IP
\n系统通过当前代理连接
\n使用Cloudflare检查代理的公网IPv4和IPv6地址,
\n并以ipify作为备用服务。
\n这些服务可以看到代理的公网IP。",
"isReviewed": "True"
},
"Approximate Location
\nThe detected public IP is sent to ipapi.co
\nto estimate country, city, region, and network organization.
\nIP-based location can be inaccurate.": {
@@ -3221,7 +3189,7 @@ TRANSLATION = {
"Furious.Window.SettingsPage"
],
"RU": "Приблизительное местоположение
\nОбнаруженный публичный IP-адрес отправляется в ipapi.co
\nдля определения страны, города, региона и сетевой организации.
\nГеолокация по IP может быть неточной.",
- "ZH": "大致位置
\n系统将检测到的公网 IP 发送至 ipapi.co,
\n用于估算国家、城市、地区和网络运营组织。
\n基于 IP 的位置可能不准确。",
+ "ZH": "大致位置
\n系统将检测到的公网IP发送至ipapi.co,
\n用于估算国家、城市、地区和网络运营组织。
\n基于IP的位置可能不准确。",
"isReviewed": "True"
},
"Map
\nMap styles and tiles for the approximate area are loaded
\nfrom OpenFreeMap, using OpenStreetMap data.
\nOpenFreeMap receives these map requests.": {
@@ -3229,7 +3197,15 @@ TRANSLATION = {
"Furious.Window.SettingsPage"
],
"RU": "Карта
\nСтили и тайлы карты приблизительной области
\nзагружаются из OpenFreeMap с использованием данных OpenStreetMap.
\nOpenFreeMap получает эти запросы карты.",
- "ZH": "地图
\n大致区域的地图样式和图块从 OpenFreeMap 加载,
\n并使用 OpenStreetMap 数据。
\nOpenFreeMap 会接收这些地图请求。",
+ "ZH": "地图
\n大致区域的地图样式和图块从OpenFreeMap加载,
\n并使用OpenStreetMap数据。
\nOpenFreeMap会接收这些地图请求。",
+ "isReviewed": "True"
+ },
+ "Traffic Statistics": {
+ "source": [
+ "Furious.Window.MetricsPage"
+ ],
+ "RU": "Статистика трафика",
+ "ZH": "流量统计",
"isReviewed": "True"
}
}
diff --git a/Furious/Qt/StyleSheets/Base.py b/Furious/Qt/StyleSheets/Base.py
index 4a340b7..7093b0e 100644
--- a/Furious/Qt/StyleSheets/Base.py
+++ b/Furious/Qt/StyleSheets/Base.py
@@ -262,13 +262,16 @@ def baseStyleSheet(palette):
image: none;
}}
- QFrame#MetricsSection {{
+ QFrame#MetricsSection,
+ QFrame#TrafficMetricsCard {{
border: 1px solid {palette['border']};
border-radius: 10px;
background-color: {palette['panel']};
}}
- QLabel#MetricsSectionTitle {{
+ QLabel#MetricsSectionTitle,
+ QLabel#TrafficMetricsCardTitle,
+ QLabel#MetricsDirectionTitle {{
color: {palette['text_strong']};
font-size: 12pt;
font-weight: 600;
diff --git a/Furious/Window/MetricsPage.py b/Furious/Window/MetricsPage.py
index 97715a8..3f60cbd 100644
--- a/Furious/Window/MetricsPage.py
+++ b/Furious/Window/MetricsPage.py
@@ -70,38 +70,104 @@ class _MetricCard(QFrame):
self.graph.setMetricLabel(title)
-class _MetricsSection(QFrame):
- """Group speed and traffic-usage graphs for one direction."""
+class _TrafficMetricsCard(QFrame):
+ """Group selectors and all traffic graphs in one metrics card."""
- def __init__(self, speedGraph, usageGraph, parent=None):
- """Initialize a two-card metrics section."""
+ def __init__(
+ self,
+ downloadSpeedGraph,
+ downloadUsageGraph,
+ uploadSpeedGraph,
+ uploadUsageGraph,
+ parent=None,
+ ):
+ """Initialize the combined download and upload metrics card."""
super().__init__(parent)
- self.setObjectName('MetricsSection')
+ self.setObjectName('TrafficMetricsCard')
- self.titleLabel = AppQLabel(translatable=False, parent=self)
- self.titleLabel.setObjectName('MetricsSectionTitle')
+ self.titleLabel = AppQLabel(_('Traffic Statistics'), parent=self)
+ self.titleLabel.setObjectName('TrafficMetricsCardTitle')
- self.speedCard = _MetricCard(speedGraph, parent=self)
- self.usageCard = _MetricCard(usageGraph, parent=self)
+ self.timeRangeLabel, self.granularityLabel = (
+ AppQLabel(_('Time Range'), parent=self),
+ AppQLabel(_('Granularity'), parent=self),
+ )
- graphLayout = QHBoxLayout()
- graphLayout.setContentsMargins(0, 0, 0, 0)
- graphLayout.setSpacing(12)
- graphLayout.addWidget(self.speedCard, 1)
- graphLayout.addWidget(self.usageCard, 1)
+ self.timeRangeComboBox, self.granularityComboBox = (
+ AppQComboBox(parent=self),
+ AppQComboBox(parent=self),
+ )
+
+ self.timeRangeComboBox.setMinimumWidth(160)
+ self.granularityComboBox.setMinimumWidth(130)
+
+ self.downloadTitleLabel, self.uploadTitleLabel = (
+ AppQLabel(translatable=False, parent=self),
+ AppQLabel(translatable=False, parent=self),
+ )
+
+ for titleLabel in (self.downloadTitleLabel, self.uploadTitleLabel):
+ titleLabel.setObjectName('MetricsDirectionTitle')
+
+ self.downloadSpeedCard, self.downloadUsageCard = (
+ _MetricCard(downloadSpeedGraph, parent=self),
+ _MetricCard(downloadUsageGraph, parent=self),
+ )
+
+ self.uploadSpeedCard, self.uploadUsageCard = (
+ _MetricCard(uploadSpeedGraph, parent=self),
+ _MetricCard(uploadUsageGraph, parent=self),
+ )
+
+ headerLayout = QHBoxLayout()
+ headerLayout.setContentsMargins(0, 0, 0, 0)
+ headerLayout.setSpacing(8)
+ headerLayout.addWidget(self.titleLabel)
+ headerLayout.addStretch(1)
+ headerLayout.addWidget(self.timeRangeLabel)
+ headerLayout.addWidget(self.timeRangeComboBox)
+ headerLayout.addSpacing(8)
+ headerLayout.addWidget(self.granularityLabel)
+ headerLayout.addWidget(self.granularityComboBox)
+
+ downloadLayout = QHBoxLayout()
+ downloadLayout.setContentsMargins(0, 0, 0, 0)
+ downloadLayout.setSpacing(12)
+ downloadLayout.addWidget(self.downloadSpeedCard, 1)
+ downloadLayout.addWidget(self.downloadUsageCard, 1)
+
+ uploadLayout = QHBoxLayout()
+ uploadLayout.setContentsMargins(0, 0, 0, 0)
+ uploadLayout.setSpacing(12)
+ uploadLayout.addWidget(self.uploadSpeedCard, 1)
+ uploadLayout.addWidget(self.uploadUsageCard, 1)
layout = QVBoxLayout(self)
layout.setContentsMargins(16, 14, 16, 16)
layout.setSpacing(10)
- layout.addWidget(self.titleLabel)
- layout.addLayout(graphLayout, 1)
+ layout.addLayout(headerLayout)
+ layout.addWidget(self.downloadTitleLabel)
+ layout.addLayout(downloadLayout, 1)
+ layout.addWidget(self.uploadTitleLabel)
+ layout.addLayout(uploadLayout, 1)
- def setTitles(self, sectionTitle, speedTitle, usageTitle):
- """Set translated section and graph-card titles."""
- self.titleLabel.setText(sectionTitle)
- self.speedCard.setTitle(speedTitle)
- self.usageCard.setTitle(usageTitle)
+ def setTitles(
+ self,
+ downloadTitle,
+ downloadSpeedTitle,
+ downloadUsageTitle,
+ uploadTitle,
+ uploadSpeedTitle,
+ uploadUsageTitle,
+ ):
+ """Set translated direction and graph-card titles."""
+ self.downloadTitleLabel.setText(downloadTitle)
+ self.downloadSpeedCard.setTitle(downloadSpeedTitle)
+ self.downloadUsageCard.setTitle(downloadUsageTitle)
+ self.uploadTitleLabel.setText(uploadTitle)
+ self.uploadSpeedCard.setTitle(uploadSpeedTitle)
+ self.uploadUsageCard.setTitle(uploadUsageTitle)
class MetricsPage(Mixins.QTranslatable, Mixins.ThemeAware, QMainWindow):
@@ -136,34 +202,8 @@ class MetricsPage(Mixins.QTranslatable, Mixins.ThemeAware, QMainWindow):
self._dirty = True
self._renderRevision = 0
- (
- self.pageTitleLabel,
- self.timeRangeLabel,
- self.granularityLabel,
- self.timeRangeComboBox,
- self.granularityComboBox,
- ) = (
- AppQLabel(_('Network Metrics')),
- AppQLabel(_('Time Range')),
- AppQLabel(_('Granularity')),
- AppQComboBox(),
- AppQComboBox(),
- )
-
+ self.pageTitleLabel = AppQLabel(_('Network Metrics'))
self.pageTitleLabel.setObjectName('MetricsPageTitle')
- self.timeRangeComboBox.setMinimumWidth(160)
- self.granularityComboBox.setMinimumWidth(130)
-
- self._populateComboBox(
- self.timeRangeComboBox,
- self._timeRangeOptions(),
- self.DefaultTimeRange,
- )
- self._populateComboBox(
- self.granularityComboBox,
- self._granularityOptions(),
- self.DefaultGranularity,
- )
(
self.downloadSpeedGraph,
@@ -189,15 +229,35 @@ class MetricsPage(Mixins.QTranslatable, Mixins.ThemeAware, QMainWindow):
),
)
- self.downloadSection, self.uploadSection = (
- _MetricsSection(
- self.downloadSpeedGraph,
- self.downloadUsageGraph,
- ),
- _MetricsSection(
- self.uploadSpeedGraph,
- self.uploadUsageGraph,
- ),
+ self.metricsCard = _TrafficMetricsCard(
+ self.downloadSpeedGraph,
+ self.downloadUsageGraph,
+ self.uploadSpeedGraph,
+ self.uploadUsageGraph,
+ parent=self,
+ )
+
+ (
+ self.timeRangeLabel,
+ self.granularityLabel,
+ self.timeRangeComboBox,
+ self.granularityComboBox,
+ ) = (
+ self.metricsCard.timeRangeLabel,
+ self.metricsCard.granularityLabel,
+ self.metricsCard.timeRangeComboBox,
+ self.metricsCard.granularityComboBox,
+ )
+
+ self._populateComboBox(
+ self.timeRangeComboBox,
+ self._timeRangeOptions(),
+ self.DefaultTimeRange,
+ )
+ self._populateComboBox(
+ self.granularityComboBox,
+ self._granularityOptions(),
+ self.DefaultGranularity,
)
self.endpointInfoWidget = EndpointInfoWidget(
@@ -210,26 +270,14 @@ class MetricsPage(Mixins.QTranslatable, Mixins.ThemeAware, QMainWindow):
self._endpointInfoEnabledChanged
)
- controlsLayout = QHBoxLayout()
- controlsLayout.setContentsMargins(0, 0, 0, 0)
- controlsLayout.setSpacing(8)
- controlsLayout.addWidget(self.pageTitleLabel)
- controlsLayout.addStretch(1)
- controlsLayout.addWidget(self.timeRangeLabel)
- controlsLayout.addWidget(self.timeRangeComboBox)
- controlsLayout.addSpacing(8)
- controlsLayout.addWidget(self.granularityLabel)
- controlsLayout.addWidget(self.granularityComboBox)
-
contentWidget = QWidget()
contentWidget.setObjectName('MetricsPageContent')
contentLayout = QVBoxLayout(contentWidget)
contentLayout.setContentsMargins(20, 18, 20, 20)
contentLayout.setSpacing(14)
- contentLayout.addLayout(controlsLayout)
- contentLayout.addWidget(self.downloadSection, 1)
- contentLayout.addWidget(self.uploadSection, 1)
+ contentLayout.addWidget(self.pageTitleLabel)
+ contentLayout.addWidget(self.metricsCard, 1)
contentLayout.addWidget(self.endpointInfoWidget)
self.scrollArea = QScrollArea()
@@ -237,6 +285,7 @@ class MetricsPage(Mixins.QTranslatable, Mixins.ThemeAware, QMainWindow):
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setFrameShape(QFrame.Shape.NoFrame)
self.scrollArea.setWidget(contentWidget)
+
self.setCentralWidget(self.scrollArea)
self._renderTimer = QtCore.QTimer(self)
@@ -401,12 +450,10 @@ class MetricsPage(Mixins.QTranslatable, Mixins.ThemeAware, QMainWindow):
def retranslate(self):
"""Refresh composite section, graph, and endpoint presentation text."""
- self.downloadSection.setTitles(
+ self.metricsCard.setTitles(
_('Download'),
_('Download Speed'),
_('Download Traffic Usage'),
- )
- self.uploadSection.setTitles(
_('Upload'),
_('Upload Speed'),
_('Upload Traffic Usage'),