Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions Doc/library/curses.rst
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,131 @@ Windows and pads
is to be displayed.


Soft labels
~~~~~~~~~~~

.. _curses-slk:

The following functions manage *soft-label keys*, a row of labels displayed
along the bottom line of the screen, typically used to label a row of function
keys. :func:`slk_init` must be called before :func:`initscr` or
:func:`newterm`; it takes one screen line away from the standard window for the
labels.


.. function:: slk_init(fmt=0)

Reserve a screen line for the soft labels and choose their layout. *fmt*
selects the arrangement: ``0`` for 3-2-3 (eight labels), ``1`` for 4-4
(eight labels). Where the underlying curses library supports them, ``2``
gives 4-4-4 (twelve labels) and ``3`` gives 4-4-4 with an index line.

Must be called before :func:`initscr` or :func:`newterm`.

.. versionadded:: next


.. function:: slk_set(labnum, label, justify)

Set the text of soft label number *labnum*, in the range ``1`` through ``8``
(or ``12`` in a twelve-label layout). *justify* controls how *label* is
placed within the label: ``0`` for left, ``1`` for centered, ``2`` for right.

.. versionadded:: next


.. function:: slk_label(labnum)

Return the current text of soft label number *labnum*, justified as it was
set, or an empty string if it has no label.

.. versionadded:: next


.. function:: slk_refresh()

Update the soft labels on the physical screen, like
:meth:`~curses.window.refresh` for a window.

.. versionadded:: next


.. function:: slk_noutrefresh()

Update the soft labels on the virtual screen, like
:meth:`window.noutrefresh`. Use it together with :func:`doupdate` to batch
screen updates.

.. versionadded:: next


.. function:: slk_clear()

Remove the soft labels from the screen.

.. versionadded:: next


.. function:: slk_restore()

Restore the soft labels to the screen after a :func:`slk_clear`.

.. versionadded:: next


.. function:: slk_touch()

Force all the soft labels to be redrawn by the next :func:`slk_refresh` or
:func:`slk_noutrefresh`.

.. versionadded:: next


.. function:: slk_attron(attr)
slk_attroff(attr)
slk_attrset(attr)

Add, remove, or set the attributes used to display the soft labels, given as
packed ``A_*`` attributes.

.. versionadded:: next


.. function:: slk_attr()

Return the current attributes of the soft labels as packed ``A_*``
attributes. Availability depends on the underlying curses library.

.. versionadded:: next


.. function:: slk_attr_on(attr)
slk_attr_off(attr)

Turn the given attributes on or off without affecting any others. Like the
``attr_*`` window methods, these work with the
:ref:`WA_* attributes <curses-wa-constants>` rather than packed ``A_*``
attributes.

.. versionadded:: next


.. function:: slk_attr_set(attr, pair=0)

Set the attributes and color pair of the soft labels. *attr* is given as
:ref:`WA_* attributes <curses-wa-constants>` and *pair* as a color pair
number.

.. versionadded:: next


.. function:: slk_color(pair)

Set the color pair of the soft labels to color pair number *pair*.

.. versionadded:: next


Saving and restoring
~~~~~~~~~~~~~~~~~~~~

Expand Down
12 changes: 12 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,18 @@ curses
:func:`~curses.scr_set`, which dump the whole screen to a file and restore it.
(Contributed by Serhiy Storchaka in :gh:`152260`.)

* Add the soft-label-key functions to the :mod:`curses` module, which manage a
row of labels along the bottom line of the screen:
:func:`~curses.slk_init`, :func:`~curses.slk_set`, :func:`~curses.slk_label`,
:func:`~curses.slk_refresh`, :func:`~curses.slk_noutrefresh`,
:func:`~curses.slk_clear`, :func:`~curses.slk_restore`,
:func:`~curses.slk_touch`, the attribute functions
:func:`~curses.slk_attron`, :func:`~curses.slk_attroff`,
:func:`~curses.slk_attrset`, :func:`~curses.slk_attr`,
:func:`~curses.slk_attr_on`, :func:`~curses.slk_attr_off`,
:func:`~curses.slk_attr_set`, and :func:`~curses.slk_color`.
(Contributed by Serhiy Storchaka in :gh:`152263`.)

* Add the :func:`curses.term_attrs` function, which returns the supported
video attributes as :ref:`WA_* <curses-wa-constants>` values, the
counterpart of :func:`curses.termattrs`.
Expand Down
119 changes: 109 additions & 10 deletions Lib/test/test_curses.py
Original file line number Diff line number Diff line change
Expand Up @@ -2843,16 +2843,12 @@ def test_move_down(self):
self.mock_win.reset_mock()


@unittest.skipUnless(hasattr(curses, 'newterm'), 'requires curses.newterm()')
@unittest.skipIf(not term or term == 'unknown',
"$TERM=%r, newterm() may not work" % term)
@unittest.skipIf(sys.platform == "cygwin",
"cygwin's curses mostly just hangs")
class ScreenTests(unittest.TestCase):
# newterm()/set_term() mutate global curses state, but each test drives its
# own pseudo-terminal(s) and never touches the screen shared by TestCurses,
# whose setUp() makes that screen current again. So these can run in this
# process, without a real terminal and without a subprocess.
class NewtermTestBase(unittest.TestCase):
# Shared plumbing for tests that drive newterm() over their own
# pseudo-terminal(s). newterm()/set_term() mutate global curses state, but
# each test never touches the screen shared by TestCurses, whose setUp()
# makes that screen current again. So these can run in this process,
# without a real terminal and without a subprocess.

