Refactor title bar theming logic; introduce helper methods for consistent application across windows and dialogs

This commit is contained in:
2025-07-31 16:05:51 +02:00
parent 21dd3e7ba4
commit cb79b5d6e1
+139 -63
View File
@@ -2468,6 +2468,13 @@ class MainWindow(QtWidgets.QMainWindow):
Apply dark mode to the window title bar.
Works on Windows 10/11, macOS, and some Linux desktop environments.
"""
return self._apply_title_bar_theme_to_window(self, enable_dark)
def _apply_title_bar_theme_to_window(self, window, enable_dark=True):
"""
Apply title bar theme to a specific window (can be main window or dialog).
This is a helper method that can be used for any QWidget.
"""
try:
import platform
system = platform.system().lower()
@@ -2479,7 +2486,7 @@ class MainWindow(QtWidgets.QMainWindow):
from ctypes import wintypes
# Get window handle
hwnd = int(self.winId())
hwnd = int(window.winId())
# Define constants for Windows API
DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19
@@ -2504,11 +2511,11 @@ class MainWindow(QtWidgets.QMainWindow):
)
if result == 0: # S_OK
logger.debug(f"Applied Windows {'dark' if enable_dark else 'light'} title bar ({'newer' if use_newer_api else 'legacy'} API)")
logger.debug(f"Applied Windows {'dark' if enable_dark else 'light'} title bar to {window.__class__.__name__}")
# Force immediate window redraw
self.update()
self.repaint()
window.update()
window.repaint()
return True
else:
@@ -2527,9 +2534,9 @@ class MainWindow(QtWidgets.QMainWindow):
ctypes.sizeof(ctypes.c_int)
)
if result == 0:
logger.debug(f"Applied Windows {'dark' if enable_dark else 'light'} title bar (fallback API)")
self.update()
self.repaint()
logger.debug(f"Applied Windows {'dark' if enable_dark else 'light'} title bar to {window.__class__.__name__} (fallback API)")
window.update()
window.repaint()
return True
except Exception as fallback_e:
@@ -2590,33 +2597,24 @@ class MainWindow(QtWidgets.QMainWindow):
# Apply title bar theming immediately
self.apply_dark_title_bar(is_dark)
# Also apply to any open dialogs immediately
for dialog in QtWidgets.QApplication.allWidgets():
if isinstance(dialog, QtWidgets.QDialog) and dialog.isVisible():
# Apply to ALL widgets in the application (including dialogs that aren't currently visible)
for widget in QtWidgets.QApplication.allWidgets():
if isinstance(widget, (QtWidgets.QDialog, QtWidgets.QMainWindow)):
try:
hwnd = int(dialog.winId())
if hwnd:
import ctypes
DWMWA_USE_IMMERSIVE_DARK_MODE = 20
DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19
# Apply title bar theme to each window/dialog
self._apply_title_bar_theme_to_window(widget, is_dark)
# Try both API versions
for attribute in [DWMWA_USE_IMMERSIVE_DARK_MODE, DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1]:
try:
result = ctypes.windll.dwmapi.DwmSetWindowAttribute(
hwnd,
attribute,
ctypes.byref(ctypes.c_int(1 if is_dark else 0)),
ctypes.sizeof(ctypes.c_int)
)
if result == 0:
dialog.update()
dialog.repaint()
break
except:
continue
except:
pass
# If it's a dialog, also force its stylesheet update
if isinstance(widget, QtWidgets.QDialog) and hasattr(widget, 'parent_window'):
if widget.parent_window == self: # Only update dialogs that belong to this main window
widget.setStyleSheet(self._get_dialog_stylesheet())
widget.update()
widget.repaint()
except Exception as e:
logger.debug(f"Failed to apply theme to {widget.__class__.__name__}: {e}")
# Store the current theme state globally so new windows can pick it up
QtWidgets.QApplication.instance().setProperty('dark_theme_enabled', is_dark)
# Main window style
main_style = f"""
@@ -2732,6 +2730,91 @@ class MainWindow(QtWidgets.QMainWindow):
self.gpu_label.setStyleSheet(f'font-size: 9pt; color: {colors["text_light"]};')
self.performance_label.setStyleSheet(f'font-size: 9pt; color: {colors["text_light"]};')
def _get_dialog_stylesheet(self):
"""
Get the stylesheet for dialogs to match the main window theme.
"""
colors = self.get_theme_styles()
return f"""
QDialog {{
background-color: {colors['background']};
color: {colors['foreground']};
}}
QGroupBox {{
font-size: 9pt;
font-weight: bold;
border: 1px solid {colors['border']};
border-radius: 5px;
margin-top: 10px;
padding-top: 5px;
color: {colors['foreground']};
}}
QGroupBox::title {{
subcontrol-origin: margin;
left: 10px;
padding: 0 5px 0 5px;
color: {colors['foreground']};
}}
QLabel {{
font-size: 9pt;
color: {colors['foreground']};
}}
QComboBox {{
font-size: 9pt;
min-height: 20px;
padding: 2px 6px;
min-width: 90px;
background-color: {colors['input_bg']};
border: 1px solid {colors['border']};
border-radius: 3px;
color: {colors['foreground']};
}}
QComboBox::drop-down {{
border: none;
width: 20px;
subcontrol-origin: padding;
subcontrol-position: center right;
}}
QComboBox::down-arrow {{
image: none;
width: 0px;
height: 0px;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 6px solid {colors['foreground']};
margin: 0px;
padding: 0px;
}}
QComboBox::down-arrow:hover {{
border-top-color: {colors['accent']};
}}
QComboBox QAbstractItemView {{
background-color: {colors['input_bg']};
border: 1px solid {colors['border']};
border-radius: 3px;
color: {colors['foreground']};
selection-background-color: {colors['accent']};
selection-color: white;
}}
QPushButton {{
font-size: 9pt;
min-height: 20px;
padding: 2px 4px;
min-width: 70px;
background-color: {colors['button_bg']};
border: 1px solid {colors['border']};
border-radius: 3px;
color: {colors['foreground']};
}}
QPushButton:hover {{
background-color: {colors['button_hover']};
}}
QPushButton:pressed {{
background-color: {colors['border']};
}}
"""
def on_theme_changed(self, theme_text):
"""
Handle theme dropdown selection change.
@@ -2845,30 +2928,9 @@ class MainWindow(QtWidgets.QMainWindow):
try:
dialog = SettingsDialog(self)
# Apply title bar theming to the dialog
# Apply title bar theming to the dialog immediately
is_dark = self.get_effective_dark_mode()
try:
hwnd = int(dialog.winId())
if hwnd:
import ctypes
DWMWA_USE_IMMERSIVE_DARK_MODE = 20
DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19
# Try both API versions
for attribute in [DWMWA_USE_IMMERSIVE_DARK_MODE, DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1]:
try:
result = ctypes.windll.dwmapi.DwmSetWindowAttribute(
hwnd,
attribute,
ctypes.byref(ctypes.c_int(1 if is_dark else 0)),
ctypes.sizeof(ctypes.c_int)
)
if result == 0: # S_OK
break
except:
continue
except:
pass # Ignore title bar theming errors
self._apply_title_bar_theme_to_window(dialog, is_dark)
dialog.exec()
@@ -2879,14 +2941,28 @@ class MainWindow(QtWidgets.QMainWindow):
"""
Update the theme combo box text based on current theme mode and system state.
"""
if self.theme_mode == 'system':
system_dark = self.is_system_dark_theme()
display_text = f"System ({'Dark' if system_dark else 'Light'})"
# Update the first item's text to show current system theme
self.theme_combo.setItemText(0, display_text)
else:
# Reset system item text
self.theme_combo.setItemText(0, 'System')
# Temporarily disconnect the signal to prevent triggering theme changes
self.theme_combo.currentTextChanged.disconnect()
try:
if self.theme_mode == 'system':
system_dark = self.is_system_dark_theme()
display_text = f"System ({'Dark' if system_dark else 'Light'})"
# Update the first item's text to show current system theme
self.theme_combo.setItemText(0, display_text)
# Ensure the combo shows the correct selection
self.theme_combo.setCurrentIndex(0)
else:
# Reset system item text
self.theme_combo.setItemText(0, 'System')
# Set the correct selection based on theme mode
if self.theme_mode == 'light':
self.theme_combo.setCurrentIndex(1)
elif self.theme_mode == 'dark':
self.theme_combo.setCurrentIndex(2)
finally:
# Reconnect the signal
self.theme_combo.currentTextChanged.connect(self.on_theme_changed)
def init_ui(self):
"""