Make routing selector fit translated content

Add opt-in content-aware sizing to AppQComboBox and refresh its geometry
after retranslation. Apply it to the routing selector while preserving
the semantic selection and avoiding synthetic routing changes.

Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Loren Eteval
2026-08-22 21:28:09 +08:00
parent 89f7403b47
commit 35e579b686
3 changed files with 143 additions and 1 deletions
+18
View File
@@ -284,6 +284,22 @@ class AppQComboBox(Mixins.QTranslatable, QComboBox):
self._themedSeparatorDelegate = None
def setContentWidthAdjustable(self, adjustable: bool = True):
"""Opt into content-aware width hints without imposing a fixed width."""
policy = (
QComboBox.SizeAdjustPolicy.AdjustToContents
if adjustable
else QComboBox.SizeAdjustPolicy.AdjustToContentsOnFirstShow
)
self.setSizeAdjustPolicy(policy)
self.refreshContentGeometry()
def refreshContentGeometry(self):
"""Notify the owning layout that changed item text may need more width."""
self.updateGeometry()
self.view().updateGeometry()
def enableThemedSeparators(self):
"""Theme real insertSeparator() rows while preserving native items."""
if self._themedSeparatorDelegate is not None:
@@ -299,6 +315,8 @@ class AppQComboBox(Mixins.QTranslatable, QComboBox):
for index in range(self.count()):
self.setItemText(index, _(self.itemText(index)))
self.refreshContentGeometry()
class AppQDialog(Mixins.QTranslatable, Mixins.ConnectionAware, QDialog):
"""Present the app Qt dialog."""
+2 -1
View File
@@ -43,8 +43,8 @@ class RoutingSelector(AppQComboBox):
self.setObjectName('HomeRoutingSelector')
self.enableThemedSeparators()
self.setContentWidthAdjustable()
self.setMinimumWidth(180)
self.setMaximumWidth(280)
self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
self.setAccessibleName(_('Routing'))
self.setToolTip(_('Routing'))
@@ -76,6 +76,7 @@ class RoutingSelector(AppQComboBox):
self.setCurrentIndex(index if index >= 0 else 0)
self.refreshContentGeometry()
self.setVisible(bool(options))
self._applyInteractionEnabled(self.controller.interactionEnabled)
+123
View File
@@ -101,6 +101,129 @@ import weakref
from unittest import mock
class RoutingControllerFixture(QtCore.QObject):
"""Provide routing state without persistence or connection side effects."""
stateChanged = QtCore.Signal(object, str)
interactionEnabledChanged = QtCore.Signal(bool)
def __init__(self, options, routing):
"""Initialize one immutable selector state."""
super().__init__()
self._options = tuple(options)
self._routing = routing
self.interactionEnabled = True
self.selected = []
def state(self):
"""Return the current options and semantic selection."""
return self._options, self._routing
def refresh(self, *, force=False):
"""Return state without consulting plugins or reconnecting."""
if force:
self.stateChanged.emit(*self.state())
return self.state()
def selectRouting(self, routing):
"""Record explicit user selection attempts."""
self.selected.append(routing)
class ComboTranslationLayoutTest(unittest.TestCase):
"""Verify translated content can republish useful combo width hints."""
@classmethod
def setUpClass(cls):
"""Create the process-wide headless QApplication."""
application()
def tearDown(self):
"""Finish deferred widget deletion between tests."""
collectAtBoundary()
def testContentAwareComboRefreshesHintWithoutChangingSemanticData(self):
"""Grow for longer translated text while retaining item identity."""
parent = QWidget()
layout = QHBoxLayout(parent)
combo = AppQComboBox(parent=parent)
combo.setContentWidthAdjustable()
combo.addItem('Short', 'semantic-route')
layout.addWidget(combo)
shortHint = combo.sizeHint().width()
translated = 'A significantly longer translated routing option'
with mock.patch(
'Furious.Qt.QtWidgets._',
side_effect=lambda text: translated if text == 'Short' else text,
):
combo.retranslate()
parent.resize(combo.sizeHint().width() + 100, 100)
parent.show()
processQtEvents()
self.assertEqual(combo.currentData(), 'semantic-route')
self.assertEqual(combo.currentText(), translated)
self.assertGreater(combo.sizeHint().width(), shortHint)
self.assertGreater(
combo.sizeHint().width(),
combo.fontMetrics().horizontalAdvance(translated),
)
self.assertGreaterEqual(combo.width(), combo.sizeHint().width())
parent.close()
parent.deleteLater()
def testRoutingRetranslationKeepsSelectionAndDoesNotReconnect(self):
"""Rebuild display text without forwarding a synthetic selection."""
option = RoutingOption(
'bypass-mainland',
'Bypass Mainland China',
translatable=True,
)
controller = RoutingControllerFixture((option,), option.id)
parent = QWidget()
layout = QHBoxLayout(parent)
with mock.patch(
'Furious.Widget.RoutingSelector.AppRoutingController',
return_value=controller,
):
selector = RoutingSelector(parent=parent)
layout.addWidget(selector)
initialHint = selector.sizeHint().width()
translated = 'Обходить подключения к серверам материкового Китая'
with mock.patch(
'Furious.Widget.RoutingSelector._',
side_effect=lambda text: (
translated if text == option.displayName else text
),
):
selector.retranslate()
parent.resize(selector.sizeHint().width() + 100, 100)
parent.show()
processQtEvents()
self.assertEqual(selector.currentData(), option.id)
self.assertEqual(selector.currentText(), translated)
self.assertGreater(selector.sizeHint().width(), initialHint)
self.assertGreater(
selector.sizeHint().width(),
selector.fontMetrics().horizontalAdvance(translated),
)
self.assertEqual(controller.selected, [])
parent.close()
parent.deleteLater()
class EditorMappingTest(unittest.TestCase):
"""Verify editor fields preserve structured configuration semantics."""