NCurses

Ncurses je v Linuxu poměrně populární knihovna. Využívá ji mnoho programů, ačkoliv v dnešní době dávají lidé přednost spíše grafickým udělátkům před textovými. Mezi mé oblíbené ncurses programy patří například mc, vim, vifm nebo mp3blaster.

V této kapitole se mimo jiné naučíte vykreslovat barvy, okna, psát na různé pozice na terminálu.

Úvod

Ncurses je knihovna, která vám umožní dělat grafické udělátka v textovém režimu. Ncurses znamená „new curses“, jde tedy o novou verzi starší curses knihovny.

Ncurses nabízí velkou sadu funkcí. Umí kreslit po celé obrazovce, měnit barvy, pracovat s myší, měnit režim klávesnice atp. Na první pohled se může zdát, že je tady hodně se co učit. A taky jsem si tentokrát připravil hodně příkladů. Ale nakonec zjistíte, že ovládnout ncurses nedá zase tak moc práce, protože rozhraní funkcí je jednoduché a blbuvzdorné :-).

Instalace a překlad

Jako obvykle je potřeba nainstalovat vývojové balíčky. A případně i dokumentaci. Tak třeba v Debianu najdete tyto balíčky:

ncurses-doc
Dokumentace, manuálové stránky. Bez toho se žádný dobrý programátor neobejde.
libncurses5-dev
Vývojový balíček ncurses (bez podpory UTF8 …)
libncursesw5-dev
Vývojový balíček ncures s podporou výceznakového kódování (např. UTF8). To je přesně balíček, který potřebujete.

V OpenSUSE místo balíčku libncurses5-dev a libncursesw5-dev najdete balíček ncurses-devel, který obsahuje obě verze.

Při překladu musíte uvést knihovnu ncurses nebo ncursesw. Jestli chcete, aby se správně zobrazovala čeština (UTF-8), tak používejte jen ncursesw. Příklad:

$ clang -o terminal terminal.c `pkg-config --cflags --libs ncursesw`

Funkce jsou deklarované v hlavičkovém souboru <ncurses.h>. Cestu k němu (volbu -I) najde příkaz `pkg-config --cflags ncursesw`. Více viz pkg-config.

V OpenSUSE se objevil bug, kvůli kterému příkaz `pkg-config` pro ncurses(w) nefunguje. Zkuste místo něj použít jednoduše volbu -lncurses (-lncursesw).

Ovládání klávesnice

První příklad vám představí některé funkce na ovládání režimu klávesnice. Jedná se o obdobu příkladu z kapitoly Terminál – knihovna termios.h.

Inicializace a ukonční Ncuress

Před použitím ncurses musíte inicializovat terminál zavoláním funkce initscr(). Po skončení práce s ncurses zase musíte terminál „uklidit“ voláním endwin(). Tím se terminál vrátí do normálního režimu.

WINDOW *initscr(void);
int endwin(void);

Struktura WINDOW se používá k ukládání zdrojů pro okna, která můžete v ncurses vytvářet. Funkce initscr() vrací odkaz na okno, které je také dostupné přes globální proměnnou stdscr (standardní screen). Nebo vrací NULL, když selže.

Okno stdscr reprezentuje celý terminál. Můžete si vytvářet i menší okna, pokud chcete. O tom viz dále.

Funkce endwin() vrací v případě úspěchu makro OK (což bude nejspíš nula), jinak ERR.

Funkce initscr() smaže celou obrazovku a nastaví tzv. curses mód. V tomto módu nefungují tradiční funkce jako je printf(), scanf() atp. Ncurses používá své vlastní funkce pro IO (inupt/output), jako jsou třeba printw(), scanw(). a getch().

int printw(const char *fmt, ...);
int scanw(char *fmt, ...);
int getch(void);

Funkce printw() tiskne do okna stdscr. Prvním argumenem je formátovací řetězec (stejně jako u printf()).
Funkce scanw() je zase obdobou funkce scanf().
Funkce getch() je obdobou funkce getchar(). Tyto funkce si nepleťte!

Pokud program skončí, aniž by zavolal endwin(), bude obrazovka v nepoužitelném stavu. (V takovém případě se ji můžete pokusit resetovat příkazem reset nebo tset). Abych se ujistil, že bude endwin() zavoláno, dal jsem ho do funkce obnovTerminal(), kterou registruji pomocí funkce atexit() z knihovny <stdlib.h>. Díky tomu bude funkce zavolána po skončení programu návratem z funkce main(), zavoláním funkce exit() i při ukončení programu klávesovou zkratkou CTRL+c.

