package org.j4me.ui.components;
import javax.microedition.lcdui.*;
import org.j4me.ui.*;
/**
* Components are UI widgets that appear on forms. Examples of components include
* labels, text boxes, and check boxes. The <code>Dialog</code> class calls methods on
* this interface to layout, paint, and manage components.
*/
public abstract class Component
{
/**
* Components that highlight entry boxes should use this value as a width for
* their borders. For example an unselected text box appears as a rectangle
* but a selected text box has a border around it that is this thick and has
* rounded edges.
*/
protected static final int HIGHLIGHTED_BORDER_WIDTH = 2;
/**
* The horizontal justification of the text in this label. It must be one of
* <code>Graphics.LEFT</code>, <code>Graphics.HCENTER</code>, and <code>Graphics.RIGHT</code>.
*/
private int horizontalAlignment = Graphics.LEFT;
/**
* Whether the component is currently visible on the screen or not.
*/
private boolean visible;
/**
* The screen this component is placed on.
*/
private DeviceScreen screen;
/**
* A component this one is embedded within. Typically components are not
* embedded within each other so this will be <code>null</code>.
*/
protected Component container;
/**
* The left corner pixel of this component. This value is specified by the
* last call to <code>paint</code>.
*/
private int x;
/**
* The top corner pixel of this component. This value is specified by the
* last call to <code>paint</code>.
*/
private int y;
/**
* The width of this component in pixels. This value is specified by the
* last call to <code>paint</code>.
*/
private int width;
/**
* The height of this component in pixels. This value is specified by the
* last call to <code>paint</code>.
*/
private int height;
/**
* Constructs a component and attaches it to a screen.
*/
public Component ()
{
}
/**
* Paints the component using <code>g</code>. The top-left corner is at (0,0)
* and the component fills the rectangle bounded by <code>width</code> and
* <code>height</code>.
*
* @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 screen is the screen object displaying this component.
* @param x is the left corner pixel of the component.
* @param y is the top corner pixel of the component.
* @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.
*/
public final void paint (Graphics g, Theme theme, DeviceScreen screen,
int x, int y, int width, int height,
boolean selected)
{
if ( isShown() )
{
// Record the position of this component.
this.screen = screen;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
// Set the graphics properties for painting the component.
int originalClipX = g.getClipX();
int originalClipY = g.getClipY();
int originalClipWidth = g.getClipWidth();
int originalClipHeight = g.getClipHeight();
// Workaround a bug by doubling the clip height.
// The Sun WTK clips the bottom of graphics operations for
// rounded rectangle drawing and filling. This can be
// avoided by doubling the clip height.
g.setClip( originalClipX, originalClipY, originalClipWidth, originalClipHeight * 2 );
g.translate( x, y );
g.clipRect( 0, 0, width, height * 2 ); // *2 to workaround clipping bug
int originalColor = g.getColor();
g.setColor( theme.getFontColor() );
Font originalFont = g.getFont();
g.setFont( theme.getFont() );
int originalStroke = g.getStrokeStyle();
g.setStrokeStyle( Graphics.SOLID );
// Actually paint the component.
paintComponent( g, theme, width, height, selected );
// Reset the graphics properties.
g.translate( -x, -y );
g.setClip( originalClipX, originalClipY, originalClipWidth, originalClipHeight );
g.setColor( originalColor );
g.setFont( originalFont );
g.setStrokeStyle( originalStroke );
}
}
/**
* Implemented by the subclass to render the item within its container. At the
* time of the call, the <code>Graphic</code>s context's destination is the content area of
* this <code>Component</code> (or back buffer for it). The translation is set so that
* the upper left corner of the content area is at (0,0), and the clip is set
* to the area to be painted. The application must paint every pixel within
* the given clip area. The item is allowed to modify the clip area, but the
* system must not allow any modification to result in drawing outside the
* bounds of the item's content area. The <code>w</code> and <code>h</code> passed in are the width
* and height of the content area of the item. These values will always be
* set to the clip width and height and are passed here for convenience.
* <p>
* Other values of the <code>Graphics</code> object are as follows:
* <ul>
* <li>the current color is <code>Theme.getFontColor()</code>;
* <li>the font is <code>Theme.getFont()</code>;
* <li>the stroke style is <code>SOLID</code>;
* </ul>
* <p>
* The <code>paint()</code> method will be called only when at least a portion of the
* item is actually visible on the display.
*
* @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.
*/
protected abstract void paintComponent (Graphics g, Theme theme, int width, int height, boolean selected);
/**
* Returns the desired width and height of this component in pixels.
*
* @param theme is the application's <code>Theme</code>.
* @param viewportWidth is the width of the screen in pixels.
* @param viewportHeight is the height of the screen in pixels.
* @return A array with two elements where the first is the width of the
* component in pixels and the second is the height.
*/
public final int[] getPreferredSize (Theme theme, int viewportWidth, int viewportHeight)
{
// Get the component's dimensions.
int[] dimensions = getPreferredComponentSize( theme, viewportWidth, viewportHeight );
if ( (dimensions == null) || (dimensions.length != 2) )
{
throw new RuntimeException(getClass().getName() + ".getPreferredComponentSize must return an array of length 2");
}
return dimensions;
}
/**
* Returns the desired width and height of this component in pixels.
* It cannot be wider than the screen or it will be cropped. However, it can
* be taller than the screen, in which case a scroll bar will be added to
* the form this component resides on.
*
* @param theme is the application's <code>Theme</code>.
* @param viewportWidth is the width of the viewable area, in pixels,
* the component can use.
* @param viewportHeight is the height of the viewable area, in pixels,
* the component can use.
* @return A array with two elements where the first is the width of the
* component in pixels and the second is the height.
*/
protected abstract int[] getPreferredComponentSize (Theme theme, int viewportWidth, int viewportHeight);
/**
* Tells if this component accepts user input or not. If it does then
* it can be scrolled to by the user. If it does not, it will be displayed,
* but can be skipped over by scrolling.
* <p>
* The default implementation returns <code>false</code>. Override this method
* to return <code>true</code> if the component accepts input.
*
* @return <code>true</code> if the component accepts user input; <code>false</code> if
* it does not.
*/
public boolean acceptsInput ()
{
return false;
}
/**
* @return The screen displaying this component.
*/
public DeviceScreen getScreen ()
{
return screen;
}
/**
* @return The pixel for the left side of this component.
*/
public int getX ()
{
return x;
}
/**
* @return The pixel for the top of this component.
*/
public int getY ()
{
return y;
}
/**
* @return The width, in pixels, for this component.
*/
public int getWidth ()
{
return width;
}
/**
* @return The height, in pixels, for this component.
*/
public int getHeight ()
{
return height;
}
/**
* @return The horizontalAlignment of the text in the label. It is one of
* <code>Graphics.LEFT</code>, <code>Graphics.HCENTER</code>, and <code>Graphics.RIGHT</code>.
*/
public int getHorizontalAlignment ()
{
return horizontalAlignment;
}
/**
* @param alignment is how the text in the label is justified. It is one of
* <code>Graphics.LEFT</code>, <code>Graphics.HCENTER</code>, and <code>Graphics.RIGHT</code>.
*/
public void setHorizontalAlignment (int alignment)
{
if ( (alignment != Graphics.LEFT) && (alignment != Graphics.HCENTER) && (alignment != Graphics.RIGHT) )
{
throw new IllegalArgumentException("setHorizontalAlignment only takes Graphics.LEFT, HCENTER, or RIGHT");
}
this.horizontalAlignment = alignment;
}
/**
* Sets if the component is currently shown on the screen or not.
* A call to <code>show(true)</code> must be made before <code>paint</code>.
* When the component is no longer visible call <code>show(false)</code>
* so that the component may clean up any resources.
*
* @param visible when <code>true</code> indicates the component is painted (or
* about to be) on the screen.
*/
public void visible (boolean visible)
{
if ( this.visible != visible )
{
this.visible = visible;
// Raise an event for the component.
if ( visible )
{
showNotify();
}
else
{
hideNotify();
}
}
}
/**
* Returns if this component is shown on the screen now.
*
* @return <code>true</code> if this component is currently visible on the
* screen; <code>false</code> if not.
*/
public boolean isShown ()
{
return visible;
}
/**
* An event raised whenever the component is made visible on the screen.
* This is called before the <code>paintComponent</code> method.
* <p>
* The default implementation does nothing. Override it to initialize
* any resources required by the component.
*/
protected void showNotify ()
{
}
/**
* An event raised whenever the component is removed from the screen.
* <p>
* The default implementation does nothing. Override it to clean up
* any resources required by the component.
*/
protected void hideNotify ()
{
}
/**
* Signals that the Component's size needs to be updated. This method
* is intended to be called by subclassed components to force layout
* of the component to change. A call to this method will return immediately,
* and it will cause the container's layout algorithm to run at some point
* in the future.
*
* @see Dialog#invalidate()
*/
protected void invalidate ()
{
// Invalidate the whole dialog and have it reposition all components.
if ( screen != null )
{
if ( screen instanceof Dialog )
{
Dialog d = (Dialog)screen;
d.invalidate();
}
}
// Erase any knowledge this component has of its position.
x = y = width = height = 0;
}
/**
* Forces this component to repaint itself.
*/
public void repaint ()
{
if ( isShown() && (screen != null) )
{
if ( height != 0 )
{
// Repaint just the component area since we know it.
screen.repaint( x, y, width, height );
}
else
{
// Repaint the entire screen since we aren't sure where
// the component is on it. An invalidate() was likely
// just called so the component may move anyway.
screen.repaint();
}
}
}
/**
* Called when a key is pressed. It can be identified using the
* constants defined in the <code>DeviceScreen</code> class. Note to
* receive key events the component must override <code>acceptsInput</code>
* to return <code>true</code>.
* <p>
* The default implementation does nothing. If a component requires
* keypad interaction, such as to enter text, it should override this
* method.
*
* @param keyCode is the key code of the key that was pressed.
*/
public void keyPressed (int keyCode)
{
}
/**
* Called when a key is repeated (held down). It can be identified using the
* constants defined in the <code>DeviceScreen</code> class. Note to
* receive key events the component must override <code>acceptsInput</code>
* to return <code>true</code>.
* <p>
* The default implementation does nothing. If a component requires
* keypad interaction, such as to enter text, it should override this
* method.
*
* @param keyCode is the key code of the key that was held down.
*/
public void keyRepeated (int keyCode)
{
}
/**
* Called when a key is released. It can be identified using the
* constants defined in the <code>DeviceScreen</code> class. Note to
* receive key events the component must override <code>acceptsInput</code>
* to return <code>true</code>.
* <p>
* The default implementation does nothing. If a component requires
* keypad interaction, such as to enter text, it should override this
* method.
*
* @param keyCode is the key code of the key that was released.
*/
public void keyReleased (int 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)
{
}
/**
* Called when the pointer is released.
*
* @param x is the horizontal location where the pointer was released
* relative to the top-left corner of the component.
* @param y is the vertical location where the pointer was released
* relative to the top-left corner of the component.
*/
public void pointerReleased (int x, int y)
{
}
/**
* Called when the pointer is dragged.
*
* @param x is the horizontal location where the pointer was dragged
* relative to the top-left corner of the component.
* @param y is the vertical location where the pointer was dragged
* relative to the top-left corner of the component.
*/
public void pointerDragged (int x, int y)
{
}
/**
* Paints a rectangle used within a component. If the rectangle is
* <code>selected</code> it will have rounded edges and be highlighted. If it
* is not it will have square edges and be slightly inset from
* (<code>x</code>, <code>y</code>). The border color, highlight color, and inner
* part of the rectangle (background color) all come from the <code>theme</code>.
*
* @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 x is the left side of the box.
* @param y is the top of the box.
* @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.
* @return The offset, in pixels, of the interior of the box. This is the
* usuable area by a component: <code>(x + offset, y + offset,
* width - 2 * offset, height - 2 * offset)</code>.
*/
protected static int paintRect (Graphics g, Theme theme, int x, int y, int width, int height, boolean selected)
{
// Calculate how much to round the edges. This makes the component
// look better. However, we don't want to make this so big it makes
// the component's border crazily thick.
int rounding = Math.min( height / 4, 5 );
// Paint the border.
if ( selected )
{
// Draw a thick, highlighted border.
int border = theme.getHighlightColor();
g.setColor( border );
g.drawRoundRect( 0, y, width - 1, height - 1, rounding, rounding );
}
// Draw a normal border.
int border = theme.getBorderColor();
g.setColor( border );
int bo = HIGHLIGHTED_BORDER_WIDTH - 1;
int bx = bo;
int by = y + bo;
int bs = 2 * bo;
int bw = width - bs;
int bh = height - bs;
g.drawRect( bx, by, bw - 1, bh - 1 );
// Return the offset from the edges of the component to the inside.
int offset = Math.max( HIGHLIGHTED_BORDER_WIDTH, rounding / 2 );
return offset;
}
}