Corso di AmigaOS

Torna all'elenco delle lezioniPer dubbi, consigli o richieste, potete mandare un'e-mail ad Andrea Carolfi.
Ringraziamo Amiga Transactor Mailing List per questo tangibile contributo!


Intuition (Decima lezione)

La volta scorsa vi avevo lasciato con un piccolo stralcio di codice, che apriva uno schermo simile a quello del Workbench e una finestra su di esso. Però avere una finestra vuota, non è molto utile. Ecco perchè in questa lezione cominceremo a vedere gli altri elementi grafici di Intuition, in modo che sia possibile creare una finestra un pò meno vuota.

Bordi e Bottoni

Intuition, fin dalle prime versioni, ha sempre permesso al programmatore di creare le proprie interfaccie mettendo a disposizione solo le primitive minime necessarie ed alcune strutture da inizializzare nel modo opportuno. In questo modo però, da una parte il programmatore poteva esprimere tutta la sua creatività in una interfaccia magari di dubbio gusto, dall'altra l'utente si ritrovava innumerevoli interfaccie senza niente in comune o quasi. Infatti, a partire dalla versione 2.0 del SO, con l'introduzione della gadtools.library, la Commodore pose delle direttive (ovviamente prese sotto gamba) per la realizzazione di interfaccie grafiche "standard". Di fatto, la gadtools.library mette a disposizione alcune nuove strutture e procedure, fatte a posta per creare in maniera più intuitiva gadget, bordi e menù; anche perchè le vecchie strutture di fatto erano poco intuitive e per creare anche un solo bottone era necessario stendere decine di righe di codice di inizializzazione prima di vedere comparire il bottone voluto (come vedremo infatti nel sorgente di esempio).
Una volta capito il funzionamento di questa libreria, utilizzare librerie più evolute (quali MUI), risulterà ancor più facile. E' comunque utile sapere come alla base viene creato un bottone, anche perchè potrebbe essere, in alcuni casi, più facile e/o meno dispendioso usare le vecchie routine di Intuition piuttosto di pesanti librerie.

Bordi

/* ======================================================================== */
/* === Border ============================================================= */
/* ======================================================================== */
/* Data type Border, used for drawing a series of lines which is intended for
 *  use as a border drawing, but which may, in fact, be used to render any
 *  arbitrary vector shape.
 *  The routine DrawBorder sets up the RastPort with the appropriate
 *  variables, then does a Move to the first coordinate, then does Draws
 *  to the subsequent coordinates.
 *  After all the Draws are done, if NextBorder is non-zero we call DrawBorder
 *  on NextBorder
 */
struct Border
{
         WORD LeftEdge, TopEdge;   /* initial offsets from the origin */
         UBYTE FrontPen, BackPen;  /* pens numbers for rendering */
         UBYTE DrawMode;     /* mode for rendering */
         BYTE Count;         /* number of XY pairs */
         WORD *XY;        /* vector coordinate pairs rel to LeftTop */
         struct Border *NextBorder;   /* pointer to any other Border too */
};

Questa struttura, permette di disegnare un bordo di qualsiasi forma e di un qualsiasi colore presente nella palette dello schermo utilizzato. Nel listato di esempio vedremo un esempio di inizializzazione.
Può essere utile, anche se laborioso, per disegnare bordi irregolari magari con segmenti di diverso colore (utilizzando la procedura DrawBorder). Anche se a mio avviso in questo caso le primitive Draw(), Move() e SetAPen() della graphics (che vedremo in seguito, non dubitate) fanno più al caso nostro. Mentre è estremamente utile per crare uno o più gadget con forme irregolari.
Per disegnare una struttura correttamente inizializzata basta usare la procedura:

void DrawBorder( struct RastPort *rp, struct Border *border, long leftOffset, long topOffset );

A partire con la versione 2.0 del SO, se vogliamo disegnare una cornice in "3D" (ovvero con il bordo sinistro e superiore bianchi e destro ed inferiore neri o viceversa) possiamo, invece di dichiarare due strutture Border e relativi array di coordinate (che sono anche difficili da gestire nel caso di eventuale finestra ridimensionabile o font-sensitive), utilizzare la procedura:

void DrawBevelBoxA( struct RastPort *rport, long left, long top, long width,
        long height, struct TagItem *taglist );
void DrawBevelBox( struct RastPort *rport, long left, long top, long width,
        long height, Tag tag1, ... );