Pozor! Funkce pro zápis do oken zapisují pouze do bufferu okna (do paměti). K tomu, aby se změny v bufferu vypsaly na obrazovku, se musí zavolat funkce refresh().
Častou chybou začátečníků je opomenutí této funkce. Pak se diví, že se nic nevypsalo.

Zápis do bufferu a následné vypsání všeho zapsaného pomocí refresh() umožňuje ncurses výstup optimalizovat. To se hodilo na pomalých terminálech, dnes se to stále hodí při práci přes internet.

int refresh(void);

Výše popsané najdete v každém programu, který jsem pro tuto kapitolu napsal.

Nastavení klávesnice

A teď k funkcím, které ovládají režim klávesnice.

int noecho(void);
int echo(void);

Funkce noecho() vypne zobrazování uživatelem zadaných znaků na obrazovce. Funkce echo() zobrazování zase zapne. Obě funkce vracejí v případě chyby ERR.

int raw();
int noraw();

Funkce raw() vypne generování signálů klávesovými zkratkami, jako je CTRL+c. Funkce noraw() to zase zapne. Obě funkce vracejí v případě chyby ERR.

Funkce raw() taky vypne buffer pro vstup z klávesnice, takže každá stisknutá klávesa je ihned předána programu.

int cbreak();
int nocbreak();

Funkce cbreak()nocbreak() dělají to samé jako raw()noraw(), jenom nevypínají speciální význam zkratek, jako je CTRL+c.

Když jsem omylem použil funkce getchar() místo getch(), a zavolal jsem funkci raw() bez toho, abych předtím zavolal nocbreak(), tak se mi nevracel znak '\n'. Proč tomu tak je nevím. Každopádně, používat s ncurses getchar() místo getch() je chyba.

  1. /*------------------------------------------------*/
  2. /* 51ncurses/terminal.c                           */
  3. #include <ncurses.h>
  4. #include <unistd.h>
  5. #include <stdlib.h>
  6.  
  7. static void obnovTerminal()
  8. {
  9.     printw("Obnovuji nastavení terminálu\n");
  10.     refresh();
  11.     sleep(1);
  12.     endwin();
  13. }
  14.  
  15. int main()
  16. {
  17.     char ch;
  18.  
  19.     initscr();
  20.     noecho();
  21.     //nocbreak();
  22.     raw();
  23.     /* nezapomenout na obnoveni nastaveni terminalu! */
  24.     atexit(obnovTerminal);
  25.  
  26.     printw("Zadávání znaků ukončíte klávesou Enter:\n");
  27.     refresh();
  28.     do {
  29.         ch = getch();
  30.         printw("ch = %c\n", ch);
  31.         refresh();
  32.     } while (ch != '\0' && ch != '\n');
  33.  
  34.     return EXIT_SUCCESS;
  35. }
  36. /*------------------------------------------------*/

Překlad:

$ gcc -o terminal terminal.c `pkg-config --libs ncurses`

Výstup z programu může vypadat takto:

Zadávání znaků ukon?~Míte klávesou Enter:
ch = S
ch = a
ch = l
ch = l
ch = y
ch = x
ch = ^C
ch = ^C
ch =

Obnovuji nastavení terminálu

Po jedné vteřině po stisku enteru celý výstup zmizí (obrazovka se vrátí do normálního režimu zavoláním funkce obnovTerminal()).

Možná by vás také mohla zajímat funkce halfdelay(), která nastavuje mód jako cbreak(), ale blokuje jen zadaný počet desetin sekundy. Když uživatel včas nic nestiskne, funkce přestane čekat na vstup, vrátí chybu a program jede dál. Viz manuálové stránky.

Kódování

Jak vidíte v příkladu výše, ne všechny písmenka se zobrazují správně. Od toho je tu knihovna ncursesw. Aby tato verze ncurses fungovala správně, musíte nastavit locale.

Přidejte do předchozího zdrojového kódu hlavičkový soubor <locale.h> a do funkce main() tuto řádku:

setlocale(LC_ALL, "");

Překlad:

$ clang -o terminalw terminalw.c `pkg-config --libs ncursesw`

A teď už se budou vypisovat UTF-8 znaky správně.

Ale aby to nebylo tak jednoduchý, tak funkce getch() bude pořád vracet jeden bajt (přetypovaný na int), což znamená, že výcebajtové znaky UTF-8 bude vracet po částech.

Porvat se s tím dá obdobně, jako je popsáno v kapitole Konverze znakových sad.