def setUp(self):
# newterm() may install signal handlers; restore them afterwards.
Expand Down Expand Up @@ -2906,6 +2902,14 @@ def stop_reader():
self.addCleanup(stop_reader)
return slave


@unittest.skipUnless(hasattr(curses, 'newterm'), 'requires curses.newterm()')
@unittest.skipIf(not term or term == 'unknown',
f"$TERM={term!r}, newterm() may not work")
@unittest.skipIf(sys.platform == "cygwin",
"cygwin's curses mostly just hangs")
class ScreenTests(NewtermTestBase):

def test_newterm(self):
s = self.make_pty()
screen = curses.newterm('xterm', s, s)
Expand Down Expand Up @@ -2979,5 +2983,100 @@ def test_disallow_instantiation(self):
check_disallow_instantiation(self, curses.screen)


@unittest.skipUnless(hasattr(curses, 'slk_init'), 'requires curses.slk_init()')
@unittest.skipUnless(hasattr(curses, 'newterm'), 'requires curses.newterm()')
@unittest.skipIf(not term or term == 'unknown',
f"$TERM={term!r}, newterm() may not work")
@unittest.skipIf(sys.platform == "cygwin",
"cygwin's curses mostly just hangs")
class SLKTests(NewtermTestBase):
# Soft-label keys reserve the bottom screen line for a row of labels.
# slk_init() must run before newterm()/initscr(), so each test sets up its
# own screen rather than reusing the one TestCurses builds in setUp().

def make_slk_screen(self, fmt=0):
s = self.make_pty()
curses.slk_init(fmt)
return curses.newterm('xterm', s, s)

def test_init_reserves_a_line(self):
# Every layout takes the bottom line for the labels; the index-line
# layout (3) takes a second line for the index. Layouts 0 and 1 are
# standard; 2 and 3 are ncurses extensions that other curses
# implementations reject (slk_init() then returns an error).
ncurses = hasattr(curses, 'ncurses_version')
for fmt, lines in [(0, 23), (1, 23), (2, 23), (3, 22)]:
with self.subTest(fmt=fmt):
try:
screen = self.make_slk_screen(fmt)
except curses.error:
if ncurses or fmt < 2:
raise
continue
self.assertEqual(screen.stdscr.getmaxyx()[0], lines)
curses.endwin()

def test_init_bad_format(self):
for fmt in (-1, 4):
self.assertRaises(ValueError, curses.slk_init, fmt)

def test_set_and_label(self):
self.make_slk_screen()
curses.slk_set(1, 'Help', 0)
curses.slk_set(2, 'Save', 1)
curses.slk_set(3, 'Quit', 2)
self.assertEqual(curses.slk_label(1), 'Help')
self.assertEqual(curses.slk_label(2), 'Save')
self.assertEqual(curses.slk_label(3), 'Quit')

def test_set_wide(self):
screen = self.make_slk_screen()
label = 'Ångström'
try:
label.encode(screen.stdscr.encoding)
except UnicodeEncodeError:
self.skipTest('the locale cannot encode %r' % label)
curses.slk_set(1, label, 0)
self.assertEqual(curses.slk_label(1), label)

def test_set_bad_justify(self):
self.make_slk_screen()
for justify in (-1, 3):
self.assertRaises(ValueError, curses.slk_set, 1, 'x', justify)

def test_refresh(self):
self.make_slk_screen()
curses.slk_set(1, 'Help', 0)
curses.slk_noutrefresh()
curses.slk_refresh()
curses.slk_clear()
curses.slk_restore()
curses.slk_touch()

def test_attributes(self):
self.make_slk_screen()
curses.slk_attron(curses.A_BOLD)
curses.slk_attrset(curses.A_UNDERLINE)
curses.slk_attroff(curses.A_BOLD)
if hasattr(curses, 'slk_attr'):
self.assertIsInstance(curses.slk_attr(), int)

def test_attr_on_off(self):
self.make_slk_screen()
curses.slk_attr_on(curses.A_BOLD)
curses.slk_attr_off(curses.A_BOLD)

def test_color(self):
# slk_attr_set() and slk_color() act on a color pair, so the color
# subsystem must be started first.
self.make_slk_screen()
if not curses.has_colors():
self.skipTest('requires colors support')
curses.start_color()
curses.slk_attr_set(curses.A_BOLD)
curses.slk_attr_set(curses.A_BOLD, 0)
curses.slk_color(0)


if __name__ == '__main__':
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Add the soft-label-key functions to the :mod:`curses` module:
:func:`~curses.slk_init`, :func:`~curses.slk_set`, :func:`~curses.slk_label`,
:func:`~curses.slk_refresh`, :func:`~curses.slk_noutrefresh`,
:func:`~curses.slk_clear`, :func:`~curses.slk_restore`,
:func:`~curses.slk_touch`, :func:`~curses.slk_attron`,
:func:`~curses.slk_attroff`, :func:`~curses.slk_attrset`,
:func:`~curses.slk_attr`, :func:`~curses.slk_attr_on`,
:func:`~curses.slk_attr_off`, :func:`~curses.slk_attr_set` and
:func:`~curses.slk_color`.
Loading
Loading