mirror of
https://github.com/LorenEteval/Furious.git
synced 2026-10-03 04:17:58 +03:00
Refine external core editor guidance
Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
@@ -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,
|
||||
|
||||
Vendored
+32
-32
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user