Ncursesw nabízí novou sadu funkcí pro práci s širokými znaky. Jejich seznam najdete v manuálové stránce k ncurses (man ncurses).
Namátkou:

int get_wch(wint_t *wch);           //nacte do wch jeden znak
int getn_wstr(wint_t *wstr, int n); // nacte retezec, max n znaku

Funkce getn_wstr() pracuje, bohužel, s polem typu wint_t namísto wchar_t. To je bug, se kterým jsou všichni happy, takže ho nikdo neopraví. V Linuxu platí, že sizeof(wint_t) == sizeof(wchar_t), proto to (zatím) není problém. Stačí, když argument přetypujete z (wchar_t *) na (wint_t *). (Ostatní funkce na práci s širokými znaky, jako je třeba wcscmp(), používají (správně) wchar_t *.

Pokud tyto funkce budete používat, musíte použít hlavičkový soubor <ncursesw/curses.h> namísto <ncurses.h>.

Jako příklad vám může posloužit program znamky. Je to jeden z prvních programů, které jsem psal, takže to není úplně učebnicový zdrojový kód (zvlášť práce s hlavičkovými soubory je zmatená, program neraguje na WINCH atp.). Ale je to docela jednoduchý a funkční program, který pracuje s ncurses a UTF-8 znaky. Taky si zopakujete práci s DBM :-).

Barvy

První, co musíte udělat, pokud chcete používat barvy, je, že zavoláte funkci has_colors(), která zjišťuje, jestli terminál barvy podporuje. Dneska už asi těžko narazíte na terminál, který barvy nepodporuje, ale je to slušnost.

Další funkci, kterou zavoláte, je start_color(), kterou si ncruses inicializuje potřebné struktury, které při práci s barvami potřebuje.

int start_color(void);
int has_colors(void);

Tyto funkce, spolu s dalšími, které volám v každém příkladu, jsem přesunul do funkce initNcurses(), aby byly příklady trochu přehlednější.

Ncurses poskytuje několik užitečných maker:

COLS
Počet sloupců v terminálu (kolik znaků se vejde do jedné řádky)
LINES
Počet řádek
COLORS
Maximální počet barev, který terminál zvládne. Z historických důvodů je jich často k dispozici jen 8.
COLOR_PAIR
Maximální počet barevných párů. Měl by jich být alespoň 64, tedy 8x8. Barevný pár je dvojice barev popředí a pozadí.

S ncurses nenastavujete barvu popření a pozadí zvlášť, ale vždycky si musíte nejdřív vytvořit barevný pár (popředí + pozadí) pomocí funkce init_pair().

Barevný pár se aktivuje funkcí attrset(), ale nepředává se přímo jako číslo inicializované funkcí init_pair(), nýbrž se předává pomocí makra COLOR_PAIR(). Proč je tomu tak, to se můžeme jen domnívat :-).

Funkcí attrset() se nastasvují ještě další vlastnosti terminálu, jako je třeba A_BOLD, který nastavuje zvýrazněné písmo. K nastavování atributů se ještě vrátím podrobněji v dalším příkladu.

Atribut, jako je A_BOLD, se dá vypnout pomocí funkce attroff().

int init_pair(short pair, short f, short b);

Funkce init_pair() vytvoří barevný pár s barvou popředí f a barvou pozadí b. Nově vytvořený barevný pár bude dostupný pod číslem pair.

Ncurses definuje 8 základních barev, které můžete pro popředí a pozadí použít. Najdete je v <ncurses.h>.

/* colors */
#define COLOR_BLACK     0
#define COLOR_RED       1
#define COLOR_GREEN     2
#define COLOR_YELLOW    3
#define COLOR_BLUE      4
#define COLOR_MAGENTA   5
#define COLOR_CYAN      6
#define COLOR_WHITE     7

Existují sice funkce, které vám umožní namýchat si téměř libovolný barevný odstín, ale co jsem je zkoušel, tak moc nefungují. Terminály to bohužel nepodporují. Takže si musíte vystačit s těmito 8 barvami.

int attrset(int attrs);
int attron(int attrs);
int attroff(int attrs);

Zatímco funkce attrset() nastaví terminál tak, aby byly aktivní jen zadané volby, funkce attron() jenom zapne předané atributy a ostatní nechá neovlivněné a attroff() jen vypne předané atributy.

V příkladu také nově používám funkci mvprintw(). Tiskne na obrazovku stejně jako printw(), jenom před tiskem posune kurzor na zadanou pozici. Stejného efektu docílíte zavoláním funkce move() a následně printw().

