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