








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:

- Implementazione in ANSI C con frame in C++
- 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 (widget): pulsanti, 
campi editabili, liste, menu' etc....
- Semplicita' nella costruzione del layout grazie alla presenza di un 
widget manager che si occupa di posizionare e ridimensionare 
automaticamente i widget 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.
- Personalizzazione risorse dei widget su file ascii, con possibilita'  di 
editing run-time e salvataggio.

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()).
Dalla release 2 e’ stato aggiunto il frame per l’uso con il linguaggio 
C++.


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 widget 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 (Widget)

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 
al widget MGUI 'SHELL' legato alla finestra, e da quest'ultimo, 
seguendo la via gerarchica del layout, perviene al widget 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 dei widget MGUI e' il piu' voluminoso (circa l'82% di tutta 
la libreria) e contiene i moduli che realizzano la API con tutti i 
widget (pulsanti, campi editabili, liste, menu' etc...).
Nel seguito di questo manuale sono descritti in dettaglio tutti i widget 
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 dei widget, 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 dieci font statici 
che vengono referenziati attraverso macro. Quattro di questi sono a 
pitch fisso: DEFAULT_FONT, FIXED_SMALL, FIXED_MEDIUM 
e FIXED_LARGE. I rimanenti sono font proporzionali di tipo Times 
ed Helvetica: TIMES_SMALL, TIMES_LARGE, 
TIMES_MEDIUM, HELV_SMALL, HELV_LARGE e 
HELV_MEDIUM.
Tranne che negli ambienti Borland BGI e Watcom DOS/4GW, 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" e 
"Courier". 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 GESTIONE DELLE RISORSE

Dalla release 1.9, la libreria fornisce il supporto alla gestione delle 
risorse su file di configurazione ascii.
Il formato di questi file e’ quello di .Xdefault in X11, e .ini in 
Windows e DOS.
Esiste un file di configurazione di sistema, che si trova quindi in 
/mgui/sys, che definisce risorse generali come i font statici da usare 
ad una data risoluzione o i colori di background e foreground di 
default. Il nome che assume tale file varia a seconda del sistema: 
Mgui in X11, mgui.ini in Windows, mgui.bcf in BGI, mgui.gcf in 
DJGPP, mgui.wcf in Watcom DOS.
La specifica dei colori in ambiente X11 segue lo standard X, negli 
altri ambienti e’ invece costituita da una terna di valori decimali, 
separati da virgola, che definiscono rispettivamente i contenuti R, G 
e B del colore (es: <255,255,0> per il giallo).
La specifica dei font statici avviene utilizzando la nomenclatura 
standard sotto X11, il nome del file .mfn sotto BGI e Watcom, il 
nome del file .fnt sotto DJGPP/GRX.
In ambiente Windows questa specifica avviene secondo il formato: 
<family,height,bold>, dove family e’ il nome del font (es. arial), 
height l’altezza (es. 20), bold e’ una flag che attiva il grassetto se 
posta a 1.

Un altro file che concorre a determinare le risorse di un applicativo 
risiede nella stessa directory dell’eseguibile (nella home dir 
dell’utente in Unix) ed ha lo stesso nome dell’applicativo con 
estensione corrispondente al file di sistema (.nomeprogr in Unix).
E’ possibile ridefinire nel file dell’applicativo risorse gia’ definite nel 
file di sistema, per cui si puo’ personalizzare sul programma il 
default dei font e dei colori.

Una risorsa e’ identificata dalla combinazione di un nome di widget e 
un nome di risorsa.
Attributi dei widget della GUI (pulsanti, label etc...) validi come 
risorse sono i colori di background e foreground, il font, il testo e lo 
shadow.
La specifica di un font per un widget deve necessariamente 
coincidere con il nome del font statico che si vuole utilizzare (es: 
TIMES_LARGE).

Il caricamento delle risorse per un widget grafico avviene attraverso 
la chiamata alla funzione MObjectEnableOptions(), da effettuare 
dopo la creazione del widget stesso.
Ad esempio, per effetto del codice:

	label = MCreateLabel(mainshell, "Ctrl+Shift+MouseClick here to 
customize", FIXED_MEDIUM);
	MObjectEnableOptions(label, "label_1", MO_ALL);

verranno impostati tutti gli attributi della LABEL ‘label’ con cio’ che 
viene trovato come risorsa per il widget di nome ‘label_1’.
E’ consentito in qualunque momento il salvataggio da programma 
delle risorse di un widget. Ad esempio:

	MObjectSaveOptions(label);

comportera’ il salvataggio, nel file di risorse dell’applicativo, delle 
risorse abilitate precedentemente (tutte nel caso dell’esempio).

Una funzionalita’ avanzata di MGUI consente, per i widget per i 
quali e’ stato abilitato l’uso delle risorse, di aprire run-time una 
finestra di editing che consente di modificare, e opzionalmente di 
salvare su file, le risorse del widget.
L’abilitazione avviene attraverso la chiamata (ne basta una in 
MGUIMain()) alla funzione MEnableCustomizing(). L’attivazione 
della finestra di editing si ha click-ando col mouse sul widget 
desiderato, tenendo pressati contemporaneamente i tasti Ctrl e Shift.

La API di MGUI offre anche funzioni per salvare/caricare risorse che 
non sono riferite ad Widget della GUI.


LA PROPAGAZIONE DEGLI EVENTI

Gli eventi sono dei messaggi che hanno come destinatario un widget 
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 ai widget 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 widget 
in quanto ogni widget possiede una propria finestra, normalmente 
figlia di altre finestre.
Una finestra MGUI, quantunque complessa sia la gerarchia di 
Widget contenuti, e' costituita da una sola finestra di sistema. Ad 
essa e' associato il widget 'SHELL' che e' il progenitore di tutti i 
widget contenuti nella finestra stessa.
Ogni evento di sistema viene tramandato di padre in figlio, partendo 
dalla 'SHELL', fino al widget destinatario.
Gli eventi di mouse sono normalmente indirizzati al widget che sta 
immediatamente sotto il puntatore. Un widget 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 Widget. 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 al widget interessato, bypassando la sua 
SHELL.
Il widget candidato alla gestione degli eventi di tastiera si dice che 
possiede l'input focus. Nella gerarchia di Widget MGUI, se un 
widget 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 al widget terminale. Lo 
spostamento dell'input focus da un widget a un altro avviene per 
selezione esplicita con il mouse, per effetto della funzione 
MSelectObject() oppure  per cessione spontanea del widget 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.


I WIDGET MGUI

I widget MGUI si dividono in quattro categorie:

- Capostipite
- Manager
- Terminale principale
- Terminale composto

Il widget 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, ai widget suoi discendenti.
La SHELL e' l'unico widget a non avere 'parent' in quanto capostipite 
e possiede un solo widget figlio. Questi appartiene alla categoria 
Manager e viene creato assieme al widget SHELL (suo parent).
Il widget Manager e' la FORM. E' l'unico tipo di widget (a parte la 
SHELL) che puo' contenere dei widget 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 dal widget parent (SHELL o 
altra FORM).
La FORM possiede delle varianti che sono la FORM a pagine, la 
scrolled FORM, la clipped FORM e la table 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 Widget 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. Infine la table FORM consente di ottenere facilmente una 
disposizione matriciale dei widget figli.
I widget Terminali principali sono tutti i rimanenti tipi di widget che 
non sono composti. A questa categoria appartengono LABEL, EDIT, 
PBUTTON (push-button),  RBUTTON (radio-button) TBUTTON 
(toggle-button), MENU, DRAWAREA, SCROLLBAR.
I widget Terminali composti sono dei widget Terminali che hanno 
bisogno, per il loro funzionamento, della collaborazione di altri 
Widget di tipo Terminale principale e di tipo Manager.
I widget 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 il widget SHELL, le funzioni di creazione di ogni 
widget richiedono come primo parametro l'identificatore del widget 
parent. Quest'ultimo deve necessariamente essere una FORM oppure 
la SHELL; in quest'ultimo caso, in realta', l'associazione con il widget 
parent viene automaticamente rediretta alla FORM figlia della 
SHELL.
Tutti i widget, 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 dei widget 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 widget possiede il suo shadow 
predefinito che lo distingue dagli altri Widget. E' consentito 
modificarlo, con la funzione MObjectSetShadow(),  anche se 
ovviamente e' consigliabile non renderlo simile a quello di altri 
Widget per mantenere consistente il look. La generazione dell'effetto 
3-D delle ombreggiature e' a carico dei widget, nel senso che il 
calcolo e l'allocazione dei colori per le sfumature e' effettuato 
internamente alla libreria quando viene creato il widget e quando gli 
si cambia il colore.
Per i widget 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 widget SHELL
- Creazione di una serie di Widget discendenti della SHELL
- Predisposizione di callback su alcuni dei widget creati
- Realizzazione della finestra del widget SHELL
- Blocco del programma nel loop di gestione degli eventi
- Esecuzione delle callback predisposte su richiesta dei widget
- 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_LARGE);
	pushbutton = MCreatePButton(shell, "Ok", TIMES_LARGE);
	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 widget. Il primo parametro e' un identificatore 
