Precedente Successivo Indice

2. La Libreria Curses

2.1 Una Panoramica delle Curses

Compilazione di Programmi che usano le Curses

Allo scopo di usare la libreria è necessario che siano definiti certi tipi e variabili. Perciò, il programmatore deve avere la linea:


#include <curses.h>

all'inizio del programma sorgente. Il pacchetto screen utilizza la libreria di I/O Standard, così <curses.h> include <stdio.h>; <curses.h> include anche <termios.h>, <termio.h>, o <sgtty.h> a seconda del vostro sistema. E` ridondante (ma non dannoso) per il programmatore ripetere questi include. Nel linkare con le curses avete bisogno di aggiungere -lncurses nel vostri LDFLAGS o nella riga di comando. Non c'è bisogno di altre librerie.

Aggiornare lo Schermo

Allo scopo di aggiornare lo schermo nel modo ottimale, è necessario che le routine sappiano cosa lo schermo mostri correntemente e cosa il programmatore vuole che mostri in seguito. Per questo motivo si definisce un certo tipo di dati chiamato WINDOW, che descrive alle routine una finestra, includendo la sua posizione iniziale sullo schermo (le coordinate (y,x) dell'angolo superiore sinistro) e la sua misura. Una di queste strutture (chiamata curscr, per current screen (schermo attuale)), è una immagine di ciò che il terminale attualmente mostra. Un altro schermo (chiamato stdscr, per standard screen (schermo standard)) viene fornito automaticamente per ricevere i cambiamenti.

Una WINDOW (finestra) è una rappresentazione puramente interna. Viene usata per contenere l'immagine potenziale di una porzione del terminale. Non è necessariamente in relazione con quello che si vede realmente sullo schermo del terminale; è piuttosto come un foglio di appunti o un buffer di scrittura.

Per far sì che la sezione dello schermo fisico corrispondente ad una finestra rifletta il contenuto di una struttura WINDOW si esegue la routine refresh() (o wrefresh() se la finestra non è stdscr).

Una data sezione dello schermo fisico può essere interessata da un qualunque numero di finestre sovrapposte. Inoltre, cambiamenti possono venire apportati alle finestre in ogni ordine, senza rispetto per l'efficienza. Quindi il programmatore, quando vuole, può effettivamente dire ''fà che mostri questo'', e lasciare che il pacchetto determini il modo più efficiente per ridisegnare lo schermo.

Finestre Standard e Convenzioni sui Nomi delle Funzioni

Come consigliato sopra, le routine possono usare parecchie finestre, ma due vengono create automaticamente: curscr, che sa cosa mostra il terminale, e stdscr, che è ciò che il programmatore vuole che mostri in seguito. L'utente non deve mai accedere direttamente a curscr. I cambiamenti vanno fatti attraverso la API, e quindi eseguendo la routine refresh() (o wrefresh()).

Sono definite molte funzioni che utilizzano stdscr come schermo. Per esempio, per aggiungere un carattere a stdscr si esegue addch() passando il carattere desiderato per argomento. Per scrivere su una finestra diversa è fornita la routine waddch() (per `w'indow addch()). Questa convenzione di premettere ai nomi di funzione un 'w' quando si accede ad una specifica finestra è coerente. Le sole routine che non la seguono sono quelle per cui deve sempre essere specificata una finestra.

Allo scopo di spostare le coordinate attuali (y,x) da un punto ad un altro, sono fornite le routine move() e wmove(). Comunque è spesso desiderabile prima eseguire il movimento e successivamente le operazioni di I/O. Per evitare goffaggini, la maggior parte delle routine di I/O possono venir precedute dal prefisso 'mv' e le coordinate desiderate (y,x) anteposte agli argomenti della funzione. Per esempio, le chiamate


move(y, x);
addch(ch);

possono venir sostituite da


mvaddch(y, x, ch);

e


wmove(win, y, x);
waddch(win, ch);

possono venir sostituite da


mvwaddch(win, y, x, ch);

Nota che il puntatore alla descrizione della finestra (win) è posto prima delle coordinate (y,x). Se una funzione richiede un puntatore ad una finestra, questo viene sempre passato come primo parametro.

Variabili

La libreria curses dispone di alcune variabili che descrivono le capacità del terminale.

 tipo   nome      descrizione
 ------------------------------------------------------------------ 
 int    LINES     numero di linee del terminale
 int    COLS      numero di colonne del terminale

Il file curses.h dichiara alcune costanti #define ed alcuni tipi di utilità generale:

bool

tipo booleano, attualmente char (es., bool finito;)

TRUE

valore booleano vero (1).

FALSE

valore booleano falso (0).

ERR

valore di una funzione che è fallita (-1).

OK

valore di una funzione correttamente eseguita.

2.2 Uso della Libreria

Ora vi descriveremo come usare il pacchetto screen. L'assunto è che tutte le operazioni si applichino a stdscr. Queste istruzioni possono operare su una qualunque finestra cambiando i nomi delle funzioni ed i loro parametri come menzionato prima.

Ecco un semplice programma per stimolare la discussione:


#include <curses.h>
#include <signal.h>

static void fine(int sig); 

main(int argc, char *argv[])
{
    /* inizializzate qui le vostre strutture di dati 'non-curses' */ 

    (void) signal(SIGINT, fine);      /* termina al segnale di interruzione */ 

    (void) initscr();      /* inizializza la libreria curses */ 
    keypad(stdscr, TRUE);  /* abilita la mappatura della tastiera */ 
    (void) nonl();         /* non convertire NL->CR/NL in output */ 
    (void) cbreak();       /* prende i caratteri in input uno alla volta, senza attendere il \n */ 
    (void) noecho();       /* nessuna echo dell'input */ 

    if (has_colors())
    {
        start_color();

        /*
         * Semplici colori, di solito tutto ciò che serve.
         */
        init_pair(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK);
        init_pair(COLOR_GREEN, COLOR_GREEN, COLOR_BLACK);
        init_pair(COLOR_RED, COLOR_RED, COLOR_BLACK);
        init_pair(COLOR_CYAN, COLOR_CYAN, COLOR_BLACK);
        init_pair(COLOR_WHITE, COLOR_WHITE, COLOR_BLACK);
        init_pair(COLOR_MAGENTA, COLOR_MAGENTA, COLOR_BLACK);
        init_pair(COLOR_BLUE, COLOR_BLUE, COLOR_BLACK);
        init_pair(COLOR_YELLOW, COLOR_YELLOW, COLOR_BLACK);
    }

    for (;;)
    {
        int c = getch();     /* rinfresca il video ed accetta un solo tasto in input */

        /* Qui potete elaborare il tasto premuto */ 
    }

    fine(0);               /* abbiamo fatto */ 
}

static void fine(int sig) 
{
    endwin();

    /* Qui mettete il vostro codice di terminazione non-curses */ 

    exit(0);
}

Incominciamo

Allo scopo di usare il pacchetto screen, le routine devono essere informate delle caratteristiche del terminale, e bisogna allocare spazio per curscr e stdscr. La funzione initscr() fa entrambe queste cose. Poichè deve allocare spazio per le finestre, potrebbe esaurire la memoria nel tentativo. Nelle rare occasioni in cui questo succede, initscr() farà terminare il programma con un messaggio d'errore. initscr() va sempre chiamata prima di ogni altra routine che agisca sulle finestre. Se non lo si facesse, il programma terminerebbe con un core dump non appena curscr o stdscr venissero utilizzate. Comunque, di solito è meglio attendere di essere sicuri di averne bisogno, prima di eseguire questa chiamata, ad esempio dopo il controllo iniziale degli errori. Routine di modifica dello stato del terminale quali nl() e cbreak() vanno chiamate dopo initscr().

Una volta allocate le finestre dello schermo, potete predisporle per il vostro programma. Se voi volete, diciamo, permettere allo schermo di scrollare, userete scrollok(). Se vorrete che il cursore venga lasciato nella posizione dell'ultima modifica, userete leaveok(). Se questo non avviene, refresh() muoverà il cursore alle coordinate (y,x) attuali della finestra dopo averla aggiornata.

Potete creare nuove finestre a piacere usando le funzioni newwin(), derwin() e subwin(). La routine delwin() vi permetterà di sbarazzarvi delle vecchie finestre. Tutte le opzioni descritte prima possono essere applicate a qualunque finestra.

Output

Ora che abbiamo messo le cose a posto, vorremo aggiornare il terminale. Le funzioni di base usate per cambiare quello che va su una finestra sono addch() e move(). addch() aggiunge un carattere alle coordinate attuali. move() cambia le coordinate attuali (y, x) in quelle che vorrete voi. Ritorna ERR se proverete a muovervi fuori della finestra. Come accennato sopra, potete combinare le due funzioni in mvaddch() per fare entrambe le cose in una volta.

Le altre funzioni di output come addstr() e printw(), chiamano tutte addch() per aggiungere caratteri alla finestra.

Dopo che avrete messo sulla finestra quello che vi volevate, quando vorrete che la parte del terminale coperta dalla finestra mostri il contenuto della finestra, dovrete chiamare refresh(). Allo scopo di ottimizzare l'aggiornamento, refresh() presuppone che ogni parte della finestra che non è cambiata dall'ultima esecuzione di refresh(), non sia cambiata neppure sul terminale, cioè che non abbiate rinfrescato una parte del terminale che ha una finestra sovrapposta. Se non è questo il caso, la routine touchwin() viene fornita per fingere che l'intera finestra sia cambiata, facendo sì che refresh() aggiorni l'intera sezione del terminale.

Se eseguite wrefresh() con curscr come argomento, farà sì che lo schermo mostri quello che curscr ritiene dovrebbe mostrare. Questo viene comodo nell'implementare un comando che ridisegni lo schermo in caso si sia pasticciato.

Input

La funzione complementare a addch() è getch() che, se echo è attiva, chiamerà addch() per eseguire la echo del carattere. Poichè il pacchetto screen ha bisogno di sapere in ogni momento che cosa c'è sul terminale, se bisogna fare la echo dei caratteri, la tty dovrà essere in modalità raw o cbreak. Poichè inizialmente il terminale ha la echo abilitata e si trova nell'ordinaria modalità cooked l'una o l'altra dovrà essere cambiata prima di chiamere getch(); altrimenti l'output del programma sarà indefinito.

Quando avete bisogno di accettare input per linee in una finestra, sono disponibili le funzioni wgetstr() e compagne. C'è addirittura la funzione wscanw() che può analizzare l'input della finestra in stile scanf()(3). Queste funzioni orientate alla linea attivano la echo durante l'esecuzione.

Il codice d'esempio di prima utilizza la chiamata keypad(stdscr, TRUE) per abilitare il supporto alla mappatura dei tasti funzione. Con questa caratteristica, la getch() esamina lo stream di input alla ricerca di sequenze di caratteri che corrispondano alle freccie ed ai tasti funzione. Queste sequenze sono trasformate in valori pseudo-caratteri. Le #define di questi valori solo elencate in curses.h. Le sequenze di caratteri da mappare nei valori delle #define sono determinati dalle capacità key_ nel database terminfo del terminale.

Uso dei Caratteri Scatola

La funzione addch() (ed alcune altre, incluse box() e border()) possono accettare come argomento alcuni pseudo-caratteri definiti dalle ncurses. Si tratta di valori #define impostati nell'header curses.h; guardate lì per un elenco completo (cercate il prefisso ACS_).

Le più utili tra le definizioni ACS sono i caratteri scatola. Si possono usare per disegnare sullo schermo scatole (appunto) e semplici grafici. Qualora il terminale non avesse tali caratteri, curses.h li mapperebbe in una serie di caratteri ASCII riconoscibili (benchè sgradevoli).

Colore e Attributi Carattere

Il pacchetto ncurses supporta le evidenziazioni inclusi il contrasto (standout), il rovescio (reverse-video), il sottolineato e il lampeggiante. Inoltre supporta il colore che è trattato come un ulteriore evidenziatore.

Gli evidenziatori sono codificati internamente nei bit alti del tipo pseudo-carattere (chtype) che curses.h usa per rappresentare il contenuto delle celle di uno schermo. Guardate l'header curses.h per una completa lista dei valori maschera degli evidenziatori (cercate il prefisso A_).

Ci sono due modi per evidenziare. Uno consiste nell'eseguire un OR logico tra il valore dell'evidenziatore ed il carattere argomento della addch(), o di qualunque altra chiamata di output che accetti per argomento il tipo chtype.

L'altro consiste nell'impostare il valore di evidenziazione corrente. Questo viene aggiunto (OR logico) ad ogni altro valore impostato prima. Per far questo si usano le funzioni attron(), attroff(), e attrset(); vedi le pagine del manuale per maggiori dettagli .

Il colore è una forma speciale di evidenziazione. Il pacchetto attualmente ragiona in termini di coppie di colori, combinazioni di colori di superficie e di sottofondo. Il codice di esempio di prima impostava fino ad otto coppie di colori, tutti quelli sicuramente disponibili. Nota che ogni coppia di colori è, in effetti, data dal nome del suo colore di superficie. Ogni altra gamma di otto valori non-discordanti avrebbe potuto essere usata come primo argomento delle init_pair().

Una volta eseguita una init_pair() che crea la coppia di colori N, potrete usare COLOR_PAIR(N) come un evidenziatore che fa apparire quella particolare combinazione di colori. Notate che COLOR_PAIR(N), con N costante, è a sua volta una costante di compilazione e può venire usata negli inizializzatori.

Interfaccciamento del Mouse

La libreria ncurses fornisce inoltre una interfaccia verso il mouse. Notate che questa è una caratteristica originale delle ncurses, e non fa parte nè dello standard XSI curses, nè delle curses System V Release 4, nè BSD. Perciò ti raccomandiamo di assoggettare il codice relativo al mouse ad una #ifdef usando la macro NCURSES_MOUSE_VERSION (fornita) in modo da non venir compilato e linkato su sistemi non-ncurses.

Al momento, il rilevamento degli eventi del mouse funziona solo sotto xterm(1). In futuro, ncurses rileverà la presenza di gpm(1), il server del mouse per sistemi Linux scritto da Alessandro Rubini, ed accetterà eventi del mouse attraverso di questo.

L'interfaccia del mouse è davvero semplice. Per attivarla si usa la funzione mousemask(), passandole come primo argomento una maschera di bit che specifichi i generi di eventi che volete che il vostro programma sia capace di vedere. Ritornerà la maschera di bit degli eventi che sono divenuti visibili, che differirà dall'argomento parametro se il dispositivo mouse non fosse in grado di rilevare alcuni degli eventi che avevate specificato.

Una volta attivato il mouse, il ciclo di comando della vostra applicazione dovrà attendersi di ritorno dalla wgetch() il valore KEY_MOUSE. Quando lo riceverà vorrà dire che un evento del mouse è stato accodato. Per estrarlo dalla coda, usate la funzione getmouse() (dovrete farlo prima della successiva wgetch(), altrimenti un altro evento del mouse potrebbe arrivare rendendo inaccessibile il precedente ).

Ogni chiamata alla getmouse() riempie una struttura (il cui indirizzo le avrete fornito) con i dati dell'evento del mouse. I dati dell'evento includono le coordinate della cella di carattere puntata dal mouse, relative allo schermo con origine a zero. E` anche inclusa una maschera di eventi. In questa maschera troverete settati i bit corrispondenti al tipo di evento riportato.

La struttura mouse contiene due campi addizionali che potrebbero essere significativi in futuro quando ncurses interfaccerà nuovi generi di dispositivi di puntamento. In aggiunta alle coordinate x e y, c'è posto per la coordinata z; potrebbe essere utile con quei touchscreen che possono riferire un valore di pressione o durata. C'è anche un campo identità del dispositivo, che potrebbe essere usato per distinguere tra dispositivi di puntamento multipli.

La classe degli eventi visibili può essere cambiata in ogni momento via mousemask(). Gli eventi che sono rilevati includono la pressione, il rilascio, il singolo, doppio e triplo click (potete impostare il massimo tempo di pressione del tasto per un click). Se non rendete visibili i click, verranno rilevati come coppie di premuto e lasciato. In alcuni ambienti la maschera degli eventi include bit di rilevamento dello stato dei tasti shift, alt e control sulla tastiera durante l'evento.

E' inoltre fornita una funzione per verificare se un evento del mouse avviene in una data finestra. Potete usarla per vedere se una data finestra dovrà considerare pertinente un certo evento del mouse.

Poichè il rilevamento degli eventi del mouse non sarà disponibile in tutti gli ambienti, sarebbe poco saggio costruire applicazioni ncurses che richiedano l'uso del mouse. Piuttosto dovreste usare il mouse come una scorciatoia per comandi di tipo punta-e-spara che la vostra applicazione accetterà normalmente da tastiera. Due dei giochi di prova contenuti nella distribuzione di ncurses (bs e knight) contengono codice che illustra come si possa fare ciò.

Vedi la pagina del manuale curs_mouse(3X) per completi dettagli sulle funzioni dell'interfaccia al mouse.

Per Ripulire

Allo scopo di ripulire quando si è finito con le ncurses, è fornita la routine endwin(). Ripristina le modalità tty come erano prima della chiamata alla initscr(), e sposta il cursore nell'angolo inferiore sinistro. Perciò prima di uscire, ogni volta che si sia usata la chiamata initscr(), dovrà venir chiamata la endwin().

2.3 Descrizione delle Funzioni

Descriviamo qui il comportamento dettagliato di alcune importanti funzioni delle curses, come supplemento alle descrizioni delle pagine del manuale.

Inizializzazione e Terminazione

initscr()

La prima funzione chiamata deve quasi sempre essere initscr(). Questa determinerà il tipo del terminale ed inizializzerà le strutture di dati delle curses. initscr() inoltre fa sì che la prima chiamata a refresh() pulisca lo schermo. Se accade un errore un messaggio viene scritto su standard error ed il programma esce. Altrimenti ritorna un puntatore a stdscr. Poche funzioni possono essere chiamate prima di initscr() (slk_init(), filter(), ripofflines(), use_env(), e, se usate più terminali, newterm().)

endwin()

Il vostro programma dovrà sempre chiamare endwin() prima di uscire o prima di lanciare una shell. Questa funzione ripristina le modalità tty, muove il cursore nell'angolo inferiore sinistro dello schermo, riporta il terminale nel proprio modo non-visuale. Chiamando refresh() o doupdate() dopo una uscita temporanea dal programma ripristinerà lo schermo ncurses precedente all'uscita.

newterm(type, ofp, ifp)

Un programma che invia il suo output a più di un terminale deve usare newterm() invece di initscr(). newterm() va chiamata una volta per ogni terminale. Ritorna una variabile di tipo SCREEN * che andrà salvata come riferimento a quel terminale. Gli argomenti sono il tipo di terminale (una stringa) e puntatori a FILE per l'input e per l'output del terminale. Se il tipo è NULL allora la variabile d'ambiente $TERM viene usata. In chiusura endwin() andrà chiamata una volta per ciascun terminale aperto con questa funzione.

set_term(new)

Questa funzione si usa per passare ad un diverso terminale aperto precedntemente da newterm(). Si passa come parametro il riferimento al nuovo terminale. La funzione ritorna il riferimento al precedente terminale. Tutte le altre chiamate agiscono solo sul terminale attivo.

delscreen(sp)

L'opposto di newterm(); rilascia le strutture di dati associate ad un dato riferimento a SCREEN.

Visualizzare sul Terminale

refresh() e wrefresh(win)

Queste funzioni vanno chiamate per visualizzare l'output sul terminale, in quanto le altre routine manipolano solamente dati in memoria. wrefresh() copia la finestra indicata sullo schermo fisico del terminale, tenendo presente quanto vi si trova già allo scopo di ottimizzare. refresh() rinfresca lo stdscr(). A meno che leaveok() non sia stato abilitato, il cursore fisico del terminale è lasciato alla locazione del cursore della finestra.

doupdate() e wnoutrefresh(win)

Queste due funzioni gestiscono aggiornamenti multipli con maggior efficienza di wrefresh(). Per usarle è importante comprendere come funzionano le curses. In aggiunta a tutte le strutture delle finestre, le curses mantengono due strutture di dati che rappresentano lo schermo del terminale: uno schermo fisico che descrive quanto si trova in quel momento sullo schermo, e uno schermo virtuale che descrive quel che il programmatore vuole sullo schermo. wrefresh() funziona prima copiando la finestra data sullo schermo virtuale (wnoutrefresh()), e quindi chiamando la routine per aggiornare lo schermo reale (doupdate()). Se il programmatore desidera mostrare diverse finestre insieme, una serie di chiamate a wrefresh causerà alternate chiamate a wnoutrefresh() e doupdate(), causando differenti lampi di output sullo schermo. Chiamando wnoutrefresh() per ciascuna finestra, è poi possibile chiamare doupdate() una volta sola, causando una unica visualizzazione, trasmettendo un minore numero di caratteri (ciò evita anche un noioso lampeggio ad ogni aggiornamento).

Accesso alle Capacità di Basso Livello

setupterm(term, filenum, errret)

Questa routine viene chiamata per inizializzare la descrizione del terminale senza creare le strutture degli schermi o cambiare i bit delle modalità della tty. term è la stringa di caratteri che rappresenta il nome del terminale in uso. filenum è il descrittore di file UNIX del terminale da usare per l'output. errret è un puntatore ad un intero in cui verrà inserita indicazione di successo o fallimento. I valori possono essere 1 (tutto bene), 0 (non c'è un terminale così), o -1 (qualche altro problema relativo al database terminfo).

Il valore di term puòessere NULL, causando l'uso del valore contenuto nella variabile d'ambiente TERM. Il puntatore errret può anch'egli valere NULL, indicando che il valore di errore non è richiesto. Se errret manca e qualcosa va storto, setupterm() mostrerà un appropriato messaggio d'errore ed uscità invece di ritornare. Così un semplice programma può chiamare setupterm(0, 1, 0) senza preoccuparsi degli errori di inizializzazione.

Dopo la chiamata a setupterm(), la variabile globale cur_term punta alla attuale struttura di capacità del terminale. Chiamando setupterm() per ciascun terminale e salvando e ripristinando cur_term, un programma può riuscire ad usare due o più terminali insieme. setupterm() inoltre immagazzina la sezione dei nomi di una descrizione del terminale in un array di caratteri globale ttytype[]. Successive chiamate a setupterm() sovrascrivono questo array, così dovrete salvarvelo se ne avete bisogno.

Debugging

NOTA: QUESTE FUNZIONI NON FANNO PARTE DELLA API STANDARD DELLE CURSES!

trace()

Questa funzione può essere usata per impostare esplicitamente un livello di tracciamento. se il livello di tracciamento è diverso da zero, l'esecuzione del vostro programma genererà un file chiamato 'trace' nel direttorio corrente contenente un rapporto sulle azioni della libreria. Più elevati livelli di tracciamento abilitano un rapporto più dettagliato (e verboso) -- per dettagli vedi i commenti acclusi alla definizione di TRACE_ nel file curses.h. E` inoltre possibile assegnare un livello di tracciamento assegnando un valore alla variabile d'ambiente NCURSES_TRACE.

_tracef()

Questa funzione può essere usata per mostrare informazioni di debug. E` disponibile solo linkando con -lncurses_g. Può essere usata nello stesso modo di printf(), solo che emette un newline alla fine degli argomenti. L'output va in un file chiamato trace nel direttorio corrente.

Le informazioni di trace possono essere difficili da interpretare a causa del solo volume di dati inseriti in essi. Esiste uno script chiamato tracemunch incluso nella distribuzione ncurses che può alleggerire in qualche modo questo problema; compatta lunghe sequenze di simili operazioni in pseudo-operazioni di una riga. Queste pseudo-operazioni sono riconoscibili dal fatto che hanno nomi maiuscoli.

2.4 Consigli, Suggerimenti e Trucchi

Le pagine del manuale delle ncurses sono un completo riferimento per questa libreria. Nel resto di questo documento discuteremo diversi utilizzi che potrebbero non sembrare ovvii leggendo le pagine del manuale.

Alcuni Punti di Cautela

Se ti dovessi ritrovare a pensare di aver bisogno di usare noraw() o nocbreak(), ripensaci e muoviti con cautela. E' probabilmente meglio progettare l'uso di getstr() o una delle sue consimili per simulare la modalità cooked. Le funzioni noraw() e nocbreak() provano a ripristinare la modalità cooked, ma potrebbero finire maltrattando alcuni bit di controllo settati prima che partisse la vostra applicazione. Inoltre sono sempre state malamente documentate, e probabilmente danneggerebbero la portabilità della vostra applicazione con altre implementazioni delle librerie curses.

Tenete bene in mente che refresh() è sinonimo di wrefresh(stdscr), non cercate di mescolare l'uso di stdscr con l'uso di finestre dichiarate da newwin(); una chiamata a refresh() può farle sparire dallo schermo. Il modo giusto per gestire questo è di usare subwin(), oppure di non toccare stdscr per nulla e riempire il vostro schermo di finestre dichiarate che poi voi rinfrescherete chiamando wnoutrefresh() da qualche parte del vostro programma, con una sola chiamata a doupdate() per far scattare l'aggiornamento.

E' molto meno probabile finire nei guai se progetterete schermate usando finestre accostate piuttosto che sovrapposte. Storicamente il supporto delle curses per le finestre sovrapposte è stato debole, fragile e poveramente documentato. La libreria ncurses non è ancora un'eccezione a questa regola.

C'è una libreria freeware di panel (lastre, pannelli) inclusa nella distribuzione ncurses che fa piuttosto un buon lavoro nel rinforzare le capacità di sovrapposizione delle finestre.

Provate ad evitare l'uso delle variabili globali LINES e COLS. Invece usate getmaxyx() su stdscr. Il motivo: il vostro codice potrebbe finire per girare in un ambiente con finestre ridimensionabili, nel qual caso parecchi schermi potrebbero avere misure diverse.

Sospendere Temporaneamente le ncurses

Alle volte vorrete scrivere un programma che passa la maggior parte del suo tempo in modalità schermo, ma occasionalmente nella modalità ordinaria cooked. Una ragione comune per questo è il supporto per lanciare una shell. Questo comportamento è semplice da organizzare nelle ncurses.

Per lasciare la modalità ncurses, chiamate endwin() come foste intenzionati a terminare il programma. Questo riporterà lo schermo in modalità cooked; potete lanciare la vostra shell. Quando vorrete ritornare in modalità ncurses, semplicemente chiamate refresh() o doupdate(). Questo ridisegnerà il vostro schermo.

C'è una funzione booleana, isendwin(), che può essere usata per verificare se la modalità ncurses è attiva. Ritorna TRUE nell'intervallo tra una endwin() e la successiva refresh(), altrimenti ritorna FALSE.

Ecco un pò di codice esempio per lanciare una shell:


addstr("Lancio shell..."); 
def_prog_mode();           /* salva le modalità tty correnti */ 
endwin();                  /* ripristina le modalità tty originali */ 
system("sh");              /* lancia la shell */ 
addstr("di ritorno.\n");   /* prepara il messagio */ 
refresh();                 /* ripristina le modalità salvate, */ 
                           /* ridisegna lo schermo */ 

Usare le ncurses sotto xterm(1)

In X una operazione di cambio delle dimensioni invia il segnale SIGWINCH all'applicazione che gira sotto xterm(1). La libreria ncurses non cattura questo segnale, perchè in generale non può sapere in qual modo voi volete ridisegnare lo schermo. Dovrete scrivervi il vostro gestore di SIGWINCH.

Il modo più facile di scrivere il vostro gestore di SIGWINCH è di chiamare endwin(), seguito da initscr() e da un ridisegno dello schermo che farete voi. initscr() si troverà le nuove dimensioni dello schermo dall'ambiente xterm(1).

Gestire più Schermi Terminale

La funzione initscr() attualmente chiama una funzione di nome newterm() per fare il più del suo lavoro. Se state scrivendo un programma che apre più terminali, usate newterm() direttamente.

Per ciascuna chiamata, dovrete specificare un tipo terminale ed una coppia di puntatori a file; ciascuna chiamata ritornerà un riferimento ad uno schermo, e stdscr verrà impostato all'ultimo allocato. Vi sposterete tra gli schermi con la chiamata set_term. Notate che dovrete inoltre chiamare def_shell_mode e def_prog_mode su ciascuna tty.

Provare le Capacità del Terminale

Alle volte vorrete scrivere programmi che saggino la presenza di varie capacità prima di decidere se entrare in modalità ncurses. Un modo facile di far questo è di chiamare setupterm(), e poi usare le funzioni tigetflag(), tigetnum(), e tigetstr() per le vostre prove.

Un caso particolarmente utile di questo viene quando volete verificare se un dato tipo di terminale deve essere trattato come capace (di muovere il cursore) oppure stupido. Il modo corretto di provare questo è di vedere se il valore ritornato da tigetstr(''cup'') è non-NULL. Alternativamente, potete includere il file term.h e saggiare il valore della macro cursor_address.

Regolare la Velocità

Usate la famiglia di funzioni addchstr() per una veloce scrittura di testo quando sapete che il testo non contiene alcun carattere di controllo. Provate a fare rari cambi di attributi sui vostri schermi. Non usate l'opzione immedok()!

Caratteristiche Speciali di ncurses

Quando gira sui cloni PC, ncurses ha supporto accresciuto per i set di caratteri IBM. L'evidenziatore A_ALTCHARSET abilita la visualizzazione sia della grafica della metà superiore che della grafica 0-31 della PC ROM che normalmente sono interpretati come caratteri di controllo.

2.5 Compatibilità con Precedenti Versioni

Nonostante tutti i nostri sforzi, esistono alcune differenze tra le ncurses ed il comportamento (non documentato!) di precedenti implementazioni delle curses. Ciò nasce da ambiguità ed omissioni nella documentazione della API.

Rinfresco di Finestre Sovrapposte

Se definite due finestre A e B che si sovrappongano, e quindi le scambiate e rinfrescate, gli aggiornamenti alla regione dove si sovrappongono non è spesso precisamente documentata nelle versioni storiche delle curses.

Per capire perchè ciò sia un problema, vi ricordo che gli aggiornamenti dello schermo vengono calcolati confrontando due rappresentazioni dell'intero schermo. La documentazione dice che quando si rinfresca una finestra, viene prima copiata sullo schermo virtuale, quindi i cambiamenti vengono calcolati per aggiornare lo schermo fisico (ed inviati al terminale). Ma ''copiata su'' non specifica molto, e sottili differenze nel come si esegue la copia può produrre diversi risultati nel caso che due finestre sovrapposte vengano rinfrescate ciascuna a inervalli indeterminati.

Quello che succede alla regione dove si sovrappongono dipende da ciò che la wnoutrefresh() fa con i suoi argomenti -- quali porzioni della finestra argomento viene copiata sullo schermo virtuale. Alcune implementazioni eseguono una ''copia dei cambiamenti'', copiando solo le parti della finestra che sono cambiate (o che sono state segnalate come cambiate dalla wtouchln e compagne). Altre implementazioni eseguono una ''intera copia'' copiando tutta la finestra sullo schermo virtuale, sia che fosse cambiata che no.

La stessa libreria ncurses non è stata sempre coerente su questo punto. A causa di un baco, le versioni dalla 1.8.7 alla 1.9.8a eseguiva l'intera copia. Le versioni fino alla 1.8.6 e le versioni 1.9.9 e successive eseguono la copia dei cambiamenti.

Per molte implementazioni commerciali delle curses, non è documentato e non è noto con certezza (almeno non ai manutentori delle ncurses) se eseguano la copia dei cambiamenti o la intera copia. Sappiamo che le curses del System V release 3 contiene della logica che sembra un tentativo di eseguire una copia dei cambiamenti, ma la logica circostante e le rappresentazioni dei dati sono sufficientemente complesse, e la nostra conoscienza sufficientemente indiretta, che è duro sapere quanto questo sia affidabile.

Non è chiaro cosa intenda la documentazione della SVr4 e dello standard XSI. Lo standard delle curses XSI accenna appena alla wnoutrefresh(); i documenti SVr4 sembrano stare descrivendo la intera copia, ma è possibile, con qualche sforzo e stortura leggere nel modo opposto.

E` comunque poco saggio confidare in uno di questi comportamenti in programmi che debbano essere linkati con altre implementazioni delle curses. Invece, potete fare una esplicita touchwin() prima di chiamare la wnoutrefresh() per garantire ovunque una copia dell'intero contenuto.

la via veramente pulita per gestire questo è di usare la libreria panel. Se, quando volete un aggiornamento dello schermo, fate una update_panels(), eseguirà tutte le necessarie chiamate alla wnoutrefresh() per qualunque ordine di sovrapposizione abbiate definito. Quindi potrete fare una doupdate e ci sarà una sola raffica di I/O fisico che eseguirà tutti i vostri aggiornamenti.

Cancellazione del Fondo

Se avete usato versioni di ncurses molto vecchie (1.8.7 o precedenti) potreste restare sorpresi dal comportamento delle funzioni di cancellazione. In versioni più vecchie, le aree cancellate di una finestra erano riempite di spazi modificati dagli attributi correnti della finestra (come settati da wattrset(), wattron(), wattroff() e compagne).

In nuove versioni, non è più così. Invece, l'attributo degli spazi cancellati è normale a meno che e finchè non viene modificato dalle funzioni bkgdset() o wbkgdset().

Questo cambiamento nel comportamento conforma le ncurses alla Release 4 di System V ed allo standard XSI Curses.

2.6 Conformità alle Curses XSI

La libreria ncurses intende essere conforme a livello base con le standard Curses XSI da X/Open. Molte caratteristiche di livello esteso (infatti almeno tutte le caratteristiche non direttamente connesse coi caratteri estesi e con l'internazionalizzazione) sono già supportate.

Un effetto della conformità XSI è il cambiamento di comportamento descritto in Cancellazione del Fondo -- Compatibilità con le Vecchie Versioni .

Inoltre, ncurses onora la richiesta XSI che ogni macro abbia una funzione corrispondente che possa essere linkata (e il cui prototipo venga controllato) se la definizione della macro viene disabilitata con #undef.


Precedente Successivo Indice