int mvprintw(int y, int x, const char *fmt, ...);
int move(int y, int x);

Argument y je řádka a argument x je sloupec, kam se kurzor přesune.

Teď už by mělo být v příkladu téměř všechno jasné. Na začátku jsou funkce pro inicializaci a ukonční ncurses.

  1. /*------------------------------------------------*/
  2. /* 51ncurses/colors.c                             */
  3. #include <stdio.h>
  4. #include <ncurses.h>
  5. #include <locale.h>
  6. #include <stdlib.h>
  7. #include <unistd.h>
  8.  
  9. #define C_LEN 8
  10.  
  11. static void obnovTerminal()
  12. {
  13.     printw("Obnovuji nastavení terminálu\n");
  14.     endwin();
  15. }
  16.  
  17. static void initNcurses() {
  18.     setlocale(LC_ALL, "");
  19.     initscr();
  20.     noecho();
  21.     raw();
  22.     atexit(obnovTerminal);
  23.  
  24.     if (!has_colors()) {
  25.         fprintf(stderr, "Terminál nepodporuje barvy\n");
  26.         exit(EXIT_FAILURE);
  27.     }
  28.     if (start_color() != OK) {
  29.         fprintf(stderr, "Nepodařilo se inicializovat barvy\n");
  30.         exit(EXIT_FAILURE);
  31.     }
  32. }
  33.  

Ve funkci main() jsem si vytvořil pole všech barev a pak jej postupně ve dvou cyklech procházím, vytvářím barevný páry (každou barvu s každou) a tisknu na obrazovku.

Normálně bych si pro barevné páry vytvořil nějaký výčtový typ, kde bych si barevné páry pojmenoval jako modro_zelena, nebo, lépe, barva_horni_menu atp.

Tentokrát ale každý barevný pár použiji jen jednou, takže si vystačím s proměnnou pair.

  1. int main(void)
  2. {
  3.     int colors[C_LEN] = {
  4.         COLOR_BLACK,
  5.         COLOR_RED,
  6.         COLOR_GREEN,
  7.         COLOR_YELLOW,
  8.         COLOR_BLUE,
  9.         COLOR_MAGENTA,
  10.         COLOR_CYAN,
  11.         COLOR_WHITE
  12.     };
  13.     int i, j, pair;
  14.  
  15.     initNcurses();
  16.  
  17.     printw("Max počet barev = %i\n", COLORS);    //8
  18.     printw("Max počet barevných párů = %i\n", COLOR_PAIR);    //64
  19.     printw("Sloupců  x Řádků = %i x %i\n", COLS, LINES);
  20.  
  21.     pair = 1;
  22.     for (i = 0; i < C_LEN; i++) {
  23.         for (j = 0; j < C_LEN; j++) {
  24.             init_pair(pair, colors[i], colors[j]);
  25.             attroff(A_BOLD);
  26.             attrset(COLOR_PAIR(pair));
  27.             mvprintw(i * 2 + 4, j * 12, "normal %i/%i", colors[i],
  28.                  colors[j]);
  29.             attrset(COLOR_PAIR(pair) | A_BOLD);
  30.             mvprintw(i * 2 + 5, j * 12, "bold %i/%i", colors[i],
  31.                  colors[j]);
  32.             refresh();
  33.             usleep(1000 * 250);
  34.             pair++;
  35.         }
  36.     }
  37.  

Nezapomeňte na volání funkce refresh().

Zkuste si ze zdrojového souboru odstranit pair++;. Co myslíte, že se stane? :-)

Defaultní barvy

Na konci zdrojového souboru ještě nastavím defaultní barvy a vypíšu text Pokračujte stiskem libovolné klávesy ....
K nastavení defaultních barev je potřeba zavolat funkci use_default_colors(), po jejímž zavolání funkce init_pair() interpretuje hodnotu barvy -1 jako defaultní barvu (popředí i pozadí).

int use_default_colors(void);

Defaultní barvy můžou být jakékoliv, jaké si uživatel ve svém terminálu nastaví. Proto byste neměli volat init_pair() jinak, než s -1 pro obě složky. Mohlo by se totiž stát, že náhodou vyberete třeba barvu popředí stejnou, jako si vybral uživatel (defaultní) barvu pozadí a text se stane nečitelný.

  1.     use_default_colors();
  2.     init_pair(pair, -1, -1);    // default colors
  3.     attroff(A_BOLD);
  4.     attrset(COLOR_PAIR(pair));
  5.     printw("\n\nPokračujte stiskem libovolné klávesy ...");
  6.     refresh();
  7.     (void)getch();
  8.     return 0;
  9. }
  10.  
  11. /*------------------------------------------------*/