che vedremo nel dettaglio successivamente quando tratteremo la gadtools.library.
Va detto che tra i tag obbligatori DEVE essere specificato GT_VisualInfo con il valore del puntatore ritornato dalla procedura GetVisualInfoA().

Bottoni

/* ======================================================================== */
/* === Gadget ============================================================= */
/* ======================================================================== */
struct Gadget
{
         struct Gadget *NextGadget;   /* next gadget in the list */

         WORD LeftEdge, TopEdge;   /* "hit box" of gadget */
         WORD Width, Height;    /* "hit box" of gadget */

         UWORD Flags;     /* see below for list of defines */

         UWORD Activation;      /* see below for list of defines */

         UWORD GadgetType;      /* see below for defines */

         /* appliprog can specify that the Gadget be rendered as either as Border
          * or an Image.  This variable points to which (or equals NULL if there's
          * nothing to be rendered about this Gadget)
          */
         APTR GadgetRender;

         /* appliprog can specify "highlighted" imagery rather than algorithmic
          * this can point to either Border or Image data
          */
         APTR SelectRender;

         struct IntuiText *GadgetText;   /* text for this gadget */

         /* MutualExclude, never implemented, is now declared obsolete.
          * There are published examples of implementing a more general
          * and practical exclusion in your applications.
          *
          * Starting with V36, this field is used to point to a hook
          * for a custom gadget.
          *
          * Programs using this field for their own processing will
          * continue to work, as long as they don't try the
          * trick with custom gadgets.
          */
         LONG MutualExclude;  /* obsolete */

         /* pointer to a structure of special data required by Proportional,
          * String and Integer Gadgets
          */
         APTR SpecialInfo;

         UWORD GadgetID;  /* user-definable ID field */
         APTR UserData;   /* ptr to general purpose User data (ignored by In) */
};

Ogni bottone creato, ha una struttura di questo tipo che lo descrive e che il programmatore può modificare in alcune sue parti anche dopo la creazione.
Inoltre, il puntatore SpecialInfo serve per contenere informazioni specifiche per i gadget che lo richiedono. Ad esempio, un gadget stringa avrà linkata da questo puntatore una struttura StringInfo.

Come si può capire dai puntatori GadgetRender e SelectRender, Intuition lascia molta libertà di scelta sull'aspetto esteriore del bottone che può essere un bordo o un'immagine. L'unica limitazione è dovuta dal fatto che Intuition gestisce solo bottoni rettangolari. Quindi è possibile, tramite una struttura Border o un'immagine disegnare un gadget rotondo o magari elissodale, ma Intuition utilizzera i campi LeftEdge, TopEdge, Width e Height per l'attivazione del bottone e non la sagoma dell'immagine. Se però si considera che mediamente l'utente preme il gadget in un "intorno" del suo centro, è possibile ottenere lo stesso l'effetto voluto che però non funzionerà nel 100% dei casi.

Flag per la struttura Gadget

Vediamo adesso come devono essere inizializzati i vari campi.

/* --- Valori per il campo Gadget.Flags --- */
/* Le combinazioni di questi bit descrivono la tecnica di illuminazione usata */
/* per evidenziare un bottone selezionato */
#define GFLG_GADGHCOMP    0x0000  /* Complement the select box */
#define GFLG_GADGHBOX     0x0001  /* Draw a box around the image */
#define GFLG_GADGHIMAGE   0x0002  /* Blast in this alternate image */
#define GFLG_GADGHNONE    0x0003  /* don't highlight */

#define GFLG_GADGIMAGE    0x0004  /* set if GadgetRender and SelectRender
                                        * point to an Image structure, clear
                                        * if they point to Border structures
                                        */

#define GFLG_SELECTED     0x0080  /* you may initialize and look at this   */

/* the GFLG_DISABLED flag is initialized by you and later set by Intuition
 * according to your calls to On/OffGadget().  It specifies whether or not
 * this Gadget is currently disabled from being selected
 */
#define GFLG_DISABLED     0x0100

/* These flags specify the type of text field that Gadget.GadgetText
 * points to.  In all normal (pre-V36) gadgets which you initialize
 * this field should always be zero.  Some types of gadget objects
 * created from classes will use these fields to keep track of
 * types of labels/contents that different from IntuiText, but are
 * stashed in GadgetText.
 */

