17 Feb 2013 Curses: Panels vs Pads.

I've finally stumbled upon this one: For one of the new pacmixer features, I need to have a panel which is always above the main application window. Unfortunately, this main window is a pad. And here comes the trouble: You cannot place a pad inside a panel...

Consider this simple example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <curses.h>
#include <panel.h>


int main() {
    initscr();
    cbreak();
    curs_set(0);
    refresh();
    /* Creating windows */
    WINDOW *topwin = newwin(10, 10, 10, 10);
    WINDOW *bottompad = newpad(20, 20);
    /* Creating panels */
    PANEL *winp = new_panel(topwin);
    PANEL *padp = new_panel(bottompad);
    /* Putting borders around, so we can actually see something */
    box(topwin, 0, 0);
    box(bottompad, 0, 0);
    /* Updating (unnecessary if pads were working properly) */
    update_panels();
    pnoutrefresh(bottompad, 0, 0, 15, 15, 35, 35);
    /* Putting window on top of pad */
    top_panel(winp);
    /* Updating panels */
    update_panels();
    doupdate();
    getch();
    del_panel(padp);
    del_panel(winp);
    delwin(bottompad);
    delwin(topwin);
    endwin();
    return 0;
}

You (and me too) suppose that the small rectangle (represented by topwin) will be on top of the other? Nope, panel library doesn't know pads, so it won't do it right and the pad will come above. In fact, we shouldn't use a pnoutrefresh call here, but without it we would not see the pad at all, as update_panels doesn't know how to refresh it.

Actually, it makes sense, right? Because we're telling pad which part to update (and it can, of course, differ for different pads), but update_panels refreshes all panels in our application, so there really is no reasonable way to tell it which parts of pads we want right now.

What to do then? Copy! I remember my programming class teacher saying that copying is evil and learning us to use strange magic, like Prolog's difference lists, to avoid it. Good old times :). We want to have a regular window inside a panel, but with scrollable contents like a pad. It is done by creating a virtual pad (which we never copy to a physical screen) and copy appropriate parts of it to a normal window, which will then be displayed on the screen.

Fortunately, there is a routine in curses which does the copying part for us and it's called copywin. It's interface is similar to the one used by prefresh. So, the updated version of our example will look like this (lines added/changed end with //).

If you want to fill the whole window with copywin, you have to supply it with your window dimensions -1!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <curses.h>
#include <panel.h>


int main() {
    initscr();
    cbreak();
    curs_set(0);
    refresh();
    /* Creating windows */
    WINDOW *topwin = newwin(10, 10, 10, 10);
    WINDOW *bottompad = newpad(20, 20);
    WINDOW *padcopy = newwin(20, 20, 15, 15); //
    /* Creating panels */
    PANEL *winp = new_panel(topwin);
    PANEL *padp = new_panel(padcopy); //
    /* Putting borders around, so we can actually see something */
    box(topwin, 0, 0);
    box(bottompad, 0, 0);
    /* Copying pad into "copy window" */
    copywin(bottompad, padcopy, 0, 0, 0, 0, 19, 19, 0); //
    /* Putting window on top of pad */
    top_panel(winp);
    /* Updating panels */
    update_panels();
    doupdate();
    getch();
    del_panel(padp);
    del_panel(winp);
    delwin(padcopy); //
    delwin(bottompad);
    delwin(topwin);
    endwin();
    return 0;
}

We then operate on a pad and copy it to the "copy window" before refreshing the screen.