Výsledek vypadá takto:

Ncurses - barvy

Ncurses - barvy

Text je na začátku a na konci zelený, protože jsem měl nastavený v terminálu takový barevný profil. Všiměte si, jak některý text není čitelný, protože jde o stejnou barvu popředí i pozadí.

Zkuste si nastavit TERM na vt100. To by mělo způsobit, že terminál nebude podporovat barvy. Uvidíte, jak se bude program chovat.

$ TERM=vt100
$ ./attrs



Terminál nepodporuje barvy
$

Existuje ještě funkce assume_default_colors(), která mění defaultní barvu textu popředí a pozadí. To se hodí například tehdy, když chcete překreslit celý terminál pomocí funkce erase().

int assume_default_colors(int fg, int bg);
int erase(void);

Funkce erase() překreslý celý termnál mezerami s nastavenou defaultní barvou. Nejjednoduší způsob jak přebarvit celé okno je nastavit defaultní barvy (důležitá je vlastně jen barva pozadí) a zavolat erase(). Viz následující příklad.

Nastavování atributů

V předchozí části jsem představil základní funkce na nastavování atributů písma a barvy (attrset(), attron() a attrof()). Zde uvidíte příklad dalších atributů, které ncurses poskytuje.

AtributVýznamPoznámka
A_BOLDZvýraznění textu
A_BLINKBlikáníFunguje jen v KDE konzoli a xtermu. V některém terminálu se chová podobně, jako A_REVERZE
A_DIMZtmavení textuNefunguje, nic neudělá.
A_REVERSEReverzní barvy
A_UNDERLINEPodtržený text
COLOR_PAIR(i)Barevný pár i

Žádný z terminálů, resp. terminálových emulátorů, které jsem zkoušel, nijak nezměnili písmo s nastaveným atributem A_DIM. A_BLINK funguje jen v málo terminálech. Třeba v KDE konzoli si můžete zaškrtnout v nastasvení, jestli má být blikání povoleno.

Zdrojový kód je, myslím, vcelku přímočarý:

  1. /*------------------------------------------------*/
  2. /* 51ncurses/attrs.c                              */
  3. #include <stdio.h>
  4. #include <ncurses.h>
  5. #include <locale.h>
  6. #include <stdlib.h>
  7. #include <unistd.h>
  8.  
  9. static void obnovTerminal()
  10. {
  11.     printw("Obnovuji nastavení terminálu\n");
  12.     endwin();
  13. }
  14.  
  15. static void initNcurses() {
  16.     setlocale(LC_ALL, "");
  17.     initscr();
  18.     noecho();
  19.     raw();
  20.     if (!has_colors()) {
  21.         endwin();
  22.         fprintf(stderr, "Terminál nepodporuje barvy\n");
  23.         exit(EXIT_FAILURE);
  24.     }
  25.     if (start_color() != OK) {
  26.         endwin();
  27.         fprintf(stderr, "Nepodařilo se inicializovat barvy\n");
  28.         exit(EXIT_FAILURE);
  29.     }
  30.     atexit(obnovTerminal);
  31. }
  32.  
  33. int main(void)
  34. {
  35.     int l, c;
  36.     int color_pair = 1;
  37.  
  38.     initNcurses();
  39.  
  40.     /* nastaveni barvy pozadi a prekresleni celeho okna */
  41.     init_pair(color_pair, COLOR_RED, COLOR_YELLOW);
  42.     attrset(COLOR_PAIR(color_pair));
  43.     assume_default_colors(COLOR_RED, COLOR_YELLOW);
  44.     erase();
  45.  
  46.     /* vypis zkusebnich textu */
  47.     l = LINES / 2 - 3;
  48.     c = COLS / 2 - 13;
  49.  
  50.     attron(A_BOLD);
  51.     mvprintw(l++, c, "Zkušební text A_BOLD");
  52.     attroff(A_BOLD);
  53.  
  54.     attron(A_BLINK);
  55.     mvprintw(l++, c, "Zkušební text A_BLINK");
  56.     attroff(A_BLINK);
  57.  
  58.     attron(A_DIM);
  59.     mvprintw(l++, c, "Zkušební text A_DIM");
  60.     attroff(A_DIM);
  61.  
  62.     attron(A_REVERSE);
  63.     mvprintw(l++, c, "Zkušební text A_REVERSE");
  64.     attroff(A_REVERSE);
  65.  
  66.     attron(A_UNDERLINE);
  67.     mvprintw(l++, c, "Zkušební text A_UNDERLINE");
  68.     attroff(A_UNDERLINE);
  69.  
  70.     l++;
  71.     mvprintw(l++, c - 5, "Pokračujte stiskem libovolné klávesy ...");
  72.     refresh();
  73.     (void)getch();
  74.  
  75.     return 0;
  76. }
  77.  
  78. /*------------------------------------------------*/