MOBJECT, definito in 'mguidefs.h', che identifica il widget MGUI 
che chiama la callback. Il secondo parametro serve al widget 
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' Widget, usando tale parametro come 
discriminante, oppure se si vuole ricevere un dato noto alla creazione 
del widget.
Nel caso dell'esempio l'unico parametro utilizzato e' l'identificatore 
del widget. Da questo si ottiene, attraverso la funzione 
MObjectShell(),l'identificatore del widget 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 Widget 
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 
widget SHELL, una LABEL e un PBUTTON, entrambi figli del 
widget SHELL.
La LABEL e' un widget che visualizza del testo su una o piu' righe, 
centrato sempre rispetto all'altezza del widget 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 dal widget 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_LARGE);
	pushbutton = MCreatePButton(shell, "Ok", TIMES_LARGE);
	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 i widget 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 i widget 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' dei widget figli.
La scalabilita' di default in altezza e in larghezza dipende dal tipo di 
widget. 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.
Nel caso in cui tutti i widget siano non scalabili e le dimensioni della 
FORM sono forzate ad essere superiori a quelle naturali, la FORM 
modifica le posizioni dei figli centrandoli in dei rettangoli virtuali 
corrispondenti alle aree assunte dai widget se fossero scalabili.
L’allineamento all’interno di questi rettangoli puo’ essere modificata 
attraverso la funzione MObjectAlign(). Se il contenitore e’ una table 
FORM, allora il rettangolo virtuale coincide con la cella della matrice 
contenente il widget.
Nella costruzione del layout i widget vengono inseriti nella FORM 
uno alla volta. L'ultimo widget 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, al widget 
precedente in caso contrario; a sinistra al contenitore se questo e' una 
FORM a righe, al widget precedente in caso contrario. Nella table 
FORM sono in modo da riempire ordinatamente le righe.
Attraverso le due funzioni di libreria MObjectSetAttachment() e 
MObjectSetOffset(), e' possibile modificare gli agganci di un widget 
nelle quattro direzioni. Queste funzioni consentono rispettivamente di 
rimuovere uno o piu' agganci e di modificare il margine di 
separazione tra due Widget o tra un widget e il contenitore. 
La scalabilita' del widget 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 i 
widget incontrati da un estremo all'altro sono legati, se almeno uno 
dei widget e' scalabile, la differenza e' ripartita in parti uguali tra i 
widget scalabili, altrimenti e' ripartita negli offset di aggancio di tutti i 
widget (si modifica la loro spaziatura).
Da quanto detto risulta chiaro che per evitare il resize o il 
riposizionamento dei widget in una direzione, e' sufficiente rimuovere 
un legame nella stessa direzione tra  un widget e uno adiacente o il 
contenitore.
L'esempio che segue mostra la politica di arrangiamento dei figli da 
parte del widget 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_LARGE); 
	pb = MCreatePButton(cform, "Quit", TIMES_LARGE);
	MObjectSetAttachment(pb, True, True, False, True);
	MPButtonSetCallback(pb, ExitCB, NULL);
	MShellRealize(shell);

	MMainLoop();
}

