/*
* NumericTextField.java - A TextField that accepts only numeric values
* :tabSize=4:indentSize=4:noTabs=false:
* :folding=explicit:collapseFolds=1:
*
* Copyright (C) 2008 Matthieu Casanova
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.gjt.sp.jedit.gui;
import javax.swing.*;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
import javax.swing.text.DocumentFilter.FilterBypass;
import org.gjt.sp.jedit.jEdit;
import org.gjt.sp.jedit.syntax.SyntaxStyle;
import org.gjt.sp.util.SyntaxUtilities;
/** A TextField that accepts only numeric values. The numeric values may be
* either integer or float values.
* @author Matthieu Casanova
* @version $Id: KeyEventWorkaround.java 12889 2008-06-23 20:14:00Z kpouer $
* @since jEdit 4.3pre15
*/
public class NumericTextField extends JTextField implements ComboBoxEditor
{
private final boolean positiveOnly;
private final boolean integerOnly;
private Number minValue;
private Number maxValue;
private SyntaxStyle invalidStyle;
private Color defaultBackground;
private Color defaultForeground;
public NumericTextField(String text)
{
this(text, false);
}
public NumericTextField(String text, boolean positiveOnly)
{
super(text);
this.positiveOnly = positiveOnly;
integerOnly = true;
minValue = positiveOnly ? new Integer(0) : Integer.MIN_VALUE;
maxValue = Integer.MAX_VALUE;
addFilter();
loadInvalidStyle();
}
public NumericTextField(String text, boolean positiveOnly, boolean integerOnly)
{
super(text);
this.positiveOnly = positiveOnly;
this.integerOnly = integerOnly;
if (integerOnly)
{
minValue = positiveOnly ? new Integer(0) : Integer.MIN_VALUE;
maxValue = Integer.MAX_VALUE;
}
else
{
minValue = positiveOnly ? new Float(0.0) : Float.MIN_VALUE;
maxValue = Float.MAX_VALUE;
}
addFilter();
loadInvalidStyle();
}
public NumericTextField(String text, int columns, boolean positiveOnly)
{
super(text, columns);
this.positiveOnly = positiveOnly;
integerOnly = true;
minValue = positiveOnly ? new Integer(0) : Integer.MIN_VALUE;
maxValue = Integer.MAX_VALUE;
addFilter();
loadInvalidStyle();
}
public NumericTextField(String text, int columns, boolean positiveOnly, boolean integerOnly)
{
super(text, columns);
this.positiveOnly = positiveOnly;
this.integerOnly = integerOnly;
if (integerOnly)
{
minValue = positiveOnly ? new Integer(0) : Integer.MIN_VALUE;
maxValue = Integer.MAX_VALUE;
}
else
{
minValue = positiveOnly ? new Float(0.0) : Float.MIN_VALUE;
maxValue = Float.MAX_VALUE;
}
addFilter();
loadInvalidStyle();
}
private void loadInvalidStyle()
{
Font font = getFont();
String family = font.getFamily();
int size = font.getSize();
invalidStyle = SyntaxUtilities.parseStyle(jEdit.getProperty("view.style.invalid"), family, size, true);
defaultForeground = getForeground();
defaultBackground = getBackground();
}
// set the minimum allowed value for this text field. If this NumericTextField
// was constructed with positive only, then values less than zero are ignored.
public void setMinValue(Number n)
{
if (positiveOnly)
{
float f = n.floatValue();
if (f < 0.0)
return;
}
if (integerOnly)
{
int i = n.intValue();
int max = maxValue.intValue();
if (i > max)
return;
}
else
{
float f = n.floatValue();
float max = maxValue.floatValue();
if (f > max)
return;
}
minValue = n;
}
// set the maximum allowed value for this text field. If this NumericTextField
// was constructed with positive only, then values less than zero are ignored.
public void setMaxValue(Number n)
{
if (positiveOnly)
{
float f = n.floatValue();
if (f < 0)
return;
}
if (integerOnly)
{
int i = n.intValue();
int min = minValue.intValue();
if (i < min)
return;
}
else
{
float f = n.floatValue();
float min = minValue.floatValue();
if (f < min)
return;
}
maxValue = n;
}
/**
* @return The value of the text field as either an Integer or a Float,
* depending on whether this text field allows Integers or Floats.
*/
public Number getValue()
{
if (integerOnly)
return Integer.valueOf(getText());
else
return Float.valueOf(getText());
}
// add an Integer or Float document filter as appropriate
private void addFilter() {
if (integerOnly)
( ( AbstractDocument ) this.getDocument() ).setDocumentFilter( new IntegerDocumentFilter() );
else
( ( AbstractDocument ) this.getDocument() ).setDocumentFilter( new FloatDocumentFilter() );
// apply the filter to the current value
try
{
String text = getText();
((AbstractDocument)getDocument()).getDocumentFilter().replace(null, 0, text.length(), text, null);
}
catch(Exception e) {} // NOPMD
}
class IntegerDocumentFilter extends DocumentFilter {
public void insertString( FilterBypass fb, int offset, String string, AttributeSet attr )
throws BadLocationException
{
if ( string == null || string.length() == 0 )
{
return ;
}
String newString = new StringBuilder(getText()).insert(offset, string).toString();
if (!isInteger( newString ))
{
return;
}
setBackground(inRange( newString ) ? defaultBackground : invalidStyle.getBackgroundColor());
setForeground(inRange( newString ) ? defaultForeground : invalidStyle.getForegroundColor());
super.insertString( fb, offset, string, attr );
}
public void remove( FilterBypass fb, int offset, int length )
throws BadLocationException
{
String newString = new StringBuilder(getText()).delete(offset, offset + length).toString();
setBackground(inRange( newString ) ? defaultBackground : invalidStyle.getBackgroundColor());
setForeground(inRange( newString ) ? defaultForeground : invalidStyle.getForegroundColor());
super.remove( fb, offset, length );
}
public void replace( FilterBypass fb, int offset, int length, String text, AttributeSet attrs )
throws BadLocationException
{
if ( text == null || text.length() == 0 )
{
return ;
}
String newString = new StringBuilder(getText()).replace(offset, offset + length, text).toString();
if (!isInteger( newString ))
{
return;
}
setBackground(inRange( newString ) ? defaultBackground : invalidStyle.getBackgroundColor());
setForeground(inRange( newString ) ? defaultForeground : invalidStyle.getForegroundColor());
super.replace( fb, offset, length, text, attrs );
}
private boolean isInteger( String string )
{
if (string == null || string.isEmpty())
{
return false;
}
try
{
if (!positiveOnly && "-".equals(string))
{
return true;
}
Integer.parseInt(string);
return true;
}
catch(Exception e)
{
return false;
}
}
private boolean inRange( String string )
{
if (string == null || string.isEmpty())
{
return false;
}
int value = Integer.parseInt( string );
return value <= maxValue.intValue() && value >= minValue.intValue();
}
}
class FloatDocumentFilter extends DocumentFilter {
public void insertString( FilterBypass fb, int offset, String string, AttributeSet attr )
throws BadLocationException
{
if ( string == null || string.length() == 0 )
{
return ;
}
String newString = new StringBuilder(getText()).insert(offset, string).toString();
if (!isFloat( newString ))
{
return;
}
setBackground(inRange( newString ) ? defaultBackground : invalidStyle.getBackgroundColor());
setForeground(inRange( newString ) ? defaultForeground : invalidStyle.getForegroundColor());
super.insertString( fb, offset, string, attr );
}
public void remove( FilterBypass fb, int offset, int length )
throws BadLocationException
{
String newString = new StringBuilder(getText()).delete(offset, offset + length).toString();
setBackground(inRange( newString ) ? defaultBackground : invalidStyle.getBackgroundColor());
setForeground(inRange( newString ) ? defaultForeground : invalidStyle.getForegroundColor());
super.remove( fb, offset, length );
}
public void replace( FilterBypass fb, int offset, int length, String text, AttributeSet attrs )
throws BadLocationException
{
if ( text == null || text.length() == 0 )
{
return ;
}
String newString = new StringBuilder(getText()).replace(offset, offset + length, text).toString();
if (!isFloat( newString ))
{
return;
}
setBackground(inRange( newString ) ? defaultBackground : invalidStyle.getBackgroundColor());
setForeground(inRange( newString ) ? defaultForeground : invalidStyle.getForegroundColor());
super.replace( fb, offset, length, text, attrs );
}
private boolean isFloat( String string )
{
if (string == null || string.isEmpty())
{
return false;
}
try
{
if (".".equals(string))
{
return true;
}
if (!positiveOnly && "-".equals(string))
{
return true;
}
Float.parseFloat(string);
return true;
}
catch(Exception e)
{
return false;
}
}
private boolean inRange( String string )
{
if (string == null || string.isEmpty())
{
return false;
}
if (".".equals(string))
{
return true;
}
float value = Float.parseFloat( string );
boolean toReturn = value <= maxValue.floatValue() && value >= minValue.floatValue();
return toReturn;
}
}
//{{{ ComboBoxEditor methods
public Component getEditorComponent()
{
return this;
}
public Object getItem()
{
return getText();
}
public void setItem(Object item)
{
if (item == null)
setText("");
else
setText(item.toString());
}
//}}}
}