








MGUI

Guida introduttiva alla programmazione

INTRODUZIONE

MGUI (Morello Graphic User Interface) e' una interfaccia grafica evoluta 
attualmente disponibile sulle piattaforme DOS (16 e 32 bit), Windows Win32 
e Unix X-Window. Caratteristiche salienti della API della libreria sono:

- Totale compatibilita' a livello di codice sorgente tra tutte le 
piattaforme supportate.
- Look & feel identico su tutte le piattaforme (ispirato a OSF/Motif)
- Compattezza del codice sorgente necessario per creare l'interfaccia.
- Disponibilita' degli oggetti tipici di una GUI (pulsanti, campi 
editabili, liste, menu' etc...).
- Semplicita' nella costruzione del layout grazie alla presenza di un 
oggetto manager che si occupa di posizionare e ridimensionare 
automaticamente gli oggetti suoi figli.
- Supporto alle schede SVGA a 16 o 256 colori sotto DOS (fino a True Color 
sotto Windows e Unix).
- Disponibilita' di un generatore di codice (MGUI Designer),  che consente 
di creare e manutenere in modo semplice i layout delle finestre 
dell'applicazione.
- Supporto dei formati BMP, GIF, TIFF e JPEG per le immagini.
- Log (con sintassi printf) su file e finestra con filtro di tracciamento 
modificabile anche run-time.

Per le sue caratteristiche MGUI e' utile a chi vuole realizzare 
applicazioni per piu' piattaforme, senza costi aggiuntivi in termini di 
sviluppo per il porting dell'interfaccia utente.

MGUI e' nata inizialmente sotto DOS (Borland BGI) per sopperire alla 
mancanza in questo ambiente di una GUI evoluta e semplice da usare. 
L'impostazione data all'architettura della libreria e' stata condizionata 
dalle esperienze acquisite precedentemente in ambiente Unix X-Window, cio' 
ha reso immediato il porting sotto quest'ultimo ambiente. Successivamente 
e' stato preso in considerazione l'ambiente DOS a 32 bit DJGPP, che oltre 
a supportare l'uso della memoria virtuale e' dotato di un'interfaccia alla 
scheda video molto piu' veloce di quanto non sia l'interfaccia Borland BGI 
per DOS a 16 bit.
Infine, dopo l'acquisizione del minimo indispensabile sulla API di 
Windows, e' stato eseguito il porting su quest'ultimo ambiente. Le 
caratteristiche di Windows hanno richiesto delle modifiche alla API di 
MGUI (ad esempio non e' stato possibile mantenere l'uso della funzione 
main() per l'inizio del programma, in quanto un programma Windows inizia 
dalla funzione WinMain()).
Si ringraziano gli amici Paolo (primo utente in assoluto della libreria) e 
Sergio (critico spietatissimo) per il loro contributo nel testing e per i 
loro suggerimenti.


ARCHITETTURA

MGUI e' scritta in linguaggio ANSI C e consta attualmente di circa 28.000 
linee di codice sorgente (commenti esclusi). Di queste, circa 23.000 sono 
comuni a tutte le piattaforme supportate, mentre una percentuale che varia 
dal 15% al 18% costituisce la parte strettamente legata all'ambiente 
operativo: DOS 16bit, DOS 32bit, Windows, X-Window.
L'architettura della GUI e' impostata in modo da avere localizzate in 
pochi moduli le funzioni di basso livello dipendenti dal sistema. 
Appoggiandosi sulla API di questi moduli, l'implementazione del codice 
relativo ai singoli oggetti della libreria e' continuamente modificata e 
arricchita, ereditando automaticamente le modifiche su tutte le 
piattaforme.
Del sistema grafico di ogni piattaforma e' utilizzata solo la API per le 
primitive di disegno, la gestione delle finestre TopLevel, la notifica 
degli eventi di input e i timeout. Per questo motivo in ambiente Unix e' 
richiesta esclusivamente la Xlib.
La libreria si puo' suddividere in quattro blocchi fondamentali:

1) Gestione degli eventi di sistema	(GES)
2) Gestione delle richieste grafiche	(GRG)
3) Gestione delle finestre TopLevel	(GFT)
4) Oggetti MGUI

Nel primo blocco sono trattati gli eventi generati dal sistema:

- pressione tasto mouse
- rilascio tasto mouse
- movimento del mouse
- pressione tasto dalla tastiera
- timeout
- spostamento finestre		(non generato in DOS)
- ridimensionamento finestre	(non generato in DOS)

All'occorrenza di uno dei succitati eventi, il modulo GES genera un evento 
e lo notifica al blocco di gestione delle finestre.
A differenza di quanto accade nelle versioni Windows e X-Window, 
l'implementazione DOS gestisce il mouse con la tecnica del polling. Per 
tale motivo non si ha l'accodamento degli eventi di mouse, e inoltre 
quest'ultimo risulta visualizzato solo mentre l'applicativo e' in attesa 
di eventi dal modulo GES.

Il modulo GFT ha il compito di gestire le finestre TopLevel 
dell'applicativo per le operazioni di apertura e chiusura e per lo 
smistamento degli eventi ottenuti dal GES. L'evento viene notificato 
all'Oggetto MGUI 'SHELL' legato alla finestra, e da quest'ultimo, seguendo 
la via gerarchica del layout, perviene all'Oggetto finale.
Nell'implementazione DOS il GFT si occupa anche della gestione dello 
spostamento e dell'attivazione delle finestre, data l'assenza di un modulo 
window manager nel sistema.

Nel blocco di gestione delle richieste grafiche sono implementate le 
funzioni che realizzano l'interfaccia per la gestione dell'output su 
video. In particolare e' fornita la API per la gestione dei colori, per la 
gestione dei font e per le primitive grafiche di disegno.

Il blocco degli Oggetti MGUI e' il piu' voluminoso (circa l'82% di tutta 
la libreria) e contiene i moduli che realizzano la API con tutti gli 
Oggetti (pulsanti, campi editabili, liste, menu' etc...).
Nel seguito di questo manuale sono descritti in dettaglio tutti gli 
Oggetti della libreria nonche' le tecniche di programmazione per un 
applicativo MGUI. Per una descrizione dettagliata delle funzioni della 
libreria si rimanda al manuale di riferimento.
Rimane implicito che qualunque esempio menzionato in questo manuale puo' 
essere compilato ed eseguito, senza particolarizzazione alcuna nel 
sorgente, in una qualunque delle piattaforme supportate.


LA GESTIONE DEI COLORI

La gestione del colore riflette lo spirito con cui e' stata concepita la 
stessa libreria MGUI: semplice, compatta (quindi anche ridotta) e 
soprattutto identica su tutte le piattaforme ospiti (DOS, Windows e X-
Window).
L'applicativo puo' trovarsi ad operare in due diverse modalita' di colore: 
'palette' o 'statica', a seconda della disponibilita' o meno di una 
palette di colori modificabile.
La modalita' palette e' disponibile normalmente quando il numero di colori 
contemporaneamente visualizzabili e' ridotto (tipicamente 16 o 256), ma il 
sistema grafico offre una piu' ampia gamma di colori selezionabili (es. 
256 K nella VGA). In questo caso il codice che nella memoria video 
corrisponde ad ogni pixel e' utilizzato dalla scheda come indice di una 
entry della palette in cui sono definite le componenti R-G-B del colore 
corrispondente. Si ha cosi' il vantaggio di utilizzare un ridotto numero 
di bit (o piani) per l'identificazione del colore (4 e 8 rispettivamente a 
16 e 256 colori). Diventa inoltre possibile realizzare efficienti 
animazioni di colore modificando semplicemente le entry della palette 
senza bisogno di alterare la memoria video.
Nella modalita' statica il sistema grafico lavora con una scala di colori 
definita e non modificabile. Ovviamente in questo caso, per avere un buon 
effetto nella rappresentazione 3-D degli Oggetti, e' necessario disporre 
di almeno 256 colori. Se il numero di colori e' maggiore (nelle modalita' 
a 15, 16 o 24 piani) si ha, da un lato una migliore definizione delle 
immagini (qualita' fotografica a 24 piani), dall'altro un abbassamento 
delle prestazioni della scheda video in quanto per codificare un pixel 
sono necessari 2 o 3 byte.
Attualmente il supporto alle risoluzioni di colori in ambiente DOS si 
limita alla modalita' palette a 16 e 256 colori. Mentre in ambiente 
Windows e Unix la libreria utilizza appieno la risoluzione di colori (fino 
alla modalita' 24 piani).
Se un applicativo non necessita della funzione di animazione della 
palette, non desidera cioe' riservare un identificatore di colore per 
modificarne a piacere i contenuti R-G-B, la API per la gestione dei colori 
e' indipendente dal numero di colori disponibili e dalla modalita' 
(statica o palette).
Qualora l'applicativo desideri informazioni dettagliate sulla risoluzione 
di colori alla quale si trova ad operare: la funzione MPaletteMode() 
restituisce 'True' se e' attiva la modalita' palette, la funzione 
MGetNColors() ritorna il numero totale di colori e la funzione 
MGetNPlanes() ritorna il numero di piani.
Per potere utilizzare un colore occorre richiederne la 'allocazione' alla 
libreria. Esistono diversi tipi di funzione che effettuano l'allocazione, 
tutte ritornano un identificatore che va utilizzato come parametro in 
tutte le funzioni che richiedono la specifica di un colore. La funzione 
MAllocColor() richiede come parametri i tre valori R-G-B (valori da 0 a 
255) del colore desiderato. Una volta che il colore non serve piu', deve 
essere 'deallocato' attraverso la funzione MFreeColor().


Modalita' Palette

Nella modalita' palette la libreria tiene in memoria una copia della 
palette video e un contatore e una flag di stato per ogni entry (o cella). 
In questo caso l'identificatore del colore coincide con l'indice di una 
cella. Inizialmente il valore di ogni contatore e' posto uguale a zero e 
lo stato della cella a 'libera'.
Quando viene richiesta l'allocazione di un colore possono verificarsi tre 
casi, di seguito descritti.
Se non esiste una cella con i valori R-G-B desiderati ma esiste almeno una 
cella libera, tale cella viene marcata come 'usata', il contatore viene 
posto ad uno e viene ritornato l'indice della cella come identificatore.
Se il colore era stato precedentemente allocato, la libreria ritorna 
l'indice della palette in cui e' definito il colore richiesto, dopo che il 
contatore corrispondente e' stato incrementato. 
Se non ci sono entry libere nella palette e nessuna delle celle usate 
contiene le percentuali R-G-B richieste, viene valutato quale sia la cella 
usata cui corrisponde il colore che meno si discosta da quello richiesto, 
viene incrementato il contatore corrispondente e viene ritornato come 
identificatore l'indice stesso.
Quando un colore viene deallocato, il contatore del corrispondente indice 
della palette viene decrementato e, se raggiunge il valore zero, allora la 
entry della palette viene marcata come libera diventando cosi' disponibile 
per la allocazione di un nuovo colore.
Questo tipo di gestione rende possibile uno sfruttamento ottimale della 
palette, purche' il codice applicativo si occupi della corretta 
deallocazione dei colori non piu' utilizzati.
L'effetto di animazione della palette si ottiene riservando una o piu' 
celle allo scopo, in questo caso si parla di allocazione di cella 
piuttosto che di colore.
La funzione MAllocCell() effettua l'allocazione della cella, la funzione 
MCellSetColor() modifica il colore definito nella cella, infine la 
funzione MFreeCell() libera la cella.
Quando una cella libera viene allocata, il corrispondente stato viene 
posto a 'riservata' e non e' quindi utilizzabile per successive 
allocazioni di colore.
Nel caso in cui non siano disponibili celle libere, la funzione 
MAllocCell() ritorna l'identificatore del colore nero (ottenibile 
attraverso la funzione MBlackColor()). Ovviamente in questo caso 
falliranno sia la funzione MCellSetColor() che la MFreeCell().


LA GESTIONE DEI FONT

La libreria rende disponibili su tutte le piattaforme otto font statici 
che vengono referenziati attraverso macro. Quattro di questi sono a pitch 
fisso: DEFAULT_FONT, SMALL_FONT, MEDIUM_FONT e LARGE_FONT. I rimanenti 
sono font proporzionali di tipo Times ed Helvetica: TIMES_L_FONT, 
TIMES_N_FONT, HELV_L_FONT e HELV_N_FONT.
Tranne che in ambiente DOS Borland BGI, e' possibile allocare ulteriori 
font attraverso la funzione MLoadFont(). I parametri della funzione 
specificano famiglia, altezza, flag di grassetto e flag di corsivo. La 
famiglia e' una stringa (case unsensitive) che puo' assumere i valori: 
"Times", "Helvetica", "Courier" e "Symbol". L'altezza rappresenta il 
valore desiderato in pixel del font. Il valore effettivo dell'altezza del 
font ritornato puo' risultare minore di quello richiesto se il sistema non 
usa font True Type.
E' possibile allocare fino a 24 nuovi font. Ogni richiesta di allocazione 
ottiene sempre un identificatore diverso (anche utilizzando uguali valori 
per i parametri) per cui e' opportuno, quando il font non e' piu' 
utilizzato, rilasciarlo con la funzione MUnloadFont().
Quando la funzione MLoadFont() fallisce per un qualunque motivo, 
restituisce il valore statico DEFAULT_FONT. Cio' offre all'applicativo la 
possibilita' di continuare senza dovere testare il valore ottenuto perche' 
esso e' sempre valido.


LA PROPAGAZIONE DEGLI EVENTI

Gli eventi sono dei messaggi che hanno come destinatario un Oggetto MGUI 
oppure una callback fornita dall'applicativo per gestire un timeout.
Per callback si intende una funzione, definita dall'applicativo, che puo' 
essere assegnata come azione da effettuare al verificarsi di un 
particolare evento generato internamente dalla libreria. In tutte le 
chiamate a funzione che predispongono una data callback, il puntatore alla 
funzione che realizza la callback stessa e' sempre seguito da un puntatore 
'void'. Tale puntatore sara' fornito alla callback come ultimo parametro 
quando viene chiamata.
Gli eventi destinati agli Oggetti possono essere di input (mouse e 
tastiera), di gestione finestre (expose, resize e move) oppure generati da 
funzioni di libreria (set text, set color, set shadow ...).
In ambienti come Windows e X-Window, gli eventi generati dal sistema (di 
input e di gestione finestre) sono associati ad una finestra. Il sistema 
ha modo di indirizzare l'evento direttamente ad un Oggetto in quanto ogni 
Oggetto possiede una propria finestra, normalmente figlia di altre 
finestre.
Una finestra MGUI, quantunque complessa sia la gerarchia di Oggetti 
contenuti, e' costituita da una sola finestra di sistema. Ad essa e' 
associato l'Oggetto 'SHELL' che e' il progenitore di tutti gli Oggetti 
contenuti nella finestra stessa.
Ogni evento di sistema viene tramandato di padre in figlio, partendo dalla 
'SHELL', fino all'Oggetto destinatario.
Gli eventi di mouse sono normalmente indirizzati all'Oggetto che sta 
immediatamente sotto il puntatore. Un Oggetto ha tuttavia la possibilita' 
di 'monopolizzare' gli eventi di mouse, in modo che, una volta ricevuto 
l'evento di pressione pulsante mouse, esso possa gestire il movimento del 
puntatore e il rilascio del pulsante anche se si verificano su altri 
Oggetti. E' da tenere presente che la generazione dell'evento 'movimento 
del mouse' si ha solo quando il mouse e' monopolizzato.
Quando il mouse e' monopolizzato, il modulo GFT indirizza l'evento di 
mouse direttamente all'Oggetto interessato, bypassando la sua SHELL.
L'Oggetto candidato alla gestione degli eventi di tastiera si dice che 
possiede l'input focus. Nella gerarchia di Oggetti MGUI, se un Oggetto 
possiede l'input focus, anche il suo parent lo possiede. In questo modo e' 
possibile tramandare l'evento di tastiera, partendo al solito dalla SHELL, 
di padre in figlio, fino all'Oggetto terminale. Lo spostamento dell'input 
focus da un Oggetto a un altro avviene per selezione esplicita con il 
mouse, per effetto della funzione MSelectObject() oppure  per cessione 
spontanea dell'Oggetto alla pressione dei tasti frecce, Tab e Shift+Tab. 
In particolare le frecce in su e a sinistra causano la cessione dell'input 
focus al fratello immediatamente precedente, le frecce in giu' e a destra 
causano la cessione dell'input focus al fratello immediatamente seguente, 
il tasto Tab al cugino successivo, la sequenza Shift+Tab al cugino 
precedente.


HELLO WORLD

In questo paragrafo viene mostrato il codice sorgente necessario per 
realizzare l'equivalente MGUI di 'Hello, World' (primo esempio nel manuale 
C di Kernighan & Ritchie).

