package org.j4me.ui.components;
import javax.microedition.lcdui.*;
import org.j4me.ui.*;
/**
* A <code>TextBox</code> is a component for entering text, numbers, and keyboard data.
* It equivalent to the <code>javax.microedition.lcdui.TextField</code> class.
* <p>
* This component is not yet complete. Features that still need to be added
* include:
* <ul>
* <li>Suggestions
* <li>Constraint: EMAILADDR
* <li>Constraint: URL
* <li>Constraint: DECIMAL
* <li>Constraint: UNEDITABLE
* <li>Constraint: SENSITIVE
* <li>Constraint: NON_PREDICTIVE
* <li>Constraint: INITIAL_CAPS_WORD
* <li>Constraint: INITIAL_CAPS_SENTENCE
* </ul>
*
* @see javax.microedition.lcdui.TextField
*/
public class TextBox
extends Component
{
/**
* The default number of characters that can be entered into this text box component.
* The native edit screen (i.e. <code>javax.microedition.lcdui.TextBox</code>) allocates a
* buffer of this size so it cannot be too big to fit into memory.
*/
private static final int DEFAULT_MAX_CHARS = 128;
/**
* The number of pixels from the left, top, right, and bottom that
* text within the box is set.
*/
private static final int TEXT_OFFSET = 2;
/**
* The label that appears right above the text box. For example it might
* say "User Name". If this is <code>null</code> then no label will appear.
*/
private Label label;
/**
* The contents of the text box.
*/
private String contents;
/**
* The maximum number of characters that can be entered. If this is 0
* there is no limit.
*/
private int maxSize = DEFAULT_MAX_CHARS;
/**
* The constraints that define what kind of text is input. This matches
* the <code>TextField</code> input constraints integer.
* <p>
* One of the following types of text are possible:
* <ul>
* <li>ANY - (Default) Text, numbers, line breaks, and all data.
* <li>EMAILADDR - Email address.
* <li>NUMERIC - An integer.
* <li>PHONENUMBER - A phone number.
* <li>URL - A URL.
* <li>DECIMAL - A floating point number.
* </ul>
* <p>
* Additionally the input can be modified with a combination of any of:
* <ul>
* <li>PASSWORD
* <li>UNEDITABLE
* <li>SENSITIVE
* <li>NON_PREDICTIVE
* <li>INITIAL_CAPS_WORD
* <li>INITIAL_CAPS_SENTENCE
* </ul>
*
* @see javax.microedition.lcdui.TextField
*/
private int constraints = TextField.ANY;
/*
* The last key typed by the user. Used to set the initial value of the textbox.
*/
private int lastKey;
/**
* Creates a new <code>TextBox</code> component.
*/
public TextBox ()
{
}
/**
* @return The text that appears above the text box. If
* <code>null</code> there is no text.
*/
public String getLabel ()
{
if ( label == null )
{
return null;
}
else
{
return label.getLabel();
}
}
/**
* @param label is the text that appears above the text box.
* If <code>null</code> there will be no text.
*/
public void setLabel (String label)
{
if ( label == null )
{
this.label = null;
}
else
{
if ( this.label == null )
{
this.label = new Label();
}
this.label.setLabel( label );
}
invalidate();
}
/**
* Gets the contents of the <code>TextBox</code> as a string value.
*
* @return The current contents or <code>null</code> if there is no data.
* @see #setString(String)
*/
public String getString ()
{
if ( contents == null )
{
return "";
}
else
{
return contents;
}
}
/**
* Sets the contents of the <code>TextBox</code> as a string value, replacing
* the previous contents.
*
* @param text is the new value of the <code>TextBox</code>, or <code>null<code> if
* the <code>TextBox</code> is to be made empty.
* @throws IllegalArgumentException if <code>text</code> is illegal for the
* current input constraints.
* @throws IllegalArgumentException if <code>text</code> would exceed the current
* maximum capacity.
*
* @see #getString()
*/
public void setString (String text)
{
this.contents = text;
invalidate();
}
/**
* Allows any text to be entered including letters, numbers, punctuation,
* and newlines.
*/
public void setForAnyText ()
{
setRestrictiveConstraint( TextField.ANY );
}
/**
* Allows only integer values to be input.
*/
public void setForNumericOnly ()
{
setRestrictiveConstraint( TextField.NUMERIC );
}
/**
* Allows for entering a phone number.
*/
public void setForPhoneNumber ()
{
setRestrictiveConstraint( TextField.PHONENUMBER );
}
/**
* @return <code>true</code> if this text box is for entering a phone number;
* <code>false</code> otherwise.
*/
public boolean isPhoneNumber ()
{
boolean phone = ((constraints & TextField.PHONENUMBER) != 0);
return phone;
}
/**
* Sets a restrictive constraint on this text box. Restrictive
* constraints define the type of data that can be entered. For
* example <code>TextField.ANY</code> or <code>TextField.NUMERIC</code>.
*
* @param restriction is the <code>TextField</code> restrictive constant.
*/
private void setRestrictiveConstraint (int restriction)
{
// Keep the modifiers.
constraints &= TextField.CONSTRAINT_MASK;
// Set the restrictive value.
constraints |= restriction;
invalidate();
}
/**
* Sets if this text box is for sensitive data or not. Sensitive data is
* displayed as '<code>*</code>' characters.
* <p>
* The default is for non-sensitive data.
*
* @param password is <code>true</code> if this text box contains a password;
* <code>false</code> otherwise.
*/
public void setPassword (boolean password)
{
setModifierConstraint( TextField.PASSWORD, password );
}
/**
* Returns if this text box is for sensitive data or not.
* <p>
* The default is for non-sensitive data.
*
* @return <code>true</code> if this text box contains a password; <code>false</code>
* otherwise.
*/
public boolean isPassword ()
{
boolean password = ((this.constraints & TextField.PASSWORD) != 0);
return password;
}
/**
* Sets a modifier constraint on this text box. Modifiers alter
* the type of text defined such as <code>TextField.PASSWORD</code>.
*
* @param restriction is the <code>TextField</code> modifier constant.
* @param on when <code>true</code> adds the modifier to the constraints
* and when <code>false</code> removes it.
*/
private void setModifierConstraint (int modifier, boolean on)
{
if ( on )
{
constraints |= modifier;
}
else
{
constraints &= ~modifier;
}
invalidate();
}
/**
* Returns the maximum size (number of characters) that can be stored
* in this <code>TextBox</code>.
*
* @return The maximum size in characters.
* @see #setMaxSize(int)
*/
public int getMaxSize ()
{
return maxSize;
}
/**
* Sets the maximum size (number of characters) that can be contained in
* this <code>TextBox</code>. If the current contents of the <code>TextBox</code> are
* larger than <code>maxSize</code>, the contents are truncated to fit.
*
* @param maxSize is the new maximum size. It must be 1 or more.
*/
public void setMaxSize (int maxSize)
{
if ( maxSize <= 0 )
{
throw new IllegalArgumentException( String.valueOf(maxSize) );
}
// Truncate if smaller than what exists already.
if ( maxSize < size() )
{
contents = contents.substring( 0, maxSize );
repaint();
}
this.maxSize = maxSize;
}
/**
* Gets the number of characters that are currently stored in this
* <code>TextBox</code>.
*
* @return The number of characters in the <code>TextBox</code>.
*/
public int size ()
{
int size = 0;
if ( contents != null )
{
size = contents.length();
}
return size;
}
/**
* Paints the text box.
*
* @param g is the <code>Graphics</code> object to be used for rendering the item.
* @param theme is the application's theme. Use it to get fonts and colors.
* @param width is the width, in pixels, to paint the component.
* @param height is the height, in pixels, to paint the component.
* @param selected is <code>true</code> when this components is currently selected
* and <code>false</code> when it is not.
*
* @see org.j4me.ui.components.Component#paintComponent(Graphics, Theme, int, int, boolean)
*/
protected void paintComponent (Graphics g, Theme theme, int width, int height, boolean selected)
{
int y = 0;
// Paint the label above this component.
if ( label != null )
{
// Make the justification the same as for this component.
label.setHorizontalAlignment( this.getHorizontalAlignment() );
// Paint the label.
label.paint( g, theme, getScreen(), 0, 0, width, height, selected );
// The top of the progress bar is below the label.
int labelHeight = label.getHeight();
y = labelHeight;
height -= labelHeight;
}
// Paint the text box.
int offset = paintRect( g, theme, 0, y, width, height, selected );
// Paint the contents of the text box.
if ( contents != null )
{
// Indent the text a bit from the sides of the component's interior.
offset += TEXT_OFFSET;
// Calculate the layout of the text inside the text box.
int left = offset;
int top = y + offset;
width -= 2 * offset;
height -= 2 * offset;
int anchor = Graphics.LEFT | Graphics.TOP;
g.clipRect( left, top, width, height );
g.setColor( theme.getFontColor() );
// Determine the text to display inside the text box.
String display = contents;
if ( isPassword() )
{
int length = contents.length();
StringBuffer builder = new StringBuffer( length );
for ( int i = 0; i < length; i++ )
{
builder.append( '*' );
}
display = builder.toString();
}
else if ( isPhoneNumber() )
{
// Modifiy phone numbers with an area code.
if ( display.length() == 10 )
{
StringBuffer builder = new StringBuffer( 15 );
builder.append( "(" );
builder.append( display.substring(0, 3) );
builder.append( ") " );
builder.append( display.substring(3, 6) );
builder.append( "-" );
builder.append( display.substring(6, 10) );
display = builder.toString();
}
}
// Paint the text inside the text box.
g.drawString( display, left, top, anchor );
}
}
/**
* Returns the dimensions of the text box.
*
* @see org.j4me.ui.components.Component#getPreferredComponentSize(org.j4me.ui.Theme, int, int)
*/
protected int[] getPreferredComponentSize (Theme theme, int viewportWidth, int viewportHeight)
{
int fontHeight = theme.getFont().getHeight();
int height = fontHeight + 2 * (HIGHLIGHTED_BORDER_WIDTH + TEXT_OFFSET);
// Add the height of the label above the component.
if ( label != null )
{
int[] labelDimensions = label.getPreferredComponentSize( theme, viewportWidth, viewportHeight );
height += labelDimensions[1];
}
return new int[] { viewportWidth, height };
}
/**
* An event raised whenever the component is made visible on the screen.
* This is called before the <code>paintComponent</code> method.
*
* @see Component#showNotify()
*/
protected void showNotify ()
{
// Pass the event to contained components.
if ( label != null )
{
label.visible( true );
}
// Continue processing the event.
super.showNotify();
}
/**
* An event raised whenever the component is removed from the screen.
*
* @see Component#hideNotify()
*/
protected void hideNotify ()
{
// Pass the event to contained components.
if ( label != null )
{
label.visible( false );
}
// Continue processing the event.
super.hideNotify();
}
/**
* @return <code>true</code> because this component accepts user input.
*/
public boolean acceptsInput ()
{
return true;
}
/**
* Called when a key is pressed.
*
* @param keyCode is the key code of the key that was pressed.
*/
public void keyPressed (int keyCode)
{
// Was it an input character?
// Otherwise it was a menu button of special character.
if ( (keyCode > 0) || (keyCode == DeviceScreen.FIRE) )
{
select();
lastKey = keyCode;
}
}
/**
* Called when the pointer is pressed.
*
* @param x is the horizontal location where the pointer was pressed
* relative to the top-left corner of the component.
* @param y is the vertical location where the pointer was pressed
* relative to the top-left corner of the component.
*/
public void pointerPressed (int x, int y)
{
// If anywhere on the text box is pressed it has been selected.
select();
// Stop processing the pointer event.
// i.e. do not call super.pointerPressed( x, y );
}
/**
* Called when the text box's value is being edited. This method
* replaces the current screen with the JVM's <code>TextBox</code> screen.
* When the user enters a new value the original screen returns and
* is updated with the new value.
*/
protected void select ()
{
// Create a text entry screen using the LCDUI.
// We use the LCDUI because we can't provide this functionality
// on all phones by painting ourselves. For instance Motorola
// phones provide special menus for deleting characters because
// they do not have a dedicated button.
//
// There is a problem with ProGuard 4.0 optimizations. We need
// to actually pass in the label and contents strings instead of
// having them collected by the constructor of TextInput. It
// does not work and throws an exception without doing this to
// trick the optimizer.
DeviceScreen current = UIManager.getScreen();
String label = getLabel();
String contents = getString();
int maxSize = getMaxSize();
TextInput entry = new TextInput( current, this, label, contents, maxSize, constraints );
// Display the text entry screen.
Display display = UIManager.getDisplay();
display.setCurrent( entry );
}
/**
* The native implementation for inputting text. This takes over the
* entire screen and returns when the user is done entering text.
*/
private final class TextInput
extends javax.microedition.lcdui.TextBox
implements CommandListener
{
/**
* The Cancel button.
*/
private final Command cancel;
/**
* The OK button.
*/
private final Command ok;
/**
* The screen that invoked this one.
*/
private final DeviceScreen parent;
/**
* The component this screen is collecting data for.
*/
private final TextBox component;
/**
* Creates a native system input screen for text.
*
* @param parent is the screen that invoked this one.
* @param box is the component on <code>parent</code> this input is for.
* @param label is the title for the input.
* @param contents is the initial value of the contents.
* @param maxSize is the maximum number of characters that can be entered.
* @param constraints are the options that effect the type of data
* that can be entered.
*/
public TextInput (DeviceScreen parent, TextBox box, String label, String contents, int maxSize, int constraints)
{
super( label,
contents,
maxSize,
constraints );
// Record the owners.
this.parent = parent;
this.component = box;
// Add the menu buttons.
Theme theme = UIManager.getTheme();
String cancelText = theme.getMenuTextForCancel();
cancel = new Command( cancelText, Command.CANCEL, 2 );
addCommand( cancel );
String okText = theme.getMenuTextForOK();
ok = new Command( okText, Command.OK, 1 );
addCommand( ok );
setCommandListener( this );
}
/**
* Called when the user hits the OK or Cancel button.
*/
public void commandAction (Command c, Displayable d)
{
if ( c == ok )
{
// Update the contents of owning box.
String input = this.getString();
component.setString( input );
}
// Return to the parent screen.
parent.show();
parent.repaint();
}
}
public void keyReleased(int keyCode) {
super.keyReleased(keyCode);
}
}