source: src/main/java/weka/gui/visualize/ClassPanel.java @ 4

Last change on this file since 4 was 4, checked in by gnappo, 14 years ago

Import di weka.

File size: 19.6 KB
RevLine 
[4]1/*
2 *    This program is free software; you can redistribute it and/or modify
3 *    it under the terms of the GNU General Public License as published by
4 *    the Free Software Foundation; either version 2 of the License, or
5 *    (at your option) any later version.
6 *
7 *    This program is distributed in the hope that it will be useful,
8 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
9 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 *    GNU General Public License for more details.
11 *
12 *    You should have received a copy of the GNU General Public License
13 *    along with this program; if not, write to the Free Software
14 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
15 */
16
17/*
18 *    ClassPanel.java
19 *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
20 *
21 */
22
23package weka.gui.visualize;
24
25import weka.core.FastVector;
26import weka.core.Instances;
27import weka.core.Utils;
28
29import java.awt.BorderLayout;
30import java.awt.Color;
31import java.awt.Component;
32import java.awt.Font;
33import java.awt.FontMetrics;
34import java.awt.Graphics;
35import java.awt.event.ActionEvent;
36import java.awt.event.ActionListener;
37import java.awt.event.MouseAdapter;
38import java.awt.event.MouseEvent;
39
40import javax.swing.JColorChooser;
41import javax.swing.JLabel;
42import javax.swing.JPanel;
43
44/**
45 * This panel displays coloured labels for nominal attributes and a spectrum
46 * for numeric attributes. It can also be told to colour on the basis
47 * of an array of doubles (this can be useful for displaying coloured labels
48 * that correspond to a clusterers predictions).
49 *
50 * @author Mark Hall (mhall@cs.waikato.ac.nz)
51 * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
52 * @version $Revision: 5086 $
53 */
54public class ClassPanel
55  extends JPanel {
56
57  /** for serialization */
58  private static final long serialVersionUID = -7969401840501661430L;
59   
60  /** True when the panel has been enabled (ie after
61      setNumeric or setNominal has been called */
62  private boolean m_isEnabled = false;
63
64  /** True if the colouring attribute is numeric */
65  private boolean m_isNumeric = false;
66   
67  /** The height of the spectrum for numeric class */
68  private final int m_spectrumHeight = 5;
69
70  /**  The maximum value for the colouring attribute */
71  private double m_maxC;
72   
73  /** The minimum value for the colouring attribute */
74  private double m_minC;
75
76  /** The size of the ticks */
77  private final int m_tickSize = 5;
78
79  /** Font metrics */
80  private FontMetrics m_labelMetrics = null;
81
82  /** The font used in labeling */
83  private Font m_labelFont = null;
84
85  /** The amount of space to leave either side of the legend */ 
86  private int m_HorizontalPad=0;
87
88  /** The precision with which to display real values */
89  private int m_precisionC;
90
91  /** Field width for numeric values */
92  private int m_fieldWidthC;
93
94  /** The old width. */
95  private int m_oldWidth = -9000;
96   
97  /** Instances being plotted */
98  private Instances m_Instances=null;
99
100  /** Index of the colouring attribute */
101  private int m_cIndex;
102
103  /** the list of colours to use for colouring nominal attribute labels */
104  private FastVector m_colorList;
105
106  /** An optional list of Components that use the colour list
107      maintained by this class. If the user changes a colour
108      using the colour chooser, then these components need to
109      be repainted in order to display the change */
110  private FastVector m_Repainters = new FastVector();
111
112  /** An optional list of listeners who want to know when a colour
113      changes. Listeners are notified via an ActionEvent */
114  private FastVector m_ColourChangeListeners = new FastVector();
115
116  /** default colours for colouring discrete class */
117  protected Color [] m_DefaultColors = {Color.blue,
118                                        Color.red,
119                                        Color.green,
120                                        Color.cyan,
121                                        Color.pink,
122                                        new Color(255, 0, 255),
123                                        Color.orange,
124                                        new Color(255, 0, 0),
125                                        new Color(0, 255, 0),
126                                        Color.white};
127 
128  /**
129   *  if set, it allows this panel to steer away from setting up
130   * a color in the color list that is equal to the background color
131   */
132  protected Color m_backgroundColor = null;
133
134  /** Inner Inner class used to create labels for nominal attributes
135   * so that there color can be changed.
136   */
137  private class NomLabel
138    extends JLabel {
139
140    /** for serialization */
141    private static final long serialVersionUID = -4686613106474820655L;
142
143    private int m_index = 0;
144
145    /**
146     * Creates a label with its name and class index value.
147     * @param name The name of the nominal class value.
148     * @param id The index value for that class value.
149     */
150    public NomLabel(String name, int id) {
151      super(name);
152      m_index = id;
153
154      this.addMouseListener(new MouseAdapter() {
155          public void mouseClicked(MouseEvent e) {
156             
157            if ((e.getModifiers() & e.BUTTON1_MASK) == e.BUTTON1_MASK) {
158              Color tmp = JColorChooser.showDialog
159                (ClassPanel.this, "Select new Color", 
160                 (Color)m_colorList.elementAt(m_index));
161               
162              if (tmp != null) {
163                m_colorList.setElementAt(tmp, m_index);
164                m_oldWidth = -9000;
165                ClassPanel.this.repaint();
166                if (m_Repainters.size() > 0) {
167                  for (int i=0;i<m_Repainters.size();i++) {
168                    ((Component)(m_Repainters.elementAt(i))).repaint();
169                  }
170                }
171               
172                if (m_ColourChangeListeners.size() > 0) {
173                  for (int i = 0; i < m_ColourChangeListeners.size(); i++) {
174                    ((ActionListener)(m_ColourChangeListeners.elementAt(i))).
175                      actionPerformed(new ActionEvent(this, 0, ""));
176                  }
177                }
178              }
179            }
180          }
181        });
182    }
183  }
184 
185  public ClassPanel() {
186    this(null);
187  }
188
189  public ClassPanel(Color background) {
190    m_backgroundColor = background;
191   
192    /** Set up some default colours */
193    m_colorList = new FastVector(10);
194    for (int noa = m_colorList.size(); noa < 10; noa++) {
195      Color pc = m_DefaultColors[noa % 10];
196      int ija =  noa / 10;
197      ija *= 2; 
198      for (int j=0;j<ija;j++) {
199        pc = pc.darker();
200      }
201       
202      m_colorList.addElement(pc);
203    }
204  }
205
206  /**
207   * Adds a component that will need to be repainted if the user
208   * changes the colour of a label.
209   * @param c the component to be repainted
210   */
211  public void addRepaintNotify(Component c) {
212    m_Repainters.addElement(c);
213  }
214
215  /**
216   * Add an action listener that will be notified if the user changes the
217   * colour of a label
218   *
219   * @param a an <code>ActionListener</code> value
220   */
221  public void addActionListener(ActionListener a) {
222    m_ColourChangeListeners.addElement(a);
223  }
224
225  /**
226   * Set up fonts and font metrics
227   * @param gx the graphics context
228   */
229  private void setFonts(Graphics gx) {
230    if (m_labelMetrics == null) {
231      m_labelFont = new Font("Monospaced", Font.PLAIN, 12);
232      m_labelMetrics = gx.getFontMetrics(m_labelFont);
233      int hf = m_labelMetrics.getAscent();
234      if (this.getHeight() < (3*hf)) {
235        m_labelFont = new Font("Monospaced",Font.PLAIN,11);
236        m_labelMetrics = gx.getFontMetrics(m_labelFont);
237      }
238    }
239    gx.setFont(m_labelFont);
240  }
241   
242  /**
243   * Enables the panel
244   * @param e true to enable the panel
245   */
246  public void setOn(boolean e) {
247    m_isEnabled = e;
248  }
249
250  /**
251   * Set the instances.
252   * @param insts the instances
253   */
254  public void setInstances(Instances insts) {
255    m_Instances = insts;
256  }
257
258  /**
259   * Set the index of the attribute to display coloured labels for
260   * @param cIndex the index of the attribute to display coloured labels for
261   */
262  public void setCindex(int cIndex) {
263    if (m_Instances.numAttributes() > 0) {
264      m_cIndex = cIndex;
265      if (m_Instances.attribute(m_cIndex).isNumeric()) {
266        setNumeric();
267      } else {
268        if (m_Instances.attribute(m_cIndex).numValues() > m_colorList.size()) {
269          extendColourMap();
270        }
271        setNominal();
272      }
273    }
274  }
275
276  /**
277   * Extends the list of colours if a new attribute with more values than
278   * the previous one is chosen
279   */
280  private void extendColourMap() {
281    if (m_Instances.attribute(m_cIndex).isNominal()) {
282      for (int i = m_colorList.size(); 
283           i < m_Instances.attribute(m_cIndex).numValues();
284           i++) {
285        Color pc = m_DefaultColors[i % 10];
286        int ija =  i / 10;
287        ija *= 2; 
288        for (int j=0;j<ija;j++) {
289          pc = pc.brighter();
290        }
291        if (m_backgroundColor != null) {
292          pc = Plot2D.checkAgainstBackground(pc, m_backgroundColor);
293        }
294       
295        m_colorList.addElement(pc);
296      }
297    }
298  }
299   
300  protected void setDefaultColourList(Color[] list) {
301    m_DefaultColors = list;
302  }
303
304  /**
305   * Set a list of colours to use for colouring labels
306   * @param cols a list containing java.awt.Colors
307   */
308  public void setColours(FastVector cols) {
309    m_colorList = cols;
310  }
311   
312  /**
313   * Sets the legend to be for a nominal variable
314   */
315  protected void setNominal() {
316    m_isNumeric = false;
317    m_HorizontalPad = 0;
318    setOn(true);
319    m_oldWidth = -9000;
320     
321    this.repaint();
322  }
323
324  /**
325   * Sets the legend to be for a numeric variable
326   */
327  protected void setNumeric() {
328    m_isNumeric = true;
329    /*      m_maxC = mxC;
330            m_minC = mnC; */
331
332    double min=Double.POSITIVE_INFINITY;
333    double max=Double.NEGATIVE_INFINITY;
334    double value;
335
336    for (int i=0;i<m_Instances.numInstances();i++) {
337      if (!m_Instances.instance(i).isMissing(m_cIndex)) {
338        value = m_Instances.instance(i).value(m_cIndex);
339        if (value < min) {
340          min = value;
341        }
342        if (value > max) {
343          max = value;
344        }
345      }
346    }
347     
348    // handle case where all values are missing
349    if (min == Double.POSITIVE_INFINITY) min = max = 0.0;
350
351    m_minC = min; m_maxC = max;
352
353    int whole = (int)Math.abs(m_maxC);
354    double decimal = Math.abs(m_maxC) - whole;
355    int nondecimal;
356    nondecimal = (whole > 0) 
357      ? (int)(Math.log(whole) / Math.log(10))
358      : 1;
359   
360    m_precisionC = (decimal > 0) 
361      ? (int)Math.abs(((Math.log(Math.abs(m_maxC)) / 
362                                      Math.log(10))))+2
363      : 1;
364    if (m_precisionC > VisualizeUtils.MAX_PRECISION) {
365      m_precisionC = 1;
366    }
367
368    String maxStringC = Utils.doubleToString(m_maxC,
369                                             nondecimal+1+m_precisionC
370                                             ,m_precisionC);
371    if (m_labelMetrics != null) {
372      m_HorizontalPad = m_labelMetrics.stringWidth(maxStringC);
373    }
374
375    whole = (int)Math.abs(m_minC);
376    decimal = Math.abs(m_minC) - whole;
377    nondecimal = (whole > 0) 
378      ? (int)(Math.log(whole) / Math.log(10))
379      : 1;
380   
381     m_precisionC = (decimal > 0) 
382       ? (int)Math.abs(((Math.log(Math.abs(m_minC)) / 
383                                      Math.log(10))))+2
384      : 1;
385     if (m_precisionC > VisualizeUtils.MAX_PRECISION) {
386       m_precisionC = 1;
387     }
388   
389     maxStringC = Utils.doubleToString(m_minC,
390                                       nondecimal+1+m_precisionC
391                                       ,m_precisionC);
392     if (m_labelMetrics != null) {
393       if (m_labelMetrics.stringWidth(maxStringC) > m_HorizontalPad) {
394         m_HorizontalPad = m_labelMetrics.stringWidth(maxStringC);
395       }
396     }
397
398    setOn(true);
399    this.repaint();
400  }
401   
402  /**
403   * Renders the legend for a nominal colouring attribute
404   * @param gx the graphics context
405   */
406  protected void paintNominal(Graphics gx) {
407    setFonts(gx);
408
409
410
411    int numClasses;
412
413    numClasses = m_Instances.attribute(m_cIndex).numValues();
414
415    int maxLabelLen = 0;
416    int idx=0;
417    int legendHeight;
418    int w = this.getWidth();
419    int hf = m_labelMetrics.getAscent();
420
421
422    for (int i=0;i<numClasses;i++) {
423      if (m_Instances.attribute(m_cIndex).value(i).length() > 
424          maxLabelLen) {
425        maxLabelLen = m_Instances.
426          attribute(m_cIndex).value(i).length();
427        idx = i;
428      }
429    }
430     
431    maxLabelLen = m_labelMetrics.stringWidth(m_Instances.
432                                             attribute(m_cIndex).value(idx));
433   
434
435    if (((w-(2*m_HorizontalPad))/(maxLabelLen+5)) >= numClasses) {
436      legendHeight = 1;
437    } else {
438      legendHeight = 2;
439    }
440       
441    int x = m_HorizontalPad;
442    int y = 1 + hf;
443
444    // do the first row
445    int ci, mp;
446    Color pc;
447    int numToDo = ((legendHeight==1) ? numClasses : (numClasses/2));
448    for (int i=0;i<numToDo;i++) {
449     
450      gx.setColor((Color)m_colorList.elementAt(i));
451      // can we fit the full label or will each need to be trimmed?
452      if ((numToDo * maxLabelLen) > (w-(m_HorizontalPad*2))) {
453        String val;
454        val = m_Instances.attribute(m_cIndex).value(i);
455
456        int sw = m_labelMetrics.stringWidth(val);
457        int rm=0;
458        // truncate string if necessary
459        if (sw > ((w-(m_HorizontalPad*2)) / (numToDo))) {
460          int incr = (sw / val.length());
461          rm = (sw -  ((w-(m_HorizontalPad*2)) / numToDo)) / incr;
462          if (rm <= 0) {
463            rm = 0;
464          }
465          if (rm >= val.length()) {
466            rm = val.length() - 1;
467          }
468          val = val.substring(0,val.length()-rm);
469          sw = m_labelMetrics.stringWidth(val);
470        }
471        NomLabel jj = new NomLabel(val, i);
472        jj.setFont(gx.getFont());
473
474        jj.setSize(m_labelMetrics.stringWidth(jj.getText()),
475                   m_labelMetrics.getAscent() + 4);
476        this.add(jj);
477        jj.setLocation(x, y);
478        jj.setForeground((Color)m_colorList.
479                         elementAt(i % m_colorList.size()));
480
481        x += sw + 2;
482      } else {
483       
484        NomLabel jj;
485        jj = new NomLabel(m_Instances.attribute(m_cIndex).value(i), i);
486
487        jj.setFont(gx.getFont());
488
489        jj.setSize(m_labelMetrics.stringWidth(jj.getText()),
490                   m_labelMetrics.getAscent() + 4);
491        this.add(jj);
492        jj.setLocation(x, y);
493        jj.setForeground((Color)m_colorList.
494                         elementAt(i % m_colorList.size()));
495
496 
497
498        x += ((w-(m_HorizontalPad*2)) / numToDo);
499      }   
500    }
501
502    x = m_HorizontalPad;
503    y = 1+ hf + 5 +hf;
504    for (int i=numToDo;i<numClasses;i++) {
505     
506      gx.setColor((Color)m_colorList.elementAt(i));
507      if (((numClasses-numToDo+1) * maxLabelLen) > 
508          (w - (m_HorizontalPad*2))) {
509        String val;
510        val = m_Instances.attribute(m_cIndex).value(i);
511
512        int sw = m_labelMetrics.stringWidth(val);
513        int rm=0;
514        // truncate string if necessary
515        if (sw > ((w-(m_HorizontalPad*2)) / (numClasses-numToDo+1))) {
516          int incr = (sw / val.length());
517          rm = (sw -  ((w-(m_HorizontalPad*2)) / (numClasses-numToDo))) 
518            / incr;
519          if (rm <= 0) {
520            rm = 0;
521          }
522          if (rm >= val.length()) {
523            rm = val.length() - 1;
524          }
525          val = val.substring(0,val.length()-rm);
526          sw = m_labelMetrics.stringWidth(val);
527        }
528        //this is the clipped string
529        NomLabel jj = new NomLabel(val, i);
530        jj.setFont(gx.getFont());
531
532        jj.setSize(m_labelMetrics.stringWidth(jj.getText()),
533                   m_labelMetrics.getAscent() + 4);
534
535        this.add(jj);
536        jj.setLocation(x, y);
537        jj.setForeground((Color)m_colorList.
538                         elementAt(i % m_colorList.size()));
539       
540        x += sw +2;
541      } else {
542        //this is the full string
543        NomLabel jj;
544        jj = new NomLabel(m_Instances.attribute(m_cIndex).value(i), i);
545
546        jj.setFont(gx.getFont());
547
548        jj.setSize(m_labelMetrics.stringWidth(jj.getText()),
549                   m_labelMetrics.getAscent() + 4);
550        this.add(jj);
551        jj.setLocation(x, y);
552        jj.setForeground((Color)m_colorList.
553                         elementAt(i % m_colorList.size()));
554
555        x += ((w - (m_HorizontalPad*2)) / (numClasses-numToDo));
556      }   
557    }
558
559  }
560
561  /**
562   * Renders the legend for a numeric colouring attribute
563   * @param gx the graphics context
564   */
565  protected void paintNumeric(Graphics gx) {
566
567    setFonts(gx);
568    if (m_HorizontalPad == 0) {
569      setCindex(m_cIndex);
570    }
571
572    int w = this.getWidth();
573    double rs = 15;
574    double incr = 240.0 / (double)(w-(m_HorizontalPad*2));
575    int hf = m_labelMetrics.getAscent();
576     
577    for (int i=m_HorizontalPad;i<
578           (w-m_HorizontalPad);i++) {
579      Color c = new Color((int)rs,150,(int)(255-rs));
580      gx.setColor(c);
581      gx.drawLine(i,0,
582                  i,0+m_spectrumHeight);
583      rs += incr;
584    }
585
586    int whole = (int)Math.abs(m_maxC);
587    double decimal = Math.abs(m_maxC) - whole;
588    int nondecimal;
589    nondecimal = (whole > 0) 
590      ? (int)(Math.log(whole) / Math.log(10))
591      : 1;
592   
593    m_precisionC = (decimal > 0) 
594      ? (int)Math.abs(((Math.log(Math.abs(m_maxC)) / 
595                        Math.log(10))))+2
596      : 1;
597    if (m_precisionC > VisualizeUtils.MAX_PRECISION) {
598      m_precisionC = 1;
599    }
600
601    String maxStringC = Utils.doubleToString(m_maxC,
602                                             nondecimal+1+m_precisionC
603                                             ,m_precisionC);
604
605       
606    int mswc = m_labelMetrics.stringWidth(maxStringC);
607    int tmsc = mswc;
608    if (w > (2 * tmsc)) {
609      gx.setColor(Color.black);
610      gx.drawLine(m_HorizontalPad,
611                  (m_spectrumHeight+5),
612                  w-m_HorizontalPad,
613                  (m_spectrumHeight+5));
614
615      gx.drawLine(w-m_HorizontalPad,
616                  (m_spectrumHeight+5),
617                  w-m_HorizontalPad,
618                  (m_spectrumHeight+5+m_tickSize));
619
620      gx.drawString(maxStringC, 
621                    (w-m_HorizontalPad)-(mswc/2),
622                    (m_spectrumHeight+5+m_tickSize+hf));
623
624      gx.drawLine(m_HorizontalPad,
625                  (m_spectrumHeight+5),
626                  m_HorizontalPad,
627                  (m_spectrumHeight+5+m_tickSize));
628
629      whole = (int)Math.abs(m_minC);
630      decimal = Math.abs(m_minC) - whole;
631      nondecimal = (whole > 0) 
632        ? (int)(Math.log(whole) / Math.log(10))
633        : 1;
634     
635      m_precisionC = (decimal > 0) 
636        ? (int)Math.abs(((Math.log(Math.abs(m_minC)) / 
637                          Math.log(10))))+2
638        : 1;
639
640      if (m_precisionC > VisualizeUtils.MAX_PRECISION) {
641        m_precisionC = 1;
642      }
643     
644      maxStringC = Utils.doubleToString(m_minC,
645                                        nondecimal+1+m_precisionC
646                                        ,m_precisionC);
647
648      mswc = m_labelMetrics.stringWidth(maxStringC);
649      gx.drawString(maxStringC, 
650                    m_HorizontalPad-(mswc/2),
651                    (m_spectrumHeight+5+m_tickSize+hf));
652
653      // draw the middle value if there is space
654      if (w > (3 * tmsc)) {
655        double mid = m_minC+((m_maxC-m_minC)/2.0);
656        gx.drawLine(m_HorizontalPad+((w-(2*m_HorizontalPad))/2),
657                    (m_spectrumHeight+5),
658                    m_HorizontalPad+((w-(2*m_HorizontalPad))/2),
659                    (m_spectrumHeight+5+m_tickSize));
660
661        whole = (int)Math.abs(mid);
662        decimal = Math.abs(mid) - whole;
663        nondecimal = (whole > 0) 
664          ? (int)(Math.log(whole) / Math.log(10))
665          : 1;
666       
667        m_precisionC = (decimal > 0) 
668          ? (int)Math.abs(((Math.log(Math.abs(mid)) / 
669                            Math.log(10))))+2
670          : 1;
671        if (m_precisionC > VisualizeUtils.MAX_PRECISION) {
672          m_precisionC = 1;
673        }
674       
675        maxStringC = Utils.doubleToString(mid,
676                                          nondecimal+1+m_precisionC
677                                          ,m_precisionC);
678
679        mswc = m_labelMetrics.stringWidth(maxStringC);
680        gx.drawString(maxStringC,
681                      m_HorizontalPad+((w-(2*m_HorizontalPad))/2)-(mswc/2),
682                      (m_spectrumHeight+5+m_tickSize+hf));
683      }
684    }
685  }
686
687  /**
688   * Renders this component
689   * @param gx the graphics context
690   */
691  public void paintComponent(Graphics gx) {
692    super.paintComponent(gx);
693    if (m_isEnabled) {
694      if (m_isNumeric) {
695        m_oldWidth = -9000;   //done so that if change back to nom, it will
696        //work
697        this.removeAll();
698        paintNumeric(gx);
699      } else {
700        if (m_Instances != null && 
701            m_Instances.numInstances() > 0 && 
702            m_Instances.numAttributes() > 0) {
703          if (m_oldWidth != this.getWidth()) {
704            this.removeAll();
705            m_oldWidth = this.getWidth();
706            paintNominal(gx);
707          }
708        }
709      }
710    }
711  }
712
713  /**
714   * Main method for testing this class.
715   * @param args first argument must specify an arff file. Second can
716   * specify an optional index to colour labels on
717   */
718  public static void main(String [] args) {
719    try {
720      if (args.length < 1) {
721        System.err.println("Usage : weka.gui.visualize.ClassPanel <dataset> "
722                           +"[class col]");
723        System.exit(1);
724      }
725      final javax.swing.JFrame jf = 
726        new javax.swing.JFrame("Weka Explorer: Class");
727      jf.setSize(500,100);
728      jf.getContentPane().setLayout(new BorderLayout());
729      final ClassPanel p2 = new ClassPanel();
730      jf.getContentPane().add(p2, BorderLayout.CENTER);
731      jf.addWindowListener(new java.awt.event.WindowAdapter() {
732          public void windowClosing(java.awt.event.WindowEvent e) {
733            jf.dispose();
734            System.exit(0);
735          }
736        });
737       
738      if (args.length >= 1) {
739        System.err.println("Loading instances from " + args[0]);
740        java.io.Reader r = new java.io.BufferedReader(
741                           new java.io.FileReader(args[0]));
742        Instances i = new Instances(r);
743        i.setClassIndex(i.numAttributes()-1);
744        p2.setInstances(i);
745      }
746      if (args.length > 1) {
747        p2.setCindex((Integer.parseInt(args[1]))-1);
748      } else {
749        p2.setCindex(0);
750      }
751      jf.setVisible(true);
752    } catch (Exception ex) {
753      ex.printStackTrace();
754      System.err.println(ex.getMessage());
755    }
756  }
757}
Note: See TracBrowser for help on using the repository browser.