Překlad:

$ gcc -g -Wall attrs.c  `pkg-config --libs ncursesw` -o attrs

Výsledek pak může vypadat takto:

Gnome console KDE terminál xterm

Okna

Doteď jste pracovali s jedním oknem, které je dostupné pod globální proměnnou stdscr. A to je taky vše, s čím byste si mohli vystačit.

Okno stdscr zabírá celou obrazovku. Ncurses umožňuje vytvářet další okna, která mohou zabírat jen část obrazovky. Nové okno si alokuje vlastní paměť, takže co do něj zapíšete neovlivní obsah jiného okna. O tom co uvidíte na obrazovce rozhoduje které okno naposledy necháte vykreslit zavoláním funkce wrefresh().

Jinak řečeno, okno (se kterým se pracuje přes odkaz na strukturu WINDOW) je jen datová struktura, která si udržuje informace o nastavených atributech, umístění kurzoru a textu, který jste do okna zapsali. Pomocí funkce wrefresh() se potom obsah okna vypisuje na obrazovku.

Nové okno vytváří funkce newwin(). Smazat ho můžete funkcí delwin().

WINDOW *newwin(int nlines, int ncols, int begin_y, int begin_x);
int delwin(WINDOW *win);

Parametry begin_y a begin_x určují souřadnice levého horního rohu okna. Dejte si pozor na to, aby se celé okno vešlo do obrazovky. Jinak se nevykreslí! To znamená, že begin_y a begin_x musí být větší nebo rovno nule a musí platit, že nlines + begin_y >= LINES a ncols + begin_x >= COLS.

Pokud vytvoříte okno mimo obrazovku, tak se nic nestane, jen se nevykreslí. Ikdyž přesahuje mimo obrazovku jen kousek, stejně se nevykreslí vůbec nic.

Jak už jsem psal, pro vykreslení obsahu okna na obrazovku je nezbytné zavolat funkci wrefresh(). Ta má jako argument odkaz na strukturu WINDOW. Funkce refresh() je vlastně synonymum pro wrefresh(stdscr);. Vlastně všechny funkce, které pracují s okny mají svojí verzi s i bez „w“. Namátkou:

// vypsání okna na obrazovku
int refresh(void);
int wrefresh(WINDOW *win);

// zápis
int printw(const char *fmt, ...);
int wprintw(WINDOW *win, const char *fmt, ...);
int mvprintw(int y, int x, const char *fmt, ...);
int mvwprintw(WINDOW *win, int y, int x, const char *fmt, ...);

// čtení
int scanw(char *fmt, ...);
int wscanw(WINDOW *win, char *fmt, ...);
int mvscanw(int y, int x, char *fmt, ...);
int mvwscanw(WINDOW *win, int y, int x, char *fmt, ...);

// přesun kurozru
int move(int y, int x);
int wmove(WINDOW *win, int y, int x);

Vysvětlovat parametry asi nemusím. Třeba funkce wscanw() bude načítat standardní vstup a pokud není vypnuté echo, tak bude vypisovat vstup na pozici kurzoru zadaného okna. Funkce mvwscanw() dělá to samé, jen nejdříve přesune kurzor na zadané souřadnice v zadaném oknu. Tak jsem to vysvtětlil. No nic.

V příkladu používám novou funkci, curs_set(), která nastavuje, jakým způsobem se bude zobrazovat kurzor.

int curs_set(int visibility);

Parametr visibility může být 0 = neviditelný, 1 = normální a 2 = zvýrazněný. Rozdíl mezi normálním a zvýrazněným jsem nezaznamenal. Asi zase jedna z vymožeností, která funguje jen na některých exotických terminálech.

Další nová funkce použitá v následujícím příkladu je funkce box(). Ta vykreslí kolem okna rámeček ze zadaných znaků pro svislou a vodorovnou část rámu. Pokud byste chtěli nad rámečkem větší kontrolu, nastujdujte si funkci wborder().

 int box(WINDOW *win, chtype verch, chtype horch);
 int wborder(WINDOW *win, chtype ls, chtype rs,
          chtype ts, chtype bs, chtype tl, chtype tr,
          chtype bl, chtype br);

