Skip to content

Commit 285d96d

Browse files
gh-152219: Add curses window attribute get/set methods and WA_* constants (GH-152221)
Add the window methods attr_get(), attr_set(), attr_on(), attr_off() and color_set(), wrapping wattr_get(), wattr_set(), wattr_on(), wattr_off() and wcolor_set(). Unlike the legacy attron()/attroff()/attrset() methods, these pass the color pair as a separate argument instead of packing it into the attribute value. Also add the corresponding WA_* attribute constants. Add an attr_converter that range-checks the attr_t attribute argument and raises OverflowError instead of silently truncating it; apply it to attr_set(), attr_on(), attr_off() and chgat(). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 794b42f commit 285d96d

6 files changed

Lines changed: 494 additions & 11 deletions

File tree

Doc/library/curses.rst

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -989,6 +989,55 @@ Window objects
989989
``0`` (no attributes).
990990

991991

992+
.. method:: window.attr_get()
993+
994+
Return the window's current rendition as a ``(attrs, pair)`` tuple,
995+
where *attrs* is the set of attributes and *pair* is the color pair number.
996+
997+
Unlike :meth:`attron` and friends, which take packed ``A_*`` attributes,
998+
this method and the other ``attr_*`` methods work with the
999+
:ref:`WA_* attributes <curses-wa-constants>` and keep the color pair as a
1000+
separate number, which lets them use color pairs that do not fit alongside
1001+
the attributes in a single value.
1002+
1003+
.. versionadded:: next
1004+
1005+
1006+
.. method:: window.attr_set(attr, pair=0)
1007+
1008+
Set the window's rendition to the attributes *attr* and the color pair *pair*.
1009+
1010+
.. versionadded:: next
1011+
1012+
1013+
.. method:: window.attr_on(attr)
1014+
1015+
Turn on the attributes *attr* without affecting any others.
1016+
1017+
.. versionadded:: next
1018+
1019+
1020+
.. method:: window.attr_off(attr)
1021+
1022+
Turn off the attributes *attr* without affecting any others.
1023+
1024+
.. versionadded:: next
1025+
1026+
1027+
.. method:: window.color_set(pair)
1028+
1029+
Set the window's color pair to *pair*, leaving the other attributes unchanged.
1030+
1031+
.. versionadded:: next
1032+
1033+
1034+
.. method:: window.getattrs()
1035+
1036+
Return the window's current attributes.
1037+
1038+
.. versionadded:: next
1039+
1040+
9921041
.. method:: window.bkgd(ch[, attr])
9931042

9941043
Set the background property of the window to the character *ch*, with
@@ -1888,6 +1937,24 @@ The exact constants available are system dependent.
18881937
.. versionadded:: 3.7
18891938
``A_ITALIC`` was added.
18901939

1940+
.. _curses-wa-constants:
1941+
1942+
The :meth:`~window.attr_get`, :meth:`~window.attr_set`, :meth:`~window.attr_on`
1943+
and :meth:`~window.attr_off` methods use a parallel set of ``WA_*`` constants.
1944+
These have the same meaning as the corresponding ``A_*`` attributes above
1945+
(``WA_BOLD`` like :const:`A_BOLD`, and so on), but belong to the ``attr_t`` type
1946+
rather than being packed into a character. In ncurses the two sets share the
1947+
same values, but other curses implementations may give them different ones, so
1948+
use the ``WA_*`` constants with the ``attr_*`` methods. The available names are
1949+
``WA_ATTRIBUTES``, ``WA_NORMAL``, ``WA_STANDOUT``, ``WA_UNDERLINE``,
1950+
``WA_REVERSE``, ``WA_BLINK``, ``WA_DIM``, ``WA_BOLD``, ``WA_ALTCHARSET``,
1951+
``WA_INVIS``, ``WA_PROTECT``, ``WA_HORIZONTAL``, ``WA_LEFT``, ``WA_LOW``,
1952+
``WA_RIGHT``, ``WA_TOP``, ``WA_VERTICAL`` and ``WA_ITALIC`` (each available only
1953+
where the platform defines it).
1954+
1955+
.. versionadded:: next
1956+
The ``WA_*`` constants were added.
1957+
18911958
Several constants are available to extract corresponding attributes returned
18921959
by some methods.
18931960

Doc/whatsnew/3.16.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@ curses
131131
available when built against an ncurses with ``NCURSES_EXT_FUNCS``.
132132
(Contributed by Serhiy Storchaka in :gh:`151776`.)
133133

