/*************************************************************************
 *              PROG.C
 *
 * Programma di esempio sull' uso di Intuition, a corredo
 * del corso di programmazione in C su Amiga. Nell' articolo
 * "corso di c, parte 4" e' contenuta una dettagliata
 * discussione di questo programma e delle funzioni
 * utilizzate.
 *
 *************************************************************************/

/*
 Un tipico programma C inizia con l'inclusione di alcuni
 files di supporto che contengono la definizione di
 variabili, strutture, macro & c.  Questo serve a rendere la
 programmazione piu' semplice. Non e' necessario per il
 momento andare a vedere cosa e' definito in questi files,
 ma e' senz'altro istruttivo. Piu' avanti diventeranno una
 necessaria e importante appendice alla documentazione sul
 sistema operativo.  Nel nostro caso includiamo il file di
 definizioni per Intuition, il quale a sua volta va a
 richiamare 'a cascata' molti altri include files di
 sistema, per librerie e risorse a piu' basso livello.
 */

#include <intuition/intuition.h>

/*
 Segue la dichiarazione e la descrizione delle strutture
 usate nel programma.  I prototipi di queste strutture sono
 definiti negli include files di sistema.  Tutti i dati
 rappresentati a lettere maiuscole, come WINDOWSIZING,
 ACTIVATE, CUSTOMSCREEN, sono delle costanti numeriche
 definite anch'esse nei vari include files, nel nostro caso
 intuition/intuition.h e intuition/screens.h.  Notate che
 gli elementi di una struttura sono tutti separati da
 virgole, che non ci sono virgole dopo l'ultimo elemento e
 che la struttura e' chiusa da una parentesi graffa e da un
 punto e virgola, al contrario delle funzioni, che non hanno
 il punto e virgola dopo la parentesi.  Le dichiarazioni
 delle strutture usate vanno fatte all'esterno di ogni
 funzione dell programma, compreso main().
 */

/*
 Questa struttura contiene la descrizione del set di
 caratteri da usare nel nostro schermo. Useremo lo standard
 Topaz da 80 caratteri per linea presente in ROM. il
 prototipo e' definito in graphics/text.h
 */

struct TextAttr TOPAZ80 = {
    "topaz.font",       /* Nome del set di caratteri */
    8,                  /* Altezza dei caratteri */
    0,                  /* Stile (0 = nessuno) */
    0                   /* Flags (ROM/DISK: 0=ROM) */
    };

/*
 Questa e' la descrizione del nuovo schermo che vogliamo
 aprire. Il suo prototipo e' in intuition/screens.h, che
 viene automaticamente incluso con l'inclusione di
 intuition/intuition.h
 */

struct NewScreen nuovo_schermo = {
    0,0,            /* Origine X,Y dello schermo sul display */
    640,200,        /* Larghezza, altezza dello schermo */
    2,              /* Numero di BitPlanes dello schermo. Questo valore
                    determina il numero di colori che e' possibile
                    visualizzare. Nel nostro caso 4 colori. */
    0,1,            /* Registri colore usati per i dettagli ed i
                    blocchi, qui NERO, BIANCO */
    HIRES,          /* Questo flag (alta risoluzione) e' necessario nel
                    nostro caso, perche' uno schermo largo 640 punti
                    puo' solo essere in alta risoluzione. */
    CUSTOMSCREEN,   /* Non e' uno schermo standard di sistema, quindi
                    va specificato CUSTOM.. L'unico schermo standard
                    e' per ora quello del WorkBench */
    &TOPAZ80,       /* Puntatore alla struttura TextAttr definita
                    piu' sopra, indica che questo schermo usera'
                    il normale carattere Topaz 8 */
    "Schermo!",     /* Titolo dello schermo, compare nella barra */
    NULL,           /* Puntatore al primo custom gadget, non usati */
    NULL            /* Puntatore ad una bitmap. Non abbiamo una nostra
                    bitmap gia' pronta, lasciamo 0, cosi' intuition
                    alloca e disalloca la memoria per questo schermo
                    da sola. */
            
    };          /* Fine della descrizione della struct NewScreen */


/*
 Descrizione di una struttura di tipo NewWindow. Contiene i
 parametri della finestra desiderata. La struttura NewWindow
 e' definita nel file intuition/intuition.h
 */

struct NewWindow nuova_finestra = {

