diff --git a/gui/config_tab.py b/gui/config_tab.py index 8175f60..e7f2324 100644 --- a/gui/config_tab.py +++ b/gui/config_tab.py @@ -200,6 +200,61 @@ class ConfigComboBox(QComboBox): ])) +class ConfigMenu(QMenu): + """Popup menu with the same fully opaque surface as config dropdowns.""" + + _BACKGROUND = QColor("#353539") + + def __init__(self, parent=None): + super().__init__(parent) + self.setStyleSheet(""" + QMenu { + background-color: #353539; + color: #f2f2f2; + border: 1px solid #56565b; + padding: 2px; + } + QMenu::item { + background-color: #353539; + color: #f2f2f2; + min-height: 26px; + padding: 3px 9px; + } + QMenu::item:selected { + background-color: #007acc; + color: #ffffff; + } + """) + self._apply_opaque_background() + + def _apply_opaque_background(self): + self.setWindowOpacity(1.0) + self.setAttribute(Qt.WA_TranslucentBackground, False) + self.setAttribute(Qt.WA_StyledBackground, True) + self.setAttribute(Qt.WA_OpaquePaintEvent, True) + self.setAutoFillBackground(True) + palette = self.palette() + palette.setColor(QPalette.Window, self._BACKGROUND) + palette.setColor(QPalette.Base, self._BACKGROUND) + palette.setColor(QPalette.Button, self._BACKGROUND) + palette.setColor(QPalette.Text, QColor("#f2f2f2")) + palette.setColor(QPalette.ButtonText, QColor("#f2f2f2")) + palette.setColor(QPalette.Highlight, QColor("#007acc")) + palette.setColor(QPalette.HighlightedText, QColor("#ffffff")) + self.setPalette(palette) + + def showEvent(self, event): + self._apply_opaque_background() + super().showEvent(event) + self._apply_opaque_background() + + def paintEvent(self, event): + painter = QPainter(self) + painter.fillRect(self.rect(), self._BACKGROUND) + painter.end() + super().paintEvent(event) + + class ApiKeyEditDialog(QDialog): """Dialog to create or overwrite a named API key (secret entered once).""" @@ -256,7 +311,7 @@ class ApiKeyEditDialog(QDialog): endpoint_preset_btn = QToolButton() endpoint_preset_btn.setText("Presets ▾") endpoint_preset_btn.setPopupMode(QToolButton.InstantPopup) - endpoint_menu = QMenu(endpoint_preset_btn) + endpoint_menu = ConfigMenu(endpoint_preset_btn) for label, url in ( ("OpenAI", "https://api.openai.com/v1"), ("Claude (Anthropic)", "https://api.anthropic.com/v1"), @@ -663,7 +718,7 @@ class ConfigTab(QWidget): self.api_url_preset_btn.setPopupMode(QToolButton.InstantPopup) self.api_url_preset_btn.setStyleSheet(btn_style) - api_url_menu = QMenu(self.api_url_preset_btn) + api_url_menu = ConfigMenu(self.api_url_preset_btn) for name, url in ( ("OpenAI", "https://api.openai.com/v1"), ("Claude (Anthropic)", "https://api.anthropic.com/v1"), diff --git a/tests/test_config_tab.py b/tests/test_config_tab.py index c038789..611e755 100644 --- a/tests/test_config_tab.py +++ b/tests/test_config_tab.py @@ -279,6 +279,33 @@ class ConfigTabRegressionTests(unittest.TestCase): self.assertFalse(popup.testAttribute(Qt.WA_TranslucentBackground)) tab.model_combo.hidePopup() + def test_presets_menu_is_opaque(self) -> None: + tab = self.make_tab() + window = QMainWindow() + self._windows.append(window) + window.setCentralWidget(tab) + window.resize(1280, 760) + window.show() + for _ in range(3): + self._app.processEvents() + + menu = tab.api_url_preset_btn.menu() + menu.popup( + tab.api_url_preset_btn.mapToGlobal( + tab.api_url_preset_btn.rect().bottomLeft() + ) + ) + for _ in range(3): + self._app.processEvents() + + self.assertTrue(menu.autoFillBackground()) + self.assertEqual(menu.palette().color(QPalette.Window).name(), "#353539") + self.assertEqual(menu.palette().color(QPalette.Base).name(), "#353539") + self.assertEqual(menu.windowOpacity(), 1.0) + self.assertFalse(menu.testAttribute(Qt.WA_TranslucentBackground)) + self.assertTrue(menu.testAttribute(Qt.WA_OpaquePaintEvent)) + menu.hide() + def test_save_and_reload_round_trip_for_every_option(self) -> None: tab = self.make_tab() tab.disconnect_auto_save()