Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the recommended way to implement text scrolling in ncurses?

Tags:

c

ncurses

curses

I am trying to implement an ncurses app with text scrolling like less. What is the recommended way to do this?

Here's what I know:

  1. You can use scroll to move the text buffer up or down by 1 row. However, you'll end up with one blank line at the top if you scroll down, or at the bottom if you scroll up, which you'll have to repaint yourself.
  2. Ncurses does wordwrap for you, which messes up my math when I have to determine what line I have to repaint in step 1.

I guess I could re-implement wordwrap myself and keep an array of all the post-wrapped lines, but this seems like a common problem, so there might be a better way.

like image 985
airportyh Avatar asked Aug 02 '11 13:08

airportyh


People also ask

What is ncurses and how does it work?

Drawing text to the terminal screen can have a performance impact on certain systems, especially on older hardware terminals. So ncurses lets you "stack up" a bunch of text to display to the screen, then use the refresh () function to make all of those changes visible to the user.

How to create scrolling text in HTML?

You can create scrolling text in HTML using the <marquee> tag, or you can create CSS scrolling text (the preferred method). You can make your text scroll from right to left.

How do I move text to a specific location in ncurses?

Moving to a screen location and displaying text is such a common thing that ncurses provides a shortcut to do both at once. The mvaddch (row, col, c) function will display a character at screen location row,col. And the mvaddstr (row, col, s) function will display a string at that location.

Is there a way to make text scroll like snow?

Here's a little trick that creates scrolling text that falls like snow. To use this, change the bit that reads Scrolling text... to the text that you want to scroll. You can also change any of the other settings to modify how the text falls, how high the falling text is, etc.


3 Answers

The answer depends on how much text you have, and what other uses you are making of the data on the screen. But usually the pad feature is preferred way to display scrollable text. This is not only an ncurses feature, but is supported by most implementations of curses (i.e., anything after the late 1980s).

A pad is like a window, but its size is not limited to the current screen size. Instead, the data is shown through a view whose position within the pad can be easily changed.

There are two sample programs in ncurses-examples which are relevant: view.c displays a file by writing its contents onto a window, while padview.c uses a pad on which the entire file is drawn, and uses the pad-functions for moving the view around as needed, up/down, and left/right.

In those programs, the show_all function does the actual drawing, and is about a third as long for padview.c (35 lines) compared to the equivalent for view.c (94 lines).

Further reading: discussion of view as an example for comparing ncurses and slang libraries.

like image 90
Thomas Dickey Avatar answered Oct 29 '22 09:10

Thomas Dickey


I'm not very experienced in ncurses yet, so I don't know all the library functions, but keeping a doubly-linked list of text lines could work. Here's a primitive clone of less:

#include <ncurses.h>
#include <stdlib.h>

#define MAXLEN 128

typedef struct Line {
    char contents[MAXLEN];
    struct Line *prev, *next;
} Line;

Line *head, *curr;

int nr_lines;
int curr_line;

int term_rows, term_cols;

void load(const char *filename);
void draw(Line *l);

int main(int argc, char **argv)
{
    if (argc == 1)
    {
        fputs("less: No file to open\n", stderr);
        return 1;
    }

    initscr();
    noecho();
    keypad(stdscr, TRUE);   // for KEY_UP, KEY_DOWN
    getmaxyx(stdscr, term_rows, term_cols);
    addstr("Reading text...\n");

    load(argv[1]);

    curr = head;
    curr_line = 0;
    draw(curr);

    int ch;
    while ((ch = getch()) != EOF && ch != 'q')
    {
        if (ch == KEY_UP && curr->prev != NULL)
        {
            curr_line--;
            curr = curr->prev;
        }
        else if (ch == KEY_DOWN && curr->next != NULL
            && curr_line + term_rows < nr_lines)
        {
            curr_line++;
            curr = curr->next;
        }
        draw(curr);
    }

    endwin();
    return 0;
}

void load(const char *filename)
{
    FILE *fp = fopen(filename, "r");
    Line *lp;

    head = malloc(sizeof(Line));
    head->prev = head->next = NULL;
    curr = head;

    while (fgets(curr->contents, MAXLEN, fp))
    {
        lp = malloc(sizeof(Line));
        lp->prev = curr;
        curr->next = lp;
        curr = lp;
        nr_lines++;
    }
    curr->next = NULL;
}

void draw(Line *l)
{
    int i;
    clear();
    for (i = 0; i < term_rows && l != NULL; i++, l = l->next)
        addstr(l->contents);
}

I'm not sure how this would handle lines longer than 128 characters, but that's a different problem with a different solution.

As for word wrapping, if you don't need to preserve the original text format you could split long lines into two as they're read from the file.

like image 25
thko Avatar answered Oct 29 '22 11:10

thko


ncurses only knows about the current screen. When the screen is scrolled ncurses does not keep track of any text that leaves the screen. Your terminal might have a scroll back buffer but that is independent of ncurses and curses has no visibility into the terminal's scroll back buffer.

I expect that you will need to keep track of your current location in the file if you need to scroll.

I didn't think ncurses did word wrap how are you doing the word wrap?

like image 37
Craig Avatar answered Oct 29 '22 10:10

Craig