1 /*
   2  * Copyright (c) 2008, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javax.swing.colorchooser;
  27 
  28 import java.awt.event.FocusEvent;
  29 import java.awt.event.FocusListener;
  30 import java.text.ParseException;
  31 import static java.util.Locale.ENGLISH;
  32 import javax.swing.JFormattedTextField;
  33 import javax.swing.JFormattedTextField.AbstractFormatter;
  34 import javax.swing.SwingConstants;
  35 import javax.swing.SwingUtilities;
  36 import javax.swing.text.AttributeSet;
  37 import javax.swing.text.BadLocationException;
  38 import javax.swing.text.DefaultFormatterFactory;
  39 import javax.swing.text.DocumentFilter;
  40 
  41 @SuppressWarnings("serial") // Same-version serialization only
  42 final class ValueFormatter extends AbstractFormatter implements FocusListener, Runnable {
  43 
  44     static void init(int length, boolean hex, JFormattedTextField text) {
  45         ValueFormatter formatter = new ValueFormatter(length, hex);
  46         text.setColumns(length);
  47         text.setFormatterFactory(new DefaultFormatterFactory(formatter));
  48         text.setHorizontalAlignment(SwingConstants.RIGHT);
  49         text.setMinimumSize(text.getPreferredSize());
  50         text.addFocusListener(formatter);
  51     }
  52 
  53     private final DocumentFilter filter = new DocumentFilter() {
  54         @Override
  55         public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
  56             if (isValid(fb.getDocument().getLength() - length)) {
  57                 fb.remove(offset, length);
  58             }
  59         }
  60 
  61         @Override
  62         public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet set) throws BadLocationException {
  63             if (isValid(fb.getDocument().getLength() + text.length() - length) && isValid(text)) {
  64                 fb.replace(offset, length, text.toUpperCase(ENGLISH), set);
  65             }
  66         }
  67 
  68         @Override
  69         public void insertString(FilterBypass fb, int offset, String text, AttributeSet set) throws BadLocationException {
  70             if (isValid(fb.getDocument().getLength() + text.length()) && isValid(text)) {
  71                 fb.insertString(offset, text.toUpperCase(ENGLISH), set);
  72             }
  73         }
  74     };
  75 
  76     private final int length;
  77     private final int radix;
  78 
  79     private JFormattedTextField text;
  80 
  81     ValueFormatter(int length, boolean hex) {
  82         this.length = length;
  83         this.radix = hex ? 16 : 10;
  84     }
  85 
  86     @Override
  87     public Object stringToValue(String text) throws ParseException {
  88         try {
  89             return Integer.valueOf(text, this.radix);
  90         }
  91         catch (NumberFormatException nfe) {
  92             ParseException pe = new ParseException("illegal format", 0);
  93             pe.initCause(nfe);
  94             throw pe;
  95         }
  96     }
  97 
  98     @Override
  99     public String valueToString(Object object) throws ParseException {
 100         if (object instanceof Integer) {
 101             if (this.radix == 10) {
 102                 return object.toString();
 103             }
 104             int value = (Integer) object;
 105             int index = this.length;
 106             char[] array = new char[index];
 107             while (0 < index--) {
 108                 array[index] = Character.forDigit(value & 0x0F, this.radix);
 109                 value >>= 4;
 110             }
 111             return new String(array).toUpperCase(ENGLISH);
 112         }
 113         throw new ParseException("illegal object", 0);
 114     }
 115 
 116     @Override
 117     protected DocumentFilter getDocumentFilter() {
 118         return this.filter;
 119     }
 120 
 121     public void focusGained(FocusEvent event) {
 122         Object source = event.getSource();
 123         if (source instanceof JFormattedTextField) {
 124             this.text = (JFormattedTextField) source;
 125             SwingUtilities.invokeLater(this);
 126         }
 127     }
 128 
 129     public void focusLost(FocusEvent event) {
 130     }
 131 
 132     public void run() {
 133         if (this.text != null) {
 134             this.text.selectAll();
 135         }
 136     }
 137 
 138     private boolean isValid(int length) {
 139         return (0 <= length) && (length <= this.length);
 140     }
 141 
 142     private boolean isValid(String text) {
 143         int length = text.length();
 144         for (int i = 0; i < length; i++) {
 145             char ch = text.charAt(i);
 146             if (Character.digit(ch, this.radix) < 0) {
 147                 return false;
 148             }
 149         }
 150         return true;
 151     }
 152 }