diff --git a/Furious/Window/HomePage.py b/Furious/Window/HomePage.py index 763d38ca..5f26ffd9 100644 --- a/Furious/Window/HomePage.py +++ b/Furious/Window/HomePage.py @@ -295,6 +295,7 @@ class NetworkStateBadge(Mixins.QTranslatable, Mixins.ThemeAware, QFrame): """Publish size constraints for the current responsive presentation.""" if self._profileDetailsVisible: requiredWidth = self.widthWithProfile() + self.setMinimumWidth(requiredWidth) self.setMaximumWidth(requiredWidth) else: @@ -751,8 +752,8 @@ class HomePage(Mixins.QTranslatable, QMainWindow): self.searchButton = SearchButton() self.subscriptionFilterComboBox = AppQComboBox() + self.subscriptionFilterComboBox.setContentWidthAdjustable() self.subscriptionFilterComboBox.setMinimumWidth(190) - self.subscriptionFilterComboBox.setMaximumWidth(300) self.subscriptionFilterComboBox.setSizePolicy( QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed, diff --git a/Furious/Window/LogPage.py b/Furious/Window/LogPage.py index 1814f0fd..c5b1cc72 100644 --- a/Furious/Window/LogPage.py +++ b/Furious/Window/LogPage.py @@ -156,6 +156,7 @@ class LogPage(Mixins.QTranslatable, QMainWindow): # metadata, so LogPage rebuilds this application-styled selector with # the correct per-category translation policy. self.filterComboBox = AppQComboBox(translatable=False) + self.filterComboBox.setContentWidthAdjustable() self.filterComboBox.setMinimumWidth(180) self.textBrowser = DraculaTextBrowser( diff --git a/Furious/Window/MetricsPage.py b/Furious/Window/MetricsPage.py index 3f60cbdc..3dd1598b 100644 --- a/Furious/Window/MetricsPage.py +++ b/Furious/Window/MetricsPage.py @@ -94,12 +94,12 @@ class _TrafficMetricsCard(QFrame): AppQLabel(_('Granularity'), parent=self), ) - self.timeRangeComboBox, self.granularityComboBox = ( - AppQComboBox(parent=self), - AppQComboBox(parent=self), - ) - + self.timeRangeComboBox = AppQComboBox(parent=self) + self.timeRangeComboBox.setContentWidthAdjustable() self.timeRangeComboBox.setMinimumWidth(160) + + self.granularityComboBox = AppQComboBox(parent=self) + self.granularityComboBox.setContentWidthAdjustable() self.granularityComboBox.setMinimumWidth(130) self.downloadTitleLabel, self.uploadTitleLabel = ( diff --git a/Furious/Window/SettingsPage.py b/Furious/Window/SettingsPage.py index adabe116..b793cf29 100644 --- a/Furious/Window/SettingsPage.py +++ b/Furious/Window/SettingsPage.py @@ -469,6 +469,7 @@ class _ApplicationThemeSettingsCard(_SettingsCard): """Initialize the translated application-theme selector.""" self.comboBox = AppQComboBox() self.comboBox.setObjectName('SettingsComboBox') + self.comboBox.setContentWidthAdjustable() self.comboBox.setMinimumWidth(260) for label, preference in self.Options: @@ -522,6 +523,7 @@ class _SystemProxySettingsCard(_SettingsCard): """Initialize the translated system-proxy mode selector.""" self.comboBox = AppQComboBox() self.comboBox.setObjectName('SettingsComboBox') + self.comboBox.setContentWidthAdjustable() self.comboBox.setMinimumWidth(260) for label, mode in self.Options: diff --git a/Furious/Window/SubscriptionPage.py b/Furious/Window/SubscriptionPage.py index b70ef122..3926e293 100644 --- a/Furious/Window/SubscriptionPage.py +++ b/Furious/Window/SubscriptionPage.py @@ -84,22 +84,32 @@ class _SubscriptionEditorDialog(AppQTransientDialog): subscription = dict(subscription or {}) - self.remarkEdit = AppQLineEdit(subscription.get('remark', '')) - self.urlEdit = AppQLineEdit(subscription.get('webURL', '')) - self.enabledSwitch = AppQSwitch() - self.enabledSwitch.syncChecked(subscription.get('enabled', True)) - self.autoUpdateComboBox = AppQComboBox() - self.proxyComboBox = AppQComboBox() - self.userAgentEdit = AppQLineEdit(subscription.get('userAgent', '')) - self.filterEdit = AppQLineEdit(subscription.get('filter', '')) + ( + self.remarkEdit, + self.urlEdit, + self.enabledSwitch, + self.autoUpdateComboBox, + self.proxyComboBox, + self.userAgentEdit, + self.filterEdit, + ) = ( + AppQLineEdit(subscription.get('remark', '')), + AppQLineEdit(subscription.get('webURL', '')), + AppQSwitch(), + AppQComboBox(), + AppQComboBox(), + AppQLineEdit(subscription.get('userAgent', '')), + AppQLineEdit(subscription.get('filter', '')), + ) self.remarkEdit.setMinimumWidth(240) self.remarkEdit.setMaximumWidth(360) self.urlEdit.setMinimumWidth(420) + self.enabledSwitch.syncChecked(subscription.get('enabled', True)) self.autoUpdateComboBox.setMinimumWidth(220) - self.autoUpdateComboBox.setMaximumWidth(320) + self.autoUpdateComboBox.setContentWidthAdjustable() self.proxyComboBox.setMinimumWidth(220) - self.proxyComboBox.setMaximumWidth(320) + self.proxyComboBox.setContentWidthAdjustable() self.userAgentEdit.setMinimumWidth(260) self.filterEdit.setMinimumWidth(260) @@ -109,8 +119,10 @@ class _SubscriptionEditorDialog(AppQTransientDialog): for value in SubscriptionTableView.ProxyOptions: self.proxyComboBox.addItem(_(value), value) - autoIndex = self.autoUpdateComboBox.findData(subscription.get('autoupdate', '')) - proxyIndex = self.proxyComboBox.findData(subscription.get('proxy', '')) + autoIndex, proxyIndex = ( + self.autoUpdateComboBox.findData(subscription.get('autoupdate', '')), + self.proxyComboBox.findData(subscription.get('proxy', '')), + ) self.autoUpdateComboBox.setCurrentIndex(max(autoIndex, 0)) self.proxyComboBox.setCurrentIndex(max(proxyIndex, 0)) @@ -236,20 +248,35 @@ class SubscriptionPage(Mixins.QTranslatable, Mixins.ThemeAware, QMainWindow): super().__init__(parent) self.setObjectName('SubscriptionPage') + self.serverTable = serverTable + self.pageTitleLabel = AppQLabel(_('Subscriptions')) self.pageTitleLabel.setObjectName('SubscriptionPageTitle') + self.proxyLabel = AppQLabel(_('Update Using')) + self.proxyComboBox = AppQComboBox() + self.proxyComboBox.setContentWidthAdjustable() self.proxyComboBox.setMinimumWidth(160) - self.addButton = AppQPushButton(_('Add')) - self.editButton = AppQPushButton(_('Edit')) - self.deleteButton = AppQPushButton(_('Delete')) - self.copyURLButton = AppQPushButton(_('Copy URL')) - self.viewProfilesButton = AppQPushButton(_('View Profiles')) - self.updateSelectedButton = AppQPushButton(_('Update Selected')) - self.updateAllButton = AppQPushButton(_('Update All')) + ( + self.addButton, + self.editButton, + self.deleteButton, + self.copyURLButton, + self.viewProfilesButton, + self.updateSelectedButton, + self.updateAllButton, + ) = ( + AppQPushButton(_('Add')), + AppQPushButton(_('Edit')), + AppQPushButton(_('Delete')), + AppQPushButton(_('Copy URL')), + AppQPushButton(_('View Profiles')), + AppQPushButton(_('Update Selected')), + AppQPushButton(_('Update All')), + ) self.table = SubscriptionTableView( deleteUniqueCallback=self._deleteProfilesForSubscription, diff --git a/tests/test_ui_behavior.py b/tests/test_ui_behavior.py index 7a64df76..757ace52 100644 --- a/tests/test_ui_behavior.py +++ b/tests/test_ui_behavior.py @@ -55,10 +55,17 @@ from Furious.Controllers.ConnectionController import ( ConnectionState, ) from Furious.Controllers.SettingsController import ( + APPLICATION_THEME_SETTING, LOG_AUTO_CLEAR_SETTING, LOG_AUTO_SCROLL_DOWN_SETTING, ) -from Furious.Frozenlib import APPLICATION_NAME, AppSettings, Mixins +from Furious.Frozenlib import ( + APPLICATION_NAME, + AppBuiltinProxyMode, + ApplicationTheme, + AppSettings, + Mixins, +) from Furious.Models import ProfileMetadata, Protocol, ServerProfile from Furious.Plugins.API import RoutingOption from Furious.Qt import ( @@ -76,13 +83,23 @@ from Furious.Service import ( LogManager, ) from Furious.Window.LogPage import LogPage +from Furious.Window.SettingsPage import ( + _ApplicationThemeSettingsCard, + _SystemProxySettingsCard, +) from Furious.Window.SubscriptionPage import _SubscriptionEditorDialog from Furious.Widget.ConnectionButton import ConnectionButton from Furious.Widget.RoutingSelector import RoutingSelector from PySide6 import QtCore from PySide6.QtTest import QTest -from PySide6.QtWidgets import QHBoxLayout, QWidget +from PySide6.QtWidgets import ( + QComboBox, + QHBoxLayout, + QStyle, + QStyleOptionComboBox, + QWidget, +) from tests.support import ( application, @@ -144,6 +161,22 @@ class ComboTranslationLayoutTest(unittest.TestCase): """Finish deferred widget deletion between tests.""" collectAtBoundary() + def assertCurrentComboTextFits(self, comboBox): + """Assert the styled combo edit field can display its current text.""" + option = QStyleOptionComboBox() + comboBox.initStyleOption(option) + textRect = comboBox.style().subControlRect( + QStyle.ComplexControl.CC_ComboBox, + option, + QStyle.SubControl.SC_ComboBoxEditField, + comboBox, + ) + + self.assertGreaterEqual( + textRect.width(), + comboBox.fontMetrics().horizontalAdvance(comboBox.currentText()), + ) + def testContentAwareComboRefreshesHintWithoutChangingSemanticData(self): """Grow for longer translated text while retaining item identity.""" parent = QWidget() @@ -223,6 +256,64 @@ class ComboTranslationLayoutTest(unittest.TestCase): parent.close() parent.deleteLater() + def testTranslatedSettingsChoicesResizeWithoutChangingSemanticValues(self): + """Resize translated Settings choices without applying fake changes.""" + cases = ( + ( + _ApplicationThemeSettingsCard, + APPLICATION_THEME_SETTING, + ApplicationTheme.System.value, + 'Follow System Appearance', + 'setApplicationTheme', + ), + ( + _SystemProxySettingsCard, + 'SystemProxyMode', + AppBuiltinProxyMode.Auto.value, + 'Automatically Configure System Proxy', + 'setSystemProxyMode', + ), + ) + + with isolatedSettings(), mock.patch( + 'Furious.Window.SettingsPage.AppSettingsController' + ) as controllerFactory: + for cardType, settingName, semanticValue, sourceText, callbackName in cases: + AppSettings.set('Language', 'EN') + AppSettings.set(settingName, semanticValue) + parent = QWidget() + layout = QHBoxLayout(parent) + card = cardType(parent=parent) + layout.addWidget(card) + parent.resize(1800, 120) + parent.show() + processQtEvents() + + comboBox = card.comboBox + callback = getattr(controllerFactory.return_value, callbackName) + indexChanges = [] + comboBox.currentIndexChanged.connect(indexChanges.append) + + self.assertEqual( + comboBox.sizeAdjustPolicy(), + QComboBox.SizeAdjustPolicy.AdjustToContents, + ) + + for language in ('EN', 'ZH', 'RU'): + AppSettings.set('Language', language) + comboBox.retranslate() + processQtEvents() + + self.assertEqual(comboBox.currentData(), semanticValue) + self.assertEqual(comboBox.currentText(), _(sourceText, language)) + self.assertCurrentComboTextFits(comboBox) + + callback.assert_not_called() + self.assertEqual(indexChanges, []) + + parent.close() + parent.deleteLater() + class EditorMappingTest(unittest.TestCase): """Verify editor fields preserve structured configuration semantics."""