Skip to content

Commit 8e01c90

Browse files
gh-155863: Add the WACS_* constants to the curses module (GH-155870)
They are the counterparts of the ACS_* line-drawing codes as complexchar cells, added to the module by initscr() and newterm() like the ACS_* ones. Besides the codes that mirror the ACS_* ones, ncurses provides the double-line and thick-line families, which have no ACS_* counterpart.
1 parent 0f9fb00 commit 8e01c90

6 files changed

Lines changed: 505 additions & 178 deletions

File tree

Doc/library/curses.rst

Lines changed: 233 additions & 171 deletions
Large diffs are not rendered by default.

Doc/whatsnew/3.16.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,11 @@ curses
216216
counterpart of :func:`curses.termattrs`.
217217
(Contributed by Serhiy Storchaka in :gh:`152332`.)
218218

219+
* Add the ``WACS_*`` constants to the :mod:`curses` module, the counterparts of
220+
the :ref:`ACS_* <curses-acs-codes>` line-drawing codes as
221+
:class:`curses.complexchar` cells.
222+
(Contributed by Serhiy Storchaka in :gh:`155863`.)
223+
219224
* Add the :mod:`curses` functions :func:`curses.alloc_pair`,
220225
:func:`curses.find_pair`, :func:`curses.free_pair` and
221226
:func:`curses.reset_color_pairs` for dynamic color-pair management,

Lib/curses/__init__.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
import os as _os
1515
import sys as _sys
1616

17-
# Some constants, most notably the ACS_* ones, are only added to the C
18-
# _curses module's dictionary after initscr() is called. (Some
17+
# Some constants, most notably the ACS_* and WACS_* ones, are only added
18+
# to the C _curses module's dictionary after initscr() is called. (Some
1919
# versions of SGI's curses don't define values for those constants
2020
# until initscr() has been called.) This wrapper function calls the
2121
# underlying C initscr(), and then copies the constants from the
@@ -30,13 +30,13 @@ def initscr():
3030
fd=_sys.__stdout__.fileno())
3131
stdscr = _curses.initscr()
3232
for key, value in _curses.__dict__.items():
33-
if key.startswith('ACS_') or key in ('LINES', 'COLS'):
33+
if key.startswith(('ACS_', 'WACS_')) or key in ('LINES', 'COLS'):
3434
setattr(curses, key, value)
3535
return stdscr
3636

37-
# newterm() is wrapped for the same reason as initscr(): the ACS_* constants
38-
# and LINES/COLS only become available once a terminal is initialized, and are
39-
# then copied to the curses package's dictionary.
37+
# newterm() is wrapped for the same reason as initscr(): the ACS_* and WACS_*
38+
# constants and LINES/COLS only become available once a terminal is
39+
# initialized, and are then copied to the curses package's dictionary.
4040

4141
try:
4242
newterm
@@ -47,7 +47,7 @@ def newterm(type=None, fd=None, infd=None, /):
4747
import _curses, curses
4848
screen = _curses.newterm(type, fd, infd)
4949
for key, value in _curses.__dict__.items():
50-
if key.startswith('ACS_') or key in ('LINES', 'COLS'):
50+
if key.startswith(('ACS_', 'WACS_')) or key in ('LINES', 'COLS'):
5151
setattr(curses, key, value)
5252
return screen
5353

Lib/test/test_curses.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,23 @@ def wrapped(self, *args, **kwargs):
5757
return wrapped
5858

5959

