mirror of
https://github.com/python/cpython.git
synced 2026-06-28 11:50:50 +00:00
gh-151774: Add curses dynamic color-pair functions (GH-151775)
Add alloc_pair(), find_pair(), free_pair() and reset_color_pairs(), wrapping the ncurses extended-color dynamic pair management. They are available only when built against a wide-character ncurses with extended-color support. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c7faa6936e
commit
3cd4283ba6
6 changed files with 374 additions and 1 deletions
|
|
@ -85,6 +85,20 @@ The module :mod:`!curses` defines the following functions:
|
|||
.. versionadded:: 3.14
|
||||
|
||||
|
||||
.. function:: alloc_pair(fg, bg)
|
||||
|
||||
Allocate a color pair for foreground color *fg* and background color *bg*,
|
||||
and return its number. If a color pair for the same combination of colors
|
||||
already exists, return its number. Otherwise allocate a new color pair and
|
||||
return its number.
|
||||
|
||||
This function is only available if Python was built against a wide-character
|
||||
version of the underlying curses library with extended-color support (see
|
||||
:func:`has_extended_color_support`).
|
||||
|
||||
.. versionadded:: next
|
||||
|
||||
|
||||
.. function:: baudrate()
|
||||
|
||||
Return the output speed of the terminal in bits per second. On software
|
||||
|
|
@ -226,6 +240,19 @@ The module :mod:`!curses` defines the following functions:
|
|||
.. versionadded:: next
|
||||
|
||||
|
||||
.. function:: find_pair(fg, bg)
|
||||
|
||||
Return the number of a color pair for foreground color *fg* and background
|
||||
color *bg*, or ``-1`` if no color pair for this combination of colors has
|
||||
been allocated.
|
||||
|
||||
This function is only available if Python was built against a wide-character
|
||||
version of the underlying curses library with extended-color support (see
|
||||
:func:`has_extended_color_support`).
|
||||
|
||||
.. versionadded:: next
|
||||
|
||||
|
||||
.. function:: flash()
|
||||
|
||||
Flash the screen. That is, change it to reverse-video and then change it back
|
||||
|
|
@ -239,6 +266,18 @@ The module :mod:`!curses` defines the following functions:
|
|||
by the user and has not yet been processed by the program.
|
||||
|
||||
|
||||
.. function:: free_pair(pair_number)
|
||||
|
||||
Free the color pair *pair_number*, which must have been allocated by
|
||||
:func:`alloc_pair`. The pair must not be in use.
|
||||
|
||||
This function is only available if Python was built against a wide-character
|
||||
version of the underlying curses library with extended-color support (see
|
||||
:func:`has_extended_color_support`).
|
||||
|
||||
.. versionadded:: next
|
||||
|
||||
|
||||
.. function:: getmouse()
|
||||
|
||||
After :meth:`~window.getch` returns :const:`KEY_MOUSE` to signal a mouse event, this
|
||||
|
|
@ -570,6 +609,18 @@ The module :mod:`!curses` defines the following functions:
|
|||
presented to curses input functions one by one.
|
||||
|
||||
|
||||
.. function:: reset_color_pairs()
|
||||
|
||||
Discard all color-pair definitions, releasing the color pairs allocated by
|
||||
:func:`init_pair` and :func:`alloc_pair`.
|
||||
|
||||
This function is only available if Python was built against a wide-character
|
||||
version of the underlying curses library with extended-color support (see
|
||||
:func:`has_extended_color_support`).
|
||||
|
||||
.. versionadded:: next
|
||||
|
||||
|
||||
.. function:: reset_prog_mode()
|
||||
|
||||
Restore the terminal to "program" mode, as previously saved by
|
||||
|
|
|
|||
|
|
@ -118,6 +118,13 @@ curses
|
|||
* Add :func:`curses.nofilter`, which undoes the effect of :func:`curses.filter`.
|
||||
(Contributed by Serhiy Storchaka in :gh:`151744`.)
|
||||
|
||||
* Add the :mod:`curses` functions :func:`curses.alloc_pair`,
|
||||
:func:`curses.find_pair`, :func:`curses.free_pair` and
|
||||
:func:`curses.reset_color_pairs` for dynamic color-pair management,
|
||||
available when built against a wide-character ncurses with extended-color
|
||||
support.
|
||||
(Contributed by Serhiy Storchaka in :gh:`151774`.)
|
||||
|
||||
gzip
|
||||
----
|
||||
|
||||
|
|
|
|||
|
|
@ -1209,6 +1209,54 @@ def test_init_pair(self):
|
|||
self.assertRaises(ValueError, curses.init_pair, 1, color, 0)
|
||||
self.assertRaises(ValueError, curses.init_pair, 1, 0, color)
|
||||
|
||||
@requires_curses_func('alloc_pair')
|
||||
@requires_colors
|
||||
def test_dynamic_color_pairs(self):
|
||||
# alloc_pair()/find_pair()/free_pair() (extended-color extension).
|
||||
fg = bg = curses.COLORS - 1
|
||||
pair = curses.alloc_pair(fg, bg)
|
||||
self.assertGreater(pair, 0)
|
||||
self.assertEqual(curses.pair_content(pair), (fg, bg))
|
||||
# The same combination of colors reuses the same pair.
|
||||
self.assertEqual(curses.alloc_pair(fg, bg), pair)
|
||||
self.assertEqual(curses.find_pair(fg, bg), pair)
|
||||
# Once freed, the pair is no longer found.
|
||||
self.assertIsNone(curses.free_pair(pair))
|
||||
self.assertEqual(curses.find_pair(fg, bg), -1)
|
||||
|
||||
# Error paths.
|
||||
for color in self.bad_colors2():
|
||||
self.assertRaises(ValueError, curses.alloc_pair, color, 0)
|
||||
self.assertRaises(ValueError, curses.alloc_pair, 0, color)
|
||||
self.assertRaises(ValueError, curses.find_pair, color, 0)
|
||||
self.assertRaises(ValueError, curses.find_pair, 0, color)
|
||||
for pair in self.bad_pairs():
|
||||
self.assertRaises(ValueError, curses.free_pair, pair)
|
||||
# Color pair 0 is reserved and cannot be freed.
|
||||
self.assertRaises(curses.error, curses.free_pair, 0)
|
||||
|
||||
# Invalid number or type of arguments.
|
||||
self.assertRaises(TypeError, curses.alloc_pair)
|
||||
self.assertRaises(TypeError, curses.alloc_pair, 0)
|
||||
self.assertRaises(TypeError, curses.alloc_pair, 0, 0, 0)
|
||||
self.assertRaises(TypeError, curses.alloc_pair, 'red', 0)
|
||||
self.assertRaises(TypeError, curses.alloc_pair, 0, 'red')
|
||||
self.assertRaises(TypeError, curses.alloc_pair, fg=0, bg=0)
|
||||
self.assertRaises(TypeError, curses.find_pair)
|
||||
self.assertRaises(TypeError, curses.find_pair, 0)
|
||||
self.assertRaises(TypeError, curses.find_pair, 0, 0, 0)
|
||||
self.assertRaises(TypeError, curses.find_pair, 'red', 0)
|
||||
self.assertRaises(TypeError, curses.find_pair, 0, 'red')
|
||||
self.assertRaises(TypeError, curses.free_pair)
|
||||
self.assertRaises(TypeError, curses.free_pair, 1, 2)
|
||||
self.assertRaises(TypeError, curses.free_pair, 'red')
|
||||
|
||||
@requires_curses_func('reset_color_pairs')
|
||||
@requires_colors
|
||||
def test_reset_color_pairs(self):
|
||||
self.assertIsNone(curses.reset_color_pairs())
|
||||
self.assertRaises(TypeError, curses.reset_color_pairs, 0)
|
||||
|
||||
@requires_colors
|
||||
def test_color_attrs(self):
|
||||
for pair in 0, 1, 255:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
Add the :mod:`curses` functions :func:`curses.alloc_pair`,
|
||||
:func:`curses.find_pair`, :func:`curses.free_pair` and
|
||||
:func:`curses.reset_color_pairs` for dynamic color-pair management. They are
|
||||
only available when Python is built against a wide-character version of the
|
||||
underlying curses library with extended-color support.
|
||||
|
|
@ -4458,6 +4458,100 @@ _curses_init_pair_impl(PyObject *module, int pair_number, int fg, int bg)
|
|||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
#if _NCURSES_EXTENDED_COLOR_FUNCS
|
||||
/*[clinic input]
|
||||
_curses.alloc_pair
|
||||
|
||||
fg: color_allow_default
|
||||
Foreground color number.
|
||||
bg: color_allow_default
|
||||
Background color number.
|
||||
/
|
||||
|
||||
Allocate a color pair for the given foreground and background colors.
|
||||
|
||||
If a color pair for the same colors already exists, return its number.
|
||||
Otherwise allocate a new color pair and return its number.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
_curses_alloc_pair_impl(PyObject *module, int fg, int bg)
|
||||
/*[clinic end generated code: output=6eb08cb643d4b5a2 input=b29bafd7b360fa35]*/
|
||||
{
|
||||
PyCursesStatefulInitialised(module);
|
||||
PyCursesStatefulInitialisedColor(module);
|
||||
|
||||
int pair = alloc_pair(fg, bg);
|
||||
if (pair < 0) {
|
||||
curses_set_error(module, "alloc_pair", NULL);
|
||||
return NULL;
|
||||
}
|
||||
return PyLong_FromLong(pair);
|
||||
}
|
||||
|
||||
/*[clinic input]
|
||||
_curses.find_pair
|
||||
|
||||
fg: color_allow_default
|
||||
Foreground color number.
|
||||
bg: color_allow_default
|
||||
Background color number.
|
||||
/
|
||||
|
||||
Return the number of a color pair for the given colors, or -1.
|
||||
|
||||
Return -1 if no color pair for this combination of foreground and
|
||||
background colors has been allocated.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
_curses_find_pair_impl(PyObject *module, int fg, int bg)
|
||||
/*[clinic end generated code: output=376026c2a3ac4a9b input=930feac14892c251]*/
|
||||
{
|
||||
PyCursesStatefulInitialised(module);
|
||||
PyCursesStatefulInitialisedColor(module);
|
||||
|
||||
return PyLong_FromLong(find_pair(fg, bg));
|
||||
}
|
||||
|
||||
/*[clinic input]
|
||||
_curses.free_pair
|
||||
|
||||
pair: pair
|
||||
The number of the color pair to free.
|
||||
/
|
||||
|
||||
Free a color pair allocated by alloc_pair().
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
_curses_free_pair_impl(PyObject *module, int pair)
|
||||
/*[clinic end generated code: output=61be0fb2e4bb4e4a input=d24df62feb4161c6]*/
|
||||
{
|
||||
PyCursesStatefulInitialised(module);
|
||||
PyCursesStatefulInitialisedColor(module);
|
||||
|
||||
return curses_check_err(module, free_pair(pair), "free_pair", NULL);
|
||||
}
|
||||
|
||||
/*[clinic input]
|
||||
_curses.reset_color_pairs
|
||||
|
||||
Discard all color-pair definitions.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
_curses_reset_color_pairs_impl(PyObject *module)
|
||||
/*[clinic end generated code: output=117e68c6614e1d06 input=57c1cf7e5447e1ac]*/
|
||||
{
|
||||
PyCursesStatefulInitialised(module);
|
||||
PyCursesStatefulInitialisedColor(module);
|
||||
|
||||
reset_color_pairs();
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
#endif /* _NCURSES_EXTENDED_COLOR_FUNCS */
|
||||
|
||||
/* Refresh the private copy of the screen encoding from a freshly created
|
||||
stdscr window object. Returns 0 on success, -1 with an exception set. */
|
||||
static int
|
||||
|
|
@ -6241,6 +6335,7 @@ _curses_has_extended_color_support_impl(PyObject *module)
|
|||
/* List of functions defined in the module */
|
||||
|
||||
static PyMethodDef cursesmodule_methods[] = {
|
||||
_CURSES_ALLOC_PAIR_METHODDEF
|
||||
_CURSES_BAUDRATE_METHODDEF
|
||||
_CURSES_BEEP_METHODDEF
|
||||
_CURSES_CAN_CHANGE_COLOR_METHODDEF
|
||||
|
|
@ -6258,8 +6353,10 @@ static PyMethodDef cursesmodule_methods[] = {
|
|||
_CURSES_ERASEWCHAR_METHODDEF
|
||||
_CURSES_FILTER_METHODDEF
|
||||
_CURSES_NOFILTER_METHODDEF
|
||||
_CURSES_FIND_PAIR_METHODDEF
|
||||
_CURSES_FLASH_METHODDEF
|
||||
_CURSES_FLUSHINP_METHODDEF
|
||||
_CURSES_FREE_PAIR_METHODDEF
|
||||
_CURSES_GETMOUSE_METHODDEF
|
||||
_CURSES_UNGETMOUSE_METHODDEF
|
||||
_CURSES_GETSYX_METHODDEF
|
||||
|
|
@ -6301,6 +6398,7 @@ static PyMethodDef cursesmodule_methods[] = {
|
|||
_CURSES_PUTP_METHODDEF
|
||||
_CURSES_QIFLUSH_METHODDEF
|
||||
_CURSES_RAW_METHODDEF
|
||||
_CURSES_RESET_COLOR_PAIRS_METHODDEF
|
||||
_CURSES_RESET_PROG_MODE_METHODDEF
|
||||
_CURSES_RESET_SHELL_MODE_METHODDEF
|
||||
_CURSES_RESETTY_METHODDEF
|
||||
|
|
|
|||
166
Modules/clinic/_cursesmodule.c.h
generated
166
Modules/clinic/_cursesmodule.c.h
generated
|
|
@ -2723,6 +2723,154 @@ exit:
|
|||
return return_value;
|
||||
}
|
||||
|
||||
#if (_NCURSES_EXTENDED_COLOR_FUNCS)
|
||||
|
||||
PyDoc_STRVAR(_curses_alloc_pair__doc__,
|
||||
"alloc_pair($module, fg, bg, /)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Allocate a color pair for the given foreground and background colors.\n"
|
||||
"\n"
|
||||
" fg\n"
|
||||
" Foreground color number.\n"
|
||||
" bg\n"
|
||||
" Background color number.\n"
|
||||
"\n"
|
||||
"If a color pair for the same colors already exists, return its number.\n"
|
||||
"Otherwise allocate a new color pair and return its number.");
|
||||
|
||||
#define _CURSES_ALLOC_PAIR_METHODDEF \
|
||||
{"alloc_pair", _PyCFunction_CAST(_curses_alloc_pair), METH_FASTCALL, _curses_alloc_pair__doc__},
|
||||
|
||||
static PyObject *
|
||||
_curses_alloc_pair_impl(PyObject *module, int fg, int bg);
|
||||
|
||||
static PyObject *
|
||||
_curses_alloc_pair(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
|
||||
{
|
||||
PyObject *return_value = NULL;
|
||||
int fg;
|
||||
int bg;
|
||||
|
||||
if (!_PyArg_CheckPositional("alloc_pair", nargs, 2, 2)) {
|
||||
goto exit;
|
||||
}
|
||||
if (!color_allow_default_converter(args[0], &fg)) {
|
||||
goto exit;
|
||||
}
|
||||
if (!color_allow_default_converter(args[1], &bg)) {
|
||||
goto exit;
|
||||
}
|
||||
return_value = _curses_alloc_pair_impl(module, fg, bg);
|
||||
|
||||
exit:
|
||||
return return_value;
|
||||
}
|
||||
|
||||
#endif /* (_NCURSES_EXTENDED_COLOR_FUNCS) */
|
||||
|
||||
#if (_NCURSES_EXTENDED_COLOR_FUNCS)
|
||||
|
||||
PyDoc_STRVAR(_curses_find_pair__doc__,
|
||||
"find_pair($module, fg, bg, /)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Return the number of a color pair for the given colors, or -1.\n"
|
||||
"\n"
|
||||
" fg\n"
|
||||
" Foreground color number.\n"
|
||||
" bg\n"
|
||||
" Background color number.\n"
|
||||
"\n"
|
||||
"Return -1 if no color pair for this combination of foreground and\n"
|
||||
"background colors has been allocated.");
|
||||
|
||||
#define _CURSES_FIND_PAIR_METHODDEF \
|
||||
{"find_pair", _PyCFunction_CAST(_curses_find_pair), METH_FASTCALL, _curses_find_pair__doc__},
|
||||
|
||||
static PyObject *
|
||||
_curses_find_pair_impl(PyObject *module, int fg, int bg);
|
||||
|
||||
static PyObject *
|
||||
_curses_find_pair(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
|
||||
{
|
||||
PyObject *return_value = NULL;
|
||||
int fg;
|
||||
int bg;
|
||||
|
||||
if (!_PyArg_CheckPositional("find_pair", nargs, 2, 2)) {
|
||||
goto exit;
|
||||
}
|
||||
if (!color_allow_default_converter(args[0], &fg)) {
|
||||
goto exit;
|
||||
}
|
||||
if (!color_allow_default_converter(args[1], &bg)) {
|
||||
goto exit;
|
||||
}
|
||||
return_value = _curses_find_pair_impl(module, fg, bg);
|
||||
|
||||
exit:
|
||||
return return_value;
|
||||
}
|
||||
|
||||
#endif /* (_NCURSES_EXTENDED_COLOR_FUNCS) */
|
||||
|
||||
#if (_NCURSES_EXTENDED_COLOR_FUNCS)
|
||||
|
||||
PyDoc_STRVAR(_curses_free_pair__doc__,
|
||||
"free_pair($module, pair, /)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Free a color pair allocated by alloc_pair().\n"
|
||||
"\n"
|
||||
" pair\n"
|
||||
" The number of the color pair to free.");
|
||||
|
||||
#define _CURSES_FREE_PAIR_METHODDEF \
|
||||
{"free_pair", (PyCFunction)_curses_free_pair, METH_O, _curses_free_pair__doc__},
|
||||
|
||||
static PyObject *
|
||||
_curses_free_pair_impl(PyObject *module, int pair);
|
||||
|
||||
static PyObject *
|
||||
_curses_free_pair(PyObject *module, PyObject *arg)
|
||||
{
|
||||
PyObject *return_value = NULL;
|
||||
int pair;
|
||||
|
||||
if (!pair_converter(arg, &pair)) {
|
||||
goto exit;
|
||||
}
|
||||
return_value = _curses_free_pair_impl(module, pair);
|
||||
|
||||
exit:
|
||||
return return_value;
|
||||
}
|
||||
|
||||
#endif /* (_NCURSES_EXTENDED_COLOR_FUNCS) */
|
||||
|
||||
#if (_NCURSES_EXTENDED_COLOR_FUNCS)
|
||||
|
||||
PyDoc_STRVAR(_curses_reset_color_pairs__doc__,
|
||||
"reset_color_pairs($module, /)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Discard all color-pair definitions.");
|
||||
|
||||
#define _CURSES_RESET_COLOR_PAIRS_METHODDEF \
|
||||
{"reset_color_pairs", (PyCFunction)_curses_reset_color_pairs, METH_NOARGS, _curses_reset_color_pairs__doc__},
|
||||
|
||||
static PyObject *
|
||||
_curses_reset_color_pairs_impl(PyObject *module);
|
||||
|
||||
static PyObject *
|
||||
_curses_reset_color_pairs(PyObject *module, PyObject *Py_UNUSED(ignored))
|
||||
{
|
||||
return _curses_reset_color_pairs_impl(module);
|
||||
}
|
||||
|
||||
#endif /* (_NCURSES_EXTENDED_COLOR_FUNCS) */
|
||||
|
||||
PyDoc_STRVAR(_curses_initscr__doc__,
|
||||
"initscr($module, /)\n"
|
||||
"--\n"
|
||||
|
|
@ -4623,6 +4771,22 @@ _curses_has_extended_color_support(PyObject *module, PyObject *Py_UNUSED(ignored
|
|||
#define _CURSES_HAS_KEY_METHODDEF
|
||||
#endif /* !defined(_CURSES_HAS_KEY_METHODDEF) */
|
||||
|
||||
#ifndef _CURSES_ALLOC_PAIR_METHODDEF
|
||||
#define _CURSES_ALLOC_PAIR_METHODDEF
|
||||
#endif /* !defined(_CURSES_ALLOC_PAIR_METHODDEF) */
|
||||
|
||||
#ifndef _CURSES_FIND_PAIR_METHODDEF
|
||||
#define _CURSES_FIND_PAIR_METHODDEF
|
||||
#endif /* !defined(_CURSES_FIND_PAIR_METHODDEF) */
|
||||
|
||||
#ifndef _CURSES_FREE_PAIR_METHODDEF
|
||||
#define _CURSES_FREE_PAIR_METHODDEF
|
||||
#endif /* !defined(_CURSES_FREE_PAIR_METHODDEF) */
|
||||
|
||||
#ifndef _CURSES_RESET_COLOR_PAIRS_METHODDEF
|
||||
#define _CURSES_RESET_COLOR_PAIRS_METHODDEF
|
||||
#endif /* !defined(_CURSES_RESET_COLOR_PAIRS_METHODDEF) */
|
||||
|
||||
#ifndef _CURSES_NEW_PRESCR_METHODDEF
|
||||
#define _CURSES_NEW_PRESCR_METHODDEF
|
||||
#endif /* !defined(_CURSES_NEW_PRESCR_METHODDEF) */
|
||||
|
|
@ -4698,4 +4862,4 @@ _curses_has_extended_color_support(PyObject *module, PyObject *Py_UNUSED(ignored
|
|||
#ifndef _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF
|
||||
#define _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF
|
||||
#endif /* !defined(_CURSES_ASSUME_DEFAULT_COLORS_METHODDEF) */
|
||||
/*[clinic end generated code: output=8188ebf7404d028a input=a9049054013a1b77]*/
|
||||
/*[clinic end generated code: output=35a3d93708112587 input=a9049054013a1b77]*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue