Programación con nCurses en C

De Wiki do Ciclo ASIR do IES de Rodeira
Saltar á navegación Saltar á procura

Introducción

A librería ncurses proporciona un conxunto de funcións pra manexar a entrada e saída por terminal no mundo UNIX. A diferencia de outros sistemas operativos (WINDOWS,DOS) Linux é un sistema multiusuario no que moitos usuarios poden traballar o mesmo tempo co mesmo ordenador dende terminais que poden estar situados en calquera sitio. Como os terminais non son todos iguais, nin dende o punto de vista físico nin lóxico, non é posible ter unhas chamadas ó sistema xerais que funcionen pra todos os modelos. O Linux, igual que a maior parte (ou todos) os UNIX, utiliza unha base de datos que recolle as capacidades dos diferentes tipos de terminais soportados, e os programas deben poder traballar con todos eles. A librería curses é unha librería de funcións de manexo de terminais de usuario que fai uso desta base de datos pra poder facer tarefas comúns como o borrado do terminal ou a colocación do cursor nunha zona concreta da pantalla, ademáis de outras tarefas máis complexas como creación de ventanas, manexo das cores... etc. A implementación da librería curses presente en (que eu saiba) todos os UNIX chámase na versión de Linux ncurses e a información máis importante pode obterse con:

  • man ncurses : Uso xeral da librería, como iniciar a librería e pechala e como enlazar a librería os nosos programas
  • man curs_color : Descripción das funcións de manexo de cores
  • man curs_attr : Descripción das funcións de manexo dos atributos
  • man curs_getch: Descripción das funcións de entrada de caracteres (getch)
  • man curs_getstr: Descripción das funcións de entrada de cadeas (getstr)
  • man curs_mouse: Descripción das funcións de manexo de rato
  • man curs_move: Descripción das funcións de posicionamento do cursor
  • man curs_initscr: Descripción das funcións de inicialización da librería
  • man curs_window: Descripción das funcións de manexo de ventanas
  • man curs_inopts: Descripción das funcións de control de entrada e saída
  • man curs_terminfo: Descripción das funcións pra obter información do terminal
  • man curs_printw: Descripción das funcións pra visualizar (printf)
  • man curs_refresh: Descripción das funcións de volcado de datos no terminal (flushing).
  • man curs_scanw: Descripción das funcións pra ler datos do teclado (scanf)
  • man curs_clear: Descripción das funcións pra borra-lo terminal (clear)
  • man curs_panel: Descripción das funcións de manexo dos paneis das ventanas.

Case todas as funcións utilizan ventanas pra traballar. Unha ventana é unha zona da pantalla delimitada na que poderemos realizar tanto entrada como saída de información. Para poder referirnos ó terminal completo, a librería curses dispón dunha ventana que sempre está creada chamada stdscr (estandar screen).

A terminoloxía empregada para o nome das funcións é moi simple; cando a función traballa cunha ventana o seu nome comenza coa letra w, mentras que si traballa coa ventana principal stdscr (o terminal completo) non. Ademáis, as funcións que moven de posición o cursor antes de facer o seu traballo comenzan con mv, de xeito que unha función que mova o cursor e que traballe cunha ventana comenzaría con mvw.

Pra utilizar as funcións da librería ncurses é necesario incluír o ficheiro curses.h e enlazar o programa coa librería ncurses mediante a opción -l do gcc do seguinte xeito

  gcc programa.c -o programa executable -lncurses

xa que as curses non forman parte da librería C estándar. A librería curses define unha serie de constantes de moita utilidade a hora de programar aplicacións:

  • LINES : Número de liñas que ten o terminal.
  • COLS : Número de columnas do terminal.
  • TRUE : Constante que indica 'certo' (1)
  • FALSE : Constante pra indicar 'falso' (0)
  • ERR : Constante que devolven as funcións de curses cando se produce un erro.
  • OK : Constante que devolven as funcións de curses cando traballan ben.
  • bool : Tipo de datos pra definir variables que poden tomar os valores TRUE ou FALSE indicados anteriormente.