Velmi důležitou novinkou je funkce touchwin().

int touchwin(WINDOW *win);

Ncurses si udržuje informaci o tom, které části okna už byli vykreslené a které se změnili od posledního volání wrefresh(). Při zavolání wrefresh() vykreslí jen ty části okna, které se změnili (například voláním funkce wprintw(), werase(), box() atp.).

Tyto infomrace se ukládají ve struktuře okna. Pokud máte na obrazovce překrývající se okna a chcete vykreslit okno, které bylo překresleno jiným oknem, tak o tom ve struktuře WINDOW nemáte žádné info. Ncurses si tak myslí, že se v okně nic nezměnilo a nemusí nic kreslit. Funkce touchwin() se okna „dotkne“, takže si ncurses bude myslet, že se celé změnilo a celé ho znovu vykreslí. V příkladu to uvidíte v praxi.

Poslední novinkou je volání funkce keypad(stdscr, TRUE);, které zapne „keypad“ režim. V tomto režimu funkce wgetch() při stisku funkční klávesy (jako je F1, nebo šipky) vrací jedno číslo, pro které ncurses definuje makra, jako je KEY_LEFT, KEY_F(i), KEY_BACKSPACE atd. Celý seznam najdete v souboru /usr/include/ncurses.h.

Pokud je „keypad“ režim vypnutý (což defaultně je), tak vrací wgetch() escape sekvenci reprezentující stisknutou funkční klávesu. A s tím se pracuje blbě.

int keypad(WINDOW *win, bool bf);

V příkladu používám funkci getch(), tedy alias pro wgetch(stdscr), proto volám keypad() s arugmentem stdscr. (Vzhledem k tomu, že mám vypnutý echo režim, tak je vlastně jedno, z jakého okna čtu.)

A teď už se můžete podívat na příklad. Vykreslím okno uprostřed obrazovky, které můžete pomocí kláves posouvat. Napsal jsem si pomocnou funkci wpozadi(), která vyplní okno tečkami (čímž taky změní barvu jeho pozadí). Můžete příklad upravit tak, aby místo teček používal mezery, nebo zkusit trik s assume_default_colors()werase().

  1. /*------------------------------------------------*/
  2. /* 51ncurses/window.c                             */
  3. #include <stdio.h>
  4. #include <ncurses.h>
  5. #include <locale.h>
  6. #include <stdlib.h>
  7. #include <unistd.h>
  8.  
  9. #define O_RADKU 5
  10. #define O_SLOUPCU 50
  11.  
  12. static void obnovTerminal()
  13. {
  14.     printw("Obnovuji nastavení terminálu\n");
  15.     endwin();
  16. }
  17.  
  18. static void initNcurses() {
  19.     setlocale(LC_ALL, "");
  20.     initscr();
  21.     noecho();
  22.     raw();
  23.     if (!has_colors()) {
  24.         endwin();
  25.         fprintf(stderr, "Terminál nepodporuje barvy\n");
  26.         exit(EXIT_FAILURE);
  27.     }
  28.     if (start_color() != OK) {
  29.         endwin();
  30.         fprintf(stderr, "Nepodařilo se inicializovat barvy\n");
  31.         exit(EXIT_FAILURE);
  32.     }
  33.     atexit(obnovTerminal);
  34. }
  35.  
  36. static void wpozadi(WINDOW * okno, const int radek, const int sloupcu)
  37. {
  38.     int i, j;
  39.     for (i = 0; i < radek; i++)
  40.         for (j = 0; j < sloupcu; j++)
  41.             mvwprintw(okno, i, j, "%c", '.');
  42.     wmove(okno, 0, 0);
  43. }
  44.  
  45. int main(void)
  46. {
  47.     enum pairs { pair_stdscr = 1, pair_okno };
  48.     int ch;
  49.     int line, col;
  50.     WINDOW *okno;
  51.  
  52.     initNcurses();
  53.     curs_set(0);
  54.  
  55.     /* nastaveni barvy pozadi a prekresleni cele obrazovky */
  56.     assume_default_colors(COLOR_RED, COLOR_YELLOW);
  57.     erase();
  58.     refresh();
  59.  
  60.     line = LINES / 2 - 3;
  61.     col = COLS / 2 - 25;
  62.     okno = newwin(O_RADKU, O_SLOUPCU, line, col);
  63.  
  64.     /* nastaveni okna */
  65.     init_pair(pair_okno, COLOR_RED, COLOR_WHITE);
  66.     wattrset(okno, COLOR_PAIR(pair_okno));
  67.     wpozadi(okno, O_RADKU, O_SLOUPCU);
  68.     //box(okno,'|','-');
  69.     box(okno, ACS_VLINE, ACS_HLINE);
  70.     wprintw(okno, "Nové okno");
  71.     mvwprintw(okno, 2, 2, "Ahoj světe!");
  72.     wattron(okno, A_BOLD);
  73.     mvwprintw(okno, 4, 1, " Enter = konec, c = clear, sipky = posun okna ");
  74.  
  75.     keypad(stdscr, TRUE);
  76.     ch = '\0';
  77.     do {
  78.         wrefresh(okno);
  79.         ch = getch();
  80.         switch (ch) {
  81.         case KEY_LEFT:
  82.             if (col > 0) {
  83.                 col--;
  84.                 mvwin(okno, line, col);
  85.             }
  86.             break;
  87.         case KEY_RIGHT:
  88.             col++;
  89.             mvwin(okno, line, col);
  90.             break;
  91.         case KEY_UP:
  92.             if (line > 0) {
  93.                 line--;
  94.                 mvwin(okno, line, col);
  95.             }
  96.             break;
  97.         case KEY_DOWN:
  98.             line++;
  99.             mvwin(okno, line, col);
  100.             break;
  101.         case 'c':
  102.             touchwin(stdscr);
  103.             wrefresh(stdscr);
  104.             break;
  105.         }
  106.     } while (ch && ch != '\n');
  107.  
  108.     return 0;
  109. }
  110.  
  111. /*------------------------------------------------*/

