Reputation: 735
I am starting to learn curses in Python. I am using Python 3.5 on Mac OS X. When I try to write in the bottom-right corner, the program crashes with the following error:
$ python ex_curses.py
[...]
File "ex_curses.py", line 19, in do_curses
screen.addch(mlines, mcols, 'c')
_curses.error: add_wch() returned ERR
The example program is:
import curses
def do_curses(screen):
curses.noecho()
curses.curs_set(0)
screen.keypad(1)
(line, col) = 12, 0
screen.addstr(line, col, "Hello world!")
line += 1
screen.addstr(line, col, "Hello world!", curses.A_REVERSE)
screen.addch(0, 0, "c")
(mlines, mcols) = screen.getmaxyx()
mlines -= 1
mcols -= 1
screen.addch(mlines, mcols, 'c')
while True:
event = screen.getch()
if event == ord("q"):
break
curses.endwin()
if __name__ == "__main__":
curses.wrapper(do_curses)
I have a feeling that I'm missing something obvious, but I don't know what.
Upvotes: 14
Views: 3603
Reputation: 735
After the @Thomas Dickey answer, I have added the following snippet to my code.
try:
screen.addch(mlines, mcols, 'c')
except _curses.error as e:
pass
Now my code looks like:
import curses
import _curses
def do_curses(screen):
curses.noecho()
curses.curs_set(0)
screen.keypad(1)
(line, col) = 12, 0
screen.addstr(line, col, "Hello world!")
line += 1
screen.addstr(line, col, "Hello world!", curses.A_REVERSE)
screen.addch(0, 0, "c")
(mlines, mcols) = screen.getmaxyx()
mlines -= 1
mcols -= 1
try:
screen.addch(mlines, mcols, 'c')
except _curses.error as e:
pass
while True:
event = screen.getch()
if event == ord("q"):
break
curses.endwin()
if __name__ == "__main__":
curses.wrapper(do_curses)
Upvotes: 5
Reputation: 54455
That is expected behavior (a quirk) because addch
attempts to wrap to the next line after adding a character. There is a comment in lib_addch.c dealing with this:
The _WRAPPED flag is useful only for telling an application that we've just
wrapped the cursor. We don't do anything with this flag except set it when
wrapping, and clear it whenever we move the cursor. If we try to wrap at
the lower-right corner of a window, we cannot move the cursor (since that
wouldn't be legal). So we return an error (which is what SVr4 does).
Unlike SVr4, we can successfully add a character to the lower-right corner
(Solaris 2.6 does this also, however).
Upvotes: 7
Reputation: 747
This is extension for Brian's answer. I decided to choose method based on writing position (y, x)
method = window.addch if (y+1, x+1) != window.getmaxyx() else window.inschr
method(y, x, char)
as the methods addch
and insch
have same call signatures.
Upvotes: 0
Reputation: 41
window.insch(...)
can place a character at the lower right of a window without advancing the cursor. Any character at that position will be bumped to the right without causing an error.
Upvotes: 4
Reputation: 1669
It is kind of exotic solution exploiting the fact that the curses
can actually draw a bottom-right character when drawing a border (without raising an exception). See the full example:
# -*- coding: utf-8 -*-
import curses
def addch_bottom_right(window, ch):
"""
Somehow, the underlying ncurses library has an issue
with writing a char into bottom-right corner
(see the https://stackoverflow.com/a/36389161 for example).
But we can use the workaround:
- create a subwindow 1x1 of the current window in the bottom-right corner
- draw a border of that window, consisting only of the desired character:
for a 1x1 window, that border will consist exclusively of this single character.
- refresh the screen to show your new 'window' with the 'border'.
"""
print("Putting char '%s' in the bottom-right corner" % ch)
beg_y, beg_x = window.getbegyx()
max_y, max_x = window.getmaxyx()
br_y = beg_y + max_y - 1
br_x = beg_x + max_x - 1
print('Coordinates of current window: %sx%s' % (br_y, br_x))
w = window.subwin(1, 1, br_y, br_x)
# only 'br' (bottom-right corner) gets printed for 1x1 box
w.border(*([ch] * 8))
w.noutrefresh()
window.noutrefresh()
curses.doupdate()
def demo(screen, show_border=True):
"""
Try the workaround with three different windows nesting levels.
Borders drawn here only to show where the windows are.
"""
curses.curs_set(0)
w = screen.subwin(8, 8, 10, 10)
if show_border:
w.border()
addch_bottom_right(w, 'Window'[0])
w2 = w.subwin(3, 3, 12, 12)
if show_border:
w2.box()
addch_bottom_right(w2, 'Subwindow'[0])
addch_bottom_right(screen, 'Main screen'[0])
screen.getch()
if __name__ == '__main__':
curses.wrapper(demo)
Upvotes: 0