Inicialización e Peche da Librería.

Pra utilizar a librería curses nun programa é necesario chamar en primeiro lugar a unha función encargada da inicialización da librería chamada initscr() (man curs_initscr), esta función encárgase de inicializar as estructuras que necesita a librería e de obter a información do terminal. Antes de rematar, o programa debe pechar as curses pra que o terminal recupere o seu modo de funcionamento normal mediante a función endwin() (man curs_initscr). Hai moitas outras funcións de inicialización comúns que afectan sobre todo o xeito de coller a entrada de usuario:

  • cbreak() ou nocbreak() (man curs_inopts): cbreak() indica que cando lemos unha tecla do terminal (getch), o código da mesma pase a estar dispoñible sin esperar a que o usuario pulse o "Enter". Con nocbreak(), o carácter non estará dispoñible hasta que o usuario pulse "Enter".
  • echo() ou noecho() (man curs_inopts): echo() indica que cando lemos unha tecla do terminal (getch) a pulsación sairá na pantalla. Con noecho() se indica que cando o usuario pulse a tecla (getch) a pulsación non se visualizará.
  • keypad(WINDOW *win,bool bf) (man curs_inopts): Activa ou desactiva o teclado numérico do terminal na ventana indicada. É imprescindible chamar a esta función pra que traballen algunhas teclas especiais como as flechas ou as teclas de función.
  • start_color() (man curs_color): Si queremos utilizar cores no programa, é necesario iniciar a librería coa función start_color().

Xa que a inicialización das curses pode requerir varias tarefas e convinte (pra clarificar o programa e para manexo máis sinxelo do código) definir unha función donde se realicen todas as tarefas de inicialización. Tamén é habitual facer unha función de finalización. Un exemplo podería ser:

//Inicialización da librería curses
//
void  inicia_terminal(void)  {
  // Inicialización da librería
  initscr();
  // Non se espera polo Enter nos getch()
  cbreak();
  // Non se visualiza a tecla pulsada ó facer o getch()
  noecho();
  // Se habilitan as teclas especiais
  keypad(stdscr,TRUE);
  // Se inicializan as estructuras de manexo de cores
  start_color();
}

// Finalización (peche) das curses.
//
void pecha_terminal(void) {
 endwin();
}

Outras funcións de uso común na inicialización das curses poden verse nas páxinas do manual (man curs_inopts, man curs_outopts).

Visualización e Posicionamento do Cursor

A visualización nos terminais faise a través dun buffer pra axilizar a saída por pantalla. Unha vez que temos 'pintada' a pantalla debemos forzar a súa saída o terminal chamando a unha das funcións de 'refresco' das curses:

 int refresh(void);
 int wrefresh(WINDOW *win);
 int wnoutrefresh(WINDOW *win);
 int doupdate(void);
 int redrawwin(WINDOW *win);
 int wredrawwin(WINDOW *win, int begline, int numlines);

É necesario chamar a unha destas funcións pra ober a saída no terminal. A función wrefresh chama en primeiro lugar a wnoutrefresh que copia a ventana na pantalla virtual, e logo a doupdate que se encarga de comprobar as diferencias co contido actual da pantalla e a actualiza. Si se queren visualizar varias ventanas o mesmo tempo é máis efectivo chamar a wrefresh pra cada ventana e facer unha soa chamada a doupdate que realizar un wrefresh pra cada unha. As funcións redrawwin e wredrawwin indican que algunhas liñas da pantalla deberán ser eliminadas na seguinte actualización.

Pra borrar a pantalla curses proporciona as seguintes funcións:

 int clear(void);
 int wclear(WINDOW *win);
 int erase(void);
 int werase(WINDOW *win);
 int clearok(WINDOW *win, bool bf);
 int clrtobot(void);
 int wclrtobot(WINDOW *win);
 int clrtoeol(void);
 int wclrtoeol(WINDOW *win);