    155,63,         /* Origine XY della finestra rispetto all'angolo
                    superiore sinistro dello schermo */
    353,104,        /* Larghezza e altezza della finestra */
    0,1,            /* Penne (registri colore) da usare per dettagli
                    e blocchi. Qui NERO,BIANCO */
    CLOSEWINDOW,    /* Eventi che si desidera ricevere dalla finestra,
                    nel nostro caso solo la chiusura. */

    WINDOWSIZING+WINDOWDRAG+WINDOWDEPTH+WINDOWCLOSE+ACTIVATE,
                /* Flags per i gadgets standard che vogliamo avere:
                ridimensionamento,spostamento,priorita' su altre
                finestre, chiusura. Inoltre vogliamo che questa
                finestra divenga attiva dopo l'apertura. */

    NULL,       /* Puntatore al primo gadget 'custom', per noi 0 */
    NULL,       /* Puntatore all'immagine da usare per segnare
                le opzioni attive nei menu (in sostituzione della
                specie di V che e' usata di solito */
    "Finestra!",    /* Nome che compare nella barra superiore della f. */
    NULL,       /* Puntatore allo schermo. Se la finestra non deve
                comparire sullo schermo del WorkBench, qui va
                messo il puntatore allo schermo voluto. Questo e'
                ovviamente possibile solo dopo che lo schermo e'
                stato aperto con OpenScreen() */
    NULL,       /* Eventuale bitmap 'fuori schermo' */
    5,5,        /* Minima larghezza e altezza, non deve essere troppo
                piccola, o si rischia di 'perdere' la finestra */
    640,200,    /* Massima larghezza e altezza, nel nostro caso l'
                abbiamo scelta coincidente con le dimensioni dello
                schermo. */
    CUSTOMSCREEN    /* Tipo di schermo su cui andra'. Nel nostro caso e'
                    uno schermo custom, ma poteva essere WBENCHSCREEN */
    };      /* Fine descrizione della struttura NewWindow */


/*
 Dichiarazione delle variabili 'puntatore a libreria'. I
 nomi di questi puntatori sono fissi. Devono essere
 dichiarati cosi' in maniera che le routines di interfaccia
 verso il S.O. le trovino con il nome che si aspettano.
 Vanno lette in questa maniera:

 IntuitionBase e' un puntatore ad una struttura di tipo
 IntuitionBase; GfxBase e' un puntatore ad una struttura di
 tipo GfxBase

 Queste strutture, 'basi delle librerie' sono definite nei
 rispettivi include files e non ci interessano. La
 confusione generata dall' uso degli stessi nomi per i
 puntatori e per le strutture puntate e' solo apparente, ed
 il compilatore non ha nessun problema a gestirla
 correttamente.
 */

struct IntuitionBase *IntuitionBase;
struct GfxBase  *GfxBase;

/*
 Dichiarazione del tipo di valori ritornati dalle funzioni
 usate (solo quelle che ritornano valori!). Non ha nessun
 significato pratico, ma serve per prendere una buona
 abitudine. Per ragioni di portabilita' tra compilatori e
 macchine diverse si dovrebbe sempre dichiarare cosa
 ritornano le funzioni. Se scriveste programmi in C 'UNIX-
 like' portabili sareste costretti a farlo, pena brutte
 sorprese. Inoltre, nel caso che si tratti di funzioni di
 libreria scritte da voi, dichiarare esplicitamente i valori
 di ritorno serve a migliorare la documentazione e la
 leggibilita' di un programma.

 Queste dichiarazioni si leggono:
 OpenScreen() ritorna un puntatore ad una struttura di tipo
     Screen, e NON e' definita in questo file, ma altrove
     (extern);

 OpenWindow() ritorna un puntatore ad una struttura di tipo
    Window, anch' essa e' definita altrove;

 La parola extern indica appunto che queste funzioni sono
 definite, scritte e compilate da qualche altra parte.
 Potrebbero essere in un file differente oppure, come in
 questo caso, essere delle funzioni di libreria. 

 Non possiamo fare una dichiarazione analoga per la funzione
 OpenLibrary() in quanto i puntatori ritornati, a secondo
 della libreria aperta, puntano a strutture di nome e
 architettura differente. Per il compilatore C e' come se
 OpenLibrary() restituisse ogni volta oggetti di tipo
 completamente differente. In sostituzione della
 dichiarazione globale, ad ogni chiamata convertiremo
 esplicitamente il tipo di valore che viene restituito. 
 Anche questa e' una operazione che non comporta
 trasformazione del valore restituito, e' solo 'cosmetica'.
 Fa felice il compilatore e vi fa acquistare tante sane
 abitudini..
 */

extern struct Screen *OpenScreen();
extern struct Window *OpenWindow();


/*
 Dichiarazione delle variabili che usiamo per immagazzinare
 i puntatori ritornati dall' apertura dello schermo e della
 finestra. Non sarebbe necessario nel nostro programma
 definire queste variabili globali, come le precedenti.
 Potremmo dichiararle all'interno di main(), ma al crescere
 delle dimensioni del programma e del numero di funzioni
 coinvolte sarebbe ben presto necessario portarle al di
 fuori, quindi tanto vale farlo adesso.

 Queste dichiarazioni si leggono:
 'schermo' e' un puntatore ad una struttura di tipo Screen;
 'finestra' e' un puntatore ad una struttura di tipo Window;
 'RP' e' un puntatore ad una struttura di tipo RastPort;
 Le definizioni di queste strutture si trovano in
 intuition/intuition.h
 intuition/screens.h e
 graphics/rastport.h
 */

struct Screen *schermo;
struct Window *finestra;
struct RastPort *RP;

/*
 Qui comincia il programma, che nel nostro caso e'
 costituito da una sola funzione, main(). Notate il
 protocollo di test e trattamento di eventuali errori. Ogni
 puntatore di ritorno viene controllato e, in caso di
 nullita' il programma 'salta' con una istruzione goto
 <etichetta> all'etichetta 'pulizia', che segna l'inizio
 della sezione terminale del programma. Qui i puntatori sono
 nuovamente testati, in modo da chiudere SOLO le strutture
 che veramente sono state aperte. Nei linguaggi strutturati
 come il C l' istruzione goto non dovrebbe essere mai usata.
 A dire il vero, in un programma C 'standard' ben scritto
 non ve ne dovrebbe proprio essere la necessita'. Su Amiga i
 programmi hanno pero' questa particolare struttura APERTURA
 RISORSE, USO, CHIUSURA RISORSE, particolarmente intensa e
 delicata.  L'uso del goto per risolvere la serie dei test
 e l'uscita pulita del programma e' allora l'unica
 soluzione non macchinosa e leggibile, tanto da essere
 consigliata dagli stessi progettisti del S.O. e convalidata
 dall'uso pressoche' generale da parte dei programmatori. 
 */