I widget aventi 'shell' come parent, sono in realta' contenuti nella 
FORM (che e' a righe) figlia della stessa 'shell', pertanto il widget 
'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 dal widget 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 widget 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 del widget '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 widget 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 widget ad una particolare pagina va fatta 
selezionando, prima di creare il widget, 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 dai widget 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 widget 
composto dal momento che possiede due Widget '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 Widget Composti ritornano 
l'identificatore del widget Terminale principale. Per mantenere 
coerente la API in operazioni come le variazioni degli agganci, tutti i 
riferimenti ad Widget Composti sono automaticamente rediretti al 
widget capostipite nella gerachia del widget Composto.
L'esempio che segue usa una scrolled FORM per incapsulare la 
stessa gerarchia di Widget 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_LARGE); 
	pb = MCreatePButton(cform, "Quit", TIMES_LARGE);
	MObjectSetAttachment(pb, True, True, False, True);
	MPButtonSetCallback(pb, ExitCB, NULL);

	MShellRealize(shell);

	MMainLoop();
}

Il widget '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 Widget 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 il widget 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 il widget, 
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_LARGE);
		MRButtonSetCallback(rb, RadioButtonCB, (void *)ii);
	}
	label = MCreateLabel(rform, "", DEFAULT_FONT);
	MRButtonActivate(rb);

	pb = MCreatePButton(shell, "Quit", TIMES_LARGE);
	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' il widget 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 il widget 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_LARGE);
		MTButtonSetCallback(tb, ToggleButtonCB, (void *)ii);
	}
	label = MCreateLabel(rform, "", DEFAULT_FONT); 
	MTButtonActivate(tb);


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

	MShellRealize(shell);

	MMainLoop();
}