/*
 * esempio 1
 */
#include "mgui.h"

void MGUIMain(int argc, char **argv)
{
	MMessageDialog("Esempio 1", "Hello, world", " Ok ", NULL);
}

L'esempio sopra riportato apre una finestra intitolata "Esempio 1" che 
presenta il testo "Hello, world" e il pulsante " Ok ". Il programma 
termina quando viene pressato il tasto 'Enter' oppure pressato e 
rilasciato il tasto sinistro del mouse all'interno del pulsante 'Ok'.
Per poter usare la libreria e' necessario includere il file 'mgui.h' che 
contiene i prototipi della API e le definizioni necessarie 
all'applicativo. Esso include a sua volta il file 'mguidefs.h' che include 
'mevent.h' e 'keycodes.h'.
Come si nota, il programma non inizia con la consueta funzione main() ma 
con MGUIMain(). Questo vincolo deriva dal fatto che in Windows il 
programma utente inizia dalla funzione WinMain(). Tuttavia, a differenza 
della WinMain() imposta da Windows, MGUIMain() inizia fornendo 
all'applicativo gli stessi parametri forniti dalla vecchia main() del C 
standard.
Questo esempio usa solo la funzione MMessageDialog() che fa parte delle 
funzioni di utilita' a disposizione. Il primo parametro e' una stringa che 
rappresenta il titolo che viene dato alla finestra. Il secondo rappresenta 
il testo del messaggio, che viene spezzato su piu' righe in corrispondenza 
ad eventuali caratteri '\n' che dovessero trovarsi nella stringa. Il terzo 
e' anch'esso una stringa e serve a specificare il testo del primo 
pulsante. Segue quindi una lista di stringhe, terminate dal puntatore 
NULL, che specifica gli eventuali pulsanti aggiuntivi da presentare. La 
funzione ritorna un intero che dice quale dei pulsanti e' stato 
selezionato e vale 0 per il primo, 1 per il secondo e cosi' via.
Questo esempio da un'idea, soprattutto a chi ha gia' esperienza di 
programmazione in ambiente Windows o Motif, della compattezza e della 
semplicita' d'uso di MGUI.


GLI OGGETTI MGUI

Gli Oggetti MGUI si dividono in quattro categorie:

- Capostipite
- Manager
- Terminale principale
- Terminale composto

L'Oggetto Capostipite e' la SHELL. Essa riceve dal modulo Gestore delle 
finestre TopLevel tutti gli eventi di sistema (input tastiera e mouse, 
resize, expose e move) che occorrono nella sua finestra e li redirige, 
quando necessario, agli Oggetti suoi discendenti.
La SHELL e' l'unico Oggetto a non avere 'parent' in quanto capostipite e 
possiede un solo Oggetto figlio. Questi appartiene alla categoria Manager 
e viene creato assieme all'Oggetto SHELL (suo parent).
L'Oggetto Manager e' la FORM. E' l'unico tipo di Oggetto (a parte la 
SHELL) che puo' contenere degli Oggetti figli (eventualmente anche altre 
FORM). Gioca un ruolo determinante nell'architettura della libreria in 
quanto si occupa di posizionare al suo interno ed eventualmente 
ridimensionare i suoi figli, nonche' di dispacciare al figlio opportuno 
gli eventi che riceve dall'Oggetto parent (SHELL o altra FORM).
La FORM possiede delle varianti che sono la FORM a pagine, la scrolled 
FORM e la clipped FORM. La prima consente di associare ad una FORM piu' 
serie di figli visualizzate una alla volta sulla stessa area. La seconda 
permette la visualizzazione di un'area ridotta rispetto alla effettiva 
area richiesta dalla FORM per presentare tutti i suoi figli. Attraverso 
delle barre di scorrimento (realizzate con Oggetti di tipo SCROLLBAR) si 
puo' cambiare la porzione di area visibile. La clipped FORM e' come una 
scrolled FORM con la differenza che non rende disponibili le SCROLLBAR per 
cambiare l'area visibile. Per modificare l'area visualizzata nella clipped 
FORM e' necessario agire attraverso le chiamate a funzione preposte.
Gli Oggetti Terminali principali sono tutti i rimanenti tipi di Oggetto 
che non sono composti. A questa categoria appartengono LABEL, EDIT, 
PBUTTON (push-button),  RBUTTON (radio-button) TBUTTON (toggle-button), 
MENU, DRAWAREA, SCROLLBAR.
Gli Oggetti Terminali composti sono degli Oggetti Terminali che hanno 
bisogno, per il loro funzionamento, della collaborazione di altri Oggetti 
di tipo Terminale principale e di tipo Manager.
Gli Oggetti Terminali composti sono l'option EDIT (simile al COMBOBOX di 
Windows) e la SBLIST con tutte le varianti: Lista Semplice, Lista Multi-
selezione, Lista Virtuale a Blocchi, Lista Virtuale a Salti, Lista File.
La scrolled FORM si puo' definire un Manager composto, in quanto e' 
costituita da una clipped FORM contenuta in un'altra FORM assieme agli 


SCROLLBAR.

Tranne che per l'Oggetto SHELL, le funzioni di creazione di ogni Oggetto 
richiedono come primo parametro l'identificatore dell'Oggetto parent. 
Quest'ultimo deve necessariamente essere una FORM oppure la SHELL; in 
quest'ultimo caso, in realta', l'associazione con l'Oggetto parent viene 
automaticamente rediretta alla FORM figlia della SHELL.
Tutti gli Oggetti, dopo la loro creazione, sono caratterizzati da un 
colore grigio per il background e nero per il foreground. Questi colori 
possono essere modificati in qualunque momento attraverso l'uso della 
funzione MObjectSetColor() oppure delle MObjectSetBackgroundRGB() e 
MObjectSetForegroundRGB().
Altra caratteristica fondamentale degli Oggetti e' costituita 
dall'ombreggiatura (shadow). Lo shadow puo' essere di quattro tipi: 
WS_SHADOW_IN (incassato), WS_SHADOW_OUT (sporgente), WS_ETCHED_IN (con 
bordino incassato) WS_ETCHED_OUT (con bordino sporgente). Ogni Oggetto 
possiede il suo shadow predefinito che lo distingue dagli altri Oggetti. 
E' consentito modificarlo, con la funzione MObjectSetShadow(),  anche se 
ovviamente e' consigliabile non renderlo simile a quello di altri Oggetti 
per mantenere consistente il look. La generazione dell'effetto 3-D delle 
ombreggiature e' a carico degli Oggetti, nel senso che il calcolo e 
l'allocazione dei colori per le sfumature e' effettuato internamente alla 
libreria quando viene creato l'Oggetto e quando gli si cambia il colore.
Per gli Oggetti che contengono testo, l'associazione con il font da 
utilizzare viene fatta all'atto della creazione. Il font associato puo' in 
seguito essere modificato con la funzione MObjectSetFont().

Il flusso tipico di un applicativo MGUI e' di seguito sintetizzato:

- Creazione di un Oggetto SHELL
- Creazione di una serie di Oggetti discendenti della SHELL
- Predisposizione di callback su alcuni degli Oggetti creati
- Realizzazione della finestra dell'Oggetto SHELL
- Blocco del programma nel loop di gestione degli eventi
- Esecuzione delle callback predisposte su richiesta degli Oggetti
- Terminazione per effetto della chiamata a una particolare callback

