1   package kerhoswing;
2   
3   import java.awt.Color;
4   import java.awt.Desktop;
5   import java.awt.event.KeyAdapter;
6   import java.awt.event.KeyEvent;
7   import java.io.IOException;
8   import java.io.PrintStream;
9   import java.net.URI;
10  import java.net.URISyntaxException;
11  import java.util.Collection;
12  import java.util.List;
13  
14  import javax.swing.JComponent;
15  import javax.swing.JLabel;
16  import javax.swing.JOptionPane;
17  import javax.swing.JTextArea;
18  import javax.swing.JTextField;
19  import javax.swing.table.DefaultTableCellRenderer;
20  import javax.swing.table.DefaultTableModel;
21  
22  import kerho.Harrastus;
23  import kanta.Kentta;
24  import kanta.SailoException;
25  import kanta.Tietue;
26  import kerho.Jasen;
27  import kerho.Kerho;
28  
29  import fi.jyu.mit.gui.AbstractChooser;
30  import fi.jyu.mit.gui.EditPanel;
31  import fi.jyu.mit.gui.IStringListChooser;
32  import fi.jyu.mit.gui.IStringTable;
33  import fi.jyu.mit.gui.SelectionChangeListener;
34  import fi.jyu.mit.gui.StringTable;
35  import fi.jyu.mit.gui.TableEditListener;
36  import fi.jyu.mit.gui.TableRendererListener;
37  import fi.jyu.mit.gui.TableSelectionListener;
38  import fi.jyu.mit.gui.TextAreaOutputStream;
39  
40  
41  
42  /**
43   * Luokka joka käsittelee kerhoa Swing-komponenteilla.
44   * Luokan käyttö:
45   * <pre>
46   * 1) Tee jollakin tavalla lomake, jossa on tarvittavat komponentit
47   * 2) Laita lomakkeelle myös Lisää ja Talleta -napit niin halutessasi
48   * 3) Luo koodissa lomakkeella KerhoSwing-olio
49   * <pre>
50   *        kerhoswing = new KerhoSwing();
51   *        kerhoswing.setListJasenet(listJasenet);
52   *        kerhoswing.setEditHaku(editHaku);
53   *        kerhoswing.setCbKentat(cbKentat);
54   *        kerhoswing.setTableHarrastukset(tableHarrastus);
55   *        kerhoswing.setPanelJasen(boxJasen);
56   *        kerhoswing.setLabelVirhe(labelVirhe);
57   *        
58   *        String virhe = kerhoswing.lueTiedosto("kelmit");
59   *        if ( virhe != null ) JOptionPane.showMessageDialog(null, virhe);
60   * </pre>       
61   * 4) Kutsu nappien tapahtumista mm. olion lisaa ja talleta -metodeja.
62   *         kerhoswing.lisaa();
63   *         kerhoswing.talleta();
64   * 5) Jotta kaikki tulee talletettua, kutsu ohjelmasta poistuvissa paikoissa
65   *         kerhoswing.talleta();        
66   * </pre>
67   * @author vesal
68   * @version 10.4.2009
69   * @version 30.1.2012 - gui-komponenteilla
70   * @version 16.2.2012 - lisätty jäsenien käsittely
71   * @version 28.2.2012 - lisätty harrastusten käsittely
72   * @version 17.3.2012 - harrastukset kenttien perusteella
73   * TODO rivin vaihtuminen taulukossa
74   */
75  public class KerhoSwing {
76  
77      /** Kun halutaan haussa nykyjäsen valituksi haun jälkeen */
78      public static final int NYKY = -1;
79      
80      private static Color virheVari = new Color(255, 0, 0);
81      private static Color normaaliVari = new Color(255, 255, 255);
82      private final static Jasen apujasen = new Jasen();
83      /** Apuharrastus josta katsotaan kenttien määrää yms */
84      protected final static Tietue apuharrastus = new Harrastus();
85      private AbstractChooser<String> cbKentat;
86      private JTextField editHaku;
87      private AbstractChooser<Jasen> listJasenet;
88      private StringTable tableHarrastukset;
89      private JComponent panelJasen;
90      private JLabel labelVirhe;
91      private final Kerho kerho;
92      
93      private Jasen jasenKohdalla;  // Kohdalla oleva jäsen, ÄLÄ muuta muuten kuin set-kautta!
94      private Jasen editJasen;      // Kohdalla oleva jäsen jos tehty muutoksia
95      private EditPanel editJasenKentta[]; // Jäsenen kenttiä vastaavat edit-kentät
96      
97      /**
98       * Alustaa luokan niin, että se voi käyttää  Swing-komponentteja
99       */
100     public KerhoSwing() {
101         kerho = new Kerho();
102     }
103 
104 
105     /**
106      * @return mihin näytetään virheteksti
107      */
108     public JLabel getLabelVirhe() {
109         return labelVirhe;
110     }
111 
112 
113     /**
114      * @param labelVirhe mihin näytetään virheteksti
115      */
116     public void setLabelVirhe(JLabel labelVirhe) {
117         this.labelVirhe = labelVirhe;
118     }
119 
120 
121     /**
122      * @return combobox jossa kenttälista 
123      */
124     public AbstractChooser<String> getCbKentat() {
125         return cbKentat;
126     }
127 
128 
129     /**
130      * @param cbKentat comboboxkenttälistaa varten
131      */
132     public void setCbKentat(AbstractChooser<String> cbKentat) {
133         this.cbKentat = cbKentat;
134     }
135 
136 
137     /**
138      * @return edit jossa hakuehto
139      */
140     public JTextField getEditHaku() {
141         return editHaku;
142     }
143 
144 
145     /**
146      * @param editHaku edit johon saa kirjoittaa hakuehdon
147      */
148     public void setEditHaku(JTextField editHaku) {
149         this.editHaku = editHaku;
150     }
151 
152 
153     /**
154      * @return lista johon jäsenet laitetaan
155      */
156     public AbstractChooser<Jasen> getListJasenet() {
157         return listJasenet;
158     }
159 
160 
161     /**
162      * @param listJasenet lista johon jäsenet laitetaan
163      */
164     public void setListJasenet(AbstractChooser<Jasen> listJasenet) {
165         this.listJasenet = listJasenet;
166     }
167 
168 
169     /**
170      * @return taulukko johon tulee jäsenen harrastukset
171      */
172     public StringTable getTableHarrastukset() {
173         return tableHarrastukset;
174     }
175 
176 
177     /**
178      * @param tableHarrastukset taulukko johon tulee jäsenen harrastukset
179      */
180     public void setTableHarrastukset(StringTable tableHarrastukset) {
181         this.tableHarrastukset = tableHarrastukset;
182     }
183 
184 
185     /**
186      * @return alue johon lisätään jäsenen yksittäiset kentät.
187      */
188     public JComponent getPanelJasen() {
189         return panelJasen;
190     }
191 
192 
193     /**
194      * @param panelJasen alue johon lisätään jäsenen tiedot.  Mielellään joku johon tulevat alekkain
195      */
196     public void setPanelJasen(JComponent panelJasen) {
197         this.panelJasen = panelJasen;
198     }
199 
200 
201     /**
202      * Taulukon malli, jossa jokainen solu on Kentta-tyyppiä
203      */
204     public static class KenttaTableModel extends DefaultTableModel {
205         private static final long serialVersionUID = 1L;
206         /**
207          * @param otsikot millä otsikoille malli alustetaan
208          */
209         public KenttaTableModel(String[] otsikot) {    
210             super(otsikot, 0); 
211         }  
212         @Override
213         public Class<?> getColumnClass(int columnIndex) {
214             return Kentta.class; // Jotta lajittelu toimii
215         }
216     }
217     
218     
219     /**
220      * Tämä alustaa valitut alueet käyttökuntoon.
221      */
222     public void alusta() {
223         if ( editJasenKentta != null ) return; // on jo alustettu
224         
225         cbKentat.setKohteet(apujasen.getOtsikot());
226         
227         editHaku.addKeyListener(new KeyAdapter() {
228             @Override  public void keyReleased(KeyEvent e) { hae(NYKY); }
229         });
230 
231         cbKentat.addSelectionChangeListener(new SelectionChangeListener<String>() {
232             @Override
233             public void selectionChange(IStringListChooser<String> sender) { hae(NYKY);}
234         });
235 
236         listJasenet.addSelectionChangeListener(new SelectionChangeListener<Jasen>() {
237             @Override
238             public void selectionChange(IStringListChooser<Jasen> sender) { naytaJasen(); }
239         });
240         
241         
242         
243         tableHarrastukset.addTableEditListener(new TableEditListener() {
244             @Override
245             public String tableEdit(IStringTable sender, int row, int column, Object s) {
246                 return setHarrastus(sender,row,column,s);
247             }
248         });
249         
250         tableHarrastukset.addTableRendererListener(new TableRendererListener() {
251             @Override // Jotta saadaan vasen/oikea reuna kentän mukaan
252             public DefaultTableCellRenderer tableRenderer(IStringTable sender, DefaultTableCellRenderer renderer, int row, int column) {
253                 renderer.setHorizontalAlignment(apuharrastus.getKentta(column+apuharrastus.ekaKentta()).getSijainti());
254                 return renderer;
255             }
256         });
257         
258         tableHarrastukset.addTableSelectionListener(new TableSelectionListener() {
259             @Override // jotta mahdollinen virhe pyyhkiytyy pois kun solua vaihdetaan
260             public void selectionChanged(IStringTable sender, int row, int column) {
261                 setVirhe(null);
262             }
263         });
264         tableHarrastukset.setEditorSetValues(false); // kun on suorat viitteet kenttiin
265         tableHarrastukset.getTable().setModel(new KenttaTableModel(apuharrastus.getOtsikot()));
266         
267 
268         luoNaytto();
269     }
270 
271 
272     /**
273      * Asetetaan editoitava jäsen
274      * @param j uusi viite editJasenelle
275      */
276     private void setEditJasen(Jasen j) {
277         editJasen = j;
278     }
279 
280     
281     /**
282      * Asetetaan editoitava jäsen
283      * @param j uusi viite editJasenelle
284      */
285     private void setJasenKohdalla(Jasen j) {
286         int jnro = NYKY;
287         if ( j != null ) jnro = j.getTunnusNro();
288         tarkistaMuutos(jnro);
289         jasenKohdalla = j;
290         setEditJasen(null);
291     }
292 
293     
294     /**
295      * Tutkii onko editoitavaan jäseneen tehty muutoksia, jotka kannattaa
296      * tallettaa.
297      * @return false jos ei muutoksia
298      */
299     private boolean muuttunut() {
300         if (jasenKohdalla == null) return false;
301         if (editJasen == null) return false;
302         return !jasenKohdalla.equals(editJasen);
303     }
304 
305 
306     /**
307      * Tarkitetaan onko jäsenen tiedot muuttuneet ja jos on, kysytään halutaanko tallentaa
308      * @param jnro mikä jäsen aktiiviseksi muutoksne jälkeen
309      */
310     public void tarkistaMuutos(int jnro) {
311         if (muuttunut()) {
312             int vastaus = JOptionPane.showConfirmDialog(null, "Talletetaanko?", "Jäsen muuttunut!", JOptionPane.YES_NO_OPTION);
313             if (vastaus == JOptionPane.YES_OPTION) talleta(jnro);
314         }
315     }
316     
317     
318     /**
319      * Luo uuden jäsenen jota aletaan editoimaan 
320      */
321     private void luoUusiJasen() {
322         Jasen uusi = new Jasen();
323         uusi.rekisteroi();
324         setJasenKohdalla(uusi);
325     }
326 
327 
328     private boolean uusiDialogilla = false;
329     
330     /**
331      * @param dialogilla kysytäänkö uudet tietueet dialogilla
332      */
333     public void setUusiDialogilla(boolean dialogilla) {
334         uusiDialogilla = dialogilla;
335     }
336 
337     
338     /**
339      * @return kysytäänkö uudet tietueet dialogilla
340      */
341     public boolean isUusiDialogilla() {
342         return uusiDialogilla;
343     }
344     
345     
346     /**
347      * Tekee uuden tyhjän jäsenen editointia varten
348      */
349     public void uusiJasen() {
350         if ( uusiDialogilla ) {
351             tarkistaMuutos(jasenKohdalla.getTunnusNro());
352             Jasen uusi = new Jasen(); 
353             if ( !TietueDialog.kysyTietue(uusi, "Anna uuden jäsenen tiedot", 150) )  return;
354             uusi.rekisteroi();
355             kerho.lisaa(uusi);
356             hae(uusi.getTunnusNro());
357             return;
358         }
359         luoUusiJasen();
360         laitaJasen();
361         listJasenet.clearSelection();
362      }
363 
364 
365     /**
366      * Tekee uuden tyhjän harrastuksen editointia varten
367      */
368     public void uusiHarrastus() {
369         if ( jasenKohdalla == null ) return;
370         tarkistaMuutos(jasenKohdalla.getTunnusNro());
371         Harrastus harrastus = new Harrastus(jasenKohdalla.getTunnusNro());
372         if ( uusiDialogilla )
373            if ( !TietueDialog.kysyTietue(harrastus, "Anna uuden harrastuksen tiedot", 80) )  return;
374         harrastus.rekisteroi();
375         kerho.lisaa(harrastus);
376         naytaHarrastukset();
377     }
378 
379 
380     /**
381      * Näytetään harrastukset taulukkoon.  Tyhjennetään ensin taulukko ja sitten
382      * lisätään siihen kaikki harrastukset
383      */
384     protected void naytaHarrastukset() {
385         if ( jasenKohdalla == null ) return;
386         tableHarrastukset.clear();
387         tableHarrastukset.getTable().getColumnModel().getColumn(0).setMinWidth(100);
388         
389         List<Harrastus> harrastukset = kerho.annaHarrastukset(jasenKohdalla);
390         for (Tietue har : harrastukset) {
391             int r = tableHarrastukset.addRow();
392             tableHarrastukset.setObjectAt(har, r);
393             for (int i=0,k = har.ekaKentta(); k < har.getKenttia() && i < tableHarrastukset.getColumnCount(); i++,k++) {
394                 tableHarrastukset.setValueAtModel(har.getKentta(k), r, i);
395             }
396         }
397 //        tableHarrastukset.selectCell(tableHarrastukset.getRowCount()-1, 0);
398     }
399 
400     
401     /**
402      * Asetetaan harrastukseen uusi arvo
403      * @param sender mistä taulukosta pyyntö tuli   
404      * @param row miltä riviltä
405      * @param column mistä sarakkeesta  
406      * @param s mitä haluttiin laittaa
407      * @return null jos ok, muuten virhe
408      */
409     public String setHarrastus(IStringTable sender, int row, int column, Object s) {
410         Tietue har = (Tietue)sender.getObjectAt(row);
411         if ( har == null ) return "Ei harrastusta";
412         String virhe = har.aseta(column+har.ekaKentta(), s.toString());
413         if ( virhe == null ) kerho.setHarrastusMuutos();
414         setVirhe(virhe);
415         return virhe;
416     }    
417     
418     
419     /**
420      * Lukee kerhon tiedot tiedostosta. Tarkistetaan aluksi että kaikki
421      * kentät on asetettu paikalleen.
422      * @param s tiedoston nimi
423      * @return null jos onnistuu, muuten virheilmoitus
424      */
425     public String lueTiedosto(String s) {
426         try {
427             if ( cbKentat == null ) return "cbKentat on alustamatta";
428             if ( editHaku == null ) return "editHaku on alustamatta";
429             if ( listJasenet == null ) return "listJasenet on alustamatta";
430             if ( tableHarrastukset == null ) return "tableHarrastukset on alustamatta";
431             if ( panelJasen == null ) return "panelJasen on alsutamatta";
432             alusta();
433             kerho.lueTiedostosta(s);
434             hae(0);
435             return null;
436         } catch (SailoException e) {
437             hae(0);
438             // uusiJasen();
439             return e.getMessage();
440         }
441     }
442 
443 
444     /**
445      * Tallettaa nykyisen mahdollisesti muutetun jäsenen ja sitten koko tiedoston
446      * @param jnro Mikä jäsen aktiiviseksi talletuksen jälkeen. -1 = nykyinen valittu jäsen.
447      * @return null jos menee hyvin, muuten virheteksti
448      */
449     public String talleta(int jnro) {
450         int jn = jnro;
451         try {
452             if (muuttunut()) {
453                 kerho.korvaaTaiLisaa(editJasen);
454                 jasenKohdalla = editJasen;
455                 if (jasenKohdalla != null && jnro < 0) {
456                     jn = jasenKohdalla.getTunnusNro();
457                 }
458                 hae(jn);
459             }
460             setEditJasen(null);
461             kerho.talleta();
462             return null;
463         } catch (SailoException ex) {
464             JOptionPane.showMessageDialog(null, "Talletuksessa ongelmia! " + ex.getMessage());
465             return ex.getMessage();
466         }
467     }
468 
469 
470     /**
471      * Tallettaa nykyisen mahdollisesti muutetun jäsenen ja sitten koko tiedoston
472      * @return null jos menee hyvin, muuten virheteksti
473      */
474     public String talleta() {
475         return talleta(NYKY);
476     }
477 
478 
479     /**
480      * Suorittaa niiden jäsenten hakemisen, joiden valittu kenttä täyttää hakuehdon
481      * @param jnro jäsenen numero, joka aktivoidaan haun jälkeen, -1 = aktivoidaan nykyinen jäsen
482      */
483     protected void hae(int jnro) {
484         int jn = jnro;
485         if ( jn == NYKY && jasenKohdalla != null ) jn = jasenKohdalla.getTunnusNro();
486         
487         int k = cbKentat.getSelectedIndex() + apujasen.ekaKentta();
488         String ehto = editHaku.getText();
489         if (ehto.indexOf('*') < 0) ehto = "*" + ehto + "*";
490         Collection<Jasen> haunTulos = kerho.etsi(ehto, k);
491 
492         listJasenet.clear();
493         
494         int index = 0, i = 0;
495         for (Jasen jasen : haunTulos ) {
496             if (jasen.getTunnusNro() == jn) index = i;
497             listJasenet.add(jasen.getNimi(),jasen);
498             i++;
499         }
500 
501         listJasenet.setSelectedIndex(index);
502     }
503 
504     
505     /**
506      * Näyttää jäsenen tiedot jäsenen alueeseen
507      */
508     private void laitaJasen() {
509         if (jasenKohdalla == null) return;
510         setVirhe(null);
511         for (int i = 0, k = jasenKohdalla.ekaKentta(); k < jasenKohdalla.getKenttia(); k++, i++) {
512             editJasenKentta[i].setText(jasenKohdalla.anna(k));
513             editJasenKentta[i].getEdit().setBackground(normaaliVari);
514             editJasenKentta[i].setToolTipText("");
515         }
516         naytaHarrastukset();
517     }
518 
519 
520     /**
521      * Näyttää listasta valitun jäsenen tiedot
522      */
523     protected void naytaJasen() {
524         int ind = listJasenet.getSelectedIndex();
525         if (ind < 0) return;
526         setJasenKohdalla(listJasenet.getSelectedObject());
527         laitaJasen();
528     }
529 
530 
531     /**
532      * Käsittelee edit-kenttään tulleen muutoksen jäseneen.
533      * Mikäli jäsentä ei vielä ole editoitu, luodaan jäsenestä
534      * kopio editJasen-olioon ja muutokset kohdistetaan tähän
535      * jäseneen.  Näin voidaan muutoksia verrata jasen-olioon
536      * ja tunnistetaan muutostarpeet.  Jos muutosta ei voida
537      * sijoittaa jäseneen, muutetaan taustaväri punaiseksi.
538      * @param edit muuttunut kenttä
539      */
540     protected void kasitteleMuutosJaseneen(JTextField edit) {
541         if (jasenKohdalla == null) luoUusiJasen();
542         if (editJasen == null)
543             try {
544                 setEditJasen(jasenKohdalla.clone());
545             } catch (CloneNotSupportedException e) { // virhettä ei tule
546             }
547         String s = edit.getText();
548         int k = Integer.parseInt(edit.getName().substring(2));
549         String virhe = editJasen.aseta(k, s);
550         setVirhe(virhe);
551         if (virhe == null) {
552             edit.setToolTipText("");
553             edit.setBackground(normaaliVari);
554         } else {
555             edit.setToolTipText(virhe);
556             edit.setBackground(virheVari);
557         }
558     }
559 
560 
561     /**
562      * Laitetaan virheilmoitus näkyville jos labelVirhe on alustettu.
563      * Miksä virhettä ei ole, niin piilotetaan virheilmoitus.    
564      * @param virhe virheteksti
565      */
566     protected void setVirhe(String virhe) {
567         if ( labelVirhe == null ) return;
568         if ( virhe == null ) {
569             labelVirhe.setText("");
570             labelVirhe.setVisible(false);
571             return;
572         }
573         labelVirhe.setVisible(true);
574         labelVirhe.setText(virhe);
575         labelVirhe.setBackground(virheVari);
576     }
577 
578    
579     /**
580      * Käsittelijäluokka edit kentän näppäimen vapauttamiselle 
581      */
582     protected class EditKeyReleased extends KeyAdapter {
583         @Override public void keyReleased(KeyEvent e) {
584             kasitteleMuutosJaseneen((JTextField)e.getComponent());
585         }
586     }
587     
588     
589     private EditKeyReleased editKeyReleased = new EditKeyReleased();
590 
591     
592     /**
593      * Luo panelJasen:een paikat johon jäsenen tiedot voi laittaa
594      */
595     private void luoNaytto() {
596         panelJasen.removeAll();
597         int n = apujasen.getKenttia() - apujasen.ekaKentta();
598         editJasenKentta = new EditPanel[n];
599 
600         for (int i = 0, k = apujasen.ekaKentta(); k < apujasen.getKenttia(); k++, i++) {
601             EditPanel edit = new EditPanel(); 
602             edit.setCaption(apujasen.getKysymys(k));
603             editJasenKentta[i] = edit;
604             edit.setName("ej" + k);
605             edit.getEdit().setName("tj" + k);
606             panelJasen.add(edit);
607             edit.addKeyListener(editKeyReleased); 
608         }
609     }
610     
611     
612     /**
613      * Poistetaan harrastustaulukosta valitulla kohdalla oleva harrastus.
614      */
615     public void poistaHarrastus() {
616         int rivi = tableHarrastukset.getSelectedRow();
617         if ( rivi < 0 ) return;
618         Harrastus harrastus = (Harrastus)tableHarrastukset.getSelectedObject();
619         if ( harrastus == null ) return;
620         kerho.poistaHarrastus(harrastus);
621         naytaHarrastukset();
622         if ( rivi >= tableHarrastukset.getRowCount() ) rivi = tableHarrastukset.getRowCount() - 1;
623         tableHarrastukset.selectCell(rivi, 0);
624     }
625 
626 
627     /**
628      * Poistetaan listasta valittu jäsen.
629      */
630     public void poistaJasen() {
631         if ( jasenKohdalla == null ) return;
632         int vastaus = JOptionPane.showConfirmDialog(null, "Poistetaanko jäsen: " + jasenKohdalla.getNimi(), "Poisto?", JOptionPane.YES_NO_OPTION);
633         if ( vastaus == JOptionPane.NO_OPTION ) return;
634         kerho.poista(jasenKohdalla.getTunnusNro());
635         int index = listJasenet.getSelectedIndex();
636         hae(0);
637         listJasenet.setSelectedIndex(index);
638     }
639 
640 
641     /**
642      * Avataan ulkoinen selain näyttämään avustustekstiä.
643      */
644     public void avustus() {
645         Desktop desktop = Desktop.getDesktop();
646         try {
647             //URI uri = new URI("kerho.html");
648             URI uri = new URI("https://trac.cc.jyu.fi/projects/ohj2k12/wiki/suunnitelmat/vesal2");
649             desktop.browse(uri);
650         } catch (URISyntaxException e) {
651             return;
652         } catch (IOException e) {
653             return;
654         }
655     }
656 
657 
658     /**
659      * Tulostaa jäsenen tiedot
660      * @param os tietovirta johon tulostetaan
661      * @param jasen tulostettava jäsen
662      */
663     public void tulosta(PrintStream os, final Jasen jasen) {
664         os.println("----------------------------------------------");
665         jasen.tulosta(os);
666         os.println("----------------------------------------------");
667         for (Tietue har:kerho.annaHarrastukset(jasen)) har.tulosta(os);
668     }
669     
670     
671     /**
672      * Tulostaa listassa olevat jäsenet tekstialueeseen
673      * @param text alue johon tulostetaan
674      */
675     public void tulostaValitut(JTextArea text) {
676         PrintStream os = TextAreaOutputStream.getTextPrintStream(text);
677         os.println("Valitut jäsenet:");
678         for (Jasen jasen : listJasenet.getObjectList()) {
679             tulosta(os, jasen);
680             os.println("\n\n");
681         }    
682     }
683 
684 
685 }
686