org.metawidget.swing.widgetbuilder.SwingWidgetBuilder Maven / Gradle / Ivy
// Metawidget
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package org.metawidget.swing.widgetbuilder;
import static org.metawidget.inspector.InspectionResultConstants.*;
import java.awt.Component;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPasswordField;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JSpinner;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.plaf.basic.BasicComboBoxEditor;
import javax.swing.plaf.basic.BasicComboBoxRenderer;
import javax.swing.text.JTextComponent;
import org.metawidget.swing.Stub;
import org.metawidget.swing.SwingMetawidget;
import org.metawidget.swing.SwingValuePropertyProvider;
import org.metawidget.swing.widgetprocessor.binding.BindingConverter;
import org.metawidget.util.ClassUtils;
import org.metawidget.util.CollectionUtils;
import org.metawidget.util.WidgetBuilderUtils;
import org.metawidget.widgetbuilder.iface.WidgetBuilder;
/**
* WidgetBuilder for Swing environments.
*
* Creates native Swing JComponents
, such as JTextField
and
* JComboBox
, to suit the inspected fields.
*
* @author Richard Kennard
*/
public class SwingWidgetBuilder
implements WidgetBuilder, SwingValuePropertyProvider {
//
// Public methods
//
@Override
public String getValueProperty( Component component ) {
if ( component instanceof JComboBox ) {
return "selectedItem";
}
if ( component instanceof JTextComponent ) {
return "text";
}
if ( component instanceof JSpinner ) {
return "value";
}
if ( component instanceof JSlider ) {
return "value";
}
if ( component instanceof JCheckBox ) {
return "selected";
}
return null;
}
@Override
public JComponent buildWidget( String elementName, Map attributes, SwingMetawidget metawidget ) {
// Hidden
if ( TRUE.equals( attributes.get( HIDDEN ) ) ) {
return new Stub();
}
// Action
if ( ACTION.equals( elementName ) ) {
return new JButton( metawidget.getLabelString( attributes ) );
}
String type = WidgetBuilderUtils.getActualClassOrType( attributes );
// If no type, assume a String
if ( type == null ) {
type = String.class.getName();
}
// Lookup the Class
Class clazz = ClassUtils.niceForName( type );
// Support mandatory Booleans (can be rendered as a checkbox, even though they have a
// Lookup)
if ( Boolean.class.equals( clazz ) && TRUE.equals( attributes.get( REQUIRED ) ) ) {
return new JCheckBox();
}
// Lookups
String lookup = attributes.get( LOOKUP );
if ( lookup != null && !"".equals( lookup ) ) {
JComboBox comboBox = new JComboBox();
// Add an empty choice (if nullable, and not required)
if ( WidgetBuilderUtils.needsEmptyLookupItem( attributes ) ) {
comboBox.addItem( null );
}
List values = CollectionUtils.fromString( lookup );
BindingConverter converter = metawidget.getWidgetProcessor( BindingConverter.class );
for ( String value : values ) {
// Convert (if supported)
Object convertedValue;
if ( converter == null ) {
convertedValue = value;
} else {
convertedValue = converter.convertFromString( value, clazz );
}
comboBox.addItem( convertedValue );
}
// May have alternate labels
String lookupLabels = attributes.get( LOOKUP_LABELS );
if ( lookupLabels != null && !"".equals( lookupLabels ) ) {
Map labelsMap = SwingWidgetBuilderUtils.getLabelsMap( values, CollectionUtils.fromString( attributes.get( LOOKUP_LABELS ) ) );
comboBox.setEditor( new LookupComboBoxEditor( labelsMap ) );
comboBox.setRenderer( new LookupComboBoxRenderer( labelsMap ) );
}
return comboBox;
}
if ( clazz != null ) {
// Primitives
if ( clazz.isPrimitive() ) {
// booleans
if ( boolean.class.equals( clazz ) ) {
return new JCheckBox();
}
// chars
if ( char.class.equals( clazz ) ) {
return new JTextField();
}
// Ranged
String minimumValue = attributes.get( MINIMUM_VALUE );
String maximumValue = attributes.get( MAXIMUM_VALUE );
if ( minimumValue != null && !"".equals( minimumValue ) && maximumValue != null && !"".equals( maximumValue ) ) {
JSlider slider = new JSlider();
slider.setMinimum( Integer.parseInt( minimumValue ) );
slider.setValue( slider.getMinimum() );
slider.setMaximum( Integer.parseInt( maximumValue ) );
return slider;
}
// Not-ranged
JSpinner spinner = new JSpinner();
if ( byte.class.equals( clazz ) ) {
byte value = 0;
byte minimum = Byte.MIN_VALUE;
byte maximum = Byte.MAX_VALUE;
if ( minimumValue != null && !"".equals( minimumValue ) ) {
minimum = Byte.parseByte( minimumValue );
value = (byte) Math.max( value, minimum );
}
if ( maximumValue != null && !"".equals( maximumValue ) ) {
maximum = Byte.parseByte( maximumValue );
value = (byte) Math.min( value, maximum );
}
setSpinnerModel( spinner, value, minimum, maximum, (byte) 1 );
} else if ( short.class.equals( clazz ) ) {
short value = 0;
short minimum = Short.MIN_VALUE;
short maximum = Short.MAX_VALUE;
if ( minimumValue != null && !"".equals( minimumValue ) ) {
minimum = Short.parseShort( minimumValue );
value = (short) Math.max( value, minimum );
}
if ( maximumValue != null && !"".equals( maximumValue ) ) {
maximum = Short.parseShort( maximumValue );
value = (short) Math.min( value, maximum );
}
setSpinnerModel( spinner, value, minimum, maximum, (short) 1 );
} else if ( int.class.equals( clazz ) ) {
int value = 0;
int minimum = Integer.MIN_VALUE;
int maximum = Integer.MAX_VALUE;
if ( minimumValue != null && !"".equals( minimumValue ) ) {
minimum = Integer.parseInt( minimumValue );
value = Math.max( value, minimum );
}
if ( maximumValue != null && !"".equals( maximumValue ) ) {
maximum = Integer.parseInt( maximumValue );
value = Math.min( value, maximum );
}
setSpinnerModel( spinner, value, minimum, maximum, 1 );
} else if ( long.class.equals( clazz ) ) {
long value = 0;
long minimum = Long.MIN_VALUE;
long maximum = Long.MAX_VALUE;
if ( minimumValue != null && !"".equals( minimumValue ) ) {
minimum = Long.parseLong( minimumValue );
value = Math.max( value, minimum );
}
if ( maximumValue != null && !"".equals( maximumValue ) ) {
maximum = Long.parseLong( maximumValue );
value = Math.min( value, maximum );
}
setSpinnerModel( spinner, value, minimum, maximum, (long) 1 );
} else if ( float.class.equals( clazz ) ) {
float value = 0;
float minimum = -Float.MAX_VALUE;
float maximum = Float.MAX_VALUE;
if ( minimumValue != null && !"".equals( minimumValue ) ) {
minimum = Float.parseFloat( minimumValue );
value = Math.max( value, minimum );
}
if ( maximumValue != null && !"".equals( maximumValue ) ) {
maximum = Float.parseFloat( maximumValue );
value = Math.min( value, maximum );
}
// Configurable step
float stepSize;
if ( attributes.containsKey( MAXIMUM_FRACTIONAL_DIGITS ) ) {
stepSize = (float) Math.pow( 10, -Integer.parseInt( attributes.get( MAXIMUM_FRACTIONAL_DIGITS ) ) );
} else {
stepSize = 0.1f;
}
setSpinnerModel( spinner, value, minimum, maximum, stepSize );
} else if ( double.class.equals( clazz ) ) {
double value = 0;
double minimum = -Double.MAX_VALUE;
double maximum = Double.MAX_VALUE;
if ( minimumValue != null && !"".equals( minimumValue ) ) {
minimum = Double.parseDouble( minimumValue );
value = Math.max( value, minimum );
}
if ( maximumValue != null && !"".equals( maximumValue ) ) {
maximum = Double.parseDouble( maximumValue );
value = Math.min( value, maximum );
}
// Configurable step
double stepSize;
if ( attributes.containsKey( MAXIMUM_FRACTIONAL_DIGITS ) ) {
stepSize = (float) Math.pow( 10, -Integer.parseInt( attributes.get( MAXIMUM_FRACTIONAL_DIGITS ) ) );
} else {
stepSize = 0.1d;
}
setSpinnerModel( spinner, value, minimum, maximum, stepSize );
}
return spinner;
}
// Strings
if ( String.class.equals( clazz ) ) {
if ( TRUE.equals( attributes.get( MASKED ) ) ) {
return new JPasswordField();
}
if ( TRUE.equals( attributes.get( LARGE ) ) ) {
JTextArea textarea = new JTextArea();
// Since we know we are dealing with Strings, we consider
// word-wrapping a sensible default
textarea.setLineWrap( true );
textarea.setWrapStyleWord( true );
// We also consider 2 rows a sensible default, so that the
// JTextArea is always distinguishable from a JTextField
textarea.setRows( 2 );
return new JScrollPane( textarea );
}
return new JTextField();
}
// Characters
if ( Character.class.isAssignableFrom( clazz ) ) {
return new JTextField();
}
// Dates
if ( Date.class.equals( clazz ) ) {
return new JTextField();
}
// Numbers
//
// Note: we use a text field, not a JSpinner or JSlider, because
// BeansBinding gets upset at doing 'setValue( null )' if the Integer
// is null. We can still use JSpinner/JSliders for primitives, though.
if ( Number.class.isAssignableFrom( clazz ) ) {
return new JTextField();
}
// Collections
if ( Collection.class.isAssignableFrom( clazz ) ) {
return new Stub();
}
}
// Not simple, but don't expand
if ( TRUE.equals( attributes.get( DONT_EXPAND ) ) ) {
return new JTextField();
}
// Nested Metawidget
return null;
}
//
// Private methods
//
/**
* Sets the JSpinner model.
*
* By default, a JSpinner calls setColumns
upon setModel
. For numbers
* like Integer.MAX_VALUE
and Double.MAX_VALUE
, this can be very large
* and mess up the layout. Here, we reset setColumns
to 0.
*
* Note it is very important we set the initial value of the JSpinner
to the same
* type as the property it maps to (eg. float or double, int or long).
*/
private void setSpinnerModel( JSpinner spinner, Number value, Comparable minimum, Comparable maximum, Number stepSize ) {
spinner.setModel( new SpinnerNumberModel( value, minimum, maximum, stepSize ) );
( (JSpinner.DefaultEditor) spinner.getEditor() ).getTextField().setColumns( 0 );
}
//
// Inner class
//
/**
* Editor for ComboBox whose values use a lookup.
*/
private static class LookupComboBoxEditor
extends BasicComboBoxEditor {
//
// Private members
//
private Map mLookups;
//
// Constructor
//
public LookupComboBoxEditor( Map lookups ) {
if ( lookups == null ) {
throw new NullPointerException( "lookups" );
}
mLookups = lookups;
}
//
// Public methods
//
@Override
public void setItem( Object item ) {
super.setItem( mLookups.get( item ) );
}
}
/**
* Renderer for ComboBox whose values use a lookup.
*/
private static class LookupComboBoxRenderer
extends BasicComboBoxRenderer {
//
// Private statics
//
private static final long serialVersionUID = 1l;
//
// Private members
//
private Map mLookups;
//
// Constructor
//
public LookupComboBoxRenderer( Map lookups ) {
if ( lookups == null ) {
throw new NullPointerException( "lookups" );
}
mLookups = lookups;
}
//
// Public methods
//
@Override
public Component getListCellRendererComponent( JList list, Object value, int index, boolean selected, boolean hasFocus ) {
Component component = super.getListCellRendererComponent( list, value, index, selected, hasFocus );
String lookup = mLookups.get( value );
if ( lookup != null ) {
( (JLabel) component ).setText( lookup );
}
return component;
}
}
}