L'esempio che segue mostra una implementazione del programma dell'esempio 
precedente facendo uso del flusso sopra descritto:

/*
 * esempio 2
 */
#include <stdlib.h>
#include "mgui.h"

void ExitCB(MOBJECT obj, void *a, void *b)
{
	if (MMessageDialog("USCITA",
	  "Sei sicuro di volere\nabbandonare il programma?", 
"Assolutamente", "Rinuncio", NULL) != 0)
		return;
	obj = MObjectShell(obj); 
	MShellUnrealize(obj);
	MShellDestroy(obj);
	MGUITerm();
	exit(0);
}

void MGUIMain(int argc, char **argv)
{
	MOBJECT shell, pushbutton;

	shell = MCreateShell("Esempio 2", 0);
	MCreateLabel(shell, "Hello, world", HELV_L_FONT);
	pushbutton = MCreatePButton(shell, "Ok", TIMES_L_FONT);
	MPButtonSetCallback(pushbutton, ExitCB, NULL);
	MShellRealize(shell);

	MMainLoop();
}

La funzione ExitCB() e' la callback relativa all'attivazione di un 
pulsante. Il suo prototipo e' molto ricorrente nella API in quanto e' 
usato per molti tipi di oggetto. Il primo parametro e' un identificatore 
MOBJECT, definito in 'mguidefs.h', che identifica l'Oggetto MGUI che 
chiama la callback. Il secondo parametro serve all'Oggetto chiamante per 
notificare all'applicazione eventuali dati. L'ultimo e' invece il 
puntatore void che specifica l'applicativo all'atto dell'impostazione 
della callback; torna utile quando si vuole associare la stessa callback a 
piu' Oggetti, usando tale parametro come discriminante, oppure se si vuole 
ricevere un dato noto alla creazione dell'Oggetto.
Nel caso dell'esempio l'unico parametro utilizzato e' l'identificatore 
dell'Oggetto. Da questo si ottiene, attraverso la funzione 
MObjectShell(),l'identificatore dell'Oggetto SHELL progenitore del 
pulsante. Quindi, attraverso la funzione MShellUnrealize(), viene rimossa 
la finestra dallo schermo e, attraverso la funzione MShellDestroy(), viene 
distrutta tutta la gerarchia di Oggetti discendenti della SHELL e la 
finestra stessa. Con la chiamata alla funzione MGUITerm() l'applicativo 
libera tutte le risorse di sistema impegnate (colori, font etc...), 
dopodiche' puo' terminare con la chiamata alla exit().
La chiamata alla MGUITerm() prima della terminazione dell'applicazione e' 
particolarmente importante in Windows, infatti questo sistema non libera 
automaticamente le risorse impegnate da una applicazione terminata.
Negli esempi seguenti si fara' riferimento alla funzione ExitCB() come 
metodo di uscita dal programma, senza che il codice relativo sia riportato 
nell'esempio.

Il codice della funzione MGUIMain() di questo esempio crea un Oggetto 
SHELL, una LABEL e un PBUTTON, entrambi figli dell'Oggetto SHELL.
La LABEL e' un Oggetto che visualizza del testo su una o piu' righe, 
centrato sempre rispetto all'altezza dell'Oggetto stesso e senza contorno 
visibile. L'allineamento orizzontale e' centrato come default anche se 
puo' essere modificato con la funzione MLabelSetAlignment(). Il testo da 
visualizzare e il font da utilizzare sono specificati nella funzione di 
creazione (in questo caso "Hello, world"  con font Helvetica-Large).
Anche il PBUTTON (push-button) visualizza un testo su una o piu' righe 
costruito con un dato font; a differenza della LABEL esso possiede un 
contorno che gli conferisce un particolare look 3-D ed e' inoltre 
possibile associare ad esso una azione (callback) che viene invocata 
dall'Oggetto quando viene pressato e rilasciato il tasto sinistro del 
mouse all'interno del PBUTTON. Al push-button dell'esempio viene associato 
il testo "Ok" con il font Times-Large e la callback di terminazione 
ExitCB().
La chiamata alla funzione MMainLoop() pone il programma nel loop 
principale di gestione degli eventi. Questa funzione non ritorna, infatti 
la terminazione del programma avviene all'interno della callback ExitCB().

Il prossimo esempio mostra un metodo alternativo di gestione del loop 
degli eventi.

/*
 * esempio 3
 */
#include "mgui.h"

static int quitting = False;

void QuitCB(MOBJECT obj, void *a, void *shell)
{
	MShellUnrealize((MOBJECT )shell);
	MShellDestroy((MOBJECT )shell);
	quitting = True;
}

void MGUIMain(int argc, char **argv)
{
	MOBJECT shell, pushbutton;

	shell = MCreateShell("Esempio 3", 0);
	MCreateLabel(shell, "Hello, world", HELV_L_FONT);
	pushbutton = MCreatePButton(shell, "Ok", TIMES_L_FONT);
	MPButtonSetCallback(pushbutton, QuitCB, shell);
	MShellRealize(shell);

	MLoopWhileFlag(&quitting, False);
}

Come si puo' subito rilevare, la funzione di uscita QuitCB() non effettua 
le chiamate alla MGUITerm() e alla exit(), ma si limita ad impostare a 
True il valore della variabile globale 'quitting'. Questo fatto viene 
rilevato dalla funzione MLoopWhileFlag(), la quale, riscontrato che il 
valore della variabile 'quitting' non e' piu' False, come richiesto con la 
sua chiamata, ritorna. Cio' fa si che il controllo ritorni alla funzione 
interna che ha chiamato la MGUIMain(), la quale provvede, come nel caso 
dell'esempio 1, a chiamare la MGUITerm() e la exit().
Da notare anche che in questo caso viene fatto uso del terzo parametro 
della callback per notificare direttamente l'identificatore della SHELL. 
Si evita cosi' la necessita' di usare la funzione MObjectShell() come 
nell'esempio 2.
Questa modalita' di gestione del loop di eventi, usata tra l'altro dalla 
funzione MMessageDialog(),  puo' tornare utile quando si vuole 
interrompere temporaneamente il flusso del programma, pur continuando a 
gestire gli eventi e quindi le callback, per poi riprenderlo allo stesso 
punto (dopo la chiamata alla MLoopWhileFlag()).


FORM

Nella forma piu' semplice la FORM si comporta come un contenitore che 
dispone i propri figli uno per riga o uno per colonna, equidistanziati fra 
di loro e dagli stessi contorni della FORM. Le dimensioni naturali della 
FORM sono quelle le che consentono di contenere tutti gli Oggetti figli 
alle loro dimensioni di default. In particolare, la dimensione 
corrispondente alla direzione di inserimento dei figli (altezza nel caso 
di FORM a righe) coincide con la somma delle dimensioni dei figli 
aumentata dei margini di separazione. L'altra dimensione coincide invece 
con la corrispondente dimensione massima di tutti i figli aumentata dei 
margini. 
Per particolari condizioni di aggancio tra gli Oggetti figli o in risposta 
ad un resize imposto da utente, la FORM provvede a forzare le dimensioni 
e/o le posizioni dei figli rispetto al default. La politica di 
posizionamento e dimensionamento dei figli e' condizionata anche dalla 
scalabilita' degli Oggetti figli.
La scalabilita' di default in altezza e in larghezza dipende dal tipo di 
Oggetto. Per esempio, la LABEL puo' essere scalata in larghezza ma non in 
altezza, mentre l'EDIT semplice non e' ridimensionabile in nessuna 
direzione.
L'attributo di scalabilita' puo' essere modificato, rispetto al default, 
usando la funzione MObjectSetResize() che consente di impostare 
separatamente la scalabilita' in altezza e larghezza.
Nella costruzione del layout gli Oggetti vengono inseriti nella FORM uno 
alla volta. L'ultimo Oggetto inserito viene agganciato secondo le seguenti 
regole: in basso e a destra sempre al contenitore; in alto al contenitore 
se quest'ultimo e' una FORM a colonne, all'Oggetto precedente in caso 
contrario; a sinistra al contenitore se questo e' una FORM a righe, 
all'Oggetto precedente in caso contrario.
Attraverso le due funzioni di libreria MObjectSetAttachment() e 
MObjectSetOffset(), e' possibile modificare gli agganci di un Oggetto 
nelle quattro direzioni. Queste funzioni consentono rispettivamente di 
rimuovere uno o piu' agganci e di modificare il margine di separazione tra 
due Oggetti o tra un Oggetto e il contenitore. 
La scalabilita' dell'Oggetto FORM e' legata ai suoi agganci con il 
contenitore e alla scalabilita' ed eventuali mancanze di agganci dei 
propri figli. Piu' precisamente: una FORM e' sempre scalabile in una 
direzione, a meno che: almeno uno dei suoi estremi non sia legato al suo 
contenitore oppure se tutti i figli che si incontrano, procedendo al suo 
interno da un estremo all'altro nella direzione in esame, risultano 
agganciati e NON dimensionabili e contemporaneamente la FORM stessa non e' 
legata in entrambi gli estremi allo stesso contenitore.
Quando interviene il resize di una FORM in una direzione e tutti gli 
Oggetti incontrati da un estremo all'altro sono legati, se almeno uno 
degli Oggetti e' scalabile, la differenza e' ripartita in parti uguali tra 
gli Oggetti scalabili, altrimenti e' ripartita negli offset di aggancio di 
tutti gli Oggetti (si modifica la loro spaziatura).
Da quanto detto risulta chiaro che per evitare il resize o il 
riposizionamento degli Oggetti in una direzione, e' sufficiente rimuovere 
un legame nella stessa direzione tra  un Oggetto e uno adiacente o il 
contenitore.
L'esempio che segue mostra la politica di arrangiamento dei figli da parte 
dell'Oggetto FORM.

/*
 * esempio 4
 */
#include <stdlib.h>
#include "mgui.h"

void MGUIMain(int argc, char **argv)
{
	MOBJECT shell, rform, cform, pb, lbl;

	shell = MCreateShell("Esempio 4", 0);

	rform = MCreateRowForm(shell); 
	lbl = MCreateLabel(rform, "Label che fissa la larghezza", 
DEFAULT_FONT);
	MObjectSetShadow(lbl, WS_SHADOW_IN, 1, 0); 
	lbl = MCreateLabel(rform, "Label piccola", DEFAULT_FONT);
	MObjectSetShadow(lbl, WS_SHADOW_IN, 1, 0); 
	lbl = MCreateLabel(rform, "Label non scalabile", DEFAULT_FONT); 
	MObjectSetOffset(lbl, -1, -1, 15, -1);
	MObjectSetShadow(lbl, WS_SHADOW_IN, 1, 0); 
	MObjectSetResize(lbl, False, False); 

	cform = MCreateColForm(shell); 
	pb = MCreatePButton(cform, "inutile", TIMES_L_FONT); 
	pb = MCreatePButton(cform, "Quit", TIMES_L_FONT);
	MObjectSetAttachment(pb, True, True, False, True);
	MPButtonSetCallback(pb, ExitCB, NULL);
	MShellRealize(shell);

	MMainLoop();
}

