Paola Magillo, Univestita' di Genova, Corso di Programmazione II per SMID, a.a. 2007-2008.

Soluzione al laboratorio 07

Vedere Testo del labotatorio, qui viene fornita la soluzione di alcuni degli esercizi.

Nota importante:
In generale la soluzione non e' unica. Questa non e' LA soluzione, ma UNA possibile soluzione.

Soluzione Esercizio 1

Richiesta

Fare in modo che le voci di menu' "non valide" in questo momento (cioe' "presta", "restituisci", "salva", "dismetti" in caso di biblioteca vuota) restino disabilitate.

Soluzione

Tali voci vanno disabilitate, previo controllo se la biblioteca e' vuota, nei seguenti momenti:

Vanno invece abilitate, senza necessita' di controlli, nel momento in cui ho aggiunto alla biblioteca un nuovo libro (sicuramente la biblioteca ora non e' piu' vuota).
In realta' e' superfluo abilitarle solo se sono gia' abilitate, ma in questo caso riabilitarle di nuovo non fa danno.

I pezzi di codice da inserire sono:

(dove i puntini significano che bisogna scrivere la stessa cosa anche per le altre voci).

I luoghi dove vanno messi sono (tutti nella classe GestioneBiblioteca):

Soluzione Esercizio 2

Richiesta

Fare si' che la finestra di dialogo per la restituzione mostri solo i libri che hanno qualche copia prestata.

Nota 1: il testo aveva un errore, diceva la finestra per "il prestito" invece che quella per la restituzione.

Nota 2: questo esercizio era dato per "breve" ma in realta' poi tanto breve non e'.

Soluzione

Vanno modificate le funzioni che, dentro la classe Dialog3 che realizza la finestra di dialogo per la restituzione, gestiscono l'avanzamento/arretramento nell'array di libri: vaiAvanti, vaiIndietro, avantiTutta, dietroTutta.

Tuttavia se le cambiassimo nella classe Dialog3 allora avremmo questo comportamento modificato anche nelle finestre di dialogo per dismissione e per prestito, poiche' anche queste sono realizzate con la classe Dialog3.

Allora definiamo una nuova classe Dialog3bis, sotto-classe di Dialog3: la finestra di dialogo per restituzione, e solo quella, sara' adesso un oggetto di classe Dialog3bis invece che Dialog3; le altre due finestre di dialogo restano di classe Dialog3.

La classe Dialog3bis deve contenere solo la nuova implementazione delle 4 funzioni vaiAvanti, vaiIndietro, avantiTutta, dietroTutta. Non deve ripetere tutto il contenuto di Dialog3 poiche' questo viene ereditato.

Nella biblioteca vi e' un array di libri, che va dalla posizione 0 alla posizione biblioteca.numeroLibri()-1. Nella finestra c'e' un attributo intero codiceCorrente che tiene una posizione dentro questo array. Un caso speciale e' dato dalla biblioteca vuota, dove il valore di codiceCorrente deve essere e restare -1 (= nessun libro).

Le 4 funzioni, nella versione attuale ereditata da Dialog3, fanno questo:

  1. indietroTutta: porta codiceCorrente a 0
  2. vaiAvanti: somma 1 a codiceCorrente, solo se il risultato rimane minore di biblioteca.numeroLibri()
  3. vaiIndietro: sottrae 1 da codiceCorrente, solo se il risultato rimane maggiore o uguale a 0
  4. avantiTutta: porta codiceCorrente a biblioteca.numeroLibri()-1

Nella nuova versione dovranno eseguire un simile scorrimento su un sotto-array virtuale fatto da un sotto-insieme delle celle di quello reale: tutte e sole le celle occupate da libri che hanno qualche copia prestata.
Nota: questo sotto-array vistuale potrebbe essere vuoto, anche se l'array reale non e' vuoto (succede quando nessun libro ha al momento copie prestate).

  1. indietroTutta: a partire dalla posizione 0 va avanti sull'array reale cercando un libro che abbia copie prestate; se lo trova mette la sua posizione in codiceCorrente, altrimenti (l'array virtuale e' vuoto) mette codiceCorrente a -1.
  2. vaiAvanti: a partire dalla posizione seguente a quella attuale di codiceCorrente, va avanti sull'array reale cercando un libro che abbia copie prestate; se lo trova mette la sua posizione in codiceCorrente, altrimenti (codiceCorrente era l'ultima posizione dell'array virtuale) lascia codiceCorrente invariato.
  3. vaiIndietro: a partire dalla posizione precedente a a quella attuale di codiceCorrente, va indietro sull'array reale cercando un libro che abbia copie prestate; se lo trova mette la sua posizione in codiceCorrente, altrimenti (codiceCorrente era la prima posizione dell'array virtuale) lascia codiceCorrente invariato.
  4. avantiTutta: a partire dall'ultima posizione, cioe' biblioteca.numeroLibri()-1, va indietro sull'array reale cercando un libro che abbia copie prestate; se lo trova mette la sua posizione in codiceCorrente, altrimenti (l'array virtuale e' vuoto) mette codiceCorrente a -1.
