1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa...

43
1. X-WINDOWS Per sistema a finestre si intende un ambiente operativo che consente di organizzare e gestire uno schermo grafico attraverso finestre multiple sulle quali sono visualizzati gli output dei programmi. Ecco come può apparire lo schermo in un tipico ambiente a finestre. Figura 1.1: aspetto dello schermo in un sistema a finestre Le finestre etichettate con A, B, C, D ed E (Figura 1 .1) formano una gerarchia (Figura 1 .2) nella quale lo schermo, che le contiene tutte, è considerato la radice (ROOT). Le finestre D ed E stanno completamente all’interno della finestra C e sono figlie di quest’ultima nella gerarchia. Questa nozione di contenimento è diversa dall’aspetto di sovrapposizione. Infatti, la finestra D potrebbe essere semplicemente sopra C senza esserne figlia. Generalmente ciò è verificabile spostando C: se D è sottofinestra di C (cioè figlia di C nella gerarchia) allora si sposterà a sua volta, altrimenti resterà fissa al suo posto. Figura 1.2: gerarchia di finestre Qualunque operazione di disegno avvenga in una finestra, essa non può influenzare il contenuto dello schermo al di fuori della finestra stessa (clipping). Ciò è vero in particolare per le sottofinestre. Se la finestra E in Figura 1 .1 fosse molto più lunga, al momento di venire visualizzata sarebbe troncata alla sola porzione contenuta in C (Figura 1 .3).

Transcript of 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa...

Page 1: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

1. X-WINDOWS

Per sistema a finestre si intende un ambiente operativo che consente di organizzare e gestire uno schermo grafico attraverso finestre multiple sulle quali sono visualizzati gli output dei programmi. Ecco come può apparire lo schermo in un tipico ambiente a finestre.

Figura 1.1: aspetto dello schermo in un sistema a finestre

Le finestre etichettate con A, B, C, D ed E (Figura 1.1) formano una gerarchia (Figura 1.2) nella quale lo schermo, che le contiene tutte, è considerato la radice (ROOT). Le finestre D ed E stanno completamente all’interno della finestra C e sono figlie di quest’ultima nella gerarchia. Questa nozione di contenimento è diversa dall’aspetto di sovrapposizione. Infatti, la finestra D potrebbe essere semplicemente sopra C senza esserne figlia. Generalmente ciò è verificabile spostando C: se D è sottofinestra di C (cioè figlia di C nella gerarchia) allora si sposterà a sua volta, altrimenti resterà fissa al suo posto.

Figura 1.2: gerarchia di finestre

Qualunque operazione di disegno avvenga in una finestra, essa non può influenzare il contenuto dello schermo al di fuori della finestra stessa (clipping). Ciò è vero in particolare per le sottofinestre. Se la finestra E in Figura 1.1 fosse molto più lunga, al momento di venire visualizzata sarebbe troncata alla sola porzione contenuta in C (Figura 1.3).

Figura 1.3: Esempio di clipping di sottofinestre

Le finestre sono visualizzate quando presenti nel frame-buffer (l’area di memoria che contiene la bitmap di tutto ciò che è visibile sullo schermo). Non sempre le finestre vengono create direttamente visibili; esse possono

Page 2: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

anche essere create separatamente e poi trasferite sullo schermo (ovvero nel frame-buffer) al momento opportuno. Il trasferimento sullo schermo viene chiamato mapping. Per togliere una finestra dallo schermo senza eliminarla dalla memoria si effettua il procedimento opposto, detto unmapping.

1.1 X Windows Server

Gestisce i seguenti tipi di oggetti:Drawables: windows, pixmaps

Si tratta degli oggetti su cui si può disegnare. Hanno i seguenti attributi:borderbackgroundcolor mapgravityevent masks

Graphics PrimitiveslinessegmentsarcspolygonRasterOp

Graphics Contextsclipping: rectangles, mask, plane maskattributes: line, color, font, tile and stipple patterns

EventsNormalmente diretti ad una singola finestraEnterNotify, LeaveNotify

1.2 Primitive grafiche

Tutte le primitive più importanti che abbiamo visto nelle librerie grafiche precedenti sono disponibili anche in X-Windows. Abbiamo così la possibilità di tracciare:

linee serie di linee collegate le une con le altre archi e porzioni di cerchi poligoni pixmap (ovvero immagini bitmap) caratteri

Sono inoltre disponibili primitive raster-op (dette anche Bit-Blt) che consentono di combinare due differenti immagini utilizzando una qualche operazione logica. Per disegnare qualunque oggetto grafico si utilizza il valore di certi attributi di sistema. Mentre in SRGP questi erano forniti tramite l’uso di variabili globali, in X-Windows viene usata una apposita struttura dati denominata graphic-context (GC) che li raggruppa tutti. Ogni primitiva grafica ha un parametro in ingresso per specificare il GC.

Tra gli attributi presenti nel graphic-context abbiamo: informazioni riguardanti il clipping, che indicano se l’operazione deve essere ristretta solo a una certa sottoarea della finestra. Ciò è ottenuto specificando uno o più rettangoli di clip (clip rectangles) che possono formare anche regioni molto complicate. Alternativamente si può specificare una clip mask, ovvero una bitmap di zeri ed uno che stabilisce su quali punti l’operazione ha effetto. Così è possibile definire regioni di clipping di forma arbitraria; la plane mask, che contiene informazioni riguardanti la profondità di colore, ovvero il numero di bit per pixel da utilizzare; stile, larghezza e forma di terminazione delle linee; colore da usare per disegnare; pattern per il riempimento; font da usare per scrivere i caratteri.

2

Page 3: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

1.3 Eventi di input (input device event)

Molti sistemi a finestre trattano l’input mediante eventi e X-Windows non fa eccezione. Le caratteristiche di un evento sono:

TIPO EnterNotify: indica che il puntatore del mouse è entrato nell’area di una finestra;LeaveNotify: indica che il puntatore del mouse è uscito dall’area di una finestra;Expose: indica che la finestra deve essere esposta. Ciò significa che qualcosa che era

sopra la finestra è stata spostata o eliminata, per cui parte della finestra che prima non era visibile ora deve essere ridisegnata;

ecc.SOURCE Sorgente dell’evento (pulsante del mouse, tasto, ecc.)POSIZIONE Indica la posizione in cui si trovava il puntatore del mouse al momento della generazione

dell’eventoTEMPO Indica l’istante di tempo in cui l’evento è avvenuto. Dato che gli eventi sono gestiti in

maniera asincrona rispetto all’applicazione è utile sapere la temporizzazione esatta tra di essi

Unix è un sistema operativo multitasking, dunque vi possono essere più applicazioni attive contemporaneamente e ognuna di esse deve avere la sua finestra di output. Come dispositivi di input, invece, abbiamo una sola tastiera e un solo mouse, per cui è necessario sapere quando avviene l’input e a quale finestra (ovvero applicazione) va inviato. La soluzione che viene adottata è quella di avere un unico gestore degli eventi, detto X-Server.

L’X-Server è un programma che gestisce tutta l’attività di input e output da parte dell’utente. Le applicazioni che vogliano utilizzare dispositivi grafici devono comunicare con il rispettivo X-Server per fare svolgere le operazioni che interessano loro. Analogamente X-Server riceve gli input dai dispositivi per poi inviarli ai clienti opportuni. L’input quindi, nella Figura 1.4, può essere destinato al cliente 1 o 2. Per sapere a chi va indirizzato viene utilizzata una tecnica detta delle liste di interesse (Interest List). Ciascun cliente indica all’X-Server a quali tipi di input è interessato. Sarà poi compito del server, seguendo la gerarchia delle finestre, stabilire, per ogni evento, chi ne è il destinatario.

Figura 1.4: struttura client-server

Questo modo di gestire gli eventi dà una grande versatilità alla gestione delle finestre. Tramite esse, ad esempio, è facile costruire una scrollbar:

Figura 1.5: struttura di una scrollbar

La gestione degli eventi è particolarmente semplice. Per sapere se è stata premuta la freccia in basso basta controllare gli eventi di click del mouse relativi alla finestra corrispondente.

3

Page 4: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

Un caso particolare si ha per gli eventi generati dalla tastiera, ad esempio in seguito alla pressione di un tasto. Ci sono due convenzioni principali:

l’evento è mandato alla finestra sopra cui si trova il cursore; l’evento va ad una particolare finestra precedentemente selezionata.

In ogni caso, la finestra che riceve gli input da tastiera è detta avere il focus. Due eventi particolari, FocusIn e FocusOut vengono inviati alla finestra che riceve o perde il focus. Nel caso in cui vige la convenzione 1, ogni evento di EnterNotify è seguito da uno di FocusIn e, viceversa, ogni FocusOut immediatamente precede un LeaveNotify.

Quasi sempre è presente un client particolare, mostrato anche in Figura 1.4, detto window-manager (WM) che si occupa di gestire l’intero schermo, di spostare le finestre e di assegnare il focus. Per far ciò il WM cattura tutti gli eventi a cui nessun altro è interessato. Ad esempio, in risposta ad un click col mouse in una zona dello schermo priva di finestre, di solito il WM fa apparire un menù definibile dall’utente. Altra tipica attività del window-manager è la decorazione delle finestre con pulsanti e tool vari che consentono di operare su di essae ridimensionandole, spostandole, ecc.

Figura 1.6: Client Server

Il vantaggio di questo tipo di architettura (distributed window system) è che le due parti (X-Server, clienti) possono essere separate. Possono essere addirittura due processi che si trovano su macchine distinte e che comunicano tramite una rete, mediante scambio di messaggi, usando dei ben definiti protocolli di comunicazione. Ciò consente di separare la parte elaborazione dalla parte visualizzazione di una applicazione.

Le difficoltà che possono nascere in una organizzazione di questo tipo hanno origine da attività che richiedono una costante interazione con l’utente: ad esempio, un programma di disegno a mano libera, nel quale è necessario seguire pedissequamente il movimento del mouse lasciandosi dietro una scia di pixel colorati. In questo caso c’è un traffico elevato di messaggi in quanto ogni evento di spostamento del mouse viene trasmesso al cliente che decide di tracciare il punto in cui l’evento ha avuto origine.

Per ridurre il traffico, il server accoda una serie di eventi in un buffer e non li manda uno per uno ma attende di avere riempito la coda ad un certo livello per spedirli poi tutti insieme. Lo stesso vale per le comunicazioni dirette dal client al server. Tutto ciò, ovviamente, rende più difficile l’interazione. Per far sì che una richiesta al server venga processata immediatamente, il cliente deve fare una richiesta esplicita di flush della coda, provocando in tal modo l’invio immediato di tutto il suo contenuto.

1.4 Come avviene la comunicazione