Gli Oggetti aventi 'shell' come parent, sono in realta' contenuti nella 
FORM (che e' a righe) figlia della stessa 'shell', pertanto l'Oggetto 
'cform' risulta disposto al di sotto di 'cform'. Le dimensioni della 
finestra coincidono, a parte i margini, con le dimensioni della FORM 
principale. Questa a sua volta, a parte i margini di separazione, ha 
dimensione in larghezza determinata dall'Oggetto figlio avente maggiore 
dimensione orizzontale, e dimensione in altezza determinata dalla somma 
delle altezze dei suoi figli (piu' i margini). 
Il contenitore 'rform' possiede tre figli di tipo LABEL ai quali, per 
renderne evidente la geometria, e' stato assegnato uno shadow visibile: il 
primo e' quello che, avendo maggiore dimensione orizzontale, fissa la 
larghezza della 'rform'; il secondo, pur avendo minore larghezza di 
default, viene forzato ad avere le stesse dimensioni del precedente in 
quanto la LABEL e' ridimensionabile in larghezza; l'ultimo Oggetto viene 
invece spostato rispetto alla posizione di default perche' gli e' stato 
inibito il resize orizzontale.
La FORM 'cform' contiene due pulsanti organizzati per colonne. La 
dimensione naturale di questo contenitore e' inferiore rispetto a quella 
del contenitore 'rform'. Inoltre, sebbene nessuno dei suoi figli risulta 
scalabile in larghezza, essa risulta essere ridimensionabile in quanto i 
suoi estremi destro e sinistro sono legati allo stesso contenitore. La 
FORM principale ne forza pertanto la larghezza rendendola identica a 
quella dell'Oggetto 'rform'. L'allargamento del contenitore si ripercuote 
interamente sul posizionamento relativo dei figli in quanto l'aggancio tra 
i due pulsanti e' stato rimosso.


FORM A PAGINE

La FORM a pagine possiede piu' serie di figli, visualizzate una alla 
volta, per le quali mantiene separate le relazioni di aggancio. Le 
dimensioni della FORM a pagine coincidono con le massime corrispondenti a 
tutte le serie di figli. La selezione della pagina attiva puo' avvenire 
per chiamata a funzione oppure attraverso la pressione dei tasti PagUp, 
PagDown, Home ed End quando l'input focus e' posseduto da un Oggetto che 
ha come progenitore la FORM a pagine. Esiste una versione 'estesa' della 
FORM a pagine che comprende dei pulsanti di selezione pagina e la 
visualizzazione della pagina corrente. Il numero di pagine viene stabilito 
alla creazione, mentre l'associazione di un Oggetto ad una particolare 
pagina va fatta selezionando, prima di creare l'Oggetto, la pagina 
desiderata con la funzione MPagedFormSetActivePage() numerando da zero.


CLIPPED FORM

La clipped FORM possiede larghezza e altezza virtuali, che corrispondono 
alle sue dimensioni naturali, e dimensioni effettive che corrispondono a 
quelle dell'area visualizzata. Le dimensioni virtuali dipendono 
esclusivamente dagli Oggetti contenuti, mentre le dimensioni effettive 
sono stabilite alla creazione. Per rendere virtuale solo una delle 
dimensioni della FORM, basta specificare nella funzione di creazione un 
valore negativo (e quindi non valido) per l'altra dimensione.
La variazione dell'area di clip (area effettiva visualizzata rispetto a 
quella virtuale) e' resa possibile attraverso due chiamate a funzione che 
modificano le coordinate di clip della FORM: MFormSetClipX() e 
MFormSetClipY().


SCROLLED FORM

La scrolled FORM non e' altro che una clipped FORM munita di SCROLLBAR con 
le callback predisposte alla variazione delle coordinate di clip. Di 
fatto, pertanto, la scrolled FORM e' un Oggetto composto dal momento che 
possiede due Oggetti 'fratelli' (gli SCROLLBAR) e un contenitore privato 
(trasparente all'utente) che contiene anche gli SCROLLBAR.
Per rendere virtuale solo una delle dimensioni della FORM si procede 
analogamente alla clipped FORM. In questo caso e' visualizzato il solo 
SCROLLBAR corrispondente alla dimensione virtuale.
Tutte le funzioni di creazione di Oggetti Composti ritornano 
l'identificatore dell'Oggetto Terminale principale. Per mantenere coerente 
la API in operazioni come le variazioni degli agganci, tutti i riferimenti 
ad Oggetti Composti sono automaticamente rediretti all'Oggetto capostipite 
nella gerachia dell'Oggetto Composto.
L'esempio che segue usa una scrolled FORM per incapsulare la stessa 
gerarchia di Oggetti dell'esempio precedente.

/*
 * esempio 5
 */
#include <stdlib.h>
#include "mgui.h"

void MGUIMain(int argc, char **argv)
{
	MOBJECT shell, sform, rform, cform, pb, lbl;

	shell = MCreateShell("Esempio 5", 0);

	sform = MCreateScrolledForm(shell, 80, 60);
 
	rform = MCreateRowForm(sform);
	lbl = MCreateLabel(rform, "Label che fissa la larghezza", 
DEFAULT_FONT);
	MObjectSetShadow(lbl, WS_SHADOW_IN, 1, 0); 
	lbl = MCreateLabel(rform, "Label piccola", DEFAULT_FONT);
	MObjectSetShadow(lbl, WS_SHADOW_IN, 1, 0); 
	lbl = MCreateLabel(rform, "Label non scalabile", DEFAULT_FONT); 
	MObjectSetOffset(lbl, -1, -1, 15, -1);
	MObjectSetShadow(lbl, WS_SHADOW_IN, 1, 0); 
	MObjectSetResize(lbl, False, False); 

	cform = MCreateColForm(sform); 
	pb = MCreatePButton(cform, "inutile", TIMES_L_FONT); 
	pb = MCreatePButton(cform, "Quit", TIMES_L_FONT);
	MObjectSetAttachment(pb, True, True, False, True);
	MPButtonSetCallback(pb, ExitCB, NULL);

	MShellRealize(shell);

	MMainLoop();
}

L'Oggetto 'sform' e' una scrolled form figlia di 'shell' avente dimensioni 
visibili 80 per 60 pixel. Avendo un valore posistivo entrambe le 
dimensioni, sono presenti sia lo scrollbar orizzontale che quello 
verticale.
Provando tale esempio in ambiente Windows o X-Window e' utile osservare 
l'adeguamento degli SCROLLBAR in risposta ad un resize della finestra.


RBUTTON

Il radio-button e' un pulsante che consente di effettuare selezioni 
esclusive in un gruppo di Oggetti dello stesso tipo. Il gruppo viene 
costituito semplicemente creando i radio-button figli della stessa FORM 
(non e' necessario che i figli siano tutti dei radio-button). La selezione 
di un qualunque pulsante comporta la deselezione del pulsante attivato 
precedentemente e lo scattare della callback di attivazione per i due 
pulsanti che hanno cambiato stato. Il prototipo della callback differisce 
da quello della funzione ExitCB() dell'esempio 2 per il tipo del secondo 
parametro. In questo caso e' un valore booleano che segnala lo stato di 
attivazione del radio-button che invoca la callback.
Dopo la creazione l'Oggetto si trova sempre nello stato disattivato. Per 
attivare la selezione iniziale nel gruppo o per modificarla da programma, 
si puo' ricorrere alla funzione MRButtonActivate().
Caratteristica particolare del radio-button e' il fatto che l'Oggetto, 
quando viene creato, assume sempre il colore del contenitore. E' possibile 
ovviamente modificare il colore successivamente.

/*
 * esempio 6
 */
#include <stdio>
#include <stdlib.h>
#include "mgui.h"

MOBJECT label;

void RadioButtonCB(MOBJECT rb, MVALUE status, void *ii)
{
	char str[64];

	if (status) {
		sprintf(str, "Selezionato il pulsante %d", (int)ii);
		MObjectSetText(label, str);
	}
}

void MGUIMain(int argc, char **argv)
{
	int ii;
	char str[16];
	MOBJECT shell, rform, pb, rb;

	shell = MCreateShell("Esempio 6", 0);

	rform = MCreateRowForm(shell);
	for (ii=0; ii < 4; ii++) {
		sprintf(str, "Radio %d", ii);
		rb = MCreateRButton(rform, str, HELV_L_FONT);
		MRButtonSetCallback(rb, RadioButtonCB, (void *)ii);
	}
	label = MCreateLabel(rform, "", DEFAULT_FONT);
	MRButtonActivate(rb);

	pb = MCreatePButton(shell, "Quit", TIMES_L_FONT);
	MPButtonSetCallback(pb, ExitCB, NULL);

	MShellRealize(shell);

	MMainLoop();
}

La callback RadioButtonCB() imposta il testo della LABEL 'label' 
evidenziando quale dei quattro RBUTTON e' attivato. Il legame tra pulsante 
attivato e testo impostato dalla callback e' stabilito attraverso l'indice 
'ii', fornito come dato all'associazione della callback ad ogni RBUTTON. 
Il prototipo di questa callback differisce per il tipo del secondo 
parametro che e' un intero da 32 bit. Attraverso il valore del parametro 
'status' l'Oggetto segnala il suo stato di attivazione on/off.
Da notare che, essendo la chiamata alla funzione MRButtonActivate() 
successiva all'impostazione della callback, il testo della label viene 
assegnato correttamente prima ancora che la SHELL sia realizzata.


TBUTTON

Il toggle-button e' un pulsante di tipo on/off che consente l'attivazione 
di opzioni. A differenza del radio-button, la selezione non e' esclusiva e 
non e' neanche necessario che l'Oggetto faccia parte di un gruppo. Alla 
creazione assume il colore del contenitore e la callback di attivazione 
viene invocata allo stesso modo del radio-button. L'impostazione dello 
stato di attivazione da programma avviene attraverso le funzioni 
MTButtonActivate() e MTButtonDeactivate().

/*
 * esempio 7
 */
#include <stdio>
#include <stdlib.h>
#include <string.h>
#include "mgui.h"

#define N_TOGGLE_BUTTON	4

MOBJECT label;
int toggle_status[N_TOGGLE_BUTTON];

void ToggleButtonCB(MOBJECT rb, MVALUE status, void *ii)
{
	char str[128];
	int i;

	toggle_status[(int)ii] = (int)status;
	strcpy(str, "Stato dei pulsanti: ");
	for (i=0; i < N_TOGGLE_BUTTON; i++)
		strcat(str, (toggle_status[i] ? "  ON" : " OFF"));
	MObjectSetText(label, str);
}

void MGUIMain(int argc, char **argv)
{
	int ii;
	char str[64];
	MOBJECT shell, rform, pb, tb;

	shell = MCreateShell("Esempio 7", 0);

	rform = MCreateRowForm(shell);
	for (ii=0; ii < N_TOGGLE_BUTTON; ii++) {
		sprintf(str, "Toggle %d", ii);
		tb = MCreateTButton(rform, str, HELV_L_FONT);
		MTButtonSetCallback(tb, ToggleButtonCB, (void *)ii);
	}
	label = MCreateLabel(rform, "", DEFAULT_FONT); 
	MTButtonActivate(tb);


	pb = MCreatePButton(shell, "Quit", TIMES_L_FONT);
	MPButtonSetCallback(pb, ExitCB, NULL);

	MShellRealize(shell);

	MMainLoop();
}

Il prototipo della callback per il TBUTTON e' identico a quello del 
RBUTTON.
L'attivazione dell'ultimo Oggetto e' effettuata solamente allo scopo di 
far partire la finestra alle dimensioni corrette, visto che la dimensione 
orizzontale e' stabilita dalla label. Le dimensioni di default degli 
Oggetti, infatti, sono valutate quando viene realizzata la SHELL.


EDIT

E' l'Oggetto che consente all'utente di comunicare del testo 
all'applicativo. E' disponibile nella modalita' singola linea o multi 
linea. Nel primo caso, il numero di caratteri che' e' possibile inserire 
e' fissato, nel secondo e' invece limitato dal valore MAXINT.
E' dotato di filtro e di ben cinque callback che consentono di effettuare 
un accurato controllo dell'inserimento. Una prima callback puo' essere 
predisposta per intercettare la pressione del tasto Enter sull'EDIT (solo 
EDIT singola linea); una seconda per potere validare il contenuto quando 
l'Oggetto sta per cedere l'input focus; una terza per intercettare ed 
eventualmente rigettare le modifica sul contenuto; una quarta per avere 
notifica ogni volta che il contenuto dell'EDIT cambia. L'ultima callback 
notifica all'applicativo la variazione di posizione del cursore 
all'interno del testo. Lo stato di validazione puo' opzionalmente essere 
evidenziato predisponendo un diverso colore.
I tasti freccia sono utilizzati per lo spostamento del cursore all'interno 
dell'Oggetto, il tasto 'Ins' per commutare tra la modalita' inserimento e 
sovrascrittura.

/*
 * esempio 8
 */
#include <stdlib.h>
#include "mgui.h"

#define IsEven(a)	(!((a)&1))

int ValidateString(char *ps)
{
	int val, even, old;

	val = True;
	old = IsEven(*ps);
	if (*ps != '\0') {
		for (ps++; *ps && val; ps++, old=even)
			val = old ^ (even = IsEven(*ps));
	}
	return val;
}

void ValidateCB(MOBJECT edit, EDIT_VAL *ev, void *ii)
{
	ev->text_validated = ValidateString(ev->text);
	if (!ev->text_validated && !ev->focus_lost)
		ev->focus_leave = False;
}

void ValueChangeCB(MOBJECT edit, EDIT_CHANGE *ec, void *ii)
{
	char buff[128];

	if (!strcmp(ec->current_text, "1234567890")) {
		ec->exec = False;
		return;
	}
	strncpy(buff, ec->current_text, ec->pos);
	strcpy(buff + ec->pos, ec->change);
	strcat(buff, ec->current_text + ec->pos + ec->len);
	ec->exec = ValidateString(buff);
}

void NewValueCB(MOBJECT edit, char *new, void *label)
{
	MObjectSetText((MOBJECT)label, new);
}

void ActivateCB(MOBJECT edit, char *text, void *ed)
{
	static int notify;

	notify ^= 1;
	MEditSetTextExt((MOBJECT)ed, text, False, notify);
}

void NewPosCB(MOBJECT edit, EDIT_CURS *ec, void *label)
{
	char str[128];

	sprintf(str, "%-3d - %-3d", ec->pos_y, ec->pos_x);
	MObjectSetText((MOBJECT)label, str);
}

void MGUIMain(int argc, char **argv)
{
	MOBJECT shell, pb, edit, edit2, label;
	MTColor black, white, seagreen, cadetblue;

	black = MBlackColor();
	white = MWhiteColor();
	cadetblue = MAllocColor(0x60, 0x85, 0x95);
	seagreen = MAllocColor(0x70, 0xa5, 0xa0);

	shell = MCreateShell("Sample 8", 0);

	edit = MCreateEdit(shell, "", 20, DEFAULT_FONT); 
	MEditSetFilter(edit, DIGIT, "");
	MEditSetValCallback(edit, ValidateCB, NULL); 
	MEditSetValidatedColors(edit, cadetblue, white);
	MEditSetNotValidatedColors(edit, seagreen, black);

	edit = MCreateEdit(shell, "", 20, DEFAULT_FONT); 
	MEditSetFilter(edit, DIGIT, "");
	MEditSetValueChangeCallback(edit, ValueChangeCB, NULL);

	label = MCreateLabel(shell, "", DEFAULT_FONT);

	edit = MCreateClippedEdit(shell, "", 15, 20, DEFAULT_FONT);
	MEditSetNewValueCallback(edit, NewValueCB, label);

	edit2 = MCreateEdit(shell, "", 20, DEFAULT_FONT);
	MEditSetFilter(edit2, UPALPHA, "_");
	MEditSetActCallback(edit2, ActivateCB, edit);

	label = MCreateLabel(shell, "", DEFAULT_FONT);

	edit = MCreateClippedMLEdit(shell, "", 15, 4, DEFAULT_FONT);
	MEditSetCursorPosCallback(edit, NewPosCB, label);

	pb = MCreatePButton(shell, "Quit", TIMES_L_FONT);
	MPButtonSetCallback(pb, ExitCB, NULL);

	MShellRealize(shell);

	MMainLoop();
}

L'esempio 8 mostra l'uso delle cinque callback previste per l'EDIT, su 
altrettanti Oggetti.
	La funzione ValidateString() ritorna 'True' se il testo della 
stringa ottenuta come parametro e' costituita da caratteri aventi codice 
ASCII alternativamente pari e dispari.
	La callback ValidateCB() scatta ogni qual volta il primo EDIT sta 
per cedere l'input focus volontariamente (ad esempio per effetto della 
pressione del tasto Tab) oppure forzatamente (per l'attivazione di 
un'altra finestra o la selezione di un altro Oggetto con la 
MSelectObject()). La struttura EDIT_VAL, notificata alla callback 
attraverso il puntatore ricevuto come secondo parametro, e' definita come 
segue:

typedef struct {
	char *text; /* Testo dell'EDIT */
	short len; /* lunghezza */
	int key;   /* Codice del tasto che causa la chiamata (0 se non appl.) */
	unsigned focus_lost;	 /* Se == 1 l'EDIT cedera' comunque il focus */
	unsigned focus_leave;	 /* Se posto a 0 l'EDIT mantiene il focus */
	unsigned text_changed;	 /* Se posto a 1 l'EDIT effettua il redraw */
	unsigned text_validated; /* Se posto a 1 l'EDIT cambia stato */
} EDIT_VAL;


Con essa l'Oggetto notifica alla callback: il contenuto dell'EDIT 
attraverso campo 'text'; la lunghezza del campo editabile attraverso il 
campo 'len'; il codice del tasto che causa lo scattare della callback con 
il campo 'key' (vale 0 se  la cessione dell'input focus non avviene per la 
pressione di un tasto); infine, con il campo 'focus_lost', notifica se 
l'input focus sara' comunque ceduto o se l'applicativo ha la possibilita' 
di far recedere la cessione dell'input focus.
Il campo 'focus_leave', preimpostato a True, puo' essere forzato a False 
per lasciare l'input focus sull'EDIT (sempre che 'focus_lost' sia False). 
La callback ha la possibilita' di modificare il contenuto dell'Oggetto, in 
tal caso il campo 'text_changed', preimpostato a False, deve essere 
impostato a True. L'ultimo campo serve a settare lo stato di validazione 
dell'EDIT. 
Nel caso dell'esempio la callback usa la funzione ValidateString() per 
impostare lo stato di validazione. Inoltre, se la validazione e' negativa 
e l'Oggetto non ha gia' perso l'input focus, richiede di mantenere 
quest'ultimo.
	La callback ValueChangeCB() viene attivata ogni qualvolta cambia il 
contenuto del secondo EDIT. Essa consente di prendere visione delle 
modifiche che stanno per intervenire nell'Oggetto ed eventualmente di 
inibirle. E' sottoposta a verifica l'impostazione di testo fatta con la 
funzione MEditSetTextExt() (con il quarto parametro a  True), ma non 
quella fatta con la MObjectSetText().
La struttura notificata dall'Oggetto e' cosi' definita:

typedef struct {
	char *current_text;	/* Testo dell'EDIT prima della modifica */
	char *change;	   /* Testo di modifica dell'EDIT */
	int pos;	   /* posizione di inizio modifica */
	int len;	   /* lunghezza del testo attuale soggetto a modifica */
	unsigned validate;	/* Flag di validazione */
	unsigned exec;		/* Flag di modifica accettata */
} EDIT_CHANGE;

Il primo campo contiene il contenuto prima della modifica, mentre il 
secondo contiene la parte di testo nuova. Il campo 'pos' specifica la 
posizione di inserimento relativo al contenuto corrente. Il testo di 
modifica 'change' sostituisce il contenuto corrente, a partire dalla 
posizione 'pos' e per una lunghezza 'len'.
Il campo 'validate' e' preimpostato a False, tranne nel caso in cui la 
callback scatta per effetto di una chiamata alla funzione 
MEditSetTextExt(). In quest'ultimo caso assume il valore del terzo 
parametro passato alla funzione stessa.
Il valore di questo campo puo' essere impostato dalla callback, alterando 
cosi' lo stato di validazione dell'EDIT.
Se l'ultimo campo, preimpostato sempre a True, viene posto al valore 
False, la modifica al testo dell'Oggetto non viene attuata.
Nell'esempio 8 la callback ValueChangeCB() inibisce ogni modifica al 
testo, una volta che il contenuto diventa "1234567890". Altrimenti 
consente la modifica purche' soddisfi la condizione imposta dalla funzione 
ValidateString().
	La callback NewValueCB() e' piu' semplice di quelle precedentemente 
descritte. Essa fornisce direttamente come secondo parametro il nuovo 
valore dell'EDIT, a modifica gia' effettuata. L'azione prodotta dalla 
callback dell'esempio e' quella di impostare ogni nuovo valore del terzo 
EDIT nella LABEL ottenuta come terzo parametro. Da notare che il terzo 
EDIT e' creato con la funzione MCreateClippedEdit(). Questa consente, 
attraverso il quarto parametro aggiuntivo, di assegnare un numero di 
caratteri visibili inferiore alla lunghezza del campo editabile.
	La callback ActivateCB() scatta quando viene pressato il tasto 
'Enter' sul quarto EDIT. Anch'essa riceve, come dato di Oggetto, il testo 
dell'EDIT. Il suo effetto e' quello di impostare il suo valore nel terzo 
EDIT (ricevuto come terzo parametro), richiedendo la notifica della 
variazione a quest'ultimo una volta su due.
	Le funzioni MBlackColor() e MWhiteColor(), usate nella MGUIMain() 
dell'esempio, ritornano rispettivamente gli identificatori dei colori nero 
e bianco (predefiniti). I due colori 'seagreen' e 'cadetblue', allocati 
con la funzione MAllocColor(), andrebbero a rigore liberati con la 
funzione MFreeColor() prima di terminare. Cio' non e' comunque necessario, 
dal momento che l'impegno dei due colori dura fino alla fine del 
programma.
Al primo Oggetto EDIT e' impostato il filtro in modo da gestire solo 
cifre. E' impostata inoltre la callback di validate e i colori che deve 
assumere l'Oggetto nei due stati.
L'input sul quarto EDIT, per effetto del filtro impostato, sara' 
costituito solo da caratteri alfabetici convertiti in maiuscolo e dal 
carattere aggiuntivo '_'.
L'ultimo EDIT e' di tipo multi linea nella versione 'clipped'. Esiste 
anche la versione con gli SCROLLBAR ottenibile utilizzando la funzione 
MCreateScrolledMLEdit() avente lo stesso prototipo della 
MCreateClippedMLEdit().
Per quest'ultimo EDIT e' mostrato l'uso della callback che segnala la 
variazione di posizione del cursore all'interno del testo.
Di seguito e' riportata la definizione della struttura che l'Oggetto usa 
per notificare alla callback la nuova posizione del cursore.

typedef struct {
	int pos;		/* posizione nello stream */
	int pos_x;		/* posizione x */
	int pos_y;		/* posizione y */
} EDIT_CURS;


SCROLLBAR

Lo SCROLLBAR e' un Oggetto presente nella libreria principalmente per 
l'uso con gli Oggetti Composti come le scrolled FORM o i vari tipi di 
lista. Viene tuttavia descritto piu' in dettaglio in quanto potrebbe 
essere usato come uno slider oppure per la costruzione di un nuovo Oggetto 
Composto.
Esiste nella versione orizzontale e verticale. La sua dimensione 
perpendicolare a quella di scorrimento e' fissa, mentre quella parallela 
non possiede un default e va imposta attraverso gli agganci.
L'Oggetto e' caratterizzato da tre valori interi long: Max, N, Pos. I 
primi due sono specificati alla creazione e possono essere variati 
successivamente. Il terzo puo' essere impostato con l'apposita funzione, 
oppure, in corrispondenza ad una sua variazione, essere notificato 
all'applicativo dall'Oggetto stesso attraverso la sua callback.


STATUSBAR

E' un Oggetto che serve a visualizzare lo stato di completamento di una 
operazione in corso. E' caratterizzato da un particolare look 3-D che lo 
distingue da tutti gli altri Oggetti e contiene al suo interno una stringa 
di testo che puo' essere variato con una apposita chiamata a funzione.
Il suo uso e' abbastanza semplice: si crea l'Oggetto specificando il testo 
iniziale, il valore di fine operazione (Max) e il font da utilizzare; 
quindi puo' essere variato il valore corrente (da 0 a Max) e/o il testo, 
al progredire dell'operazione di cui si visualizza l'evoluzione.

/*
 * esempio 9
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mgui.h"

void AppTimerCB(TIMEOUT_ID id, void *data)
{
	static int perc;
	char str[128];
	MOBJECT sbar = (MOBJECT)data;

	perc += 10;
	sprintf(str, "%d %%", perc);
	MObjectSetText(sbar, str);
	MStatusBarSetPos(sbar, perc);
	if (perc < 100)
		MAddTimeout(500L, AppTimerCB, data);
	else
        	MObjectSetSensitive((MOBJECT)MObjecGetUserData(sbar));
}

void MGUIMain(int argc, char **argv)
{
	MOBJECT shell, pb, statusbar;

	shell = MCreateShell("Esempio 9", 0);

	statusbar = MCreateStatusBar(shell, "0 %", 100, DEFAULT_FONT);
	pb = MCreatePButton(shell, "Quit", TIMES_L_FONT);
	MPButtonSetCallback(pb, ExitCB, NULL);
	MObjectSetUnsensitive(pb);
	MObjectSetUserData(statusbar, pb);

	MAddTimeout(500L, AppTimerCB, statusbar);

	MShellRealize(shell);

	MMainLoop();
}

La funzione AppTimerCB() e' una callback legata ad un TIMEOUT impostato 
con durata di 500 millisecondi. Essa incrementa il valore corrente della 
STATUSBAR di 10 unita' e setta il testo dell'Oggetto in modo da 
visualizzare in percentuale il progredire di una operazione fittizia. La 
callback di TIMEOUT deve essere reimpostata all'interno della stessa 
callback se si vuole mantenere attivo il timer. Nel caso dell'esempio, la 
funzione di libreria MAddTimeout() predispone la callback 'AppTimerCB' con 
parametro 'data' dopo un intervallo di 500 millisecondi.
Non appena il valore corrente diviene uguale al valore di fine operazione, 
anziche' reimpostare il TIMEOUT, la callback rimette il PBUTTON per 
l'uscita da programma allo stato 'sensibile'. Cio' e' realizzato 
utilizzando il campo 'user_data' dell'Oggetto STATUSBAR, che viene 
assegnato nella funzione MGUIMain(). Tale campo  e' a disposizione 
dell'applicativo non essendo gestito internamente dalla libreria.
Nella funzione MGUIMain() e' creata la STATUSBAR con il testo iniziale "0 
%" e valore di fondo scala pari a 100. Il pulsante di uscita, subito dopo 
la creazione, e' posto allo stato 'insensibile' per inibire la sua 
attivazione.
La chiamata alla funzione MAddTimeout() imposta il TIMEOUT che simula il 
progredire dell'operazione fittizia. Il valore che ricevera' la callback 
AppTimerCB, stabilito dal terzo parametro, e' l'identificatore 
dell'Oggetto STATUSBAR.


MENU

Il MENU a tendina della libreria e' un Oggetto Terminale un po' 
particolare. Infatti, sebbene visto dal contenitore si comporti come un 
Terminale comune,  possiede caratteristiche da Manager per gestire le 
finestre di PULLDOWN. Il MENU non e' trattato in modo speciale dagli 
Oggetti Manager nel senso che non esiste nessun Oggetto che preveda il 
MENU come risorsa predefinita o impostabile. Cio' offre la liberta' di 
porre il MENU in qualunque posizione all'interno della gerarchia di 
layout, ad esempio sotto una LABEL o anche all'interno di una scrolled 
FORM. Ovviamente, per uniformita' con tutte le interfacce grafiche che 
usano menu a tendina, e' opportuno creare il MENU come primo Oggetto 
Terminale, possibilmente figlio della FORM principale (della SHELL).
Nel seguito col termine 'titolo' si intendera' il testo del MENU a cui e' 
associata una tendina (PULLDOWN), col termine 'item' invece si intendera' 
il testo della tendina che puo' essere selezionato.
La costruzione del MENU inizia con la creazione del menu stesso, quindi 
con l'aggiunta dei titoli e la creazione dei PULLDOWN releativi e infine 
con l'aggiunta degli item ad ogni PULLDOWN. L'associazione tra MENU e 
PULLDOWN, fatta alla creazione di quest'ultimo, non stabilisce una 
relazione di contenimento come avviene con le altre funzioni di creazione, 
infatti l'Oggetto PULLDOWN e' contenuto in una SHELL indipendente. 
Stabilisce piuttosto una relazione tra la selezione del titolo di MENU e 
l'apertura della tendina relativa. Ad ogni item puo' essere assegnata una 
callback, un tasto acceleratore di selezione, oppure l'apertura di 
un'altra tendina. Gli item possono anche essere marcati oppure resi 
insensibili.

/*
 * esempio 10
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mgui.h"

void ItemCheckCB(MOBJECT obj, void *item, void *udata)
{
	int status;
	char str[128];

	status = MToggleItemCheckStatus(obj, (int)item);
	sprintf(str, "L'item di menu' selezionato\ne' stato %s",
		(status ? "Attivato" : "Disattivato"));
	MMessageDialog("", str, "Ok", NULL);
}

void ItemSelectCB(MOBJECT obj, void *item, void *udata)
{
	char str[128];

	sprintf(str, "Selezionato l'item di menu' <%d>", (int)item);
	MMessageDialog("", str, "Ok", NULL);
}

void MGUIMain(int argc, char **argv)
{
	MOBJECT shell, pb, menu, pdown, subpd;
	PULLDOWN_ITEM pd_item_id;

	shell = MCreateShell("Esempio 10", 0);

	menu = MCreateMenuBar(shell, HELV_L_FONT);
	MMenuSetAcc(menu, F10);
	pdown = MCreatePullDown(menu, "File");
	MPullDownAddItem(pdown, "Quit", ExitCB, NULL);

	pdown = MCreatePullDown(menu, "Sottomenu");
	MPullDownAddItem(pdown, "Item 1", ItemCheckCB, NULL);
	pd_item_id = MPullDownAddItem(pdown, "Item 2", NULL, NULL);
	MPullDownAddItem(pdown, "Item 3", ItemCheckCB, NULL);

	subpd = MCreateSubPullDown(pdown, pd_item_id);
	MPullDownAddItem(subpd, "subItem 1", ItemSelectCB, NULL);
	MPullDownAddItem(subpd, "subItem 2", ItemSelectCB, NULL);
	MPullDownAddItem(subpd, "subItem 3", ItemSelectCB, NULL);

	MShellRealize(shell);

	MMainLoop();
}

La callback ItemCheckCB() commuta lo stato di check degli item di menu per 
i quali e' stata predisposta. Allo scopo usa la funzione 
MToggleItemCheckStatus() la quale effettua la commutazione dello stato e 
ne ritorna il valore.
E' possibile ottenere lo stato di check di un item attraverso la funzione 
MGetItemCheckStatus(), speficando come parametri L'Oggetto PULLDOWN e 
l'identificatore dell'item stesso.
La callback ItemSelectCB() si limita a visualizzare un messaggio ogni 
qualvolta essa e' chiamata.


DRAWAREA

La libreria possiede delle primitive di disegno che operano sugli Oggetti. 
Sebbene sia possibile disegnare su una LABEL o un EDIT, cio' ha ovviamente 
poco senso ed e' anzi decisamente da evitare. Allo scopo esiste l'Oggetto 
DRAWAREA, dotato delle callback di Expose, Input e Resize, con le quali 
notifica all'applicativo rispettivamente: quando e' necessario ridisegnare 
una porzione dell'area, quando viene pressato un tasto del mouse o della 
tastiera e quando le dimensioni dell'area vengono variate per un qualunque 
motivo.
Se viene impostata la callback di input, alla ricezione dell'evento 
pressione pulsante mouse, l'Oggetto monopolizza il mouse fino al rilascio 
del pulsante, in modo da fare intercettare alla callback anche gli eventi 
di movimento mouse e rilascio del pulsante, indipendentemente dalla 
posizione del cursore sullo schermo.
Le coordinate del mouse ricevute dalla callback sono sempre relative alla 
DRAWAREA.

/*
 * esempio 11
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "mgui.h"

MTColor black, white, cadetblue, seagreen;

void DrawAreaExposeCB(MOBJECT obj, MEvent *pexp, void *a_data)
{
	int hsize, wsize;

	wsize = ObjectWidth(obj);
	hsize = ObjectHeight(obj);
	MDrawLine(obj, 0, 0, wsize, hsize, black);
	MDrawLine(obj, 0, hsize, wsize, 0, white); 
	MDrawFilledCircle(obj, wsize/2, hsize/2, wsize/4, cadetblue);
	MDrawCircle(obj, wsize/2, hsize/2, wsize/4, black);
}

void DrawAreaResizeCB(MOBJECT obj, DRAWAREA_PREF *pref, long label)
{
	char str[128];

	sprintf(str, "Dimensioni: %dx%d", pref->new_w, pref->new_h);
	MObjectSetText((MOBJECT )label, str);
}

void DrawAreaInputCB(MOBJECT obj, MEvent *pe, void *a_data)
{
	static int x1, y1, x2, y2;

	if (pe->type == E_BUTTON_PRESS) {
		x1 = x2 = pe->mouse.x;
		y1 = y2 = pe->mouse.y;
		MDrawXorLine(obj, x1, y1, x2, y2);
	}
	else if (pe->type == E_MOTION) {
		MDrawXorLine(obj, x1, y1, x2, y2);
		x2 = pe->mouse.x;
		y2 = pe->mouse.y;
		MDrawXorLine(obj, x1, y1, x2, y2);
	}
	else if (pe->type == E_BUTTON_RELEASE) {
		MDrawXorLine(obj, x1, y1, x2, y2);
		x2 = pe->mouse.x;
		y2 = pe->mouse.y;
		MDrawLine(obj, x1, y1, x2, y2, seagreen);
	}
}

void MGUIMain(int argc, char **argv)
{
	MOBJECT pbutton, shell, label, drawa;

	black = MBlackColor();
	white = MWhiteColor();
	cadetblue = MAllocColor(0x60, 0x85, 0x95);
	seagreen = MAllocColor(0x70, 0xa5, 0xa0);

	shell = MCreateShell("Esempio 11", 0);

	label = MCreateLabel(shell, "", HELV_L_FONT);

	drawa = MCreateDrawArea(shell, 256, 256);
	MObjectSetResize(drawa, True, True);
	MDrawAreaSetExposeCallback(drawa, DrawAreaExposeCB, NULL);
	MDrawAreaSetInputCallback(drawa, DrawAreaInputCB, NULL);
	MDrawAreaSetResizeCallback(drawa, DrawAreaResizeCB, label);

	pbutton = MCreatePButton(shell, "Chiudi", TIMES_L_FONT);
	MPButtonSetCallback(pbutton, ExitCB, NULL);

	MShellRealize(shell);
	MMainLoop();
}

La callback DrawAreaExposeCB() riceve, come secondo parametro, un 
puntatore ad un evento di tipo Expose che specifica quale porzione 
dell'Oggetto deve essere ridisegnata. L'area di 'clip' notificata 
dall'Oggetto puo' essere usata per evitare le richieste di disegno al di 
fuori dell'area, aumentando l'efficienza del redraw. In ogni caso tutte le 
richieste di disegno, fatte all'interno della callback, hanno effetto solo 
nell'area di clip determinata dall'evento di Expose.
Nel caso dell'esempio, l'area di clip viene ignorata per semplicita'.
La callback DrawAreaResizeCB() si limita a impostare in una label le 
dimensioni attuali della DRAWAREA.
La callback DrawAreaInputCB() gestisce gli eventi di mouse per disegnare 
delle linee nell'Oggetto. Da notare che il disegno di tali linee, non 
essendo predisposto nella callback di Expose, si perde forzando il redraw 
dell'Oggetto (ad esempio effettuando un resize della finestra).


PIXMAP

Il formato di default per le immagini e' il BMP di Windows. La scelta e'
caduta su questo formato per la sua semplicita' e perche' gestisce immagini
da 1 a 24 piani. Dalla release 1.7 sono comunque supportati anche il
formato GIF e (solo su piattaforme a 32 bit) anche TIFF e JPEG.
Non esiste un Oggetto di tipo PIXMAP, ma la API della libreria dispone di 
una funzione che crea un Oggetto DRAWAREA con le dimensioni dell'immagine 
e ne predispone la callback di Expose per disegnare l'immagine stessa.
L'immagine (struttura di tipo MIMAGE) puo' essere caricata in memoria con
le funzioni MReadBMPFile(), MReadGIFFile(), MReadTIFFFile() e MReadJPEGFile(),
le quali richiedono come parametro il nome del file e ritornano un descrittore
della immagine (puntatore alla struttura MIMAGE).
Prima di potere essere utilizzata, la MIMAGE deve essere installata con la 
funzione MInstallImage(). Questa operazione comporta l'allocazione dei 
colori dell'immagine e la conversione del suo formato in uno piu' 
efficiente per la risoluzione di colori in uso.
Il descrittore di MIMAGE puo' essere fornito come parametro alla funzione 
MCreatePixmapFromData(), la quale crea un Oggetto DRAWAREA legando la sua 
callback di Expose al disegno della MIMAGE.
Quando la MIMAGE non si usa temporaneamente e' conveniente deinstallarla 
con la funzione MUninstallImage(). Quando non e' piu' necessario tenerla 
in memoria, puo' essere distrutta con la funzione MFreeImage() che 
provvede preliminarmente a deinstallarla liberando cosi' i colori 
impegnati.
Esiste un modo piu' semplice di creare una PIXMAP a partire da un file, 
purche' non sia richiesto avere l'identificatore della MIMAGE. La funzione 
MCreatePixmapFromFile()  provvede internamente a caricare l'immagine ed 
installarla alla creazione, e a deinstallarla e liberarla quando la 
DRAWAREA viene distrutta.
L'esempio 12 mostra il metodo piu' semplice per visualizzare una bitmap.
 
/*
 * esempio 12
 */
#include <stdio.h>
#include <stdlib.h>

#include "mgui.h"

void MGUIMain(int argc, char **argv)
{
	MOBJECT pbutton, shell, pixmap;

	shell = MCreateShell("Esempio 12", 0);

	pixmap = MCreatePixmapFromFile(shell, "paulina.bmp");

	pbutton = MCreatePButton(shell, "Chiudi", TIMES_L_FONT);
	MPButtonSetCallback(pbutton, ExitCB, NULL);

	MShellRealize(shell);
	MMainLoop();
}


E' possibile creare pulsanti di tipo PBUTTON, TBUTTON o RBUTTON, 
contenenti una MIMAGE anziche' del semplice testo (i pulsanti della FORM a 
pagine ne sono un esempio). Le pixmap associate al pulsante sono tre: la 
prima viene visualizzata quando il pulsante si trova allo stato normale 
(non pressato), la seconda quando il pulsante e' attivato e la terza 
quando il pulsante non e' sensibile. Per la seconda e la terza e' 
possibile usare il default NULL. In questo caso sara' visualizzata la 
prima pixmap.
Per ogni tipo di pulsante esistono due diversi metodi per creare l'Oggetto 
pulsante con pixmap. Il primo, che usa la funzione MCreateDrawn?Button(), 
prevede il passaggio come parametri delle tre MIMAGE gia' installate. E' a 
carico dell'applicativo, inoltre, la chiamata alla MFreeImage() quando la 
MIMAGE non e' piu' referenziata.
Il secondo metodo, che usa la funzione MCreateImage?Button(), consente 
invece di specificare le tre pixmap con il nome del file, lasciando 
all'Oggetto, quando viene distrutto, l'onere della chiamata alla 
MFreeImage().
Il primo metodo risulta conveniente solo quando una stessa pixmap e' 
associata a piu' Oggetti, oppure si si vuole conoscere l'esito del 
caricamento della pixmap stessa.
E' possibile modificare le pixmap associate ai pulsanti, o anche di 
associarle a pulsanti con testo, attraverso la chiamata alla funzione 
M?ButtonSetImage().


