Paola Magillo, Univestita' di Genova, Corso di Programmazione II per SMID, a.a. 2005-2006.

Lezione 09:

UN'INTERFACCIA GRAFICA PER I RETTANGOLI

Obiettivo

Facciamo un programma per disegnare rettangoli e compiere operazioni su di essi. Le operazioni sono:

  1. Aggiungere un nuovo rettangolo stabilendo posizione e dimensioni
  2. Cancellare un rettangolo
  3. Stabilire il colore di un rettangolo
  4. Portare un rettangolo sopra o sotto gli altri, dove per sopra / sotto si intende l'ordine di disegno, i rettangoli sopra (disegnati dopo) coprono quelli sotto (disegnati prima)
  5. Spostare un rettangolo

Da che cosa partiamo?

Che cosa dobbiamo fare?

Il materiale di partenza

Interfaccia MyRectangle

File MyRectangle.java.
Stabilisce che la nostra classe per il rettangolo deve avere i seguenti metodi:

Classe RectanglePainter

File RectanglePainter.java.
Non guardiamo come e' fatta dentro, ma solo come si comporta e quali metodi interessanti ha. La useremo cosi' come e', senza modificarla.

E' sottoclasse di Panel, quindi ne eredita le caratteristiche. A differenza pero' di un Panel, che e' pensato per contenere altre cose, un RectanglePainter e' pensato per restare vuoto e disegnare i rettangoli sul proprio sfondo.
Mentre le dimensioni di un Panel di norma vengono calcolate in base alle dimensioni del contenuto, un RectanglePainter stabilisce all'atto della costruzione quali sono le sue dimensioni preferite.
Nota: le dimensioni preferite (ved. lezione 8) verranno poi usate dai layout manager della gerarchia di contenimento in cui si trova per stabilire le sue dimensioni effettive all'interno dell'interfaccia grafica.

I rettangoli presenti sono disegnati pieni e ognuno del suo colore. I rettangoli presenti hanno un ordine da "sotto" a "sopra", tale ordine per default e' quello in cui sono stati aggiunti (il piu' veccchio sotto, il piu' recente sopra), ma ci sono appositi metodi per cambiarlo. I rettangoli vengono disegnati in ordine da sotto a sopra. Se due rettangoli sono parzialmente sovrapposti, quello che viene disegnato dopo (sopra) copre il precedente (sotto).

Il RectanglePainter puo' essere in due modi operativi: selezione (selection) e disegno (drawing). Per default (cioe' appena dopo costruito) e' in modo selezione. Ci sono metodi appositi per cambiarlo. In ciascuno dei due modi operativi RectanglePainter ha dei comportamenti predefiniti per certi eventi.

Come comportamento predefinito in modo selezione, cliccare col mouse su un rettangolo seleziona il rettangolo. L'avvenuta selezione e' segnalata disegnando un sottile contorno bianco al rettangolo selezionato. In ogni istante puo' essere selezionato al piu' un solo rettangolo, selezionare un rettangolo disseleziona l'eventuale altro rettangolo selezionato in precedenza. Cliccare fuori dai rettangoli invece non seleziona nulla, ma disseleziona il rettangolo eventualmente selezionato.

La classe RectanglePainter prevede un listener apposito (ved. interfaccia SelectionListener) per aggiungere altri comportamenti all'evento di selezione di un rettangolo.

Come comportamento predefinito in modo disegno, premere il mouse su un punto inizia il processo di disegno di un nuovo rettangolo. Un rettangolo e' definito mediante due vertici opposti: si preme il mouse in un punto, si trascina il mouse a bottone premuto (drag) e infine si rilascia il mouse, i punti di pressione e rilascio definiscono il rettangolo. Durante il trascinamento del mouse, viene disegnato in nero il contorno del rettangolo che si otterrebbe rilasciando alla posizione attuale del mouse.

Nota bene: il nuovo rettangolo non viene automaticamente creato ed aggiunto al RectanglePainter! E' previsto un listener apposito (ved. interfaccia EndDrawingListener) da aggiungere per gestire l'evento di fine disegno del nuovo rettangolo. Tipicamente tale evento verra' gestito costruendo ed aggiungendo il nuovo rettangolo.

Vediamo ora i metodi interessanti della classe RectanglePainter:

Interfaccia EndDrawingListener

File EndDrawingListener.java.
L'interfaccia EndDrawingListener prevede come unico metodo il seguente:

Interfaccia SelectionListener

File SelectionListener.java.
L'interfaccia SelectionListener prevede come unico metodo il seguente:

Materiale da realizzare

Nostra classe per i rettangoli

Facile. Implementare l'interfaccia MyRectangle ispirandosi alle numerose varianti di rettangolo viste (lezione 2 e lezione 3).

MyRectangle impone che un rettangolo abbia tre caratteristiche: dimensioni (lunghezza, larghezza), colore e punto di aggancio (x,y del suo angolo di coordinare minime). Solo l'ultima e' nuova rispetto ai rettangoli visti a lezione.

Nota: i metodi sono pubblici nell'interfaccia MyRectangle, percio' devono essere pubblici anche nella nostra classe rettangolo. La nostra classe puo' avere anche altri metodi (es: area, print...) oltre a quelli previsti da MyRectangle.

L'interfaccia grafica

Per fare un buon software, l'ultima cosa e' scrivere il codice! Prima bisogna progettare...

Progetttazione dell'interfaccia grafica

Le operazioni

Esaminiamo le operazioni richieste e cerchiamo il modo migliore per renderele possibili all'utente.

1) Aggiungere un nuovo rettangolo stabilendone la posizione e le dimensioni
Possiamo mettere il RectanglePainter in modo disegno e usare la funzionalita' che gia' offre per disegnare rettangoli. Gestiremo l'evento di fine disegno prelevando le dimensioni e il punto di aggancio del rettangolo tracciato, costruendo con queste un nuovo rettangolo ed aggiungendolo al RectanglePainter. A questo nuovo rettangolo possiamo dare un colore di default (ved. dopo).

2) Cancellare un rettangolo
Possiamo mettere il RectanglePainter in modo selezione, l'utente seleziona un rettangolo da cancellare e questo viene cancellato.

3) Stabilire il colore di un rettangolo
Possiamo mettere il RectanglePainter in modo selezione, l'utente seleziona un rettangolo e a questo viene dato il nuovo colore. Il nuovo colore viene scelto dall'utente in qualche modo (ved. dopo).

4) Portare un rettangolo sopra o sotto gli altri
Possiamo mettere il RectanglePainter in modo selezione, l'utente seleziona un rettangolo e questo viene portato sopra o sotto.

5) Spostare un rettangolo
Possiamo mettere il RectanglePainter in modo selezione, l'utente seleziona un rettangolo e questo viene spostato in una direzione stabilita: verso nord, sud, est, ovest.

Con questo NON abbiamo precisato tutto!

Rapporto fra selezione e operazioni

L'utente prima seleziona il rettangolo e poi sceglie che cosa farne (cancellarlo, cambiare colore, portarlo sopra/sotto, stampare informazioni, spostare) oppure prima sceglie che cosa fare e poi seleziona un rettangolo?
Per comodita' d'uso sembra meglio la prima!

Inoltre come scegliere l'operazione da fare sul rettangolo selezionato? tramite menu' o tramite una serie di bottoni?
Per comodita' d'uso sembra meglio la seconda!

Quindi: l'interfaccia grafica ha una serie di bottoni per le operazioni da compiere. Se l'utente aziona uno di questi bottoni e c'e' un rettangolo selezionato, l'operazione corrispondente viene applicata su quel rettangolo.

Impostazione del colore

E' utile avere un "colore corrente", da usare sia quando si cambia colore ad un rettangolo esistente, sia quando si aggiunge un nuovo rettangolo.

Con quali dispositivi scegliere il colore corrente?

Possiamo mettere una serie di bottoni radio con dei colori predefiniti piu' il colore casuale (cioe' un colore generato a caso ogni volta).

Cambio di modo operativo

Come passare da modo selezione a modo disegno e viceversa?
Possiamo usare un gruppo di due bottoni radio.

Quando il RectanglePainter passa in modo disegno, i bottoni delle operazioni (cancella, cambia colore ecc.) vengono disabilitati. Vengono riabilitati al ritorno in modo selezione.

I componenti

Ecco infine quali componenti ci saranno nell'interfaccia grafica:

La disposizione

Come disponiamo questi componenti? Procediamo in modo modulare: individuiamo prima dei sotto-gruppi di componenti che andranno a costituire pannelli, poi decidiamo come disporre questi pannelli nella finestra principale.

Pannello direzioni
Disponiamo i bottoni per spostamento secondo lo schema:

      +-----+
      |nord |
+-----+-----+-----+
|ovest|     | est |
+-----+-----+-----+
      | sud |
      +-----+
Questo sara' un pannello con GridLayout a 3x3 caselle, di cui 4 occupate dai bottoni e 5 occupate da etichette (Label) vuote come riempitivo.

Pannello bottoni
Disponiamo i restanti 4 bottoni per le operazioni (cancella, colora, sopra, sotto) per esempio in fila orizzontale.

Pannello colore
I dispositivi per la scelta del colore corrente li organizziamo per esempio secondo questo schema:
Colore corrente:
[] rosso
[] verde
[] blu
[] giallo
[] ...
[] casuale
Il pannello sara' gestito con GridLayout Nx1 (N dipende da quanti colori predefiniti vogliamo offrire) e conterra' la Label del titolo e i Checkbox dei colori (inseriti in gruppo radio).

Pannello modo operativo
Disponiamo i due bottoni per la scelta del modo operativo per esempio secondo lo schema:

  Modo operativo:
  [] selezione
  [] disegno

Nota: in generale possiamo scrivere i titoli dei pannelli di un altro colore cosi' si distinguono meglio.

Finestra principale
Alla luce di queste considerazioni per esempio possiamo disporre il contenuto della finestra principale in questo modo:

+--------------------------------------------------------------+
| Pannello bottoni                          Pannello direzioni |
+-----------+--------------------------------------------------+
| Pannello  |                                                  |
| modo      |                                                  |
| operativo |                                                  |
|           |    RectanglePainter                              |
|           |                                                  |
|           |                                                  |
| Pannello  |                                                  |
| colore    |                                                  |
+-----------+--------------------------------------------------+
| TextField per messaggi                                       |
+--------------------------------------------------------------+

PER ESERCIZIO: Quale gerarchia di contenimento e quali layout manager servono per ottenere questa disposizione?

I comportamenti

Decidiamo che cosa eseguire nella callback di ciascun dispositivo.

ItemListener del Checkbox "disegno"
-- Passaggio in modo disegno
Chiama setDrawingMode (classe RectanglePainter) e disabilita i bottoni delle operazioni (cancella, colora, sopra, sotto, stampa).

ItemListener del Checkbox "selezione"
-- Passaggio in modo selezione
Chiama setSelectionMode (classe RectanglePainter) e riabilita i bottoni delle operazioni (cancella, colora, sopra, sotto, stampa).

EndDrawingListener del RectanglePainter
-- Aggiunta di nuovo rettangolo
Deve costruire e aggiungere al RectanglePainter il rettangolo appena finito di disegnare.
Controlla se esiste con isNewRectangle, se esiste legge il punto di aggancio e le dimensioni con getNewMinX, getNewMinY, getNewLength, getNewWidth (classe RectanglePainter). Crea un rettangolo con il costruttore della nostra classe per i rettangoli, gli assegna il colore corrente e lo aggiunge al RectanglePainter con addRectangle.

SelectionListener del RectanglePainter
-- Selezione di un rettangolo
Per come abbiamo organizzato le cose, all'atto della selezione di un rettangolo non occorre fare nulla. Infatti al rettangolo selezionato "accade qualcosa" solo quando invochiamo un'operazione.
Percio' non associamo alcun listener. Diverso sarebbe se avessimo deciso di fare delle operazioni non appena un rettangolo viene selezionato (per es. di stampare le sue dimensioni in un'area apposita dell'interfaccia).
In fase di debug, potete associare un listener "di prova" che scriva su standard output le informazioni (coordinate del punto di aggancio, larghezza, altezza) del rettangolo appena selezionato, tanto per avere un riscontro che la selezione funziona.

ActionListener del Button "cancella"
-- Cancellazione del rettangolo selezionato
Ottiene il rettangolo selezionato con getSelectedRectangle (classe RectanglePainter), controlla se esiste. Se si' lo cancella con removeRectangle (classe RectanglePainter)

ActionListener del Button "colora"
-- Assegnazione del colore al rettangolo selezionato
Ottiene il rettangolo selezionato con getSelectedRectangle (classe RectanglePainter), controlla se esiste. Se si', ottiene il colore corrente controllando quale dei bottoni radio e' selezionato, e lo assegna al rettangolo con setColor (interfaccia MyRectangle).
Nota: se il colore corrente e' casuale, dobbiamo creare un colore avente come quantita' di rosso, verde, blu tre numeri casuali, ottenuti con la funzione matematica Math.random (ved. lezione 5).

ActionListener dei TextField del colore
-- Cambio del colore corrente
Avendo usato un gruppo di bottoni radio, non occorre gestire alcun evento (semplicemente quando ci servira' il colore lo leggeremo, ved. bottone "colora").

ActionListener dei Button "sopra" e "sotto"
-- Portare il rettangolo selezionato sopra o sotto
Ottiene il rettangolo selezionato con getSelectedRectangle (classe RectanglePainter). Controlla se esiste. Se si', lo porta sopra / sotto con raise / lower (classe RectanglePainter).

ActionListener dei Button "nord", "sud", "est", "ovest"
-- Spostamento del rettangolo selezionato
Deve modificare le coordinate del punto di aggancio. Per esempio per "est" deve incrementare la x. Bisogna scegliere di quanto, per es. 5 pixel.
Ottiene il rettangolo selezionato con getSelectedRectangle (classe RectanglePainter). Controlla se esiste. Se si', ottiene la vecchia x del punto di aggancio con getMinX, le somma 5 e la riassegna con setMinX (interfaccia MyRectangle). Analogamente gli altri punti cardinali.
Attenzione: per Java le x crescono da sinistra a destra mentre le y crescono dall'alto verso il basso (asse y rovesciato rispetto al solito)!

Inoltre bisogna far terminare il programma quando l'utente chiude la finestra principale (mettendo un WindowListener, ved. lezione 7)!