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'),