SBLIST

L'Oggetto SBLIST possiede diverse caratterizzazioni, ognuna delle quali si 
comporta esternamente come un Oggetto diverso. E' costituito da righe di 
testo, scrollabile nelle due direzioni, che possono essere sensibili.
Tutti i tasti freccia sono utilizzati per lo scroll nelle due direzioni, 
pertanto, per cedere l'input focus ad un altro Oggetto e' necessario usare 
il tasto 'Tab' o 'Shift+Tab'.

SFile

La caratterizzazione piu' semplice (vista dall'esterno) e' il tipo 'SFile' 
(scrolled file). Consiste in una finestra munita dei due scrollbar che 
visualizza al suo interno il contenuto di un file ASCII. Il file non e' 
caricato interamente in memoria, ma a blocchi di 200 righe per un massimo 
su file di 25600. L'interfaccia con l'applicativo richiede solamente la 
specificazione del nome del file, quando l'Oggetto viene creato, come 
parametro alla funzione MCreateSFile(). Successivamente e' possibile 
cambiare il file visualizzato utilizzando la funzione MSFileSetFile(), 
alla quale occorre specificare il nome del nuovo file.

SList

Il tipo 'SList' (scrolled list) e' il piu' comune e realizza la lista 
semplice della libreria.
Per la sua creazione e' necessario specificare, fra i parametri della 
funzione MCreateSList(), il font da utilizzare, il numero di righe 
visibili, il numero di colonne visibili e il numero effettivo di colonne 
(lunghezza degli elementi). Se quest'ultimo coincide con il numero 
visibile richiesto, lo scrollbar orizzontale non viene visualizzato.
Ogni riga (item) e' caratterizzata da una stringa di testo, un puntatore 
'void' che l'applicativo gli puo' associare e uno stato che puo' essere 
opzionalmente impostato.
L'inserimento di nuovi item si effettua attraverso la funzione 
MSListAddItem(), alla quale si specifica il testo e il puntatore da 
associare all'item. La rimozione di item dalla lista si realizza con la 
funzione MSListDelItem(), specificando il testo dell'item da rimuovere. E' 
possibile anche cambiare il testo e/o il puntatore 'void' di un item 
usando la funzione MSListChangeItem().
La lista possiede sempre un item evidenziato (in rilievo). 
L'evidenziazione si modifica usando le frecce su e giu' oppure clickando 
con il puntatore del mouse su un altro item. La pressione del tasto Enter 
quando la lista possiede l'input focus o il doppio click del pulsante 
sinistro del mouse fa scattare la callback di attivazione della lista. La 
callback riceve come secondo parametro il puntatore ad una struttura di 
tipo LIST_ACT che contiene tutti i dati relativi all'item attivato.
Per la scansione degli elementi di una lista e' previsto l'uso della 
funzione MSListGetFirstItem(), che ritorna il puntatore ad una struttura 
LIST_ACT contenente le informazioni relative al primo item, e della 
funzione MSListGetNextItem() che ritorna le informazioni per tutti gli 
item successivi.
Di default gli item sono inseriti in fondo alla lista. E' possibile 
tuttavia impostare una funzione di ordinamento, nel qual caso gli item 
sono sempre mantenuti ordinati, anche dopo  eventuali modifiche. La 
funzione di ordinamento agisce sulla parte testo dell'item ed ha prototipo 
uguale alla funzione di libreria standard strcmp().
Quando gli item di una lista sono ordinati alfabeticamente, si puo' 
sfruttare il metodo di ricerca interno per trovare un item. Allo scopo 
basta digitare i caratteri della stringa dell'item cercato, quando 
l'Oggetto possiede l'input focus. Per iniziare una nuova ricerca si deve 
pressare il tasto 'Esc'.
Ad una lista e' possibile associare una testata, con l'uso della funzione 
MSListSetHead(), a cui puo' essere assegnato un colore indipendente da 
quello della lista. Il testo viene troncato alla lunghezza dell'item e 
l'intestazione segue l'eventuale scorrimento orizzontale degli item, 
rimanendo ovviamente insensibile a quello verticale.

