/*
 * Copyright (c) 2002, 2004, 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.
 */

/*
 * (C) Copyright IBM Corp. 2000 - All Rights Reserved
 *
 * The original version of this source code and documentation is
 * copyrighted and owned by IBM. These materials are provided
 * under terms of a License Agreement between IBM and Sun.
 * This technology is protected by multiple US and International
 * patents. This notice and attribution to IBM may not be removed.
 *
 */

package com.sun.inputmethods.internal.indicim;

import java.awt.im.spi.InputMethodContext;

import java.awt.event.KeyEvent;
import java.awt.event.InputMethodEvent;
import java.awt.font.TextAttribute;
import java.awt.font.TextHitInfo;

import java.text.AttributedCharacterIterator;

import java.util.Hashtable;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

class IndicInputMethodImpl {

    protected char[] KBD_MAP;

    private static final char SUBSTITUTION_BASE = '\uff00';

    // Indexed by map value - SUBSTITUTION_BASE
    protected char[][] SUBSTITUTION_TABLE;

    // Invalid character.
    private static final char INVALID_CHAR              = '\uffff';

    // Unmapped versions of some interesting characters.
    private static final char KEY_SIGN_VIRAMA           = '\u0064'; // or just 'd'??
    private static final char KEY_SIGN_NUKTA            = '\u005d';  // or just ']'??

    // Two succeeding viramas are replaced by one virama and one ZWNJ.
    // Viram followed by Nukta is replaced by one VIRAMA and one ZWJ
    private static final char ZWJ                       = '\u200d';
    private static final char ZWNJ                      = '\u200c';

    // Backspace
    private static final char BACKSPACE                 = '\u0008';

    // Sorted list of characters which can be followed by Nukta
    protected char[] JOIN_WITH_NUKTA;

    // Nukta form of the above characters
    protected char[] NUKTA_FORM;

    private int log2;
    private int power;
    private int extra;