#define GFLG_LABELITEXT   0x0000  /* GadgetText points to IntuiText  */
#define GFLG_LABELSTRING  0x1000  /* GadgetText points to (UBYTE *) */
#define GFLG_LABELIMAGE   0x2000  /* GadgetText points to Image (object)   */

/* New for V39:  If set, this bit means that the Gadget is actually
 * a struct ExtGadget, with new fields and flags.  All V39 boopsi
 * gadgets are ExtGadgets.  Never ever attempt to read the extended
 * fields of a gadget if this flag is not set.
 */
#define GFLG_EXTENDED     0x8000  /* Gadget is extended */


/* --- Valori per il campo Gadget.Activation --- */
/* Set GACT_RELVERIFY if you want to verify that the pointer was still over
 * the gadget when the select button was released.  Will cause
 * an IDCMP_GADGETUP message to be sent if so.
 */
#define GACT_RELVERIFY    0x0001

/* the flag GACT_IMMEDIATE, when set, informs the caller that the gadget
 *  was activated when it was activated.  This flag works in conjunction with
 *  the GACT_RELVERIFY flag
 */
#define GACT_IMMEDIATE    0x0002

/* --- Valori per il campo Gadget.GadgetType --- */
#define GTYP_BOOLGADGET    0x0001
#define GTYP_GADGET0002    0x0002
#define GTYP_PROPGADGET    0x0003
#define GTYP_STRGADGET     0x0004
#define GTYP_CUSTOMGADGET  0x0005

Come si può vedere i valori sono tanti (e non sono nemmeno tutti) e può essere laborioso creare anche solo un paio di gadget. Infatti la gadtools.library ci mette a disposizione la funzione:

struct Gadget *CreateGadgetA( unsigned long kind, struct Gadget *gad,
        struct NewGadget *ng, struct TagItem *taglist );
struct Gadget *CreateGadget( unsigned long kind, struct Gadget *gad,
        struct NewGadget *ng, Tag tag1, ... );

che vedremo nella prossima puntata.

 

Listato di esempio Chiudiamo questa lezione con un piccolo listato di esempio (basato principalmente su quello della volta scorsa). Vedremo passo dopo passo come migliorare la nostra "applicazione" didattica.


#define WIDTH     120
#define HEIGHT    20
#define SHINE     2     // Bianco
#define SHADOW    1     // Nero
#define OFFSET    5

/* Inizializzazione coordinate bordo */
WORD shadowXY[] = {-1,1,-1,HEIGHT - 1,-WIDTH,HEIGHT - 1};
WORD shineXY[] = {0,-1,0,-HEIGHT,WIDTH - 1,-HEIGHT};

/* Inizializzazione struttura Border */
struct Border shadow_normal = {WIDTH,0,SHADOW,0,JAM1,3,shadowXY,NULL};
struct Border shine_normal = {0,HEIGHT,SHINE,0,JAM1,3,shineXY,&shadow_normal};
struct Border shadow_selected = {WIDTH,0,SHINE,0,JAM1,3,shadowXY,NULL};
struct Border shine_selected = {0,HEIGHT,SHADOW,0,JAM1,3,shineXY,&shadow_selected};

/* Inizializzazione struttura IntuiText */
struct IntuiText testo = {SHADOW,0,JAM1,NULL,NULL,NULL,"Premi quì",NULL};

/* Inizializzazione struttura Gadget */
struct Gadget gad = {NULL,OFFSET,OFFSET,WIDTH,HEIGHT,
        GFLG_GADGHIMAGE | GFLG_LABELITEXT,GACT_RELVERIFY | GACT_IMMEDIATE,
        GTYP_BOOLGADGET,&shine_normal,&shine_selected,&testo,NULL,NULL,1,NULL};

