/* * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores * CA 94065 USA or visit www.oracle.com if you need additional information or * have any questions. */ package com.sun.lwuit; import com.sun.lwuit.util.EventDispatcher; import com.sun.lwuit.events.ActionEvent; import com.sun.lwuit.events.DataChangedListener; import com.sun.lwuit.geom.Dimension; import com.sun.lwuit.geom.Rectangle; import com.sun.lwuit.layouts.GridLayout; import com.sun.lwuit.plaf.UIManager; import com.sun.lwuit.plaf.Style; import java.util.Hashtable; import java.util.Vector; /** * Allows in place editing using a lightweight API without necessarily moving to * the external native text box. The main drawback in this approach is that editing * can't support features such as T9 and might not have the same keymapping or * behavior of the native text input. * <p>Notice that due to limitations of text area and text field input modes in * text area aren't properly supported since they won't work properly across devices. * To limit input modes please use the setInputModeOrder method. All constants * declated in TextArea are ignored with the exception of PASSWORD. * * @author Shai Almog */ public class TextField extends TextArea { private static boolean replaceMenuDefault = true; /** * Indicates that native text input should be used in text field when in place editing is supported by the platform * @return the useNativeTextInput */ public static boolean isUseNativeTextInput() { return useNativeTextInput; } /** * Indicates that native text input should be used in text field when in place editing is supported by the platform * @param aUseNativeTextInput the useNativeTextInput to set */ public static void setUseNativeTextInput(boolean aUseNativeTextInput) { useNativeTextInput = aUseNativeTextInput; } private long cursorBlinkTime = System.currentTimeMillis(); private boolean drawCursor = true; private int cursorY = 0; private int cursorX = -1;; private boolean pressedAndNotReleased; private long pressTime; private boolean useSoftkeys = true; private long releaseTime; private String previousText; private int commitTimeout = 1000; private boolean pendingCommit; private int pressCount = 0; private int lastKeyCode; private int pressedKeyCode; private static String clearText = "Clear"; private static String t9Text = "T9"; private boolean longClick; private Command originalClearCommand; private static Hashtable inputModes; private static String[] defaultInputModeOrder = {"Abc", "ABC", "abc", "123"}; private String inputMode = defaultInputModeOrder[0]; private String[] inputModeOrder = defaultInputModeOrder; private static Vector firstUppercaseInputMode = new Vector(); private int blinkOnTime = 800; private int blinkOffTime = 200; private static boolean qwertyAutoDetect = true; private boolean qwertyInitialized; private static boolean qwertyDevice; private boolean qwerty = qwertyDevice; private boolean replaceMenu = replaceMenuDefault; private Command[] originalCommands; private EventDispatcher listeners = new EventDispatcher(); private boolean overwriteMode; private boolean enableInputScroll = true; private int keyFwd = Display.GAME_RIGHT; private int keyBack = Display.GAME_LEFT; /** * Indicates whether the left/right keys will trigger editing, this is true by default. * Left and right key edit trigger might be disabled for cases such as text field * positioned horizontally one next to the other. */ private boolean leftAndRightEditingTrigger = true; /** * Key to change the input mode on the device */ private static int defaultChangeInputModeKey = '#'; /** * The default key for poping open the symbol dialog */ private static int defaultSymbolDialogKey = '*'; private Command selectCommand; private static boolean useNativeTextInput = true; /** * Set the text that should appear on the clear softkey * * @param text localized text for the clear softbutton */ public static void setClearText(String text) { clearText = text; } /** * Set the text that should appear on the T9 softkey * * @param text text for the T9 softbutton */ public static void setT9Text(String text) { t9Text = text; } /** * @inheritDoc */ public boolean isEnableInputScroll() { return enableInputScroll; } /** * Indicates whether text field input should scroll to the right side when no * more room for the input is present. * * @param enableInputScroll true to enable scrolling to the side */ public void setEnableInputScroll(boolean enableInputScroll) { this.enableInputScroll = enableInputScroll; } class CommandHandler extends Command { CommandHandler(String title, int id) { super(title, id); } public void actionPerformed(ActionEvent ev) { ev.consume(); switch(getId()) { case 1: // key press for activating clear causes keyRelease never to be // called triggering a long click, this code disables the long click releaseTime = System.currentTimeMillis(); pressedAndNotReleased = false; longClick = false; deleteChar(); break; case 2: ev.consume(); editString(); break; } } } private Command DELETE_COMMAND = new CommandHandler(clearText, 1); private Command T9_COMMAND = new CommandHandler(t9Text, 2); private static final char[] DEFAULT_SYMBOL_TABLE = new char[] { '.', ',', '?', '!', '$', '@', '\'', '-', '_', ')', '(', ':', ';', '&', '/', '~', '\\', '%', '*', '#', '+', '>', '=', '<', '"' }; private static char[] symbolTable = DEFAULT_SYMBOL_TABLE; private static final String[] DEFAULT_KEY_CODES = { // 0 " 0", // 1 ".,?!'\"1-()@/:_", // 2 "ABC2", // 3 "DEF3", // 4 "GHI4", // 5 "JKL5", // 6 "MNO6", // 7 "PQRS7", // 8 "TUV8", // 9 "WXYZ9", }; /** * Default constructor */ public TextField() { super(1, 20); setUIID("TextField"); setSingleLineTextArea(true); } /** * Construct a text field with space reserved for columns * * @param columns - the number of columns */ public TextField(int columns) { super(1, columns); setUIID("TextField"); setSingleLineTextArea(true); } /** * Construct text field * * @param text the text of the field */ public TextField(String text) { super(text, 1, 20); setUIID("TextField"); setSingleLineTextArea(true); } /** * Performs a backspace operation */ public void deleteChar() { String text = getText(); if(text.length() > 0) { int cursorCharPosition = getCursorPosition(); if(cursorCharPosition > 0) { int tmpCursorCharPosition = cursorCharPosition - 1; if(!overwriteMode) { if(tmpCursorCharPosition == text.length()){ text = text.substring(0, text.length() - 1); }else{ text = text.substring(0, tmpCursorCharPosition) + text.substring(tmpCursorCharPosition + 1, text.length()); } super.setText(text); commitChange(); fireDataChanged(DataChangedListener.REMOVED, tmpCursorCharPosition); } if(cursorCharPosition == 1){ cursorX = 0; cursorCharPosition = 0; }else{ setCursorPosition(tmpCursorCharPosition); } } } } /** * Construct text field * * @param text the text of the field * @param columns - the number of columns */ public TextField(String text, int columns) { super(text, 1, columns); setSingleLineTextArea(true); } /** * Construct text field/area depending on whether native in place editing is supported * * @param text the text of the field * @param columns - the number of columns * @return a text field if native in place editing is unsupported and a text area if it is */ public static TextArea create(String text, int columns) { if(Display.getInstance().getImplementation().isNativeInputSupported()) { return new TextArea(text, 1, columns); } return new TextField(text, columns); } /** * Default factory method * * @return a text field if native in place editing is unsupported and a text area if it is */ public static TextArea create() { return create(20); } /** * Construct text field/area depending on whether native in place editing is supported * * @param columns - the number of columns * @return a text field if native in place editing is unsupported and a text area if it is */ public static TextArea create(int columns) { return create("", columns); } /** * Construct text field/area depending on whether native in place editing is supported * * @param text the text of the field * @return a text field if native in place editing is unsupported and a text area if it is */ public static TextArea create(String text) { return create(text, 20); } /** * Commit the changes made to the text field as a complete edit operation. This * is used in a numeric keypad to allow the user to repeatedly press a number * to change values. */ protected void commitChange() { pendingCommit = false; previousText = null; pressCount = 0; } /** * @inheritDoc */ public boolean isPendingCommit() { return pendingCommit; } /** * The amount of time in milliseconds it will take for a change to get committed into * the field. * * @param commitTimeout indicates the amount of time that should elapse for a commit * to automatically occur */ public void setCommitTimeout(int commitTimeout) { this.commitTimeout = commitTimeout; } /** * The amount of time in milliseconds it will take for a change to get committed into * the field. * * @return the time for a commit timeout */ public int getCommitTimeout() { return commitTimeout; } /** * Sets the current selected input mode matching one of the existing input * modes * * @param inputMode the display name of the input mode by default the following modes * are supported: Abc, ABC, abc, 123 */ public void setInputMode(String inputMode) { this.inputMode = inputMode; repaint(); } /** * @inheritDoc */ public String getInputMode() { return inputMode; } /** * Indicates whether the key changes the current input mode * * @param keyCode the code * @return true for the hash (#) key code */ protected boolean isChangeInputMode(int keyCode) { return keyCode == defaultChangeInputModeKey; } private static void initInputModes() { if(inputModes == null) { firstUppercaseInputMode.addElement("Abc"); inputModes = new Hashtable(); Hashtable upcase = new Hashtable(); for(int iter = 0 ; iter < DEFAULT_KEY_CODES.length ; iter++) { upcase.put(new Integer('0' + iter), DEFAULT_KEY_CODES[iter]); } inputModes.put("ABC", upcase); Hashtable lowcase = new Hashtable(); for(int iter = 0 ; iter < DEFAULT_KEY_CODES.length ; iter++) { lowcase.put(new Integer('0' + iter), DEFAULT_KEY_CODES[iter].toLowerCase()); } inputModes.put("abc", lowcase); Hashtable numbers = new Hashtable(); for(int iter = 0 ; iter < 10 ; iter++) { numbers.put(new Integer('0' + iter), "" + iter); } inputModes.put("123", numbers); } } /** * Adds a new inputmode hashtable with the given name and set of values * * @param name a unique display name for the input mode e.g. ABC, 123 etc... * @param values The key for the hashtable is an Integer keyCode and the value * is a String containing the characters to toggle between for the given keycode * @param firstUpcase indicates if this input mode in an input mode used for the special * case where the first letter is an upper case letter */ public static void addInputMode(String name, Hashtable values, boolean firstUpcase) { initInputModes(); inputModes.put(name, values); if(firstUpcase) { firstUppercaseInputMode.addElement(name); } } /** * @inheritDoc */ public String[] getInputModeOrder() { return inputModeOrder; } /** * Sets the order in which input modes are toggled and allows disabling/hiding * an input mode * * @param order the order for the input modes in this field */ public void setInputModeOrder(String[] order) { inputModeOrder = order; inputMode = order[0]; } /** * Returns the order in which input modes are toggled by default * * @return the default order of the input mode */ public static String[] getDefaultInputModeOrder() { return defaultInputModeOrder; } /** * Sets the order in which input modes are toggled by default and allows * disabling/hiding an input mode * * @param order the order for the input modes in all future created fields */ public static void setDefaultInputModeOrder(String[] order) { defaultInputModeOrder = order; } /** * Used for the case of first sentence character should be upper case */ private String pickLowerOrUpper(String inputMode) { // check the character before the cursor.. int pos = getCursorPosition() - 1; // we have input which has moved the cursor position further if(pendingCommit) { pos--; } String text = getText(); if(pos >= text.length()) { pos = text.length() - 1; } while(pos > -1) { if(text.charAt(pos) == '.') { return inputMode.toUpperCase(); } if(text.charAt(pos) != ' ') { return inputMode.toLowerCase(); } pos--; } return inputMode.toUpperCase(); } /** * Returns the input mode for the ong click mode * * @return returns 123 by default */ protected String getLongClickInputMode() { return "123"; } /** * Returns the character matching the given key code after the given amount * of user presses * * @param pressCount number of times this keycode was pressed * @param keyCode the actual keycode input by the user * @param longClick does this click constitute a long click * @return the char mapping to this key or 0 if no appropriate char was found * (navigation, input mode change etc...). */ protected char getCharPerKeyCode(int pressCount, int keyCode, boolean longClick) { initInputModes(); String input = inputMode; // if this is a first letter uppercase input mode then we need to pick either // the upper case mode or the lower case mode... if(longClick) { input = getLongClickInputMode(); } else { if(firstUppercaseInputMode.contains(input)) { input = pickLowerOrUpper(input); } } Hashtable mode = (Hashtable)inputModes.get(input); if(mode != null) { String s = (String)mode.get(new Integer(keyCode)); if(s != null) { pressCount = pressCount % s.length(); return s.charAt(pressCount); } } return 0; } /** * Blocks the text area from opening the native text box editing on touchscreen click */ void onClick() { if(useNativeTextInput && Display.getInstance().isNativeInputSupported()) { super.onClick(); } } /** * Sets the position of the cursor char position * * @param pos the cursor position */ public void setCursorPosition(int pos) { if(pos < -1) { throw new IllegalArgumentException("Illegal cursor position: " + pos); } pos = Math.min(pos, getText().length()); pos = Math.max(0, pos); updateCursorLocation(pos); } private void increaseCursor(){ cursorX++; String txt = getCurrentTextLine(); if(cursorX > txt.length() || cursorY >= getLines()){ if(isSingleLineTextArea()){ cursorX = 0; return; } cursorY++; if(cursorY > getLines()){ cursorY = 0; if(!isCursorPositionCycle()) { cursorY = getLines(); cursorX = txt.length(); } }else{ cursorX = 0; } } updateScroll(); } private void decreaseCursor(){ cursorX--; if(cursorX < 0 || cursorY >= getLines()){ if(isSingleLineTextArea()){ cursorX = getCurrentTextLine().length(); return; } cursorY--; if(cursorY < 0){ cursorY = getLines() - 1; if(!isCursorPositionCycle()) { cursorY = 0; cursorX = 0; } }else{ cursorX = getTextAt(cursorY).length(); } } updateScroll(); } private void increaseCursorLine() { cursorY++; if (cursorY >= getLines()) { if (isCursorPositionCycle()) { cursorY = 0; }else{ cursorY--; } } String line = getTextAt(cursorY); if (cursorX > line.length()) { cursorX = line.length(); } updateScroll(); } private void decreaseCursorLine(){ cursorY--; if (cursorY < 0) { if (isCursorPositionCycle()) { cursorY = getLines() - 1; }else{ cursorY++; } } String line = getTextAt(cursorY); if (cursorX > line.length()) { cursorX = line.length(); } updateScroll(); } private void updateScroll() { if(!isSingleLineTextArea() && isScrollableY()){ Font textFont = getStyle().getFont(); int rowsGap = getRowsGap(); int lineHeight = textFont.getHeight() + rowsGap; Rectangle rect = new Rectangle(getScrollX(), getCursorY()*lineHeight, getWidth(), lineHeight); scrollRectToVisible(rect, this); } } private void updateCursorLocation(int pos){ if(isSingleLineTextArea()){ cursorX = pos; return; } String text = getText(); String currentLine = ""; int currentLineLength = 0; int numOfChars = 0; int currentY = 0; while( numOfChars <= pos && currentY < getLines()){ currentLine = getTextAt(currentY); currentLineLength = currentLine.length(); if(numOfChars + currentLineLength < text.length() && (text.charAt(numOfChars + currentLineLength) == '\n' || text.charAt(numOfChars + currentLineLength) == ' ')){ currentLineLength++; } numOfChars += currentLineLength; currentY++; } cursorY = Math.max(0, currentY-1); cursorX = currentLineLength - (numOfChars - pos); if(cursorX == currentLineLength && currentLineLength > currentLine.length()){ cursorX--; } } private String getCurrentTextLine() { if (isSingleLineTextArea()) { return getText(); } return getTextAt(getCursorY()); } private void moveCursor(int game) { if (handlesInput()) { if (game == keyFwd) { increaseCursor(); repaint(); } else if (game == keyBack) { decreaseCursor(); repaint(); } if (!isSingleLineTextArea()) { if (game == Display.GAME_UP) { decreaseCursorLine(); repaint(); } else if (game == Display.GAME_DOWN) { increaseCursorLine(); repaint(); } } } } /** * @inheritDoc */ public int getCursorPosition() { String txt = getText(); if(txt == null || txt.length() == 0) { updateCursorLocation(0); return 0; } if(cursorX == -1){ updateCursorLocation(getText().length()); } if(isSingleLineTextArea()){ return getCursorX(); } int pos = 0; int line = 0; while(line < cursorY && line < getLines()){ String l = getTextAt(line); pos += l.length(); if(pos < txt.length() && (txt.charAt(pos) == '\n' || txt.charAt(pos) == ' ') ){ pos++; } line++; } return Math.min(getText().length(), pos + cursorX); } /** * @inheritDoc */ public int getCursorY() { return cursorY; } /** * @inheritDoc */ public int getCursorX() { return cursorX; } /** * @inheritDoc */ public void setText(String text) { super.setText(text); fireDataChanged(DataChangedListener.CHANGED, -1); int pos = getCursorPosition(); if(pos < 0) { pos = text.length(); } else { if(pos > text.length()) { pos = text.length(); } } setCursorPosition(pos); } /** * Cleares the text from the TextField */ public void clear(){ setText(""); commitChange(); } /** * Invoked on a long click by the user */ private void longClick(int keyCode) { longClick = true; keyReleaseOrLongClick(keyCode, true); moveCursor(Display.getInstance().getGameAction(keyCode)); } /** * Returns true if this is the clear key on the device, many devices don't contain * a clear key and even in those that contain it this might be an issue * * @param keyCode the key code that might be the clear key * @return true if this is the clear key. */ protected boolean isClearKey(int keyCode) { return keyCode == MenuBar.clearSK || keyCode == MenuBar.backspaceSK; } /** * @inheritDoc */ protected void longKeyPress(int keyCode) { if(isClearKey(keyCode)){ setText(""); } } /** * @inheritDoc */ public boolean isQwertyInput() { if(!qwertyInitialized) { qwertyInitialized = true; int type = Display.getInstance().getKeyboardType(); if(type == Display.KEYBOARD_TYPE_UNKNOWN) { qwerty = qwertyDevice; } else { qwerty = type == Display.KEYBOARD_TYPE_QWERTY || type == Display.KEYBOARD_TYPE_VIRTUAL; if(!qwertyDevice) { qwertyDevice = qwerty; } } } return qwerty; } /** * True is this is a qwerty device or a device that is currently in * qwerty mode. * * @param qwerty the value of qwerty mode */ public void setQwertyInput(boolean qwerty) { this.qwerty = qwerty; } /** * Returns true if the given input mode should commit immediately or * wait for the commit timeout * * @param mode the input mode * @return returns true for input mode 123 by default */ protected boolean isImmediateInputMode(String mode) { return "123".equals(mode); } private boolean keyReleaseOrLongClick(int keyCode, boolean longClick) { // user pressed a different key, autocommit everything if(lastKeyCode != keyCode && pendingCommit) { commitChange(); } lastKeyCode = keyCode; boolean isClearKey = isClearKey(keyCode); int pos = getCursorPosition(); if(isQwertyInput() && !isClearKey) { if(keyCode > 0) { if(previousText == null) { previousText = getText(); } if(pos < 0) { pos = 0; setCursorPosition(pos); } insertChars("" + (char)keyCode); commitChange(); fireDataChanged(DataChangedListener.ADDED, pos); return true; } } else { char c = getCharPerKeyCode(pressCount, keyCode, longClick); if (c != 0) { String text; if(previousText == null) { previousText = getText(); } if(!pendingCommit) { insertChars("" + c); pendingCommit = true; pressCount++; } else { if(overwriteMode) { //neutralize incrementation within *insertChars* decreaseCursor(); //overwrite the character of previous keystroke insertChars("" + c); pressCount++; } else { if(pos > 0 && pos < getMaxSize() && previousText.length() < getMaxSize() && validChar("" + c)){ text = previousText.substring(0, pos - 1) + c + previousText.substring(pos - 1, previousText.length()); pendingCommit = true; pressCount++; super.setText(text); } } } if(isImmediateInputMode(inputMode)) { commitChange(); fireDataChanged(DataChangedListener.ADDED, pos); } else { if(pressCount == 1) { fireDataChanged(DataChangedListener.ADDED, pos); } else { fireDataChanged(DataChangedListener.CHANGED, pos); } } return true; } } if(isChangeInputMode(keyCode)) { for(int iter = 0 ; iter < inputModeOrder.length ; iter++) { if(inputModeOrder[iter].equals(inputMode)) { iter++; if(iter < inputModeOrder.length) { setInputMode(inputModeOrder[iter]); } else { setInputMode(inputModeOrder[0]); } return true; } } return true; } if(isClearKey) { if(longClick) { // long click on the clear key should erase the field entirely setText(""); commitChange(); } else { deleteChar(); } return true; } if(isSymbolDialogKey(keyCode)) { showSymbolDialog(); return true; } return false; } /** * This method is responsible for adding a character into the field and is * the focal point for all input. It can be overriden to prevent a particular * char from insertion or provide a different behavior for char insertion. * It is the responsibility of this method to shift the cursor and invoke * setText... * <p>This method accepts a string for the more elaborate cases such as multi-char * input and paste. * * @param c character for insertion */ public void insertChars(String c) { String currentText = getText(); //if the contraint is numeric only, don't insert a char that isn't //numeric if(!validChar(c)){ return; } int pos = getCursorPosition() + 1; if(overwriteMode && pos <= currentText.length()) { setText(currentText.substring(0, pos - 1) + c + currentText.substring(pos, currentText.length())); } else { if(currentText.length() + c.length() > getMaxSize()) { return; } setText(currentText.substring(0, pos - 1) + c + currentText.substring(pos - 1, currentText.length())); } if (c.length() > 1) { pos += c.length() - 1; //update cursor to the end. setCursorPosition(pos); return; } increaseCursor(); } /** * Checks if the candidate input is valid for this TextField * @param c the String to insert * * @return true if the String is valid */ public boolean validChar(String c){ if (getConstraint() == TextArea.NUMERIC) { if (c.charAt(0) < '0' || c.charAt(0) > '9') { return false; } } else if (getConstraint() == TextArea.PHONENUMBER) { if ((c.charAt(0) < '0' || c.charAt(0) > '9') && c.charAt(0) != '+') { return false; } } else if (getConstraint() == TextArea.DECIMAL) { if ((c.charAt(0) < '0' || c.charAt(0) > '9') && c.charAt(0) != '+' && c.charAt(0) != '-' && c.charAt(0) != '.') { return false; } } return true; } /** * Invoked to show the symbol dialog, this method can be overriden by subclasses to * manipulate the symbol table */ protected void showSymbolDialog() { Command cancel = new Command(UIManager.getInstance().localize("cancel", "Cancel")); Command r = Dialog.show(null, createSymbolTable(), new Command[] {cancel}); if(r != null && r != cancel) { insertChars(r.getCommandName()); } } /** * Creates a symbol table container used by the showSymbolDialog method. * This method is designed for subclases to override and customize. * * @return container for the symbol table. */ protected Container createSymbolTable() { char[] symbolArray = getSymbolTable(); Container symbols = new Container(new GridLayout(symbolArray.length / 5, 5)); for(int iter = 0 ; iter < symbolArray.length ; iter++) { Button button = new Button(new Command("" + symbolArray[iter])); button.setAlignment(CENTER); symbols.addComponent(button); } return symbols; } /** * @inheritDoc */ public void keyReleased(int keyCode) { if(useNativeTextInput && Display.getInstance().isNativeInputSupported()) { super.keyReleased(keyCode); return; } if(!isEditable()) { return; } pressedAndNotReleased = false; releaseTime = System.currentTimeMillis(); if(!longClick) { if(keyReleaseOrLongClick(keyCode, false)) { return; } } longClick = false; super.keyReleased(keyCode); } /** * The amount of time considered as a "long click" causing the long click method * to be invoked. * * @return currently defaults to 800 */ protected int getLongClickDuration() { return 800; } /** * Returns the symbol table for the device * * @return the symbol table of the device for the symbol table input */ public static char[] getSymbolTable() { return symbolTable; } /** * Sets the symbol table to show when the user clicks the symbol table key * * @param table the symbol table of the device for the symbol table input */ public static void setSymbolTable(char[] table) { symbolTable = table;; } /** * Returns true if the cursor should cycle to the beginning of the text when the * user navigates beyond the edge of the text and visa versa. * @return true by default */ protected boolean isCursorPositionCycle() { return true; } /** * Returns true if this keycode is the one mapping to the symbol dialog popup * * @param keyCode the keycode to check * @return true if this is the star symbol * */ protected boolean isSymbolDialogKey(int keyCode) { return keyCode == defaultSymbolDialogKey; } /** * @inheritDoc */ protected void deinitialize() { getComponentForm().deregisterAnimated(this); // if the text field is removed without restoring the commands we need to restore them if(handlesInput()) { if(useSoftkeys) { removeCommands(DELETE_COMMAND, T9_COMMAND, originalClearCommand); } else { Form f = getComponentForm(); if(f != null) { f.setClearCommand(originalClearCommand); } originalClearCommand = null; } pressedAndNotReleased = false; setHandlesInput(false); } } /** * @inheritDoc */ public void setEditable(boolean b) { super.setEditable(b); if(!b && handlesInput()) { setHandlesInput(false); if(useSoftkeys) { removeCommands(DELETE_COMMAND, T9_COMMAND, originalClearCommand); } else { Form f = getComponentForm(); f.setClearCommand(originalClearCommand); originalClearCommand = null; } pressedAndNotReleased = false; } } /** * @inheritDoc */ public void keyRepeated(int keyCode) { if(useNativeTextInput && Display.getInstance().isNativeInputSupported()) { super.keyRepeated(keyCode); return; } // the default implementation interfears with long press if(isQwertyInput()) { super.keyRepeated(keyCode); } } /** * @inheritDoc */ public void keyPressed(int keyCode) { if(useNativeTextInput && Display.getInstance().isNativeInputSupported()) { super.keyPressed(keyCode); return; } if(!isEditable()) { return; } pressedAndNotReleased = true; pressedKeyCode = keyCode; pressTime = System.currentTimeMillis(); // try to autodetect a qwerty device if(qwertyAutoDetect) { if((!qwerty) && ((keyCode >= 'a' && keyCode <= 'z') || (keyCode >= 'A' && keyCode <= 'Z'))) { qwertyDevice = true; qwerty = true; } } if((!handlesInput()) && isEditingTrigger(keyCode)) { setHandlesInput(true); if(useSoftkeys) { T9_COMMAND.setDisposesDialog(false); DELETE_COMMAND.setDisposesDialog(false); originalClearCommand = installCommands(DELETE_COMMAND, T9_COMMAND); } else { Form f = getComponentForm(); if(f != null) { originalClearCommand = f.getClearCommand(); f.setClearCommand(DELETE_COMMAND); } } return; } moveCursor(Display.getInstance().getGameAction(keyCode)); if(handlesInput() && isEditingEndTrigger(keyCode)) { setHandlesInput(false); if(useSoftkeys) { removeCommands(DELETE_COMMAND, T9_COMMAND, originalClearCommand); } else { Form f = getComponentForm(); if(f != null) { f.setClearCommand(originalClearCommand); originalClearCommand = null; } } fireActionEvent(); return; } else { if(handlesInput()) { return; } } super.keyPressed(keyCode); } /** * Installs the clear and t9 commands onto the parent form, this method can * be overriden to provide device specific placement for these commands * * @param clear the clear command * @param t9 the t9 command * @return clear command already installed in the form if applicable, none if no * clear command was installed before or not applicable. */ protected Command installCommands(Command clear, Command t9) { Form f = getComponentForm(); if(f != null) { Command original = f.getClearCommand(); if(original instanceof CommandHandler) { original = originalClearCommand; } if(replaceMenu && originalCommands == null) { originalCommands = new Command[f.getCommandCount()]; for(int iter = 0 ; iter < originalCommands.length ; iter++) { originalCommands[iter] = f.getCommand(iter); } f.removeAllCommands(); if(Display.getInstance().isThirdSoftButton()) { getComponentForm().getMenuBar().addSelectCommand(getSelectCommandText()); selectCommand = getComponentForm().getCommand(0); } } f.addCommand(clear, 0); f.addCommand(t9, 0); f.setClearCommand(clear); return original; } return null; } /** * @inheritDoc */ protected boolean isSelectableInteraction() { return true; } /** * @inheritDoc */ protected void fireClicked() { if(useNativeTextInput && Display.getInstance().isNativeInputSupported()) { super.fireClicked(); return; } if((!handlesInput())) { setHandlesInput(true); if(useSoftkeys) { T9_COMMAND.setDisposesDialog(false); DELETE_COMMAND.setDisposesDialog(false); originalClearCommand = installCommands(DELETE_COMMAND, T9_COMMAND); } else { Form f = getComponentForm(); originalClearCommand = f.getClearCommand(); f.setClearCommand(DELETE_COMMAND); } return; } if(handlesInput()) { setHandlesInput(false); if(useSoftkeys) { removeCommands(DELETE_COMMAND, T9_COMMAND, originalClearCommand); } else { Form f = getComponentForm(); f.setClearCommand(originalClearCommand); originalClearCommand = null; } fireActionEvent(); return; } else { if(handlesInput()) { return; } } } /** * Removes the clear and t9 commands from the parent form, this method can * be overriden to provide device specific placement for these commands * * @param clear the clear command * @param t9 the t9 command * @param originalClear the command originally assigned as the clear command (or null if no command was assigned before) */ protected void removeCommands(Command clear, Command t9, Command originalClear) { Form f = getComponentForm(); if(f != null) { f.removeCommand(clear); f.removeCommand(t9); if(selectCommand != null) { f.removeCommand(selectCommand); } f.setClearCommand(originalClearCommand); if(replaceMenu && originalCommands != null) { for(int iter = originalCommands.length - 1 ; iter >= 0 ; iter--) { f.addCommand(originalCommands[iter]); } originalCommands = null; } } } void focusLostInternal() { // we don't call super to avoid the text area blocking the scrolling stopComponentLableTicker(); if(handlesInput() || pressedAndNotReleased || pendingCommit) { setHandlesInput(false); Form f = getComponentForm(); if(f != null) { if (useSoftkeys) { removeCommands(DELETE_COMMAND, T9_COMMAND, originalClearCommand); } else { f.setClearCommand(originalClearCommand); } } releaseTime = System.currentTimeMillis(); commitChange(); pressedAndNotReleased = false; longClick = false; } Form f = getComponentForm(); if(f != null) { // prevent the VKB folding in case we are moving from one text component to another if(f.getFocused() instanceof TextField) { return; } if(f.getFocused() instanceof TextArea && Display.getInstance().isNativeInputSupported()) { return; } Display.getInstance().setShowVirtualKeyboard(false); } } void focusGainedInternal() { startComponentLableTicker(); releaseTime = System.currentTimeMillis(); pressedAndNotReleased = false; longClick = false; } /** * Indicates whether the given key code should be ignored or should trigger * editing, by default fire or any numeric key should trigger editing implicitly. * This method is only called when handles input is false. * * @param keyCode the keycode passed to the keyPressed method * @return true if this key code should cause a switch to editing mode. */ protected boolean isEditingTrigger(int keyCode) { if(!isEditable()) { return false; } int gk = Display.getInstance().getGameAction(keyCode); if(isQwertyInput()) { return keyCode > 0 || (gk == Display.GAME_FIRE) || isClearKey(keyCode) || isEnterKey(keyCode) || (leftAndRightEditingTrigger && ((gk == Display.GAME_LEFT) || (gk == Display.GAME_RIGHT))); } return (keyCode >= '0' && keyCode <= '9') || isClearKey(keyCode) || (gk == Display.GAME_FIRE) || (leftAndRightEditingTrigger && ((gk == Display.GAME_LEFT) || (gk == Display.GAME_RIGHT))); } /** * Indicates whether the given key code should be ignored or should trigger * cause editing to end. By default the fire key, up or down will trigger * the end of editing. * * @param keyCode the keycode passed to the keyPressed method * @return true if this key code should cause a switch to editing mode. */ protected boolean isEditingEndTrigger(int keyCode) { int k =Display.getInstance().getGameAction(keyCode); boolean endTrigger = false; if(isQwertyInput()) { if(isSingleLineTextArea()){ endTrigger = keyCode < 0 && (k == Display.GAME_FIRE || k == Display.GAME_UP || k == Display.GAME_DOWN || isEnterKey(keyCode)); }else{ endTrigger = keyCode < 0 && (k == Display.GAME_FIRE); } }else{ if(isSingleLineTextArea()){ endTrigger = (k == Display.GAME_FIRE || k == Display.GAME_UP || k == Display.GAME_DOWN); }else{ endTrigger = (k == Display.GAME_FIRE); } } return endTrigger; } /** * @inheritDoc */ public void paint(Graphics g) { UIManager.getInstance().getLookAndFeel().drawTextField(g, this); if (drawCursor && hasFocus() && isEditable()) { UIManager.getInstance().getLookAndFeel().drawTextFieldCursor(g, this); } paintHint(g); } /** * @inheritDoc */ protected Dimension calcPreferredSize() { if(isSingleLineTextArea()){ return UIManager.getInstance().getLookAndFeel().getTextFieldPreferredSize(this); }else{ return UIManager.getInstance().getLookAndFeel().getTextAreaSize(this, true); } } /** * @inheritDoc */ void initComponentImpl() { super.initComponentImpl(); boolean rtl = isRTL(); keyFwd = rtl ? Display.GAME_LEFT : Display.GAME_RIGHT; keyBack = rtl ? Display.GAME_RIGHT : Display.GAME_LEFT; // text field relies too much on animation to use internal animations getComponentForm().registerAnimated(this); } /** * The amount of time in milliseconds in which the cursor is visible * * @param time for the cursor to stay "on" */ public void setCursorBlinkTimeOn(int time) { blinkOnTime = time; } /** * The amount of time in milliseconds in which the cursor is invisible * * @param time for the cursor to stay "off" */ public void setCursorBlinkTimeOff(int time) { blinkOffTime = time; } /** * The amount of time in milliseconds in which the cursor is visible * * @return time for the cursor to stay "on" */ public int getCursorBlinkTimeOn() { return blinkOnTime; } /** * The amount of time in milliseconds in which the cursor is invisible * * @return time for the cursor to stay "off" */ public int getCursorBlinkTimeOff() { return blinkOffTime; } /** * @inheritDoc */ public boolean animate() { boolean ani = super.animate(); if(hasFocus()) { long currentTime = System.currentTimeMillis(); if (drawCursor) { if ((currentTime - cursorBlinkTime) > blinkOnTime) { cursorBlinkTime = currentTime; drawCursor = false; return true; } } else { if ((currentTime - cursorBlinkTime) > blinkOffTime) { cursorBlinkTime = currentTime; drawCursor = true; return true; } } if(pressedAndNotReleased) { if(currentTime - pressTime >= getLongClickDuration()) { pressedAndNotReleased = false; longClick(pressedKeyCode); } } else { if(pendingCommit && currentTime - releaseTime > commitTimeout) { commitChange(); } } } else { drawCursor = false; } return ani; } /** * @inheritDoc */ public void pointerReleased(int x, int y) { if(useNativeTextInput && Display.getInstance().isNativeInputSupported()) { super.pointerReleased(x, y); return; } // unlike text area the text field supports shifting the cursor with the touch screen int lineHeight = getStyle().getFont().getHeight() + getRowsGap(); int line = (y - getAbsoluteY())/lineHeight; cursorY = line; cursorY = Math.min(cursorY, getLines() - 1); String text = getTextAt(cursorY); int textLength = text.length(); int position = 0; Font f = getStyle().getFont(); x -= getAbsoluteX(); for(int iter = 0 ; iter < textLength ; iter++) { int width = f.substringWidth(text, 0, iter); if(x > width) { position = iter; } else { break; } } if(position == textLength - 1) { if(f.stringWidth(text) < x) { position = textLength; } } cursorX = position; if(!isEditable()){ return; } super.pointerReleased(x, y); } /** * When set to true softkeys are used to enable delete functionality * * @return true if softkeys should be used */ public boolean isUseSoftkeys() { return useSoftkeys; } /** * When set to true softkeys are used to enable delete functionality * * @param useSoftkeys true if softkeys should be used */ public void setUseSoftkeys(boolean useSoftkeys) { this.useSoftkeys = useSoftkeys; } /** * Adds a listener for data change events it will be invoked for every change * made to the text field * * @param d the listener */ public void addDataChangeListener(DataChangedListener d) { listeners.addListener(d); } /** * Removes the listener for data change events * * @param d the listener */ public void removeDataChangeListener(DataChangedListener d) { listeners.removeListener(d); } private void fireDataChanged(int type, int index) { if(listeners != null) { listeners.fireDataChangeEvent(index, type); } } /** * @inheritDoc */ void onEditComplete(String text) { super.onEditComplete(text); setCursorPosition(text.length()); } /** * Indicates whether the menu of the form should be replaced with the T9/Clear * commands for the duration of interactivity with the text field * * @return true if the menu should be replaced */ public boolean isReplaceMenu() { return replaceMenu; } /** * Indicates whether the menu of the form should be replaced with the T9/Clear * commands for the duration of interactivity with the text field * * @param replaceMenu true if the menu should be replaced */ public void setReplaceMenu(boolean replaceMenu) { this.replaceMenu = replaceMenu; } /** * Indicates whether the menu of the form should be replaced with the T9/Clear * commands for the duration of interactivity with the text field * * @return true if the menu should be replaced */ public static boolean isReplaceMenuDefault() { return replaceMenuDefault; } /** * Indicates whether the menu of the form should be replaced with the T9/Clear * commands for the duration of interactivity with the text field * * @param replaceMenu true if the menu should be replaced */ public static void setReplaceMenuDefault(boolean replaceMenu) { replaceMenuDefault = replaceMenu; } /** * Indicates whether the text field should try to auto detect qwerty and * switch the qwerty device flag implicitly * * @param v true for qwerty auto detection */ public static void setQwertyAutoDetect(boolean v) { qwertyAutoDetect = v; } /** * The default value for the qwerty flag so it doesn't need setting for every * text field individually. * * @param v true for qwerty device */ public static void setQwertyDevice(boolean v) { qwertyDevice = v; } /** * Indicates whether the text field should try to auto detect qwerty and * switch the qwerty device flag implicitly * * @return true for qwerty auto detection */ public static boolean isQwertyAutoDetect() { return qwertyAutoDetect; } /** * The default value for the qwerty flag so it doesn't need setting for every * text field individually. * * @return true for qwerty devices */ public static boolean isQwertyDevice() { return qwertyDevice; } /** * Key to change the input mode on the device * * @param k key to change the input mode */ public static void setDefaultChangeInputModeKey(int k) { defaultChangeInputModeKey = k; } /** * Key to change the input mode on the device * * @return key to change the input mode */ public static int getDefaultChangeInputModeKey() { return defaultChangeInputModeKey; } /** * The default key for poping open the symbol dialog * * @param d new key value */ public static void setDefaultSymbolDialogKey(int d) { defaultSymbolDialogKey = d; } /** * The default key for poping open the symbol dialog * * @return the default key */ public static int getDefaultSymbolDialogKey() { return defaultSymbolDialogKey; } /** * Indicates that this is the overwrite mode * * @param overwriteMode set to true if input with overwrite characters */ public void setOverwriteMode(boolean overwriteMode) { this.overwriteMode = overwriteMode; } /** * Indicates that this is the overwrite mode * * @return true if input with overwrite characters */ public boolean isOverwriteMode() { return overwriteMode; } /** * Indicates whether the left/right keys will trigger editing, this is true by default. * Left and right key edit trigger might be disabled for cases such as text field * positioned horizontally one next to the other. * * @param leftAndRightEditingTrigger Indicates whether the left/right keys will trigger editing */ public void setLeftAndRightEditingTrigger(boolean leftAndRightEditingTrigger) { this.leftAndRightEditingTrigger = leftAndRightEditingTrigger; } /** * Indicates whether the left/right keys will trigger editing, this is true by default. * Left and right key edit trigger might be disabled for cases such as text field * positioned horizontally one next to the other. * * @return leftAndRightEditingTrigger Indicates whether the left/right keys will trigger editing */ public boolean isLeftAndRightEditingTrigger() { return leftAndRightEditingTrigger; } /** * @inheritDoc */ public void setAlignment(int align) { if(align == Component.CENTER) { throw new IllegalArgumentException("CENTER alignment is not supported in TextField."); } else { super.setAlignment(align); } } }