001    /*
002     * $Id: McVTextField.java,v 1.13 2012/02/19 17:35:52 davep Exp $
003     *
004     * This file is part of McIDAS-V
005     *
006     * Copyright 2007-2012
007     * Space Science and Engineering Center (SSEC)
008     * University of Wisconsin - Madison
009     * 1225 W. Dayton Street, Madison, WI 53706, USA
010     * https://www.ssec.wisc.edu/mcidas
011     * 
012     * All Rights Reserved
013     * 
014     * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015     * some McIDAS-V source code is based on IDV and VisAD source code.  
016     * 
017     * McIDAS-V is free software; you can redistribute it and/or modify
018     * it under the terms of the GNU Lesser Public License as published by
019     * the Free Software Foundation; either version 3 of the License, or
020     * (at your option) any later version.
021     * 
022     * McIDAS-V is distributed in the hope that it will be useful,
023     * but WITHOUT ANY WARRANTY; without even the implied warranty of
024     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
025     * GNU Lesser Public License for more details.
026     * 
027     * You should have received a copy of the GNU Lesser Public License
028     * along with this program.  If not, see http://www.gnu.org/licenses.
029     */
030    
031    package edu.wisc.ssec.mcidasv.util;
032    
033    import java.awt.BorderLayout;
034    import java.awt.Color;
035    import java.awt.event.FocusEvent;
036    import java.awt.event.FocusListener;
037    import java.util.regex.Matcher;
038    import java.util.regex.Pattern;
039    import java.util.regex.PatternSyntaxException;
040    
041    import javax.swing.InputVerifier;
042    import javax.swing.JComponent;
043    import javax.swing.JLabel;
044    import javax.swing.JTextField;
045    import javax.swing.SwingConstants;
046    import javax.swing.border.EmptyBorder;
047    import javax.swing.event.DocumentEvent;
048    import javax.swing.event.DocumentListener;
049    import javax.swing.text.AttributeSet;
050    import javax.swing.text.BadLocationException;
051    import javax.swing.text.Document;
052    import javax.swing.text.JTextComponent;
053    import javax.swing.text.PlainDocument;
054    
055    /**
056     * Extend JTextField to add niceties such as uppercase,
057     * length limits, and allow/deny character sets
058     */
059    public class McVTextField extends JTextField {
060            
061            public static char[] mcidasDeny = new char[] { '/', '.', ' ', '[', ']', '%' };
062            
063            public static Pattern ipAddress = Pattern.compile("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
064    
065            private McVTextFieldDocument document = new McVTextFieldDocument();
066            
067            private Pattern validPattern;
068            private String[] validStrings;
069            
070            public McVTextField() {
071                    this("", 0, false);
072            }
073            
074            public McVTextField(String defaultString) {
075                    this(defaultString, 0, false);
076            }
077            
078            public McVTextField(String defaultString, int limit) {
079                    this(defaultString, limit, false);
080            }
081            
082            public McVTextField(String defaultString, boolean upper) {
083                    this(defaultString, 0, upper);
084            }
085            
086            // All other constructors call this one
087            public McVTextField(String defaultString, int limit, boolean upper) {
088                    super(limit);
089                    this.document = new McVTextFieldDocument(limit, upper);
090                    super.setDocument(document);
091                    this.setText(defaultString);
092            }
093            
094            public McVTextField(String defaultString, int limit, boolean upper, String allow, String deny) {
095                    this(defaultString, limit, upper);
096                    setAllow(makePattern(allow));
097                    setDeny(makePattern(deny));
098            }
099            
100            public McVTextField(String defaultString, int limit, boolean upper, char[] allow, char[] deny) {
101                    this(defaultString, limit, upper);
102                    setAllow(makePattern(allow));
103                    setDeny(makePattern(deny));
104            }
105            
106            public McVTextField(String defaultString, int limit, boolean upper, Pattern allow, Pattern deny) {
107                    this(defaultString, limit, upper);
108                    setAllow(allow);
109                    setDeny(deny);
110            }
111    
112            public int getLimit() {
113                    return this.document.getLimit();
114            }
115            
116            public void setLimit(int limit) {
117                    this.document.setLimit(limit);
118                    super.setDocument(document);
119            }
120            
121            public boolean getUppercase() {
122                    return this.document.getUppercase();
123            }
124            
125            public void setUppercase(boolean uppercase) {
126                    this.document.setUppercase(uppercase);
127                    super.setDocument(document);
128            }
129                    
130            public void setAllow(String string) {
131                    this.document.setAllow(makePattern(string));
132                    super.setDocument(document);
133            }
134            
135            public void setDeny(String string) {
136                    this.document.setDeny(makePattern(string));
137                    super.setDocument(document);
138            }
139                    
140            public void setAllow(char[] characters) {
141                    this.document.setAllow(makePattern(characters));
142                    super.setDocument(document);
143            }
144            
145            public void setDeny(char[] characters) {
146                    this.document.setDeny(makePattern(characters));
147                    super.setDocument(document);
148            }
149            
150            public void setAllow(Pattern newPattern) {
151                    this.document.setAllow(newPattern);
152                    super.setDocument(document);
153            }
154            
155            public void setDeny(Pattern newPattern) {
156                    this.document.setDeny(newPattern);
157                    super.setDocument(document);
158            }
159            
160            // Take a string and turn it into a pattern
161            private Pattern makePattern(String string) {
162                    if (string == null) return null;
163                    try {
164                            return Pattern.compile(string);
165                    }
166                    catch (PatternSyntaxException e) {
167                            return null;
168                    }
169            }
170            
171            // Take a character array and turn it into a [abc] class pattern
172            private Pattern makePattern(char[] characters) {
173                    if (characters == null) return null;
174                    String string = ".*";
175                    if (characters.length > 0) {
176                            string = "[";
177                            for (char c : characters) {
178                                    if (c == '[') string += "\\[";
179                                    else if (c == ']') string += "\\]";
180                                    else if (c == '\\') string += "\\\\";
181                                    else string += c;
182                            }
183                            string += "]";
184                    }
185                    try {
186                            return Pattern.compile(string);
187                    }
188                    catch (PatternSyntaxException e) {
189                            return null;
190                    }
191            }
192            
193            // Add an InputVerifier if we want to validate a particular pattern
194            public void setValidPattern(String string) {
195                    if (string == null) return;
196                    try {
197                            Pattern newPattern = Pattern.compile(string);
198                            setValidPattern(newPattern);
199                    }
200                    catch (PatternSyntaxException e) {
201                            return;
202                    }
203            }
204            
205            // Add an InputVerifier if we want to validate a particular pattern
206            public void setValidPattern(Pattern pattern) {
207                    if (pattern == null) {
208                            this.validPattern = null;
209                            if (this.validStrings == null) removeInputVerifier();
210                    }
211                    else {
212                            this.validPattern = pattern;
213                            addInputVerifier();
214                    }
215            }
216            
217            // Add an InputVerifier if we want to validate a particular set of strings
218            public void setValidStrings(String[] strings) {
219                    if (strings == null) {
220                            this.validStrings = null;
221                            if (this.validPattern == null) removeInputVerifier();
222                    }
223                    else {
224                            this.validStrings = strings;
225                            addInputVerifier();
226                    }
227            }
228            
229            private void addInputVerifier() {
230                    this.setInputVerifier(new InputVerifier() {
231                            public boolean verify(JComponent comp) {
232                                    return verifyInput();
233                            }
234                            public boolean shouldYieldFocus(JComponent comp) {
235                                    boolean valid = verify(comp);
236                                    if (!valid) {
237                                            getToolkit().beep();
238                                    }
239                                    return valid;
240                            }
241                    });
242                    verifyInput();
243            }
244            
245            private void removeInputVerifier() {
246                    this.setInputVerifier(null);
247            }
248            
249            private boolean verifyInput() {
250                    boolean isValid = false;
251                    String checkValue = this.getText();
252                    if (checkValue=="") return true;
253                    
254                    if (this.validStrings != null) {
255                            for (String string : validStrings) {
256                                    if (checkValue.equals(string)) isValid = true;
257                            }
258                    }
259                    
260                    if (this.validPattern != null) {
261                            Matcher validMatch = this.validPattern.matcher(checkValue);
262                            isValid = isValid || validMatch.matches();
263                    }
264                    
265                    if (!isValid) {
266                            this.selectAll();
267                    }
268                    return isValid;
269            }
270            
271            /**
272             * Extend PlainDocument to get the character validation features we require
273             */
274            private class McVTextFieldDocument extends PlainDocument {
275                    private int limit;
276                    private boolean toUppercase = false;            
277                    private boolean hasPatterns = false;
278                    private Pattern allow = Pattern.compile(".*");
279                    private Pattern deny = null;
280                                                                                    
281                    public McVTextFieldDocument() {
282                            super();
283                    }
284                                    
285                    public McVTextFieldDocument(int limit, boolean upper) {
286                            super();
287                            setLimit(limit);
288                            setUppercase(upper);
289                    }
290    
291                    public void insertString(int offset, String str, AttributeSet attr) throws BadLocationException {
292                            if (str == null) return;
293                            if (toUppercase) str = str.toUpperCase();
294                                                    
295                            // Only allow certain patterns, and only check if we think we have patterns
296                            if (hasPatterns) {
297                                    char[] characters = str.toCharArray();
298                                    String okString = "";
299                                    for (char c : characters) {
300                                            String s = "" + c;
301                                            if (deny != null) {
302                                                    Matcher denyMatch = deny.matcher(s);
303                                                    if (denyMatch.matches()) continue;
304                                            }
305                                            if (allow != null) {
306                                                    Matcher allowMatch = allow.matcher(s);
307                                                    if (allowMatch.matches()) okString += s;
308                                            }
309                                    }
310                                    str = okString;
311                            }
312                            if (str.equals("")) return;
313    
314                            if ((getLength() + str.length()) <= limit || limit <= 0) {
315                                    super.insertString(offset, str, attr);
316                            }
317                    }
318                    
319                    public int getLimit() {
320                            return this.limit;
321                    }
322                    
323                    public void setLimit(int limit) {
324                            this.limit = limit;
325                    }
326                    
327                    public boolean getUppercase() {
328                            return this.toUppercase;
329                    }
330                    
331                    public void setUppercase(boolean uppercase) {
332                            this.toUppercase = uppercase;
333                    }
334                                            
335                    public void setAllow(Pattern newPattern) {
336                            if (newPattern==null) return;
337                            this.allow = newPattern;
338                            hasPatterns = true;
339                    }
340                    
341                    public void setDeny(Pattern newPattern) {
342                            if (newPattern==null) return;
343                            this.deny = newPattern;
344                            hasPatterns = true;
345                    }
346                    
347            }
348    
349            public static class Prompt extends JLabel implements FocusListener, DocumentListener {
350    
351                public enum FocusBehavior { ALWAYS, FOCUS_GAINED, FOCUS_LOST };
352    
353                private final JTextComponent component;
354    
355                private final Document document;
356    
357                private FocusBehavior focus;
358    
359                private boolean showPromptOnce;
360    
361                private int focusLost;
362    
363                public Prompt(final JTextComponent component, final String text) {
364                    this(component, FocusBehavior.FOCUS_LOST, text);
365                }
366    
367                public Prompt(final JTextComponent component, final FocusBehavior focusBehavior, final String text) {
368                    this.component = component;
369                    setFocusBehavior(focusBehavior);
370    
371                    document = component.getDocument();
372    
373                    setText(text);
374                    setFont(component.getFont());
375                    setForeground(component.getForeground());
376                    setHorizontalAlignment(JLabel.LEADING);
377                    setEnabled(false);
378    
379                    component.addFocusListener(this);
380                    document.addDocumentListener(this);
381    
382                    component.setLayout(new BorderLayout());
383                    component.add(this);
384                    checkForPrompt();
385                }
386    
387                public FocusBehavior getFocusBehavior() {
388                    return focus;
389                }
390    
391                public void setFocusBehavior(final FocusBehavior focus) {
392                    this.focus = focus;
393                }
394    
395                public boolean getShowPromptOnce() {
396                    return showPromptOnce;
397                }
398    
399                public void setShowPromptOnce(final boolean showPromptOnce) {
400                    this.showPromptOnce = showPromptOnce;
401                }
402    
403                /**
404                 * Check whether the prompt should be visible or not. The visibility
405                 * will change on updates to the Document and on focus changes.
406                 */
407                private void checkForPrompt() {
408                    // text has been entered, remove the prompt
409                    if (document.getLength() > 0) {
410                        setVisible(false);
411                        return;
412                    }
413    
414                    // prompt has already been shown once, remove it
415                    if (showPromptOnce && focusLost > 0) {
416                        setVisible(false);
417                        return;
418                    }
419    
420                    // check the behavior property and component focus to determine if the
421                    // prompt should be displayed.
422                    if (component.hasFocus()) {
423                        if (focus == FocusBehavior.ALWAYS || focus == FocusBehavior.FOCUS_GAINED) {
424                            setVisible(true);
425                        } else {
426                            setVisible(false);
427                        }
428                    } else {
429                        if (focus == FocusBehavior.ALWAYS || focus == FocusBehavior.FOCUS_LOST) {
430                            setVisible( true);
431                        } else {
432                            setVisible(false);
433                        }
434                    }
435                }
436    
437                // from FocusListener
438                public void focusGained(FocusEvent e) {
439                    checkForPrompt();
440                }
441    
442                public void focusLost(FocusEvent e) {
443                    focusLost++;
444                    checkForPrompt();
445                }
446    
447                // from DocumentListener
448                public void insertUpdate(DocumentEvent e) {
449                    checkForPrompt();
450                }
451    
452                public void removeUpdate(DocumentEvent e) {
453                    checkForPrompt();
454                }
455    
456                public void changedUpdate(DocumentEvent e) {}
457            }
458    }