La comunicazione tra client e server avviene tramite un protocollo di rete che si basa su una forma di comunicazione bidirezionale affidabile. Un cliente deve come prima operazione mettersi in contatto con il server e stabilire una connessione affidabile (reliable byte stream). Questo significa che è possibile inviare una sequenza di byte essendo sicuri che l’ordine con cui sono trasmessi è uguale a quello con cui vengono ricevuti e inoltre che nessuno di questi pacchetti venga perso. Non tutte le reti hanno queste caratteristiche. In tal caso si risolve il problema attraverso una serie di protocolli stratificati, come nel caso del TCP/IP:

Transport (TCP) protocollo per la trasmissione affidabileNetwork (IP) protocollo per la trasmissione (non affidabile) e il ruoting dei pacchettiData Link specifica l’organizzazione dei pacchettiPhysical (Ethernet) specifica il tipo di collegamento fiso

4

Page 5: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

I primi due livelli (dal basso) si riferiscono alla comunicazione tra due nodi direttamente collegati. Il livello network fornisce la possibilità di collegare due nodi attraverso passaggi intermedi fra altri nodi. Il livello transport fornisce l’affidabilità.

Per quanto riguarda X-Windows si utilizza un protocollo particolare detto X-Windows protocol, che consiste essenzialmente nel linguaggio in cui vengono espresse le richieste che un cliente fa al server per lo svolgimento delle sue funzioni e nel formato delle risposte di quest’ultimo. I dati viaggiano sotto forma di sequenze di byte. Ci sono fondamentalmente due tipi di messaggi:

Request (richieste da parte del client)

1° byte: codice principale che identifica il tipo di richiesta (Major OpCode);

2° byte: codice secondario della richiesta (Minor OpCode);

3° e 4° byte: lunghezza della parte restante del messaggio in parole di 4 byte;

dal 5° byte: corpo del messaggio

Reply (risposte da parte del server)

1° byte: codice reply;

2° – 4° byte: numero di sequenza;

5° – 8° byte: lunghezza della parte restante del messaggio in parole di 4 byte;

dal 9° byte: corpo del messaggio

È stata anche sviluppata una estensione del protocollo, denominata PEX, che permette di utilizzare operazioni grafiche 3D.

Visto che client e server possono anche risiedere su macchine distinte, c’è il rischio che queste utilizzino delle convenzioni diverse per l’ordinamento dei byte all’interno di blocchi di dati più lunghi quali word o double word. I due tipi di ordinamento standard sono:

Big Endian: i byte sono trasmessi dal più significativo al meno significativo. Little Endian: i byte sono trasmessi dal meno significativo al più significativo.

Ad esempio, le macchine Motorola e Sparc utilizzano il Big Endian, mentre Intel e DEC il Little Endian.

Bisogna dunque evitare che la stesse serie di dati sia interpretata in modo diverso dalle due macchine client e server. X-Windows, per risolvere questo problema, riserva due porte di accesso:

6000 riceve i dati in Little Endian6001 riveve i dati in Big Endian.

Il client si collegherà all’una o all’altra porta a seconda del modo con cui ordina i byte. Per quanto riguarda la programmazione, invece, il programmatore non si deve occupare di inviare pacchetti di byte o altro visto che il tutto è incapsulato in un meccanismo a più alto livello, denominato Remote Procedure Call.

Supponiamo che l’utente voglia eseguire una primitiva, per esempio la GetWindowHeight(Wid),una procedura che dato l’identificatore di una finestra restituisce la sua altezza in pixel come risultato. L’esecuzione di questa primitiva comporta l’invocazione di una procedura locale STUB che si occupa di ricevere i parametri della richiesta, comporre un pacchetto da inviare al server (Marshalling) e trasmetterlo. L’X-server è in un ciclo (dispatch loop) nel quale aspetta di leggere ed eseguire le richieste provenienti dal client. A seconda del tipo di richiesta, il server esegue una diversa operazione (nel nostro caso calcola l’altezza della finestra) e prepara un pacchetto di risposta, che viene trasmesso via rete e ricevuto da STUB che poi invia la risposta alla procedura locale che ne ha fatto richiesta. Tutto il procedimento è riassunto in Figura 1.7. Il meccanismo di Remote Procedure Call è implementato tramite una particolare libreria denominata XLib.

5

Page 6: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

Figura 1.7: Invocazione tramite RPC

1.5 Attivazione dell’X-Server

Prima di poter utilizzare una applicazione X-Windows è necessario attivare l’X-Server. Questo si fa tipicamente tramite il comando xinit o startx. Il file .xinitrc, nella proprio home directory, è uno script della shell di Unix che viene eseguito subito dopo l’attivazione del server. Normalmente è da qui che viene fatto partire in background il windows manager.

Spesso, in particolare su macchine dedicate esclusivamente al compito di X-Server, il tutto è già attivo e sotto il controllo di Xdm, un daemon (così si chiamano i programmi che sono costantemente in esecuzione) che si occupa di effettuare le operazioni di login e riconoscimento password dai terminali X.

1.6 La libreria Xlib

Vediamo ora più in dettaglio la libreria Xlib e la struttura generale di un’applicazione che la utilizza. La struttura delle librerie per X11 comprende al più basso livello Xlib. Essa è una libreria C (quindi facilmente linkabile con programmi C) che consente di dialogare con un X-Server attraverso la rete, incapsulando tutti i dettagli relativi alla gestione dell’X-Windows protocol.

L’X-Server può essere sulla stessa macchina o su una macchina diversa. In ogni caso si tratta di un processo che non condivide lo spazio indirizzi del client e quindi ogni passaggio di parametri deve avvenire esplicitamente tramite scambio di messaggi. Si noti come, nel caso di X, il concetto di server e client sia rovesciato rispetto a quello usuale. Un terminale diventa infatti un server in quanto fornisce le funzionalità grafiche ad un processo client che può risiedere anche su un grosso mainframe.

Xlib è la libreria di più basso livello utilizzabile e fornisce le primitive grafiche elementari (simili a quelle della libreria grafica SRGP). Sopra Xlib ci sono di solito dei toolkit. Il toolkit Xt (X toolkit) è una libreria che fornisce una serie di funzionalità per la gestione di finestre complesse che comprendono gadget quali menù a tendina, scrollbar, pulsanti, ecc. Le finestre create con Xt vengono dette widget (windows + gadget). Sopra Xt vi sorno varie librerie di widget, quali Xm (Motif toolkit) e Xaw (Athena Widget). Un altro toolkit in alternativa a Xt è TK che ha anche una collezione di widget differente da quella di Motif e Athena.

6

Page 7: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

Il toolkit non maschera completamente le primitive Xlib ed alcune, quali ad esempio quelle di basso livello per tracciare linee, poligoni o altre forme geometriche, non vengono neanche replicate. Un’applicazione potrà utilizzare delle istruzioni a più alto livello per quanto riguarda la strutturazione delle finestre (cose come il trattamento degli eventi sono più semplici da gestire con l’uso dei widget) e fare affidamento a Xlib per le primitive di grafica elementari. Le varie librerie di toolkit e widget sono tra loro incompatibili per cui quando si sviluppa una applicazione è bene decidere in partenza quale toolkit e collezione di widget utilizzare.

1.6.1 Un primo esempio

Per addentrarci nella programmazione in Xlib vediamo un esempio che consiste nel creare una finestra e tracciare alcune rette al suo interno.

#include <stdio.h>#include <X11/Xlib.h>#include <X11/Xutil.h>

Display *display;int screen;unsigned long whitePixel, blackPixel;Window rootWindow;

void Initialise() { display = XOpenDisplay(NULL); screen = DefaultScreen(display); rootWindow = DefaultRootWindow(display); whitePixel = WhitePixel(display,screen); blackPixel = BlackPixel(display,screen);}

Window CreateWindow (Window parent, unsigned int posx, unsigned int posy, unsigned int width, unsigned int height, unsigned int border) {

Window win = XCreateSimpleWindow(display, parent, posx, posy,width,height, border, blackPixel, whitePixel);

XMapWindow(display, win); return(win);}

I parametri per creare finestre sono:

Window XCreateSimpleWindow(Display *display,Window, parent,int posx,int posy,uint width,uint height,uint border,ulong foreground,ulong background)

main(int argc, char **argv) { Window win1, win2; unsigned int width, height, border; int posx, posy; GC gc;

Initialise(); border = 4; width = height = 400; posx = posy = 100; win1 = CreateWindow(rootWindow, posx, posy, width, height, border); border = 1; width = height = 200; posx = posy = 50; win2 = CreateWindow(win1, posx, posy, width, height, border);

7

Page 8: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

gc = DefaultGC(display, screen); XDrawLine(display, win1, gc, 0, 0, 400, 400); XDrawLine(display, win2, gc, 0, 199, 199, 0); XSync(display, false); sleep(5); XDestroyWindow(display, win2); XDestroyWindow(display, win1); XCloseDisplay(display);}

Il programma C comprende alcuni file da includere:

#include <stdio.h> (per l’input e l’output da terminale)#include <X11/Xlib.h> (contiene tutte le dichiarazioni dei dati e degli oggetti manipolati da Xlib)#include <X11/Xutil.h>

La prima cosa che deve fare una applicazione che voglia gestire grafica è mettersi in contatto con un server e aprire lo schermo. Definiamo un puntatore alla struttura Display. Un display (stazione di lavoro) può essere formato da diversi schermi per cui è necessario selezionarne uno.

Display *display;int screen;

Ci serviranno i valori dei colori “bianco” e “nero”. In generale l’utente può avere scelto dei colori particolari come sfondo (background) e primo piano (foreground) che non i classici bianco e nero (ad esempio, nel caso limite, i due colori possono essere invertiti in quanto lo schermo è in stato di reverse). Il programma non è obbligato a utilizzare questi, ma è comunque preferibile attenersi alle preferenze dell’utente. Allo scopo usiamo le seguenti variabili che verranno riempite con valori richiesti al display stesso:

unsigned long whitePixel, blackPixel;

Ad ogni schermo corrisponde una finestra principale, la rootWindow. Usiamo una variabile opportuna per mantenere il suo identificatore.

Window rootWindow;

La cosa da fare, prima di poter operare su qualsiasi finestra, è collegarci con un display e selezionare uno schermo. Tutto ciò lo affidiamo ad una funzione di inizializzazione. Per collegarci ad un display usiamo la funzione XOpenDisplay di Xlib. In generale, occorrerebbe passarle il nome simbolico della macchina remota su cui si trova il display. Nel nostro caso il valore NULL sta ad indicare che vogliamo accedere al display di default. L’identificatore restituito dalla funzione lo mettiamo nella variabile che abbiamo precedentemente dichiarato.

display = XOpenDisplay(NULL);