As funcións que comenzan por _w_, como sempre, traballan cunha ventana mentras que as outras o fan coa ventana principal stdscr (a pantalla). clear e erase borran a ventana, pero mentras erase o fai poñendo blancos no interior, clear chama despois a función clearok con bf a TRUE o que forza o repintado de toda a pantalla dende 0. clrtobot borra o contido da pantalla dende o cursor ó final da pantalla e clrtoeol dende o cursor ata final de liña.

As funcións de manexo do cursor son:

 int move(int y, int x);
 int wmove(WINDOW *win, int y, int x);

Que poñen o cursor na posición y (fila) e x (columna). wmove traballa cunha ventana que ten que estar creada, mentras que move traballa con stdscr.

Pra visualizar información dispoñemos das seguintes funcións:

 int addch(chtype ch);
 int waddch(WINDOW *win, chtype ch);
 int mvaddch(int y, int x, chtype ch);
 int wmvaddch(WINDOW *win, int y, int x, chtype ch);
 int echochar(chtype ch);
 int wechochar(WINDOW *win, chtype ch);

Estas funcións visualizan unha letra na pantalla ou nunha ventana (w...), as que empezan por mv colocan tamén o cursor. Unha vez visualizada a letra o cursor avanza á posición seguinte. Si non hi máis posicións e scrollok está activo, a pantalla/ventana fará un scroll do seu contido hacia arriba. echochar e wechochar fan o mesmo pero actualizan a pantalla chamando á función (w)refresh. A librería curses ten predefinidos unha serie de caracteres especiais pra facer debuxos (caixas... etc). Eses caracteres poden consultarse na páxina do manual (man curs_addch) e son constantes que comenzan por ACS_. Si o terminal non soporta eses dibuxos, os sustituirá pola letra ASCII máis axeitada.

Pra visualizar información máis complexa, curses dispón de unha serie de funcións análogas á función printf:

 int printw(char *formato [, arg] ...);
 int wprintw(WINDOW *win, char *formato [, arg] ...);
 int mvprintw(int y, int x, char *formato [, arg] ...);
 int mvwprintw(WINDOW *win, int y, int x, char *formato [, arg] ...);

Entrada de Datos.

A función complementaria á familia de addch() é a familia wgetch(), que lee da entrada estándar o valor dunha tecla:

 int getch(void);
 int wgetch(WINDOW *win);
 int mvgetch(int y, int x);
 int mvwgetch(WINDOW *win,int y,int x);
 int ungetch(int ch);
 int has_key(int ch);

O comportamento das funcións getch varía según si se chamou a cbreak; (a función non espera pola pulsación do Enter) ou a nocbreak (é necesario pulsar Enter despois da tecla) e a noecho (a letra non sae na pantalla) ou a echo (a letra sae na pantalla). Mediante a función int nodelay(WINDOW *w,bool bf) pódese controlar si getch espera pola pulsación da tecla (bf=TRUE) ou non (bf=FALSE). Ademáis si se chama á función keypad con TRUE, se habilita a lectura das teclas especiais (como as teclas de función), devolvendo éstas unha das constantes indicadas na páxina do manual (man curs_getch) que empezan por KEY_. A función ungetch devolve o caracter indicado ó buffer do teclado, e has_key nos indicará si o terminal soporta a tecla especial que lle pasamos como parámetro.

Na librería curses están dispoñibles tamén versións especiais das funcións de entrada estándar da librería C:

a) man curs_getstr

 int getstr(char *str);
 int getnstr(char *str, int n);
 int mvgetstr(int y, int x, char *str);
 int mvgetnstr(int y, int x, char *str, int n);
 int wgetstr(WINDOW *win, char *str);
 int wgetnstr(WINDOW *win, char *str, int n);
 int mvwgetstr(WINDOW *win, int y, int x, char *str);
 int mvwgetnstr(WINDOW *win, int y, iny x, char *str, int n);

b)man curs_scanw

 int scanw(char *fmt [, arg] ...);
 int mvscanw(int y, int x, char *fmt [, arg] ...);
 int wscanw(WINDOW *win, char *fmt [, arg] ...);
 int mvwscanw(WINDOW *win, int y, int x, char *fmt [, arg] ...);

