Refine external core editor guidance

Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Loren Eteval
2026-08-26 10:14:20 +08:00
parent abf35f9bcd
commit ffebf6e185
3 changed files with 126 additions and 43 deletions
+23 -11
View File
@@ -19,6 +19,7 @@
from __future__ import annotations
from Furious.Frozenlib import Mixins
from Furious.Interface import EditorWidgetBinding
from Furious.Models import CoreConfiguration, ServerProfile
from Furious.Qt import (
@@ -79,7 +80,7 @@ class ExternalCorePathInput(EditorWidgetBinding):
self._key = key
self._directory = directory
self._title = AppQLabel(_(title), translatable=True)
self._title = AppQLabel(_(title))
self._input = AppQLineEdit()
self._input.setPlaceholderText(_(placeholder) if placeholder else '')
self._browse = AppQPushButton(_('Browse...'))
@@ -159,9 +160,9 @@ class ExternalCoreArgumentsInput(EditorWidgetBinding):
"""Initialize the single-line command argument editor."""
super().__init__()
self._title = AppQLabel(_('Arguments'), translatable=True)
self._title = AppQLabel(_('Arguments'))
self._input = AppQLineEdit()
self._input.setPlaceholderText(_('Space-separated arguments'))
self._input.setPlaceholderText(_('e.g. --config config.json --verbose'))
def widgets(self):
"""Return the form-row label and line editor."""
@@ -203,6 +204,17 @@ class ExternalCoreArgumentsInput(EditorWidgetBinding):
)
class ExternalCoreEnvironmentTextEdit(Mixins.QTranslatable, QPlainTextEdit):
"""Provide a runtime-translatable environment placeholder."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def retranslate(self):
"""Refresh the translated placeholder without changing its contents."""
self.setPlaceholderText(_(self.placeholderText()))
class ExternalCoreEnvironmentInput(EditorWidgetBinding):
"""Bind KEY=VALUE lines to inherited-environment overrides."""
@@ -210,10 +222,10 @@ class ExternalCoreEnvironmentInput(EditorWidgetBinding):
"""Initialize the environment override editor."""
super().__init__()
self._title = AppQLabel(_('Environment Variables'), translatable=True)
self._input = QPlainTextEdit()
self._title = AppQLabel(_('Environment Variables'))
self._input = ExternalCoreEnvironmentTextEdit()
self._input.setStyleSheet('min-height: 120px; max-height: 136px;')
self._input.setPlaceholderText(_('KEY=VALUE, one per line'))
self._input.setPlaceholderText(_('One per line, e.g. KEY=VALUE'))
def widgets(self):
"""Return the form-row label and environment editor."""
@@ -268,7 +280,7 @@ class ExternalCoreShutdownTimeoutInput(GuiEditorItemTextSpinBox):
def __init__(self):
"""Initialize a one-to-sixty-second timeout input."""
super().__init__(title=_('Shutdown Timeout (seconds)'), translatable=True)
super().__init__(title=_('Shutdown Timeout (seconds)'))
self.setRange(1, 60)
@@ -297,7 +309,7 @@ class ExternalCoreApplicationTun2socksInput(GuiEditorItemTextSwitch):
def __init__(self):
"""Initialize the application tun2socks opt-in control."""
super().__init__(title=_('Use Application Tun2socks'), translatable=True)
super().__init__(title=_('Use Application Tun2socks'))
def connectToggled(self, receiver, methodName: str):
"""Connect a field-state callback without retaining its binding."""
@@ -319,9 +331,9 @@ class ExternalCoreTunRemoteAddressInput(EditorWidgetBinding):
"""Initialize a hostname-or-IP input independent of process paths."""
super().__init__()
self._title = AppQLabel(_('TUN Remote Address'), translatable=True)
self._title = AppQLabel(_('TUN Remote Address'))
self._input = AppQLineEdit()
self._input.setPlaceholderText(_('Hostname, IPv4, or IPv6 address'))
self._input.setPlaceholderText(_('Remote server hostname or IP address'))
def widgets(self):
"""Return the form-row label and remote-address editor."""
@@ -368,7 +380,7 @@ class ExternalCoreConfigurationGroup(GuiEditorWidgetQGroupBox):
_('Working Directory'),
'workingDirectory',
directory=True,
placeholder=_('Defaults to executable folder'),
placeholder=_('Leave empty to use executable directory'),
),
self._argumentsInput,
self._environmentInput,
+32 -32
View File
@@ -2797,14 +2797,6 @@ TRANSLATION = {
"ZH": "TUN远程地址",
"isReviewed": "True"
},
"Hostname, IPv4, or IPv6 address": {
"source": [
"Furious.Backends.ExternalCore.Editor"
],
"RU": "Имя узла, адрес IPv4 или IPv6",
"ZH": "主机名、IPv4或IPv6地址",
"isReviewed": "True"
},
"Arguments": {
"source": [
"Furious.Backends.ExternalCore.Editor"
@@ -2821,14 +2813,6 @@ TRANSLATION = {
"ZH": "环境变量",
"isReviewed": "True"
},
"KEY=VALUE, one per line": {
"source": [
"Furious.Backends.ExternalCore.Editor"
],
"RU": "KEY=VALUE, по одной на строку",
"ZH": "KEY=VALUE,每行一个",
"isReviewed": "True"
},
"Shutdown Timeout (seconds)": {
"source": [
"Furious.Backends.ExternalCore.Editor"
@@ -2837,22 +2821,6 @@ TRANSLATION = {
"ZH": "关闭超时(秒)",
"isReviewed": "True"
},
"Space-separated arguments": {
"source": [
"Furious.Backends.ExternalCore.Editor"
],
"RU": "Аргументы через пробел",
"ZH": "参数以空格分隔",
"isReviewed": "True"
},
"Defaults to executable folder": {
"source": [
"Furious.Backends.ExternalCore.Editor"
],
"RU": "Папка исполняемого файла",
"ZH": "默认使用可执行文件目录",
"isReviewed": "True"
},
"Arguments contain invalid quoting.": {
"source": [
"Furious.Backends.ExternalCore.Editor"
@@ -3228,5 +3196,37 @@ TRANSLATION = {
"RU": "Например: curl",
"ZH": "例如:curl",
"isReviewed": "True"
},
"e.g. --config config.json --verbose": {
"source": [
"Furious.Backends.ExternalCore.Editor"
],
"RU": "Например: --config config.json --verbose",
"ZH": "例如:--config config.json --verbose",
"isReviewed": "True"
},
"One per line, e.g. KEY=VALUE": {
"source": [
"Furious.Backends.ExternalCore.Editor"
],
"RU": "По одной на строку, например KEY=VALUE",
"ZH": "每行一个,例如 KEY=VALUE",
"isReviewed": "True"
},
"Remote server hostname or IP address": {
"source": [
"Furious.Backends.ExternalCore.Editor"
],
"RU": "Имя узла или IP-адрес удалённого сервера",
"ZH": "远程服务器的主机名或IP地址",
"isReviewed": "True"
},
"Leave empty to use executable directory": {
"source": [
"Furious.Backends.ExternalCore.Editor"
],
"RU": "Оставьте пустым для каталога исполняемого файла",
"ZH": "留空则使用可执行文件目录",
"isReviewed": "True"
}
}
+71
View File
@@ -1050,6 +1050,77 @@ class EditorMappingTest(unittest.TestCase):
editor.close()
def testExternalCorePlaceholdersRetranslateWithoutChangingValues(self):
"""Translate field guidance without persisting it as configuration."""
configuration = ConfigExternalCore(copy.deepcopy(BLANK_CONFIG_EXTERNAL_CORE))
configuration.update(
{
'arguments': ['--config', 'fixture.json', '--verbose'],
'environment': {'TOKEN': 'fixture'},
'useApplicationTun2socks': True,
'tunRemoteAddress': 'server.example.com',
}
)
profile = ServerProfile.fromConfiguration(
configuration,
ProfileMetadata(displayName='Fixture core'),
)
with isolatedSettings():
AppSettings.set('Language', 'EN')
editor = ExternalCoreEditor()
editor.factoryToInput(profile)
original = copy.deepcopy(profile.connection)
basicGroup = editor.groupBoxSequence()[0]
workingDirectoryInput = basicGroup._containers[2]._input
placeholderInputs = (
(
workingDirectoryInput,
'Leave empty to use executable directory',
),
(
editor._argumentsInput._input,
'e.g. --config config.json --verbose',
),
(
editor._environmentInput._input,
'One per line, e.g. KEY=VALUE',
),
(
editor._tunRemoteAddressInput._input,
'Remote server hostname or IP address',
),
)
for widget, sourceText in placeholderInputs:
self.assertEqual(widget.placeholderText(), sourceText)
for language in ('ZH', 'RU', 'EN'):
AppSettings.set('Language', language)
Mixins.QTranslatable.retranslateAll()
for widget, sourceText in placeholderInputs:
self.assertEqual(widget.placeholderText(), _(sourceText, language))
self.assertEqual(
editor._argumentsInput.values(),
['--config', 'fixture.json', '--verbose'],
)
self.assertEqual(
editor._environmentInput.values(),
{'TOKEN': 'fixture'},
)
self.assertEqual(
editor._tunRemoteAddressInput.text(),
'server.example.com',
)
self.assertFalse(editor.inputToFactory(profile))
self.assertEqual(profile.connection, original)
self.assertEqual(workingDirectoryInput.text(), '')
editor.close()
def testExternalCoreSectionUsesCanonicalEditorInset(self):
"""Use canonical editor grids and insets for both External Core groups."""
editor = ExternalCoreEditor()