uk.ac.starlink.table.gui.NumericCellRenderer Maven / Gradle / Ivy
Show all versions of stil Show documentation
package uk.ac.starlink.table.gui;
import java.awt.Font;
import java.lang.reflect.Array;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import javax.swing.BorderFactory;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.table.DefaultTableCellRenderer;
/**
* Provides better rendering of numeric table cells than the default
* JTable renderer.
* Single-element primitive arrays are treated just like the corresponding
* wrapper classes, and the first few elements of a multi-element array
* are displayed.
* Numeric values are displayed with decimal points aligned and so on.
* This class is generally adapted to display of numeric values, but
* it can render Strings or other Objects too.
*
* Cell rendering can be further refined by extending this class and
* overriding the {@link #setValue} method.
*
* @author Mark Taylor (Starlink)
*/
public class NumericCellRenderer extends DefaultTableCellRenderer {
/** The most elements that will be shown in a single cell. */
public static final int MAX_SHOW_ELEMENTS = 10;
private NumberFormat sciFormatDouble;
private NumberFormat sciFormatFloat;
private NumberFormat fixFormatDouble;
private NumberFormat fixFormatFloat0;
private NumberFormat fixFormatFloat1;
private NumberFormat fixFormatFloat2;
private NumberFormat fixFormatFloat3;
private NumberFormat fixFormatFloat4;
private NumberFormat fixFormatFloat5;
private NumberFormat fixFormatFloat6;
private NumberFormat intFormat;
private NumberFormat longFormat;
private char decimalPoint;
private String decimalPointString;
private boolean likeHeading;
private String badText;
private Object badValue = new Object();
private Class> clazz;
private Font font;
private static JTable dummyTable;
/**
* Construct a new NumericCellRenderer with a hint about the values
* it will be expected to render. An attempt will be made to render
* other objects, but the alignment and so on may not be so good.
*
* @param clazz the type of object it will expect to render on the whole
*/
public NumericCellRenderer( Class> clazz ) {
this.clazz = clazz;
/* Set the font. */
if ( Number.class.isAssignableFrom( clazz ) ) {
setHorizontalAlignment( RIGHT );
font = new Font( "Monospaced", Font.PLAIN, getFont().getSize() );
}
else {
setHorizontalAlignment( LEFT );
}
/* Configure some formatters here. */
intFormat = NumberFormat.getInstance();
if ( intFormat instanceof DecimalFormat ) {
((DecimalFormat) intFormat)
.applyPattern( " #########0;-#########0" );
}
/* Configure bad value representation. */
badText = "";
}
/**
* Sets the font in which to render cell contents.
*
* @param font the font to use for text rendering
*/
public void setCellFont( Font font ) {
this.font = font;
}
/**
* Returns the font in which cell contents will be rendererd.
*
* @return the font used for text rendering
*/
public Font getCellFont() {
return font;
}
/**
* Sets the state of this renderer, overriding the method in
* DefaultTableCellRenderer to provide more intelligent behaviour.
*
* Subclasses note: the work is done by invoking this object's
* setText and possibly setIcon methods (remember this
* object is a javax.swing.JLabel).
*
* @param value the value to be rendered
*/
protected void setValue( Object value ) {
setText( null );
Object obj;
/* Is it bad? */
if ( isBadValue( value ) ) {
setText( badText );
return;
}
/* Is it null? */
if ( value == null ) {
return;
}
if ( font != null ) {
setFont( font );
}
/* Is it an array? */
if ( value.getClass().getComponentType() != null ) {
int nel = Array.getLength( value );
/* If it's a zero-element array, treat the cell as empty. */
if ( nel == 0 ) {
return;
}
/* If it has multiple elements, print some out. */
else if ( nel > 1 ) {
int limit = Math.min( nel, MAX_SHOW_ELEMENTS );
StringBuffer sbuf = new StringBuffer( " (" );
for ( int i = 0; i < limit; i++ ) {
if ( i > 0 ) {
sbuf.append( ", " );
}
sbuf.append( Array.get( value, i ) );
}
sbuf.append( ") " );
setText( sbuf.toString() );
return;
}
/* Single object in the array - treat it as a bare object
* (this will wrap a primitive if necessary). */
else {
assert nel == 1;
obj = Array.get( value, 0 );
}
}
else {
obj = value;
}
/* Now we have a single object to format. */
/* Is it null? */
if ( obj == null ) {
return;
}
/* Is it a floating point number? */
else if ( obj instanceof Double ) {
double dval = ((Double) obj).doubleValue();
double aval = Math.abs( dval );
if ( Double.isNaN( dval ) ) {
return;
}
else if ( aval <= Double.MIN_VALUE ) {
setText( formatFixedDouble( 0.0 ) );
return;
}
else if ( aval < 1e-4 || aval >= 1e5 ) {
setText( formatSciDouble( dval ) );
return;
}
else {
setText( formatFixedDouble( dval ) );
return;
}
}
else if ( obj instanceof Float ) {
float fval = ((Float) obj).floatValue();
float aval = Math.abs( fval );
if ( Float.isNaN( fval ) ) {
return;
}
else if ( aval <= Float.MIN_VALUE ) {
boolean isNeg =
( Float.floatToIntBits( fval ) & 0x80000000 ) != 0;
setText( formatFixedFloat( isNeg ? -0.0f : 0.0f ) );
return;
}
else if ( aval < 1e-4 || aval >= 1e5 ) {
setText( formatSciFloat( fval ) );
return;
}
else {
setText( formatFixedFloat( fval ) );
return;
}
}
/* Is it a long? */
else if ( obj instanceof Long ) {
setText( ((Long) obj).toString() + " " );
}
/* Is it an integral number? */
else if ( ( obj instanceof Integer ) ||
( obj instanceof Short ) ||
( obj instanceof Byte ) ) {
setText( intFormat.format( ((Number) obj).intValue() ) + ' ' );
return;
}
/* It's just an object. */
else {
setText( ' ' + obj.toString() + ' ' );
return;
}
}
/**
* Sets a value to be regarded as bad when found in a non-header cell.
* Any cell containing an object which equals() this value
* will be represented specially in the table body (currently just blank).
*
* @param badValue the special bad value
*/
public void setBadValue( Object badValue ) {
this.badValue = badValue;
}
/**
* Returns the advised width for table cells rendered by this object,
* on the assumption that the objects it is asked to render are
* as per the constructor.
*
* @return the advised cell width in pixels
*/
public int getCellWidth() {
Object testob;
if ( clazz == null ) {
return widthFor( " " );
}
else if ( clazz.equals( Byte.class ) ) {
return widthFor( new Byte( (byte) 0x7f ) );
}
else if ( clazz.equals( Short.class ) ) {
return widthFor( new Short( (short) 0x7fff ) );
}
else if ( clazz.equals( Integer.class ) ) {
return widthFor( new Integer( 0x7fffffff ) );
}
else if ( clazz.equals( Long.class ) ) {
return widthFor( new Long( 0x7fffffffffffffffL ) );
}
else if ( clazz.equals( Float.class ) ) {
return Math.max( widthFor( new Float( - Float.MAX_VALUE ) ),
widthFor( new Float( - ( 1e5 - Math.PI ) ) ) );
}
else if ( clazz.equals( Double.class ) ) {
return Math.max( widthFor( new Double( - Double.MAX_VALUE ) ),
widthFor( new Double( - ( 1e5 - Math.PI ) ) ) );
}
else {
return widthFor( " " );
}
}
/**
* Returns the width in pixels of a cell required to render the given
* object.
*
* @return width in pixels
*/
private int widthFor( Object ob ) {
setValue( ob );
return getPreferredSize().width + 2;
}
/**
* Tests against the bad value. Make sure that we can't get a
* NullPointerException here.
*/
private boolean isBadValue( Object val ) {
if ( badValue == null ) {
return val == null;
}
else {
return badValue.equals( val );
}
}
/**
* Does fixed-type formatting of a double value.
*/
private String formatFixedDouble( double dval ) {
if ( fixFormatDouble == null ) {
fixFormatDouble = NumberFormat.getInstance();
if ( fixFormatDouble instanceof DecimalFormat ) {
DecimalFormat dformat = (DecimalFormat) fixFormatDouble;
dformat.applyPattern( " #####0.#####;-#####0.#####" );
dformat.setDecimalSeparatorAlwaysShown( true );
decimalPoint = dformat.getDecimalFormatSymbols()
.getDecimalSeparator();
decimalPointString = "" + decimalPoint;
}
}
StringBuffer buf = new StringBuffer( 20 );
buf.append( fixFormatDouble.format( dval ) );
int dotpos = buf.indexOf( decimalPointString );
if ( dotpos < 0 ) {
dotpos = buf.length();
buf.append( decimalPoint );
}
int pad = 7 - ( buf.length() - dotpos );
for ( int i = 0; i < pad; i++ ) {
buf.append( ' ' );
}
return buf.toString();
}
/**
* Does fixed-type formatting of a float value.
*/
private String formatFixedFloat( float fval ) {
if ( fixFormatFloat0 == null ) {
fixFormatFloat0 = NumberFormat.getInstance();
if ( fixFormatFloat0 instanceof DecimalFormat ) {
DecimalFormat dformat0 = (DecimalFormat) fixFormatFloat0;
dformat0.setDecimalSeparatorAlwaysShown( true );
decimalPoint = dformat0.getDecimalFormatSymbols()
.getDecimalSeparator();
decimalPointString = "" + decimalPoint;
}
fixFormatFloat1 = (NumberFormat) fixFormatFloat0.clone();
fixFormatFloat2 = (NumberFormat) fixFormatFloat0.clone();
fixFormatFloat3 = (NumberFormat) fixFormatFloat0.clone();
fixFormatFloat4 = (NumberFormat) fixFormatFloat0.clone();
fixFormatFloat5 = (NumberFormat) fixFormatFloat0.clone();
fixFormatFloat6 = (NumberFormat) fixFormatFloat0.clone();
if ( fixFormatFloat0 instanceof DecimalFormat ) {
((DecimalFormat) fixFormatFloat0)
.applyPattern( " 0.######;-0.######" );
((DecimalFormat) fixFormatFloat1)
.applyPattern( " 0.#####;-0.#####" );
((DecimalFormat) fixFormatFloat2)
.applyPattern( "#0.####;-#0.####" );
((DecimalFormat) fixFormatFloat3)
.applyPattern( " ##0.###;-##0.###" );
((DecimalFormat) fixFormatFloat4)
.applyPattern( " ###0.##;-###0.##" );
((DecimalFormat) fixFormatFloat5)
.applyPattern( " ####0.#;-####0.#" );
((DecimalFormat) fixFormatFloat6)
.applyPattern( " #####0.;-#####0." );
}
}
StringBuffer buf = new StringBuffer( 20 );
float aval = Math.abs( fval );
NumberFormat fmt;
if ( aval < 1e0 ) {
fmt = fixFormatFloat0;
}
else if ( aval < 1e1 ) {
fmt = fixFormatFloat1;
}
else if ( aval < 1e2 ) {
fmt = fixFormatFloat2;
}
else if ( aval < 1e3 ) {
fmt = fixFormatFloat3;
}
else if ( aval < 1e4 ) {
fmt = fixFormatFloat4;
}
else if ( aval < 1e-5 ) {
fmt = fixFormatFloat5;
}
else {
fmt = fixFormatFloat6;
}
buf.append( fmt.format( fval ) );
int dotpos = buf.indexOf( decimalPointString );
if ( dotpos < 0 ) {
dotpos = buf.length();
buf.append( decimalPoint );
}
int pad = 8 - ( buf.length() - dotpos );
for ( int i = 0; i < pad; i++ ) {
buf.append( ' ' );
}
return buf.toString();
}
/**
* Does scientific-type formatting of a double value.
*/
private String formatSciDouble( double dval ) {
if ( sciFormatDouble == null ) {
sciFormatDouble = NumberFormat.getInstance();
if ( sciFormatDouble instanceof DecimalFormat ) {
((DecimalFormat) sciFormatDouble)
.applyPattern( " 0.000000E0;-0.000000E0" );
}
}
StringBuffer buf = new StringBuffer( 20 );
buf.append( sciFormatDouble.format( dval ) );
int pad = 4 - ( buf.length() - buf.indexOf( "E" ) );
for ( int i = 0; i < pad; i++ ) {
buf.append( ' ' );
}
return buf.toString();
}
/**
* Does scientific-type formatting of a float value.
*/
private String formatSciFloat( float fval ) {
if ( sciFormatFloat == null ) {
sciFormatFloat = NumberFormat.getInstance();
if ( sciFormatFloat instanceof DecimalFormat ) {
((DecimalFormat) sciFormatFloat)
.applyPattern( " 0.00000E0;-0.00000E0" );
}
}
StringBuffer buf = new StringBuffer( 20 );
buf.append( sciFormatFloat.format( fval ) );
int pad = 4 - ( buf.length() - buf.indexOf( "E" ) );
for ( int i = 0; i < pad; i++ ) {
buf.append( ' ' );
}
return buf.toString();
}
}