134+
* Add the :mod:`curses` window methods :meth:`~curses.window.attr_get`,
135+
:meth:`~curses.window.attr_set`, :meth:`~curses.window.attr_on`,
136+
:meth:`~curses.window.attr_off` and :meth:`~curses.window.color_set`, which
137+
pass the color pair as a separate argument instead of packing it into the
138+
attribute value, and the corresponding ``WA_*`` attribute constants.
139+
(Contributed by Serhiy Storchaka in :gh:`152219`.)
140+
134141
gzip
135142
----
136143

Lib/test/test_curses.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,6 @@ def test_scroll(self):
651651
win.scrollok(False)
652652

653653
def test_attributes(self):
654-
# TODO: attr_get(), attr_set(), ...
655654
win = curses.newwin(5, 15, 5, 2)
656655
win.attron(curses.A_BOLD)
657656
win.attroff(curses.A_BOLD)
@@ -660,6 +659,45 @@ def test_attributes(self):
660659
win.standout()
661660
win.standend()
662661

662+
# The attr_*() family works on attr_t attributes paired with a color
663+
# pair, unlike the chtype-based attron()/attroff()/attrset().
664+
win.attr_set(curses.A_BOLD | curses.A_UNDERLINE)
665+
attrs, pair = win.attr_get()
666+
self.assertTrue(attrs & curses.A_BOLD)
667+
self.assertTrue(attrs & curses.A_UNDERLINE)
668+
self.assertEqual(pair, 0)
669+
self.assertEqual(win.getattrs(), attrs)
670+
671+
win.attr_on(curses.A_REVERSE)
672+
self.assertTrue(win.attr_get()[0] & curses.A_REVERSE)
673+
win.attr_off(curses.A_REVERSE)
674+
self.assertFalse(win.attr_get()[0] & curses.A_REVERSE)
675+
676+
# color_set() with a real pair needs start_color(); see
677+
# test_attr_color_pair. Here only the argument validation is checked,
678+
# which fails before wcolor_set() is reached.
679+
self.assertRaises(TypeError, win.attr_set, 'x')
680+
self.assertRaises(TypeError, win.attr_set, curses.A_BOLD, 'x')
681+
self.assertRaises(TypeError, win.attr_on, 'x')
682+
self.assertRaises(TypeError, win.color_set, 'x')
683+
self.assertRaises(ValueError, win.color_set, -1)
684+
self.assertRaises(ValueError, win.attr_set, curses.A_BOLD, -1)
685+
# attr_t is unsigned: a negative or too-large attribute overflows.
686+
self.assertRaises(OverflowError, win.attr_set, -1)
687+
self.assertRaises(OverflowError, win.attr_on, -1)
688+
self.assertRaises(OverflowError, win.attr_set, 1 << 64)
689+
690+
@requires_colors
691+
def test_attr_color_pair(self):
692+
win = curses.newwin(5, 15, 5, 2)
693+
curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK)
694+
win.attr_set(curses.A_BOLD, 1)
695+
attrs, pair = win.attr_get()
696+
self.assertTrue(attrs & curses.A_BOLD)
697+
self.assertEqual(pair, 1)
698+
win.color_set(0)
699+
self.assertEqual(win.attr_get()[1], 0)
700+
663701
@requires_curses_window_meth('chgat')
664702
def test_chgat(self):
665703
win = curses.newwin(5, 15, 5, 2)
@@ -691,6 +729,11 @@ def test_chgat(self):
691729
self.assertEqual(win.inch(3, 11), b'm'[0] | curses.A_UNDERLINE)
692730
self.assertEqual(win.inch(3, 14), b' '[0] | curses.A_UNDERLINE)
693731

732+
# attr_t is unsigned: a negative or too-large attribute overflows.
733+
self.assertRaises(TypeError, win.chgat, 'x')
734+
self.assertRaises(OverflowError, win.chgat, -1)
735+
self.assertRaises(OverflowError, win.chgat, 1 << 64)
736+
694737
def test_background(self):
695738
win = curses.newwin(5, 15, 5, 2)
696739
win.addstr(0, 0, 'Lorem ipsum')
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Add the :mod:`curses` window methods :meth:`~curses.window.attr_get`,
2+
:meth:`~curses.window.attr_set`, :meth:`~curses.window.attr_on`,
3+
:meth:`~curses.window.attr_off` and :meth:`~curses.window.color_set`, which use
4+
a separate color pair argument instead of packing it into the attribute value,
5+
and the corresponding ``WA_*`` attribute constants.

0 commit comments

Comments
 (0)