/*
 * esempio 13
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mgui.h"

void ScrolledListCB(MOBJECT p, LIST_ACT *la, void *v)
{
	char str[128];

	sprintf(str, "Selezionata la riga %ld\n<%s>\nuser data: %ld",
		la->pos, la->item, la->u_data);
	MMessageDialog("", str, "Ok", NULL);
}

void MGUIMain(int argc, char **argv)
{
	MOBJECT pbutton, shell, slist;
	int i;
	char str[512];
	MTColor white, seagreen;

	white = MWhiteColor();
	seagreen = MAllocColor(0x70, 0xa5, 0xa0);

	shell = MCreateShell("Esempio 11", SF_MODAL);

	slist = MCreateSList(shell, DEFAULT_FONT, 16, 10, 12);
	MSListSetCallback(slist, ScrolledListCB, slist);
	MSListSetSortCallback(slist, strcmp);

	MSBLSetHead(slist, "Nome    Cognome ");
	MSBLHeadSetColor(slist, seagreen, white);

	for (i=0; i<50; i++) {
		sprintf(str, "%c%03d cogn%04d", 'a'+i%26, i, i);
		MSListAddItem(slist, str, (void *)i);
	}

	pbutton = MCreatePButton(shell, "Chiudi", TIMES_L_FONT);
	MPButtonSetCallback(pbutton, ExitCB, NULL);
	MObjectSetResize(pbutton, False, False);

	MShellRealize(shell);

	MMainLoop();
}

La callback ScrolledListCB() e' legata alla attivazione di un item della 
lista. Dal secondo parametro sono ricavati i dati relativi all'item 
attivato, e quindi visualizzati con la funzione MMessageDialog().
Di seguito e' riportata la definizione della struttura LIST_ACT:

typedef struct {
	char *item;			/* testo dell'item */
	void *u_data;			/* user data associato */
	long pos;			/* posizione all'interno della lista */
	unsigned char selected;	/* flag di selezione (SListMultiSel) */
	unsigned char checked;	/* stato impostabile da applicativo */
} LIST_ACT;

