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

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. Stampare informazioni su un rettangolo
  6. Spostare un rettangolo
  7. Copiare 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.

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) Stampare informazioni su un rettangolo
Possiamo mettere il RectanglePainter in modo selezione, l'utente seleziona un rettangolo e di questo vengono mostrate le informazioni: punto di aggancio, lunghezza, larghezza. Bisogna stabilire dove e come vengono mostrate (ved. dopo).

6) 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.

7) Copiare un rettangolo
Possiamo mettere il RectanglePainter in modo selezione, l'utente seleziona un rettangolo e di questo viene fatta una copia, spostata di qualche pixel ed aggiunta al RectanglePainter.

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, copiare, 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.

Stampa delle informazioni

Dove stampare le informazioni su un rettangolo? In una zona della finestra principale oppure in una finestra di dialogo (che avra' un bottone per chiuderla)?
Per semplicita' usiamo una zona della finestra principale.

Quando stampare le informazioni? Stabiliamo che, non appena viene selezionato un rettangolo, le sue informazioni vengano stampate automaticamente.

Quindi: la stampa delle informazioni e' trattata in modo speciale rispetto alle altre operazioni, non ha bisogno di un bottone per invocarla, viene eseguita automaticamente all'atto della selezione.

Nota: se avessimo usato la finestra di dialogo, la stampa automatica sarebbe stata scomoda (finestre di dialogo che saltano fuori e bisogna andare a richiuderle)!

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?

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 5 bottoni per le operazioni (cancella, colora, sopra, sotto, copia) per esempio in fila orizzontake.

Pannello colore
I dispositivi per la scelta del colore corrente li organizziamo per esempio secondo uno dei due schemi:
Colore corrente:
[] rosso
[] verde
[] blu
[] giallo
oppure
  Colore corrente:
  +-------------+
  |             |
  |             |
  +-------------+
  rosso: [      ]
  verde: [      ]
  blu  : [      ]
Nel primo caso sara' gestito con GridLayout 4x1 e conterra' la Label del titolo e i tre Checkbox (inseriti in gruppo radio).
Nel secondo caso sara' a sua volta strutturato per esempio tramite BorderLayout con a nord il titolo, al centro una Label (etichetta costituita da spazi oppure dalla parola "anteprima" e colore di sfondo che riflette il colore corrente) e a sud un altro pannello strutturato mediante GridLayout 3x2 dove ciascuna riga ha Label col nome del colore e TextField per il valore del colore.

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

  Modo operativo:
  [] selezione
  [] disegno

Pannello info
Organizziamo la zona delle informazioni sul rettangolo selezionato in questo modo (x,y,l,w stanno per i numeri...):

  Rettangolo selezionato:
  punto di aggancio = (x,y)
  lunghezza = l
  larghezza = w
Questo puo' essere semplicemente un pannello con GridLayout 4x1 e dentro quattro etichette.

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    |  
|           |                                                  |
|           |                                                  |
| Pannello  |                                                  |
| info      |                                                  |
+-----------+--------------------------------------------------+
| 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, copia).

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

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
-- Stampa automatica delle informazioni quando viene selezionato un rettangolo
Ottiene il rettangolo appena selezionato con getSelectedRectangle (classe RectanglePainter). Se questo esiste, ottiene le sue informazioni con getMinX, getMinY, getLength, getWidth (interfaccia MyRectangle). Le mette poi nelle tre etichette del pannello info con setText (classe Label). Se il rettangolo selezionato non esiste, mette nelle etichette "--" invece dei valori.

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

ActionListener del Button "colora"
-- Assegnazione del colore al rettangolo selezionato
Ottiene il rettangolo selezionato con getSelectedRectangle (classe RectanglePainter). Se esiste, ottiene il colore corrente: se ho usato un gruppo di bottoni radio controlla quale dei bottoni radio e' selezionato, altrimenti prende il colore di sfondo dell'etichetta anteprima (metodo getBackground). Assegna poi il colore al rettangolo con setColor (interfaccia MyRectangle).

ActionListener dei TextField del colore
-- Cambio del colore corrente
Se abbiamo usato un gruppo di bottoni radio non occorre gestire alcun evento (semplicemente quando ci servira' il colore lo leggeremo, ved. bottone "colora").
Se invece abbiamo usato le tre componenti con anteprima, bisogna gestire la fine interazione nei i tre TextField: leggere il contenuto numerico di tutti e tre (prelevando la stringa con getText e convertendola in intero come visto nella lezione 2), costruire il colore corrispondente e assegnarlo come colore di sfondo all'etichetta dell'anteprima (metodo setBackground).
Bisogna gestire il caso in cui l'utente non abbia digitato un numero oppure abbia messo un numero non valido (non tra 0 e 255). In quel caso si puo' rimettere il numero che c'era e mandare un messaggio di errore nel TextField dei messaggi. Il colore che c'era si ottiene leggendo il colore di sfondo dell'etichetta anteprima e prendendone la componente rossa / verde / blu a seconda del caso.

ActionListener dei Button "sopra" e "sotto"
-- Portare il rettangolo selezionato sopra o sotto
Ottiene il rettangolo selezionato con getSelectedRectangle (classe RectanglePainter). Se esiste, 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). Se esiste, 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)!

ActionListener del Button "copia"
-- Copia del rettangolo selezionato
Ottiene il rettangolo selezionato con getSelectedRectangle (classe RectanglePainter). Se esiste, costruisce un nuovo rettangolo a cui assegna stesse dimensioni, stesso colore e punto di aggancio leggermente spostato (per es. 5 pixel in piu' su x ed y). Poi aggiunge il nuovo rettangolo al RectanglePainter.

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