    // cached TextHitInfo. Only one type of TextHitInfo is required.
    private static final TextHitInfo ZERO_TRAILING_HIT_INFO = TextHitInfo.trailing(0);

    
Returns the index of the given character in the JOIN_WITH_NUKTA array. If character is not found, -1 is returned.
/** * Returns the index of the given character in the JOIN_WITH_NUKTA array. * If character is not found, -1 is returned. */
private int nuktaIndex(char ch) { if (JOIN_WITH_NUKTA == null) { return -1; } int probe = power; int index = 0; if (JOIN_WITH_NUKTA[extra] <= ch) { index = extra; } while (probe > (1 << 0)) { probe >>= 1; if (JOIN_WITH_NUKTA[index + probe] <= ch) { index += probe; } } if (JOIN_WITH_NUKTA[index] != ch) { index = -1; } return index; }
Returns the equivalent character for hindi locale.
Params:
  • originalChar – The original character.
/** * Returns the equivalent character for hindi locale. * @param originalChar The original character. */
private char getMappedChar( char originalChar ) { if (originalChar <= KBD_MAP.length) { return KBD_MAP[originalChar]; } return originalChar; }//getMappedChar() // Array used to hold the text to be sent. // If the last character was not committed it is stored in text[0]. // The variable totalChars give an indication of whether the last // character was committed or not. If at any time ( but not within a // a call to dispatchEvent ) totalChars is not equal to 0 ( it can // only be 1 otherwise ) the last character was not committed. private char [] text = new char[4]; // this is always 0 before and after call to dispatchEvent. This character assumes // significance only within a call to dispatchEvent. private int committedChars = 0;// number of committed characters // the total valid characters in variable text currently. private int totalChars = 0;//number of total characters ( committed + composed ) private boolean lastCharWasVirama = false; private InputMethodContext context; // // Finds the high bit by binary searching // through the bits in n. // private static byte highBit(int n) { if (n <= 0) { return -32; } byte bit = 0; if (n >= 1 << 16) { n >>= 16; bit += 16; } if (n >= 1 << 8) { n >>= 8; bit += 8; } if (n >= 1 << 4) { n >>= 4; bit += 4; } if (n >= 1 << 2) { n >>= 2; bit += 2; } if (n >= 1 << 1) { n >>= 1; bit += 1; } return bit; } IndicInputMethodImpl(char[] keyboardMap, char[] joinWithNukta, char[] nuktaForm, char[][] substitutionTable) { KBD_MAP = keyboardMap; JOIN_WITH_NUKTA = joinWithNukta; NUKTA_FORM = nuktaForm; SUBSTITUTION_TABLE = substitutionTable; if (JOIN_WITH_NUKTA != null) { int log2 = highBit(JOIN_WITH_NUKTA.length); power = 1 << log2; extra = JOIN_WITH_NUKTA.length - power; } else { power = extra = 0; } } void setInputMethodContext(InputMethodContext context) { this.context = context; } void handleKeyTyped(KeyEvent kevent) { char keyChar = kevent.getKeyChar(); char currentChar = getMappedChar(keyChar); // The Explicit and Soft Halanta case. if ( lastCharWasVirama ) { switch (keyChar) { case KEY_SIGN_NUKTA: currentChar = ZWJ; break; case KEY_SIGN_VIRAMA: currentChar = ZWNJ; break; default: }//endSwitch }//endif if (currentChar == INVALID_CHAR) { kevent.consume(); return; } if (currentChar == BACKSPACE) { lastCharWasVirama = false; if (totalChars > 0) { totalChars = committedChars = 0; } else { return; } } else if (keyChar == KEY_SIGN_NUKTA) { int nuktaIndex = nuktaIndex(text[0]); if (nuktaIndex != -1) { text[0] = NUKTA_FORM[nuktaIndex]; } else { // the last character was committed, commit just Nukta. // Note : the lastChar must have been committed if it is not one of // the characters which combine with nukta. // the state must be totalChars = committedChars = 0; text[totalChars++] = currentChar; } committedChars += 1; } else { int nuktaIndex = nuktaIndex(currentChar); if (nuktaIndex != -1) { // Commit everything but currentChar text[totalChars++] = currentChar; committedChars = totalChars-1; } else { if (currentChar >= SUBSTITUTION_BASE) { char[] sub = SUBSTITUTION_TABLE[currentChar - SUBSTITUTION_BASE]; System.arraycopy(sub, 0, text, totalChars, sub.length); totalChars += sub.length; } else { text[totalChars++] = currentChar; } committedChars = totalChars; } } ACIText aText = new ACIText( text, 0, totalChars, committedChars ); int composedCharLength = totalChars - committedChars; TextHitInfo caret=null,visiblePosition=null; switch( composedCharLength ) { case 0: break; case 1: visiblePosition = caret = ZERO_TRAILING_HIT_INFO; break; default: assert false : "The code should not reach here. There is no case where there can be more than one character pending."; } context.dispatchInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, aText, committedChars, caret, visiblePosition); if (totalChars == 0) { text[0] = INVALID_CHAR; } else { text[0] = text[totalChars - 1];// make text[0] hold the last character } lastCharWasVirama = keyChar == KEY_SIGN_VIRAMA && !lastCharWasVirama; totalChars -= committedChars; committedChars = 0; // state now text[0] = last character // totalChars = ( last character committed )? 0 : 1; // committedChars = 0; kevent.consume();// prevent client from getting this event. }//dispatchEvent() void endComposition() { if( totalChars != 0 ) {// if some character is not committed. ACIText aText = new ACIText( text, 0, totalChars, totalChars ); context.dispatchInputMethodEvent( InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, aText, totalChars, null, null ); totalChars = committedChars = 0; text[0] = INVALID_CHAR; lastCharWasVirama = false; }//end if }//endComposition() // custom AttributedCharacterIterator -- much lightweight since currently there is no // attribute defined on the text being generated by the input method. private class ACIText implements AttributedCharacterIterator { private char [] text = null; private int committed = 0; private int index = 0; ACIText( char [] chArray, int offset, int length, int committed ) { this.text = new char[length]; this.committed = committed; System.arraycopy( chArray, offset, text, 0, length ); }//c'tor // CharacterIterator methods. public char first() { return _setIndex( 0 ); } public char last() { if( text.length == 0 ) { return _setIndex( text.length ); } return _setIndex( text.length - 1 ); } public char current() { if( index == text.length ) return DONE; return text[index]; } public char next() { if( index == text.length ) { return DONE; } return _setIndex( index + 1 ); } public char previous() { if( index == 0 ) return DONE; return _setIndex( index - 1 ); } public char setIndex(int position) { if( position < 0 || position > text.length ) { throw new IllegalArgumentException(); } return _setIndex( position ); } public int getBeginIndex() { return 0; } public int getEndIndex() { return text.length; } public int getIndex() { return index; } public Object clone() { try { ACIText clone = (ACIText) super.clone(); return clone; } catch (CloneNotSupportedException e) { throw new InternalError(); } } // AttributedCharacterIterator methods. public int getRunStart() { return index >= committed ? committed : 0; } public int getRunStart(AttributedCharacterIterator.Attribute attribute) { return (index >= committed && attribute == TextAttribute.INPUT_METHOD_UNDERLINE) ? committed : 0; } public int getRunStart(Set<? extends Attribute> attributes) { return (index >= committed && attributes.contains(TextAttribute.INPUT_METHOD_UNDERLINE)) ? committed : 0; } public int getRunLimit() { return index < committed ? committed : text.length; } public int getRunLimit(AttributedCharacterIterator.Attribute attribute) { return (index < committed && attribute == TextAttribute.INPUT_METHOD_UNDERLINE) ? committed : text.length; } public int getRunLimit(Set<? extends Attribute> attributes) { return (index < committed && attributes.contains(TextAttribute.INPUT_METHOD_UNDERLINE)) ? committed : text.length; } public Map getAttributes() { Hashtable result = new Hashtable(); if (index >= committed && committed < text.length) { result.put(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_ONE_PIXEL); } return result; } public Object getAttribute(AttributedCharacterIterator.Attribute attribute) { if (index >= committed && committed < text.length && attribute == TextAttribute.INPUT_METHOD_UNDERLINE) { return TextAttribute.UNDERLINE_LOW_ONE_PIXEL; } return null; } public Set getAllAttributeKeys() { HashSet result = new HashSet(); if (committed < text.length) { result.add(TextAttribute.INPUT_METHOD_UNDERLINE); } return result; } // private methods
This is always called with valid i ( 0 < i <= text.length )
/** * This is always called with valid i ( 0 < i <= text.length ) */
private char _setIndex( int i ) { index = i; if( i == text.length ) { return DONE; } return text[i]; }//_setIndex() }//end of inner class }