60+
# The WACS_* double-line and thick-line character cells, without the common
61+
# prefix, paired with the alternate name spelling out their four sides
62+
# (blank, double or thick, clockwise from the top).
63+
WACS_LINE_ALIASES = [
64+
('D_ULCORNER', 'BDDB'), ('D_LLCORNER', 'DDBB'),
65+
('D_URCORNER', 'BBDD'), ('D_LRCORNER', 'DBBD'),
66+
('D_LTEE', 'DDDB'), ('D_RTEE', 'DBDD'),
67+
('D_BTEE', 'DDBD'), ('D_TTEE', 'BDDD'),
68+
('D_HLINE', 'BDBD'), ('D_VLINE', 'DBDB'), ('D_PLUS', 'DDDD'),
69+
('T_ULCORNER', 'BTTB'), ('T_LLCORNER', 'TTBB'),
70+
('T_URCORNER', 'BBTT'), ('T_LRCORNER', 'TBBT'),
71+
('T_LTEE', 'TTTB'), ('T_RTEE', 'TBTT'),
72+
('T_BTEE', 'TTBT'), ('T_TTEE', 'BTTT'),
73+
('T_HLINE', 'BTBT'), ('T_VLINE', 'TBTB'), ('T_PLUS', 'TTTT'),
74+
]
75+
76+
6077
def requires_colors(test):
6178
@functools.wraps(test)
6279
def wrapped(self, *args, **kwargs):
@@ -470,6 +487,72 @@ def test_wide_characters(self):
470487
# border() and box() cannot mix integer and wide-string characters.
471488
self.assertRaises(TypeError, stdscr.box, vline, ord('-'))
472489

490+
@requires_wide_build
491+
def test_wacs_constants(self):
492+
# Every ACS_* code has a WACS_* character cell counterpart, plus the
493+
# double-line and thick-line codes, which have no ACS_* counterpart.
494+
acs = {name.removeprefix('ACS_')
495+
for name in dir(curses) if name.startswith('ACS_')}
496+
wacs = {name.removeprefix('WACS_')
497+
for name in dir(curses) if name.startswith('WACS_')}
498+
extra = {name for pair in WACS_LINE_ALIASES for name in pair}
499+
self.assertEqual(wacs - extra, acs)
500+
for name in sorted(wacs):
501+
with self.subTest(name=name):
502+
self.assertIsInstance(getattr(curses, 'WACS_' + name),
503+
curses.complexchar)
504+
# The alternate names refer to the same cells.
505+
self.assertEqual(curses.WACS_BSSB, curses.WACS_ULCORNER)
506+
self.assertEqual(curses.WACS_BSBS, curses.WACS_HLINE)
507+
self.assertEqual(curses.WACS_SBSB, curses.WACS_VLINE)
508+
self.assertEqual(curses.WACS_SSSS, curses.WACS_PLUS)
509+
510+
@requires_wide_build
511+
def test_wacs_line_constants(self):
512+
# The double-line and thick-line codes are optional, but a supporting
513+
# implementation provides the whole family under both names.
514+
present = [name for name, alias in WACS_LINE_ALIASES
515+
if hasattr(curses, 'WACS_' + name)]
516+
if not present:
517+
self.skipTest('requires double-line and thick-line characters')
518+
self.assertEqual(len(present), len(WACS_LINE_ALIASES))
519+
for name, alias in WACS_LINE_ALIASES:
520+
with self.subTest(name=name):
521+
cell = getattr(curses, 'WACS_' + name)
522+
self.assertIsInstance(cell, curses.complexchar)
523+
self.assertEqual(getattr(curses, 'WACS_' + alias), cell)
524+
# They are distinct from the single-line characters.
525+
self.assertNotEqual(curses.WACS_D_HLINE, curses.WACS_HLINE)
526+
self.assertNotEqual(curses.WACS_T_HLINE, curses.WACS_HLINE)
527+
self.assertNotEqual(curses.WACS_D_HLINE, curses.WACS_T_HLINE)
528+
stdscr = self.stdscr
529+
stdscr.border(curses.WACS_D_VLINE, curses.WACS_D_VLINE,
530+
curses.WACS_D_HLINE, curses.WACS_D_HLINE,
531+
curses.WACS_D_ULCORNER, curses.WACS_D_URCORNER,
532+
curses.WACS_D_LLCORNER, curses.WACS_D_LRCORNER)
533+
self.assertEqual(stdscr.in_wch(0, 0), curses.WACS_D_ULCORNER)
534+
self.assertEqual(stdscr.in_wch(0, 1), curses.WACS_D_HLINE)
535+
536+
@requires_wide_build
537+
def test_wacs_in_cell_methods(self):
538+
# A WACS_* cell can be used wherever a character cell is accepted.
539+
stdscr = self.stdscr
540+
stdscr.addch(0, 0, curses.WACS_ULCORNER)
541+
self.assertEqual(stdscr.in_wch(0, 0), curses.WACS_ULCORNER)
542+
stdscr.insch(1, 0, curses.WACS_DIAMOND)
543+
self.assertEqual(stdscr.in_wch(1, 0), curses.WACS_DIAMOND)
544+
stdscr.hline(2, 0, curses.WACS_HLINE, 5)
545+
self.assertEqual(stdscr.in_wch(2, 4), curses.WACS_HLINE)
546+
stdscr.vline(3, 0, curses.WACS_VLINE, 3)
547+
self.assertEqual(stdscr.in_wch(5, 0), curses.WACS_VLINE)
548+
stdscr.border(curses.WACS_VLINE, curses.WACS_VLINE,
549+
curses.WACS_HLINE, curses.WACS_HLINE,
550+
curses.WACS_ULCORNER, curses.WACS_URCORNER,
551+
curses.WACS_LLCORNER, curses.WACS_LRCORNER)
552+
self.assertEqual(stdscr.in_wch(0, 0), curses.WACS_ULCORNER)
553+
stdscr.box(curses.WACS_VLINE, curses.WACS_HLINE)
554+
self.assertEqual(stdscr.in_wch(0, 1), curses.WACS_HLINE)
555+
473556
def test_complexchar_in_cell_methods(self):
474557
# Every single-character-cell method also accepts a complexchar, whose
475558
# attributes and color pair come from the cell itself.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Add the ``WACS_*`` constants to the :mod:`curses` module. They are the
2+
counterparts of the ``ACS_*`` line-drawing codes as :class:`curses.complexchar`
3+
cells, and are added, like the latter, by :func:`curses.initscr` and
4+
:func:`curses.newterm`.

