/*
* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/
/*
* - TextField modifier flag: single_line (if not present some constraint
* combinations may still be implicitly single line)
* - TextField should define how caret moves after programmatic manipulation
* of the content; e.g. if text is inserted the caret should be in the
* end of the inserted text
* - methods for adding / removing TextEditor (one or more) to
* Canvas / CustomItem
* - z-order of added TextEditors (e.g. if they overlap)
*
* - any Command issues left after TextEditor is no longer an Item?
*/
/*
* $HeadURL$
*
*
* Copyright (c) 2008 Nokia. All rights reserved.
*
*/
package javax.microedition.lcdui;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.*;
/**
* <P>
* A <code>TextEditor</code> is an editable text component that is draws on
* top of a graphics surface; in LCDUI on top of <code>Canvas</code> or
* <code>CustomItem</code> (including <code>IdleItem</code>). The
* TextEditor must be presented as an own layer on top of the Canvas and the
* paint cycle of Canvas must be independent of that; i.e. MIDlet does not see
* the TextEditor content via the Graphics and the TextEditor must not rely on
* the framebuffer of Canvas.
* </P>
* <P>
* The implementation should initially present the TextEditor with minimal
* decoration; only the caret should be shown at the requested text insertion
* position. It is then the responsibility of the application to draw any
* additional decoration like focus highlight, border or scroll bar.
* </P>
* <P>
* The application controls whether the input indicators are shown or not and
* the drawing position of the indicators on the display. If the application
* sets no location for the indicators, the implementation must not draw any
* indicators. If the application chooses the implementation to show input
* indicators, the application can control the drawing colors of the indicators.
* It should be noted that if there are no indicators are shown to the user, the
* usability maybe diminished especially with complex input methods.
* </P>
* <P>
* <code>TextEditor</code> supports <em>input constraints</em> identically
* to <code>TextField</code>. See <a href="TextField.html#constraints">input
* constraints</a> section in the <code>TextField</code> for the definition
* of these constants. In addition <code>TextEditor</code> has the same
* concept of <em>actual contents</em> and <em>displayed contents</em> as
* <code>TextField</code>; these are described in the same <a
* href="TextField.html#constraints">input constraints</a> section.
* </P>
* <P>
* <code>TextEditor</code> supports <em>input modes</em> identically to
* <code>TextField</code>. See <a href="TextField.html#modes">input modes</a>
* section in the <code>TextField</code> for the definition of these
* constants.
* </P>
* <P>
* The text may contain <A HREF="Form.html#linebreak">line breaks</A>. The
* display of the text must break accordingly and the user must be able to enter
* line break characters.
* </P>
* <P>
* The implementation should provide necessary interaction for example for pen
* input (handwriting recognition) on touch screen devices and selection of
* additional characters. Any implementation provided visuals should be drawn
* with the drawing colors and transparency defined by the application.
* </P>
* <P>
* Application can add a TextEditorChangeListener to the TextEditor for example
* for keeping track of user navigation (caret movement) and other content
* changes such as text selection. The events are sent on all occasions that
* cause the the caret position to change (including but not limited to text
* typing by user, programmatic text insertion, navigation within the TextEditor
* content, and text deletion). The events must be sent to the application prior
* to they have effect on the editor - for example an event indicating caret
* movement must be available for the application before the implementation
* actually moves the caret in the editor.
* </P>
* <P>
* When the TextEditor has focus all the key events that are not consumed by
* TextEditor or mapped to Commands by the implementation are sent to Canvas.
* </P>
*
* @since 3.0
*/
public class TextEditor
{
private static final int BITSPERPIXEL8 = 255;
private TextEditorChangeListener changelistener;
private TextModifyListener modListener = new TextModifyListener();
private TextSelectionListener selListener = new TextSelectionListener();
private TextWrapper textWrapper;
private Object parent;
/**
* Creates a new <code>TextEditor</code> object with the given initial
* contents, maximum size in characters, constraints and editor size in
* pixels. If the text parameter is <code>null</code>, the
* <code>TextEditor</code> is created empty. The <code>maxSize</code>
* parameter must be greater than zero. An
* <code>IllegalArgumentException</code> is thrown if the length of the
* initial contents string exceeds <code>maxSize</code>. However, the
* implementation may assign a maximum size smaller than the application had
* requested. If this occurs, and if the length of the contents exceeds the
* newly assigned maximum size, the contents are truncated from the end in
* order to fit, and no exception is thrown.
*
* @param text the initial contents, or <code>null</code> if the
* <code>TextEditor</code> is to be created empty
* @param maxSize the maximum capacity in characters
* @param constraints see the input constraints in TextField
* @param width the width of the bounding box, in pixels
* @param height the height of the bounding box, in pixels
* @throws IllegalArgumentException if <code>maxSize</code> is zero or
* less
* @throws IllegalArgumentException if the value of the constraints
* parameter is invalid
* @throws IllegalArgumentException if <code>text</code> is illegal for
* the specified constraints
* @throws IllegalArgumentException if the length of the string exceeds the
* requested maximum capacity
* @throws IllegalArgumentException if the width or height is less than zero
*/
public TextEditor(String text, int maxSize, int constraints, int width,
int height)
{
textWrapper = new TextWrapper(text, maxSize, constraints);
textWrapper.setFocused(false);
textWrapper.setModifyListener(modListener);
textWrapper.setSelectionListener(selListener);
textWrapper.setPosition(0, 0);
textWrapper.setSize(width, height);
}
/**
* Set the parent of this TextEditor. Null parameter removes it.
*
* @param newParent parent
*/
public void setParent(Object newParent)
{
if(newParent != null)
{
if(newParent instanceof Canvas)
{
// constructing on another composite disposes the old one
textWrapper.construct(((Canvas) newParent).getContentComp(),
SWT.NONE);
parent = newParent;
}
}
else
{
textWrapper.dispose();
parent = newParent;
}
}
/**
* Deletes characters from the <code>TextEditor</code>. The
* <code>offset</code> and <code>length</code> parameters must specify a
* valid range of characters within the contents of the
* <code>TextEditor</code>. The <code>offset</code> parameter must be
* within the range <code>[0..(size())]</code>, inclusive. The
* <code>length</code> parameter must be a non-negative integer such that
* <code>(offset + length) <= size()</code>.
*
* @param offset the beginning of the region to be deleted
* @param length the number of characters to be deleted
* @throws IllegalArgumentException if the resulting contents would be
* illegal for the current input constraints
* @throws StringIndexOutOfBoundsException if <code>offset</code> and
* <code>length</code> do not specify a valid range within the
* content of the <code>TextEditor</code>
*/
public void delete(int offset, int length)
{
textWrapper.delete(offset, length);
}
/**
* <AU>note: Modified method name</AU> Gets the current position of the
* caret in the editor in pixels. The returned x and y coordinates are
* relative to the upper left corner of the editor, and are returned in this
* order. The x value must mark horizontally the middlemost pixel of the
* caret and the y value must mark vertically the lowest pixel of the caret.
*
* @return the x and y coordinates of the current caret position
*/
public int[] getCaretPixelPosition()
{
// TODO: implement this
return null;
}
/**
* Gets the current position of the caret in the editor.
*
* @return the current caret position, <code>0</code> if at the beginning
*/
public int getCaretPosition()
{
return textWrapper.getCaretPosition();
}
/**
* Gets the current input constraints of this <code>TextEditor</code>.
*
* @return the current constraints value (see <a
* href="TextField.html#constraints">input constraints</a>)
*/
public int getConstraints()
{
return textWrapper.getConstraints();
}
/**
* Gets a string on a range of the content in the <code>TextEditor</code>.
* The range is defined by the given index and length.
*
* @param index the index of the first character to be returned
* @param length the number of the characters to be returned
* @return the editor current content
*/
public String getContent(int index, int length)
{
String content = textWrapper.getContent();
return content.substring(Math.max(0, index),
Math.min(index + length, content.length()));
}
/**
* Gets the whole content height in this TextEditor in pixels. The returned
* value must include the height of the whole content in the editor, not
* just the height of the visible content.
*
* @return the height of the whole content in the editor in pixels
*/
public int getContentHeight()
{
return textWrapper.getLineCount() * textWrapper.getLineHeight();
}
/**
* Gets the height of this TextEditor in pixels.
*
* @return height in pixels
*/
public int getHeight()
{
return textWrapper.getHeight();
}
/**
* Gets the size of the area needed for drawing the implementation provided
* input indicators. The returned width and height must include all pixels
* that are changed if an input indicator is drawn, and are returned in this
* order. <AU> Size (0,0) is returned if device UI does not use input
* indicators in text editors.</AU>
*
* @return the width and height of area needed for drawing input indicators
*/
public int[] getIndicatorSize()
{
// TODO: implement this
return null;
}
/**
* Gets the line height in this TextEditor in pixels.
*
* @return the line height in pixels
*/
public int getLineHeight()
{
return textWrapper.getLineHeight();
}
/**
* Returns the maximum size (number of characters) that can be stored in
* this <code>TextEditor</code>.
*
* @return the maximum size in characters
*/
public int getMaxSize()
{
return textWrapper.getMaxSize();
}
/**
* Gets the number of visible rows.
*
* @return the number of visible rows in the editor
*/
public int getRows()
{
return textWrapper.getHeight() / textWrapper.getLineHeight();
}
/**
* Gets the currently selected content in the TextEditor. A selection may be
* set with setSelection method or by user interaction.
*
* @return the currently selected content
*/
public String getSelection()
{
return textWrapper.getSelectedContent();
}
/**
* Gets the width of this TextEditor in pixels.
*
* @return width in pixels
*/
public int getWidth()
{
return textWrapper.getWidth();
}
/**
* Gets the topmost pixel position of the topmost visible line in the
* editor. The returned y coordinate value must be relative to the whole
* content height, not just the visible part. This method can be used by the
* application together with the getContentHeight, getLineHeight and
* getCaretPosition methods in drawing custom visual cues like a scroll bar
* or other content sensitive pop-ups.
*
* @return the topmost pixel position of the visible content
*/
public int getVisibleContentPosition()
{
return textWrapper.getTopPixelPosition();
}
/**
* Sets this TextEditor hidden. Hides the editor and its content. <AU>If the
* editor is already hidden does nothing.</AU> If the application has
* requested the implementation to draw input indicators, and they are
* visible on the display, the indicators are made invisible by the call of
* this method. <AU>
*
* @throws IllegalStateException If the TextEditor is not added to Canvas or
* CustomItem
* @see #show() </AU>
*/
public void hide()
{
textWrapper.setVisible(false);
}
/**
* Inserts a string into the content of the <code>TextEditor</code>. The
* string is inserted just prior to the character indicated by the
* <code>position</code> parameter, where zero specifies the first
* character of the content in the <code>TextEditor</code>. If
* <code>position</code> is less than or equal to zero, the insertion
* occurs at the beginning of the content. If <code>position</code> is
* greater than or equal to the current size of the content, the insertion
* occurs immediately after the end of the content. The current size of the
* contents is increased by the number of inserted characters. The resulting
* string must fit within the current maximum capacity.
*
* @param text the <code>String</code> to be inserted
* @param position the position at which insertion is to occur
* @throws IllegalArgumentException if the resulting content would be
* illegal for the current input constraints
* @throws IllegalArgumentException if the insertion would exceed the
* current maximum capacity
* @throws NullPointerException if <code>text</code> is <code>null</code>
*/
public void insert(String text, int position)
{
textWrapper.insert(text, position);
}
/**
* Removes the focus from this TextEditor. <AU> Calling
* <code>removeFocus</code> disables text editing and returns the delivery
* of key events and pointer events to the underlying Canvas. See method
* setFocus for description on how focus affects key and pointer event
* delivery. </AU> Removing focus does not cause any visual change by the
* implementation other than removing the caret and, if visible, any input
* indicators. Any other change in the visual appearance of the editor when
* the focus is removed is the responsibility of the application; this can
* be done for example by changing the background color or transparency of
* the editor.
*
* @see #setFocus()
*/
public void removeFocus()
{
textWrapper.setFocused(false);
}
/**
* Sets the background color and alpha of this TextEditor to the specified
* values. The set values apply also to any other related implementation
* provided input visuals; for example to the implementation provided
* additional character table or handwriting recognition panel.
*
* @param alpha the alpha component of the color being set in range
* <code>0-255</code>
* @param red the red component of the color being set in range
* <code>0-255</code>
* @param green the green component of the color being set in range
* <code>0-255</code>
* @param blue the blue component of the color being set in range
* <code>0-255</code>
* @throws IllegalArgumentException if any parameter is outside of the range
* <code>0-255</code>
*/
public void setBackgroundColor(int alpha, int red, int green, int blue)
{
if((alpha < 0 || alpha > BITSPERPIXEL8)
|| (red < 0 || red > BITSPERPIXEL8)
|| (green < 0 || green > BITSPERPIXEL8)
|| (blue < 0 || blue > BITSPERPIXEL8))
{
throw new IllegalArgumentException();
}
textWrapper.setBackgroundColor(alpha, red, green, blue);
}
/**
* Sets the index of the caret. The caret can be used to indicate a position
* in the text.
*
* @param index the character index before which to place the caret
*/
public void setCaret(int index)
{
textWrapper.setCaretposition(index);
}
/**
* Sets the input constraints of this <code>TextEditor</code>. If the
* current content of this <code>TextEditor</code> do not match the new
* constraints, the content is set to empty.
*
* @param constraints see <a href="TextField.html#constraints">input
* constraints</a>
* @throws IllegalArgumentException if the value of the constraints
* parameter is invalid
*/
public void setConstraints(int constraints)
{
textWrapper.setConstraints(constraints);
}
/**
* Sets the content of the <code>TextEditor</code> as a string. The set
* string replaces any previous content in the editor.
*
* @param text the new content of the <code>TextEditor</code> as string,
* <code>null</code> empties the <code>TextEditor</code>
* @throws IllegalArgumentException if <code>text</code> is illegal for
* the current input constraints
* @throws IllegalArgumentException if the given text would exceed the
* current maximum capacity of the editor
*/
public void setContent(String text)
{
textWrapper.setContent(text);
}
/**
* Sets this TextEditor focused. <AU> Calling setFocus enables text editing
* and consumes the delivery of needed key events or pointer events from the
* underlying Canvas. Focus needs to be explicitly set to the TextEditor,
* for example, based on Canvas or CustomItem key events or pointer events.
* When Canvas or CustomItem does not have any focused TextEditor all key
* and pointer events are delivered to normal Canvas key and pointer event
* delivery methods. Necessary key and pointer events are captured by
* TextEditor only when it has focus. The setFocus affects to the
* Canvas/CustomItem pointer event delivery in following way: Unfocused
* TextEditors do not consume pointer events and they are delivered to
* Canvas/CustomItem. When user taps top of unfocused TextEditor the normal
* pointer event methods of Canvas are called and application may choose
* based on these to focus the TextEditor. If the editor is focused with
* setFocus it starts to consume the pointer events targeted inside the
* TextEditor area in the contained Canvas, however, taps outside the area
* are delivered to Canvas and based on those events the application may
* choose to unfocus the still focused TextEditor calling removeFocus(). The
* setFocus affects to the Canvas/CustomItem key event delivery in following
* way: If all TextEditors are unfocused the key event delivery of Canvas
* works as if there isn't any TextEditors in it, only when a focus is set
* via this method to a TextEditor the key event delivery is modified. The
* focused editor starts to capture the necessary key events and these key
* events are not delivered to containing Canvas/CustomItem. The set of key
* events captured depends on TextEditor implementation and device HW but in
* most implementations nearly all device keys are captured by the editor
* for text insertion, input mode changes and caret move functionalities.
* Especially applications should not assume to get key events from keys
* mapped to game actions as most probably the same keys are used to
* navigate the text caret within the editor. The keys that are used for
* Command launching in Canvas are available for applications on focused
* TextEditor and these keys either launch commands or send low-level key
* events as normally. </AU> Setting focus does not cause any visual focus
* indication by the implementation other than showing the caret and, if the
* application has requested, drawing the input indicators to the requested
* location on the display. Any other change in the visual appearance of the
* editor in focused state is the responsibility of the application; this
* can be done for example by drawing a focus border around the TextEditor
* or by changing the background color or transparency of the editor. If
* this TextEditor has already been focused earlier and the editor contents
* has not changed after previous unfocusing, then after calling setFocus
* again the caret position should be retained. If there already is another
* focused TextEditor on the Canvas, the focus is first removed from that
* TextEditor before setting this TextEditor focused. <AU>
*
* @throws IllegalStateException If the TextEditor is not added to Canvas or
* CustomItem
* @see #removeFocus() </AU>
*/
public void setFocus()
{
if(parent == null)
{
throw new IllegalStateException();
}
textWrapper.setFocused(true);
}
/**
* Sets the application preferred font for rendering the text content in
* this <code>TextEditor</code>. Setting the font is a hint to the
* implementation, and the implementation may disregard the requested font.
* The <code>font</code> parameter must be a valid <code>Font</code>
* object or <code>null</code>. If the <code>font</code> parameter is
* <code>null</code>, the implementation must use its default font to
* render the text content.
*
* @param font the application preferred font to be used in this TextEditor
*/
public void setFont(Font font)
{
textWrapper.setFont(font);
}
/**
* Sets the foreground color and alpha of this TextEditor to the specified
* values. The content, e.g. text, in the editor must be drawn with this
* color.
*
* @param alpha the alpha component of the color being set in range
* <code>0-255</code>
* @param red the red component of the color being set in range
* <code>0-255</code>
* @param green the green component of the color being set in range
* <code>0-255</code>
* @param blue the blue component of the color being set in range
* <code>0-255</code>
* @throws IllegalArgumentException if any parameter is outside of the range
* <code>0-255</code>
*/
public void setForegroundColor(int alpha, int red, int green, int blue)
{
if((alpha < 0 || alpha > BITSPERPIXEL8)
|| (red < 0 || red > BITSPERPIXEL8)
|| (green < 0 || green > BITSPERPIXEL8)
|| (blue < 0 || blue > BITSPERPIXEL8))
{
throw new IllegalArgumentException();
}
textWrapper.setForegroundColor(alpha, red, green, blue);
}
/**
* Sets the drawing location for implementation provided input indicators on
* the display. The anchor point given is relative to the upper left corner
* of the rendering surface. The location may be outside the TextEditor
* boundaries. The application should first query the size of the input
* indicators with getIndicatorSize method to determine that the indicators
* will not be clipped outside the available display area when drawn to the
* requested location. <AU>If this method is not called no indicators are
* present and usability of complex device specific input methods may be
* compromised.</AU>
*
* @param x the x coordinate of the anchor point, in pixels.
* @param y the y coordinate of the anchor point, in pixels.
*/
public void setIndicatorLocation(int x, int y)
{
// TODO: implement this
}
/**
* Sets a hint to the implementation as to the input mode that should be
* used when the user initiates editing of this <code>TextEditor</code>.
* The <code>characterSubset</code> parameter names a subset of Unicode
* characters that is used by the implementation to choose an initial input
* mode. If <code>null</code> is passed, the implementation should choose
* a default input mode. See <a href="TextField#modes">Input Modes</a> for
* a full explanation of input modes.
*
* @param inputMode a string naming a Unicode character subset, or
* <code>null</code>
*/
public void setInitialInputMode(String inputMode)
{
textWrapper.setInputMode(inputMode);
}
/**
* Sets the maximum size (number of characters) that can be contained in
* this <code>TextEditor</code>. If the current content of the
* <code>TextEditor</code> is larger than the new <code>maxSize</code>,
* the content is truncated to fit.
*
* @param maxSize the new maximum size
* @return assigned maximum capacity - may be smaller than requested.
* @throws IllegalArgumentException if <code>maxSize</code> is zero or
* less.
* @throws IllegalArgumentException if the contents after truncation would
* be illegal for the current input constraints
*/
public int setMaxSize(int maxSize)
{
textWrapper.setMaxSize(maxSize);
return textWrapper.getMaxSize();
}
/**
* Sets the rendering position of this TextEditor. The anchor point given is
* relative to the upper left corner of the rendering surface.
*
* @param x the x coordinate of the anchor point, in pixels.
* @param y the y coordinate of the anchor point, in pixels.
*/
public void setPosition(int x, int y)
{
textWrapper.setPosition(x, y);
}
/**
* Sets the number of visible rows. <AU>The height of the TextField gets
* modified depending the text row height.</AU>
*
* @param rows the number of visible rows in the editor
* @throws IllegalArgumentException if rows is zero or less
*/
public void setRows(int rows)
{
if(rows <= 0)
{
throw new IllegalArgumentException();
}
textWrapper.setSize(textWrapper.getWidth(),
textWrapper.getLineHeight() * rows);
}
/**
* Sets a selection on a range of text in the TextEditor content. The
* implementation should highlight the selection visually. A selection may
* be set with this method or by user interaction. If there already is a
* selection set, it is replaced by this new selection.
*
* @param index the index of the first character to be selected.
* @param length the length of the selection in characters.
*/
public void setSelection(int index, int length)
{
textWrapper.setSelection(index, index + length);
}
/**
* Sets the size of this TextEditor in pixels.
*
* @param width width in pixels
* @param height height in pixels
* @throws IllegalArgumentException if the width or height is less than zero
*/
public void setSize(int width, int height)
{
textWrapper.setSize(width, height);
}
/**
* Sets a listener for content changes in this <code>TextEditor</code>,
* replacing any previous <code>TextEditorChangeListener</code>. A
* <code>null</code> reference is allowed and has the effect of removing
* any existing <code>TextEditorChangeListener</code> from this
* <code>TextEditor</code>.
*
* @param aChangelistener the new listener, or <code>null</code>
*/
public void setTextEditorChangeListener(
TextEditorChangeListener aChangelistener)
{
changelistener = aChangelistener;
}
/**
* Sets this TextEditor visible. Shows the editor with its content, but
* without the caret. If the editor does not have any visible content and
* does not have any background color set then this method effectively does
* not cause any visual change in the display. If the application has
* requested the implementation to draw input indicators, they are not made
* visible by the call of this method. The indicators are made visible only
* when actual editing is enabled by the call of setFocus() method. <AU>
*
* @throws IllegalStateException If the TextEditor is not added to Canvas or
* CustomItem
* @see #hide() </AU>
*/
public void show()
{
if(parent == null)
{
throw new IllegalStateException();
}
textWrapper.setVisible(true);
}
/**
* Gets the number of characters that are currently stored in this
* <code>TextEditor</code>.
*
* @return the number of characters
*/
public int size()
{
return textWrapper.getSize();
}
/**
* Text modify listener.
*/
class TextModifyListener implements ModifyListener
{
public void modifyText(ModifyEvent me)
{
if(changelistener != null)
{
changelistener.textChanged(0, textWrapper.getSize());
}
}
}
/**
* Text selection listener.
*/
class TextSelectionListener implements SelectionListener
{
public void widgetDefaultSelected(SelectionEvent se)
{
Logger.verbose("TE - DefSel: " + se);
}
public void widgetSelected(SelectionEvent se)
{
Logger.verbose("TE - Sel: " + se);
}
}
}