001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026: package com.sun.midp.chameleon.input;
027:
028: import com.sun.midp.log.Logging;
029: import com.sun.midp.log.LogChannels;
030:
031: import java.util.Vector;
032: import javax.microedition.lcdui.Display;
033: import javax.microedition.lcdui.Displayable;
034: import javax.microedition.lcdui.TextField;
035:
036: /**
037: * The BasicTextInputSession represents the relationship between the
038: * system's key input, the TextInputComponent, the available InputModes,
039: * and the graphical display.
040: */
041: public class BasicTextInputSession implements TextInputSession,
042: InputModeMediator {
043:
044: /** The currently "active" InputMode */
045: protected InputMode currentMode;
046:
047: /** The set of all possible InputModes */
048: protected InputMode[] inputModeSet;
049:
050: /** The current Display object */
051: private Display currentDisplay;
052:
053: /** The previous Displayable */
054: private Displayable previousScreen;
055:
056: /**
057: * If the user has specifically chosen an InputMode, that choice
058: * becomes sticky when the InputSession chooses the InputMode to
059: * make active.
060: */
061: protected InputMode stickyMode;
062:
063: /** The text component receiving the input */
064: protected TextInputComponent textComponent;
065:
066: /**
067: * Construct a new BasicTextInputSession
068: */
069: public BasicTextInputSession() {
070: inputModeSet = InputModeFactory.createInputModes();
071: }
072:
073: /**
074: * Start a text input session for the given TextInputComponent.
075: * The TextInputComponent can be used to determine the initial
076: * input mode, constraints, etc.
077: *
078: * @param component the TextInputComponent which is receiving text input
079: */
080: public void beginSession(TextInputComponent component) {
081: if (component == null) {
082: throw new IllegalArgumentException(
083: "Null TextInputComponent in beginSession()");
084: }
085:
086: if (this .textComponent == null) {
087: this .textComponent = component;
088: } else if (this .textComponent != component) {
089: throw new IllegalStateException(
090: "InputModeHandler in use by another TextInputComponent");
091: }
092:
093: // Select a suitable InputMode
094: selectInputMode();
095: }
096:
097: /**
098: * List the appropriate InputModes available for the current input
099: * session. This method may be used by UI components in order to make
100: * certain input mode choices available to the user for selection.
101: * If this handler is not currently in an active text input session,
102: * this method returns null.
103: *
104: * @return an array of InputModes which are available to use given the
105: * current TextInputComponent and its input constraints
106: */
107: public InputMode[] getAvailableModes() {
108: if (textComponent == null) {
109: throw new IllegalStateException(
110: "Call to InputModeHandler while outside of a valid session");
111: }
112:
113: int constraints = textComponent.getConstraints();
114: Vector v = new Vector();
115: for (int i = 0; i < inputModeSet.length; i++) {
116: if (inputModeSet[i].supportsConstraints(constraints)) {
117: v.addElement(inputModeSet[i]);
118: }
119: }
120:
121: if (v.size() == 0) {
122: return null;
123: }
124:
125: InputMode[] modes = new InputMode[v.size()];
126: v.copyInto(modes);
127:
128: return modes;
129: }
130:
131: /**
132: * Retrieve the InputMode which is the current "active" mode
133: * for this TextInputSession. This does not necessarily mean there is
134: * any pending input with the InputMode itself, it means that if this
135: * TextInputSession receives key input, the returned InputMode will be
136: * the mode which processes that input.
137: *
138: * @return the currently "active" InputMode
139: */
140: public InputMode getCurrentInputMode() {
141: return currentMode;
142: }
143:
144: /**
145: * Set this TextInputSession's current "active" InputMode to the
146: * given mode. The given mode must be one of the InputModes listed
147: * in the array of InputModes returned from the getAvailableModes()
148: * method of this TextInputSession. Calling this method will terminate
149: * any existing input session with the current InputMode and will
150: * result in any subsequent key input being processed by the given
151: * InputMode. If the given mode is already the current "active"
152: * InputMode, this method has no effect. If this TextInputSession
153: * is not currently in an input session (ie, there is no active
154: * TextInputComponent), this method has no effect.
155: *
156: * @param mode the InputMode to switch key processing to
157: */
158: public void setCurrentInputMode(InputMode mode) {
159: if (mode == null || mode == currentMode) {
160: return;
161: }
162:
163: for (int i = 0; i < inputModeSet.length; i++) {
164: if (inputModeSet[i] == mode) {
165: try {
166: endInputMode(currentMode);
167: setInputMode(inputModeSet[i]);
168: } catch (Throwable t) {
169: // IMPL_NOTE Log exception?
170: }
171: break;
172: }
173: }
174: }
175:
176: /**
177: * This method abstracts key processing to a single call (from
178: * the assorted key press, release, repeat events). This method
179: * should be called from the TextInputComponent to pass along
180: * key input from the user. The TextInputComponent is responsible
181: * for determining what key events should be processed (ie,
182: * key events trigger processing on press or on release).
183: *
184: * @param keyCode the numeric code representing the key which was
185: * pressed
186: * @param longPress return true if it's long key press otherwise false
187: * @return true if the current InputMode processed the key event,
188: * false if the key was not processed at all by the current
189: * InputMode (not all keys apply to input)
190: */
191: public int processKey(int keyCode, boolean longPress) {
192: try {
193: return currentMode.processKey(keyCode, longPress);
194: } catch (Throwable t) {
195: // Since InputModes are pluggable, we'll catch any possible
196: // Throwable when calling into one
197: // IMPL_NOTE : log the throwable
198: }
199: return InputMode.KEYCODE_NONE;
200: }
201:
202: /**
203: * return the pending char
204: * used to bypass the asynchronous commit mechanism
205: * e.g. to immediately commit a char before moving the cursor
206: *
207: * @return return the pending char
208: */
209: public char getPendingChar() {
210: return currentMode != null ? currentMode.getPendingChar() : 0;
211: }
212:
213: /**
214: * An iterative method to return the next available match given
215: * the key processing thus far. If the return value of hasMoreMatches()
216: * is true, this method will return a non-null String and will iterate
217: * through the entire set of available matches until the set is exhausted.
218: *
219: * Each subsequent call to processKey() will reset the iterator over
220: * the set of available matches regardless if the key resulted in a change
221: * to the set.
222: *
223: * The two methods, hasMoreMatches() and getNextMatch(), can be used by
224: * the User Interface system to retrieve the current set of pending inputs
225: * and possibly present a chooser option to the user.
226: *
227: * @return a String representing the best possible pending
228: * input, or null, if there is no pending input
229: */
230: public String getNextMatch() {
231: try {
232: return currentMode.getNextMatch();
233: } catch (Throwable t) {
234: // Since InputModes are pluggable, we'll catch any possible
235: // Throwable when calling into one
236: // IMPL_NOTE : log the throwable
237: }
238: return null;
239: }
240:
241: /**
242: * If the InputMode supports multiple matches and more matches are
243: * available this method will return true, false otherwise.
244: *
245: * @return true if the current InputMode supports multiple matches and
246: * there are currently more matches available
247: */
248: public boolean hasMoreMatches() {
249: try {
250: return currentMode.hasMoreMatches();
251: } catch (Throwable t) {
252: // Since InputModes are pluggable, we'll catch any possible
253: // Throwable when calling into one
254: // IMPL_NOTE : log the throwable
255: }
256: return false;
257: }
258:
259: /**
260: * Gets the possible string matches
261: *
262: * @return returns the set of options.
263: */
264: public String[] getMatchList() {
265: return currentMode != null ? currentMode.getMatchList()
266: : new String[0];
267: }
268:
269: /**
270: * End the current text input session and do not commit any pending
271: * characters to the buffer.
272: */
273: public void endSession() {
274: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
275: Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI,
276: "[BTIS.endSession]");
277: }
278:
279: if (currentMode != null) {
280: endInputMode(currentMode);
281: setInputMode(null);
282: }
283: textComponent = null;
284: stickyMode = null;
285: }
286:
287: // ******* Begin InputModeMediator Interface *******
288: /**
289: * Called by an InputMode in order to automatically commit the given
290: * input to the Text component. For example, when the timer expires
291: * in an AlphaNumeric InputMode it will commit the current pending
292: * character.
293: * @param input text to commit
294: */
295: public void commit(String input) {
296: if (input != null && textComponent != null) {
297: textComponent.commit(input);
298: }
299: }
300:
301: /**
302: * Clear the particular number of symbols
303: *
304: * @param num number of symbols
305: */
306: public void clear(int num) {
307: if (num == 0) {
308: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
309: Logging.report(Logging.INFORMATION,
310: LogChannels.LC_HIGHUI,
311: "WARNING: BasicTextInput.clear calld with 0");
312: }
313: return;
314: }
315: textComponent.clear(num);
316: }
317:
318: /**
319: * Called by an InputMode to inform a TextComponent of a sub-inputMode
320: * change.
321: */
322: public void subInputModeChanged() {
323: textComponent.notifyModeChanged();
324: }
325:
326: /**
327: * Called by an InputMode in order to signal that the input process
328: * has been completed with respect to the InputMode. Subsequent key
329: * input should be handled in a new input session, possibly by the
330: * same InputMode or by a different InputMode alltogether. For example,
331: * when the timer expires in an AlphaNumeric InputMode, the character
332: * is committed and the AlphaNumeric InputMode signals its completion.
333: * Further key input may start a new session with the AlphaNumeric
334: * InputMode or possibly some other InputMode.
335: */
336: public void inputModeCompleted() {
337: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
338: Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI,
339: "[Basic.inputModeCompleted()] >>> ");
340: }
341: try {
342: if (currentMode != null) {
343: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
344: Logging.report(Logging.INFORMATION,
345: LogChannels.LC_HIGHUI,
346: "[Basic.inputModeCompleted()] !=null");
347: }
348: endInputMode(currentMode);
349: setInputMode(null);
350: }
351: // Select a suitable InputMode
352: selectInputMode();
353: } catch (Exception e) {
354: e.printStackTrace();
355: }
356: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
357: Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI,
358: "[Basic.inputModeCompleted()] <<<< ");
359: }
360: }
361:
362: // ******* End InputModeMediator Interface *******
363:
364: /**
365: * Based on the constraints of the current TextInputComponent,
366: * select the most appropriate InputMode from the list available.
367: * This method will also start the session with the InputMode by
368: * calling the InputMode's beginInput() method.
369: */
370: protected void selectInputMode() {
371: if (textComponent == null) {
372: throw new IllegalStateException(
373: "Attempted input on null TextInputComponent");
374: }
375:
376: int constraints = textComponent.getConstraints();
377:
378: InputMode newMode = null;
379:
380: if (stickyMode != null
381: && stickyMode.supportsConstraints(constraints)) {
382: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
383: Logging.report(Logging.INFORMATION,
384: LogChannels.LC_HIGHUI,
385: "[BTIS.selectInputMode] setting mode to sticky:"
386: + stickyMode.getName());
387: }
388: newMode = stickyMode;
389: } else {
390: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
391: Logging
392: .report(Logging.INFORMATION,
393: LogChannels.LC_HIGHUI,
394: "[BTIS.selectInputMode] not setting mode to sticky");
395: }
396: for (int i = 0; i < inputModeSet.length; i++) {
397: if (inputModeSet[i].supportsConstraints(constraints)) {
398: boolean[][] map = inputModeSet[i]
399: .getIsConstraintsMap();
400: int index = 0;
401: String is = textComponent.getInitialInputMode();
402: for (; index < INPUT_SUBSETS.length; index++) {
403: if (INPUT_SUBSETS[index].equals(is))
404: break;
405: }
406: int constraint = constraints
407: & TextField.CONSTRAINT_MASK;
408: if (constraint < TextInputSession.MAX_CONSTRAINTS
409: && map[index][constraint]) {
410: newMode = inputModeSet[i];
411: break;
412: }
413: }
414: }
415: }
416:
417: if (newMode != null) {
418: if (newMode != currentMode) {
419: endInputMode(currentMode);
420: setInputMode(newMode);
421: }
422: } else {
423: throw new IllegalStateException(
424: "No InputMode found supporting the current constraints");
425: }
426: }
427:
428: /**
429: * Set the required input mode. Sticky mode can be set as the old mode just
430: * in case it will have to be reverted back. Text component has to be
431: * notified about the mode change.
432: *
433: * @param mode the required input mode
434: */
435: private void setInputMode(InputMode mode) {
436: InputMode oldMode = currentMode;
437: currentMode = mode;
438:
439: if (currentMode != null && textComponent != null) {
440: currentMode.beginInput(this , textComponent
441: .getInitialInputMode(), textComponent
442: .getConstraints());
443: if (currentMode.hasDisplayable()) {
444: currentDisplay = textComponent.getDisplay();
445: previousScreen = currentDisplay.getCurrent();
446: currentDisplay.setCurrent(currentMode.getDisplayable());
447: stickyMode = oldMode;
448: } else {
449: stickyMode = currentMode;
450: }
451: textComponent.notifyModeChanged();
452: }
453: }
454:
455: /**
456: * End the expired input mode.
457: *
458: * @param mode expired input mode
459: */
460: private void endInputMode(InputMode mode) {
461: if (mode != null) {
462: mode.endInput();
463: if (mode.hasDisplayable() && textComponent != null) {
464: currentDisplay.setCurrent(previousScreen);
465: previousScreen = null;
466: currentDisplay = null;
467: }
468: }
469: }
470:
471: /**
472: * Check if the given char is symbol
473: * @param c char
474: * @return true if the char is symbol otherwise false.
475: */
476: public boolean isSymbol(char c) {
477: return SymbolInputMode.isSymbol(c);
478: }
479:
480: /**
481: * Returns true if the keyCode is used as 'clear'
482: * @param keyCode key code
483: * @return true if keu code is Clear one, false otherwise
484: */
485: public boolean isClearKey(int keyCode) {
486: return textComponent != null
487: && textComponent.isClearKey(keyCode);
488: }
489:
490: /**
491: * Returns the available size (number of characters) that can be
492: * stored in this <code>TextInputComponent</code>.
493: * @return available size in characters
494: */
495: public int getAvailableSize() {
496: return textComponent != null ? textComponent.getAvailableSize()
497: : 0;
498: }
499: }
|