La predisposizione della funzione di libreria standard 'strcmp' come 
funzione di ordinamento fa si che gli item siano mantenuti dall'Oggetto in 
ordine alfabetico crescente.
Alla lista dell'esempio e' predisposta una intestazione, attraverso la 
funzione MSBLSetHead(), alla quale sono modificati il colori di background 
e foreground con la funzione MSBLHeadSetColor().

SListMultiSel

Esiste una caratterizzazione dell'Oggetto SBLIST leggermente diversa 
rispetto al tipo SList: la 'SListMultiSel' (lista a selezione multipla).
L'unica differenza di comportamento sta nel fatto che la attivazione di un 
item con il mouse ne commuta lo stato di selezione. La commutazione da 
tastiera si effettua con la pressione del tasto 'Ins'.
La API differisce da quella per il tipo SList, oltre che per la funzione 
di creazione, per la presenza di quattro funzioni che consentono di 
scandire la lista prendendo in considerazione solo gli item selezionati o 
quelli non selezionati.
Per gli item di una SListMultiSel, il campo 'selected' della struttura 
LIST_ACT ne segnala lo stato di selezione.

SListClip

La caratterizzazione di tipo 'SListClip' consente di implementare una 
lista virtuale in cui il numero di item effettivamente tenuto in memoria 
dall'Oggetto e' una piccola parte rispetto a quella virtuale. Ovviamente 
e' a carico dell'applicativo la fornitura di nuovi item, man mano che 
l'Oggetto li richiede.
Il metodo di ricerca interno si puo' ancora utilizzare, purche' 
l'applicativo fornisca una funzione che collabora con l'Oggetto nella 
ricerca (vedi la funzione SkipSearchCB() del prossimo esempio).
La funzione di creazione MCreateSListClip() possiede dei parametri 
aggiuntivi rispetto alla MCreateSList(): il puntatore alla callback che 
deve fornire gli item, il puntatore alla callback per la ricerca, i due 
long relativi alle callback. Il parametro che fornisce il numero di item 
della lista e' stavolta un intero long.

SListSkip