Il prototipo della callback per il TBUTTON e' identico a quello del 
RBUTTON.
L'attivazione dell'ultimo widget 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 dei 
widget, infatti, sono valutate quando viene realizzata la SHELL.


EDIT

E' il widget 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 il widget 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 
del widget, 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_LARGE);
	MPButtonSetCallback(pb, ExitCB, NULL);

	MShellRealize(shell);

	MMainLoop();
}

L'esempio 8 mostra l'uso delle cinque callback previste per l'EDIT, su 
altrettanti Widget.
	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 widget 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 il widget 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 del 
widget, 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 il widget 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 nelil widget 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 dal widget 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 swidget 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 del widget 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 widget, 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 widget EDIT e' impostato il filtro in modo da gestire solo 
cifre. E' impostata inoltre la callback di validate e i colori che deve 
assumere il widget 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 il widget 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 widget presente nella libreria principalmente 
per l'uso con i widget 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 
widget 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.
Il widget 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 dal widget stesso attraverso la sua callback.


STATUSBAR

E' un widget 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 Widget 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 il widget 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_LARGE);
	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 del widget 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' del widget 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 del widget STATUSBAR.


MENU

Il menu della libreria e’ abbastanza simile come look & feel a quello 
delle GUI standard. Esso consiste in una finestra temporanea 
contenente delle righe di testo (voci di menu) selezionabili.
Il modo piu’ semplice di usare i menu e’ quello di inserirli in una 
MENUBAR : un widget Terminale un po' particolare. Infatti, 
sebbene vista dal contenitore si comporti come un Terminale 
comune,  possiede caratteristiche da Manager per gestire le finestre 
di MENU. La MENUBAR non e' trattata in modo speciale dai 
widget Manager nel senso che non esiste nessun widget che preveda 
la MENUBAR come risorsa predefinita o impostabile. Cio' offre la 
liberta' di porre la MENUBAR 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 
crearla come primo widget Terminale, possibilmente figlia della 
FORM principale (della SHELL).
Nel seguito col termine 'titolo' si intendera' il testo della MENUBAR 
a cui e' associata una tendina (MENU), col termine 'item' invece si 
intendera' il testo della tendina che puo' essere selezionato.
La costruzione del menu  a tendina inizia con la creazione della 
MENUBAR, quindi con l'aggiunta dei titoli e la creazione dei MENU 
releativi e infine con l'aggiunta degli item ad ogni MENU. 
L'associazione tra MENUBAR e MENU, fatta alla creazione di 
quest'ultimo, non stabilisce una relazione di contenimento come 
avviene con le altre funzioni di creazione, infatti il widget MENU e' 
contenuto in una SHELL indipendente. Stabilisce piuttosto una 
relazione tra la selezione del titolo di MENUBAR e l'apertura della 
tendina relativa. Ad ogni item puo' essere assegnata una callback, un 
tasto acceleratore di selezione, oppure l'apertura di un'altro MENU.
Gli item possono anche resi insensibili oppure avere comportamento 
e aspetto simile ai pulsanti toggle e radio.

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

#include "mgui.h"

void ExitCB(MENU_ITEM item, MVALUE status, void *b)
{
	MOBJECT obj;

	obj = MMenuItemGetMenu(item);
	obj = MObjectShell(obj);
	MShellUnrealize(obj);
	MShellDestroy(obj);
	MGUITerm();
	exit(0);
}

void ToggleItemCB(MENU_ITEM item, MVALUE status, void *udata)
{
	char str[128];

	sprintf(str, "The selected menu item\nhas been %s",
		(status ? "Activated" : "Deactivated"));
	MMessageDialog("", str, "Ok", NULL);
}

void RadioItemCB(MENU_ITEM item, MVALUE status, void *udata)
{
	char str[128];

	if (!status)
		return;
	sprintf(str, "Menu item <%lx> selected", item);
	MMessageDialog("", str, "Ok", NULL);
}