Creación e Manexo de Ventanas

Unha ventana é unha área da pantalla na que se pode operar como si se tratara dunha pantalla independiente. Todas as funcións das curses que acceden á pantalla teñen versións (que teñen w_ ou mvw__ ó principio) que incorporan como parámetro a ventana á que se refiren. A librería curses proporciona moitas funcións pra manexar ventanas:

 WINDOW *newwin(int nlines, int ncols, int begin_y, int begin_x);
 int delwin(WINDOW *win);
 int mvwin(WINDOW *win, int y, int x);
 WINDOW *subwin(WINDOW *orig, int nlines, int ncols, int begin_y, int begin_x);
 WINDOW *derwin(WINDOW *orig, int nlines, int ncols, int begin_y, int begin_x);
 int mvderwin(WINDOW *win, int par_y, int par_x);
 WINDOW *dupwin(WINDOW *win);
 int wresize(WINDOW *win, int lines, int cols);
 void wsyncup(WINDOW *win);
 void syncok(WINDOW *win, bool bf);
 void wcursyncup(WINDOW *win);
  • A función newwin crea unha nova estructura de tipo WINDOW que representará unha área de traballo na pantalla que comenza nas coordenadas begin_y,begin_x (fila,columna) sendo a primeira posición a 0,0 e que mide nlines liñas e ncols columnas. Si nlines e 0, as liñas serán as que faltan dende begin_y ata final da pantalla, e si ncols é 0 as columnas son as que van dende begin_x ata final da pantalla, deste xeito newwin(0,0,0,0) creará unha ventana que ocupa todo o terminal.
  • delwin elimina a ventana indicada liberando todos os recursos que tiña ocupados. Si a ventana ten subventanas, é necesario eliminalas antes. Non se borrará a ventana da pantalla, simplemente se elimina da memoria.
  • mvwin coloca a ventana indicada na posición y,x que se especifique. Non se permite mover unha ventana máis alá dos límites do terminal.
  • subwin crea unha ventana filla de outra. As coordenadas que se indican na creación son relativas á pantalla completa. Antes de refrescar unha ventana con subventanas con wrefresh é necesario chamar á función int touchwin(WINDOW *win); pra esa ventana.
  • derwin e similar a subwin pero as coordenadas indicadas na creación son relativas á ventana pai.
  • mvderwin move unha ventana filla xa sexa creada con subwin como con derwin.
  • dupwin crea un duplicado exacto da ventana indicada.
  • wresize cambia o tamaño dunha ventana ó tamaño indicado.
  • wsyncup marca pra actualizar todas as zonas que cambiaron nos fillos da ventana indicada. Si se chama a syncok con bf a TRUE, esta función chamarase automáticamente cando se produzca algún cambio.
  • wcursyncup actualiza a posición do cursor en todos os pais da ventana.

As funcións de subventanas (subwin, derwin, mvderwin, syncok, wsyncup, wcursyncup) son incompletas, están pouco probadas e poden ter bugs

O principal inconvinte das ventanas das curses é que non permiten xestionar de xeito eficiente as ventanas solapadas. Pra solucionar este tema xurdiu a librería panel. A librería panel proporciona unha serie de funcións que permiten o solapamento de ventanas que normalmente ven acompañando a curses. Si a queremos utilizar debemos incluir o ficheiro panel.h e indicarlle ó compilador que a enlace mediante o parámetro -lpanel :

  gcc programa.c -o executable -lncurses -lpanel