Il display di default è di solito indicato da una variabile di ambiente DISPLAY per cui possiamo conoscerne il valore digitando il comando echo $DISPLAY dalla shell. L’output avrà il formato mostrato in Figura 1.8.

Figura 1.8: formato della variabile DISPLAY

Un programma serio dovrebbe anche andare a controllare se il risultato della chiamata a XOpenDisplay ha avuto successo e comportarsi di conseguenza. Una volta ottenuta correttamente la connessione con il server richiesto, selezioniamo uno schermo, in questo caso quello di default. Normalmente il valore 0 indica il primo degli schermi a disposizione. Possiamo a questo punto ottenere un riferimento alla finestra principale dello schermo selezionato mediante una chiamata a DefaultRootWindow.

8

Page 9: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

rootWindow = DefaultRootWindow(display);

Successivamente una chiamata alle funzioni WhitePixel e BlackPixel ci restituisce i colori effettivi corrispondenti al bianco e al nero logico.

whitePixel = WhitePixel(display, screen);blackPixel = BlackPixel(display, screen);

Vediamo ora il programma principale. Vogliamo creare una finestra quadrata, tracciare una retta diagonale, aprire una finestra nel mezzo e tracciare in quest’ultima un’altra linea diagonale nel verso opposto.

Figura 1.9: Output del programma di esempio

Ci servono dunque due finestre, delle variabili per memorizzare la dimensione e la posizione delle stesse e un Graphics Context, che, ricordiamo, è la struttura in cui sono raccolti tutti i parametri relativi alle operazioni grafiche. Il tipo degli oggetti Graphics Context è GC.

Window win1, win2;unsigned int width, height, border;int posx, posy;GC gc;

A questo punto il primo passo è l’inizializzazione delle finestre, che affidiamo alla funzione CreateWindow. Ad essa dobbiamo passare come parametri:

la finestra dentro la quale vogliamo creare la nuova finestra, la posizione dove va creata, le dimensioni, l’ampiezza del bordo.

Si noti che la finestra win2 non è figlia di rootWindow ma di win1. Come conseguenza le posizioni x e y che diamo non sono relative all’origine dello schermo ma all’angolo in alto a sinistra della finestra win1.

È importante osservare che tutte le operazioni che facciamo su una finestra sono ad essa relativizzate e il clipping avviene sul bordo della finestra stessa. Vogliamo ora tracciare le due linee diagonali. Ricordiamo che ogni primitiva grafica richiede la specifica di un Graphics Context. Possiamo crearlo ex-novo oppure utilizzare quello di default.

gc = DefaultGC(display, screen);

Successivamente si passa al disegno delle linee con la funzione XDrawLine. Come quasi tutte le primitive X, anche la XDrawLine richiede di indicare il display e la finestra su quel display dove disegnare. Va notato anche che una finestra non ha alcun senso se non relativamente ad un ben preciso display. Il tipo Window infatti non è altro che un intero, usato come identificatore ad una struttura dati locale al display Inoltre, dobbiamo indicare anche il Graphics Context e gli estremi della retta in questione.

XDrawLine(display, win1, gc, 0, 0, 400, 400);XDrawLine(display, win2, gc, 0, 199, 199, 0);

Le operazioni grafiche non vengono necessariamente svolte immediatamente perché in genere Xlib accumula le richieste finché ne ha un certo numero e le trasmette poi tutte insieme al server. Noi vogliamo assicurarci che le nostre richieste vengano eseguite e per far ciò utilizziamo la primitiva XSync con la quale chiediamo ad Xlib di inviare tutte le richieste ancora sospese.

9

Page 10: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

XSync(display, False);

Il secondo parametro serve a indicare che cosa vogliamo fare degli eventi che in questo momento sono in coda. False indica di lasciarli stare e non buttarli via. Al momento dell’esecuzione di XSync siamo sicuri che le rette sono state visualizzare. A questo punto aspettiamo qualche secondo e poi chiudiamo il tutto.

XDestroyWindow(display, win2);XDestroyWindow(display, win1);XCloseDisplay(display);

Le chiamate di XDestroyWindow servono per assicurarsi che il server rilasci le risorse utilizzate dalle finestre. Queste infatti vengono create ed allocate sul server e non sulla macchina client. Visto che di solito i terminali X hanno da 4 a 8 Megabyte di memoria, è meglio stare attenti a non esaurirla, rimuovendo le finestre che non servono. Normalmente quando un programma termina rilascia automaticamente tutte le risorse perché queste sono allocate nel suo spazio di indirizzi mentre questo non accade per le applicazioni distribuite. In questo caso, infatti, ogni applicazione può creare e gestire risorse su vari nodi della rete. Un’applicazione che gira su un determinato nodo non ha modo di sapere se chi ha invocato un certo servizio (o chi la ha chiamata) è ancora interessato a quel servizio oppure no.

Per quanto riguarda la funzione CreateWindow, essa si basa sulla primitiva XCreateWindow di Xlib. Gli ultimi due parametri sono i colori da usare per il foreground e background. Essa alloca sul server le risorse necessarie alla rappresentazione e visualizzazione della finestra, ma non la visualizza. Per far ciò occorre una richiesta esplicita tramite la primitiva XMapWindow.

Ovviamente vi sono molte altre primitive grafiche oltre alla XDrawLine. Ad esempio la:

XDrawLines(Display *display, Drawable d, GC gc, XPoint *points, int npoints, int mode)

Il primo argomento è il solito puntatore al display. Segue un parametro di tipo Drawable che può essere ho una finestra o una PixMap, ovvero un’area di memoria vista come bitmap dove eseguire le operazioni grafiche, che può poi essere eventualmente visualizzata con operazioni di BitBlt. Si noti che anche la XDrawLine e praticamente tutte le primitive di disegno accettano PixMap al posto di finestre. La primitiva traccia una serie di segmenti i cui vertici si trovano ordinati nel vettore di strutture XPoint che viene passato come quarto argomento. Il numero effettivo di elementi nell’array è dato dal parametro npoints. Infine, l’ultimo parametro specifica il tipo di tracciamento.

La struttura XPoint è definita come segue:

typedef struct { int x, y; } XPoint;

La definizione è presente nel file Xlib.h insieme a definizioni di strutture simili per descrivere segmenti (XSegment), archi (XArc), ecc. che vengono utilizzate da primitive simili alla XDrawLines.

1.7 Primitive grafiche

XDrawPoint(Display *, Drawable, GC, int x, int y)XDrawPoints(Display *, Drawable, GC, Xpoint*, int n, int mode)

XDrawLine(Display *, Drawable, GC, int x1, int x2, int y1, int y2)XDrawLines(Display *, Drawable, GC, Xpoint *, int n, int mode)

XDrawSegments(Display *, Drawable, GC, int x1, int x2, int y1, int y2)XDrawRectangle(Display *, Drawable, GC, Xpoint *, int n, int mode)XDrawRectangles(Display *, Drawable, GC, int x1, int x2, int y1, int y2)XDrawArcs(Display *, Drawable, GC, Xpoint *, int n, int mode)XFillRectangles(Display *, Drawable, GC, int x1, int x2, int y1, int y2)XFillPolygon(Display *, Drawable, GC, Xpoint *, int n, int shape, int mode)

XMoveArea(Display, Window, int srcX, int srcY, int dstX, int dstY, int width, int height)XCopyArea(Display, Drawable src, Drawable dest, GC, int src_x, int src_y, int width, int height, int dest_x, int dest_y)XCopyPlane(Display, Drawable src, Drawable dest, GC, int src_x, int src_y, int width, int height, int dest_x, int dest_y, ulong plane) XClearArea(Display, Window, int x, int y, uint width, uint height, Bool exposures)XClearWindow(Display, Window)

10

Page 11: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

1.8 Graphic Context

GC contiene gli attributi da usare nelle operazioni grafiche:

function per la copy areaplanemaskforegroundbackgroundline_widthline_style solid, OnOffDash, DoubleDashcap_style NotLast, Butt, Round, Projectingjoin_style Miter, Round, Bevelfill_style Solid, Tiled, Stippled, opaqueStippledfill_rulearc_mode chord, PieSlicetile Pixmap for tilingstipplefontclip_mask…

XCreateGC(Display *, Drawable, ulong mask, XGCValues *)XSetFillStyle(Display *, GC int)XChangeGC(Display *, GC, ulong mask, XGCValues *)

La struttura di un graphics context è la seguente:

typedef struct {/*DEFAULTS*/ /*MASKS*/

int function; GXcopy GCFunction unsigned long plane_mask; ~0 GCPlaneMask unsigned long foreground; 0 GCForeground unsigned long background; 1 GCBackground int line_width; 0 GCLineWidth int line_style; LineSolid GCLineStyle int cap_style; CapButt GCCapStyle int join_style; JoinMiter GCJoinStyle int fill_style; FillSolid GCFillStyle int fill_rule; EvenOddRule GCFillRule int arc_mode; ArcPieSlice GCArcMode Pixmap tile; a pixmap in the fg color GCTile Pixmap stipple; a pixmap of 1's GCStipple int ts_x_origin; 0 GCTileStipXOrigin int ts_y_origin; 0 GCTileStipYOrigin Font font; GCFont int subwindow_mode; ClipByChildren GCSubwindowMode Bool graphics_exposures; True GCGraphicsExposures int clip_x_origin; 0 GCClipXOrigin int clip_y_origin; 0 GCClipYOrigin Pixmap clip_mask; None GCClipMask int dash_offset; 0 GCDashOffset char dashes; 4 GCDashList

} XGCValues;

Il campo function serve ad indicare l’operatore da usare nelle operazioni di RasterOp (XcopyArea, XcopyPlane), e può avere i seguenti valori:

Function OperationGXclear 0GXand src AND dstGXandReverse src AND NOT dstGXCopy srcGXandInverted (NOT src) AND dstGXnoop dstGXxor src XOR dstGXor src OR dst

11

Page 12: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

GXnor (NOT src) AND dstGXequiv (NOT src) XOR dstGXinvert NOT dstGXorReverse src OR NOT dstGXcopyInverted NOT srcGXorInverted (NOT src) OR dstGXnand (NOT src) OR (NOT dst)GXset 1

1.8.1 Coda di eventi

Gli eventi generati sul server X, vengono smistati nella coda di ciascun cliente che ne faccia richiesta, dopo essere stati filtrati attraverso una event mask che il cliente stesso ha indicato.

Figura 1.10. Coda di eventi

Gli eventi rientrano nelle seguenti categorie:

keyboard (KeyPress, KeyRelease) pointer motion (ButtonPress, ButtonRelease, MotionNotify) window crossing (EnterNotify, LeaveNotify) exposure (Expose, GraphicsExpose, NoExpose)

