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