Výsledek může vypadat takto:

Ncurses - okna

Ncurses - okna

Pokud zmáčknete šipku, okno se posune a vykreslí na nové pozici. Nevolám nic jiného, než wrefresh(okno);, proto se nevykreslí na obrazovce nic jiného, než toto okno (na nové pozici) a na obrazovce zůstávají „duchové“. Funkce mvwin() označí celé okno k překreslení, takže není nutné volat touchwin().

Když stisknete klávesu c, tak se dotknu pozadí (struktura strdscr se nijak neměnila, nic jsem do ní nezapisoval, takže se musím dotknout) a překreslím ho. Okna jsem se ale nedotknul a nijak ho nezměnil, proto volání wrefresh(okno); nic neudělá. Výsledkem je prázdná žlutá obrazovka.

Do třetice je zajímavé, co se stane, když posunete okno mimo obrazovku. Zdánlivě nic. Okno se nehne, protože ncurses ho odmítne mimo obrazovku překreslit. Ale změní se hodnota proměnné line nebo col. Takže kolikrát jste posunuli okno mimo obrazovku, tolikrát ho zese musíte posunout zpět, aby se začalo hýbat.

Aby se okno hýbalo bez duchů, museli byste nejdříve překreslit pozadí. Tedy zavolat funkce nějak takto:

touchwin(stdscr);
wrefresh(stdscr);
// ted je  prazdna (zluta) obrazovka
mvwin(okno, line, col);
wrefresh(okno);
// okno se vykreslilo na nove pozici

To ale není úplně efektivní řešení. Část pod oknem se totiž vykreslí dvakrát. Nejdříve žluté pozadí, které je následně překresleno obsahem okna. Na terminálu vám to proběhne tak rychle, že si ničeho nevšimnete, ale při práci po síti by vám to mohlo trochu blikat.

Můžete použít optimálnější postup:

touchwin(stdscr);
wnoutrefresh(stdscr);
// ted je v bufferu prazdna (zluta) obrazovka
// terminal se nijak nezmenil
mvwin(okno, line, col);
wnoutrefresh(okno);
// ted je v bufferu vykreslene okno na nove pozici
// terminal se porad nijak nezmenil
doupdate();
// ted se vysledek vykreslil na terminal

Funkce wnoutrefresh() překreslí okno do bufferu (ne na terminál, takže třeba při práci po síti se ještě nikam nic neposílá). Funkce doupdate() pak výsledek vypíše na terminál.

int wnoutrefresh(WINDOW *win);
int doupdate(void);

Funkce wrefresh() je vlastně alias pro volání funkcí wnoutrefresh() a doupdate().

V další kapitole se dostane na některé složitější funkce. Proto si nejdříve pořádně prostudujte tuto kapitolu, ať v tom pak nemáte zmatek.

Komentář Hlášení chyby
Created: 1.11.2014
Last updated: 21.9.2017
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..