Per indicare la maschera per filtrare gli eventi si usa:

XselectInput(Display *, Window, ulong mask)

Il server scorre la gerarchia delle finestre a partire da quella all’interno della quale l’evento si è verificato (source window) fino a risalire ad una finestra interessata all’evento (event window).

Figura 1.11. Finestre coinvolte in un evento

12

Page 13: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

1.8.2 Eventi

Gli eventi sono rappresentati da un struttura diversa per ciascun tipo di evento. Per esempio, l’evento Button è rappresentato da:

typedef struct {int type;Display * display;Window windos, root, subwindow;Time time;int x, y;int x_root, y_root;uint state, button;Bool samescreen;

} XButtonEvent

Un evento generico è rappresentato dal tipo unione dei diversi tipi di eventi:

typedef union _XEvent { int type; /* must not be changed; first element */ XAnyEvent xany; XKeyEvent xkey; XButtonEvent xbutton; XMotionEvent xmotion; XCrossingEvent xcrossing; XFocusChangeEvent xfocus; XExposeEvent xexpose; XGraphicsExposeEvent xgraphicsexpose; XNoExposeEvent xnoexpose; XVisibilityEvent xvisibility; XCreateWindowEvent xcreatewindow; XDestroyWindowEvent xdestroywindow; XUnmapEvent xunmap; XMapEvent xmap; XMapRequestEvent xmaprequest; XReparentEvent xreparent; XConfigureEvent xconfigure; XGravityEvent xgravity; XResizeRequestEvent xresizerequest; XConfigureRequestEvent xconfigurerequest; XCirculateEvent xcirculate; XCirculateRequestEvent xcirculaterequest; XPropertyEvent xproperty; XSelectionClearEvent xselectionclear; XSelectionRequestEvent xselectionrequest; XSelectionEvent xselection; XColormapEvent xcolormap; XClientMessageEvent xclient; XMappingEvent xmapping; XErrorEvent xerror; XKeymapEvent xkeymap; long pad[24];} XEvent;

Quando si ha un evento (anEvent), se ne può esaminare il tipo e di conseguenza i campi nel seguente modo:

anEvent.type == ButtonPress

anEvent.xbutton.x

Per trattare gli eventi si usano le seguenti procedure:

Xpending(Display *)XnextEvent(Display *, Xevent *)

13

Page 14: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

1.8.3 Un esempio più complesso

Si vuole realizzare un’interfaccia utente in un programma di disegno che consenta di tracciare delle curve parametriche. Una curva parametrica è gestita attraverso punti di controllo che possiamo spostare a piacimento. Il programma interpola la curva attraverso i punti di controllo consentendo così all’utente di dare origine a figure anche molto complesse.

Figura 1.12: curve paramtriche

Nell’esempio che vediamo ci limitiamo a realizzare un’interfaccia che ci consenta di tracciare un rettangolo con quattro punti di aggancio (handle) connessi tra di loro da segmenti.

Figura 1.13: rettangolo parametrico del programma di esempio

Sfrutteremo il meccanismo di gestione degli eventi di X per tutto il trattamento dell’interazione. In particolare, faremo sì che i punti di aggancio siano delle finestre. Questa scelta ci consentirà di individuare semplicemente il fatto che l’utente clicca su un handle poiché l’evento di mouse click viene generato sulla finestra corrispondente. Ne risulta un buon esempio di come X sfrutti pesantemente il concetto di finestra.

Il nostro nuovo programma avrà una fase di inizializzazione simile alla precedente e una funzione CreateControlPointWindow per i quattro handle che ha gli stessi parametri della CreateWindow e il seguente codice:

Window CreateControlPointWindow (Window parent, int posx, int posy, unsigned int width, unsigned int height, unsigned int border){ Window win = XCreateSimpleWindow(display, parent, posx, posy, width, height,

border, blackPixel, whitePixel); XSelectInput(display, win, EnterWindowMask | ExposureMask); XMapWindow(display, win); return(win);}

La finestra relativa ad un handle deve essere sensibile a certi eventi e per questo chiamiamo la primitiva XSelectInput. I due eventi che vogliamo considerare sono Exposure ed EnterNotify (in modo da annerire il quadratino sopra il quale si trova il mouse). Il terzo parametro di XSelectInput è appunto una maschera di bit che serve a indicare gli eventi a cui la finestra è interessata. ExposureMask ed EnterWindowMask sono due maschere costanti corrispondenti agli eventi citati. Facciamo l’or tra di esse in quanto siamo interessati ad entrambi gli eventi.

Il programma principale è il seguente:

main(int argc, char **argv) { Window win;

Initialise(); win = CreateApplicationWindow(rootWindow, 0, 0, 400, 400, 1); XSync(display,True); MainEventLoop(win); XCloseDisplay(display);}

14

Page 15: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

che, ha grandi linee, fa le seguenti cose:

1. inizializza il sistema;2. crea la finestra dove vogliamo visualizzare la nostra applicazione: per far ciò utilizza una funziona

apposita CreateApplicationWindow che ha gli stessi parametri di CreateWindow;3. si assicura che tutto sia visibile e butta via ogni eventuale evento in coda;4. passa il controllo al MainEventLoop, un ciclo che continuamente riceve eventi dal server e decide come

vadano interpretati e trattati;5. chiude il display ed esce dal programma.

Vediamo come si svolge il MainEvenLoop:

void MainEventLoop(Window win) { XEvent ev; char ch;

PaintWindow(win); while (true) { XNextEvent(display,&ev); switch (ev.type) { case Expose: default: PaintWindow(win); break; case KeyPress: if (XLookupString(&ev.xkey, &ch, 1, NULL, NULL) == 1 && ch == 'q')

return; break; case MotionNotify: if (moveWindow == None) break; XMoveWindow(display, moveWindow, ev.xmotion.x, ev.xmotion.y); PaintWindow(win); break; case EnterNotify: moveWindow = ev.xcrossing.window; PaintWindow(win); break; case ButtonRelease: moveWindow = None; PaintWindow(win); break; } }}

Il MainEvenLoop ha un parametro che è la finestra in cui sta girando l’applicazione. Quello che deve fare il ciclo è ricevere gli eventi generati dall’utente e gestirli uno ad uno.

Prima di iniziare il ciclo, la chiamata alla procedura PaintWindow imbianca la finestra principale e disegna i punti di controllo e i segmenti che li uniscono. A questo punto si può entrare nel ciclo, da cui si uscirà solo premendo il tasto ‘q’. Per sapere qual’è il prossimo evento in coda si usa la funzione XNextEvent e il risultato viene messo nella variabile ev di tipo XEvent. Questo dato viene ricevuto dalla rete ed è Xlib che si occupa di interpretare i pacchetti e riempire la struttura dell’evento.

Ogni evento ha un tipo particolare e a seconda di quello che si riceve eseguiremo delle azioni opportune. Per distinguere tra i vari tipi si accede al campo type della struttura ev (ev.type). Vediamo ora separatamente i vari casi.

Expose: La finestra è stata portata in primo piano e quindi va rinfrescata. In generale non è il server ma l’applicazione che si preoccupa di ridisegnare la finestra. La stessa operazione la facciamo anche come caso di default;

KeyPress: L’utente ha pigiato un tasto sulla tastiera. Dobbiamo verificare se il tasto in questione è ‘q’. Per fare questo utilizziamo la procedura XLookupString a cui passiamo il codice del tasto che estraiamo dall’evento (&ev.xkey), il posto dove andare a mettere il codice ASCII corrispondente (la variabile ch) e altri parametri per noi non significativi. Se effettivamente è

15

Page 16: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

stato pigiato un tasto corrispondente a un carattere (LookupString(…) restituisce il valore vero) e questo carattere è ‘q’, allora dobbiamo chiudere l’applicazione uscendo dal MainEventLoop.

MotionNotify: Per ogni spostamento del mouse riceviamo un evento di MotionNotify che ci dice di quanto si è spostato il mouse. L’evento di tipo MotionNotify non è rilevante se non è stata selezionata alcuna maniglia. Utilizziamo per questo motivo una variabile moveWindow che ci dice se e quale maniglia è selezionata. Se il contenuto di moveWindow è diverso dalla finestra indefinita None, allora chiamiamo la primitiva XMoveWindow per spostare il punto di controllo appropriato e ridisegniamo il tutto.

ButtonRelease: Il pulsante del mouse è stato rilasciato. Dobbiamo riazzerare la variabile moveWindow e ridisegnare.

La funziona PaintWindow che utilizziamo per disegnare la finestra è la seguente:

void PaintWindow(Window win) { XPoint points[5]; int i; Window root; int x,y; unsigned int w, h, border, junk; XClearWindow(display,win); for (i = 0; i < 4; i++) { XGetGeometry(display, children[i], &root, &x, &y, &w, &h, &border, &junk); points[i].x = x + border + w/2; points[i].y = y + border + h/2; if (children[i ] == moveWindow) XFillRectangle(display, children[i], gc, 0, 0, w+1, h+1); else XClearWindow(display, children[i]); } points[4] = points[0]; XDrawLines(display, win, gc, points, 5, CoordModeOrigin); XSync(display,False);}

Per disegnare il rettangolo abbiamo bisogno di un vettore di cinque punti (l’ultimo punto è ripetuto e coincide con il primo). I quattro quadratini alle estremità del rettangolo sono visualizzati sfruttando il bordo delle finestre. È possibile chiedere ad esse stesse la loro posizione e dimensione, tramite la primitiva XGetGeometry. Come punti per tracciare il rettangolo prendiamo il loro centro.

Visto che vogliamo disegnare annerito l’handle attualmente selezionato, usiamo a tale scopo la XFillRectangle per riempire il tutto di nero. Gli handle non selezionati saranno invece imbiancati chiamando la XClearWindow. In questo modo riportiamo al colore bianco l’ultimo handle selezionato, che altrimenti potrebbe restare annerito.

Per disegnare i lati del rettangolo, facciamo in modo che il primo punto coincida con l’ultimo e chiamiamo XDrawLines. Infine ci assicuriamo che il tutto sia riportato sul video con XSync.

Resta solo da descrivere come si fa a creare la finestra principale. Il compito è affidato a CreateApplicationWindow che non solo deve aprire la finestra ma deve anche creare e posizionare opportunamente le finestre handle figlie e selezionare gli eventi a cui siamo interessati: Exposure, StructureNotify (indica un cambiamento di dimensione della finestra), ButtonMotion, ButtonRelease e KeyPress.

