001    package org.LiveGraph.gui;
002    
003    import java.awt.Color;
004    import java.awt.Point;
005    import java.awt.event.FocusEvent;
006    import java.awt.event.FocusListener;
007    import java.awt.event.KeyEvent;
008    import java.awt.event.KeyListener;
009    import java.util.HashMap;
010    
011    import javax.swing.JLabel;
012    import javax.swing.JTextField;
013    import javax.swing.Popup;
014    import javax.swing.PopupFactory;
015    
016    
017    /**
018     * This validating adaptor listens to {@code focusLost}-messages of a
019     * {@code JTextField}. If at that moment the field contains a valid
020     * {@code double} value (as a string), the {@link #valueChanged(JTextField, double)}-method is
021     * called. That method must be overridden by subclasses to take some appropriate
022     * action. If, however, the field does not contain a valid {@code double} value,
023     * the {@link #valueChanged(JTextField, double)}-method is not called and a tooltip with an error
024     * message is displayed near the text fiels. The last known "good" value is then
025     * restored.
026     * 
027     * <p style="font-size:smaller;">This product includes software developed by the
028     *    <strong>LiveGraph</strong> project and its contributors.<br />
029     *    (<a href="http://www.live-graph.org" target="_blank">http://www.live-graph.org</a>)<br />
030     *    Copyright (c) 2007 G. Paperin.<br />
031     *    All rights reserved.
032     * </p>
033     * <p style="font-size:smaller;">File: RealNumFieldValueChangeAdaptor.java</p> 
034     * <p style="font-size:smaller;">Redistribution and use in source and binary forms, with or
035     *    without modification, are permitted provided that the following terms and conditions are met:
036     * </p>
037     * <p style="font-size:smaller;">1. Redistributions of source code must retain the above
038     *    acknowledgement of the LiveGraph project and its web-site, the above copyright notice,
039     *    this list of conditions and the following disclaimer.<br />
040     *    2. Redistributions in binary form must reproduce the above acknowledgement of the
041     *    LiveGraph project and its web-site, the above copyright notice, this list of conditions
042     *    and the following disclaimer in the documentation and/or other materials provided with
043     *    the distribution.<br />
044     *    3. All advertising materials mentioning features or use of this software or any derived
045     *    software must display the following acknowledgement:<br />
046     *    <em>This product includes software developed by the LiveGraph project and its
047     *    contributors.<br />(http://www.live-graph.org)</em><br />
048     *    4. All advertising materials distributed in form of HTML pages or any other technology
049     *    permitting active hyper-links that mention features or use of this software or any
050     *    derived software must display the acknowledgment specified in condition 3 of this
051     *    agreement, and in addition, include a visible and working hyper-link to the LiveGraph
052     *    homepage (http://www.live-graph.org).
053     * </p>
054     * <p style="font-size:smaller;">THIS SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY
055     *    OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
056     *    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT SHALL
057     *    THE AUTHORS, CONTRIBUTORS OR COPYRIGHT  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
058     *    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  FROM, OUT OF OR
059     *    IN CONNECTION WITH THE SOFTWARE OR THE USE OR  OTHER DEALINGS IN THE SOFTWARE.
060     * </p>
061     * 
062     * @author Greg Paperin (<a href="http://www.paperin.org" target="_blank">http://www.paperin.org</a>)
063     * @version {@value org.LiveGraph.LiveGraph#version}
064     */
065    public abstract class RealNumFieldValueChangeAdaptor implements FocusListener, KeyListener {
066    
067    /**
068     * Display length for the error messgae tooltip in milliseconds.
069     */
070    public static final long TOOLTIP_DISPLAY_LEN = 2000;
071    
072    private double defaultValue = Double.NaN;
073    private HashMap<JTextField, Double> lastLegalvaluesCache = null;
074    
075    /**
076     * Constructor.
077     * @param defaultValue Default "good" value.
078     */
079    public RealNumFieldValueChangeAdaptor(double defaultValue) {
080            this.defaultValue = defaultValue;
081            this.lastLegalvaluesCache = new HashMap<JTextField, Double>();
082    }
083    
084    /**
085     * Does nothing.
086     */
087    public void focusGained(FocusEvent e) {
088            ;
089    }
090    
091    /**
092     * Catches the focus lost event and performs field validation.
093     */
094    public void focusLost(FocusEvent e) {   
095            Object source = e.getSource();
096            if (source instanceof JTextField)               
097                    handleEvent((JTextField) source);       
098    }
099    
100    /**
101     * Catches the enter pressed event and performs field validation.
102     */
103    public void keyPressed(KeyEvent e) {
104            Object source = e.getSource();
105            if (! (source instanceof JTextField))
106                    return;
107            if (KeyEvent.VK_ENTER != e.getKeyCode())
108                    return;
109            handleEvent((JTextField) source);
110    }
111    
112    /**
113     * Does nothing.
114     */
115    public void keyReleased(KeyEvent e) {
116            ;
117    }
118    
119    /**
120     * Does nothing.
121     */
122    public void keyTyped(KeyEvent e) {
123            ;
124    }
125    
126    /**
127     * Performs the validation and handles appropriately.
128     * 
129     * @param field The text field that generated the event.
130     */
131    public void handleEvent(final JTextField field) {       
132            try {                           
133                    double val = Double.parseDouble(field.getText());
134                    lastLegalvaluesCache.put(field, val);           
135                    double newVal = valueChanged(field, val);
136                    if (newVal != val) {
137                            field.setText(Double.toString(newVal));
138                            lastLegalvaluesCache.put(field, newVal);
139                    }
140            } catch (NumberFormatException ex) {
141                    Point sc = field.getLocationOnScreen();
142                    sc.x += field.getWidth() / 2;
143                    sc.y += field.getHeight() / 2;
144                    JLabel info = new JLabel("You need a valid real value here. \"" + field.getText() + "\" is not a real number.");
145                    info.setOpaque(true);
146                    info.setBackground(Color.ORANGE);
147                    info.setForeground(Color.DARK_GRAY);
148                    final Popup popup = PopupFactory.getSharedInstance().getPopup(field, info, sc.x, sc.y);
149                    popup.show();
150                    Thread offSwitch = new Thread(new Runnable() {
151                            public void run() {
152                                    try { Thread.sleep(TOOLTIP_DISPLAY_LEN); } catch(InterruptedException e) {}
153                                    finally {
154                                            popup.hide();
155                                            if (lastLegalvaluesCache.containsKey(field))
156                                                    field.setText(lastLegalvaluesCache.get(field).toString());
157                                            else
158                                                    field.setText(Double.toString(defaultValue));
159                                    }
160                    }}, "RealNumFieldValueChangeAdaptor.OffSwitch");
161                    offSwitch.start();
162            }
163    }
164    
165    /**
166     * Subclasses must override that in order to take the appropriate action when the field
167     * contains a valid {@code double} value.
168     * 
169     * @param field The text field containing the value.
170     * @param newValue The {@code double} value in the field.
171     * @return A string representing the value returned by this method will be used instead
172     * of the current field content.  
173     */
174    abstract public double valueChanged(JTextField field, double newValue);
175    
176    }