Il tipo 'SListSkip' e' una variante del tipo precedente. La differenza 
visibile esternamente consiste nel fatto che lo scrollbar verticale e' 
sostituito da una serie di pulsanti per lo scorrimento della lista. Da un 
punto di vista operativo la differenza si concretizza nel fatto che la 
richiesta di dati fatta dall'Oggetto, quando gli servono nuovi item, non 
e' piu fatta specificando l'offset assoluto di inizio blocco, ma un offset 
relativo alla posizione corrente, all'inzio oppure alla fine della lista.
Questa particolarita' rende la SListClip particolarmente indicata per 
realizzare finestre su DB voluminosi in cui risultano efficienti piccoli 
spostamenti relativi alla posizione corrente e il posizionamento 
all'inizio o alla fine del DB stesso.

/*
 * esempio 14
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "mgui.h"

#define MAX_POS	26L*26L*26L*26L*26L*26L

static long skip_pos = MAX_POS - 10;

static char *PosToString(long ll)
{
	static char buff[8];
	int i;

	i = 6;
	buff[i] = '\0';
	for (; i-- > 0; ll/= 26)
		buff[i] = 'a' + ll%26;

	return buff;
}

static long StringToPos(char *str)
{
	long ll;
	int i;

	ll = 0L;
	for (i=0; i<6; i++) {
		ll *= 26;
		if (*str) {
			if (!islower(*str))
                        	return -1L;
			ll += *str - 'a';
			str++;
		}
	}
	return (*str == '\0' ? ll : -1L);
}

static void SkipBlockCB(MOBJECT obj, SBL_NEW_BLOCK *pnb, void *data)
{
	int i;
	long off=skip_pos;
	XL_ITEM *pd = pnb->pi;
	long max = MAX_POS;

	switch (pnb->from) {
		case SEEK_SET:
			off = pnb->offset;
			break;
		case SEEK_CUR:
			off += pnb->offset;
			if (off < 0L) {
				pnb->nr = -1;
				return;
			}
			else if (off >= max) {
				pnb->nr = -1;
				return;
			}
			break;
		case SEEK_END:
			off = pnb->offset + max;
			break;
		default:
			off = 0L;
	}
	for (i=0; i < pnb->nr && pd && off < max; i++) {
		sprintf(pd->data, "%s %09ld %09ld",
			PosToString(off), off, pnb->offset);
		pd->u_data = (void *)off++;
		pd = pd->next;
	}
	pnb->nr = i;
/*
 * skip_pos punta all'ultimo item fornito
 */
	skip_pos = off-1;
}

static void SkipSearchCB(MOBJECT obj, SBL_ITEM_SEEK *pis, void *data)
{
	long ll;
	char *str = pis->sub_item;

	if ((ll = StringToPos(str)) != -1L) {
		pis->pos = 0;
		skip_pos = ll;
	}
}

void MGUIMain(int argc, char **argv)
{
	MTColor black, white, cadetblue, seagreen;
	MOBJECT pbutton, shell, form, slist;
	char str[512];

	black = MBlackColor();
	white = MWhiteColor();
	cadetblue = MAllocColor(0x60, 0x85, 0x95);
	seagreen = MAllocColor(0x70, 0xa5, 0xa0);

	shell = MCreateShell("Esempio 12", SF_MODAL);

	form = MCreateRowForm(shell);

	sprintf(str, "%ld elementi", MAX_POS);
	MCreateLabel(form, str, DEFAULT_FONT);

	slist = MCreateSListSkip(form, SkipBlockCB, 0L, SkipSearchCB, 0L, 
DEFAULT_FONT, 12, 16, 30);
	MObjectSetColor(slist, cadetblue, black);
	MSBLSetHead(slist, "Valore Posizione Offset");
	MSBLHeadSetColor(slist, seagreen, white);

	pbutton = MCreatePButton(shell, "Chiudi", TIMES_L_FONT);
	MPButtonSetCallback(pbutton, ExitCB, NULL);

	MShellRealize(shell);
	MMainLoop();
}

L'esempio 14 mostra l'uso tipico della SListSkip. Esso visualizza i dati 
di un DB virtuale, contenente elementi con chiave costituita da tutte le 
stringhe di sei caratteri alfabetici minuscoli (piu' di 300 milioni di 
elementi).
L'interfaccia con il DB fittizio e' realizzata con le funzioni 
PosToString() e StringToPos(). La prima ritorna la stringa di 6 caratteri 
corrispondente a una data posizione nel DB. La seconda invece ritorna la 
posizione nel DB virtuale di una stringa lunga fino a 6 caratteri.
La callback SkipBlockCB() si occupa di fornire all'Oggetto gli item. Allo 
scopo usa la struttura SBL_NEW_BLOCK (valida anche per l'Oggetto 
SListClip), di seguito descritta, per dialogare con la lista.

typedef struct {
	int from;       /* SEEK_SET, SEEK_CUR, SEEK_END */
	long offset;    /* Offset relativo alla posizione richiesta */
	int nc;         /* Dimensione delle righe in colonne */
	int nr;         /* Numero di righe da fornire */
	XL_ITEM *pi;    /* Puntatore alla lista concatenata di item */
} SBL_NEW_BLOCK;

Il campo 'from' (significativo solo per il tipo SListSkip) specifica, 
utilizzando le stesse macro della funzione di libreria lseek(), rispetto a 
quale posizione sono richiesti gli item. Il campo 'offset' specifica di 
quanto spostarsi, a partire dalla posizione 'from', per ottenere il primo 
item.
Il campo 'nc' fornisce la lunghezza degli item e 'nr' il numero di righe 
richieste.
Attraverso la lista concatenata 'pi', la callback ha modo di impostare, 
direttamente nella memoria dell'Oggetto, i dati degli item richiesti.
Se il numero effettivo di righe disponibili risulta inferiore a quello 
richiesto, la callback deve impostare tale valore nello stesso campo 'nr', 
in modo che l'Oggetto possa correttamente adeguarsi.
La callback SkipSearchCB() collabora con l'Oggetto per la ricerca di item 
nella lista. Il dialogo avviene attraverso la struttura SBL_ITEM_SEEK 
(valida anche per il tipo SListClip) cosi' definita:

typedef struct {
	long pos;		/* posizione dell'item (-1 se non trovato) */
	char *sub_item;	/* chiave parziale di ricerca */
} SBL_ITEM_SEEK;

Il campo 'sub_item' specifica il testo di ricerca, che deve essere inteso 
dalla callback come la sottostringa di un item.
Il campo 'pos' serve a notificare all'Oggetto se l'item ricercato esiste 
(-1 in caso contrario). Nel caso di Oggetto di tipo SListSkip, la callback 
deve porre a zero il valore di 'pos' e impostare la posizione corrente dei 
dati in quella corrispondente all'item ricercato. Nel caso di tipo 
SListClip, invece, il campo 'pos' deve essere posto uguale alla posizione 
competente all'item ricercato.

EditList

Esiste infine un ultimo tipo di lista: 'EditList', anch'essa estensione 
della SList.
La sua API consente di implementare liste di campi editabili, 
l'applicativo, cioe', non vede piu' una lista di item, ma una lista di 
record formati da una serie di campi. Il suo layout prevede: nella parte 
superiore la testata (con una stringa per campo), immediatamente sotto una 
riga contenente un Oggetto EDIT per ogni campo, infine la lista vera e 
propria, costituita da item aventi come testo la concatenazione dei campi.
Alla creazione l'applicativo fornisce un vettore di strutture, una per 
ogni campo, specificando: l'intestazione del campo, la sua lunghezza e una 
flag che specifica se il campo e' editabile o meno. Nella stessa 
struttura, in ritorno dalla funzione di creazione, l'applicativo trova 
impostati gli identificatori degli EDIT corrispondenti ad ogni campo.
La callback di attivazione della lista e' predisposta internamente ed ha 
come effetto la copia dei campi del record selezionato nei rispettivi 
EDIT. E' a carico dell'applicativo la gestione delle modifiche che possono 
avvenire negli EDIT.


OPTIONEDIT

L'OPTIONEDIT e' un Oggetto composto, funzionalmente simile al 'COMBOBOX' 
di Windows.
E' realizzato con un EDIT (di cui viene ritornato il puntatore alla 
creazione), una SBLIST di tipo SList e un PBUTTON che visualizza la lista. 
La callback di attivazione della lista e' impostata internamente in modo 
da copiare il testo dell'item attivato nell'EDIT.
Una volta ottenuto l'identificatore della lista, con la funzione 
MOptionEditGetList(), la gestione dell'Oggetto si realizza attraverso la 
API gia' descritta per gli Oggetti EDIT e SList.

/*
 * esempio 15
 */
#include <stdio.h>
#include <stdlib.h>

#include "mgui.h"

void MGUIMain(int argc, char **argv)
{
	MOBJECT pbutton, shell, edit, list;

	shell = MCreateShell("Esempio 15", 0);

	MCreateLabel(shell, "Font statici", LARGE_FONT);

	edit = MCreateOptionEdit(shell, "", 16, 16, 6, DEFAULT_FONT);
	MObjectSetUnsensitive(edit);
	list = MOptionEditGetList(edit);
	MSListAddItem(list, "DEFAULT_FONT", 0L);
	MSListAddItem(list, "MEDIUM_FONT", 0L);
	MSListAddItem(list, "LARGE_FONT", 0L);
	MSListAddItem(list, "HELV_N_FONT", 0L);
	MSListAddItem(list, "HELV_L_FONT", 0L);
	MSListAddItem(list, "TIMES_N_FONT", 0L);
	MSListAddItem(list, "TIMES_L_FONT", 0L);

	pbutton = MCreatePButton(shell, "Chiudi", TIMES_L_FONT);
	MPButtonSetCallback(pbutton, ExitCB, 0L);

	MShellRealize(shell);
	MMainLoop();
}

Il prototipo della funzione di creazione e' simile a quello della funzione 
MCreateClipEdit (). Il terzo e quarto parametro specificano 
rispettivamente la lunghezza totale dell'EDIT e quella visibile. Il 
penultimo parametro specifica il numero di righe della lista da 
visualizzare.


TRACCIAMENTO MESSAGGI

La libreria dispone di funzioni che facilitano il tracciamento del 
programma, offrendo la possibilita' di scrivere dei messaggi su file e/o 
di visualizzarli in una finestra attivabile in qualsiasi momento con la 
sequenza di tasti <Shift-Ctrl-F1>.
Il file eventuale su cui scrivere va specificato chiamando la funzione 
MLogInit() all'inzio del programma. Attraverso questa funzione si 
specifica anche il livello iniziale di tracciamento, la geometria della 
finestra di Log e il numero massimo di messaggi da tenere sulla finestra.
I messaggi di tracciamento si richiedono con la chiamata alla funzione 
Mlog(). Il primo parametro di questa funzione specifica il livello di Log 
associato al messaggio, i rimanenti sono la stringa di formato e gli 
eventuali valori cosi' come da sintassi printf(). Il messaggio richiesto 
con la chiamata alla Mlog() sara' recepito solo se il livello di 
tracciamento attuale risulta maggiore o uguale a quello associato al 
messaggio. Ad esempio, la richiesta di Log effettuata con la chiamata:

Mlog(3, "sono alla linea: %d", __LINE__);

avra' effetto solo se nella MLogInit() viene specificato un livello 
maggiore o uguale a 3.
E' possibile, in qualsiasi momento, cambiare il livello di tracciamento 
sia da programma (con la funzione MLogSetLevel()), sia da utente attivando 
la finestra di controllo con la sequenza di tasti <Shift-Ctrl-F2>.
Un filtro piu' fine sul tracciamento si ottiene usando la funzione 
MMaskedLog() al posto della Mlog(). In questo caso, infatti, al livello di 
tracciamento del messaggio si aggiunge una maschera di bit, per cui la 
stampa sara' filtrata sia dal livello che dal valore attuale della 
maschera. Purtroppo al momento la maschera si puo' modificare solo da 
codice, con le funzione MLogSetMask(), MLogSetMaskBits() e 
MLogResetMaskBits().