Window CreateApplicationWindow(Window parent, int posx, int posy, unsigned int width, unsigned int height, unsigned int border){ int i,opx[4], opy[4]; long emask; XEvent ev;

Window win=XCreateSimpleWindow(display, parent, posx, posy, width, height, border, blackPixel, whitePixel);

XMapWindow(display,win);

opx[0] = opx[3] = border+width/4; opx[1] = opx[2] = border+3*width/4;

16

Page 17: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

opy[0] = opy[1] = border+height/4; opy[2] = opy[3] = border+3*height/4; for (i = 0; i<4; i++) children[i] = CreateControlPointWindow(win, opx[i], opy[i], 5, 5, 1); moveWindow = None; emask = ExposureMask | StructureNotifyMask | ButtonMotionMask | ButtonReleaseMask | KeyPressMask; XSelectInput(display, win, emask); XSync(display,False); XWindowEvent(display, win, ExposureMask, &ev); return(win);}

La chiamata a XWindowEvent provoca l’attesa e l’estrazione di un evento Exposure dalla coda degli eventi prima di continuare con l’esecuzione.

1.9 Toolkits

Un toolkit grafico è una libreria di strumenti di interazione invocabili dalle applicazioni. A widget is a way of using a physical input device to input a certain type of value. A toolkit is a library of interaction techniques called by applications. I toolkit contengono procedure per creare e manipolare vari tipi di widget:

menus, buttons, scroll bars, dialog boxes, windows, icons, text boxes provide a procedural interface are used only by programmers

Alcuni esempi di toolkit sono:

Macintosh Toolbox Microsoft Foundation Classes Xtk for X (Motif e OpenLook both use Xtk) NeXTStep for NeXT Tk, parte di TCL/Tk

I vantaggi di usare un toolkit sono:

dare un “look and feel” (aspetto) uniforme alle applicazioni consentire il riutilizzo del codice sollevare i programmatori dal compito di scrivere codice per operazioni standard, quali la selezione da un menu o il disegno di un bottone.

Gli svantaggi di usare un toolkit sono:

gli stili di interazione sono limitati a quelli forniti dal toolkit. Per esempio, potrebbe servire una barra di scorrimento fatta ad ascensore, con le due frecce attaccate al cursore, anziché alle due estremità. spesso le applicazioni scritte con i toolkit diventano intricate e complesse piuttosto difficili da usare, librerie molto grosse, manuali corposi, non è chiaro il protocollo delle chiamate.

I toolkit sono classificabili secondo due stili di programmazione:

procedurale object-oriented

Procedurale Sono forniti come librerie di procedure. Ad esempio: Macintosh Toolbox, SunTools for SunView. Questa scelta ha il vantaggio di una semplicità di implementazione.

Object-Oriented Sono realizzati come una libreria di classi standard, che i programmatori possono specializzare attraverso sottoclassi. Richiedono pertanto l’utilizzo di un linguaggio di programmazione ad oggetti, o preesistente (C++ per MFC, Smalltalk, Java per l’AWT, CLOS, Objective-C per NeXTStep) o creandone uno proprio (Xt, Garnet, Amulet). La

17

Page 18: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

programmazione ad oggetti ha il vantaggio di essere naturale, in quanto i widget (menu, bottoni, ecc.) appaiono come oggetti, ed è più agevole l’adattamento tramite l’ereditarietà delle classi. Purtroppo c’è anche lo svantaggio che lega le applicazioni ad un particolare linguaggio di implementazione.

1.9.1 Toolkits: livelli

Poichè i progettisti di X Windows non sono riusciti a mettersi d’accordo su un unico “look-and-feel”, hanno creato un livello chiamato intrinsics layer su cui costruire diversi widget sets.

Il livello intrinsics degli fornisce l’interfaccia procedurale per implemetare i widget ed una raccolta di servizi comuni, ad esempio per fornire uno stile di programmazione ad oggetti, ed un cotrollo del layout. I widget set comprendono una raccolta di widget di uso generale costruiti usando gli intrinsics e forniscono un particolare look-and-feel. Widget set diversi (con aspetto diverso) possono essere implementati sopra gli stessi intrinsics.

1.10 Il toolkit Xt

I toolkit sono progettati per facilitare il compito del programmatore nel realizzare l’interfaccia utente. Essi forniscono una serie di primitive per la gestione degli eventi e dell’interazione che si materializzano nei widget, classi di oggetti predefiniti che interagiscono direttamente con l’utente. Mentre Xlib è una libreria standard C, nel senso che lo stile di programmazione con Xlib è quello standard del C, a livello di toolkit sarebbe preferibile adottare un approccio diverso: la programmazione ad oggetti.

Quando si parla di widget si pensa proprio a delle classi che possono essere eventualmente specializzate per modificarne il comportamento o per inserire capacità addizionali. In realtà questi toolkit sono filosoficamente tutti orientati ad oggetti ma codificati in C standard. Questo più che altro per motivi storici, dato che il C++ al tempo in cui furono sviluppati non era ancora del tutto affidabile. Si parla dunque di classi di widget, istanze di widget, metodi di widget ma tutto è implementato in maniera manuale seguendo le convenzioni di programmazione C. Esistono comunque dei toolkit commerciali sviluppati direttamente in C++.

Per questioni di tempo, illustreremo brevemente i fondamenti di Xt per avere una idea del tipo di funzionalità che fornisce. I concetti che sono introdotti in questo toolkit possono essere così riassunti:

Widget Gerarchia di oggetti Callback Resource Action & Translation

Approfondiamo questi concetti analizzandoli uno alla volta.

Widget: è una finestra articolata in componenti in grado di gestire completamente l’interazione con l’utente. Un widget contiene all’interno un certo numero di oggetti che vengono gestiti in maniera autonoma. Ad esempio, se si modifica la dimensione di un widget, tutti gli oggetti che esso contiene vengono automaticamente riposizionati e ridimensionati senza che l’applicazione se ne debba preoccupare;

Callback: è un meccanismo che astrae il concetto di gestione degli eventi. Il programma che usa Xlib deve preoccuparsi di gestire uno per uno gli eventi che vengono generati: estrarli dalla coda, decidere il da farsi e intervenire in maniera opportuna. Il tutto è gestito interamente da codice scritto dal programmatore. Usando il toolkit, la gestione degli eventi è affidata ad una procedura specifica del toolkit che si preoccupa di leggere gli eventi ricevuti e invocare le opportune callback per ciascun tipo di evento. Un callback non è altro che la procedura che è stata designata dal programmatore a gestire un determinato tipo di evento. L’applicazione scritta con il toolkit deve indicare gli eventi a cui è interessata e associare ad essi delle procedure (callback). È compito del mainloop del toolkit individuare quale callback deve essere invocato e passargli il controllo. Può anche esserci più di un widget interessato a un particolare evento e in questo caso il toolkit invocherà in ordine tutti i callback: ciò risulta più difficile da realizzare se la gestione degli eventi è fatta manualmente dal mainloop scritto dal programmatore;

Resource: rappresenta un particolare aspetto di un widget che può essere il colore, il font, la dimensione, ecc. che invece di essere prefissato o rigidamente stabilito all’interno di un programma con

18

Page 19: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

delle costanti è gestito dall’esterno e condiviso tra più widget. Per esempio si potrebbe volere che tutti i menù usati all’interno di una stessa applicazione siano dello stesso colore. Se il colore del menù è una risorsa è possibile indicarne il valore separatamente e l’effetto dell’assegnazione sarà propagato a tutti i widget che faranno uso di quella risorsa;

Action & Translation: è un’estensione del meccanismo di callback. È possibile definire, oltre alle callback specificatamente designate dentro il widget, una propria istanza di widget.

1.10.1 Un esempio

Vogliamo sviluppare una semplice applicazione sfruttando il toolkit Xt. Si tratta di creare una finestra che contiene due pulsanti. La pressione di uno di questi provoca la visualizzazione di un semplice messaggio, l’altro la chiusura dell’applicazione.

Figura 1.14: Esempio di applicazione Xlib

Come nella maggioranza dei casi, anche in questo esempio l’applicazione è articolata sulla base di una gerarchia di finestre, contenute all’interno di una finestra principale detta application shell, per la cui inizializzazione esistono delle primitive specifiche. All’interno della nostra shell vogliamo creare una scatola (box) con due pulsanti, che nella terminologia di Xt sono chiamati command. La box è un widget contenitore che serve come supporto ai widget figli, i command nel nostro caso. La struttura delle finestre è la seguente:

Figura 1.15: Gerarchia dei widget nel programma di esempio

Un certo widget non è solo contenuto in una certa finestra ma è anche gestito (managed) dalla finestra genitore. Ciò consente di ridimensionare e/o riposizionare i widget in seguito, ad esempio, a ridimensionamenti della finestra dell’applicazione. Quando un widget viene creato con XtCreateWidget non è vi è nessuno che lo gestisce. Serve una chiamata esplicita a XtManageChild(w) per far si che il genitore di w lo prenda in gestione. Alternativamente è possibile creare un widget già gestito con XtCreateManagedWidget.

Il nostro programma deve come prima cosa creare la shell dell’applicazione. Per far ciò usiamo una funzione che svolge una parte dei compiti che altrimenti dovrebbero essere affidati a varie primitive di Xlib tra cui, ad esempio, la connessione con il server X e l’apertura del display. Come risultato la funzione restituisce un oggetto di tipo Widget, ma si aspetta anche in input il puntatore ad un oggetto di tipo XtAppContext dove memorizza informazioni relativi all’intera applicazione.

static XtAppContext app;

19

start

Page 20: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

Widget toplevel;

int main(int argc, char **argv) { toplevel = XtAppInitialize(&app, "command test", NULL, 0, &argc, argv, NULL, NULL, 0); ...}

Gli altri parametri di XtAppInitialize sono il nome della classe dell’applicazione (utile soprattutto per la gestione delle risorse, come vedremo nel seguito), i parametri della linea di comando e varie liste di opzioni che attualmente non ci interessano e che poniamo a NULL. A questo punto possiamo passare a creare le finestre della nostra applicazione.

box = XtCreateManagedWidget(argv[0], boxWidgetClass, toplevel, NULL, 0);comm1 = XtCreateManagedWidget("Hi", commandWidgetClass, box, NULL, 0);comm2 = XtCreateManagedWidget("Quit", commandWidgetClass, box, NULL, 0);

Utilizziamo XtCreateManagedWidget in modo che i nostri widget siano automaticamente gestiti dalle finestre genitrici. Il primo parametro della funzione è il nome della finestra, che, per quanto riguarda i pulsanti, appare anche fisicamente all’interno del riquadro che costituisce la loro rappresentazione. Il secondo parametro identifica la classe di widget a cui siamo interessati, segue poi il widget genitore e il puntatore ad una lista di opzioni. Il numero di opzioni effettivamente presenti nella lista è indicato nell’ultimo argomento.

Adesso dobbiamo associare un comportamento alle varie finestre. Vogliamo che quando si clicca sul pulsante “Hi” venga scritto Hi mentre vogliamo uscire dall’applicazione in seguito alla pressione del pulsante “Quit”. Se lavorassimo a livello di Xlib dovremmo creare un mainloop in cui andiamo ad analizzare e gestire ad uno ad uno gli eventi. Programmando con il toolkit possiamo semplicemente indicare ai due widget la funzione callback da invocare quando il pulsante viene pigiato.

XtAddCallback(comm1, XtNcallback, hi, NULL);XtAddCallback(comm2, XtNcallback, quit, NULL);

Il primo parametro è il widget interessato, segue il tipo di callback (XtNcallback o XtNdestroyCallback), la funzione da chiamare ed infine un puntatore da passare alla procedura callback quando viene invocata. Le procedure callback hanno tre parametri:

il widget su cui è avvenuto l’evento che ha invocato la callback; il puntatore fornito durante la dichiarazione del callback; ulteriore puntatore a parametri che il toolkit passa alla funzione callback.

Vediamo il codice sorgente delle due funzioni. La chiamata a XtDestroyApplicationContext libera tutte le risorse X utilizzate dal programma.

void hi(Widget w, // C++ thisXtPointer closure, // dati forniti tramite XtAddCallbackXtPointer calldata) { // specifici della chiamata, es. posizione scrollbar

   printf(“Hi!\n”;)}

void quit(Widget w, XtPointer p, XtPointer c) {    printf("Bye\n");    XtDestroyApplicationContext(app);    exit(0);}

A questo punto il programma è praticamente finito. Occorre solo far comparire fisicamente le finestre sullo schermo e attivare il main loop:

XtRealizeWidget(toplevel);XtAppMainLoop(app);

La XtRealizeWidget equivale grosso modo ad eseguire una XMapWindow su tutte le finestre dell’applicazione. Si noti inoltre che in nessuna delle nostre chiamate di funzione abbiamo mai dato indicazioni sulla posizione o le dimensioni delle finestre. A tutto ciò pensa automaticamente Xt.

20

Page 21: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

La XtAppMainLoop implementa il ciclo di rilevamento e gestione eventi: individua le finestre coinvolte, stabilisce se e quale callback deve essere chiamata ed eventualmente la invoca passando tutti i dati previsti dal caso, riprendendo poi il controllo al suo ritorno.

Per potere effettivamente compilare questo esempio occorre includere alcuni file

#include <stdio.h>#include <X11/Xlib.h>#include <X11/Intrinsic.h>#include <X11/StringDefs.h>#include <X11/Xaw/Command.h>#include <X11/Xaw/Box.h>

e compilare il programma con le librerie X11, Xt ed Xaw.

1.10.2 Interazione di Xt con Xlib

Nell’esempio che abbiamo appena visto il toolkit si occupa di tutto. Per compiti anche leggermente più complicati, però, non si può più fare a meno delle chiamate dirette ad Xlib. Ad esempio, supponiamo di voler modificare il pulsante “Hi” in modo che tracci un disegno nella finestra invece di scrivere sullo standard output.

Innanzitutto occorre cambiare il callback associato sostituendo alla funzione hi() quella che si occupa di fare il disegno di nostro interesse.

XtAddCallback(comm1, XtNcallback, draw, NULL);

Le funzioni del toolkit non replicano le primitive di tracciamento per cui occorrerà ricorrere alla più tradizionale Xlib. Come sappiamo, però, ogni operazione grafica richiede un puntatore al display, una finestra su cui tracciare e il graphics context. Queste entità non campiono esplicitamente in una applicazione scritta usando il toolkit, ma possono essere ricavate facilmente. Il display può essere richiesto al widget toplevel con la funzione XtDisplay mentre per ogni widget, la finestra ad esso associata è restituita dalla funzione XtWindow.

dpy = XtDisplay(widget);win = XtWindow(box);gc = DefaultGC(dpy, DefaultScreen(dpy));

A questo punto il tracciamento effettivo del disegno procede come nel caso di una applicazione Xlib pura:

XDrawLine(dpy, win, gc, 150, 50, 200, 74);XFillRectangle(dpy, win, gc, 50, 50, 100, 100);

Ecco la nuova versione completa del callback:

void hi(Widget w, XtPointer closure, XtPointer calldata) {   Display * dps = XtDisplay(widget);   Window parent = XtWindow(XtParent(widget));   GC gc = DefaultGC(dpy, DefaultScreen(dpy));   XfillRectangle(dpy, parent, gc, 50, 50 100, 100);}

Talvolta è necessario o semplicemente preferibile affiancare alla gestione degli eventi tramite callback la possibilità di gestire un evento in maniera diretta. In Xt ciò è possibile con gli EventHandler. Indicando il tipo di evento a cui siamo interessati e la funzione che lo deve gestire, possiamo affidare al main loop del toolkit il compito di chiamare la nostra funzione quando tale evento si verifica, in maniera simile ai callback.

Nel nostro esempio potremmo volere trattare in maniera diretta l’evento exposure. Aggiungiamo allora al nostro programma

XtAddEventHandler(box, ExposureMask, False, exposed, NULL);

in modo che ogni evento exposure diretto al widget box venga intercettato e passato alla procedura exposed. Questa è così definita:

void exposed(Widget w, XtPointer c, XEvent* ev, Boolean* b) {

21

Page 22: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

printf("Exposed\n");}

I tre parametri che vengono passati all’event handler sono il widget dove si è verificato l’evento, il solito puntatore a dati che abbiamo eventualmente fornito con la chiamata a XtAddEventHandler, l’evento intercettato e un valore booleano.

1.10.3 Risorse

È una proprietà individuabile da un nome e da un valore ad esso associato. Le proprietà possono essere organizzate per classi in modo tale da far condividere ad una stessa classe di oggetti le stesse risorse. In questo modo tutte le finestre di un certo tipo all’interno della stessa applicazione possono avere facilmente stesso font o stesso colore.Per descrivere le risorse si utilizza una notazione basata su coppie del tipo “nome risorsa: valore”. Il nome può riferirsi ad un singolo widget o ad un’intera classe di widget. Ad esempio

mailer*font : –adobe–courier–*–*–*–14–*

indica che le finestre di classe mailer, che è la classe cui appartengono le finestre dell’applicazione omonima, devono avere tutte un certo font. Analogamente

mailer*menu.font: –adobe–courer–*–*–*–18–*

specifica un font di tipo diverso per una determinata sottoclasse delle finestre di classe mailer, quella associata ai menù. Le risorse possono essere definite in tre modi diversi:

all’interno dell’applicazione mediante particolari struttura dati in C, tramite l’uso di file esterni, con opzioni sulla linea di comando.

Nel primo caso la struttura dati da utilizzare si chiama XtResource ed è così definita:

typedef struct { String resource_name; String resource_class; String resource_type; Cardinal resource_size; Cardinal resource_offset; String default_type; XtPointer default_addr;}

Per esempio è possibile definire una risorsa come segue:

static XtResource res = { XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel), …, XtDefaultForeground}

In realtà è più conveniente definire le risorse esternamente all’applicazione in un file. Ad ogni applicazione è associato un file che si trova nella directory /usr/lib/X11/app-default e che ha come nome il nome della classe dell’applicazione utilizzato nella chiamata a XtAppInitialize. Questo file contiene una serie di linee di testo del tipo:

*foreground: red*font: –adobe–courier–*–*.*

e simili. Quando un’applicazione Xt parte con la chiamata a XtAppInitialize viene automaticamente letto il file con le risorse che sono poi utilizzate ovunque necessario. Si noti che risorse quali foreground o font sono standard e utilizzate dai tutti i widget Xt ma è anche possibile definire nuove risorse.

Un altro file che viene utilizzato per specificare il valore delle varie risorse è ~/.Xdefaults o ~/.Xresources a seconda dei casi. Questo viene letto al momento dell’attivazione del server X (ma può essere riletto successivamente tramite il comando xrdb), ha la stessa sintassi dei file in app-default ma si riferisce a tutte le applicazioni anziché ad una specifica. Ne segue che delle linee come quelle di sopra setterebbero il colore di primo piano e il font per tutte le applicazioni X, a meno che questi non siano ridefiniti da risorse più specifiche.

22

Page 23: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

Per specificare in .Xdefaults delle risorse relative alla singola applicazione, occorre mettere prima di * il nome della classe dell’applicazione a cui vogliamo riferirci. Ad esempio:

command test*foreground: redcommand test*font: –adobe–courier–*–*.*

Infine, un ultimo modo per specificare le risorse è dalla linea di comando. Quando si chiama la XtAppInitialize passiamo come parametri argc e argv. La linea di comando viene quindi analizzata dal toolkit che riconosce alcune opzioni particolari per settare i valori di alcune risorse (ad esempio –fg per il foreground o –fn per il font).

1.10.4 Action & Translation

Le risorse forniscono un modo per particolareggiare l’aspetto di un widget. Un altro modo è quello di associare ad essi delle azioni particolari che sono aggiuntive o diverse da quelle di default. Per comprendere come ciò possa avvenire guardiamo più in dettaglio come funziona il main event loop del toolkit.

Ogni qual volta riceve un evento il tookit deve:1. trovare la finestra che ha generato l’evento;2. risalire la gerarchia delle finestre fino a trovare un widget;3. controllare se l’evento è compreso nella tabella di traduzione del widget;4. se sì, estrarre l’azione corrispondente; se no, salta a 7;5. estrarre la procedura associata nella tabella delle azioni;6. invocare la procedura con il widget e gli altri dati necessari;7. cercare eventuali event handler e invocarli;8. prendere il prossimo evento.

L’azione di invocazione di callback è solo una delle possibili azioni associate con un certo evento. Altre azioni sono, ad esempio, la discesa di menù a tendina, la visualizzazione in reverse di un command, ecc. Un utente può quindi modificare il comportamento di un widget alterando la tabella di traduzione.È possibile creare una nuova tabella di traduzione tramite la procedura XtParseTranslationTable a cui va passato come parametro una stringa che rappresenti una descrizione simbolica delle azioni da compiere a seguito dei vari eventi. Ad esempio

button_actions = XtParseTranslationTable( “<Btn1Up>: Unset()\n\ <Key> q: Quit()\n\ <Btn1Down>:Set() MenuPopup(menu)\n”);

La tabella di traduzione va poi sostituita al posto di quella standard prevista per un certo pulsante con

XtOverrideTranslations(popbutton, button_actions);

dove popbutton è il widget pulsante e button_actions la tabella di traduzione precedentemente definita.In questo modo possiamo utilizzare i widget presenti nel toolkit modificandoli per soddisfare nuove