In vaiAvanti e vaiIndietro bisogna anche controllare, come viene fatto in Dialog3, che codiceCorrente attuale non sia gia' alla fine o all'inizio dell'array reale.

Definiamo due funzioni ausiliarie cercaAvanti e cercaIndietro che cercano, a partire da una certa posizione, la prossima posizione, rispettivamente in avanti e indietro, occupata da un libro che ha copie prestate; se non esiste, ritornano -1.

int cercaAvanti(int partenza)
{
  // p conterra' il risultato
  int p = partenza;
  // vado avanti finche' non sono infondo e finche' il libro
  // corrente non ha copie prestate
  while ( (p0) && 
          (biblioteca.libroPerCodice(p).copiePrestate()==0) )
     p--;
  // a questo punto guardo se ho trovato un libro corrente
  // con copie prestate
  if (biblioteca.libroPerCodice(p).copiePrestate()!=0)
  return p;
  // altrimenti non ce ne sono
  else return -1;
}

Le due funzioni vaiAvanti e indietroTutta usano la funzione ausiliaria cercaAvanti:

  void vaiAvanti()
  {
    int tentativo; // mettero' qui la posizione dove andare
    // se sono gia' alla fine, niente
    if (codiceCorrente>=biblioteca.numeroLibri()-1)
        tentativo = -1;
    // altrimenti cerco avanti
    else tentativo = cercaAvanti(codiceCorrente+1);
    if (tentativo!=-1) 
    // se ho trovato lo metto
    {  codiceCorrente = tentativo;
       mostraLibro(codiceCorrente);  }
    // altrimenti codiceCorrente resta quello che era
  }
  void indietroTutta()
  {
    int tentativo; // mettero' qui la posizione dove andare
    // se biblioteca vuota, niente
    if (biblioteca.numeroLibri()==0) tentativo = -1;
    // altrimenti cerco avanti partendo dall'inizio
    else tentativo = cercaAvanti(0);
    // si fa la stessa cosa anche se e' venuto -1,
    // in questo caso mettera' il libro nullo
    codiceCorrente = tentativo;
    mostraLibro(codiceCorrente);
  }

Le due funzioni vaiIndietro e avantiTutta usano la funzione ausiliaria cercaIndietro:

  void vaiIndietro()
  {
    int tentativo; // mettero' qui la posizione dove andare
    // se sono gia' all'inizio, niente
    if (codiceCorrente<=0) tentativo = -1;
    // altrimenti cerco indietro
    else tentativo = cercaIndietro(codiceCorrente-1);
    if (tentativo!=-1) 
    // se ho trovato lo metto
    {  codiceCorrente = tentativo;
       mostraLibro(codiceCorrente);  }
    // altrimenti codiceCorrente resta quello che era
  }
  void avantiTutta()
  {
    int tentativo; // mettero' qui la posizione dove andare
    // se biblioteca vuota, niente
    if (biblioteca.numeroLibri()==0) tentativo = -1;
    // altrimenti cerco indietro partendo dalla fine
    else tentativo = cercaIndietro(biblioteca.numeroLibri()-1);
    // si fa la stessa cosa anche se e' venuto -1,
    // in questo caso mettera' il libro nullo
    codiceCorrente = tentativo;
    mostraLibro(codiceCorrente);
  }

Inoltre la classe Dialog3bis dovra' definire il suo costruttore, che semplicemente richiamera' quello della super-classe:

  public Dialog3bis(Frame f, String title, Biblioteca bib, boolean modal)
  {  super(f,title,bib,modal);  }

Soluzione Esercizio 3

Richiesta