main () {

    /*
     Apertura delle due librerie usate. Qualsiasi versione
     va bene.  Notare come il valore ritornato viene testato
     per rilevare errori.  Notare il doppio segno di uguale
     nei test! Notare la conversione esplicita del valore
     restituito. Sottolineo che il valore non viene
     alterato, cambia solo la 'opinione' del compilatore su
     che cosa dovrebbe contenere.  L'espressione del tipo:

     (struct IntuitionBase *)

     significa infatti 'tratta come puntatore ad una
     struttura di tipo IntuitionBase il seguente valore..'.
     Queste espressioni si chiamano 'CAST', e sono molto
     diffuse. Ne incontreremo spesso.
     */

    IntuitionBase = (struct IntuitionBase *) OpenLibrary ("intuition.library",0);
    if (IntuitionBase == NULL) goto pulizia;

    GfxBase = (struct GfxBase *) OpenLibrary ("graphics.library",0);
    if (GfxBase == NULL) goto pulizia;


    /*
     Apertura dello schermo. Notare le espressioni
     &nuovo_schermo &nuova_finestra

     'nuovo_schermo', 'nuova_finestra' NON sono dei
     puntatori, ma i nomi delle due strutture NEL LORO
     COMPLESSO. L'& davanti al nome sta per 'indirizzo
     dell'oggetto', e fa si' che ad OpenScreen e ad
     OpenWindow siano passati dei puntatori contenenti
     l'indirizzo delle strutture definite in testa al nostro
     programma.
     */

    schermo = OpenScreen (&nuovo_schermo);
    if (schermo == NULL) goto pulizia;

    /*
     Se e' andato tutto bene dobbiamo mettere nella
     struttura nuova_finestra il puntatore allo schermo su
     cui deve comparire.  Notate l'espressione
     nuova_finestra.Screen, con cui si indirizza UN
     COMPONENTE di una struttura. Sappiamo che il campo dove
     mettere il puntatore si chiama
     nome_mia_struttura.Screen perche' la nostra struttura
     nuova_finestra e' di tipo NewWindow ed abbiamo
     sbirciato la definizione della struttura NewWindow in
     intuition/intuition.h.
     */

    nuova_finestra.Screen = schermo;

    /*
     Possiamo ora aprire la finestra, osservando il consueto
     protocollo
     */

    finestra = OpenWindow (&nuova_finestra);
    if (finestra == NULL) goto pulizia;

    /*
     Poof! Se siamo arrivati fino a qui abbiamo a
     disposizione uno schermo ed una finestra nuovi di pacca
     su cui lavorare.  Le routines della graphic library che
     andiamo ad usare vogliono un puntatore alla RastPort
     (struttura per il controllo della grafica) associata
     alla finestra che abbiamo aperto. Questo puntatore e'
     leggibile nella struttura di tipo Window ritornata da
     OpenWindow(), nel campo RPort. Anche questo lo sappiamo
     per aver letto il manuale di Intuition e sbirciato in
     intuition/intuition.h.

     Attenzione! Anche poche righe fa abbiamo letto un campo
     di una struttura, ma l'operazione che faremo ora ha una
     sintassi diversa.  Infatti nel caso visto qui sopra
     avevamo TUTTA la struttura nuova_finestra sottomano,
     mentre ora disponiamo solo di un PUNTATORE nella
     variabile 'finestra' alla stuttura di tipo Window. Nel
     primo caso si usava un punto, nel secondo la
     freccettina 'costruita' con il segno di 'meno' e
     'maggiore'.

     In sintesi ecco le due sintassi a confronto:
     Ho la STRUTTURA:   nome_struttura . nome_campo
     Ho un PUNTATORE:   puntatore_a_struttura -> nome_campo 
     */

    RP = finestra->RPort;

    /*
     Scegliamo una 'penna' bianca, registro colore 1 nella
     palette standard usata da Intuition quando apre uno
     schermo.
     */

    SetAPen (RP,1);

    /*
     Ora scriviamo sulla finestra un messaggio di tre righe.
     Prima di ogni messaggio spostiamo la 'penna' con una
     istruzione Move (RP,x,y), quindi scriviamo il testo del
     messaggio con una chiamata alla funzione Text
     (RP,"testo",lunghezza_testo).
     */

    Move (RP,52,17);
    Text (RP,"Questa e' una nuova finestra",28);

    Move (RP,45,28);
    Text (RP,"Si trova su uno schermo 640x200",31);

    Move (RP,30,57);
    Text (RP,"Clicka sul Close Gadget per terminare",37);


    /*
     A questo punto non ci resta che attendere che l'utente
     clicki sul gadget di chiusura della finestra. Questo
     evento fa 'alzare' un flag nella variabile di attesa
     segnali associata al nostro task. Questa chiamata a
     Wait attende che si alzi il bit associato alla nostra
     finestra. Scoprire quale e' questo bit comporta
     pasticciare con una catena di strutture. Non serve
     capire esattamente come funziona, per il momento.  In
     questo esempio non ci preoccupiamo di controllare che
     tipo di evento e' avvenuto nella nostra finestra,
     perche' se andate a controllare nella definizione della
     struttura NewWindow, all'inizio del programma, vedrete
     che abbiamo chiesto ad Intuition di essere avvisati
     solo quando viene clickato il gadget di chiusura.
     */

    Wait ( 1 << finestra->UserPort->mp_SigBit);

    /*
     Siamo alla fine. Qui il programma fa pulizia di tutto
     quello che e' stato aperto o allocato, e lo chiude o lo
     restituisce al sistema. 'pulizia:' non e' un'istruzione
     o una funzione, ma solo un' etichetta che segna il
     punto del programma dove vogliamo saltare con i goto
     nel caso si siano verificati degli errori.
     */

pulizia:
    /*
     Notate che clickare sul gadget di chiusura non provoca
     direttamente la chiusura della finestra, ma solo la
     segnalazione del fatto al programma, che ha cosi' modo,
     se lo desidera, di fare un po' di ordine prima di
     chiudere esplicitamente la finestra.
     */

    if (finestra != NULL)   CloseWindow (finestra);
    if (schermo != NULL)    CloseScreen (schermo);
    if (GfxBase != NULL)    CloseLibrary (GfxBase);
    if (IntuitionBase != NULL) CloseLibrary (IntuitionBase);

    /*
     La funzione exit() fa terminare il programma ritornando
     il valore tra parentesi al sistema operativo. Sarebbe
     necessaria nel caso volessimo ritornare dei codici di
     errore. Non metterla e' esattamente come ritornare 0 al
     sistema (condizione 'nessun errore') Qui e' stata
     inclusa solo per ragioni di folklore. Si tratta dell'
     unica funzione 'standard UNIX-like' che abbiamo
     utilizzato in tutto il programma!
     */

    exit (0);

    } /* fine della definizione di main() */
