001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2016
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
029package edu.wisc.ssec.mcidasv.util;
030
031import java.awt.BorderLayout;
032import java.awt.Color;
033import java.awt.event.FocusEvent;
034import java.awt.event.FocusListener;
035import java.util.regex.Matcher;
036import java.util.regex.Pattern;
037import java.util.regex.PatternSyntaxException;
038
039import javax.swing.InputVerifier;
040import javax.swing.JComponent;
041import javax.swing.JLabel;
042import javax.swing.JTextField;
043import javax.swing.SwingConstants;
044import javax.swing.border.EmptyBorder;
045import javax.swing.event.DocumentEvent;
046import javax.swing.event.DocumentListener;
047import javax.swing.text.AttributeSet;
048import javax.swing.text.BadLocationException;
049import javax.swing.text.Document;
050import javax.swing.text.JTextComponent;
051import javax.swing.text.PlainDocument;
052
053/**
054 * Extend JTextField to add niceties such as uppercase,
055 * length limits, and allow/deny character sets
056 */
057public 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.isEmpty()) 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}