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