Fare si' che non sia possibile prestare l'ultima copia di un libro (cioe' quando numero di copie prestate == numero di copie totali - 1).

Soluzione

La soluzione e' semplicissima. Modificare la funzione actionPerformed della classe interna EseguiPrestito in GestioneBiblioteca. Subito prima della linea:

 biblioteca.prestaLibro(dPresta.codiceScelto());
Inserire controllo se il libro individuato dal codice scelto ha numero di copie prestate uguale a tutte tranne una. In caso affermativo sollevare eccezione (provochera' comparsa della finestra di dialogo e non esecuzione del prestito):
 LibroInBiblioteca l =
        biblioteca.libroPerCodice(dPresta.codiceScelto());
 if (l.copieTotali()-l.copiePrestate()==1)
        throw new Exception("Non posso prestare ultima copia");

Soluzione Esercizio 4

Richiesta

Scrivere in rosso i dati dei libri non prestabili nella finestra per il prestito e in verde i dati dei libri non restituibili nella finestra per la restituzione.

Soluzione

Anche qui, come nell'esercizio 2, bisogna definire sotto-classi della classe Dialog3:

  1. una sottoclasse Dialog3Presta, che re-implementa il metodo mostraLibro per mettere in rosso i libri non prestabili (di questa classe sara' creata la finestra dPresta)
  2. una sottoclasse Dialog3Restit, che re-implementa il metodo mostraLibro per mettere in verde i libri non restituibili (di questa classe sara' creata la finestra dRestit)

Entrambe queste sotto-classi devono contenere solo la funzione mostraLibro (con la nuova implementazione) e il costruttore (che richiama il costruttore della super-classe con gli stessi parametri, ved. come fatto nella soluzione dell'esercizio 2).

Ecco qui la nuova implementazione della funzione mostraLibro in Dialog3Presta (in Dialog3Restit e' analoga - cambia solo il colore e la condizione per usarlo).

  public void mostraLibro(int cod)
  {
    LibroInBiblioteca l = null;
    if (biblioteca!=null) l = biblioteca.libroPerCodice(cod);
    if (l==null) pulisci();
    else
    {  codiceCorrente = cod;
/*inizio parte aggiunta*/
       if (l.copiePrestate()==l.copieTotali())
       {  tAut.setForeground(Color.red); 
          tTit.setForeground(Color.red);
          tCop.setForeground(Color.red);
       }
       else
       {  tAut.setForeground(Color.black);
          tTit.setForeground(Color.black);
          tCop.setForeground(Color.black);
       }
/*fine parte aggiunta*/
       tAut.setText(l.autore); tTit.setText(l.titolo);
       tCop.setText(l.copieTotali() + " (" + l.copiePrestate() + ")");
    }
  }
Nota: e' necessario mettere il colore nero quando il libro e' prestabile, altrimenti rimarrebbe il rosso impostato quando e' stato precedentemente mostrato un libro non prestabile.

Soluzione Esercizio 5

Richiesta

Aggiungere alla finestra Dialog3 in alto (nello stesso pannello dei bottoni) un bottone "cerca" e un campo di testo in cui l'utente puo' digitare la stringa con cui deve iniziare il titolo del libro.
Aggiungere la callback del bottone "cerca" che

Soluzione

Nella classe Dialog3 eseguire le seguenti modifiche:

1) Aggiungere gli attributi (con questi o altri nomi):
Button bCerca;

TextField tDaCercare;

2) Nella funzione "costruisci" il pannello pSopra ha grid layout 1x6 invece che 1x4 sempre nella funzione "costruisci", creare e aggiungere i due nuovi componenti nel pannello pSopra:
pSopra.add(bCerca = new Button("Cerca:"));

pSopra.add(tDaCercare = new TextField(""));

3) Scrivere la funzione che costituira' il comportamento del bottone bCerca (analogamente alle funzioni che costituiscono i comportamenti degli altri 4 bottoni):

void cercaTitolo()
{
  // prende la stringa che l'utente ha digitato nel campo
  String s = tDaCercare.getText();
  // inizia ciclo sui libri 
  int i;
  int trovato = -1; // codice del libro trovato
  for (i=0; i

4) Nel costruttore di Dialog3, assegnare al bottone bCerca action listener che esegue la funzione cercaTitolo sopra definita, in modo analogo a come viene fatto con gli altri bottoni:

  bCerca.addActionListener(new ActionListener()
  {
     public void actionPerformed(ActionEvent e)
     { cercaTitolo(); }
  });

Esercizio 6

Richiesta

Aggiungere al menu' generale una voce "salva con nome". Associare a questa voce una callback (event listener) che fa apparire una finestra di dialogo che chiede un nome di file.

Soluzione

La finestra che chiede il nome del file e' realizzata dalla classe Dialog5, sotto-classe di Dialog1. Rispetto a Dialog1, essa:

Il codice di questa nuova classe e':

class Dialog5 extends Dialog1
{
  public TextField tNomefile;
  protected void costruisci()
  {
    super.costruisci(); // fa cio' che faceva in Dialog1
    pCentro = new Panel(new BorderLayout());
    tNomefile = new TextField(20); // campo testo lungo 20 caratteri
    pCentro.add(tNomefile, BorderLayout.CENTER);
    pCentro.add(new Label("Nome file:"), BorderLayout.WEST);
    add(pCentro, BorderLayout.CENTER); // aggiunge alla finestra
  }
  Dialog5(Frame f, String title, boolean modal)
  {  super(f,title,modal);  }
}

Nella classe GestioneBiblioteca, abbiamo una finestra di dialogo dSalva, di classe Dialog5, che compare quando l'utente seleziona la voce "salva". Il bottone Ok di questa finestra salvera' sul file il cui nome e' stato digitato nella finestra dall'utente.

Ricalcare quanto viene fatto per le altre voci di menu' che fanno comparire finestre di dialogo: