/*********************************************************************************
* TotalCross Software Development Kit *
* Copyright (C) 1998, 1999 Wabasoft <www.wabasoft.com> *
* Copyright (C) 2000-2012 SuperWaba Ltda. *
* All Rights Reserved *
* *
* This library and virtual machine 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. *
* *
* This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 *
* A copy of this license is located in file license.txt at the root of this *
* SDK or can be downloaded here: *
* http://www.gnu.org/licenses/lgpl-3.0.txt *
* *
*********************************************************************************/
package totalcross.ui;
import totalcross.sys.*;
import totalcross.ui.dialog.*;
import totalcross.ui.event.*;
import totalcross.ui.gfx.*;
import totalcross.ui.image.*;
import totalcross.util.*;
/**
* Edit is a text entry control.
* <p>
* Here is an example showing an edit control being used:
*
* <pre>
* public class MyProgram extends MainWindow
* {
* Edit edit;
*
* public void initUI()
* {
* edit = new Edit();
* add(edit,LEFT,TOP);
* }
* </pre>
* Important: if you wish to open a popup window after a FOCUS_OUT event has occured,
* you must open the window with popupNonBlocking, never with popup. Otherwise,
* the window will be openned twice.
* Here's a sample code on how to proceed:
* <pre>
* public class Test extends MainWindow
* {
Edit ed;
MessageBox mb;
public void initUI()
{
add(ed = new Edit(""), LEFT, CENTER);
add(new Button("btn"), LEFT, AFTER+5);
}
public void onEvent(Event event)
{
switch (event.type)
{
case ControlEvent.FOCUS_OUT:
if (event.target == ed)
{
event.consumed = true; // this is important!
(mb=new MessageBox("Hi","Verinha")).popupNonBlocking();
}
break;
case ControlEvent.WINDOW_CLOSED:
if (event.target == mb)
ed.setText("Window closed.");
break;
}
}
}
* </pre>
* A long click on an Edit will result in a menu with copy/paste options to be displayed.
* @see #clipboardDelay
*/
public class Edit extends Control
{
private TimerEvent blinkTimer; // only valid while the edit has focus
private static int xMins[] = {4,1,3,3,4};
public static final int prefH = uiAndroid ? 4 : 2;
private boolean hasFocus;
private boolean cursorShowing;
/** Specifies if the control accepts input from the user.
* Note: do not change this directly; use the setEditable method instead.
* @see #setEditable(boolean)
*/
protected boolean editable = true;
/** Specifies if new chars should overwrite existing ones. */
public boolean overwrite;
/** Sets the alignment of this Edit, which can be LEFT (default), CENTER or RIGHT.
* Note that it will always edit at left, but on focus lost, it will drawn aligned.
* @since SuperWaba 5.03
*/
public int alignment=LEFT;
/** @see CalculatorBox#rangeCheck */
public CalculatorBox.RangeCheck rangeCheck;
private ControlEvent cursorChangedEvent;
private StringBuffer chars = new StringBuffer(10);
protected boolean hasBorder=true;
private int xMax,xMin,gap;
private int fColor,back0,back1;
private int fourColors[] = new int[4];
protected boolean isMaskedEdit;
private int decimalPlaces = 2;
private int insertPos;
private int startSelectPos;
private int xOffset;
private boolean wasFocusIn,wasFocusInOnPenDown; // jairocg@450_31: used to verify if the event was focusIn
private int oldTabIndex=-1;
private boolean ignoreSelect;
private int wildW;
private boolean useFillAsPreferred;
private StringBuffer masked = new StringBuffer(20);
private boolean isNegative;
private int cursorX;
private int lastCommand;
private String mapFrom,mapTo; // guich@tc110_56
private Image npback;
public boolean showKeyboardOnNextEvent;
static PushButtonGroup clipboardMenu;
/** Used to inform that a <i>copy</i> operation has been made. You can localize this message if you wish. */
public static String copyStr = "copy";
/** Used to inform that a <i>cut</i> operation has been made. You can localize this message if you wish. */
public static String cutStr = "cut";
/** Used to inform that a <i>paste</i> operation has been made. You can localize this message if you wish. */
public static String pasteStr = "paste";
/** Used to inform that a <i>replace</i> operation has been made. You can localize this message if you wish. */
public static String replaceStr = "replace";
/** Used to inform that a <i>command</i> operation has been made. You can localize this message if you wish. */
public static String commandStr = "command";
/** Defines an optional value to be used in the CalculatorBox when the keyboard type is KBD_NUMERIC or KBD_CALCULATOR.
* Replaces the decimal separator / 00 char.
* @since TotalCross 1.5
*/
public String optionalValue4CalculatorBox;
/** Defines a title that can be used in the Keyboards.
* @since TotalCross 1.53
*/
public String keyboardTitle;
/** Defines the time that the user will have to press to see a popup menu with copy/paste options.
* Set to -1 to disable it; defaults to 1500 (1.5 seconds) in pen devices, -1 in finger devices (because in these devices . Also affects MultiEdit.
* @since TotalCross 1.3
*/
public static int clipboardDelay = 1500;
protected String validChars; // guich
/** The KeyboardBox used in all Edits. */
public static KeyboardBox keyboard; // guich
/** The CalendarBox used in all Edits. */
public static CalendarBox calendar; // guich
/** The CalculatorBox used in all Edits. */
public static CalculatorBox calculator; // guich@200
/** The NumericBox used in all Edits. */
public static CalculatorBox numeric;
/** The TimeBox used in all Edits. */
public static TimeBox time;
protected byte mode; // guich
protected int maxLength; // guich@200b4
/** used only to compute the preferred width of this edit. If the mask is empty, the edit fills to width. */
private char[] mask;
/** Sets the capitalise settings for this Edit. Text entered will made as is, uppercase or
* lowercase
* @see #ALL_NORMAL
* @see #ALL_UPPER
* @see #ALL_LOWER
*/
public byte capitalise; // guich@320_26
public static boolean removeFocusOnAction = true; // as per MBertrand - guich@580
/** Color to apply to the Edit when it has focus (works only on Android user interface style).
* By default, there's only a blinking cursor.
* @since TotalCross 1.3
*/
public int focusColor = -1;
/** Use the NumericBox instead of the Calculator in all Edits that have mode equal to CURRENCY.
* Note that you can set for each control by calling <code>ed.setKeyboard(Edit.KBD_NUMERIC)</code>.
* @since TotalCross 1.3
*/
public static boolean useNumericBoxInsteadOfCalculator;
protected byte kbdType=KBD_DEFAULT;
/** Set to false if you don't want the cursor to blink when the edit is not editable */
public boolean hasCursorWhenNotEditable = true; // guich@340_23
/** If set to true, the text will be auto-selected when the focus enters.
* True by default on penless devices. */
public boolean autoSelect = Settings.keyboardFocusTraversable; // guic@550_20
/** No keyboard will be popped up for this Edit */
public static final byte KBD_NONE = 0;
/** The default keyboard for the current mode will be used for this Edit */
public static final byte KBD_DEFAULT = 1;
/** The Keyboard class (or the internal virtual keyboard) will be used for this Edit */
public static final byte KBD_KEYBOARD = 2;
/** The Calculator will be used for this Edit */
public static final byte KBD_CALCULATOR = 3;
/** The Calendar will be used for this Edit */
public static final byte KBD_CALENDAR = 4;
/** The NumericBox will be used for this Edit */
public static final byte KBD_NUMERIC = 5;
/** The TimeBox will be used for this Edit */
public static final byte KBD_TIME = 6;
/** to be used in the setValidChars method */
public static final String numbersSet = "0123456789";
/** to be used in the setValidChars method */
public static final String currencyCharsSet = "0123456789.+-";
/** to be used in the setValidChars method */
public static final String dateSet = numbersSet+Settings.dateSeparator;
/** to be used in the setMode method */
public static final byte NORMAL = 0;
/** to be used in the setMode method */
public static final byte DATE = 1;
/** to be used in the setMode method */
public static final byte CURRENCY = 2;
/** to be used in the setMode method. The last char will be always shown */
public static final byte PASSWORD = 3;
/** to be used in the setMode method. All chars are replaced by '*' */
public static final byte PASSWORD_ALL = 4;
/** to be used in the capitalise property */
public static final byte ALL_NORMAL = 0;
/** to be used in the capitalise property */
public static final byte ALL_UPPER = 1;
/** to be used in the capitalise property */
public static final byte ALL_LOWER = 2;
/** Defines if this Edit can behave as with virtual keyboard or not.
* @since TotalCross 1.6
*/
public boolean virtualKeyboard = Settings.virtualKeyboard;
/** Cursor thickness */
public static int cursorThickness = Math.max(Settings.screenWidth,Settings.screenHeight) > 1500 ? 3 : Math.max(Settings.screenWidth,Settings.screenHeight) > 700 ? 2 : 1;
/** Construct an Edit with FILL as preferred width. Note that you cannot use RIGHT or CENTER at the x coordinate if you use this constructor. */
public Edit()
{
clearPosState();
onFontChanged();
useFillAsPreferred = true;
}
/** Construct an Edit with the default width computed based in the specified
* mask and in the control's font. In order to allow the mask to be used as
* a real mask, you must call the setMode method.
* If mask is "", the FILL width is choosen.
* @see #setMode(byte, boolean)
*/
public Edit(String mask)
{
this();
this.mask = mask.toCharArray();
useFillAsPreferred = mask.length() == 0;
}
/** Maps the keys in the from char array into the keys in the to char array. For example enable a 'numeric pad'
* on devices that has the 1 in the u character, you can use this:
* <pre>
* ed.mapKeys("uiojklnm!.","1234567890");
* </pre>
* To make sure that lowercase characters are also handled, you should also change the capitalise mode:
* <pre>
* ed.capitalise = Edit.ALL_LOWER;
* </pre>
* If you want to disable a set of keys, use the setValidChars method. Note that mapKeys have precendence over setValidChars.
* @param from The source keys. Must have the same length of <code>to</code>. Set to null to disable mapping.
* @param to The destination keys. Must have the same length of <code>from</code>
* @since TotalCross 1.01
* @see #setValidChars(String)
*/
public void mapKeys(String from, String to)
{
if (from == null || to == null)
from = to = null;
else
if (from.length() != to.length())
throw new IllegalArgumentException("from.length must match to.length");
this.mapFrom = from;
this.mapTo = to;
}
/** Set the number of decimal placed if a masked edit with CURRENCY mode. Default is 2 decimal places.
* It cannot be used with masked Edits; pass the number of decimal places in the mask itself.
* The only exception is when you want to use the default CURRENCY mask, passing a null mask in the constructor;
* in this situation, you can call setDecimalPlaces before calling setMode, and a mask will be constructed with
* the given number of decimals.
*/
public void setDecimalPlaces(int count)
{
if (count < 0)
throw new IllegalArgumentException("count must be >= 0");
if (isMaskedEdit)
throw new RuntimeException("Edit.setDecimalPlaces can't be used after the mask is applied using setMode.");
this.decimalPlaces = count;
}
/** Returns the number of decimal places. */
public int getDecimalPlaces()
{
return decimalPlaces;
}
/** Used to change the default keyboard to be used with this Edit control.
* Note that setMode calls setKeyboard(KBD_DEFAULT), so be sure to set the mode before calling setKeyboard.
* @see #KBD_NONE
* @see #KBD_DEFAULT
* @see #KBD_KEYBOARD
* @see #KBD_CALCULATOR
* @see #KBD_CALENDAR
* @see #KBD_NUMERIC
* @see #KBD_TIME
* @see #useNumericBoxInsteadOfCalculator
*/
public void setKeyboard(byte kbd) // guich@310_19
{
this.kbdType = kbd;
if (kbd == KBD_DEFAULT)
switch (mode)
{
case DATE: kbdType = KBD_CALENDAR; break;
case CURRENCY: kbdType = useNumericBoxInsteadOfCalculator ? KBD_NUMERIC : KBD_CALCULATOR; break;
default: kbdType = KBD_KEYBOARD; break;
}
}
/** Returns the keyboard type of this Edit control.
* @see #KBD_NONE
* @see #KBD_DEFAULT
* @see #KBD_KEYBOARD
* @see #KBD_CALCULATOR
* @see #KBD_CALENDAR
* @see #KBD_NUMERIC
* @see #KBD_TIME
* @since SuperWaba 5.67
*/
public byte getKeyboardType() // guich@567_6
{
return kbdType;
}
protected void onFontChanged()
{
wildW = fm.charWidth('*');
}
/** Returns the mask passed on the constructor. */
public String getMask()
{
return mask == null ? "" : new String(mask);
}
/** Used to set the valid characters that can be entered by using one of the mode constants,
* <b>without</b> masking. To enable masking the input, you have to call
* the setMode method passing the mode and <code>true</code> as parameter.
* Note that setMode calls setKeyboard(KBD_DEFAULT), so be sure to set the mode before calling setKeyboard.
* @see #NORMAL
* @see #DATE
* @see #CURRENCY
* @see #PASSWORD
* @see #PASSWORD_ALL
*/
public void setMode(byte mode)
{
setMode(mode, false);
}
/** Return the current mode.
* @since TotalCross 1.27
*/
public int getMode()
{
return mode;
}
/** Used to set the valid characters that can be entered by using one of the mode constants,
* optionally enabling the mask to be applied to the input.
* Note that setMode calls setKeyboard(KBD_DEFAULT), so be sure to set the mode before calling setKeyboard.
* @see #NORMAL
* @see #DATE
* @see #CURRENCY
* @see #PASSWORD
* @see #PASSWORD_ALL
*/
public void setMode(byte mode, boolean maskedEdit)
{
this.mode = mode;
this.isMaskedEdit = maskedEdit;
this.useFillAsPreferred = this.useFillAsPreferred && !maskedEdit;
switch (mode)
{
case DATE:
setValidChars(maskedEdit ? numbersSet : (numbersSet+Settings.dateSeparator));
if (maskedEdit)
{
maxLength = 8;
mask = Settings.dateFormat == Settings.DATE_YMD ? ("9999"+Settings.dateSeparator+"99"+Settings.dateSeparator+"99").toCharArray()
: ("99"+Settings.dateSeparator+"99"+Settings.dateSeparator+"9999").toCharArray();
}
break;
case CURRENCY:
setValidChars(currencyCharsSet);
if (maskedEdit)
{
if (mask == null || mask.length == 0) // use a default mask
mask = getDefaultCurrencyMask(decimalPlaces);
applyMaxLengthBasedOnMask();
alignment = RIGHT;
}
break;
default:
setValidChars(null);
if (maskedEdit && mask != null && mask.length > 0)
applyMaxLengthBasedOnMask();
break;
}
if (kbdType != KBD_NONE) // guich@tc115_29
setKeyboard(KBD_DEFAULT);
}
public static char[] getDefaultCurrencyMask(int decimalPlaces)
{
String s = decimalPlaces == 0 ? "999a999a999a999a999" : ("999a999a999a999a999b"+Convert.dup('9',decimalPlaces));
s = s.replace('a', Settings.thousandsSeparator);
s = s.replace('b', Settings.decimalSeparator);
return s.toCharArray();
}
private void applyMaxLengthBasedOnMask()
{
int nines = 0;
for (int i =0; i < mask.length; i++)
if (mask[i] == '9')
nines++;
else
if (mode == CURRENCY && mask[i] == Settings.decimalSeparator)
decimalPlaces = mask.length-i-1;
maxLength = nines;
}
/** Sets the valid chars that can be entered in this edit
* (they are converted to uppercase to make the verification easy).
* if null is passed, any char can be entered. The chars are case insensitive.
* If you pass "" (empty string), no chars will be able to be inputted, and movement,
* delete and copy/paste operations will also be disabled.
* @see #mapKeys
*/
public void setValidChars(String validCharsString)
{
if (validCharsString != null)
validChars = validCharsString.toUpperCase();
else
validChars = null;
}
/** Return true if the given char exists in the set of valid characters for this Edit */
protected boolean isCharValid(char c)
{
return validChars == null || validChars.indexOf(Convert.toUpperCase(c)) != -1;
}
/** Sets the desired maximum length for text entered in the Edit.
* Does nothing if the edit has a mask.
@since SuperWaba 2.0 beta 4 */
public void setMaxLength(int length)
{
//if (!isMaskedEdit) // guich@tc115_83: ignore if using masks
{
maxLength = length;
if (length != 0 && maxLength < chars.length()) // jescoto@421_15: resize text if maxLength < len
chars.setLength(length);
}
}
private void clearPosState()
{
insertPos = 0;
startSelectPos = -1;
xOffset = xMins[Settings.uiStyle];
}
protected int pushedInsertPos;
protected int pushedStartSelectPos;
protected int pushedxOffset;
protected void pushPosState()
{
pushedInsertPos = insertPos;
pushedStartSelectPos = startSelectPos;
pushedxOffset = xOffset;
}
protected void popPosState()
{
int len = chars.length();
insertPos = Math.min(len,pushedInsertPos); // guich@571_5: make sure the insert position isn't bigger than the size of the text.
startSelectPos = Math.min(len,pushedStartSelectPos); // guich@571_5
xOffset = pushedxOffset;
}
protected int charPos2x(int n)
{
if (!isMaskedEdit)
{
if (n == 0) // start of string?
return xOffset;
if (n >= chars.length()) // end or beyond end of string?
return xOffset + getTotalCharWidth();
}
else
if (n > chars.length()) n = chars.length();
switch (mode)
{
case PASSWORD_ALL:
return xOffset + wildW * n;
case PASSWORD:
return xOffset + wildW * (n-1) + fm.charWidth(chars, chars.length()-1);
case CURRENCY:
if (isMaskedEdit) // in currency, we go from right to left
{
int xx = xMax,i,pos = masked.length();
n = chars.length() - n;
for (i = n; i > 0 && --pos >= 0;)
{
char c = masked.charAt(pos);
xx -= fm.charWidth(c);
if ('0' <= c && c <= '9') // update the position at the main string only when a numeric value is represented
i--;
}
return xx;
}
else break;
default://case DATE:
if (masked.length() > 0)
{
int i=0,pos=0;
for (; i < n; pos++)
if (pos >= mask.length)
break;
else
if (mask[pos] == '9') // update the position at the main string only when a numeric value is represented
i++;
while (pos < mask.length && mask[pos] != '9') pos++; // skip next non-numeric chars
return xOffset + fm.sbWidth(masked, 0, pos);//Math.min(pos,masked.length())); // guich@tc152: changed mask to masked, otherwise, using old font and 1's will make the cursor appear incorrectly
}
}
return xOffset + fm.sbWidth(chars, 0, n);
}
/** Returns the text displayed in the edit control. If masking is enabled, the text with the mask is returned;
* to get the text without the mask, use the getTextWithoutMask method.
* @see #getTextWithoutMask()
*/
public String getText()
{
return isMaskedEdit ? masked.toString() : chars.toString();
}
/** Returns the text without the mask. For non-currency mode, only chars whose corresponding mask is '9' are returned.
* @see #getText()
*/
public String getTextWithoutMask()
{
if (chars.length() == 0)
return "";
String str = chars.toString();
if (isMaskedEdit)
{
if (mode == CURRENCY)
{
if (!hasSignificantDigits())
{
if (str.indexOf('.') < 0 && str.indexOf(',') < 0) // guich@tc130: return "0" instead of "000"
str = Convert.toString(0d,decimalPlaces);//"0";
}
else
{
if (decimalPlaces > 0) // for currency mode, remove the , and . and put it in Java's format (xxxx.yy)
{
int k = str.length() - decimalPlaces; // get the number of decimal places
if (k <= 0)
str = "0.".concat(Convert.zeroPad(str,decimalPlaces));
else
str = str.substring(0,k)+"."+str.substring(k);
}
if (isNegative)
str = "-".concat(str);
}
}
else
{
StringBuffer sbuf = new StringBuffer(str.length());
if (mask.length == str.length()) // totally formatted? faster algorithm
{
// 25/03/1970 -> 25031970
for (int i =0; i < mask.length; i++)
if (mask[i] == '9')
sbuf.append(chars.charAt(i));
}
else
{
// 25031970 -> 25031970
int max = chars.length();
for (int i =0,j=0; i < mask.length; i++) // guich@tc124_23: must go throught all the mask size
if ('0' <= mask[i] && mask[i] <= '9' && j < max)
sbuf.append(chars.charAt(j++));
}
str = sbuf.toString();
}
}
return str;
}
/** Returns the text's buffer. Do NOT change the buffer contents, since changing it
* will not affect the char widths array, thus, leading to a wrong display.
* @since TotalCross 1.0
*/
public StringBuffer getTextBuffer()
{
return chars;
}
public void setText(String s, boolean postPressed)
{
int len,dot,decimals;
chars.setLength(0);
if (s != null && (len=s.length()) > 0)
{
chars.append(s);
if (mode == CURRENCY && isMaskedEdit) // correct the number if this is a numeric edit
{
isNegative = s.startsWith("-");
if (isNegative) {len--; s = s.substring(1); chars.setLength(0); chars.append(s);} // guich@tc168 - if user sends a negative value, remove it from start and set the flag
if (s.indexOf(',') >= 0 || Convert.numberOf(s, '.') > 1)
s = Convert.replace(s,".","").replace(',','.');
dot = s.indexOf('.');
decimals = len - dot - 1;
if (decimalPlaces == 0) // setText("12.34") -> "12"
{
if (dot >= 0)
chars.setLength(dot); // cut
}
else
if (dot < 0) // setText("12") -> "12.00"
chars.append(Convert.dup('0',decimalPlaces));
else
{
if (decimals == decimalPlaces)
;
else
if (decimals > decimalPlaces) // setText("12.3456") -> "12.34"
chars.setLength(len - (decimals - decimalPlaces));
else // decimals <= decimalPlaces setText("12.1") -> "12.10"
chars.append(Convert.dup('0', decimalPlaces - decimals));
int delpos = len - decimals - 1;
chars.delete(delpos, delpos+1); // remove the . - guich@tc100b4_11: added +1
}
}
else
if (mode != CURRENCY && isMaskedEdit && chars.length() >= mask.length)
{
String unmasked = getTextWithoutMask();
chars.setLength(0);
chars.append(unmasked);
}
}
applyMaskToInput();
clearPosState();
Window.needsPaint = true;
if (postPressed)
postPressedEvent();
}
/**
* Sets the text displayed in the edit control.
* If you're setting the text in CURRENCY mode,
* the text must be set <b>not</b> formatted (unmasked).
*/
public void setText(String s)
{
setText(s,Settings.sendPressEventOnChange);
}
/** Sets if the control accepts input from the user.
* If set to false, you must explicitly call the clear method of this edit.
*/
public void setEditable(boolean on)
{
focusTraversable = editable = on;
}
/** Gets if the control accepts input from the user */
public boolean isEditable()
{
return editable;
}
protected void onBoundsChanged(boolean screenChanged) // guich
{
xMin = xMins[Settings.uiStyle];
xMax = this.width - xMin;
gap = hasBorder ? (xMin>>1) : 0;
npback = null;
}
public int getPreferredWidth()
{
return (mask==null || useFillAsPreferred)?FILL:(fm.stringWidth(new String(mask)) + (uiAndroid?10:(uiFlat||uiVista)?8:4)); // guich@200b4_202: from 2 -> 4 is PalmOS style - guic@300_52: empty mask means FILL - guich@570_88: fixed width when uiFlat
}
public int getPreferredHeight()
{
return fmH+prefH;
}
protected void onColorsChanged(boolean colorsChanged)
{
npback = null;
fColor = getForeColor();
back0 = UIColors.sameColors ? backColor : Color.brighter(getBackColor()); // guich@572_15
back1 = back0 != Color.WHITE ?(UIColors.sameColors?Color.darker(getBackColor()):backColor):Color.getCursorColor(back0);//guich@300_20: use backColor instead of: back0.getCursorColor();
if (!uiAndroid) Graphics.compute3dColors(isEnabled(),backColor,foreColor,fourColors);
}
private int getTotalCharWidth()
{
int len = chars.length();
switch (mode)
{
case PASSWORD_ALL: return len == 0 ? 0 : wildW * len;
case PASSWORD: return len == 0 ? 0 : wildW * (len-1) + fm.charWidth(chars, len-1);
default:
if (isMaskedEdit)
{
int pos = masked.length();
int n = Math.max(pos,len),i;
int ww = 0;
for (i = n; i > 0 && --pos >= 0;)
{
char c = masked.charAt(pos);
ww += fm.charWidth(c);
if ('0' <= c && c <= '9') // update the position at the main string only when a numeric value is represented
i--;
}
return ww;
}
else
return fm.sbWidth(chars,0,len);
}
}
/* protected void draw(Graphics g, boolean cursorOnly)
{
draw(g);
}
*/
protected void draw(Graphics g)
{
if (g == null || !isDisplayed()) return; // guich@tc114_65: check if its displayed
int y = this.height - fmH - gap;
if (uiAndroid) y -= 1;
//if (!cursorOnly) // guich@200b4_23: optimized when cursorOnly
{
g.backColor = back0;
if (!transparentBackground)
{
int gg = gap;
if (uiAndroid) {g.backColor = parent.backColor; gg = 0;}
if (!uiAndroid || !hasBorder) g.fillRect(gg,gg, this.width - (gg << 1), this.height - (gg << 1));
if (hasBorder && uiAndroid)
{
try
{
if (npback == null || focusColor != -1)
npback = NinePatch.getInstance().getNormalInstance(NinePatch.EDIT, width, height, isEnabled() ? hasFocus && focusColor != -1 ? focusColor : back0 : (back0 == parent.backColor ? Color.darker(back0,32) : Color.interpolate(back0,parent.backColor)), false);
}
catch (ImageException e) {e.printStackTrace();}
NinePatch.tryDrawImage(g,npback,0,0);
}
}
// draw the text and/or the selection
int len = chars.length();
if (len > 0)
{
if (startSelectPos != -1 && editable) // moved here to avoid calling g.eraseRect (call fillRect instead) - guich@tc113_38: only if editable
{
// character regions are:
// 0 to (sel1-1) .. sel1 to (sel2-1) .. sel2 to last_char
int sel1 = Math.min(startSelectPos,insertPos);
int sel2 = Math.max(startSelectPos,insertPos);
int sel1X = charPos2x(sel1);
int sel2X = charPos2x(sel2);
int old = g.backColor;
g.backColor = back1;
g.fillRect(sel1X,y,sel2X-sel1X+1,fmH);
g.backColor = old;
}
g.foreColor = fColor;
int xx = xOffset;
if (!hasFocus) // guich@503_2: align the edit after it looses focus
switch (alignment)
{
case RIGHT: xx = this.width-getTotalCharWidth()-xOffset; break;
case CENTER: xx = (this.width-getTotalCharWidth())>>1; break;
}
if (hasBorder) g.setClip(xMin,0,xMax-Edit.prefH,height);
switch (mode)
{
case PASSWORD: // password fields usually have small text, so this method does not have to be very optimized
g.drawText(Convert.dup('*',len-1)+chars.charAt(len-1), xx, y, textShadowColor != -1, textShadowColor);
break;
case PASSWORD_ALL:
g.drawText(Convert.dup('*',len), xx, y, textShadowColor != -1, textShadowColor);
break;
case CURRENCY:
if (isMaskedEdit)
xx = this.width-getTotalCharWidth()-xOffset-1;
default:
if (masked.length() > 0)
g.drawText(masked, 0, masked.length(), xx, y, textShadowColor != -1, textShadowColor);
else
g.drawText(chars, 0, len, xx, y, textShadowColor != -1, textShadowColor);
}
if (hasBorder) g.clearClip();
}
if (hasBorder && !uiAndroid)
g.draw3dRect(0,0,this.width,this.height,Graphics.R3D_EDIT,false,false,fourColors); // draw the border and erase the rect
cursorX = charPos2x(insertPos);
}
if (hasFocus && isEnabled() && (editable || hasCursorWhenNotEditable)) // guich@510_18: added check to see if it is enabled
{
// draw cursor
if (xMin <= cursorX && cursorX <= xMax) // guich@200b4_155
{
if (cursorShowing)
{
g.clearClip();
g.foreColor = Color.interpolate(backColor,foreColor);
g.drawRect(cursorX - 1, uiAndroid?y+1:y, cursorThickness, fmH);
}
}
cursorShowing = !cursorShowing;
}
else
cursorShowing = false;
}
private void applyMaskToInput()
{
int len = chars.length();
StringBuffer masked = this.masked; // cache instance field
masked.setLength(0);
if (len == 0)
isNegative = false;
else
if ((mode == DATE || mode == NORMAL) && isMaskedEdit) // date must go forward
{
int n = Math.min(len,mask.length),i=0,pos=0;
while (i < n)
if (pos >= mask.length)
break;
else
if (mask[pos] == '9')
{
masked.append(chars.charAt(i++));
pos++;
}
else
{
masked.append(mask[pos]);
if (mask[pos] == chars.charAt(i)) // if the user passed 25/03/1970 to a mask 99/99/9999, skip the chars if the char matches
i++;
pos++;
}
if (pos < mask.length && mask[pos] != '9') // put the slash if it's next
masked.append(mask[pos]);
}
else
if (mode == CURRENCY && isMaskedEdit && len > 0) // currency must go backward
{
for (int i =len-1,pos=mask.length-1; i >= 0 && pos >= 0;)
if (mask[pos] == '9')
{
masked.append(chars.charAt(i--));
pos--;
}
else
{
masked.append(mask[pos]);
if (chars.charAt(i) == mask[pos])
i--;
pos--;
}
if (hasSignificantDigits())
{
if (decimalPlaces > 0)
{
int k = masked.length() - decimalPlaces; // get the number of decimal places
if (k <= 0)
masked.append(Convert.zeroPad("",-k)).append(Settings.decimalSeparator).append('0');
}
if (isNegative)
masked.append('-');
}
else isNegative = false;
masked.reverse();
}
}
private boolean hasSignificantDigits()
{
for (int i=chars.length()-1; i >= 0; i--)
if (chars.charAt(i) != '0')
return true;
return false;
}
/** Sets the selected text of this Edit (if start != end). Can be used to set the cursor position,
* if start equals end. Start must be less or equal to end, and both must be >= 0.
* It can also be used to clear the selectedText, calling <code>setCursorPos(-1,0)</code>.
* Note: if you're setting the cursor position before the edit is drawn for the first
* time, the edit will not be scrolled if the end position goes beyond the limits.
* Important! No bounds checking is made. Be sure to not call this method with invalid positions!
* Example:
* <pre>
* ed.setText("1234567890123456");
* ed.setCursorPos(3,14);
* ed.requestFocus();
* </pre>
*/
public void setCursorPos(int start, int end) // guich@400_18
{
startSelectPos = (start != end)?start:-1;
insertPos = end;
if (cursorChangedEvent == null)
cursorChangedEvent = new ControlEvent(ControlEvent.CURSOR_CHANGED,this);
onEvent(cursorChangedEvent);
Window.needsPaint = true;
}
/** Returns an array with the cursor positions. You can use it with getText
* to find the selected text String.
* E.g.:
* <pre>
* int []cursorPos = ed.getCursorPos();
* int start = cursorPos[0];
* int end = cursorPos[1];
* String text = ed.getText();
* if (start != -1) // is the text selected?
* {
* String selectedText = text.substring(start,end);
* ...
* </pre>
*/
public int[] getCursorPos() // guich@400_18
{
return new int[]{startSelectPos,insertPos};
}
/** User method to popup the keyboard/calendar/calculator for this edit. */
public void popupKCC()
{
if (kbdType == KBD_NONE || !editable || !isEnabled()) // fdie@ nothing to do if kdb has been disabled
return;
if (!popupsHidden())
{
// check if the keyboard is already popped up
if(Settings.fingerTouch && kbdType != KBD_TIME && kbdType != KBD_CALENDAR && kbdType != KBD_CALCULATOR && kbdType != KBD_NUMERIC)
return;
}
Window w = getParentWindow();
if (w != null) w.swapFocus(this);//requestFocus(); // guich@200b4: bring focus back - guich@401_15: changed to swapFocus
switch (kbdType)
{
case KBD_TIME:
if (time == null) time = new TimeBox();
time.tempTitle = keyboardTitle;
try
{
time.setTime(new Time(getText(),false,false,false,true,true,true));
}
catch (Exception e)
{
time.setTime(new Time(0));
if (chars.length() > 0 && Settings.onJavaSE) e.printStackTrace();
}
hideSip();
time.popup();
setText(time.getTime().toString(),true);
break;
case KBD_CALENDAR:
if (calendar == null) calendar = new CalendarBox();
calendar.tempTitle = keyboardTitle;
try {calendar.setSelectedDate(new Date(getText()));} catch (InvalidDateException ide) {} // if the date is invalid, just ignore it
hideSip();
calendar.popupNonBlocking();
break;
case KBD_CALCULATOR:
if (calculator == null) calculator = new CalculatorBox();
calculator.rangeCheck = this.rangeCheck;
calculator.tempTitle = keyboardTitle;
calculator.optionalValue = optionalValue4CalculatorBox;
hideSip();
calculator.popupNonBlocking();
break;
case KBD_NUMERIC:
if (numeric == null) numeric = new CalculatorBox(false);
numeric.rangeCheck = this.rangeCheck;
numeric.tempTitle = keyboardTitle;
numeric.optionalValue = optionalValue4CalculatorBox;
hideSip();
numeric.popupNonBlocking();
break;
default:
if (virtualKeyboard)
{
if (editable && !"".equals(validChars))
{
int sbl = Settings.SIPBottomLimit;
if (sbl == -1) sbl = Settings.screenHeight / 2;
boolean onBottom = Settings.unmovableSIP || getAbsoluteRect().y < sbl;
if (!Window.isSipShown)
{
Window.isSipShown = true;
Window.setSIP(onBottom ? Window.SIP_BOTTOM : Window.SIP_TOP, this, mode == PASSWORD || mode == PASSWORD_ALL); // if running on a PocketPC device, set the bounds of Sip in a way to not cover the edit
}
if (Settings.unmovableSIP) // guich@tc126_21
{
Window ww = getParentWindow();
if (ww != null)
ww.shiftScreen(this,0);
}
}
}
else
{
if (keyboard == null) keyboard = new KeyboardBox();
keyboard.tempTitle = keyboardTitle;
showInputWindow(keyboard);
}
return;
}
}
protected void hideSip()
{
if (Window.isSipShown) // non-default keyboards gets here
{
Window.isSipShown = false;
Window.setSIP(Window.SIP_HIDE,null,false);
}
}
private void showInputWindow(Window w)
{
oldTabIndex = parent.tabOrder.indexOf(this);
pushPosState();
if (removeTimer(blinkTimer)) // guich@200b4_167
blinkTimer = null;
w.popupNonBlocking();
popPosState();
requestFocus();
}
private void focusOut()
{
if (Settings.isWindowsDevice() && virtualKeyboard && editable && kbdType != KBD_NONE && Window.isSipShown) // guich@tc126_58: always try to close the sip
hideSip();
hasFocus = false;
clearPosState();
if (removeTimer(blinkTimer)) // guich@200b4_167
blinkTimer = null;
}
/** Called by the system to pass events to the edit control. */
public void onEvent(Event event)
{
if (calendar != null && event.type == ControlEvent.WINDOW_CLOSED && event.target == calendar) // called from the keyboard and from the calendar
{
Date d = calendar.getSelectedDate();
if (d != null)
setText(d.toString(),true);
else
if (!calendar.canceled)
setText("",true);
return;
}
boolean extendSelect = false;
boolean clearSelect = false;
boolean reapplyMask = false;
int len = chars.length();
if (len == 0) // guich@571_3: make sure the insert position is zero if there's no text.
insertPos = startSelectPos = 0;
int newInsertPos = insertPos;
switch (event.type)
{
case ControlEvent.CURSOR_CHANGED:
break;
case TimerEvent.TRIGGERED:
if (showKeyboardOnNextEvent)
{
event.consumed=true;
showKeyboardOnNextEvent = false;
popupKCC();
return;
}
if (event == blinkTimer) // kmeehl@tc100: make sure its our timer
{
if (!isTopMost()) // must check here and not in the onPaint method, otherwise it results in a problem: show an edit field, then popup a window and move it: the edit field of the other window is no longer being drawn
focusOut();
else
if (parent != null)
{
Window.needsPaint = true;
// guich@tc130: show the copy/paste menu
/* if (editable && enabled && lastPenDown != -1 && clipboardDelay != -1 && (Vm.getTimeStamp() - lastPenDown) >= clipboardDelay)
if (showClipboardMenu())
{
event.consumed = true;
break;
}
*/ }
event.consumed=true; //astein@230_5: prevent blinking cursor event from propagating
}
return;
case ControlEvent.FOCUS_IN:
isHighlighting = false; // guich@573_28: after closing a KCC, don't let the focus move from here.
wasFocusIn=true; // jairocg@450_31: set it so we can validate later
hasFocus = true;
if (blinkTimer == null)
blinkTimer = addTimer(350);
if (len > 0) // guich@550_20: autoselect the text
{
if (autoSelect && !ignoreSelect) // guich@570_112: changed to !ignoreSelect
{
startSelectPos = len;
newInsertPos = 0;
}
else
if (Settings.moveCursorToEndOnFocus)
newInsertPos = len;
}
break;
case ControlEvent.FOCUS_OUT:
if (cursorShowing)
Window.needsPaint = true; //draw(drawg=getGraphics(), true); // erase cursor at old insert position
newInsertPos = 0;
focusOut();
break;
case KeyEvent.KEY_PRESS:
case KeyEvent.SPECIAL_KEY_PRESS:
if (editable && isEnabled())
{
KeyEvent ke = (KeyEvent)event;
if (event.type == KeyEvent.SPECIAL_KEY_PRESS && ke.key == SpecialKeys.ESCAPE) event.consumed = true; // don't let the back key be passed to the parent
if (insertPos == 0 && ke.key == ' ' && (mode == CURRENCY || mode == DATE)) // guich@tc114_34
{
popupKCC();
break;
}
boolean moveFocus = !Settings.geographicalFocus && (ke.isActionKey() || ke.key == SpecialKeys.TAB);
if (event.target == this && moveFocus) // guich@tc100b2: move to the next edit in the same container
{
if (parent != null && parent.moveFocusToNextEditable(this, ke.modifiers == 0) != null)
return;
}
boolean loseFocus = moveFocus || ke.key == SpecialKeys.ESCAPE;
if (event.target == this && loseFocus)
{
//isHighlighting = true; // kmeehl@tc100: set isHighlighting first, so that Window.removeFocus() wont trample Window.highlighted
if (removeFocusOnAction)
{
Window w = getParentWindow(); // guich@tc114_32: restore the highlight to this control...
if (w != null) // guich@tc123_16
{
if (w.getFocus() == this)
w.removeFocus();
w.setHighlighted(this);
}
}
break;
}
// print state
if ((ke.key == SpecialKeys.KEYBOARD_ABC || ke.key == SpecialKeys.KEYBOARD_123) && popupsHidden()) // guich@102: pop's up the keyboard
{
popupKCC();
break;
}
boolean isControl = (ke.modifiers & SpecialKeys.CONTROL) != 0; // guich@320_46
if (Settings.onJavaSE) // guich@tc100b4_26: if the user pressed the command and then a key, assume is a control
{
if (lastCommand > 0 && (Vm.getTimeStamp() - lastCommand) < 2500)
{
ke.modifiers |= SpecialKeys.CONTROL;
isControl = true;
lastCommand = 0;
}
else
if (ke.key == SpecialKeys.COMMAND) // just a single COMMAND? break
{
showTip(this, Edit.commandStr, 2500, -1);
lastCommand = Vm.getTimeStamp();
break;
}
}
if ("".equals(validChars)) // guich@tc115_33
break;
boolean isDelete = (ke.key == SpecialKeys.DELETE);
boolean isBackspace = (ke.key == SpecialKeys.BACKSPACE);
boolean isPrintable = ke.key > 0 && event.type == KeyEvent.KEY_PRESS && (ke.modifiers & SpecialKeys.ALT) == 0 && (ke.modifiers & SpecialKeys.CONTROL) == 0;
int del1 = -1;
int del2 = -1;
int sel1 = startSelectPos;
int sel2 = insertPos;
if (sel1 > sel2)
{
int temp = sel1;
sel1 = sel2;
sel2 = temp;
}
// clipboard
if (isControl)
{
if (0 < ke.key && ke.key < 32) ke.key += 64;
ke.modifiers &= ~SpecialKeys.CONTROL; // remove control
int key = Convert.toUpperCase((char)ke.key);
switch (key)
{
case ' ': // guich@320_47
setText("");
return;
case 'X':
case 'C':
if (key == 'X') // cut
clipboardCut();
else
clipboardCopy();
return;
case 'P':
case 'V':
clipboardPaste();
return;
}
}
if (mapFrom != null) // guich@tc110_56
{
int idx = mapFrom.indexOf(Convert.toLowerCase((char)ke.key));
if (idx != -1)
ke.key = mapTo.charAt(idx);
}
if (isPrintable)
{
if (capitalise == ALL_UPPER)
ke.key = Convert.toUpperCase((char)ke.key);
else
if (capitalise == ALL_LOWER)
ke.key = Convert.toLowerCase((char)ke.key);
if (!isCharValid((char)ke.key)) // guich@101: tests if the key is in the valid char set - moved to here because a valid clipboard char can be an invalid edit char
break;
}
if (sel1 != -1 && (isPrintable || isDelete || isBackspace))
{
del1 = sel1;
del2 = sel2 - 1;
}
else if (isDelete)
{
del1 = insertPos;
del2 = insertPos;
}
else if (isBackspace)
{
del1 = insertPos - 1;
del2 = insertPos - 1;
}
if (del1 >= 0 && del2 < len)
{
if (cursorShowing)
Window.needsPaint = true; //draw(drawg == null ? (drawg = getGraphics()) : drawg, true); // erase cursor at old insert position
if (len > del2 - 1)
{
chars.delete(del1, del2+1);
reapplyMask = true;
}
newInsertPos = del1;
clearSelect = true;
}
if (isPrintable)
if (maxLength == 0 || len < maxLength || clearSelect) // guich@tc125_34
{
char c = (char)ke.key;
boolean append = true;
if (isMaskedEdit && masked.length() > 0) // put or remove '-' at the beginning of a string
{
char first = masked.charAt(0);
if (c == '+' || c == '-')
{
if (first == '-')
isNegative = false; // typed + and is negative (or neg of neg)?
else
if (c == '+')
break; // else, if its already positive, just ignore
else
isNegative = true;
append = false;
}
}
if (append)
if (newInsertPos >= chars.length())
chars.append(c);
else
Convert.insertAt(chars, newInsertPos, c);
reapplyMask = true;
newInsertPos++;
clearSelect = true;
}
boolean isMove = true;
switch (ke.key)
{
case SpecialKeys.HOME : newInsertPos = 0; break;
case SpecialKeys.END : newInsertPos = len; break;
case SpecialKeys.LEFT :
case SpecialKeys.UP : newInsertPos--; break;
case SpecialKeys.RIGHT :
case SpecialKeys.DOWN : newInsertPos++; break;
default: isMove = false;
}
if (isMove && newInsertPos != insertPos)
{
if ((ke.modifiers & SpecialKeys.SHIFT) > 0)
extendSelect = true;
else
clearSelect = true;
}
}
break;
case PenEvent.PEN_DOWN:
{
wasFocusInOnPenDown = wasFocusIn;
PenEvent pe = (PenEvent)event;
if (!autoSelect || !wasFocusIn) // jairocg@450_31: if the event was focusIn, do not change the selected text
{
for (newInsertPos = 0; newInsertPos < chars.length() && charPos2x(newInsertPos) < pe.x-3; newInsertPos++) {}
if ((pe.modifiers & SpecialKeys.SHIFT) > 0) // shift
extendSelect = true;
else
clearSelect = true;
} else wasFocusIn = false; // guich@570_98: let the user change cursor location after the first focus_in event.
break;
}
case PenEvent.PEN_DRAG:
{
PenEvent pe = (PenEvent)event;
for (newInsertPos = 0; newInsertPos < chars.length() && charPos2x(newInsertPos) <= pe.x; newInsertPos++) {}
if (newInsertPos != insertPos && isEnabled())
extendSelect = true;
break;
}
case PenEvent.PEN_UP:
if (kbdType != KBD_NONE && virtualKeyboard && !hadParentScrolled())
{
if (!autoSelect && clipboardDelay != -1 && startSelectPos != -1 && startSelectPos != insertPos)
showClipboardMenu();
else
if (wasFocusInOnPenDown || !Window.isScreenShifted())
popupKCC();
}
break;
case KeyboardBox.KEYBOARD_ON_UNPOP: // guich@320_34
pushPosState();
ignoreSelect = true; // guich@570_112: don't autoselect when keyboard is called
return;
case KeyboardBox.KEYBOARD_POST_UNPOP:
popPosState();
if (oldTabIndex != -1) // reinsert this control in the previous position
{
parent.tabOrder.removeElement(this);
parent.tabOrder.insertElementAt(this, oldTabIndex);
oldTabIndex = -1;
}
isHighlighting = false;
ignoreSelect = false; // guich@570_112: after the keyboard is definetively gone, its safe to set this to false.
wasFocusIn = false; // guich@570_112: fix first click on edit being ignored after keyboard is closed.
startSelectPos = -1;
return;
default:
return;
}
if (extendSelect)
{
if (startSelectPos == -1)
startSelectPos = insertPos;
else if (newInsertPos == startSelectPos)
startSelectPos = -1;
}
if (wasFocusIn && startSelectPos != -1 && insertPos>startSelectPos) // jairocg@450_31: event validation with text selection
wasFocusIn=false;
else
if (clearSelect && startSelectPos != -1)
startSelectPos = -1;
newInsertPos = Math.min(newInsertPos, chars.length());
if (newInsertPos < 0)
newInsertPos = 0;
boolean insertChanged = event.type == ControlEvent.CURSOR_CHANGED || (newInsertPos != insertPos);
if (reapplyMask && mask != null && (mode == CURRENCY || mode == DATE || mode == NORMAL))
applyMaskToInput();
if (insertChanged)
{
int x = charPos2x(newInsertPos);
if (cursorShowing)
Window.needsPaint = true;//draw(drawg == null ? (drawg = getGraphics()) : drawg, true); // erase cursor at old insert position
if (x - 3 < xMin)
{
// characters hidden on left - jump
xOffset += (xMin - x) + fmH;
if (xOffset > xMin)
xOffset = xMin;
}
int totalCharWidth = getTotalCharWidth();
if (x > xMax)
{
// characters hidden on right - jump
xOffset -= (x - xMax) + fmH;
int minOfs = xMax - totalCharWidth;
if (xOffset < minOfs)
xOffset = minOfs;
}
if (totalCharWidth < xMax - xMin && xOffset != xMin)
xOffset = xMin;
cursorX = x;
}
if (reapplyMask)
postPressedEvent(); // guich@tc113_1
insertPos = newInsertPos;
if (isTopMost()) // guich@tc124_24: prevent screen updates when we're not the topmost window
Window.needsPaint = true; // must repaint everything due to a possible background image
}
private boolean showClipboardMenu()
{
int idx = showClipboardMenu(this);
if (0 <= idx && idx <= 3)
{
if (idx != 3 && startSelectPos == -1) // if nothing was selected, select everything
{
startSelectPos = 0;
insertPos = chars.length();
}
if (idx == 0)
clipboardCut();
else
if (idx == 1)
clipboardCopy();
else
{
clipboardPaste();
return true;
}
}
return false;
}
private static class ClipboardMenuListener implements PressListener
{
public void controlPressed(ControlEvent e)
{
clipSel = clipboardMenu.selectedIndex;
}
}
static int clipSel = -2;
static int showClipboardMenu(Control host)
{
try
{
if (clipboardMenu == null)
{
String[] names = {cutStr,copyStr,replaceStr,pasteStr};
clipboardMenu = new PushButtonGroup(names, false, -1, 0,3,2,true,PushButtonGroup.BUTTON)
{
protected boolean willOpenKeyboard()
{
return true;
}
};
clipboardMenu.setFocusLess(true);
}
Container w = host.getParentWindow();
clipboardMenu.setSelectedIndex(-1);
Rect cli = host.getAbsoluteRect();
int ph = clipboardMenu.getPreferredHeight();
w.add(clipboardMenu,LEFT+2, host instanceof MultiEdit ? cli.y+cli.height-ph : (cli.y > w.height/2 ? cli.y-ph : cli.y+cli.height), PREFERRED+4,PREFERRED+4,host);
clipboardMenu.setBackForeColors(UIColors.clipboardBack,UIColors.clipboardFore);
clipboardMenu.bringToFront();
w.repaintNow();
clipSel = -2;
PressListener pl;
clipboardMenu.addPressListener(pl = new ClipboardMenuListener());
int end = Vm.getTimeStamp() + 3000; // make sure we will elapse only 3 seconds
while (Vm.getTimeStamp() < end)
{
Vm.sleep(10);
if (Event.isAvailable())
{
Window.pumpEvents();
if (clipSel != -2)
break;
}
}
clipboardMenu.removePressListener(pl);
w.remove(clipboardMenu);
return clipSel;
}
catch (Exception e)
{
if (Settings.onJavaSE) e.printStackTrace();
}
return -1;
}
public static boolean popupsHidden()
{
return (keyboard == null || !keyboard.isVisible()) &&
(calendar == null || !calendar.isVisible()) &&
(calculator == null || !calculator.isVisible()) &&
(time == null || !time.isVisible()) &&
(numeric == null || !numeric.isVisible());
}
protected void onWindowPaintFinished()
{
if (!hasFocus) _onEvent(new ControlEvent(ControlEvent.FOCUS_IN,this)); // this event is called on the focused control of the parent window. so, if we are not in FOCUS state, set it now. - guich@tc112_
}
/** Called by the system to draw the edit control. */
public void onPaint(Graphics g)
{
draw(g);
}
/** Returns the length of the text.
* @since SuperWaba 4.21
*/
public int getLength()
{
return chars.length();
}
/** Returns the length of the text after applying a trim to it.
* This method consumes less memory than <code>getText().trim().length()</code>.
* @since TotalCross 1.3
*/
public int getTrimmedLength()
{
StringBuffer sb = isMaskedEdit ? masked : chars;
int l = sb.length();
int s = 0;
while (s < l && sb.charAt(s) <= ' ')
s++;
while (l > s && sb.charAt(l-1) <= ' ')
l--;
return l-s;
}
/** Clears this control, settings the text to clearValueStr. Note that if the Edit
* is not editable, you will have to explicitly call the clear method of this Edit. */
public void clear() // guich@572_19
{
setText(clearValueStr,Settings.sendPressEventOnChange);
}
public Control handleGeographicalFocusChangeKeys(KeyEvent ke) // kmeehl@tc100
{
if (ke.isUpKey() || ke.isDownKey() || (ke.isNextKey() && insertPos == chars.length()) || (ke.isPrevKey() && insertPos == 0))
return null;
_onEvent(ke);
return this;
}
/** Copies the text to the clipboard.
* If there's a selected text, copies the portion selected, otherwise, copies the whole text.
* @since TotalCross 1.14
*/
public void clipboardCopy() // guich@tc114_66
{
int sel1 = startSelectPos;
int sel2 = insertPos;
if (sel1 > sel2)
{
int temp = sel1;
sel1 = sel2;
sel2 = temp;
}
try
{
String s = chars.toString();
Vm.clipboardCopy(sel1 != -1 ? s.substring(sel1,sel2) : s);
showTip(this, copyStr, 500, -1);
}
catch (Exception e) {/* just ignore */}
}
/** Cuts the selected text to the clipboard.
* @since TotalCross 1.14
*/
public void clipboardCut() // guich@tc114_66
{
int sel1 = startSelectPos;
int sel2 = insertPos;
if (sel1 > sel2)
{
int temp = sel1;
sel1 = sel2;
sel2 = temp;
}
if (sel1 != -1) // cut/copy
{
Vm.clipboardCopy(chars.toString().substring(sel1,sel2)); // brunosoares@tc100: Changed from chars.substring to chars.toString().substring
showTip(this, cutStr, 500, -1);
// simulate a backspace to erase the selected text
KeyEvent ke = new KeyEvent();
ke.type = KeyEvent.SPECIAL_KEY_PRESS;
ke.key = SpecialKeys.BACKSPACE;
_onEvent(ke);
}
}
/** Paste from the clipboard into the Edit at the current insert position.
* @since TotalCross 1.14
*/
public void clipboardPaste() // guich@tc114_66
{
String pasted = Vm.clipboardPaste();
if (pasted == null || pasted.length() == 0)
;
else
{
showTip(this, pasteStr, 500, -1);
KeyEvent ke = new KeyEvent();
if (startSelectPos != insertPos) // if a text is selected, replace the value
{
ke.type = SpecialKeys.BACKSPACE;
_onEvent(ke);
}
ke.type = KeyEvent.KEY_PRESS;
int n = pasted.length();
for (int i =0; i < n; i++)
{
ke.key = pasted.charAt(i);
_onEvent(ke);
}
try {setCursorPos(insertPos+n, insertPos+n);} catch (Exception e) {}
}
}
/** Returns a copy of this Edit with almost all features. Used by Keyboard and SIPBox classes.
* @since TotalCross 1.27
*/
public Edit getCopy()
{
Edit ed = mask == null ? new Edit() : new Edit(new String(mask));
ed.setDecimalPlaces(decimalPlaces);
ed.setMode(mode,isMaskedEdit);
ed.startSelectPos = startSelectPos;
ed.insertPos = insertPos;
ed.setBackForeColors(backColor,foreColor);
if (validChars != null)
ed.setValidChars(validChars);
ed.capitalise = capitalise;
ed.alignment = alignment;
ed.maxLength = maxLength;
ed.autoSelect = autoSelect;
ed.kbdType = kbdType;
ed.editable = editable;
return ed;
}
protected boolean willOpenKeyboard()
{
return editable && (kbdType == KBD_DEFAULT || kbdType == KBD_KEYBOARD);
}
}