esigenze. Infine, quando le circostanze lo richiedano, è possibile anche creare dei widget completamente nuovi, anche se questo è raramente il caso visto che la libreria Xaw è fornita di numerosi tipi di widget per tutte le esigenze (pulsanti, menù, radio button, multichoice button, editor di testi, ecc.) che possono essere agevolmente combinati tra di loro.

1.10.4.1Altri Toolkits Microsoft Windows X Windows: Athena Toolkit X Windows: Motif Toolkit X Windows: OpenLook Toolkit Abstract Window Toolkit (AWT): Java

1.10.4.2Virtual Toolkits

I widget che si ritrovano in vari toolkit si assomigliano parecchio, eccetto che per lievi differenze cosmetiche. Se si volesse rendere disponibile un’applicazione su piattaforme diverse, quali X Windows, Macintosh, Microsoft Windows, si trova costretto ad effettuare il port dell’applicazione su tre diversi toolkit:

X Windows: using Motif Macintosh: using Macintosh Toolbox

23

Page 24: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

Microsoft Windows: using the Microsoft toolkit

Tuttavia … c’è un lavoro considerevole da fare per portare un’applicazione da Motif al Macintosh … e quindi a Microsoft Windows. Una soluzione è quella di usare dei virtual toolkit, che consentono di programmare l’applicazine una volta sola, usando dei virtual widget. Il programma funzionerà senza cambiamenti (eccetto che una ricompilazione) sulle varie piattaforme supportate dal virtual toolkit. I virtual toolkit sono anche detti sistemi di cross-platform development.

Ci sono due tipi di virtual toolkit.

Actual widgets the virtual toolkit links to the actual toolkit on the host machine XVT

provides a C or C++ interface that links to the actual widgets of Motif, OpenLook, Macintosh, Microsoft Windows, and OS/2 Presentation Manager

Vantaggi producono interfacce che seguono il look-and-feel di ciascuna piattaforma

Svantaggi must provide an interface to graphical drawing primitives on each platform tend to only provide widgets/functions common to all toolkits; toolkit specific style features are lost

Re-implemented widgets the virtual toolkit re-implements the widgets for eah supported platform Examples: Galaxy and OpenInterface:

each re-implements all of the widgets on the various platforms Svantaggi librerie run-time molto grandi

1.10.4.3Application Frameworks

I toolkits risultano piuttosto ostici da utilizzare:

how and when are the various toolkit functions called are the correct user interface guielines being followed for the platform

Application frameworks are very popular now:

usually object-oriented classes are provided for all of the various widgets programmers specialze the classes (by using inheritance) to implement behavior unique to their application classes are also provided for other important features of the platform and hide much of the complexity of the underlying API application frameworks encapsulate the API often contain interface builders

Come esempi di application frameworks citiamo:

Microsoft Windows: Microsoft Foundation Classes (MFC) Borland's Object Windows Library (OWL) Macintosh: CodeWarrior PowerPlant

Other frameworks are for specific types of applications:

graphical editors/applications: Unidraw, Amulet graph programs: Edge, TGE network programming: ACE

1.10.4.4Programmazione Event Driven

Older character-based or form-based user interfaces created a sequential, linear interaction mechanism when presented with a selection menu, users had no choice but to make a selection it was not possible to manipulate some other aspect of the application

24

Page 25: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

the application was in control

A sequential, linear interaction mechanism does not work well with the direct manipulation style of interaction found on most GUIs

users see visual representations of objects users manipulate the objects directly to perform commands/actions at any time the user desires the user is in control

Applications cannot pause execution and just wait for the user to make a selection. Instead, an event-driven model of interaction is used.

1.10.4.5Modello di interazione Event Driven

Le applicazioni in esecuzione (di solito più di una contemporaneamente) attendono eventi:

Mouse button: LeftButtonDown Mouse movement Keyboard: KeyUp, KeyDown Windowing: resize, close, destroy, move, minimize

Le API grafiche adottano il motto “non chiamarci, ti chiamiamo noi”: quando l’utente pigia il bottone sinistro del mouse, per esempio, viene generato un evento LeftButtonDown, dal quale consegue che la API invoca il codice dell’applicazione per rispondere alla pressione del bottone.

Ci sono diverse tecniche per implementare la gestione degli eventi (event handling):

callbacks (X Windows) window procedures (Microsoft Windows) istruzioni switch/case (Macintosh) object-oriented techniques (MFC, Java)

1.10.4.6Gestione Eventi: switch/case

La tecnica delle istruzioni switch (o case) è comune nel Macintosh.Il Macintosh Toolbox consiste di diversi moduli, uno dei quali è l’EventManager che è responsabile per

inserire gli eventi nella coda di eventi di ciascuna applicazione.Le code si comportano in modalità “first-in-first-out” (FIFO), tuttavia alcuni eventi hanno priorità più alta e

devono essere trattati anticipatamente rispetto al loro ordine FIFO.Dopo che l’applicazione ha completato le sue inizializzazioni, entra in un main event loop, dove:

l’applicazione esamina ripetutamente la sua coda di eventi per individuare nuovi eventi si esamini il tipo dell’evento per determinare quali azioni il programma deve compiere generalmente è presente un lungo g ruppo di istruzioni switch/case per invocare le routine corrette a seconda del tipo dell’evento.

Ecco un tipico event loop per il Macintosh. Tramite eventmask si filtrano gli eventi che non interessano all’applicazione.

// initialize application ...// main event loopdo { // get an event from the queue getnextevent(eventmask, &event);

// handle the event switch (event.type) { case mousedown: DoMouseDown(event); break;

// lots and lots of case statements !

25

Page 26: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

// quit case quitmessage: done = true; break; }} while (!done);

1.10.5 Gestione Eventi nei Framework a Oggetti

Un application frameworks consiste di un libreria di classi astratte che: incapsulano i widgets incapsulano la complessità di una API

I programmatori usano sottoclassi (ereditarietà) per implementare i propri programmi con il comportamento richiesto per l’applicazione. Sotto il toolkit, il meccanismo degli eventi del sistema di finestre rimane lo stesso, ma invece di utilizzare procedure callback o codice all’interno delle WindowProc, il framework invoca metodi particolari delle classi, chiamati handlers, quando viene ricevuto un messaggio. Lo sviluppatore deve programmare questi handler in modo che i widget nella loro applicazione rispondano agli eventi nel modo desiderato.

Per esempio, usando le Microsoft Foundation Classes, per fornire il codice da eseguire quando il bottone “submit” viene premuto, basta aggiungere un gestore per il messaggio BN_CLICKED in modo che venga invocato il metodo OnClickSubmit() in corrispondenza di tale messaggio:

void CMyDialog::OnClickSubmit(){ ...}

Alcune classi già forniscono una funzione virtuale per certi messaggi. Per esempio, tutte le finestre hanno una routine chiamata OnPaint() che viene invocata quando si riceve il messaggio WM_PAINT. Questo metodo fa parte della classe CWnd. Pertanto, per specializzare il comportamento di ridisegno di una finestra, basta fornire un metodo OnPaint() nella classe derivata. In questo caso non occorre aggiungere un gestore perché è già presente nella classe base.

1.10.6 Gestione degli Eventi in Java

1.11 Motif

Uno dei toolkit più diffusi è Motif, della Open Software Foundation (OSF). La figura seguente illustra i widget disponibili in Motif.

26

Page 27: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

1.12 Tcl/Tk

Tcl e Tk formano assieme un ambiente per la programmazione grafica sotto X. È concettualmente simile al toolkit Xt nel senso che è implementato tramite Xlib e si trova ad un livello superiore ad essa, pur senza oscurarla completamente. La particolarità di Tk è quella di essere programmabile: a differenza di altri toolkit che si presentano come una collezione di procedure per linguaggi compilati, Tk è fornito di un proprio ambiente di sviluppo e di un linguaggio (Tcl) che consente di realizzare delle procedure (dette script) per Tk senza alcun bisogno di compilazioni.

L’interprete Tk è accessibile mediante il comando wish di Unix. Questo provvederà ad aprire una finestra vuota dove verrà inviato tutto l’output grafico mentre sulla finestra di terminale dove è stato invocato appare il prompt wish> di Tk. L’interprete è interattivo: gli si possono dare dei comandi che vengono direttamente eseguiti come se facessero parte di un programma. La sintassi di questi comandi è simile a quelli della shell di Unix. In più, tuttavia, ha dei comandi specifici per la programmazione in grafica.

Un esempio di comando è expr che prende come parametro un’espressione e ne restituisce il risultato. Ad esempio:

expr 2+24

Si hanno a disposizione tutti gli operatori aritmetici del C compreso, ad esempio, lo shift logico. Per uscire da Tcl si usa invece il comando exit.

Un esempio di comando per la programmazione grafica è il seguente:

button .b –text “Hello, world!” –command exit

27

Page 28: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

Lo schema generale di comando Tcl è formato dal nome del comando seguito come primo argomento dal nome da dare al risultato dell’operazione. Seguono possibilmente uno o più parametri opzionali, preceduti da – nello stile dei comandi Unix.

Nel nostro caso button è il comando per creare un pulsante e .b il nome dell’oggetto creato. Il . è utilizzato per indicare una struttura gerarchica degli oggetti. Le finestre create con i comandi Tk possono infatti essere create l’uno dentro l’altra ed è necessaria una notazione per rappresentare questa struttura. Perciò, ad esempio, .b.c.d sta ad indicare che la finestra b è contenuta nella c e quest’ultima nella d. Il . da solo sta ad indicare la finestra più esterna.

L’opzione text indica ciò che deve essere scritto dentro il pulsante mentre l’opzione command specifica il comando da eseguire quando il pulsante viene premuto, in questo caso exit che provoca l’uscita dall’interprete Tcl/Tk.

Analogamente a quanto avviene con la Xlib, il comando button serve solo a creare il pulsante, che però non appare sullo schermo. Per visualizzarlo occorre un’istruzione apposita:

pack .b

che indica di far appare il widget che si trova nella finestra b. Pack vuol dire “attaccato” ed infatti visualizzando vari oggetti con questo comando essi saranno messi il più possibile vicini tra di loro, senza alcuno spazio separatore. Gli oggetti appaiono nella finestra che era stata predisposta al momento della partenza di Tk, che viene eventualmente ridimensionata o espansa per essere esattamente delle dimensioni adatte a contenere tutti i widget.

Osserviamo che i parametri da dare ai vari comandi sono specifici a seconda del tipo di widget. Ad esempio non tutti i comandi avranno l’opzione text. A tutti i widget si applicano però le opzioni foreground, background e font. Ovviamente la cosa interessante di Tk è che i comandi possono anche essere inseriti in dei file per creare delle applicazioni autonome. Ponendo all’inizio del file una riga della forma:

#!/usr/local/bin/X11/wish

si indica alla shell di Unix che il file è uno script che deve essere eseguito dall’interprete indicato dopo #! (è importante mettere il percorso completo). A questo punto dando l’attributo eseguibile al file col comando chmod, è possibile eseguirlo direttamente come se fosse un programma compilato.

1.12.1 Gerarchia dei widget

Figura 16: Gerarchia di widget Tk

#! /usr/local/bin/wish

frame .menu -relief raised -borderwidth 2menubutton .menu.file -text Filemenubutton .menu.help -text Helplistbox .listboxscrollbar .scroll

pack .menu -side top -fill xpack .menu.file -side leftpack .menu.help -side rightpack .listbox -side leftpack .scroll -side right -fill y

.listbox insert 0 Hawaii Idaho Illinois Indiana

28

Page 29: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

Principali tipi di widget di Tk:Frame frame name -relief

Labels label name -text label

Buttons button name -text label -command procedureradiobutton name -text label -variable variablecheckbutton name -text label -variable variable

Messages message name -text message

Listboxes listbox namename insert position itemname delete itemname get item

Scrollbar

Scale

Entries

Menu menu namename add command -label label -command procname add separatorname add checkbutton -label label

Pulldown Menu menubutton name -menu menu

1.12.2 Un esempio in Tk

Il linguaggio Tcl è completo per quanto riguarda strutture di controllo, di flusso e strutturazione a blocchi. Vediamo, ad esempio, una procedura Tcl per il calcolo della potenza:

proc power { base p } { set result 1 while {$p > 0} { set result [expr $result * $base] set p [expr $p – 1] } result $result}

Il programma è perfettamente comprensibile in quanto Tcl è pur sempre un linguaggio imperativo come il C. Le variabili base e p sono i parametri della procedura. L’assegnamento utilizza il comando set come nella shell di Unix e, sempre analogamente alla shell, si accede al valore di una variabile premettendone il nome dal segno $.

Vogliamo ora dare una interfaccia grafica alla procedura. Quello che vogliamo realizzare è un’applicazione che si presenti con due campi numerici da riempire e che calcoli automaticamente il risultato del loro elevamento a potenza ogni qual volta viene premuto il tasto =.

Figura 1.17: applicazione di esempio

Occorre dunque scrivere uno script che comprenda la procedura power e costruisca la struttura grafica dell’interfaccia utente con tutti i widget necessari, associando ad essi un comportamento. Il comportamento dei widget viene specificato creando delle associazioni, cioè dei binding di eventi con operazioni: quando un certo evento si verifica, l’operazione specificata sarà eseguita.

Innanzitutto occorrono due finestre per editare il testo. Questo tipo di widget è già previsto in Tk e si chiama entry. Ne abbiamo bisogno di uno per la base e di un altro per l’esponente.

29

Page 30: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

entry .base –width 6 –relief sunken –textvariable baseentry .power –width 6 –relief sunken –textvariable power

I parametri del comando entry sono: il nome dell’oggetto, come abbiamo già visto per button, la larghezza della finestra di editing espressa in numero di caratteri; lo stile grafico del campo (sunken vuol dire affondato, rientrante), che per molti aspetti è simile allo stile delle finestre Motif; la variabile destinata a contenere il valore presente nella entry. Leggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore, possiamo modificare da programma il contenuto del widget.

Bisogna poi inserire i simboli di elevamento a potenza, l’uguale e il risultato dell’operazione. Usiamo questa volta il comando label:

label .label1 –text "^"label .label2 –text "="label .result –textvariable result

Alla fine visualizziamo il tutto con il comando pack:

pack .base .label1 .power .label2 .result –side left –padx 1m –pady 2m

dove l’opzione side left specifica che gli oggetti vanno visualizzati l’uno accanto all’altro a partire da sinistra mentre padx e pady specifica le distanza (in questi casi in millimetri grazie alla m dopo il numero) tra due widget e tra widget e bordo della finestra esterna, rispettivamente in orizzontale e in verticale. Lasciamo il compito di visualizzare l’etichetta POWER al windows manager: la finestra principale di un programma Tk ha infatti come titolo il nome del file dove risiede l’applicazione. Basta chiamare POWER questo file e il gioco è fatto.

Il problema è che adesso abbiamo l’interfaccia utente ma ad essa non è associato alcun comportamento, ance se possiamo riempire i campi delle entry poiché i widget sono gestiti interamente da Tk. Decidiamo di calcolare la potenza tra i due numeri base ed esponente ogni qualvolta premiamo il tasto return dentro uno dei campi di immissione. Il comando per fare ciò è:

bind .base <Return> {set result [power $base $power]}bind .power <Return> {set result [power $base $power]}

dove il power dentro le parentesi quadre è la procedura Tcl che abbiamo definito pocanzi. Si noti che Tk manipola stringhe, e ove necessario, ad esempio per le operazioni aritmetiche, fa conversioni al volo tra stringhe e numeri.

Oltre al modo standalone (interattivo o da programma) è possibile usare Tk anche in mondo embedded. Infatti Tk è costruito con una libreria che può essere linkata con programmi in ObjectC. In questo modo è anche possibile estendere il linguaggio Tcl. Un’altra possibilità è quella di costruire interfacce direttamente in maniera grafica (visuale) con un tool che si chiama XF. Invece di scrivere una serie di comandi come fatto finora, si può editare direttamente sul video l’aspetto dell’interfaccia utente, scegliendo tra i vari widget a disposizione, selezionando font, colore e tutti i vari attributi e scrivendo script Tcl per descriverne il comportamento

1.12.3 Ulteriori caratteristiche di Tcl/Tk

Vediamo più in dettaglio ulteriori caratteristiche del linguaggio Tcl. Prima di tutto i tipi di dato: ci sono le stringhe i numeri interi e vari tipi di dato strutturati come liste ed array. Per i tipi lista ci sono numerose funzioni predefinite: concat, join, lappend, lindex, linsert, list, llength, lrange, lsearch, lsort, split, ecc...Una lista è semplicemente una serie di oggetti separati da spazi. Gli elementi di una lista possono essere selezionati uno alla volta col comando lindex dandone la posizione relativa (0 è la prima). Ad esempio

wish> lindex { John Ann Mary } 1Ann

Più liste possono essere concatenate col comando concat

wish> concat { a b } { c d }a b c d

30

Page 31: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

mentre il comando split, data una stringa e un carattere, restituisce una lista ottenuta dividendo la stringa in input in corrispondenza di ogni separatore:

wish> set a /usr/local/binwish> split $a /{} usr local bin

Comportamento opposto ha il comando join che data una lista e un catattere, concatena tutti gli elementi della lista separandoli col carattere indicato:

wish> join { {} a b c } //a/b/c

I costrutti di controllo di Tcl sono simili a quelli del C: if, while, for, break, continue, switch. In più abbiamo foreach che serve per iterare una operazione su tutti gli elementi di una lista.

Per quanto riguarda più espressamente Tk, esaminiamo quali sono i widget disponibili. La forma più semplice è la finestra vuota, denominato frame. Esso viene usato come raccoglitore di altri widget, in maniera analoga alla box di Xt. Il suo aspetto, come quello di tutti i widget Tk può essere piano, sollevato oppure ribassato.

Altro tipo di widget è la label. Il contenuto di una label può essere un testo o un’immagine (cosa, comunque, che vale in genere per tutti i tipi di oggetti). Alle label non si può associare alcun comportamento. Simili alle label sono i pulsanti (button) ai quali, però, si può associare un comportamento corrispondente all’azione di pressione. Come sempre, quando bisogna scegliere tra varie alternative, ci sono due tipi di widget diversi: i check button, nelle quali ogni voce può essere o meno selezionata e i radio button, nella quale le scelte si escludono a vicenda.

Tra i widget più usati vi sono anche i menù, che si suddividono in pulldown, pop up e cascaded. I primi sono associati alla solita barra dei menù. I secondi, invece, possono apparire ovunque sullo schermo. Infine, gli ultimi sono quelli che appaiono in seguito a scelte fatte in altri menù e che richiedono ulteriori specifiche.

Abbiamo poi listbox, ovvero liste di elementi non modificabili, entry, che abbiamo visto nell’esempio, scrollbar orizzontali e verticali. Simile alle entry sono i text, che consentono però di scrivere testo che si estende per più di una riga. Un widget particolare che permette di visualizzare valori numerici è lo scale. L’utente può posizionare il cursore scorrevole col mouse e far assumere il valore voluto allo scale. Infine ci sono i canvas, aree dove è possibile disegnare con le primitive di Xlib.

1.12.4 Interfaccia C di Tk

Tk adotta una metafora ad oggetti, pur non essendo implementato in un linguaggio ad oggetti, ma in C. Tk adotta quindi opportune convenzioni di progammazione per simulare I meccanismi della programmazione ad oggetti.

1.12.4.1Definizione di classi

Per definire una classe, occorre creare la struttura dato che contiene le informazioni della classe e creare il metodo fondamentale di una classe, quello per la creazione delle sue istanze. Per convenzione in Tk, il metodo per instanziare una clase è una procedura che ha il nome della classe. Per esempio la classe Button usa la procedura button. Tale procedura viene inserita tra i comandi disponibili in un interprete TCL, mendiante la procedura Tcl_CreateCommand().

typedef struct {Tk_Windows tkwin, … } Class;

Tcl_CreateCommand(interp, “class”, ClassCmd, NULL, NULL);

1.12.4.2Istanziazione di classi

Ecco cosa deve fare la procedura che istanzia una classe:

ClassCmd(mainwin, interp, argc, argv[ ]) { tkwin = Tk_CreateWindowFromPath( … ); Tk_SetClass(tkwin, “Class”); inst = malloc(sizeof(Class)); Tk_CreateEventHandler(tkwin, Mask, ClassEventProc, inst); interp->result = Tk_PathName(tkwin);

31

Page 32: 1medialab.di.unipi.it/web/IUM/Programmazione/X11.doc · Web viewLeggendo il valore di questa variabile avremo come risultato il contenuto della entry e viceversa, modificando il valore,

Tcl_CreateCommand(interp, argv[1], ClassWidgetCommand, …);}

L’ultima istruzione crea un comando che ha il nome dell’oggetto creato (argv[1]) e che svolge le funzioni di dispatch dei messaggi per quell’oggetto.

1.12.4.3Method dispatch

I metodi associati ad una classe sono trattati da una procedura di dispatch associata alla classe. Questa procedura viene invocata ogni volta che viene richiesta un’operazione sull’istanza di quella classe. La procedura al suo interno determina il messaggio richiesto, analizzando argv[ ], ed invoca il metodo corrispondente.

ClassWidgetCmd(this, interp, arc, argv[ ]) {…}

32