La libreria form
è una estensione delle curses che
supporta una facile programmazione di maschere video per l'inserimento
di dati e un facile controllo del programma.
La libreria form
apparve la prima volta nel
System V della AT&T.
La versione qui documentata è il codice freeware della form
distribuito con le ncurses
.
I vostri moduli che utilizzino la form devono importare le dichiarazioni della libreria form con
#include <form.h>
e dovrà essere esplicitamente linkato alla libreria form utilizzando
l'argomento -lform
. Notate che dovono anche linkare la
libreria ncurses
mediante -lncurses
. Molti moderni
linker sono a due passaggi ed accettano qualunque ordine, ma è buona
pratica mettere -lform
prima e -lncurses
dopo.
Una form è una collezione di campi: ciascun campi può essere sia un'etichetta (testo visualizzato) oppure un luogo per l'inserimento di dati. Grandi form possono venire suddivise in pagine; il passaggio ad un'altra pagina pulisce lo schermo. Per avere form, create gruppi di campi e connetteteli con oggetti riquadro di form; la libreria form vi rende ciò relativamente semplice.
Una volta definita, una form può essere pubblicata, cioè scritta nella finestra associata. Attualmente, ogni form ha due finestre associate; una finestra contenitore in cui il programmatore può scribacchiare titoli e bordi, ed una sottofinestra in cui i campi della form vengono visualizzati.
Mentre l'utente riempie la form pubblicata, comandi di navigazione
supportano spostamenti tra campi, comandi di editazione supportano
modifiche all'interno dei campi, e semplice testo aggiunge o modifica
i dati nel campo corrente. La libreria form (il designer) vi permette
di legare ogni comando di navigazione e di editazione
a qualunque tasto riconosciuto dalle curses
.
I campi possono avere condizioni di validazione imposte per controllare i dati d'input per tipo e valore. La libreria form supporta un ricco insieme di tipi di campo predefiniti, e rende relativamente facile definirne di nuovi.
Appena la transazione è completata (o annullata), una form può venir spubblicata (cioè de-visualizzata), ed infine la memoria associata ad essa ed ai suoi campi lasciata disponibile al riutilizzo.
Il flusso generico del controllo di una form somiglia a questo:
curses
.new_field()
.new_form()
.form_post()
.form_unpost()
.free_form()
.free_field()
.curses
.Notate come questo somigli molto ad un programma menù: la libreria form gestisce compiti simili sotto diversi aspetti, e la sua interfaccia è stata ovviamente progettata per assomigliare alla libreria menù laddove possibile.
Nei programmi form, comunque, la ''gestione delle richieste dell'utente'' è in ogni modo più complicata. Oltre alle operazioni di navigazione simili al menù, il ciclo di gestione del menù deve supportare l'editazione dei campi e la validazione dei dati.
La funzione base per creare campi è la new_field()
:
FIELD *new_field(int height, int width, /* dimensioni del campo */
int top, int left, /* angolo superiore sinistro */
int offscreen, /* numero di righe fuori-schermo */
int nbuf); /* numero di buffer di lavoro */
Le voci di un menù occupano sempre una sola riga, ma i campi di una form
possono occupare più righe. Così new_field()
vi richiede di
specificare larghezza ed altezza (i primi due argomenti, che devono
essere entrambi maggiori di zero).
Dovete inoltre specificare la locazione dell'angolo superiore sinistro
del campo sullo schermo (il terzo ed il quarto argomento, che devono
valere zero o più). Notate che queste coordinate sono relative alla
sottofinestra della form, che coincide inizialmente con stdscr
,
ma non è necessariamente stdscr
se avete chiamato
esplicitamente la set_form_window()
.
Il quinto argomento vi permette di specificare un numero di righe fuori schermo. Se questo è zero, l'intero campo sarà sempre visualizzato. Se non è zero, l'intera form diverrà scrollabile, con solo uno schermo intero (inizialmente la parte superiore) visualizzato in ogni momento. Se voi create un campo dinamico e lo fate crescere in modo che non stia più nello schermo, la form diverrà scrollabile anche se l'argomento fosse zero.
La libreria form alloca un buffer di lavoro per ogni campo:
la dimensione di ciascun campo è data da
((altezza + fuori-schermo)*larghezza + 1
, un carattere
per ogni posizione nel campo più un terminatore NULL.
Il sesto argomento è il numero di buffer di lavoro addizionali
da allocare per il campo: la vostra applicazione può usarli
per i suoi scopi.
FIELD *dup_field(FIELD *field, /* campo da copiare */
int top, int left); /* posizione della nuova copia */
La funzione dup_field()
duplica un campo esistente in una
nuova posizione. Dimensioni e dati nei buffer vengono copiati;
alcuni attributi e bit di stato no (vedi la
form_field_new(3X)
dettagli).
FIELD *link_field(FIELD *field, /* campo da copiare */
int top, int left); /* posizione della nuova copia */
Anche la funzione link_field()
duplica un campo esistente
in una nuova posizione. La differenza colla dup_field()
è
che fa sì che il buffer del nuovo campo sia in comune col vecchio.
Oltre all'ovvio utilizzo nel creare un campo editabile in due differenti pagine di una form, campi linkati vi danno un modo di creare etichette dinamiche. Se dichiarate parecchi campi linkati ad un originale, e li rendete inattivi, cambiamenti nell'originale si propagheranno agli altri campi linkati.
Come i campi duplicati, anche i campi linkati hanno bit di attributi separati dall'originale.
Come potete immaginare, tutte queste allocazioni ritornano NULL
se la allocazione non è possibile a causa di un errore di memoria
o di argomenti fuori limiti.
Per connettere i campi ad una form, usate
FORM *new_form(FIELD **fields);
Questa funzione si aspetta un vettore (terminato da NULL) di puntatori a campi. Detti campi sono connessi ad un oggetto form di nuova allocazione; viene ritornato il suo indirizzo (o NULL se la creazione fallisce).
Notate che la new_field()
non copia il vettore in
memoria interna; se voi ne modificate il contenuto durante
l'elaborazione della form, possono accadere un sacco di cose bizzarre.
Inoltre notate che ogni campo dato può essere connesso ad una sola form.
Le funzioni free_field()
e free_form
sono disponibili
per rilasciare campi ed oggetti form. È un errore cercare
di rilasciare un campo connesso ad una form, ma non vice-versa;
perciò generalmente rilascerete per prime le vostre form.
Ogni campo di una form ha un certo numero di attributi posizione
e dimensione associati ad esso.
Ci sono altri attributi del campo usati per controllare la
visualizzazione e l'editazione del campo.
Alcuni (ad esempio, il bit O_STATIC
) implica sufficienti
complicazioni da necessitare di loro proprie sezioni più oltre.
Qui ci occupereremo delle funzioni usate per interagire con parecchi
attributi di base.
Quando si crea un campo, gli attributi non specificati dalla funzione
new_field
vengono ricopiati da un campo invisibile di
riferimento. Nelle funzioni di cambio e cattura degli attributi
un argomento NULL intende fare riferimento a questo campo. Cambiamenti
in esso sono permanenti sino alla terminazione dell'applicazione.
Potete prelevare dimensioni e locazioni di un campo attraverso:
int field_info(FIELD *field, /* campo dal quale prelevare */
int *height, *int width, /* dimensioni del campo */
int *top, int *left, /* angolo superiore sinistro */
int *offscreen, /* numero di righe fuori-schermo */
int *nbuf); /* numero di buffer di lavoro */
Questa funzione è una specie di inverso della new_field()
;
invece di settare dimensione e locazione di un nuovo campo, lei
li cattura da uno esistente.
È possibile spostare la posizione di un campo sullo schermo:
int move_field(FIELD *field, /* campo da spostare */
int top, int left); /* nuovo angolo superiore sinistro */
Potete, naturalmente, vedere la locazione corrente attraverso la
field_info()
.
I campi di una riga possono essere senza-giustificazione, giustificati a destra, a sinistra o centrati. Ecco come manipolare questo attributo:
int set_field_just(FIELD *field, /* campo da modificare */
int justmode); /* modalità da impostare */
int field_just(FIELD *field); /* cattura la modalità del campo */
I valori modalità accettati e ritornati da questa funzione sono le
macro NO_JUSTIFICATION
, JUSTIFY_RIGHT
,
JUSTIFY_LEFT
, o JUSTIFY_CENTER
.
Per ciascun campo, potete impostare un attributo di primo piano per i caratteri inseriti, un attributo di sottofondo per l'intero campo, ed un carattere riempitivo per le porzioni vuote del campo. Potete inoltre controllare la paginazione della form.
Questo gruppo di quattro attributi di campo controllano l'aspetto visivo del campo sullo schermo, senza influenzare in alcun modo i dati nel buffer del campo.
int set_field_fore(FIELD *field, /* campo da modificare */
chtype attr); /* attributo da impostare */
chtype field_fore(FIELD *field); /* campo da interrogare */
int set_field_back(FIELD *field, /* campo da modificare */
chtype attr); /* attributo da impostare */
chtype field_back(FIELD *field); /* campo da interrogare */
int set_field_pad(FIELD *field, /* campo da modificare */
int pad); /* carattere riempitivo da impostare */
chtype field_pad(FIELD *field);
int set_new_page(FIELD *field, /* campo da modificare */
int flag); /* TRUE per forzare una nuova pagina */
chtype new_page(FIELD *field); /* campo da interrogare */
Gli attributi impostati e ritornati dalle prime quattro funzioni sono i
normali attributi video delle curses(3x)
(A_STANDOUT
,
A_BOLD
, A_REVERSE
ecc.).
Il bit di pagina di un campo controlla se la sua visualizzazione avviene all'inizio di una nuova schermata di form.
C'è anche una vasta collezione di bit-opzione di campo che potete impostare per controllare vari aspetti dell'elaborazione delle form. Potete manipolarle con queste funzioni:
int set_field_opts(FIELD *field, /* campo da modificare */
int attr); /* attributi da impostare */
int field_opts_on(FIELD *field, /* campo da modificare */
int attr); /* attributi da attivare */
int field_opts_off(FIELD *field, /* campo da modificare */
int attr); /* attributi da disattivare */
int field_opts(FIELD *field); /* campo da interrogare */
Inizialmente, tutte le opzioni sono attive. Questi sono i bit-opzione disponibili:
Controlla se il campo è visibile sullo schermo. Può essere usato durante l'elaborazione della form per nascondere o visualizzare dei campi in base al valore di altri campi.
Controlla se il campo è attivo durante l'elaborazione della form (per esempio visitabile mediante i comandi di navigazione). Può essere usato per creare etichette o campi derivati con buffer modificabili dalla applicazione, non dall'utente.
Controlla se i dati sono visualizzati durante l'inserimento nel campo. Se questa opzione è spenta su un campo, la libreria accetterà dati in quel campo, ma non vi sarà visualizzazione ed il cursore non si muoverà. Potete disattivare il bit O_PUBLIC per i campi password.
Controlla se i dati nel campo possono essere modificati. Quando questa
opzione è disabilitata, tutte le richieste di editing eccetto
REQ_PREV_CHOICE
e REQ_NEXT_CHOICE
falliranno.
Tali campi di sola lettura possono essere utili per messaggi di help.
Controlla l'a-capo nei campi multi linea. Normalmente, quando un carattere di una parola (separata da spazi) raggiunge la fine della riga corrente, l'intera parola viene spostata alla linea successiva (ammesso che ve ne sia una). Quando questa opzione è disattivata, la parola verrà spezzata alla fine della linea.
Controlla lo sbiancamento del campo. Quando questa opzione è attiva, lo scrivere un carattere nella prima posizione del campo provoca la cancellazione dell'intero campo (eccetto il carattere appena inserito).
Controlla il salto automatico al campo successivo quando un campo è pieno. Normalmente, quando l'utente prova ad inserire più dati di quanti ne possano stare nel campo, il punto di editazione passa nel campo successivo. Quando questa opzione è disattivata il cursore dell'utente si blocca alla fine del campo. Questa opzione è ignorata nei campi dinamici che non hanno raggiunto la loro dimensione massima.
Controlla se la validazione si applica al campo vuoto. Normalmente non lo è; l'utente può lasciare il campo vuoto senza che venga eseguita l'usuale validazione all'uscita. Se questa opzione è spenta su un campo, all'uscita avverrà comunque la validazione.
Controlla se la validazione avviene ad ogni passaggio oppure solo se il campo è stato modificato. Normalmente l'ultima è vera. Settare O_PASSOK può essere utile se la vostra funzione di validazione può cambiare durante la elaborazione della form.
Controlla se il campo è fissato alle sue dimensioni originali. Se spegnete questa opzione, il campo diventa dinamico e verrà allungato per contenere i dati inseritivi.
Una opzione di un campo non può venir cambiata quando il campo è correntemente selezionata. Comunque, le opzioni possono essere cambiate nei campi pubblicati che non siano correnti.
I valori delle opzioni sono maschere di bit e possono venir composti mediante OR logico nel modo noto.
Ogni campo ha un flag di stato che può essere posto a FALSE quando il campo è creato e a TRUE quando il valore nel buffer zero cambia. Questo flag può essere impostato e interrogato direttamente:
int set_field_status(FIELD *field, /* campo da modificare */
int status); /* modalità da impostare */
int field_status(FIELD *field); /* cattura la modalità del campo */
Impostare questo campo da programma può essere utile se usate la stessa form ripetutamente, cercando ogni volta i campi modificati.
Chiamando field_status()
su un campo non correntemente selezionato
per l'input fornirà il valore corretto.
Chiamando field_status()
su un campo correntemente selezionato
per l'input può non necessariamente dare il valore corretto,
perchè i dati inseriti non vengono necessariamente copiati nel
buffer zero prima del controllo di validazione in uscita.
Per garantire che il valore di stato ritornato rifletta la realtà,
chiamate la field_status()
sia (1) nella routine di validazione
in uscita dal campo, che (2) dalle routine agganciate
all'inizializzazione o terminazione, oppure (3) proprio dopo che la
richiesta REQ_VALIDATION
è stata elaborata dal driver della form.
Ogni struttura di campo contiene un puntatore a carattere che non è usato dalla libreria form. Il suo uso è specifico all'applicazione per contenere dati privati del campo. È possibile manipolarlo mediante:
int set_field_userptr(FIELD *field, /* campo da modificare */
char *userptr); /* puntatore da inizializzare */
char *field_userptr(FIELD *field); /* recupera il valore del puntatore */
(In verità questo puntatore dovrebbe essere di tipo (void *)
.
Il tipo (char *)
è mantenuto per compatibilità con System V.)
È permesso inizializzare il puntatore del campo di riferimento
(con una chiamata a set_field_userptr()
con il puntatore al
campo posto a NULL.)
Quando un nuovo campo viene creato, il puntatore d'utente del
campo di riferimento viene copiato per inizializzare il puntatore
d'utente del nuovo campo.
Normalmente, un campo è limitato alle dimensioni per esso specificate al momento della sua creazione. Se comunque viene disattivato il bit O_STATIC, il campo diventa dynamic e si ridimensionerà automaticamente per far posto ai dati che vengono inseriti. Se il campo ha buffer addizionali associati ad esso, questi cresceranno di pari passo con il buffer principale di input.
Un campo dinamico di una sola riga avrà altezza fissa (uno, appunto), ma larghezza variabile, scorrendo orizzontalmente i dati all'interno dell'area del campo originariamente dimensionata. Un campo dinamico a più righe avrà larghezza fissa, ma altezza variabile (numero di righe), scrollando verticalmente per visualizzare i dati all'interno dell'area del campo come originariamente dimensionata.
Normalmente ad un campo dinamico è consentito di crescere senza limite. Ma è possibile fissare un limite superiore alla dimensione di un campo dinamico. Lo fate con questa funzione:
int set_max_field(FIELD *field, /* campo da modificare (non accetta NULL) */
int max_size); /* limite superiore alla dimensione del campo */
Se il campo è a riga singola, max_size
è il numero limite
di colonne; se è a più righe, è il numero limite di righe.
Per disabilitare ogni limite usate un argomento zero.
Il limite di crescita può venir cambiato sia che il bit
O_STATIC
sia attivo o meno, ma non avrà effetto finchè
non sarà attivo.
Le seguenti proprietà di un campo cambiano quando diventa dinamico:
O_AUTOSKIP
e O_NL_OVERLOAD
vengono ignorati.dup_field()
e link_field()
copiano le
dimensioni dei buffer dinamici. Se la opzione O_STATIC
è attiva in
uno di una serie di link, il ridimensionamento del buffer avverrà solo
quando il campo viene editato attraverso quel link.field_info()
ritornerà la dimensione statica
originaria del campo; usate la dynamic_field_info()
per ottenere
la sua attuale dimensione.Inizialmente, ogni campo accetterà qualunque dato possa entrare nel suo buffer. Comunque è possibile attaccare un tipo validazione ad un campo. Se lo fate, ogni tentativo di lasciare un campo che contiene dati che non soddifano la validazione, fallirà. Alcuni tipi di validazione hanno anche un controllo di validità del carattere ad ogni carattere inserito nel campo.
Il controllo di validità di un campo (se esiste) non viene eseguito
quando set_field_buffer()
modifica il buffer di input,
nè quando quel buffer viene modificato attraverso un campo linkato.
La libreria form
dispone di un ricco insieme di tipi di validazione
pre-definiti, e vi dà la capacità di definirne di vostri.
Potete esaminare e cambiare gli attributi di validazione di un
campo con le seguenti funzioni:
int set_field_type(FIELD *field, /* campo da modificare */
FIELDTYPE *ftype, /* tipo da associare */
...); /* argomenti addizionali*/
FIELDTYPE *field_type(FIELD *field); /* campo da interrogare */
Il tipo validazione di un campo è considerato un attributo del campo.
Come per altri attributi di campo, eseguire set_field_type()
su un campo NULL
modifica l'attributo di validazione del
campo di riferimento per i campi creati successivamente.
Ecco i tipi validazione pre-definiti:
Questo tipo accessta dati alfabetici; niente spazi, cifre o caratteri speciali (questo controllo avviene all'input di ogni carattere). Viene impostato con:
int set_field_type(FIELD *field, /* campo da modificare */
TYPE_ALPHA, /* tipo da associare */
int width); /* larghezza minima accettata */
L'argomento width
imposta una quantità minima di dati. Tipicamente
lo imposterete alla lunghezza del campo; se è più grande della larghezza
del campo, il controllo di validità fallirà sempre.
Una larghezza minima a zero rende l'inserimento nel campo opzionale.
Questo tipo accetta dati alfanumerici; niente spazi, nè caratteri speciali (questo controllo avviene all'input di ogni carattere). Viene impostato con:
int set_field_type(FIELD *field, /* campo da modificare */
TYPE_ALPHA, /* tipo da associare */
int width); /* larghezza minima accettata */
L'argomento width
imposta una quantità minima di dati.
Come nel TYPE_ALPHA, tipicamente lo imposterete alla lunghezza
del campo; se è più grande della larghezza del campo,
il controllo di validità fallirà sempre.
Una larghezza minima a zero rende l'inserimento nel campo opzionale.
Questo tipo vi permette di restringere i valori di un campo ad uno specificato insieme di stringhe (per esempio, le sigle provinciali a due lettere). Viene impostato con:
int set_field_type(FIELD *field, /* campo da modificare */
TYPE_ENUM, /* tipo da associare */
char **valuelist; /* lista di possibili valori */
int checkcase; /* sensibilità alle maiuscole */
int checkunique); /* deve essere univoco? */
Il parametro valuelist
deve puntare ad una lista di stringhe
valide terminata da NULL.
L'argomento checkcase
, se TRUE, rende il confronto
sensibile alle maiuscole/minuscole.
Quando l'utente lascia un campo TYPE_ENUM, la procedura di validazione prova a completare i dati nel buffer con una stringa valida. Se una stringa interamente valida è stata inserita, naturalmente viene accettata. È inoltre possibile inserire la parte iniziale di una stringa valida ed ottenerla completa.
Inizialmente, se inserite un prefisso e questo coincide
con più di un valore nella lista di stringhe,
il prefisso verrà completato con lo primo valore coincidente.
Ma se l'argomento checkunique
è TRUE, si richiede
che il prefisso coincida con un solo valore perchè venga accettato.
Le richieste REQ_NEXT_CHOICE
e REQ_PREV_CHOICE
possono essere
particolarmente utili con questi campi.
Questo tipo di campo accetta un intero. si imposta nel modo seguente:
int set_field_type(FIELD *field, /* campo da modificare */
TYPE_INTEGER, /* tipo da associare */
int padding, /* numero di posti da riempire di zeri */
int vmin, int vmax); /* valori limite */
Caratteri validi consistono in un ''meno'' iniziale opzionale e cifre. La verifica dei limiti viene eseguita all'uscita. Se il valore massimo è minore o uguale al minimo, i limiti vengono ignorati.
Se il valore supera il controllo sui limiti, vengono aggiunti tanti zeri iniziali quanti indicati nell'argomento padding.
Un valore in un buffer TYPE_INTEGER
può venire convenientemente
interpretato dalla funzione standard atoi(3)
.
Questo campo accetta un numero decimale. Viene impostato così:
int set_field_type(FIELD *field, /* campo da modificare */
TYPE_INTEGER, /* tipo da associare */
int padding, /* precisione */
int vmin, int vmax); /* valori limite */
Caratteri validi consistono in un ''meno''iniziale opzionale, cifre, un punto decimale. Il controllo sui limiti viene eseguito in uscita. Se il limita massimo è minore o uguale al limite minimo, i limiti vengono ignorati.
Se il valore supera il controllo sui limiti, vengono aggiunti tanti zeri iniziali quanti indicati nell'argomento padding.
Un valore in un buffer TYPE_NUMERIC
può venire convenientemente
interpretato dalla funzione standard atof(3)
.
Questo tipo di campo accetta dati che soddisfino una espressione regolare. Viene impostato come segue:
int set_field_type(FIELD *field, /* campo da modificare */
TYPE_REGEXP, /* tipo da associare */
char *regexp); /* espressione da confrontare */
La sintassi dell'espressione regolare è la stessa della regcomp(3)
.
Il controllo sulla espressione regolare è eseguito in uscita.
L'attributo più centrale di un campo è il contenuto del suo buffer. Quando una form è stata completata, la vostra applicazione di solito ha bisogno di conoscere lo stato del buffer di ogni campo. Potete trovarlo con questa:
char *field_buffer(FIELD *field, /* campo da interrogare */
int bufindex); /* numero del buffer da interrogare */
Di norma, lo stato del buffer numero zero di ciascun campo è impostato dalle azioni di editazione dell'utente su quel campo. È alle volte utile poter impostare il valore del buffer numero zero (o altri) dalla vostra applicazione:
int set_field_buffer(FIELD *field, /* campo da modificare */
int bufindex, /* numero del buffer da modificare */
char *value); /* valore stringaa cui impostare il buffer */
Se il campo non è abbastanza largo e non può essere ridimensionato ad una dimensione sufficiente per contenere il valore specificato, il valore verrà troncato alla misura.
Chiamare la field_buffer()
con un puntatore NULL genera un errore.
Chiamare la field_buffer()
su un campo non correntemente
selezionato produce un valore corretto.
Chiamare la field_buffer()
su un campo correntemente
selezionato per l'input può non fornire necessariamente il
corretto valore del buffer, perchè i dati inseriti non vengono
necessariamente copiati nel buffer zero prima della
validazione in uscita.
Per garantire che il valore del buffer ritornato rifletta
la realtà sullo schermo, chiamate la field_buffer()
sia (1) nella routine di validazione in uscita dal campo,
che (2) dalle routine agganciate all'inizializzazione o
terminazione del campo o della form, oppure (3) proprio
dopo che la richiesta REQ_VALIDATION
è stata elaborata
dal driver della form.
Come con gli attributi di campo, gli attributi di form ereditano
valori da una struttura form di riferimento.
Questi valori iniziali possono essere interrogati o modificati
da una di queste funzioni usando NULL
come argomento
del parametro puntatore a form.
Il più importante attributo di una form è la sua lista di campi. Potete interrogare e cambiare questa lista con:
int set_form_fields(FORM *form, /* form da modificare */
FIELD **fields); /* campi da connettere */
char *form_fields(FORM *form); /* recupera i campi della form */
int field_count(FORM *form); /* conta i campi connessi */
Il secondo argomento della set_form_fields()
può essere un
vettore terminato da NULL di puntatori a campi come quello richiesto
dalla new_form()
. In questo caso i vecchi campi della form sono
disconnessi ma non rilasciati (e quindi in grado di essere connessi ad
altre form), quindi i nuovi campi vengono connessi.
Può anche essere NULL, nel qual caso i vecchi campi sono disconnessi (e non rilasciati) ma nessun nuovo campo viene connesso.
La funzione field_count()
semplicemente ritorna il numero di campi
connessi ad una data form. Ritorna -1 se l'argomento puntatore a form
è NULL.
Nella sezione iniziale, dicemmo che per visualizzare una
form normalmente si parte definendo le sue dimensioni
(e i campi), pubblicandola, e rinfrescando lo schermo.
C'è un passo nascosto prima della pubblicazione, che consiste
nell'associazione della form con una finestra riquadro
(attualmente una coppia di finestre) in cui verrà visualizzata.
Inizialmente, la libreria form associa ogni form con la
finestra tutto-schermo stdscr
.
eseguendo esplicitamente questo passo intermedio, potrete associare una form con una finestra riquadro dichiarata sul vostro schermo. Questo può essere utile se volete adattare la form a diverse dimensioni dello schermo, legare dinamicamente le form allo schermo o usare una form in una interfaccia gestita da pannelli.
Le due finestre associate con ciascuna form hanno le stesse funzioni delle loro analoghe nella libreria menù. Entrambe queste finestre sono visualizzate quando la form è pubblicata, e cancellate quando è spubblicata.
La finestra più esterna non è in alcun modo toccata dalle routine della form. Esiste affinchè il programmatore possa associarvi un titolo, un bordo o perfino testo di help insieme con la form ed averlo correttamente rinfrescato o cancellato al momento giusto. La finestra interna o sottofinestra è dove la form viene attualmente visualizzata.
Allo scopo di dichiarare la vostra finestra riquadro per una form, avete bisogno di sapere le dimensioni del rettangolo che contiene la form. Potete ottenere questa informazione con:
int scale_form(FORM *form, /* form da consultare */
int *rows, /* righe della form */
int *cols); /* colonne della form */
Le dimensioni della form sono inserite nelle locazioni puntate dagli argomenti. Una volta che avete queste informazioni, potete usarle per dichiarare le finestre, quindi usare una di queste funzioni:
int set_form_win(FORM *form, /* form da modificare */
WINDOW *win); /* finestra da connettere */
WINDOW *form_win(FORM *form); /* ritorna la finestra della form */
int set_form_sub(FORM *form, /* form da modificare */
WINDOW *win); /* sottofinestra da connettere */
WINDOW *form_sub(FORM *form); /* ritorna la sottofinestra della form */
Notate che le operazioni curses, inclusa la wrefresh()
, sulla form,
vanno eseguite sulla finestra, non sulla sottofinestra.
È possibile controllare dalla vostra applicazione se tutto un campo scrollabile è visualizzato attualmente nella sottofinestra. Usate queste funzioni:
int data_ahead(FORM *form); /* form da interrogare */
int data_behind(FORM *form); /* form da interrogare */
La funzione data_ahead()
ritorna TRUE se (a) il campo corrente
è a singola riga ed ha dati non-visualizzati alla destra, (b) il campo
corrente è multi-riga e ci sono dati fuori schermo di sotto.
La funzione data_behind()
ritorn TRUE se il carattere alla
prima posizione (a sinistra in alto) è fuori schermo (non visualizzato).
Infine, c'è una funzione per ripristinare il cursore della finestra della form al valore atteso dal driver delle form:
int pos_form_cursor(FORM *) /* form da interrogare */
Se la vostra applicazione modifica il cursore della finestra della form, chiamate questa funzione prima di restituire il controllo al driver delle form allo scopo di re-sincronizzarlo.
La funzione form_driver()
gestisce richieste di input virtuali
per la navigazione, editazione, validazione della form, proprio come la
menu_driver
fa per i menù (vedere la sezione
Gestione dell'Input del menù ).
int form_driver(FORM *form, /* form a cui passare l'input */
int request); /* codice richiesta */
La vostra funzione di input virtuale deve prendere l'input e convertirlo o in un carattere alfanumerico (che viene trattato come dato da inserire nel campo correntemente selezionato), o una richiesta di elaborazione della form.
Il driver delle form fornisce agganci (attraverso le funzioni di validazione dell'input e di terminazione del campo) con cui la vostra applicazione può controllare che l'input ricevuto soddisfi quello atteso.
Queste richieste causano movimenti a livello di pagina nella form, facendo scattare la visualizzazione di una nuova schermata.
REQ_NEXT_PAGE
Si sposta alla prossima pagina nella form.
REQ_PREV_PAGE
Si sposta alla precedente pagina nella form.
REQ_FIRST_PAGE
Si sposta alla prima pagina della form.
REQ_LAST_PAGE
Si sposta all'ultima pagina della form.
Queste richieste usano la lista come ciclica; cioè, REQ_NEXT_PAGE
dall'ultima pagina riporta alla prima, e REQ_PREV_PAGE
dalla prima
pagina porta all'ultima.
Queste richieste gestiscono la navigazione tra campi di una stessa pagina.
REQ_NEXT_FIELD
Si sposta al prossimo campo.
REQ_PREV_FIELD
Si sposta al campo precedente.
REQ_FIRST_FIELD
Si sposta al primo campo.
REQ_LAST_FIELD
Si sposta all'ultimo campo.
REQ_SNEXT_FIELD
Si sposta al prossimo campo sullo schermo.
REQ_SPREV_FIELD
Si sposta al precedente campo sullo schermo.
REQ_SFIRST_FIELD
Si sposta al primo campo sullo schermo.
REQ_SLAST_FIELD
Si sposta all'ultimo campo sullo schermo.
REQ_LEFT_FIELD
Si sposta sul campo alla sinistra.
REQ_RIGHT_FIELD
Si sposta sul campo alla destra.
REQ_UP_FIELD
Si sposta al campo di sopra.
REQ_DOWN_FIELD
Si sposta al campo di sotto.
Queste richieste considerano la lista di campi in una pagina
come ciclica; cioè REQ_NEXT_FIELD
dall'ultimo riporta
al primo, e REQ_PREV_FIELD
dal primo riporta all'ultimo.
L'ordine dei campi per queste (più le REQ_FIRST_FIELD
e
REQ_LAST_FIELD
) segue l'ordine dei puntatori nel vettore
della form (come costruito dalla new_form()
o dalla
set_form_fields()
.
È inoltre possibile scorrere i campi come se fossero stati ordinati in base alla loro posizione sullo schermo, in una sequenza che va da sinistra a destra e dall'alto verso il basso. Per far ciò, utilizzate il secondo gruppo di richieste.
Infine, è possibile muoversi tra i campi usando le direzioni su, giù, destra e sinistra. Per ottenere questo, usate il terzo gruppo di richieste. Notate, comunque, che la posizione di un campo, per lo scopo di queste richieste, è quella del suo angolo superiore sinistro.
Per esempio, supponete di avere il campo multi-riga B, e due campi
a singola riga A e C sulla stessa riga di B, con A alla sinistra di B
e C alla destra di B. Una REQ_MOVE_RIGHT
da A porterà a B
solo se A, B e C hanno tutti la prima riga in comune;
altrimenti salterà B verso C.
Queste richieste guidano i movimenti del cursore all'interno del campo correntemente selezionato.
REQ_NEXT_CHAR
Muove al prossimo carattere.
REQ_PREV_CHAR
Muove al precedente carattere.
REQ_NEXT_LINE
Muove alla prossima riga.
REQ_PREV_LINE
Muove alla precedente riga.
REQ_NEXT_WORD
Muove alla prossima parola.
REQ_PREV_WORD
Muove alla precedente parola.
REQ_BEG_FIELD
Muove all'inizio del campo.
REQ_END_FIELD
Muove alla fine del campo.
REQ_BEG_LINE
Muove all'inizio della riga.
REQ_END_LINE
Muove alla fine della riga.
REQ_LEFT_CHAR
Muove alla sinistra nel campo.
REQ_RIGHT_CHAR
Muove alla destra nel campo.
REQ_UP_CHAR
Muove sù nel campo.
REQ_DOWN_CHAR
Muove giù nel campo.
Ogni parola è separata dai precedenti e dai successivi caratteri tramite spazi. I comandi per muovere all'inizio e alla fine della riga o del campo, cercano il primo o l'ultimo carattere non riempitivo.
Campi dinamici che siano cresciuti e campi esplicitamente creati con righe fuori schermo sono scorrevoli. Campi a singola riga scorrono orizzontalmente; campi multi-riga scorrono verticalmente. La maggior parte dello scorrimento è attivata dall'editing e da movimenti nel campo (la libreria fa scorrere il campo per mantenere visibile il cursore). È possibile ottenere esplicitamente lo scorrimento con le seguenti richieste:
REQ_SCR_FLINE
Scorre verticalmente in avanti di una riga.
REQ_SCR_BLINE
Scorre verticalmente indietro di una riga.
REQ_SCR_FPAGE
Scorre verticalmente in avanti di una pagina.
REQ_SCR_BPAGE
Scorre verticalmente indietro di una pagina.
REQ_SCR_FHPAGE
Scorre verticalmente in avanti di mezza pagina.
REQ_SCR_BHPAGE
Scorre verticalmente indietro di mezza pagina.
REQ_SCR_FCHAR
Scorre orizzontalmente in avanti di un carattere.
REQ_SCR_BCHAR
Scorre orizzontalmente indietro di un carattere.
REQ_SCR_HFLINE
Scorre orizzontalmente in avanti della lunghezza del campo.
REQ_SCR_HBLINE
Scorre orizzontalmente indietro della lunghezza del campo.
REQ_SCR_HFHALF
Scorre orizzontalmente in avanti di metà lunghezza del campo.
REQ_SCR_HBHALF
Scorre orizzontalmente indietro di metà lunghezza del campo.
Negli scorrimenti, una pagina di un campo e l'altezza della sua parte visibile.
Il passare un carattere ASCII al driver delle form viene trattato come fosse una richiesta di inserire il carattere nel buffer del campo. Dipende dalla modalità di edit se questo è un inserimento oppure una ricopertura (inizialmente è inserimento).
Le seguenti richieste supportano l'editazione del campo ed il cambio della modalità di edit:
REQ_INS_MODE
Imposta la modalità inserimento.
REQ_OVL_MODE
Imposta la modalità riscrittura.
REQ_NEW_LINE
Richiesta di a-capo (vedi sotto per chiarimenti).
REQ_INS_CHAR
Inserisce uno spazio nella locazione corrente.
REQ_INS_LINE
Inserisce una riga vuota alla locazione corrente.
REQ_DEL_CHAR
Cancella il carattere sotto il cursore.
REQ_DEL_PREV
Cancella la parola che precede il cursore.
REQ_DEL_LINE
Cancella la riga dove si trova il cursore.
REQ_DEL_WORD
Cancella la parola su cui è il cursore.
REQ_CLR_EOL
Cancella fino alla fine della riga.
REQ_CLR_EOF
Cancella fino alla fine del campo.
REQ_CLEAR_FIELD
Cancella l'intero campo.
Il comportamento della richiesta REQ_NEW_LINE
e REQ_DEL_PREV
è complicato e in parte controllato da un paio di opzioni della form.
Casi speciali si attivano quando il cursore si trova all'inizio di un
campo, o sull'ultima riga di un campo.
Prima consideriamo la REQ_NEW_LINE
:
Il normale comportamento della REQ_NEW_LINE
in modalità
inserimento è di spezzare la riga corrente alla posizione del
cursore, inserendo la parte della riga che si trova a destra
del cursore come una nuova riga sotto la riga corrente e
spostando il cursore all'inizio di questa nuova linea
(si può pensare a questo come all'inserimento del carattere
di newline nel buffer del campo).
Il normale comportamento della REQ_NEW_LINE
nella modalità
di riscrittura è di pulire la riga corrente dalla posizione
del cursore fino alla fine della riga.
Il cursore viene quindi spostato all'inizio della nuova linea.
Comunque, la REQ_NEW_LINE
all'inizio di un campo o sull'ultima
riga di un campo esegue invece la REQ_NEXT_FIELD
.
Se l'opzione O_NL_OVERLOAD
è disabilitata, allora
anche questa speciale azione è disabilitata.
Ora prendiamo in considerazione la REQ_DEL_PREV
:
Il normale comportamento della REQ_DEL_PREV
è di cancellare
il carattere precedente.
Se la modalità di inserimento è attiva, ed il cursore
si trova all'inizio di una riga e il testo di quella riga
può essere contenuto nella riga precedente, piuttosto unisce
il contenuto della riga corrente con la riga precedente
e pulisce la riga corrente (potete pensare a questo come alla
cancellazione del carattere di newline dal buffer del campo).
Comunque, la REQ_DEL_PREV
all'inizio del campo esegue invece la
REQ_PREV_FIELD
. Se l'opzione O_BS_OVERLOAD
è
disabilitata, allora anche questa speciale azione è disabilitata e il
driver dell form ritorna E_REQUEST_DENIED
.
Vedere Opzioni della Form per una discussione su come impostare ed annullare le opzioni di sovrascrittura.
Se il campo è di tipo lista ordinata, ed ha funzioni associate per prelevare il successivo ed il precedente valore dalla lista data, queste sono le richieste che possono fornire il valore nel buffer:
REQ_NEXT_CHOICE
Prende il valore successivo dalla lista e lo pone nel buffer.
REQ_PREV_CHOICE
Prende il valore precedente dalla lista e lo pone nel buffer.
Tra i tipi di campo pre-definiti solo TYPE_ENUM
è fornito
di funzioni pre-definite di recupero precedente e succssivo.
Quando definite un vostro proprio tipo di campo
(see
Tipi di Validazione Aggiunti ),
potrete associarvi vostre funzioni di ricerca in sequenza.
Le richieste specifiche alla form sono rappresentate da interi
al di sopra del valore curses
più grande di
KEY_MAX
e minore o uguale a MAX_COMMAND
.
Se la vostra routine di virtualizzazione dell'input ritorna un valore
superiore a MAX_COMMAND
, il driver delle form lo ignorerà.
È possibile impostare agganci a funzioni da eseguire ogniqualvolta il campo corrente o la form cambino. Ecco le funzioni che supportano questo:
typedef void (*HOOK)(); /* puntatore a funzione che ritorna void */
int set_form_init(FORM *form, /* form da modificare */
HOOK hook); /* aggancio all'inizializzazione */
HOOK form_init(FORM *form); /* form da interrogare */
int set_form_term(FORM *form, /* form da modificare */
HOOK hook); /* aggancio alla terminazione */
HOOK form_term(FORM *form); /* form da interrogare */
int set_field_init(FORM *form, /* form da modificare */
HOOK hook); /* aggancio all'inizializzazione */
HOOK field_init(FORM *form); /* form da interrogare */
int set_field_term(FORM *form, /* form da modificare */
HOOK hook); /* aggancio alla terminazione */
HOOK field_term(FORM *form); /* form da interrogare */
Queste funzioni vi permettono di impostare o verificare quattro diversi agganci. In ciascuna delle funzioni che impostano, il secondo argomento deve essere l'indirizzo di una funzione d'aggancio. Queste funzioni differiscono solo per il momento in cui vengono eseguite.
Questo aggancio viene chiamato quando la form viene pubblicata; ed anche ad ogni operazione di cambio pagina.
Questo aggancio viene chiamato quando la form viene pubblicata; ed anche ad ogni cambio di campo.
Questo aggancio viene chiamato proprio dopo la validazione del campo; cioè proprio prima che il campo venga modificato. Viene inoltre chiamato quando la form viene spubblicata.
Questo aggancio viene chiamato quando la form viene spubblicata; ed anche ad ogni operazione di cambio pagina.
La chiamata di questi agganci può essere attivata
set_current_field()
set_form_page()
Vedere Comandi di Cambiamento di Campo per una discussione sugli ultimi due casi.
Potete inizialmente impostare un aggancio per tutti i campi chiamando una di queste funzioni con il primo argomento a NULL.
Per disabilitare ogniuno di questi agganci basta reimpostarlo al valore iniziale, NULL.
Normalmente, la navigazione attraverso la form è guidata dall'utente con richieste di input. Ma alcune volte e utile poter cambiare il punto di editazione e di vista dall'interno della vostra applicazione, o domandare che campo è correntemente attivo. Le seguenti funzioni vi aiutano ad ottenere questo:
int set_current_field(FORM *form, /* form da modificare */
FIELD *field); /* campo su cui spostarsi */
FIELD *current_field(FORM *form); /* form da interrogare */
int field_index(FORM *form, /* form da interrogare */
FIELD *field); /* campo di cui si vuole sapere l'indice */
La funzione field_index()
ritorna l'indice di un dato campo
nel vettore di campi della form data (il vettore passato alla
new_form()
o alla set_form_fields()
).
Il campo corrente iniziale è il primo campo attivo della prima pagina.
La funzioneset_current_field()
lo reimposta.
È anche possibile nuoversi tra le pagine.
int set_form_page(FORM *form, /* form da modificare */
int page); /* pagina a cui andare (0=inizio) */
int form_page(FORM *form); /* ritorna la pagina corrente nella form */
La pagina iniziale di una form appena creata è la 0. La funzione
set_form_page()
la modifica.
Come i campi, le form hanno dei bit opzione di controllo, che possono essere cambiati o ispezionati con queste funzioni:
int set_form_opts(FORM *form, /* form da modificare */
int attr); /* attributo da impostare */
int form_opts_on(FORM *form, /* form da modificare */
int attr); /* attributi da attivare */
int form_opts_off(FORM *form, /* form da modificare */
int attr); /* attributi da disattivare */
int form_opts(FORM *form); /* form da interrogare */
Inizialmente tutte le opzioni sono attiva. Ecco le opzioni disponibili:
Abilita lo speciale comportamento della REQ_NEW_LINE
come
descritto in
Richieste di Editazione .
Il valore di questa opzione è ignorato nei campi dinamici
che non hanno raggiunto la loro dimensione limite; questi
non hanno un'ultima linea, così la circostanza per attivare
la REQ_NEXT_FIELD
non giunge mai.
Abilita lo speciale comportamento della REQ_DEL_PREV
come
descritto in
Richieste di Editazione .
I valori delle opzioni sono maschere di bit e possono venir composti mediante OR logico nel modo noto.
La libreria form
vi dà la possibilità di definire da voi dei
tipi di validazione aggiuntivi.
In più, gli argomenti opzionali aggiuntivi della
set_field_type
vi permettono effettivamente di
parametrizzare i tipi di validazione.
La maggior parte delle complicazioni nell'interfaccia
dei tipi di validazione ha a che fare con la gestione
degli argomenti addizionali nelle funzioni di validazione aggiunte.
Il modo più semplice per creare un tipo di dato aggiuntivo è di comporlo unendone due già esistenti:
FIELD *link_fieldtype(FIELDTYPE *type1,
FIELDTYPE *type2);
Questa funzione crea un tipo di campo che accetterà ogni valore valido in uno dei due tipi di campo in argomento (che possono essere sia pre-esistenti o definiti da programma).
Se una chiamata a set_field_type()
successivamente richiede
argomenti, il numvo tipo composito si aspetta tutti gli argomenti
per il primo tipo, quindi tutti gli argomenti per il secondo.
Funzioni di ordinamento (vedere
Richieste di Ordinamento )
in associazione con i tipi compositi lavorano in modo composito:
ciò che fanno è di controllare la funzione di validazione per il
primo tipo, poi per il secondo, per immaginarsi in base a quale
tipo i contenuti del buffer andrebbero elaborati.
Per creare dal nulla un tipo di campo, dovete costruire una o entrambe le seguenti:
Ecco come potete farlo:
typedef int (*HOOK)(); /* puntatore a funzione che ritorna un int */
FIELDTYPE *new_fieldtype(HOOK f_validate, /* validatore di campo */
HOOK c_validate) /* validatore di carattere */
int free_fieldtype(FIELDTYPE *ftype); /* tipo da rilasciare */
Almeno uno dei due argomenti della new_fieldtype()
deve
essere non NULL.
Il driver delle form chiamerà automaticamente le funzioni
di validazione del nuovo tipo dai punti giusti nell'elaborazione
di un campo del nuovo tipo.
La funzione free_fieldtype()
de-alloca il tipo di campo in
argomento, rilasciando tutta la memoria ad esso associata.
Di norma, un validatore di campo viene chiamato quando l'utente cerca di lasciare il campo. Il suo primo argomento è un puntatore a campo, da cui ottenere il buffer zero e controllarlo. Se la funzione ritorna TRUE, l'operazione ha successo; se ritorna FALSE, il cursore di editazione rimane fermo sul campo.
Un validatore di caratteri preleva il carattere dal suo primo argomento. Dovrebbe ritornare TRUE se il carattere è accettabile, altrimenti FALSE.
Alle vostre funzioni di validazione di campo e di carattere
viene comunque passato un secondo argomento.
Si tratta dell'indirizzo di una struttura (che chiameremo
mucchio) costruita con ciascuno degli argomenti specifici
al tipo di campo passati alla set_field_type()
.
Se non ci sono di questi argomenti definiti per il tipo di
campo, il puntatore al mucchio sarà NULL.
Allo scopo di far sì che tali argomenti vengano passati alle vostre
funzioni di validazione, dovrete associare al tipo un piccolo insieme
di funzioni di gestione della memoria.
Il driver delle form le utilizzerà per accumulare un mucchio dagli
argomenti agganciati a ciascun argomento della set_field_type()
,
e un puntatore al mucchio verrà passato alle funzioni di validazione.
Ecco come creare l'associazione:
typedef char *(*PTRHOOK)(); /* puntatore a una funzione che ritorna (char *) */
typedef void (*VOIDHOOK)(); /* puntatore a una funzione che ritorna void */
int set_fieldtype_arg(FIELDTYPE *type, /* tipo da modificare */
PTRHOOK make_str, /* crea la struttura dagli argomenti */
PTRHOOK copy_str, /* crea una copia della struttura */
VOIDHOOK free_str); /* rilascia la memoria della struttura */
Ecco come vengono usate le funzioni aggancio per la gestione della memoria:
make_str
Questa funzione viene chiamata da set_field_type()
.
Vuole un argomento, una lista variabile (va_list
) degli
argomenti specifici al tipo, passati alla set_field_type()
.
Ci si aspetta che ritorni un puntatore ad un mucchio,
cioè alla struttura di dati che incapsula questi argomenti.
copy_str
Questa funzione viene chiamata dalle funzioni della libreria form che allocano nuove istanze del campo. Prende un puntatore ad un mucchio, copia il mucchio nella memoria allocata e ritorna l'indirizzo della copia.
free_str
Questa funzione viene chiamata dalle routine di de-allocazione dei campi e dei tipi contenute nella libreria. Vuole un argomento puntatore a mucchio, e ci si aspetta che ne rilasci la memoria.
Le funzioni make_str
e copy_str
possono ritornare NULL
per indicare fallimenti nell'allocazione di memoria.
Le routine di libreria che le chiamano ritorneranno
un'indicazione di errore quando questo accade.
Perciò le vostre funzioni di validazione non riceveranno mai un NULL in
un puntatore a mucchio, e quindi non hanno bisogno di gestirlo.
Alcuni tipi di campo aggiunti sono ordinati semplicamente nello stesso e
ben definito modo che è TYPE_ENUM
. Per tali tipi, è possibile
definire funzioni successore e predecessore per supportare le richieste
REQ_NEXT_CHOICE
e REQ_PREV_CHOICE
. Ecco come:
typedef int (*INTHOOK)(); /* pointer to function returning int */
int set_fieldtype_arg(FIELDTYPE *type, /* tipo da modificare */
INTHOOK succ, /* funzione che dà il successore */
INTHOOK pred); /* funzione che dà il precessore */
Le funzioni argomento precessore e successore riceveranno ciascuna
due argomenti; un puntatore a campo e un puntatore a mucchio
(come le funzioni di validazione).
Ci si aspetta che queste usino la funzione field_buffer()
per
leggere il valore corrente, e la set_field_buffer()
sul buffer
zero per impostare il precedente o successivo valore.
Ogni funzione aggancio può ritornare TRUE per indicare successo
(l'impostazione di un valido valore precedente o successivo) oppure
FALSE per indicare fallimento.
L'interfaccia per definire tipi aggiunti è complicata e rischiosa. Piuttosto che buttarvi a creare un tipo aggiunto da zero, dovreste incominciare studiandovi il codice sorgente della libreria per qualunque siano i tipi predefiniti che sembrino più vicini ai vostri bisogni.
Usate quel codice come modello ed evolvetelo verso ciò che
volete realmente.
In questo modo vi eviterete parecchi problemi e perdite di tempo.
Il codice sorgente della libreria ncurses
non è coperto da
copyright proprio per permettere questo uso.
Se il vostro tipo aggiunto definisce funzioni di ordinamento, cercate di trattare in modo intuitivo il campo vuoto. Una convenzione utile dice che il successore di un campo vuoto è il valore più piccolo, ed il suo predecessore quello più grande.