Modules/_cursesmodule.c

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6612,6 +6612,179 @@ curses_init_dict(PyObject *module)
66126612
SetDictInt("ACS_STERLING", (ACS_STERLING));
66136613
#endif
66146614

6615+
#ifdef HAVE_NCURSESW
6616+
/* The same graphic symbols as character cells, for the methods that take
6617+
a complexchar. Unlike the ACS_* codes, these are not restricted to the
6618+
8-bit alternate character set. */
6619+
cursesmodule_state *state = get_cursesmodule_state(module);
6620+
#define SetDictWACS(NAME, VALUE) \
6621+
do { \
6622+
PyObject *value = PyCursesComplexChar_New(state, (VALUE)); \
6623+
if (value == NULL) { \
6624+
return -1; \
6625+
} \
6626+
int rc = PyDict_SetItemString(module_dict, (NAME), value); \
6627+
Py_DECREF(value); \
6628+
if (rc < 0) { \
6629+
return -1; \
6630+
} \
6631+
} while (0)
6632+
6633+
SetDictWACS("WACS_ULCORNER", WACS_ULCORNER);
6634+
SetDictWACS("WACS_LLCORNER", WACS_LLCORNER);
6635+
SetDictWACS("WACS_URCORNER", WACS_URCORNER);
6636+
SetDictWACS("WACS_LRCORNER", WACS_LRCORNER);
6637+
SetDictWACS("WACS_LTEE", WACS_LTEE);
6638+
SetDictWACS("WACS_RTEE", WACS_RTEE);
6639+
SetDictWACS("WACS_BTEE", WACS_BTEE);
6640+
SetDictWACS("WACS_TTEE", WACS_TTEE);
6641+
SetDictWACS("WACS_HLINE", WACS_HLINE);
6642+
SetDictWACS("WACS_VLINE", WACS_VLINE);
6643+
SetDictWACS("WACS_PLUS", WACS_PLUS);
6644+
SetDictWACS("WACS_S1", WACS_S1);
6645+
SetDictWACS("WACS_S9", WACS_S9);
6646+
SetDictWACS("WACS_DIAMOND", WACS_DIAMOND);
6647+
SetDictWACS("WACS_CKBOARD", WACS_CKBOARD);
6648+
SetDictWACS("WACS_DEGREE", WACS_DEGREE);
6649+
SetDictWACS("WACS_PLMINUS", WACS_PLMINUS);
6650+
SetDictWACS("WACS_BULLET", WACS_BULLET);
6651+
SetDictWACS("WACS_LARROW", WACS_LARROW);
6652+
SetDictWACS("WACS_RARROW", WACS_RARROW);
6653+
SetDictWACS("WACS_DARROW", WACS_DARROW);
6654+
SetDictWACS("WACS_UARROW", WACS_UARROW);
6655+
SetDictWACS("WACS_BOARD", WACS_BOARD);
6656+
SetDictWACS("WACS_LANTERN", WACS_LANTERN);
6657+
SetDictWACS("WACS_BLOCK", WACS_BLOCK);
6658+
6659+
SetDictWACS("WACS_BSSB", WACS_ULCORNER);
6660+
SetDictWACS("WACS_SSBB", WACS_LLCORNER);
6661+
SetDictWACS("WACS_BBSS", WACS_URCORNER);
6662+
SetDictWACS("WACS_SBBS", WACS_LRCORNER);
6663+
SetDictWACS("WACS_SBSS", WACS_RTEE);
6664+
SetDictWACS("WACS_SSSB", WACS_LTEE);
6665+
SetDictWACS("WACS_SSBS", WACS_BTEE);
6666+
SetDictWACS("WACS_BSSS", WACS_TTEE);
6667+
SetDictWACS("WACS_BSBS", WACS_HLINE);
6668+
SetDictWACS("WACS_SBSB", WACS_VLINE);
6669+
SetDictWACS("WACS_SSSS", WACS_PLUS);
6670+
6671+
/* The following are never available with strict SYSV curses */
6672+
#ifdef WACS_S3
6673+
SetDictWACS("WACS_S3", WACS_S3);
6674+
#endif
6675+
#ifdef WACS_S7
6676+
SetDictWACS("WACS_S7", WACS_S7);
6677+
#endif
6678+
#ifdef WACS_LEQUAL
6679+
SetDictWACS("WACS_LEQUAL", WACS_LEQUAL);
6680+
#endif
6681+
#ifdef WACS_GEQUAL
6682+
SetDictWACS("WACS_GEQUAL", WACS_GEQUAL);
6683+
#endif
6684+
#ifdef WACS_PI
6685+
SetDictWACS("WACS_PI", WACS_PI);
6686+
#endif
6687+
#ifdef WACS_NEQUAL
6688+
SetDictWACS("WACS_NEQUAL", WACS_NEQUAL);
6689+
#endif
6690+
#ifdef WACS_STERLING
6691+
SetDictWACS("WACS_STERLING", WACS_STERLING);
6692+
#endif
6693+
6694+
/* Double-line and thick-line symbols have no ACS_* counterpart, and are
6695+
only provided by some implementations. */
6696+
#ifdef WACS_D_ULCORNER
6697+
SetDictWACS("WACS_D_ULCORNER", WACS_D_ULCORNER);
6698+
SetDictWACS("WACS_BDDB", WACS_D_ULCORNER);
6699+
#endif
6700+
#ifdef WACS_D_LLCORNER
6701+
SetDictWACS("WACS_D_LLCORNER", WACS_D_LLCORNER);
6702+
SetDictWACS("WACS_DDBB", WACS_D_LLCORNER);
6703+
#endif
6704+
#ifdef WACS_D_URCORNER
6705+
SetDictWACS("WACS_D_URCORNER", WACS_D_URCORNER);
6706+
SetDictWACS("WACS_BBDD", WACS_D_URCORNER);
6707+
#endif
6708+
#ifdef WACS_D_LRCORNER
6709+
SetDictWACS("WACS_D_LRCORNER", WACS_D_LRCORNER);
6710+
SetDictWACS("WACS_DBBD", WACS_D_LRCORNER);
6711+
#endif
6712+
#ifdef WACS_D_LTEE
6713+
SetDictWACS("WACS_D_LTEE", WACS_D_LTEE);
6714+
SetDictWACS("WACS_DDDB", WACS_D_LTEE);
6715+
#endif
6716+
#ifdef WACS_D_RTEE
6717+
SetDictWACS("WACS_D_RTEE", WACS_D_RTEE);
6718+
SetDictWACS("WACS_DBDD", WACS_D_RTEE);
6719+
#endif
6720+
#ifdef WACS_D_BTEE
6721+
SetDictWACS("WACS_D_BTEE", WACS_D_BTEE);
6722+
SetDictWACS("WACS_DDBD", WACS_D_BTEE);
6723+
#endif
6724+
#ifdef WACS_D_TTEE
6725+
SetDictWACS("WACS_D_TTEE", WACS_D_TTEE);
6726+
SetDictWACS("WACS_BDDD", WACS_D_TTEE);
6727+
#endif
6728+
#ifdef WACS_D_HLINE
6729+
SetDictWACS("WACS_D_HLINE", WACS_D_HLINE);
6730+
SetDictWACS("WACS_BDBD", WACS_D_HLINE);
6731+
#endif
6732+
#ifdef WACS_D_VLINE
6733+
SetDictWACS("WACS_D_VLINE", WACS_D_VLINE);
6734+
SetDictWACS("WACS_DBDB", WACS_D_VLINE);
6735+
#endif
6736+
#ifdef WACS_D_PLUS
6737+
SetDictWACS("WACS_D_PLUS", WACS_D_PLUS);
6738+
SetDictWACS("WACS_DDDD", WACS_D_PLUS);
6739+
#endif
6740+
6741+
#ifdef WACS_T_ULCORNER
6742+
SetDictWACS("WACS_T_ULCORNER", WACS_T_ULCORNER);
6743+
SetDictWACS("WACS_BTTB", WACS_T_ULCORNER);
6744+
#endif
6745+
#ifdef WACS_T_LLCORNER
6746+
SetDictWACS("WACS_T_LLCORNER", WACS_T_LLCORNER);
6747+
SetDictWACS("WACS_TTBB", WACS_T_LLCORNER);
6748+
#endif
6749+
#ifdef WACS_T_URCORNER
6750+
SetDictWACS("WACS_T_URCORNER", WACS_T_URCORNER);
6751+
SetDictWACS("WACS_BBTT", WACS_T_URCORNER);
6752+
#endif
6753+
#ifdef WACS_T_LRCORNER
6754+
SetDictWACS("WACS_T_LRCORNER", WACS_T_LRCORNER);
6755+
SetDictWACS("WACS_TBBT", WACS_T_LRCORNER);
6756+
#endif
6757+
#ifdef WACS_T_LTEE
6758+
SetDictWACS("WACS_T_LTEE", WACS_T_LTEE);
6759+
SetDictWACS("WACS_TTTB", WACS_T_LTEE);
6760+
#endif
6761+
#ifdef WACS_T_RTEE
6762+
SetDictWACS("WACS_T_RTEE", WACS_T_RTEE);
6763+
SetDictWACS("WACS_TBTT", WACS_T_RTEE);
6764+
#endif
6765+
#ifdef WACS_T_BTEE
6766+
SetDictWACS("WACS_T_BTEE", WACS_T_BTEE);
6767+
SetDictWACS("WACS_TTBT", WACS_T_BTEE);
6768+
#endif
6769+
#ifdef WACS_T_TTEE
6770+
SetDictWACS("WACS_T_TTEE", WACS_T_TTEE);
6771+
SetDictWACS("WACS_BTTT", WACS_T_TTEE);
6772+
#endif
6773+
#ifdef WACS_T_HLINE
6774+
SetDictWACS("WACS_T_HLINE", WACS_T_HLINE);
6775+
SetDictWACS("WACS_BTBT", WACS_T_HLINE);
6776+
#endif
6777+
#ifdef WACS_T_VLINE
6778+
SetDictWACS("WACS_T_VLINE", WACS_T_VLINE);
6779+
SetDictWACS("WACS_TBTB", WACS_T_VLINE);
6780+
#endif
6781+
#ifdef WACS_T_PLUS
6782+
SetDictWACS("WACS_T_PLUS", WACS_T_PLUS);
6783+
SetDictWACS("WACS_TTTT", WACS_T_PLUS);
6784+
#endif
6785+
#undef SetDictWACS
6786+
#endif /* HAVE_NCURSESW */
6787+
66156788
SetDictInt("LINES", LINES);
66166789
SetDictInt("COLS", COLS);
66176790
#undef SetDictInt

0 commit comments

Comments
 (0)