mirror of
https://github.com/LorenEteval/Furious.git
synced 2026-09-30 02:48:11 +03:00
Add EditSubscriptionWidget
Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
@@ -82,6 +82,8 @@ SUPPORTED_SETTINGS = (
|
||||
Settings('CustomRouting'),
|
||||
# User Configuration
|
||||
Settings('Configuration'),
|
||||
# User Custom Subscription object
|
||||
Settings('CustomSubscription'),
|
||||
# User Activated Server Index
|
||||
Settings('ActivatedItemIndex'),
|
||||
# User Tor Relay Settings,
|
||||
@@ -90,10 +92,14 @@ SUPPORTED_SETTINGS = (
|
||||
Settings('ServerWidgetWindowSize'),
|
||||
# Routes Widget Window Size
|
||||
Settings('RoutesWidgetWindowSize'),
|
||||
# Subscription Widget Window Size
|
||||
Settings('SubscriptionWidgetWindowSize'),
|
||||
# Server Widget Section Size
|
||||
Settings('ServerWidgetSectionSizeTable'),
|
||||
# Routes Widget Section Size
|
||||
Settings('RoutesWidgetSectionSizeTable'),
|
||||
# Subscription Widget Section Size
|
||||
Settings('SubscriptionWidgetSectionSizeTable'),
|
||||
# Server Widget Font Point Size
|
||||
Settings('ServerWidgetPointSize'),
|
||||
# Log Viewer Widget Font Point Size
|
||||
|
||||
@@ -108,6 +108,7 @@ NO_TRANSLATION = [
|
||||
TOR_FAQ_LABEL,
|
||||
'socks',
|
||||
'http',
|
||||
'URL',
|
||||
]
|
||||
|
||||
NO_TRANSLATION_DICT = {key: True for key in NO_TRANSLATION}
|
||||
@@ -169,20 +170,20 @@ TRANSLATION = {
|
||||
'ZH': '自定义',
|
||||
'TW': '自定義',
|
||||
},
|
||||
'VPN Mode': {
|
||||
'ES': 'Modo VPN',
|
||||
'ZH': 'VPN模式',
|
||||
'TW': 'VPN模式',
|
||||
'Subscription': {
|
||||
'ES': 'Suscripción',
|
||||
'ZH': '订阅',
|
||||
'TW': '訂閲',
|
||||
},
|
||||
'VPN Mode Disabled (Administrator)': {
|
||||
'ES': 'Modo VPN Deshabilitado (Administrador)',
|
||||
'ZH': 'VPN模式已禁用(管理员)',
|
||||
'TW': 'VPN模式已禁用(管理員)',
|
||||
'Update Subscription': {
|
||||
'ES': 'Actualizar Suscripción',
|
||||
'ZH': '更新订阅',
|
||||
'TW': '更新訂閲',
|
||||
},
|
||||
'VPN Mode Disabled (root)': {
|
||||
'ES': 'Modo VPN Deshabilitado (root)',
|
||||
'ZH': 'VPN模式已禁用(root)',
|
||||
'TW': 'VPN模式已禁用(root)',
|
||||
'Edit Subscription...': {
|
||||
'ES': 'Editar Suscripción...',
|
||||
'ZH': '编辑订阅...',
|
||||
'TW': '編輯訂閲...',
|
||||
},
|
||||
'Import': {
|
||||
'ES': 'Importar',
|
||||
@@ -229,6 +230,21 @@ TRANSLATION = {
|
||||
'ZH': '在编辑器中显示制表符和空格',
|
||||
'TW': '在編輯器中顯示制表符和空格',
|
||||
},
|
||||
'VPN Mode': {
|
||||
'ES': 'Modo VPN',
|
||||
'ZH': 'VPN模式',
|
||||
'TW': 'VPN模式',
|
||||
},
|
||||
'VPN Mode Disabled (Administrator)': {
|
||||
'ES': 'Modo VPN Deshabilitado (Administrador)',
|
||||
'ZH': 'VPN模式已禁用(管理员)',
|
||||
'TW': 'VPN模式已禁用(管理員)',
|
||||
},
|
||||
'VPN Mode Disabled (root)': {
|
||||
'ES': 'Modo VPN Deshabilitado (root)',
|
||||
'ZH': 'VPN模式已禁用(root)',
|
||||
'TW': 'VPN模式已禁用(root)',
|
||||
},
|
||||
'Routing Settings...': {
|
||||
'ES': 'Ajustes de Enrutamiento...',
|
||||
'ZH': '路由设置...',
|
||||
@@ -791,6 +807,32 @@ TRANSLATION = {
|
||||
'ZH': f'注意:"自定义"将使用当前用户配置中定义的acl和mmdb。',
|
||||
'TW': f'注意:"自定義"將使用當前用戶配置中定義的acl和mmdb。',
|
||||
},
|
||||
# Edit Subscription Widget
|
||||
'Edit Subscription': {
|
||||
'ES': 'Editar Suscripción',
|
||||
'ZH': '编辑订阅',
|
||||
'TW': '編輯訂閲',
|
||||
},
|
||||
'Subscription List': {
|
||||
'ES': 'Lista de Suscripción',
|
||||
'ZH': '订阅列表',
|
||||
'TW': '訂閲列表',
|
||||
},
|
||||
'Add subscription': {
|
||||
'ES': 'Agregar suscripción',
|
||||
'ZH': '添加订阅',
|
||||
'TW': '添加訂閲',
|
||||
},
|
||||
'Enter subscription remark:': {
|
||||
'ES': 'Introducir comentario de suscripción:',
|
||||
'ZH': '输入订阅名称:',
|
||||
'TW': '輸入訂閲名稱:',
|
||||
},
|
||||
'Enter subscription URL:': {
|
||||
'ES': 'Introducir URL de suscripción:',
|
||||
'ZH': '输入订阅URL:',
|
||||
'TW': '輸入訂閲URL:',
|
||||
},
|
||||
# Asset Viewer
|
||||
'Asset File': {
|
||||
'ES': 'Activos',
|
||||
|
||||
@@ -152,6 +152,44 @@ class RoutesStorage:
|
||||
APP().CustomRouting = ''
|
||||
|
||||
|
||||
class SubscriptionStorage:
|
||||
# remark, webURL, unique
|
||||
EMPTY_OBJECT = {'model': []}
|
||||
|
||||
@staticmethod
|
||||
def init():
|
||||
return copy.deepcopy(SubscriptionStorage.EMPTY_OBJECT)
|
||||
|
||||
@staticmethod
|
||||
def sync(ob=None):
|
||||
if ob is None:
|
||||
# Object is up-to-date
|
||||
APP().CustomSubscription = SubscriptionStorage.toStorage(
|
||||
APP().SubscriptionWidget.StorageObj
|
||||
)
|
||||
else:
|
||||
# Object is up-to-date
|
||||
APP().CustomSubscription = SubscriptionStorage.toStorage(ob)
|
||||
|
||||
@staticmethod
|
||||
def toObject(st):
|
||||
if not st:
|
||||
# Server storage does not exist, or is empty
|
||||
return SubscriptionStorage.init()
|
||||
|
||||
return ujson.loads(Base64Encoder.decode(st))
|
||||
|
||||
@staticmethod
|
||||
def toStorage(ob):
|
||||
return Base64Encoder.encode(
|
||||
ujson.dumps(ob, ensure_ascii=False, escape_forward_slashes=False).encode()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def clear():
|
||||
APP().CustomSubscription = ''
|
||||
|
||||
|
||||
class TorRelaySettingsStorage:
|
||||
EMPTY_OBJECT = {
|
||||
'socksTunnelPort': DEFAULT_TOR_SOCKS_PORT,
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
from Furious.Widget.SystemTrayIcon import SystemTrayIcon
|
||||
from Furious.Widget.EditConfiguration import EditConfigurationWidget
|
||||
from Furious.Widget.EditRouting import EditRoutingWidget
|
||||
from Furious.Widget.EditSubscription import EditSubscriptionWidget
|
||||
from Furious.Widget.LogViewer import LogViewerWidget, TorViewerWidget
|
||||
from Furious.Widget.TorRelaySettings import TorRelaySettingsWidget
|
||||
from Furious.Utility.Constants import (
|
||||
@@ -170,6 +171,7 @@ class Application(SingletonApplication):
|
||||
|
||||
self.ServerWidget = None
|
||||
self.RoutesWidget = None
|
||||
self.SubscriptionWidget = None
|
||||
self.torRelaySettingsWidget = None
|
||||
|
||||
def __getattr__(self, key):
|
||||
@@ -270,6 +272,7 @@ class Application(SingletonApplication):
|
||||
|
||||
self.ServerWidget = EditConfigurationWidget()
|
||||
self.RoutesWidget = EditRoutingWidget()
|
||||
self.SubscriptionWidget = EditSubscriptionWidget()
|
||||
self.torRelaySettingsWidget = TorRelaySettingsWidget()
|
||||
|
||||
self.tray = SystemTrayIcon()
|
||||
|
||||
@@ -209,20 +209,9 @@ class AssetViewerWidget(SupportThemeChangedCallback, MainWindow):
|
||||
self._fileMenu = Menu(*fileMenuActions, title=_('File'), parent=self)
|
||||
self.menuBar().addMenu(self._fileMenu)
|
||||
|
||||
if PLATFORM != 'Darwin':
|
||||
self.setWidthAndHeight()
|
||||
|
||||
def show(self):
|
||||
super().show()
|
||||
|
||||
if PLATFORM == 'Darwin':
|
||||
self.setWidthAndHeight()
|
||||
|
||||
def setWidthAndHeight(self):
|
||||
self.setGeometry(100, 100, 360, 360 * GOLDEN_RATIO)
|
||||
|
||||
moveToCenter(self)
|
||||
|
||||
@QtCore.Slot(QtCore.QPoint)
|
||||
def handleCustomContextMenuRequested(self, point):
|
||||
self.contextMenu.exec(self.mapToGlobal(point))
|
||||
|
||||
@@ -622,6 +622,23 @@ class ZoomOutAction(Action):
|
||||
self.parent().plainTextEdit.zoomOut()
|
||||
|
||||
|
||||
class UpdateSubscriptionAction(Action):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(_('Update Subscription'), **kwargs)
|
||||
|
||||
|
||||
class EditSubscriptionAction(Action):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(
|
||||
_('Edit Subscription...'),
|
||||
icon=bootstrapIcon('star.svg'),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def triggeredCallback(self, checked):
|
||||
APP().SubscriptionWidget.show()
|
||||
|
||||
|
||||
class ShowLogAction(Action):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(_(f'Show {APPLICATION_NAME} Log...'), **kwargs)
|
||||
@@ -1827,6 +1844,15 @@ class EditConfigurationWidget(MainWindow):
|
||||
],
|
||||
}
|
||||
|
||||
subsMenu = {
|
||||
'name': 'Subscription',
|
||||
'actions': [
|
||||
UpdateSubscriptionAction(parent=self),
|
||||
Seperator(),
|
||||
EditSubscriptionAction(parent=self),
|
||||
],
|
||||
}
|
||||
|
||||
helpMenu = {
|
||||
'name': 'Help',
|
||||
'actions': [
|
||||
@@ -1839,7 +1865,7 @@ class EditConfigurationWidget(MainWindow):
|
||||
}
|
||||
|
||||
# Menus
|
||||
for menuDict in (fileMenu, editMenu, viewMenu, helpMenu):
|
||||
for menuDict in (fileMenu, editMenu, viewMenu, subsMenu, helpMenu):
|
||||
menuName = menuDict['name']
|
||||
menuObjName = f'_{menuName}Menu'
|
||||
menu = Menu(*menuDict['actions'], title=_(menuName), parent=self.menuBar())
|
||||
@@ -1856,15 +1882,6 @@ class EditConfigurationWidget(MainWindow):
|
||||
|
||||
self.menuBar().addMenu(menu)
|
||||
|
||||
if PLATFORM != 'Darwin':
|
||||
self.setWidthAndHeight()
|
||||
|
||||
def show(self):
|
||||
super().show()
|
||||
|
||||
if PLATFORM == 'Darwin':
|
||||
self.setWidthAndHeight()
|
||||
|
||||
def setWidthAndHeight(self):
|
||||
try:
|
||||
self.setGeometry(
|
||||
@@ -1877,8 +1894,6 @@ class EditConfigurationWidget(MainWindow):
|
||||
|
||||
self.setGeometry(100, 100, 1800, 960)
|
||||
|
||||
moveToCenter(self)
|
||||
|
||||
def importServer(self, remark, config, syncStorage=False):
|
||||
server = {'remark': remark, 'config': config}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ from Furious.Action.Routing import (
|
||||
from Furious.Core.Core import XrayCore, Hysteria1
|
||||
from Furious.Gui.Action import Action, Seperator
|
||||
from Furious.Widget.Widget import (
|
||||
Dialog,
|
||||
HeaderView,
|
||||
Menu,
|
||||
MainWindow,
|
||||
@@ -612,20 +613,9 @@ class RoutingEditor(MainWindow):
|
||||
self.menuBar().addMenu(self._fileMenu)
|
||||
self.menuBar().addMenu(self._viewMenu)
|
||||
|
||||
if PLATFORM != 'Darwin':
|
||||
self.setWidthAndHeight()
|
||||
|
||||
def show(self):
|
||||
super().show()
|
||||
|
||||
if PLATFORM == 'Darwin':
|
||||
self.setWidthAndHeight()
|
||||
|
||||
def setWidthAndHeight(self):
|
||||
self.setGeometry(100, 100, 656, 856)
|
||||
|
||||
moveToCenter(self)
|
||||
|
||||
def addTabWithData(self, core, text):
|
||||
if self.isBuiltin:
|
||||
editor = RoutingTextBrowser(parent=self)
|
||||
@@ -722,7 +712,7 @@ class RoutingEditor(MainWindow):
|
||||
self.setWindowTitle(_(self.windowTitle()))
|
||||
|
||||
|
||||
class RoutingDialog(Translatable, SupportConnectedCallback, QDialog):
|
||||
class RoutingDialog(Dialog):
|
||||
LINE_EDIT_FIXED_WIDTH = 500
|
||||
BUTTON_FIXED_WIDTH = 30
|
||||
|
||||
@@ -825,12 +815,6 @@ class RoutingDialog(Translatable, SupportConnectedCallback, QDialog):
|
||||
def mmdbValue(self):
|
||||
return self.mmdbEdit.text()
|
||||
|
||||
def connectedCallback(self):
|
||||
self.setWindowIcon(bootstrapIcon('rocket-takeoff-connected-dark.svg'))
|
||||
|
||||
def disconnectedCallback(self):
|
||||
self.setWindowIcon(bootstrapIcon('rocket-takeoff-window.svg'))
|
||||
|
||||
def retranslate(self):
|
||||
with StateContext(self):
|
||||
if self.isBuiltin:
|
||||
@@ -924,6 +908,13 @@ class EditRoutingTableWidget(Translatable, SupportConnectedCallback, TableWidget
|
||||
# Restore horizontal section size
|
||||
self.sectionSizeTable = ujson.loads(APP().RoutesWidgetSectionSizeTable)
|
||||
|
||||
# Fill missing value
|
||||
for column in range(self.columnCount()):
|
||||
if self.sectionSizeTable.get(str(column)) is None:
|
||||
self.sectionSizeTable[
|
||||
str(column)
|
||||
] = self.horizontalHeader().defaultSectionSize()
|
||||
|
||||
# Block resize callback
|
||||
self.horizontalHeader().blockSignals(True)
|
||||
|
||||
@@ -1163,7 +1154,7 @@ class EditRoutingTableWidget(Translatable, SupportConnectedCallback, TableWidget
|
||||
item.setText(_(item.text()))
|
||||
|
||||
|
||||
class AddRoutingDialog(Translatable, SupportConnectedCallback, QDialog):
|
||||
class AddRoutingDialog(Dialog):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
@@ -1189,15 +1180,12 @@ class AddRoutingDialog(Translatable, SupportConnectedCallback, QDialog):
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def setWidthAndHeight(self):
|
||||
self.setGeometry(100, 100, 276, 80)
|
||||
|
||||
def routingRemark(self):
|
||||
return self.remarkEdit.text()
|
||||
|
||||
def connectedCallback(self):
|
||||
self.setWindowIcon(bootstrapIcon('rocket-takeoff-connected-dark.svg'))
|
||||
|
||||
def disconnectedCallback(self):
|
||||
self.setWindowIcon(bootstrapIcon('rocket-takeoff-window.svg'))
|
||||
|
||||
def retranslate(self):
|
||||
with StateContext(self):
|
||||
self.setWindowTitle(_(self.windowTitle()))
|
||||
@@ -1207,6 +1195,8 @@ class AddRoutingDialog(Translatable, SupportConnectedCallback, QDialog):
|
||||
for button in self.dialogBtns.buttons():
|
||||
button.setText(_(button.text()))
|
||||
|
||||
moveToCenter(self)
|
||||
|
||||
|
||||
class EditRoutingWidget(MainWindow):
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -1256,15 +1246,6 @@ class EditRoutingWidget(MainWindow):
|
||||
|
||||
self.setCentralWidget(self.fakeCentralWidget)
|
||||
|
||||
if PLATFORM != 'Darwin':
|
||||
self.setWidthAndHeight()
|
||||
|
||||
def show(self):
|
||||
super().show()
|
||||
|
||||
if PLATFORM == 'Darwin':
|
||||
self.setWidthAndHeight()
|
||||
|
||||
def setWidthAndHeight(self):
|
||||
try:
|
||||
self.setGeometry(
|
||||
@@ -1277,8 +1258,6 @@ class EditRoutingWidget(MainWindow):
|
||||
|
||||
self.setGeometry(100, 100, 360 * GOLDEN_RATIO, 360)
|
||||
|
||||
moveToCenter(self)
|
||||
|
||||
def addRoute(self):
|
||||
choice = self.addRoutingDialog.exec()
|
||||
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
# Copyright (C) 2023 Loren Eteval <loren.eteval@proton.me>
|
||||
#
|
||||
# This file is part of Furious.
|
||||
#
|
||||
# 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from Furious.Widget.Widget import (
|
||||
Dialog,
|
||||
HeaderView,
|
||||
MainWindow,
|
||||
MessageBox,
|
||||
PushButton,
|
||||
StyledItemDelegate,
|
||||
TableWidget,
|
||||
TabWidget,
|
||||
)
|
||||
from Furious.Widget.EditConfiguration import QuestionDeleteBox
|
||||
from Furious.Utility.Constants import (
|
||||
APP,
|
||||
PLATFORM,
|
||||
GOLDEN_RATIO,
|
||||
Color,
|
||||
)
|
||||
from Furious.Utility.Utility import (
|
||||
SubscriptionStorage,
|
||||
StateContext,
|
||||
SupportConnectedCallback,
|
||||
bootstrapIcon,
|
||||
moveToCenter,
|
||||
)
|
||||
from Furious.Utility.Translator import Translatable, gettext as _
|
||||
|
||||
from PySide6 import QtCore
|
||||
from PySide6.QtGui import QFont
|
||||
from PySide6.QtWidgets import (
|
||||
QAbstractItemView,
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QFormLayout,
|
||||
QGridLayout,
|
||||
QHeaderView,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QTableWidget,
|
||||
QTableWidgetItem,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
import uuid
|
||||
import ujson
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EditSubsHorizontalHeader(HeaderView):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(QtCore.Qt.Orientation.Horizontal, *args, **kwargs)
|
||||
|
||||
self.sectionResized.connect(self.handleSectionResized)
|
||||
|
||||
@QtCore.Slot(int, int, int)
|
||||
def handleSectionResized(self, index, oldSize, newSize):
|
||||
# Keys are string when loaded from json
|
||||
self.parent().sectionSizeTable[str(index)] = newSize
|
||||
|
||||
|
||||
class EditSubsVerticalHeader(HeaderView):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(QtCore.Qt.Orientation.Vertical, *args, **kwargs)
|
||||
|
||||
|
||||
class EditSubsTableWidget(Translatable, SupportConnectedCallback, TableWidget):
|
||||
# Might be extended in the future
|
||||
HEADER_LABEL = [
|
||||
'Remark',
|
||||
'URL',
|
||||
]
|
||||
|
||||
# Corresponds to header label
|
||||
HEADER_LABEL_GET_FUNC = [
|
||||
lambda subscription: subscription['remark'],
|
||||
lambda subscription: subscription['webURL'],
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.SubscriptionWidget = kwargs.get('parent')
|
||||
|
||||
self.questionDeleteBox = QuestionDeleteBox(
|
||||
icon=MessageBox.Icon.Question, parent=self.parent()
|
||||
)
|
||||
|
||||
# Handy reference
|
||||
self.SubscriptionList = self.SubscriptionWidget.SubscriptionList
|
||||
|
||||
# Column count
|
||||
self.setColumnCount(len(EditSubsTableWidget.HEADER_LABEL))
|
||||
|
||||
# Delegate
|
||||
self.delegate = StyledItemDelegate(parent=self)
|
||||
self.setItemDelegate(self.delegate)
|
||||
|
||||
# Install custom header
|
||||
self.setHorizontalHeader(EditSubsHorizontalHeader(self))
|
||||
|
||||
self.initTableFromData()
|
||||
|
||||
# Horizontal header resize mode
|
||||
for index in range(self.columnCount()):
|
||||
if index < self.columnCount() - 1:
|
||||
self.horizontalHeader().setSectionResizeMode(
|
||||
index, QHeaderView.ResizeMode.Interactive
|
||||
)
|
||||
else:
|
||||
self.horizontalHeader().setSectionResizeMode(
|
||||
index, QHeaderView.ResizeMode.Stretch
|
||||
)
|
||||
|
||||
try:
|
||||
# Restore horizontal section size
|
||||
self.sectionSizeTable = ujson.loads(
|
||||
APP().SubscriptionWidgetSectionSizeTable
|
||||
)
|
||||
|
||||
# Fill missing value
|
||||
for column in range(self.columnCount()):
|
||||
if self.sectionSizeTable.get(str(column)) is None:
|
||||
self.sectionSizeTable[
|
||||
str(column)
|
||||
] = self.horizontalHeader().defaultSectionSize()
|
||||
|
||||
# Block resize callback
|
||||
self.horizontalHeader().blockSignals(True)
|
||||
|
||||
for key, value in self.sectionSizeTable.items():
|
||||
self.horizontalHeader().resizeSection(int(key), value)
|
||||
|
||||
# Unblock resize callback
|
||||
self.horizontalHeader().blockSignals(False)
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
# Leave keys as strings since they will be
|
||||
# loaded as string from json
|
||||
self.sectionSizeTable = {
|
||||
str(row): self.horizontalHeader().defaultSectionSize()
|
||||
for row in range(self.columnCount())
|
||||
}
|
||||
|
||||
self.setHorizontalHeaderLabels(
|
||||
list(_(label) for label in EditSubsTableWidget.HEADER_LABEL)
|
||||
)
|
||||
|
||||
for column in range(self.horizontalHeader().count()):
|
||||
self.horizontalHeaderItem(column).setFont(QFont(APP().customFontName))
|
||||
|
||||
# Install custom header
|
||||
self.setVerticalHeader(EditSubsVerticalHeader(self))
|
||||
|
||||
# Selection
|
||||
self.setSelectionColor(Color.LIGHT_BLUE)
|
||||
self.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
|
||||
self.setSelectionMode(QTableWidget.SelectionMode.ExtendedSelection)
|
||||
|
||||
# No drag and drop
|
||||
self.setDragEnabled(False)
|
||||
self.setDragDropMode(QAbstractItemView.DragDropMode.NoDragDrop)
|
||||
self.setDropIndicatorShown(False)
|
||||
self.setDefaultDropAction(QtCore.Qt.DropAction.IgnoreAction)
|
||||
|
||||
def initTableFromData(self):
|
||||
for subscription in self.SubscriptionList:
|
||||
self.appendDataByColumn(
|
||||
lambda column: EditSubsTableWidget.HEADER_LABEL_GET_FUNC[column](
|
||||
subscription
|
||||
)
|
||||
)
|
||||
|
||||
def deleteSelectedItem(self):
|
||||
indexes = self.selectedIndex
|
||||
|
||||
if len(indexes) == 0:
|
||||
# Nothing to do
|
||||
return
|
||||
|
||||
self.questionDeleteBox.isMulti = bool(len(indexes) > 1)
|
||||
self.questionDeleteBox.possibleRemark = f'{self.item(indexes[0], 0).text()}'
|
||||
self.questionDeleteBox.setText(self.questionDeleteBox.getText())
|
||||
|
||||
if self.questionDeleteBox.exec() == MessageBox.StandardButton.No.value:
|
||||
# Do not delete
|
||||
return
|
||||
|
||||
for i in range(len(indexes)):
|
||||
takedRow = indexes[i] - i
|
||||
|
||||
self.removeRow(takedRow)
|
||||
self.SubscriptionList.pop(takedRow)
|
||||
|
||||
# TODO: Remove related server
|
||||
|
||||
# Sync it
|
||||
SubscriptionStorage.sync()
|
||||
|
||||
def appendDataByColumn(self, func):
|
||||
row = self.rowCount()
|
||||
|
||||
self.insertRow(row)
|
||||
|
||||
for column in range(self.columnCount()):
|
||||
item = QTableWidgetItem(func(column))
|
||||
|
||||
item.setFont(QFont(APP().customFontName))
|
||||
item.setFlags(
|
||||
QtCore.Qt.ItemFlag.ItemIsEnabled | QtCore.Qt.ItemFlag.ItemIsSelectable
|
||||
)
|
||||
|
||||
self.setItem(row, column, item)
|
||||
|
||||
def connectedCallback(self):
|
||||
self.setSelectionColor(Color.LIGHT_RED_)
|
||||
|
||||
def disconnectedCallback(self):
|
||||
self.setSelectionColor(Color.LIGHT_BLUE)
|
||||
|
||||
def retranslate(self):
|
||||
self.setHorizontalHeaderLabels(
|
||||
list(_(label) for label in EditSubsTableWidget.HEADER_LABEL)
|
||||
)
|
||||
|
||||
|
||||
class AddSubsDialog(Dialog):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setWindowTitle(_('Add subscription'))
|
||||
self.setWindowIcon(bootstrapIcon('rocket-takeoff-window.svg'))
|
||||
|
||||
self.remarkText = QLabel(_('Enter subscription remark:'))
|
||||
self.remarkEdit = QLineEdit()
|
||||
|
||||
self.webURLText = QLabel(_('Enter subscription URL:'))
|
||||
self.webURLEdit = QLineEdit()
|
||||
|
||||
self.dialogBtns = QDialogButtonBox(QtCore.Qt.Orientation.Horizontal)
|
||||
|
||||
self.dialogBtns.addButton(_('OK'), QDialogButtonBox.ButtonRole.AcceptRole)
|
||||
self.dialogBtns.addButton(_('Cancel'), QDialogButtonBox.ButtonRole.RejectRole)
|
||||
self.dialogBtns.accepted.connect(self.accept)
|
||||
self.dialogBtns.rejected.connect(self.reject)
|
||||
|
||||
layout = QFormLayout()
|
||||
|
||||
layout.addRow(self.remarkText)
|
||||
layout.addRow(self.remarkEdit)
|
||||
layout.addRow(self.webURLText)
|
||||
layout.addRow(self.webURLEdit)
|
||||
layout.addRow(self.dialogBtns)
|
||||
layout.setFormAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def setWidthAndHeight(self):
|
||||
self.setGeometry(100, 100, 456, 150)
|
||||
|
||||
def subscriptionRemark(self):
|
||||
return self.remarkEdit.text()
|
||||
|
||||
def subscriptionWebURL(self):
|
||||
return self.webURLEdit.text()
|
||||
|
||||
def retranslate(self):
|
||||
with StateContext(self):
|
||||
self.setWindowTitle(_(self.windowTitle()))
|
||||
|
||||
self.remarkText.setText(_(self.remarkText.text()))
|
||||
self.webURLText.setText(_(self.webURLText.text()))
|
||||
|
||||
for button in self.dialogBtns.buttons():
|
||||
button.setText(_(button.text()))
|
||||
|
||||
moveToCenter(self)
|
||||
|
||||
|
||||
class EditSubscriptionWidget(MainWindow):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.setWindowTitle(_('Edit Subscription'))
|
||||
self.setWindowIcon(bootstrapIcon('rocket-takeoff-window.svg'))
|
||||
|
||||
self.addSubsDialog = AddSubsDialog()
|
||||
|
||||
try:
|
||||
self.StorageObj = SubscriptionStorage.toObject(APP().CustomSubscription)
|
||||
# Check model. Shallow copy
|
||||
self.SubscriptionList = self.StorageObj['model']
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
self.StorageObj = SubscriptionStorage.init()
|
||||
# Shallow copy
|
||||
self.SubscriptionList = self.StorageObj['model']
|
||||
|
||||
# Clear it
|
||||
SubscriptionStorage.clear()
|
||||
|
||||
self.editSubsTableWidget = EditSubsTableWidget(parent=self)
|
||||
|
||||
self.editorTab = TabWidget(self)
|
||||
self.editorTab.addTab(self.editSubsTableWidget, _('Subscription List'))
|
||||
|
||||
# Buttons
|
||||
self.addButton = PushButton(_('Add'))
|
||||
self.addButton.clicked.connect(lambda: self.addSubscription())
|
||||
self.deleteButton = PushButton(_('Delete'))
|
||||
self.deleteButton.clicked.connect(lambda: self.deleteSelectedItem())
|
||||
|
||||
# Button Layout
|
||||
self.buttonWidget = QWidget()
|
||||
self.buttonWidgetLayout = QGridLayout(parent=self.buttonWidget)
|
||||
self.buttonWidgetLayout.addWidget(self.addButton, 0, 0)
|
||||
self.buttonWidgetLayout.addWidget(self.deleteButton, 0, 1)
|
||||
|
||||
self.fakeCentralWidget = QWidget()
|
||||
self.fakeCentralWidgetLayout = QVBoxLayout(self.fakeCentralWidget)
|
||||
self.fakeCentralWidgetLayout.addWidget(self.editorTab)
|
||||
self.fakeCentralWidgetLayout.addWidget(self.buttonWidget)
|
||||
|
||||
self.setCentralWidget(self.fakeCentralWidget)
|
||||
|
||||
def setWidthAndHeight(self):
|
||||
try:
|
||||
self.setGeometry(
|
||||
100,
|
||||
100,
|
||||
*list(
|
||||
int(size) for size in APP().SubscriptionWidgetWindowSize.split(',')
|
||||
),
|
||||
)
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
self.setGeometry(100, 100, 360 * GOLDEN_RATIO, 360)
|
||||
|
||||
def addSubscription(self):
|
||||
choice = self.addSubsDialog.exec()
|
||||
|
||||
if choice == QDialog.DialogCode.Accepted.value:
|
||||
subscriptionRemark = self.addSubsDialog.subscriptionRemark()
|
||||
subscriptionWebURL = self.addSubsDialog.subscriptionWebURL()
|
||||
|
||||
if subscriptionRemark:
|
||||
subscription = {
|
||||
'remark': subscriptionRemark,
|
||||
'webURL': subscriptionWebURL,
|
||||
# Unique id. Used by display and deletion
|
||||
'unique': str(uuid.uuid4()),
|
||||
}
|
||||
|
||||
self.appendDataByColumn(
|
||||
lambda column: EditSubsTableWidget.HEADER_LABEL_GET_FUNC[column](
|
||||
subscription
|
||||
)
|
||||
)
|
||||
|
||||
self.SubscriptionList.append(subscription)
|
||||
|
||||
# Sync it
|
||||
SubscriptionStorage.sync()
|
||||
else:
|
||||
# Do nothing
|
||||
pass
|
||||
|
||||
def appendDataByColumn(self, func):
|
||||
self.editSubsTableWidget.appendDataByColumn(func)
|
||||
|
||||
def deleteSelectedItem(self):
|
||||
self.editSubsTableWidget.deleteSelectedItem()
|
||||
|
||||
def syncSettings(self):
|
||||
APP().SubscriptionWidgetWindowSize = (
|
||||
f'{self.geometry().width()},{self.geometry().height()}'
|
||||
)
|
||||
|
||||
APP().SubscriptionWidgetSectionSizeTable = ujson.dumps(
|
||||
self.editSubsTableWidget.sectionSizeTable,
|
||||
ensure_ascii=False,
|
||||
escape_forward_slashes=False,
|
||||
)
|
||||
@@ -15,6 +15,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from Furious.Widget.Widget import Dialog
|
||||
from Furious.Utility.Utility import (
|
||||
StateContext,
|
||||
SupportConnectedCallback,
|
||||
@@ -24,7 +25,6 @@ from Furious.Utility.Translator import Translatable, gettext as _
|
||||
|
||||
from PySide6 import QtCore
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QFormLayout,
|
||||
QLabel,
|
||||
@@ -32,7 +32,7 @@ from PySide6.QtWidgets import (
|
||||
)
|
||||
|
||||
|
||||
class IndentSpinBox(Translatable, SupportConnectedCallback, QDialog):
|
||||
class IndentSpinBox(Dialog):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
@@ -63,12 +63,6 @@ class IndentSpinBox(Translatable, SupportConnectedCallback, QDialog):
|
||||
def value(self):
|
||||
return self.indentSpin.value()
|
||||
|
||||
def connectedCallback(self):
|
||||
self.setWindowIcon(bootstrapIcon('rocket-takeoff-connected-dark.svg'))
|
||||
|
||||
def disconnectedCallback(self):
|
||||
self.setWindowIcon(bootstrapIcon('rocket-takeoff-window.svg'))
|
||||
|
||||
def retranslate(self):
|
||||
with StateContext(self):
|
||||
self.setWindowTitle(_(self.windowTitle()))
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from Furious.Widget.Widget import GroupBox, Label
|
||||
from Furious.Widget.Widget import Dialog, GroupBox, Label
|
||||
from Furious.Utility.Constants import (
|
||||
APP,
|
||||
PLATFORM,
|
||||
@@ -36,7 +36,6 @@ from PySide6 import QtCore
|
||||
from PySide6.QtWidgets import (
|
||||
QCheckBox,
|
||||
QComboBox,
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QFormLayout,
|
||||
QGridLayout,
|
||||
@@ -45,7 +44,7 @@ from PySide6.QtWidgets import (
|
||||
)
|
||||
|
||||
|
||||
class TorRelaySettingsWidget(Translatable, SupportConnectedCallback, QDialog):
|
||||
class TorRelaySettingsWidget(Dialog):
|
||||
TOR_LOG_LEVEL = ['err', 'warn', 'notice', 'info', 'debug']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -227,12 +226,6 @@ class TorRelaySettingsWidget(Translatable, SupportConnectedCallback, QDialog):
|
||||
self.restoreValueFromObject()
|
||||
self.hide()
|
||||
|
||||
def connectedCallback(self):
|
||||
self.setWindowIcon(bootstrapIcon('rocket-takeoff-connected-dark.svg'))
|
||||
|
||||
def disconnectedCallback(self):
|
||||
self.setWindowIcon(bootstrapIcon('rocket-takeoff-window.svg'))
|
||||
|
||||
def retranslate(self):
|
||||
with StateContext(self):
|
||||
self.setWindowTitle(_(self.windowTitle()))
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from Furious.Gui.Action import Action, Seperator
|
||||
from Furious.Utility.Constants import APP, Color
|
||||
from Furious.Utility.Constants import APP, PLATFORM, Color
|
||||
from Furious.Utility.Utility import (
|
||||
StateContext,
|
||||
SupportConnectedCallback,
|
||||
@@ -29,6 +29,7 @@ from Furious.Utility.Translator import Translatable, gettext as _
|
||||
from PySide6 import QtCore
|
||||
from PySide6.QtGui import QBrush, QColor, QFont, QTextCursor
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog,
|
||||
QGroupBox,
|
||||
QHeaderView,
|
||||
QLabel,
|
||||
@@ -49,6 +50,44 @@ from PySide6.QtWidgets import (
|
||||
import functools
|
||||
|
||||
|
||||
class Dialog(Translatable, SupportConnectedCallback, QDialog):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if PLATFORM != 'Darwin':
|
||||
self.setWidthAndHeight()
|
||||
|
||||
def setWidthAndHeight(self):
|
||||
pass
|
||||
|
||||
def exec(self):
|
||||
self.show()
|
||||
|
||||
return super().exec()
|
||||
|
||||
def open(self):
|
||||
self.show()
|
||||
|
||||
return super().open()
|
||||
|
||||
def show(self):
|
||||
super().show()
|
||||
|
||||
if PLATFORM == 'Darwin':
|
||||
self.setWidthAndHeight()
|
||||
|
||||
moveToCenter(self)
|
||||
|
||||
def connectedCallback(self):
|
||||
self.setWindowIcon(bootstrapIcon('rocket-takeoff-connected-dark.svg'))
|
||||
|
||||
def disconnectedCallback(self):
|
||||
self.setWindowIcon(bootstrapIcon('rocket-takeoff-window.svg'))
|
||||
|
||||
def retranslate(self):
|
||||
pass
|
||||
|
||||
|
||||
class GroupBox(Translatable, QGroupBox):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -123,9 +162,23 @@ class MainWindow(Translatable, SupportConnectedCallback, NeedSyncSettings, QMain
|
||||
self._menuBar = MenuBar(parent=self)
|
||||
self.setMenuBar(self._menuBar)
|
||||
|
||||
if PLATFORM != 'Darwin':
|
||||
self.setWidthAndHeight()
|
||||
|
||||
def syncSettings(self):
|
||||
pass
|
||||
|
||||
def setWidthAndHeight(self):
|
||||
pass
|
||||
|
||||
def show(self):
|
||||
super().show()
|
||||
|
||||
if PLATFORM == 'Darwin':
|
||||
self.setWidthAndHeight()
|
||||
|
||||
moveToCenter(self)
|
||||
|
||||
def closeEvent(self, event):
|
||||
event.ignore()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user