8

I have the following code that allows you to scroll up and down a pad of text. Each time you scroll (i.e. handle a user input) the pad updates as expected. However, before the first key is pressed nothing is shown, despite that I'm calling pad.refresh() just as I do after each user input.

My code looks like this :

def main(self,stdscr):

    x,y = 20,150 # size of the window
    u,a = 10,20 # where to place window - up,across
    pad = curses.newpad(20,150) # nlines, ncols
    pad_pos = 0
    exit = False

    pad.addstr(0,0,str(self.all_results))

    while not exit:
        pad.addstr(0,0,str(self.format_results()))
        ++ stdscr.refresh()
        pad.refresh(pad_pos,10, u,a, x,y)

        -- cmd = stdscr.getch()
        ++ cmd = pad.getch()

        stdscr.nodelay(1)

        + pad.getch() - caused the screen not to update
        + stdscr.refresh() - no change

        if cmd != -1:
            + pad.getch() - - caused the screen not to update
            if  cmd == curses.KEY_DOWN:
                if pad_pos < 3:
                    pad_pos += 1
                try:
                    pad.refresh(pad_pos,0, u,a, x,y)
                except curses.error:
                    pass
            elif cmd == curses.KEY_UP:
                if pad_pos != 0:
                    pad_pos -= 1
                try:
                    pad.refresh(pad_pos,0, u,a, x,y)
                except curses.error:
                    pass

Edit : changes shown within code as to what has been tried (+,++,--)

Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
felix001
  • 15,341
  • 32
  • 94
  • 121

3 Answers3

13

There seems to be the (AFAIK undocumented) necessity to refresh the stdscr once, i.e. call stdscr.refresh(), before everything works as expected. I incorporated this into your example (which I previously had to make working at all - see below):

import curses

def main(stdscr):
    stdscr.keypad(True)
    stdscr.refresh() # This is the necessary initial refresh

    heigth, width = 20, 150 # size of the pad
    top, left = 10, 20 # where to place pad
    viewportHeight = 10
    scrollOffset = 0

    # generate a string to fill pad
    string = ''.join(chr(ord('a') + (x % 26)) for x in range(heigth*width-1))

    pad = curses.newpad(heigth, width)
    pad.addstr(string)
    pad.refresh(scrollOffset, 0, top, left, top + 10, left + width)

    cmd = stdscr.getch()
    while True:
        if  cmd == curses.KEY_DOWN and scrollOffset < heigth - viewportHeight - 1:
            scrollOffset += 1
        elif cmd == curses.KEY_UP and scrollOffset > 0:
            scrollOffset -= 1
        if cmd:
            pad.refresh(scrollOffset, 0, top, left, top + 10, left + width)
        cmd = stdscr.getch()

curses.wrapper(main)

A few remarks:

  • For the future I recommend you to use the curses.wrapper(main)-function which sets up the curses environment and calls your main(stdscr) method with the stdscr as parameter. It also takes care to tear down curses appropriately, which prevents a broken terminal if something goes wrong in your code. Maybe you want to check out the helpful tutorial by Kuchling and Raymond on how to use the python curses package.

  • Please provide a reproducible example. Yours contains variables such as self.all_result where I had to make guesses in order to understand your question. In fact I spent more time figure out what your code is trying to achieve exactly and to reproduce the erroneous behavior described by you, than to find the actual solution.

If you edit your question to bring it down to the actual problem - I think it could be very helpful to many people using Python's curses module.

I just realized you offered a bounty for a response drawing from official sources, so I dug around a little bit more and found an equivalent response on SO, that also suggests, that this - let's call it bug - is undocumented so far. My best guess for the reason doing some libncurses research, would be that the curses.wrapper in my case, you manually, or even libnurses by itself called an clear screen method at any point during the initialization. Thus, the clear flag might be set, which consequently clears the screen on the first refresh instead of the expected behavior.

Dharman
  • 30,962
  • 25
  • 85
  • 135
jan.vogt
  • 1,801
  • 10
  • 27
  • I believe this will work with a string that is not constantly changing. But the string I am using is updating constantly. – felix001 Oct 14 '14 at 20:16
  • After testing all of this the answer below fixed my issue. However thanks for all the info, much appreciated. – felix001 Oct 14 '14 at 20:32
4

stdscr.getch() causes an implicit refresh of stdscr (not updated prior to this), which erases pad from the screen by overwriting it with the blank stdscr. Try pad.getch(), or else refresh stdscr before the first pad.refresh().

William McBrine
  • 2,166
  • 11
  • 7
  • I tried these suggestions but I still face the same issue. – felix001 Sep 26 '14 at 09:22
  • If you tried those suggestions, it's not apparent from the edited code above. – William McBrine Oct 01 '14 at 02:16
  • Those lines are not *before* the first call to `pad.refresh()`. Also, the intended use of `pad.getch()` is to replace `cmd = stdscr.getch()` with `cmd = pad.getch()`, not just to stick in a `pad.getch()` call after you've already called `stdscr.getch()`. – William McBrine Oct 01 '14 at 18:40
  • Ive also added the changes you mention based on ++ -- and still no joy. You could always update your answer with how you think code should look :-) – felix001 Oct 02 '14 at 08:12
0

Try calling stdscr.nodelay(1) BEFORE you hang up on stdscr.getch().

mp_
  • 705
  • 3
  • 7