void MGUIMain(int argc, char **argv)
{
	MOBJECT shell, menubar, menu, submenu;
	MENU_ITEM pd_item_id;

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

	menubar = MCreateMenuBar(shell, HELV_L_FONT);
	MMenuBarSetAcc(menubar, F10);
	menu = MCreateMenu(menubar, "File");
	MMenuAddItem(menu, "Quit", ExitCB, NULL);
	menu = MCreateMenu(menubar, "Submenu");
	MMenuAddToggleItem(menu, "Item 1", ToggleItemCB, NULL);
	MMenuAddToggleItem(menu, "Item 2", ToggleItemCB, NULL);
	pd_item_id = MMenuAddItem(menu, "Item 3", NULL, NULL);
	MMenuAddToggleItem(menu, "Item 4", ToggleItemCB, NULL);

	submenu = MCreateSubMenu(pd_item_id);
	MMenuAddRadioItem(submenu, "RadioItem 3-1", RadioItemCB, NULL);
	MMenuAddRadioItem(submenu, "RadioItem 3-2", RadioItemCB, NULL);
	MMenuAddRadioItem(submenu, "RadioItem 3-3", RadioItemCB, NULL);
	MMenuAddSeparator(submenu);
	MMenuAddRadioItem(submenu, "RadioItem 3-4", RadioItemCB, NULL);
	MMenuAddRadioItem(submenu, "RadioItem 3-5", RadioItemCB, NULL);

	MShellRealize(shell);

	MMainLoop();
}

La callback ExitCB() e’ legata alla selezione dell’item ‘Exit’ del 
menu ‘File’ e causa la terminazione del programma. La callback 
ToggleItemCB() e’ impostata su item di tipo toggle, essa visualizza 
lo stato di attivazione dell’item selezionato. 
La callback RadioItemCB() viene invocata dagli item di tipo radio 
ogni qualvolta cambia il loro stato. Ad ogni selezione di un radio 
item vengono chiamate la callback dell’item che perde 
automaticamente l’attivazione, e successivamente quella dell’item 
attivato. Nel caso dell’esempio, viene visualizzato un messaggio solo 
per l’item attivato.
E' possibile ottenere lo stato di check di un item (sia radio che toggle) 
attraverso la funzione MMenuItemGetCheckStatus(), speficando 
come parametro l'identificatore dell'item stesso.
Lo stato di attivazione puo’ essere impostato dall’esterno attraverso 
le funzioni MMenuItemSetCheckStatus() e 
MMenuItemToggleCheckStatus(), in questo caso, al contrario di 
quanto accade per i pulsanti, non vengono chiamate le callback.


DRAWAREA

La libreria possiede delle primitive di disegno che operano sui 
widget. Sebbene sia possibile disegnare su una LABEL o un EDIT, 
cio' ha ovviamente poco senso ed e' anzi decisamente da evitare. Allo 
scopo esiste il widget 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, il widget 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_LARGE);

	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_LARGE);
	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 del widget deve essere ridisegnata. L'area di 'clip' notificata 
dal widget 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 nelil widget. Da notare che il disegno di tali 
linee, non essendo predisposto nella callback di Expose, si perde 
forzando il redraw del widget (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 widget di tipo PIXMAP, ma la API della libreria 
dispone di 
una funzione che crea un widget 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 widget 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_LARGE);
	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 il 
widget 
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 
al widget, quando viene distrutto, l'onere della chiamata alla 
MFreeImage().
Il primo metodo risulta conveniente solo quando una stessa pixmap e' 
associata a piu' Widget, 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

Il widget SBLIST possiede diverse caratterizzazioni, ognuna delle 
quali si comporta esternamente come un widget 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 widget 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 100 righe per un 
massimo su file di 25600 linee. L'interfaccia con l'applicativo richiede 
solamente la specificazione del nome del file, quando il widget 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 il 
widget 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_LARGE);
	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 dal widget 
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 del widget 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 dal widget e' una piccola parte rispetto a quella virtuale. 
Ovviamente e' a carico dell'applicativo la fornitura di nuovi item, man 
mano che il widget li richiede.
Il metodo di ricerca interno si puo' ancora utilizzare, purche' 
l'applicativo fornisca una funzione che collabora con il widget 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 dal widget, 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_LARGE);
	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 al widget gli item. 
Allo scopo usa la struttura SBL_NEW_BLOCK (valida anche per il 
widget 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 del widget, 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 il widget possa correttamente adeguarsi.
La callback SkipSearchCB() collabora con il widget 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 al widget se l'item ricercato esiste (-1 
in caso contrario). Nel caso di widget 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 widget 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 widget 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 del widget si realizza attraverso la 
API gia' descritta per i widget 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_MEDIUM", 0L);
	MSListAddItem(list, "HELV_LARGE", 0L);
	MSListAddItem(list, "TIMES_MEDIUM", 0L);
	MSListAddItem(list, "TIMES_LARGE", 0L);

	pbutton = MCreatePButton(shell, "Chiudi", TIMES_LARGE);
	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().

