From bc9cd5026e862db6fb6165c3812971262885285e Mon Sep 17 00:00:00 2001 From: LorenEteval Date: Sun, 14 Jun 2026 23:22:56 +0800 Subject: [PATCH] Refactor: Migrate to QTableView for user servers, and added search box Signed-off-by: LorenEteval --- Furious/Externals/GenTranslation.py | 90 +++++++++++-------- Furious/Qt/QtWidgets.py | 33 +++++++ ...ableWidget.py => UserServersQTableView.py} | 79 +++++----------- Furious/Widget/__init__.py | 2 +- Furious/Window/AppMainWindow.py | 13 ++- 5 files changed, 120 insertions(+), 97 deletions(-) rename Furious/Widget/{UserServersQTableWidget.py => UserServersQTableView.py} (96%) diff --git a/Furious/Externals/GenTranslation.py b/Furious/Externals/GenTranslation.py index e61622a6..7e3a0e12 100644 --- a/Furious/Externals/GenTranslation.py +++ b/Furious/Externals/GenTranslation.py @@ -2,7 +2,7 @@ TRANSLATION = { "Delete": { "source": [ "Furious.Qt.QtWidgets", - "Furious.Widget.UserServersQTableWidget", + "Furious.Widget.UserServersQTableView", "Furious.Widget.UserSubsQTableWidget", "Furious.Widget.XrayAssetViewerQListWidget", "Furious.Window.UserSubsWindow" @@ -338,7 +338,7 @@ TRANSLATION = { }, "Select All": { "source": [ - "Furious.Widget.UserServersQTableWidget", + "Furious.Widget.UserServersQTableView", "Furious.Window.LogViewerWindow", "Furious.Window.TextEditorWindow" ], @@ -440,7 +440,7 @@ TRANSLATION = { "Furious.Widget.GuiTrojan", "Furious.Widget.GuiVLESS", "Furious.Widget.GuiVMess", - "Furious.Widget.UserServersQTableWidget", + "Furious.Widget.UserServersQTableView", "Furious.Widget.UserSubsQTableWidget" ], "RU": "Примечание", @@ -450,7 +450,7 @@ TRANSLATION = { "Protocol": { "source": [ "Furious.Widget.GuiHysteria1", - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Протокол", "ZH": "协议", @@ -462,7 +462,7 @@ TRANSLATION = { "Furious.Widget.GuiTrojan", "Furious.Widget.GuiVLESS", "Furious.Widget.GuiVMess", - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Адрес", "ZH": "地址", @@ -474,7 +474,7 @@ TRANSLATION = { "Furious.Widget.GuiTrojan", "Furious.Widget.GuiVLESS", "Furious.Widget.GuiVMess", - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Порт", "ZH": "端口", @@ -483,7 +483,7 @@ TRANSLATION = { "Transport": { "source": [ "Furious.Widget.GuiVTransport", - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Способ передачи", "ZH": "传输方式", @@ -491,7 +491,7 @@ TRANSLATION = { }, "TLS": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "TLS", "ZH": "TLS", @@ -499,7 +499,7 @@ TRANSLATION = { }, "Subscription": { "source": [ - "Furious.Widget.UserServersQTableWidget", + "Furious.Widget.UserServersQTableView", "Furious.Window.AppMainWindow" ], "RU": "Подписка", @@ -508,7 +508,7 @@ TRANSLATION = { }, "Latency": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Задержка", "ZH": "延迟", @@ -517,7 +517,7 @@ TRANSLATION = { "Speed": { "source": [ "Furious.Widget.GuiHysteria1", - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Скорость", "ZH": "速度", @@ -533,7 +533,7 @@ TRANSLATION = { }, "Move Up": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Переместить вверх", "ZH": "上移", @@ -541,7 +541,7 @@ TRANSLATION = { }, "Move Down": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Переместить вниз", "ZH": "下移", @@ -549,7 +549,7 @@ TRANSLATION = { }, "Duplicate": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Дубликат", "ZH": "克隆", @@ -557,7 +557,7 @@ TRANSLATION = { }, "Scroll To Activated Server": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Перейдите к активированному серверу", "ZH": "滚动到激活的服务器", @@ -565,7 +565,7 @@ TRANSLATION = { }, "Test Ping Latency": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Измерить задержку (ping)", "ZH": "测试Ping延迟", @@ -573,7 +573,7 @@ TRANSLATION = { }, "Test Download Speed": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Измерить скорость загрузки", "ZH": "测试下行速度", @@ -581,7 +581,7 @@ TRANSLATION = { }, "Test Download Speed (Multithreaded)": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Измерить скорость загрузки (многопоточная)", "ZH": "测试下行速度(多线程)", @@ -589,7 +589,7 @@ TRANSLATION = { }, "Clear Test Results": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Очистить результаты тестирования", "ZH": "清除测试结果", @@ -597,7 +597,7 @@ TRANSLATION = { }, "New Empty Configuration": { "source": [ - "Furious.Widget.UserServersQTableWidget", + "Furious.Widget.UserServersQTableView", "Furious.Window.AppMainWindow" ], "RU": "Создать новую пустую конфигурацию", @@ -606,7 +606,7 @@ TRANSLATION = { }, "Export Share Link To Clipboard": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Копировать ссылку в буфер обмена", "ZH": "导出分享链接至剪贴板", @@ -614,7 +614,7 @@ TRANSLATION = { }, "Export As QR Code": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Экспорт в виде QR-кода", "ZH": "导出为二维码", @@ -622,7 +622,7 @@ TRANSLATION = { }, "Export JSON Configuration To Clipboard": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Копировать JSON конфигурацию в буфер обмена", "ZH": "导出JSON配置至剪贴板", @@ -631,7 +631,7 @@ TRANSLATION = { "Connecting": { "source": [ "Furious.TrayActions.Connect", - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Подключение", "ZH": "正在连接", @@ -639,7 +639,7 @@ TRANSLATION = { }, "Connecting. Please wait...": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Подключение. Пожалуйста, подождите...", "ZH": "正在连接。请稍候", @@ -647,7 +647,7 @@ TRANSLATION = { }, "Untitled": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Без названия", "ZH": "未命名", @@ -819,7 +819,7 @@ TRANSLATION = { }, "Show Furious Log...": { "source": [ - "Furious.Widget.UserServersQTableWidget", + "Furious.Widget.UserServersQTableView", "Furious.Window.AppMainWindow" ], "RU": "Показать журнал Furious...", @@ -828,7 +828,7 @@ TRANSLATION = { }, "Show Core Log...": { "source": [ - "Furious.Widget.UserServersQTableWidget", + "Furious.Widget.UserServersQTableView", "Furious.Window.AppMainWindow" ], "RU": "Показать журнал ядра...", @@ -837,7 +837,7 @@ TRANSLATION = { }, "Show Tun2socks Log...": { "source": [ - "Furious.Widget.UserServersQTableWidget", + "Furious.Widget.UserServersQTableView", "Furious.Window.AppMainWindow" ], "RU": "Показать журнал Tun2socks...", @@ -1247,7 +1247,7 @@ TRANSLATION = { }, "Test Tcping Latency": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Измерить задержку (Tсping)", "ZH": "测试Tcping延迟", @@ -1342,7 +1342,7 @@ TRANSLATION = { }, "Customize JSON Configuration...": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Редактировать JSON конфигурацию...", "ZH": "自定义JSON配置...", @@ -1443,7 +1443,7 @@ TRANSLATION = { }, "Advanced...": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Расширенные...", "ZH": "高级...", @@ -1459,7 +1459,7 @@ TRANSLATION = { }, "Update subscription completed": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Подписка на обновление завершена", "ZH": "更新订阅完成", @@ -1467,7 +1467,7 @@ TRANSLATION = { }, "Update subscription failed": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Обновить подписку не удалось", "ZH": "更新订阅失败", @@ -1475,7 +1475,7 @@ TRANSLATION = { }, "Configuration has been updated": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Конфигурация обновлена", "ZH": "配置已更新", @@ -1483,7 +1483,7 @@ TRANSLATION = { }, "Configuration update failed": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Обновление конфигурации не удалось", "ZH": "配置更新失败", @@ -1845,10 +1845,26 @@ TRANSLATION = { }, "Activate Selected Server": { "source": [ - "Furious.Widget.UserServersQTableWidget" + "Furious.Widget.UserServersQTableView" ], "RU": "Активировать выбранный сервер", "ZH": "激活选定的服务器", "isReviewed": "True" + }, + "Search servers, e.g. vmess|trojan": { + "source": [ + "Furious.Window.AppMainWindow" + ], + "RU": "Поиск серверов, например: vmess|trojan", + "ZH": "查找服务器,例如 vmess|trojan", + "isReviewed": "True" + }, + "Search": { + "source": [ + "Furious.Window.AppMainWindow" + ], + "RU": "Поиск", + "ZH": "查找", + "isReviewed": "True" } } diff --git a/Furious/Qt/QtWidgets.py b/Furious/Qt/QtWidgets.py index 2e17bd86..533899ab 100644 --- a/Furious/Qt/QtWidgets.py +++ b/Furious/Qt/QtWidgets.py @@ -38,12 +38,14 @@ __all__ = [ 'AppQGroupBox', 'AppQHeaderView', 'AppQLabel', + 'AppQLineEdit', 'AppQListWidget', 'AppQMainWindow', 'AppQMenu', 'AppQMenuBar', 'AppQMessageBox', 'AppQPushButton', + 'AppQTableView', 'AppQTableWidget', 'AppQTabWidget', 'AppQToolBar', @@ -272,6 +274,14 @@ class AppQLabel(Mixins.QTranslatable, QLabel): self.setText(_(self.text())) +class AppQLineEdit(Mixins.QTranslatable, QLineEdit): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def retranslate(self): + self.setPlaceholderText(_(self.placeholderText())) + + class AppQListWidget(Mixins.ConnectionAware, QListWidget): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -486,6 +496,29 @@ class AppQPushButton(Mixins.QTranslatable, QPushButton): self.setText(_(self.text())) +class AppQTableView(Mixins.ConnectionAware, QTableView): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.setWordWrap(False) + self.setAlternatingRowColors(True) + + @staticmethod + def getStyleSheet(color): + return f'QTableView {{ selection-background-color: {color}; }}' + + def setSelectionColor(self, color): + # self.setStyleSheet(self.getStyleSheet(color)) + + pass + + def disconnectedCallback(self): + self.setSelectionColor(AppHue.disconnectedColor()) + + def connectedCallback(self): + self.setSelectionColor(AppHue.connectedColor()) + + class AppQTableWidget(Mixins.ConnectionAware, QTableWidget): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/Furious/Widget/UserServersQTableWidget.py b/Furious/Widget/UserServersQTableView.py similarity index 96% rename from Furious/Widget/UserServersQTableWidget.py rename to Furious/Widget/UserServersQTableView.py index 418e8e0e..89930371 100644 --- a/Furious/Widget/UserServersQTableWidget.py +++ b/Furious/Widget/UserServersQTableView.py @@ -40,11 +40,11 @@ from PySide6.QtNetwork import * from typing import Callable, Union, MutableSequence +import re import queue import logging import icmplib import functools -import re __all__ = ['UserServersQTableWidget'] @@ -701,43 +701,17 @@ class TestDownloadSpeedWorker(WebGETManager): self.sync() -class UserServersQTableWidgetHorizontalHeader(AppQHeaderView): - class SortOrder: - Ascending_ = QtCore.Qt.SortOrder.AscendingOrder - Descending = QtCore.Qt.SortOrder.DescendingOrder - +class UserServersQTableViewHorizontalHeader(AppQHeaderView): def __init__(self, *args, **kwargs): super().__init__(QtCore.Qt.Orientation.Horizontal, *args, **kwargs) - self.sortOrderTable = list( - self.SortOrder.Ascending_ for i in range(self.columnCount) - ) - self.sectionClicked.connect(self.handleSectionClicked) - - @QtCore.Slot(int) - def handleSectionClicked(self, clickedIndex: int): - parent = self.parent() - - if isinstance(parent, QTableView): - order = self.sortOrderTable[clickedIndex] - - parent.sortByColumn(clickedIndex, order) - - self.setSortIndicator(clickedIndex, order) - - if order == self.SortOrder.Ascending_: - self.sortOrderTable[clickedIndex] = self.SortOrder.Descending - else: - self.sortOrderTable[clickedIndex] = self.SortOrder.Ascending_ - - -class UserServersQTableWidgetVerticalHeader(AppQHeaderView): +class UserServersQTableViewVerticalHeader(AppQHeaderView): def __init__(self, *args, **kwargs): super().__init__(QtCore.Qt.Orientation.Vertical, *args, **kwargs) -class UserServersQTableWidgetHeaders: +class UserServersQTableViewHeaders: def __init__(self, name: str, func: Callable[[ConfigFactory], str] = None): self.name = name self.func = func @@ -758,7 +732,7 @@ class UserServersQTableWidgetHeaders: class UserServersTableModel(QtCore.QAbstractTableModel): SortRole = QtCore.Qt.ItemDataRole.UserRole + 1 - def __init__(self, headers: list[UserServersQTableWidgetHeaders], parent=None): + def __init__(self, headers: list[UserServersQTableViewHeaders], parent=None): super().__init__(parent) self.headers = headers @@ -816,7 +790,7 @@ class UserServersTableModel(QtCore.QAbstractTableModel): if row == Storage.UserActivatedItemIndex(): return QColor(AppHue.currentColor()) - return QBrush() + return None if role == QtCore.Qt.ItemDataRole.TextAlignmentRole: if str(header) == 'Latency' or str(header) == 'Speed': @@ -975,25 +949,22 @@ _TRANSLATABLE_HEADERS = [ ] -class UserServersQTableWidget(Mixins.QTranslatable, Mixins.ConnectionAware, QTableView): +class UserServersQTableWidget(Mixins.QTranslatable, AppQTableView): Headers = [ - UserServersQTableWidgetHeaders('Remark'), - UserServersQTableWidgetHeaders('Protocol'), - UserServersQTableWidgetHeaders('Address'), - UserServersQTableWidgetHeaders('Port'), - UserServersQTableWidgetHeaders('Transport'), - UserServersQTableWidgetHeaders('TLS'), - UserServersQTableWidgetHeaders('Subscription'), - UserServersQTableWidgetHeaders('Latency'), - UserServersQTableWidgetHeaders('Speed'), + UserServersQTableViewHeaders('Remark'), + UserServersQTableViewHeaders('Protocol'), + UserServersQTableViewHeaders('Address'), + UserServersQTableViewHeaders('Port'), + UserServersQTableViewHeaders('Transport'), + UserServersQTableViewHeaders('TLS'), + UserServersQTableViewHeaders('Subscription'), + UserServersQTableViewHeaders('Latency'), + UserServersQTableViewHeaders('Speed'), ] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.setWordWrap(False) - self.setAlternatingRowColors(True) - self.sourceModel = UserServersTableModel(self.Headers, parent=self) self.proxyModel = UserServersSortFilterProxyModel(parent=self) self.proxyModel.setSourceModel(self.sourceModel) @@ -1019,18 +990,19 @@ class UserServersQTableWidget(Mixins.QTranslatable, Mixins.ConnectionAware, QTab # Install custom header self.setHorizontalHeader( - UserServersQTableWidgetHorizontalHeader( + UserServersQTableViewHorizontalHeader( parent=self, legacySectionSizeSettingsName='ServerWidgetSectionSizeTable', sectionSizeSettingsName='UserServersHeaderViewState', ) ) - self.setVerticalHeader(UserServersQTableWidgetVerticalHeader(self)) + self.setVerticalHeader(UserServersQTableViewVerticalHeader(self)) self.horizontalHeader().setCustomSectionResizeMode() self.horizontalHeader().restoreSectionSize() - self.setSortingEnabled(False) + self.setSortingEnabled(True) + self.proxyModel.sort(-1) # Selection self.setSelectionColor(AppHue.disconnectedColor()) @@ -1257,13 +1229,6 @@ class UserServersQTableWidget(Mixins.QTranslatable, Mixins.ConnectionAware, QTab self.setCurrentIndex(self.activatedIndex()) self.activateItemByIndex(Storage.UserActivatedItemIndex(), True) - @staticmethod - def getStyleSheet(color): - return f'QTableView {{ selection-background-color: {color}; }}' - - def setSelectionColor(self, color): - self.setStyleSheet(self.getStyleSheet(color)) - @property def selectedIndex(self): indexes = list( @@ -1320,12 +1285,12 @@ class UserServersQTableWidget(Mixins.QTranslatable, Mixins.ConnectionAware, QTab ) def disconnectedCallback(self): - self.setSelectionColor(AppHue.disconnectedColor()) + super().disconnectedCallback() self.activateItemByIndex(Storage.UserActivatedItemIndex(), True) def connectedCallback(self): - self.setSelectionColor(AppHue.connectedColor()) + super().connectedCallback() self.activateItemByIndex(Storage.UserActivatedItemIndex(), True) diff --git a/Furious/Widget/__init__.py b/Furious/Widget/__init__.py index dd9bfce6..2c887cc4 100644 --- a/Furious/Widget/__init__.py +++ b/Furious/Widget/__init__.py @@ -28,6 +28,6 @@ from .GuiVLESS import * from .GuiVMess import * from .IndentSpinBox import * from .SystemTrayIcon import * -from .UserServersQTableWidget import * +from .UserServersQTableView import * from .UserSubsQTableWidget import * from .XrayAssetViewerQListWidget import * diff --git a/Furious/Window/AppMainWindow.py b/Furious/Window/AppMainWindow.py index 6eea9c84..dfbec46e 100644 --- a/Furious/Window/AppMainWindow.py +++ b/Furious/Window/AppMainWindow.py @@ -22,7 +22,7 @@ from Furious.Interface import * from Furious.Library import * from Furious.Qt import * from Furious.Qt import gettext as _ -from Furious.Widget.UserServersQTableWidget import * +from Furious.Widget.UserServersQTableView import * from Furious.Widget.GuiCustomizeNetworkTest import * from Furious.Widget.GuiCustomizeProxyBypass import * from Furious.Widget.GuiTUNSettings import * @@ -441,7 +441,8 @@ class AppMainWindow(AppQMainWindow): self._widget = QWidget() self._layout = QVBoxLayout(self._widget) self.searchLayout = QHBoxLayout() - self.searchLineEdit = QLineEdit() + self.searchLineEdit = AppQLineEdit() + self.searchLineEdit.setPlaceholderText(_('Search servers, e.g. vmess|trojan')) self.searchButton = AppQPushButton(_('Search')) self.searchLayout.addWidget(self.searchLineEdit) self.searchLayout.addWidget(self.searchButton) @@ -454,9 +455,17 @@ class AppMainWindow(AppQMainWindow): self.searchLineEdit.returnPressed.connect( lambda: self.userServersQTableWidget.search(self.searchLineEdit.text()) ) + self.searchLineEdit.textChanged.connect( + self.handleUserServersSearchTextChanged + ) self.setCentralWidget(self._widget) + @QtCore.Slot(str) + def handleUserServersSearchTextChanged(self, text: str): + if not text: + self.userServersQTableWidget.clearSearch() + def updateSubsByUnique(self, unique: str, httpProxy: Union[str, None], **kwargs): self.userServersQTableWidget.updateSubsByUnique(unique, httpProxy, **kwargs)