From b5a74a7d7c014c22e250b3900c075ddb640ef25b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Tue, 26 Aug 2025 12:30:08 -0400 Subject: [PATCH 1/5] Add 'get_qcolor' to 'qthelpers' --- qtapputils/qthelpers.py | 44 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/qtapputils/qthelpers.py b/qtapputils/qthelpers.py index 2c89181..e72ed49 100644 --- a/qtapputils/qthelpers.py +++ b/qtapputils/qthelpers.py @@ -10,7 +10,7 @@ """Qt utilities""" from __future__ import annotations -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING, Callable, Any, Optional if TYPE_CHECKING: from qtpy.QtGui import QIcon from qtapputils.widgets.waitingspinner import WaitingSpinner @@ -23,7 +23,7 @@ from math import pi # ---- Third party imports -from qtpy.QtGui import QKeySequence +from qtpy.QtGui import QKeySequence, QColor, QPalette from qtpy.QtCore import QByteArray, Qt, QSize, QEventLoop, QTimer from qtpy.QtWidgets import ( QWidget, QSizePolicy, QToolButton, QApplication, QStyleFactory, QAction, @@ -40,6 +40,46 @@ def hexstate_to_qbytearray(hexstate): return QByteArray().fromHex(str(hexstate).encode('utf-8')) +def get_qcolor(color: QColor | tuple | list | str) -> QColor: + """ + Return a QColor from various input formats: + - QColor instance (returned as is) + - RGB or RGBA tuple/list of ints (e.g. (r,g,b) or (r,g,b,a)) + - RGB Hex string (e.g. '#FFAA00') + - Qt color name string (e.g. 'red', 'blue', 'lightgray') + + Parameters + ---------- + color : QColor, tuple/list, or str + The color specification. + + Returns + ------- + QColor + The corresponding QColor object. + + Raises + ------ + ValueError + If the color argument is not valid. + """ + # QColor instance + if isinstance(color, QColor): + return color + + # Tuple/list RGB(A) + if isinstance(color, (tuple, list)): + return QColor(*color) + + # String: hex code or color name + if isinstance(color, str): + # Accept hex (with '#') or color name + if color.startswith('#') or color in QColor.colorNames(): + return QColor(color) + raise ValueError(f"Unknown color string: {color!r}") + + raise ValueError(f"Cannot convert argument to QColor: {color!r}") + def create_mainwindow_toolbar( title: str, iconsize: int = None, areas: int = Qt.TopToolBarArea, movable: bool = False, floatable: bool = False, From f656c55c4a84324521aea3bb982947a55bc39660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Tue, 26 Aug 2025 12:30:21 -0400 Subject: [PATCH 2/5] Update test_qthelpers.py --- qtapputils/tests/test_qthelpers.py | 53 +++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/qtapputils/tests/test_qthelpers.py b/qtapputils/tests/test_qthelpers.py index e0e9bd0..f16a9b2 100644 --- a/qtapputils/tests/test_qthelpers.py +++ b/qtapputils/tests/test_qthelpers.py @@ -15,10 +15,12 @@ # ---- Third party imports from qtpy.QtCore import Qt, QTimer +from qtpy.QtGui import QColor import pytest # ---- Local imports -from qtapputils.qthelpers import format_tooltip, create_waitspinner, qtwait +from qtapputils.qthelpers import ( + format_tooltip, create_waitspinner, qtwait, get_qcolor) # ============================================================================= @@ -140,5 +142,54 @@ def test_qtwait_timeout(qtbot): assert "Timeout reached!" in str(excinfo.value) +def test_get_qcolor(): + """ + Test that get_qcolor correctly creates a QColor from various + input formats. + """ + # Test from QColor. + color = QColor(10, 20, 30) + result = get_qcolor(color) + assert isinstance(result, QColor) + assert result == color + + # Test from RGB tuple. + result = get_qcolor((100, 150, 200)) + assert isinstance(result, QColor) + assert result.red() == 100 + assert result.green() == 150 + assert result.blue() == 200 + + # Test from RGBA list. + result = get_qcolor([10, 20, 30, 40]) + assert isinstance(result, QColor) + assert result.red() == 10 + assert result.green() == 20 + assert result.blue() == 30 + assert result.alpha() == 40 + + # Test from HEX string. + result = get_qcolor("#336699") + assert isinstance(result, QColor) + assert result.red() == 51 + assert result.green() == 102 + assert result.blue() == 153 + + # Test from color name. + result = get_qcolor("red") + assert isinstance(result, QColor) + assert result.red() == 255 + assert result.green() == 0 + assert result.blue() == 0 + + # Test from invalid string. + with pytest.raises(ValueError): + get_qcolor("notacolor") + + # Test from invalid type. + with pytest.raises(ValueError): + get_qcolor(12345) + + if __name__ == "__main__": pytest.main(['-x', __file__, '-v', '-rw']) From 88646b60b5d29d386673bdf121624bea9c119584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Tue, 26 Aug 2025 12:30:30 -0400 Subject: [PATCH 3/5] Add set_widget_palette --- qtapputils/qthelpers.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/qtapputils/qthelpers.py b/qtapputils/qthelpers.py index e72ed49..d942c82 100644 --- a/qtapputils/qthelpers.py +++ b/qtapputils/qthelpers.py @@ -80,6 +80,39 @@ def get_qcolor(color: QColor | tuple | list | str) -> QColor: raise ValueError(f"Cannot convert argument to QColor: {color!r}") + +def set_widget_palette( + widget: QWidget, + bgcolor: Optional[Any] = None, + fgcolor: Optional[Any] = None): + """ + Set the background and foreground color of a QWidget. + + If colors are None, keeps the current palette value. Also enables + autoFillBackground for proper background rendering on most widgets. + + Parameters + ---------- + widget : QWidget + The widget whose palette will be set. + bgcolor : optional + Background color specification (QColor, tuple/list, or str). + fgcolor : optional + Foreground color specification (QColor, tuple/list, or str). + """ + palette = widget.palette() + if bgcolor is not None: + palette.setColor(widget.backgroundRole(), get_qcolor(bgcolor)) + + # Ensure background fills for most widgets. + widget.setAutoFillBackground(True) + + if fgcolor is not None: + palette.setColor(widget.foregroundRole(), get_qcolor(fgcolor)) + + widget.setPalette(palette) + + def create_mainwindow_toolbar( title: str, iconsize: int = None, areas: int = Qt.TopToolBarArea, movable: bool = False, floatable: bool = False, From 451d2b66403a36ad44df3f53e4de77da584a9077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Tue, 26 Aug 2025 12:34:53 -0400 Subject: [PATCH 4/5] Update test_qthelpers.py --- qtapputils/tests/test_qthelpers.py | 42 +++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/qtapputils/tests/test_qthelpers.py b/qtapputils/tests/test_qthelpers.py index f16a9b2..59a5675 100644 --- a/qtapputils/tests/test_qthelpers.py +++ b/qtapputils/tests/test_qthelpers.py @@ -14,13 +14,15 @@ from itertools import product # ---- Third party imports +from PyQt5.QtWidgets import QWidget from qtpy.QtCore import Qt, QTimer from qtpy.QtGui import QColor import pytest # ---- Local imports from qtapputils.qthelpers import ( - format_tooltip, create_waitspinner, qtwait, get_qcolor) + format_tooltip, create_waitspinner, qtwait, get_qcolor, + set_widget_palette) # ============================================================================= @@ -191,5 +193,43 @@ def test_get_qcolor(): get_qcolor(12345) +def test_set_widget_palette(qtbot): + """ + Test set_widget_palette for background, foreground, both, and None cases. + """ + # Background only + widget = QWidget() + set_widget_palette(widget, bgcolor=(10, 20, 30)) + + bg_color = widget.palette().color(widget.backgroundRole()) + assert isinstance(bg_color, QColor) + assert (bg_color.red(), bg_color.green(), bg_color.blue()) == (10, 20, 30) + assert widget.autoFillBackground() is True + + # Foreground only + widget = QWidget() + set_widget_palette(widget, fgcolor="#FF00AA") + + fg_color = widget.palette().color(widget.foregroundRole()) + assert isinstance(fg_color, QColor) + assert (fg_color.red(), fg_color.green(), fg_color.blue()) == (255, 0, 170) + + # Both background and foreground + widget = QWidget() + set_widget_palette(widget, bgcolor="blue", fgcolor="yellow") + + bg_color = widget.palette().color(widget.backgroundRole()) + fg_color = widget.palette().color(widget.foregroundRole()) + assert (bg_color.red(), bg_color.green(), bg_color.blue()) == (0, 0, 255) + assert (fg_color.red(), fg_color.green(), fg_color.blue()) == (255, 255, 0) + + # None for both + widget = QWidget() + orig_palette = widget.palette() + set_widget_palette(widget, bgcolor=None, fgcolor=None) + + assert widget.palette() == orig_palette + + if __name__ == "__main__": pytest.main(['-x', __file__, '-v', '-rw']) From c458edd5ac47cb94febdddcbaffc9c5e50d7c259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 28 Aug 2025 09:11:26 -0400 Subject: [PATCH 5/5] get_qcolor: add option to pass a color role --- qtapputils/qthelpers.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/qtapputils/qthelpers.py b/qtapputils/qthelpers.py index d942c82..56615ab 100644 --- a/qtapputils/qthelpers.py +++ b/qtapputils/qthelpers.py @@ -27,7 +27,7 @@ from qtpy.QtCore import QByteArray, Qt, QSize, QEventLoop, QTimer from qtpy.QtWidgets import ( QWidget, QSizePolicy, QToolButton, QApplication, QStyleFactory, QAction, - QToolBar) + QToolBar, QStyleOption) def qbytearray_to_hexstate(qba): @@ -45,7 +45,7 @@ def get_qcolor(color: QColor | tuple | list | str) -> QColor: Return a QColor from various input formats: - QColor instance (returned as is) - RGB or RGBA tuple/list of ints (e.g. (r,g,b) or (r,g,b,a)) - - RGB Hex string (e.g. '#FFAA00') + - RGB Hex string (e.g. '#FFAA00') or QPalette color role (e.g. 'window') - Qt color name string (e.g. 'red', 'blue', 'lightgray') Parameters @@ -76,6 +76,12 @@ def get_qcolor(color: QColor | tuple | list | str) -> QColor: # Accept hex (with '#') or color name if color.startswith('#') or color in QColor.colorNames(): return QColor(color) + + try: + return getattr(QStyleOption().palette, color)().color() + except AttributeError: + pass + raise ValueError(f"Unknown color string: {color!r}") raise ValueError(f"Cannot convert argument to QColor: {color!r}")