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 javax.microedition.lcdui.TextField;
029:
030: import javax.microedition.lcdui.Displayable;
031: import javax.microedition.lcdui.Graphics;
032: import javax.microedition.lcdui.Canvas;
033: import javax.microedition.lcdui.Font;
034: import javax.microedition.lcdui.Command;
035: import javax.microedition.lcdui.CommandListener;
036: import com.sun.midp.i18n.Resource;
037: import com.sun.midp.i18n.ResourceConstants;
038: import com.sun.midp.lcdui.EventConstants;
039:
040: /**
041: * An InputMode instance which allows to select the particular symbol
042: * from the table of predefined symbols.
043: */
044: public class SymbolInputMode implements InputMode {
045:
046: /** A holder for the keyCode which was last processed */
047: protected int lastKey = -1;
048:
049: /** The InputModeMediator for the current input session */
050: protected InputModeMediator mediator;
051:
052: /** Symbol table */
053: protected final SymbolTable st = new SymbolTable();
054:
055: /**
056: * The number of symbol_table is designed to be 29 for 5x6 matrix,
057: * starting the selection at 12. But if you have more, the total
058: * must be under 36 for 6x6 matrix.
059: */
060: protected final static char[] symbolTableChars;
061:
062: static {
063: symbolTableChars = Resource.getString(
064: ResourceConstants.LCDUI_TF_SYMBOLS_TABLE).toCharArray();
065: }
066:
067: /**
068: * This method is called to determine if this InputMode supports
069: * the given text input constraints. The semantics of the constraints
070: * value are defined in the javax.microedition.lcdui.TextField API.
071: * If this InputMode returns false, this InputMode must not be used
072: * to process key input for the selected text component.
073: *
074: * @param constraints text input constraints. The semantics of the
075: * constraints value are defined in the TextField API.
076: *
077: * @return true if this InputMode supports the given text component
078: * constraints, as defined in the MIDP TextField API
079: */
080: public boolean supportsConstraints(int constraints) {
081: switch (constraints & TextField.CONSTRAINT_MASK) {
082: case TextField.NUMERIC:
083: case TextField.DECIMAL:
084: case TextField.PHONENUMBER:
085: return false;
086: default:
087: return true;
088: }
089: }
090:
091: /**
092: * Returns the display name which will represent this InputMode to
093: * the user, such as in a selection list or the softbutton bar.
094: *
095: * @return the locale-appropriate name to represent this InputMode
096: * to the user
097: */
098: public String getName() {
099: return Resource.getString(ResourceConstants.LCDUI_TF_SYMBOLS);
100: }
101:
102: /**
103: * Returns the command name which will represent this InputMode to
104: * the user
105: *
106: * @return the locale-appropriate command name to represent this InputMode
107: * to the user
108: */
109: public String getCommandName() {
110: return getName();
111: }
112:
113: /**
114: * This method will be called before any input keys are passed
115: * to this InputMode to allow the InputMode to perform any needed
116: * initialization. A reference to the InputModeMediator which is
117: * currently managing the relationship between this InputMode and
118: * the input session is passed in. This reference can be used
119: * by this InputMode to commit text input as well as end the input
120: * session with this InputMode. The reference is only valid until
121: * this InputMode's endInput() method is called.
122: *
123: * @param constraints text input constraints. The semantics of the
124: * constraints value are defined in the TextField API.
125: *
126: * @param mediator the InputModeMediator which is negotiating the
127: * relationship between this InputMode and the input session
128: *
129: * @param inputSubset current input subset
130: */
131: public void beginInput(InputModeMediator mediator,
132: String inputSubset, int constraints) {
133: validateState(false);
134: this .mediator = mediator;
135: }
136:
137: /**
138: * Symbol Input mode is represented by Symbol table implemented as canvas
139: * @return SymbolTable
140: */
141: public Displayable getDisplayable() {
142: return st;
143: }
144:
145: /**
146: * Process the given key code as input.
147: *
148: * This method will return true if the key was processed successfully,
149: * false otherwise.
150: *
151: * @param keyCode the keycode of the key which was input
152: * @param longPress return true if it's long key press otherwise false
153: * @return true if the key was processed by this InputMode, false
154: * otherwise.
155: */
156: public int processKey(int keyCode, boolean longPress) {
157: return KEYCODE_NONE;
158: }
159:
160: /**
161: * return the pending char
162: * used to bypass the asynchronous commit mechanism
163: * e.g. to immediately commit a char before moving the cursor
164: * @return return the pending char
165: */
166: public char getPendingChar() {
167: return lastKey == KEYCODE_NONE ? 0 : (char) lastKey;
168: }
169:
170: /**
171: * Return the next possible match for the key input processed thus
172: * far by this InputMode. A call to this method should be preceeded
173: * by a check of hasMoreMatches(). If the InputMode has more available
174: * matches for the given input, this method will return them one by one.
175: *
176: * @return a String representing the next available match to the key
177: * input thus far
178: */
179: public String getNextMatch() {
180: return null;
181: }
182:
183: /**
184: * True, if after processing a key, there is more than one possible
185: * match to the input. If this method returns true, the getNextMatch()
186: * method can be called to return the value.
187: *
188: * @return true if after processing a key, there is more than the one
189: * possible match to the given input
190: */
191: public boolean hasMoreMatches() {
192: return false;
193: }
194:
195: /**
196: * Gets the possible string matches
197: *
198: * @return returns the set of options.
199: */
200: public String[] getMatchList() {
201: return new String[0];
202: }
203:
204: /**
205: * Mark the end of this InputMode's processing. The only possible call
206: * to this InputMode after a call to endInput() is a call to beginInput()
207: * to begin a new input session.
208: */
209: public void endInput() {
210: validateState(true);
211: mediator = null;
212: }
213:
214: /**
215: * Returns true if input mode is using its own displayable, false ifinput
216: * mode does not require the speial displayable for its representation.
217: * For Symbol mode is represented by Symbol table canvas, so it returns true
218: * @return true if input mode is using its own displayable, otherwise false
219: */
220: public boolean hasDisplayable() {
221: return true;
222: }
223:
224: /**
225: * This method will validate the state of this InputMode. If this
226: * is a check for an "active" operation, the TextInputMediator must
227: * be non-null or else this method will throw an IllegalStateException.
228: * If this is a check for an "inactive" operation, then the
229: * TextInputMediator should be null.
230: * @param activeOperation true if any operation is active otherwise false.
231: */
232: protected void validateState(boolean activeOperation) {
233: if (activeOperation && mediator == null) {
234: throw new IllegalStateException(
235: "Illegal operation on an input session already in progress");
236: } else if (!activeOperation && mediator != null) {
237: throw new IllegalStateException(
238: "Illegal operation on an input session which is not in progress");
239: }
240: }
241:
242: /**
243: * A special Canvas to display a symbol table.
244: */
245: protected class SymbolTable extends Canvas implements
246: CommandListener {
247: /** The margin size */
248: private static final int MARGIN = 1;
249:
250: /** The margin size */
251: private static final int DMARGIN = 2;
252:
253: /** Select commant to accept the symbol */
254: private final Command okCmd = new Command(Resource
255: .getString(ResourceConstants.OK), Command.OK, 1);
256:
257: /** Back commant to reject the symbol */
258: private final Command backCmd = new Command(Resource
259: .getString(ResourceConstants.BACK), Command.BACK, 0);
260:
261: /** Cell size */
262: private int cellSize;
263:
264: /** Height margin */
265: private int hmargin;
266:
267: /** Width margin */
268: private int wmargin;
269:
270: /** Margin for the cursor */
271: private int margin = 1;
272:
273: /** Window x position */
274: private int wx;
275:
276: /** Window y position */
277: private int wy;
278:
279: /** Window width */
280: private int ww;
281:
282: /** Window height */
283: private int wh;
284:
285: /** Number of columns */
286: private int cols;
287:
288: /** Number of rows */
289: private int rows;
290:
291: /** Current cursor position */
292: private int pos;
293:
294: /** New cursor position */
295: private int newpos;
296:
297: /** Font */
298: private Font font;
299:
300: /** x cursor coordinate */
301: int x_cursor_coord;
302:
303: /** y cursor coordinate */
304: int y_cursor_coord;
305:
306: /** default location to start the cursor */
307: protected int defaultSymbolCursorPos = 14;
308:
309: /** Default constructor for SymbolTable */
310: protected SymbolTable() {
311: init();
312: }
313:
314: /**
315: * Symbol accept is run in separate thread
316: */
317: private class Accept implements Runnable {
318: /**
319: * run for accept
320: */
321: public void run() {
322: lastKey = symbolTableChars[pos];
323: completeInputMode(true);
324: }
325: }
326:
327: /**
328: * Symbol reject is run in separate thread
329: */
330: private class Reject implements Runnable {
331: /**
332: * run for reject
333: */
334: public void run() {
335: completeInputMode(false);
336: }
337: }
338:
339: /**
340: * Instance of accept object
341: */
342: private Runnable accept = new Accept();
343:
344: /**
345: * Instance of reject object
346: */
347: private Runnable reject = new Reject();
348:
349: /**
350: * Initialize the symbol table
351: */
352: void init() {
353: calculateProportions();
354: addCommand(backCmd);
355: addCommand(okCmd);
356: setCommandListener(this );
357: }
358:
359: /**
360: * Calculate numbers of colomns and rows of symbol table.
361: * Calculate cell size of symbol table.
362: */
363: private void calculateProportions() {
364:
365: { // choose the table sizes
366: rows = cols = (int) Math.sqrt(symbolTableChars.length);
367: if (rows * cols < symbolTableChars.length) {
368: rows++;
369: }
370: if (rows * cols < symbolTableChars.length) {
371: cols++;
372: }
373: }
374:
375: int w = getWidth() / cols;
376: int h = getHeight() / rows;
377:
378: cellSize = (w > h) ? h : w;
379:
380: int cw = 0, ch = 0;
381:
382: int temp_cols = getWidth() / cellSize;
383: if (temp_cols > cols) {
384: cols = temp_cols;
385: rows = (int) Math.ceil((double) symbolTableChars.length
386: / cols);
387: }
388:
389: int[] fs = { Font.SIZE_LARGE, Font.SIZE_MEDIUM,
390: Font.SIZE_SMALL };
391: for (int i = 0; i < fs.length; i++) {
392: font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD,
393: fs[i]);
394: cw = font.charWidth('M');
395: ch = font.getHeight();
396: if (cw <= cellSize && ch <= cellSize) {
397: break;
398: }
399: }
400:
401: ww = cols * cellSize;
402: wh = rows * cellSize;
403:
404: hmargin = (cellSize - ch) / 2;
405: wmargin = cellSize / 2;
406:
407: wx = (getWidth() - ww) / 2;
408: wy = (getHeight() - wh) / 2;
409: }
410:
411: /**
412: * Notify this symbol table its being shown on the screen.
413: * Overrides Canvas.showNotify.
414: */
415: protected void showNotify() {
416: pos = newpos = defaultSymbolCursorPos;
417: }
418:
419: /**
420: * Called when the drawable area of the <code>Canvas</code> has
421: * been changed. This
422: * method has augmented semantics compared to {@link
423: * Displayable#sizeChanged(int,int) Displayable.sizeChanged}.
424: *
425: * @param w the new width in pixels of the drawable area of the
426: * <code>Canvas</code>
427: * @param h the new height in pixels of the drawable area of
428: * the <code>Canvas</code>
429: */
430: protected void sizeChanged(int w, int h) {
431: calculateProportions();
432: }
433:
434: /**
435: * Paint this symbol table.
436: * Overrides Canvas.paint.
437: *
438: * @param g The Graphics object to paint to
439: */
440: protected void paint(Graphics g) {
441:
442: paintPanel(g);
443: }
444:
445: /**
446: * Paint the symbol table panel
447: *
448: * @param g The Graphics object to paint to
449: */
450: void paintPanel(Graphics g) {
451:
452: int clipX = g.getClipX();
453: int clipY = g.getClipY();
454: int clipW = g.getClipWidth();
455: int clipH = g.getClipHeight();
456:
457: Font old_font = g.getFont();
458: g.setFont(font);
459:
460: g.setGrayScale(255);
461: g.fillRect(clipX, clipY, clipW, clipH);
462:
463: int sr = (clipY - wy) / cellSize;
464: if (sr < 0)
465: sr = 0;
466:
467: int sc = (clipX - wx) / cellSize;
468: if (sc < 0)
469: sc = 0;
470:
471: int er = (clipH + clipY + cellSize - wy) / cellSize;
472: if (er > rows - 1)
473: er = rows - 1;
474:
475: int ec = (clipW + clipX + cellSize - wx) / cellSize;
476: if (ec > cols - 1)
477: ec = cols - 1;
478:
479: g.setGrayScale(0);
480: g.drawRect(wx, wy, ww, wh);
481:
482: for (int r = sr; r <= er; r++) {
483: for (int c = sc; c <= ec; c++) {
484: int i = r * cols + c;
485: if (i >= symbolTableChars.length) {
486: break;
487: }
488: if (i == newpos) {
489: g.setGrayScale(0);
490: setCursorCoord(newpos);
491: g.fillRect(x_cursor_coord, y_cursor_coord,
492: cellSize - margin, cellSize - margin);
493: drawChar(g, symbolTableChars[i], r, c, true);
494: g.setGrayScale(255);
495: pos = newpos;
496: } else {
497: drawChar(g, symbolTableChars[i], r, c, false);
498: }
499:
500: }
501: }
502: g.setFont(old_font);
503: }
504:
505: /**
506: * Draw a character
507: *
508: * @param g The Graphics object to paint to
509: * @param c The character to draw
510: * @param row The row the character is in
511: * @param col The column the character is in
512: * @param reverse A flag to draw the character in inverse
513: */
514: void drawChar(Graphics g, char c, int row, int col,
515: boolean reverse) {
516: int y = wy + row * cellSize + hmargin;
517: int x = wx + col * cellSize + wmargin;
518:
519: Font old_font = g.getFont();
520: g.setFont(font);
521: g.setGrayScale(reverse ? 255 : 0);
522: g.drawChar(c, x, y, Graphics.HCENTER | Graphics.TOP);
523: g.setFont(old_font);
524: }
525:
526: /**
527: * Move focus to the symbol
528: * @param x x coordinate of the pointer
529: * @param y y coordinate of the pointer
530: */
531: protected void pointerPressed(int x, int y) {
532: int pressedId;
533: pressedId = getIdAtPointerPosition(x, y);
534: // move focus to the clicked position
535: if (pressedId < symbolTableChars.length && pressedId >= 0) {
536: newpos = pressedId;
537: repaint();
538: }
539: }
540:
541: /**
542: * Select the symbol by one click
543: * @param x x coordinate of the pointer
544: * @param y y coordinate of the pointer
545: */
546: protected void pointerReleased(int x, int y) {
547: int pressedId;
548: // select character
549: pressedId = getIdAtPointerPosition(x, y);
550: if (pos == pressedId) {
551: new Thread(accept).start();
552: }
553: }
554:
555: /**
556: * Helper function to determine the cell index at the x,y position
557: *
558: * @param x, y pointer coordinates in this popup
559: * layer's space (0,0 means left-top corner)
560: *
561: * @return cell id at the pointer position
562: */
563: private int getIdAtPointerPosition(int x, int y) {
564: int ret = -1;
565:
566: int col = (x - wx) / cellSize;
567: int row = (y - wy) / cellSize;
568:
569: if (col >= 0 && col < cols && row >= 0 && row < rows) {
570: ret = cols * row + col;
571: }
572: return ret;
573: }
574:
575: /**
576: * Handle a key press event on this symbol table.
577: * Overrides Canvas.keyPressed.
578: *
579: * @param keyCode The key that was pressed
580: */
581: protected void keyPressed(int keyCode) {
582:
583: validateState(true);
584: if (mediator != null && mediator.isClearKey(keyCode)) {
585: new Thread(reject).start();
586: } else {
587: switch (getGameAction(keyCode)) {
588: case Canvas.RIGHT:
589: if ((pos + 1) < symbolTableChars.length) {
590: newpos = pos + 1;
591: setCursorCoord(pos);
592: repaint(x_cursor_coord, y_cursor_coord,
593: cellSize - margin, cellSize - margin);
594: setCursorCoord(newpos);
595: repaint(x_cursor_coord, y_cursor_coord,
596: cellSize - margin, cellSize - margin);
597: }
598: break;
599: case Canvas.LEFT:
600: if (pos > 0) {
601: newpos = pos - 1;
602: setCursorCoord(pos);
603: repaint(x_cursor_coord, y_cursor_coord,
604: cellSize - margin, cellSize - margin);
605: setCursorCoord(newpos);
606: repaint(x_cursor_coord, y_cursor_coord,
607: cellSize - margin, cellSize - margin);
608: }
609: break;
610: case Canvas.UP: {
611: int p = pos - cols;
612: if (p >= 0) {
613: newpos = p;
614: setCursorCoord(pos);
615: repaint(x_cursor_coord, y_cursor_coord,
616: cellSize - margin, cellSize - margin);
617: setCursorCoord(newpos);
618: repaint(x_cursor_coord, y_cursor_coord,
619: cellSize - margin, cellSize - margin);
620: }
621: break;
622: }
623: case Canvas.DOWN: {
624: int p = pos + cols;
625: if (p < symbolTableChars.length) {
626: newpos = p;
627: setCursorCoord(pos);
628: repaint(x_cursor_coord, y_cursor_coord,
629: cellSize - margin, cellSize - margin);
630: setCursorCoord(newpos);
631: repaint(x_cursor_coord, y_cursor_coord,
632: cellSize - margin, cellSize - margin);
633: }
634: break;
635: }
636: case Canvas.FIRE:
637: new Thread(accept).start();
638: break;
639: }
640: }
641: }
642:
643: /**
644: * Set bounds of repaint area
645: * @param curr_pos - position of cursor
646: */
647: private void setCursorCoord(int curr_pos) {
648: int row = curr_pos / cols;
649: int col = curr_pos % cols;
650: y_cursor_coord = wy + row * cellSize;
651: x_cursor_coord = wx + col * cellSize;
652: }
653:
654: /**
655: * This method is used to immediately commit the given
656: * string and then call the TextInputMediator's inputModeCompleted()
657: * method
658: * @param c command
659: * @param d displayable
660: */
661: public void commandAction(Command c, Displayable d) {
662: validateState(true);
663: if (c == backCmd) {
664: new Thread(reject).start();
665: } else if (c == okCmd) {
666: new Thread(accept).start();
667: }
668: }
669: }
670:
671: /**
672: * Complete current input mode
673: * @param commit true if the symbol has to be committed otherwise false
674: */
675: protected void completeInputMode(boolean commit) {
676: if (commit) {
677: commitPendingChar();
678: }
679: mediator.inputModeCompleted();
680: }
681:
682: /**
683: * Commit pending char
684: * @return true if the char has been committed otherwise false
685: */
686: protected boolean commitPendingChar() {
687: boolean committed = false;
688: if (lastKey != KEYCODE_NONE) {
689: committed = true;
690: mediator.commit(String.valueOf((char) lastKey));
691: }
692: lastKey = -1;
693: return committed;
694: }
695:
696: /**
697: * Check if the char is the symbol from the symbol table
698: * @param c char
699: * @return true if this char exists in the symbol table otherwise false.
700: */
701: public static boolean isSymbol(char c) {
702: for (int i = 0; i < symbolTableChars.length; i++) {
703: if (symbolTableChars[i] == c) {
704: return true;
705: }
706: }
707: return false;
708: }
709:
710: /** this mode is not set as default. So the map is initialoized by false */
711: private static final boolean[][] isMap = new boolean[TextInputSession.INPUT_SUBSETS.length][TextInputSession.MAX_CONSTRAINTS];
712:
713: /**
714: * Returns the map specifying this input mode is proper one for the
715: * particular pair of input subset and constraint. The form of the map is
716: *
717: * |ANY|EMAILADDR|NUMERIC|PHONENUMBER|URL|DECIMAL|
718: * ---------------------------------------------------------------------
719: * IS_FULLWIDTH_DIGITS |t|f| t|f | t|f | t|f |t|f| t|f |
720: * IS_FULLWIDTH_LATIN |t|f| t|f | t|f | t|f |t|f| t|f |
721: * IS_HALFWIDTH_KATAKANA |t|f| t|f | t|f | t|f |t|f| t|f |
722: * IS_HANJA |t|f| t|f | t|f | t|f |t|f| t|f |
723: * IS_KANJI |t|f| t|f | t|f | t|f |t|f| t|f |
724: * IS_LATIN |t|f| t|f | t|f | t|f |t|f| t|f |
725: * IS_LATIN_DIGITS |t|f| t|f | t|f | t|f |t|f| t|f |
726: * IS_SIMPLIFIED_HANZI |t|f| t|f | t|f | t|f |t|f| t|f |
727: * IS_TRADITIONAL_HANZI |t|f| t|f | t|f | t|f |t|f| t|f |
728: * MIDP_UPPERCASE_LATIN |t|f| t|f | t|f | t|f |t|f| t|f |
729: * MIDP_LOWERCASE_LATIN |t|f| t|f | t|f | t|f |t|f| t|f |
730: * NULL |t|f| t|f | t|f | t|f |t|f| t|f |
731: *
732: * @return input subset x constraint map
733: */
734: public boolean[][] getIsConstraintsMap() {
735: return isMap;
736: }
737: }
|