cncurses

Regarding window movement and refresh in ncurses


I'm exploring with a modified program based on ncurses window tutorial to move a window without deleting and remaking it each time using mvwin, and have run into the following questions:

  1. Moving the window leaves behind a trace of the old window (see example below code). I'm used to that, and needing to clear old characters with erase (comment 1 in code) or printing a newline at the start of each line. Looking at the docs, this looks like something a call to touchwin/touchline is supposed to solve, but don't. First question: what does touchwin do if not that?
  2. Erasing the entire screen, then reprinting the first line and moving the window will display the first line, but not the window after refresh. I tried to force displaying the moved window with redrawwin, but the only thing that worked is adding another refresh right after erasing the screen (comment 2 in code). I dislike that method, it causes screen flickering, and it feels like there has to be a better method. Second question: is there a way to display a moving window after erasing the screen without another refresh?
  3. In comment 3 in the code, after moving the window and reprinting the first line, calling refresh will display only the written line and not the moved window, but calling wrefresh(my_win) will display both the moved window and the printed line. Third question: why does wrefresh displays things outside the refreshed window?

example code:

int main()
{   WINDOW *my_win;
    int startx, starty, width, height;
    int ch, err;

    initscr();
    cbreak();
    keypad(stdscr, TRUE);

    height = 3;
    width = 10;
    starty = (LINES - height) / 2;
    startx = (COLS - width) / 2;
    
    refresh();
    my_win = create_newwin(height, width, starty, startx);

    while((ch = getch()) != KEY_F(4))
    {
        erase();  // <--(1)
        refresh();  // <--(2)

        switch(ch)
        {   
            case KEY_LEFT:
                mvprintw(0, 0, "moving window to position (%d, %d)", starty, --startx);
                err = mvwin(my_win, starty, startx);
                break;
            case KEY_RIGHT:
                mvprintw(0, 0, "moving window to position (%d, %d)", starty, ++startx);
                err = mvwin(my_win, starty, startx);
                break;
            case KEY_UP:
                mvprintw(0, 0, "moving window to position (%d, %d)", --starty, startx);
                err = mvwin(my_win, starty, startx);
                break;
            case KEY_DOWN:
                mvprintw(0, 0, "moving window to position (%d, %d)", ++starty, startx);
                err = mvwin(my_win, starty, startx);
                break;
            default:
                mvprintw(0, 0, "UNKNOWN INPUT!");
        }

        if (err != NCURSES_OK)
        {
            mvprintw(0, 0, "Error %d while moving window", err);
        }

        wrefresh(my_win);  // <--(3)
    }
        
    endwin();
    return 0;
}

exmaple for "leftover characters" after moving the window left twice:

moving window to position (6, 32)





                                ┌────────┐┐┐
                                │        │││
                                └────────┘┘┘



Solution

  • The basic problem is that curses does not manage overlapping windows. The window which you are moving using mvwin overlaps with stdscr. When you move it, stdscr is not repainted to reflect the fact that my_win used to occupy some parts of the screen which it no longer does.

    The panel library is designed to solve this problem nicely, but as a workaround, you can ensure that stdscr is repainted by using touchwin and then refresh. Repainting just a line is not effective unless you limit the program to moving the window up and down -- never left or right.

    The ncurses test-programs include one which demonstrates how to use mvwin (test/movewindow.c). The test-program keeps track of all of the windows and calls refresh_all after calling mvwin:

    static void
    repaint_one(WINDOW *win)
    {
        touchwin(win);
        wnoutrefresh(win);
    }
    
    static void
    refresh_all(WINDOW *win)
    {
        unsigned n;
    
        for (n = 0; n < num_windows; ++n) {
            if (all_windows[n].child != win) {
                repaint_one(all_windows[n].child);
            }
        }
    
        repaint_one(win);
        doupdate();
    }
    

    By the way, the "documentation" referred to in the question and suggested answer is very old - from 2009. Your local machine probably has manual pages installed which are newer than that.