void main(void)
{
        struct Screen *scr;
        struct Window *win;
        struct IntuiMessage *msg;
        BOOL finito = FALSE;

        /* Apertura schermo */
        if((scr = OpenScreenTags(NULL,
                SA_LikeWorkbench, TRUE,
                SA_Title,         "Schermo di prova",
                TAG_DONE)))
        {
                /* Aggiusto in base alla dimensione del font */
                testo.LeftEdge = gad.Width / 2 - IntuiTextLength(&testo) / 2;
                testo.TopEdge = gad.Height / 2 - scr -> Font -> ta_YSize / 2;

                /* Apertura finestra */
                if((win = OpenWindowTags(NULL,
                        WA_Top,        scr -> BarHeight + scr -> BarVBorder,
                        WA_Height,     scr -> Height - scr -> BarHeight - scr -> BarVBorder,
                        WA_IDCMP,      IDCMP_CLOSEWINDOW | IDCMP_GADGETUP,
                        WA_Flags,      WFLG_CLOSEGADGET | WFLG_SIZEGADGET |
                                                                WFLG_DRAGBAR | WFLG_DEPTHGADGET,
                        WA_Title,      "Finestra di prova",
                        WA_PubScreen,  scr,
                        TAG_DONE)))
                {
                        /* Aggiusto in base alla dimensione del font */
                        gad.LeftEdge += win -> BorderLeft;
                        gad.TopEdge += win -> BorderTop;

                        /* Più piccola di così no */
                        WindowLimits(win,
                                gad.LeftEdge + gad.Width + win -> BorderRight + OFFSET,
                                gad.TopEdge + gad.Height + win -> BorderBottom + OFFSET,0,0);

                        /* Aggiungo il bottone alla finestra */
                        AddGadget(win,&gad,0);

                        /* Rinfresco i bottoni della finestra */
                        RefreshGadgets(&gad,win,NULL);

                        while(!finito)
                        {
                                WaitPort(win -> UserPort);
                                while((msg = (struct IntuiMessage *)GetMsg(win -> UserPort)))
                                {
                                        switch(msg -> Class)
                                        {
                                                case IDCMP_CLOSEWINDOW:
                                                        finito = TRUE;
                                                break;
                                                case IDCMP_GADGETUP:
                                                        Printf("Bottone premuto!\n");
                                                break;
                                        }
                                        ReplyMsg((struct Message *)msg);
                                }
                        }
                        RemoveGadget(win,&gad);
                        CloseWindow(win);
                }
                CloseScreen(scr);
        }
}

Vediamo le procedure/funzioni che non ho ancora spiegato:

UWORD AddGadget( struct Window *window, struct Gadget *gadget,
        unsigned long position );

void RefreshGadgets( struct Gadget *gadgets, struct Window *window,
        struct Requester *requester );

UWORD RemoveGadget( struct Window *window, struct Gadget *gadget );

BOOL WindowLimits( struct Window *window, long widthMin, long heightMin,
        unsigned long widthMax, unsigned long heightMax );

LONG IntuiTextLength( struct IntuiText *iText );

Le funzioni AddGadget/RemoveGadget servono per collegare o scollegare dalla finestra un bottone. Infatti è possibile creare un certo numero di bottoni e decidere poi quali utilizzare effettivamente in determinati momenti. Una volta aggiunto o rimosso un bottone perchè il risultato sia visibile a schermo è necessario chiamare la procedura RefreshGadgets (mettendo a NULL il campo requester che non serve nel nostro caso). Ovviamente, se dobbiamo agire su un discreto numero di bottoni, allora è meglio collegarli tra di loro ed usare le funzioni:

void RefreshGList( struct Gadget *gadgets, struct Window *window,
        struct Requester *requester, long numGad );

UWORD AddGList( struct Window *window, struct Gadget *gadget,
        unsigned long position, long numGad, struct Requester *requester );

UWORD RemoveGList( struct Window *remPtr, struct Gadget *gadget,
        long numGad );
che lavorano su liste di Gadget. Queste funz/proc lavorano a partire dal
bottone 'gadget' per un numero di bottoni pari a 'numGad'. Quindi se nella
lista sono presenti quindici bottoni, ma vogliamo collegare solo i primi
cinque faremo:
AddGList(win,, /* di solito 0 */,5,NULL);
RefreshGList(,win,NULL,5);

ed otterremo l'effetto desiderato.
Inoltre, la funzione RefreshGList è più veloce della RefreshGadgets nel caso si debba, in una lista numerosa, rinfrescare solo un determinato bottone, dato che RefreshGadgets ridisegna TUTTI i bottoni presenti nella lista.

Per finire, la funzione WindowLimits serve per impostare i valori di dimensione minima e massima per le finestre ridimensionabili (specificando come parametro il valore 0, rimangono le impostazioni precedenti); mentre la funzione IntuiTextLength serve per calcolare la lunghezza in pixel di un determinato testo utilizzando il carattere specificato nella struttura IntuiText. Se questo puntatore è NULL, come nel nostro caso, viene usato il carattere predefinito di sistema specificato dall'utente con il programma di preferenze 'Font'.

Lezione precedente Indice delle lezioni Lezione successiva
Copyright AMiWoRLD Ph0ton