Os paneles están asociados con ventanas creadas con newwin, e perminten o manexo de ventanas superpostas.As principais funcións da librería panel son as seguintes:

 PANEL *new_panel(WINDOW *win);
 void update_panels(void);
 int del_panel(PANEL *pan);
 int hide_panel(PANEL *pan);
 int show_panel(PANEL *pan);
 int top_panel(PANEL *pan);
 int bottom_panel(PANEL *pan);
 int move_panel(PANEL *pan, int starty, int startx);
 int replace_panel(PANEL *pan,WINDOW *win);
 PANEL *panel_above(const PANEL *pan);
 PANEL *panel_below(const PANEL *pan);
 int set_panel_userptr(PANEL *pan, const void *ptr);
 const void *panel_userptr(const PANEL *pan);
 WINDOW *panel_window(const PANEL *pan);
  • new_panel crea unha estructura PANEL asociándoa con unha ventana e colocándoa enriba de todas as creadas con anterioridade. Devolve NULL en caso de erro e se non a dirección do PANEL creado.
  • update_panels actualiza a pantalla virtual pra reflexar os cambios, pero non os pon na pantalla real. Pra visualizar os cambios realmente, será necesario chamara doupdate. É posible (e aconsellable) chamar a update_panels as veces que faga falta antes de chamar a doupdate.
  • del_panel elimina o panel e todos os recursos que ocupe. A ventana a que está asociado o panel non se elimina.
  • hide_panel oculta o panel da vista.
  • show_panel fai que un panel que non está á vista se coloque visible e por enriba do resto.
  • top_panel pon o panel por enriba do resto.
  • bottom_panel pon o panel debaixo dos demáis.
  • move_panel move o panel á posición indicada. Non cambia a súa posición na pila de paneis (continúa a ter os mesmos enriba e debaixo). Esta é a función apropiada pra mover unha ventana.
  • replace_panel cambia a ventana asociada ó panel pola ventana indicada. Non modifica a súa posición na pila de paneis.
  • panel_above devolve o panel que se atopa xusto enriba do indicado. Si lle pasamos como argumento NULL devolverá o primeiro panel (o de abaixo de todo).
  • panel_below devolve o panel que se atoba xusto debaixo do indicado. Si lle pasamos como argumento NULL devolverá o panel de enriba de todo.
  • set_panel_userptr pon un valor no punteiro do usuario do panel. Este punteiro de usuario é útil pra poder asociar datos co panel.
  • panel_userptr devolve o punteiro de usuario asociado ó panel.
  • panel_window devolve un punteiro á ventana asociada co panel.

Debuxo de Liñas e Bordes.

As curses proporcionan unha multitude de funcións pra debuxar liñas e bordes pras ventanas utilizando os caracteres que se desexe. As funcións principais son:

 int border(chtype ls, chtype rs, chtype ts, chtype bs, chtype tl, chtype tr, chtype bl, chtype br);
 int wborder(WINDOW *win, chtype ls, chtype rs, chtype ts, chtype bs, chtype tl, chtype tr, chtype bl, chtype br);
 int box(WINDOW *win, chtype verch, chtype horch);
 int hline(chtype ch, int n);
 int whline(WINDOW *win, chtype ch, int n);
 int mvhline(int y, int x, chtype ch, int n);
 int mvwhline(WINDOW *win, int y, int x, chtype ch, int n);
 int vline(chtype ch, int n);
 int wvline(WINDOW *win, chtype ch, int n);
 int mvvline(int y, int x, chtype ch, int n);
 int mvwvline(WINDOW *win, int y, int x, chtype ch, int n);
  • border e wborder debuxan un marco á ventana indicada (border traballa con stdscr). Os parámetros indican os caracteres que se utilizarán pra face-lo debuxo: ls esquerda, rs dereita, ts arriba, bs abaixo, tl esquina superior esquerda, tr esquina superior dereita, bl esquina inferior esquerda e br esquina inferior dereita. Si algún valor é 0 curses utiliza uns caracteres por defecto.
  • box en realidade é unha chamada a wborder(win,verch,verch,horch,horch,0,0,0,0).
  • hline, whline, mvhline e mvwhline debuxan unha liña horizontal de n caracteres ch de lonxitude comenzando na posición do cursor e na ventana actual ou especificada. A posición do cursor non cambia.
  • vline, wvline, mvvline e mvwvline debuxan unha liña vertical de n caracteres ch de lonxitude comenzando na posición do cursor e na ventana actual ou especificada. A posición do cursor non cambia.

Manexo das Cores.

Creación de Formularios.

Manexo do Rato.

Exemplos.