jdk/src/share/classes/com/sun/inputmethods/internal/indicim/IndicInputMethodImpl.java
changeset 5656 4868963e05e0
parent 5655 8dacdb7bb25b
parent 5645 c98f230a6078
child 5657 7e406ebed9a5
equal deleted inserted replaced
5655:8dacdb7bb25b 5656:4868963e05e0
     1 /*
       
     2  * Copyright (c) 2002, 2004, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 /*
       
    27  * (C) Copyright IBM Corp. 2000 - All Rights Reserved
       
    28  *
       
    29  * The original version of this source code and documentation is
       
    30  * copyrighted and owned by IBM. These materials are provided
       
    31  * under terms of a License Agreement between IBM and Sun.
       
    32  * This technology is protected by multiple US and International
       
    33  * patents. This notice and attribution to IBM may not be removed.
       
    34  *
       
    35  */
       
    36 
       
    37 package com.sun.inputmethods.internal.indicim;
       
    38 
       
    39 import java.awt.im.spi.InputMethodContext;
       
    40 
       
    41 import java.awt.event.KeyEvent;
       
    42 import java.awt.event.InputMethodEvent;
       
    43 import java.awt.font.TextAttribute;
       
    44 import java.awt.font.TextHitInfo;
       
    45 
       
    46 import java.text.AttributedCharacterIterator;
       
    47 
       
    48 import java.util.Hashtable;
       
    49 import java.util.HashSet;
       
    50 import java.util.Map;
       
    51 import java.util.Set;
       
    52 
       
    53 class IndicInputMethodImpl {
       
    54 
       
    55     protected char[] KBD_MAP;
       
    56 
       
    57     private static final char SUBSTITUTION_BASE = '\uff00';
       
    58 
       
    59     // Indexed by map value - SUBSTITUTION_BASE
       
    60     protected char[][] SUBSTITUTION_TABLE;
       
    61 
       
    62     // Invalid character.
       
    63     private static final char INVALID_CHAR              = '\uffff';
       
    64 
       
    65     // Unmapped versions of some interesting characters.
       
    66     private static final char KEY_SIGN_VIRAMA           = '\u0064'; // or just 'd'??
       
    67     private static final char KEY_SIGN_NUKTA            = '\u005d';  // or just ']'??
       
    68 
       
    69     // Two succeeding viramas are replaced by one virama and one ZWNJ.
       
    70     // Viram followed by Nukta is replaced by one VIRAMA and one ZWJ
       
    71     private static final char ZWJ                       = '\u200d';
       
    72     private static final char ZWNJ                      = '\u200c';
       
    73 
       
    74     // Backspace
       
    75     private static final char BACKSPACE                 = '\u0008';
       
    76 
       
    77     // Sorted list of characters which can be followed by Nukta
       
    78     protected char[] JOIN_WITH_NUKTA;
       
    79 
       
    80     // Nukta form of the above characters
       
    81     protected char[] NUKTA_FORM;
       
    82 
       
    83     private int log2;
       
    84     private int power;
       
    85     private int extra;
       
    86 
       
    87     // cached TextHitInfo. Only one type of TextHitInfo is required.
       
    88     private static final TextHitInfo ZERO_TRAILING_HIT_INFO = TextHitInfo.trailing(0);
       
    89 
       
    90     /**
       
    91      * Returns the index of the given character in the JOIN_WITH_NUKTA array.
       
    92      * If character is not found, -1 is returned.
       
    93      */
       
    94     private int nuktaIndex(char ch) {
       
    95 
       
    96         if (JOIN_WITH_NUKTA == null) {
       
    97             return -1;
       
    98         }
       
    99 
       
   100         int probe = power;
       
   101         int index = 0;
       
   102 
       
   103         if (JOIN_WITH_NUKTA[extra] <= ch) {
       
   104             index = extra;
       
   105         }
       
   106 
       
   107         while (probe > (1 << 0)) {
       
   108             probe >>= 1;
       
   109 
       
   110             if (JOIN_WITH_NUKTA[index + probe] <= ch) {
       
   111                 index += probe;
       
   112             }
       
   113         }
       
   114 
       
   115         if (JOIN_WITH_NUKTA[index] != ch) {
       
   116             index = -1;
       
   117         }
       
   118 
       
   119         return index;
       
   120     }
       
   121 
       
   122     /**
       
   123      * Returns the equivalent character for hindi locale.
       
   124      * @param originalChar The original character.
       
   125      */
       
   126     private char getMappedChar( char originalChar )
       
   127     {
       
   128         if (originalChar <= KBD_MAP.length) {
       
   129             return KBD_MAP[originalChar];
       
   130         }
       
   131 
       
   132         return originalChar;
       
   133     }//getMappedChar()
       
   134 
       
   135     // Array used to hold the text to be sent.
       
   136     // If the last character was not committed it is stored in text[0].
       
   137     // The variable totalChars give an indication of whether the last
       
   138     // character was committed or not. If at any time ( but not within a
       
   139     // a call to dispatchEvent ) totalChars is not equal to 0 ( it can
       
   140     // only be 1 otherwise ) the last character was not committed.
       
   141     private char [] text = new char[4];
       
   142 
       
   143     // this is always 0 before and after call to dispatchEvent. This character assumes
       
   144     // significance only within a call to dispatchEvent.
       
   145     private int committedChars = 0;// number of committed characters
       
   146 
       
   147     // the total valid characters in variable text currently.
       
   148     private int totalChars = 0;//number of total characters ( committed + composed )
       
   149 
       
   150     private boolean lastCharWasVirama = false;
       
   151 
       
   152     private InputMethodContext context;
       
   153 
       
   154     //
       
   155     // Finds the high bit by binary searching
       
   156     // through the bits in n.
       
   157     //
       
   158     private static byte highBit(int n)
       
   159     {
       
   160         if (n <= 0) {
       
   161             return -32;
       
   162         }
       
   163 
       
   164         byte bit = 0;
       
   165 
       
   166         if (n >= 1 << 16) {
       
   167             n >>= 16;
       
   168             bit += 16;
       
   169         }
       
   170 
       
   171         if (n >= 1 << 8) {
       
   172             n >>= 8;
       
   173             bit += 8;
       
   174         }
       
   175 
       
   176         if (n >= 1 << 4) {
       
   177             n >>= 4;
       
   178             bit += 4;
       
   179         }
       
   180 
       
   181         if (n >= 1 << 2) {
       
   182             n >>= 2;
       
   183             bit += 2;
       
   184         }
       
   185 
       
   186         if (n >= 1 << 1) {
       
   187             n >>= 1;
       
   188             bit += 1;
       
   189         }
       
   190 
       
   191         return bit;
       
   192     }
       
   193 
       
   194     IndicInputMethodImpl(char[] keyboardMap, char[] joinWithNukta, char[] nuktaForm,
       
   195                          char[][] substitutionTable) {
       
   196         KBD_MAP = keyboardMap;
       
   197         JOIN_WITH_NUKTA = joinWithNukta;
       
   198         NUKTA_FORM = nuktaForm;
       
   199         SUBSTITUTION_TABLE = substitutionTable;
       
   200 
       
   201         if (JOIN_WITH_NUKTA != null) {
       
   202             int log2 = highBit(JOIN_WITH_NUKTA.length);
       
   203 
       
   204             power = 1 << log2;
       
   205             extra = JOIN_WITH_NUKTA.length - power;
       
   206         } else {
       
   207             power = extra = 0;
       
   208         }
       
   209 
       
   210     }
       
   211 
       
   212     void setInputMethodContext(InputMethodContext context) {
       
   213 
       
   214         this.context = context;
       
   215     }
       
   216 
       
   217     void handleKeyTyped(KeyEvent kevent) {
       
   218 
       
   219         char keyChar = kevent.getKeyChar();
       
   220         char currentChar = getMappedChar(keyChar);
       
   221 
       
   222         // The Explicit and Soft Halanta case.
       
   223         if ( lastCharWasVirama ) {
       
   224             switch (keyChar) {
       
   225             case KEY_SIGN_NUKTA:
       
   226                 currentChar = ZWJ;
       
   227                 break;
       
   228             case KEY_SIGN_VIRAMA:
       
   229                 currentChar = ZWNJ;
       
   230                 break;
       
   231             default:
       
   232             }//endSwitch
       
   233         }//endif
       
   234 
       
   235         if (currentChar == INVALID_CHAR) {
       
   236             kevent.consume();
       
   237             return;
       
   238         }
       
   239 
       
   240         if (currentChar == BACKSPACE) {
       
   241             lastCharWasVirama = false;
       
   242 
       
   243             if (totalChars > 0) {
       
   244                 totalChars = committedChars = 0;
       
   245             } else {
       
   246                 return;
       
   247             }
       
   248         }
       
   249         else if (keyChar == KEY_SIGN_NUKTA) {
       
   250             int nuktaIndex = nuktaIndex(text[0]);
       
   251 
       
   252             if (nuktaIndex != -1) {
       
   253                 text[0] = NUKTA_FORM[nuktaIndex];
       
   254             } else {
       
   255                 // the last character was committed, commit just Nukta.
       
   256                 // Note : the lastChar must have been committed if it is not one of
       
   257                 // the characters which combine with nukta.
       
   258                 // the state must be totalChars = committedChars = 0;
       
   259                 text[totalChars++] = currentChar;
       
   260             }
       
   261 
       
   262             committedChars += 1;
       
   263         }
       
   264         else {
       
   265             int nuktaIndex = nuktaIndex(currentChar);
       
   266 
       
   267             if (nuktaIndex != -1) {
       
   268                 // Commit everything but currentChar
       
   269                 text[totalChars++] = currentChar;
       
   270                 committedChars = totalChars-1;
       
   271             } else {
       
   272                 if (currentChar >= SUBSTITUTION_BASE) {
       
   273                     char[] sub = SUBSTITUTION_TABLE[currentChar - SUBSTITUTION_BASE];
       
   274 
       
   275                     System.arraycopy(sub, 0, text, totalChars, sub.length);
       
   276                     totalChars += sub.length;
       
   277                 } else {
       
   278                     text[totalChars++] = currentChar;
       
   279                 }
       
   280 
       
   281                 committedChars = totalChars;
       
   282             }
       
   283         }
       
   284 
       
   285         ACIText aText = new ACIText( text, 0, totalChars, committedChars );
       
   286         int composedCharLength = totalChars - committedChars;
       
   287         TextHitInfo caret=null,visiblePosition=null;
       
   288         switch( composedCharLength ) {
       
   289             case 0:
       
   290                 break;
       
   291             case 1:
       
   292                 visiblePosition = caret = ZERO_TRAILING_HIT_INFO;
       
   293                 break;
       
   294             default:
       
   295                 assert false : "The code should not reach here. There is no case where there can be more than one character pending.";
       
   296         }
       
   297 
       
   298         context.dispatchInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
       
   299                                          aText,
       
   300                                          committedChars,
       
   301                                          caret,
       
   302                                          visiblePosition);
       
   303 
       
   304         if (totalChars == 0) {
       
   305             text[0] = INVALID_CHAR;
       
   306         } else {
       
   307             text[0] = text[totalChars - 1];// make text[0] hold the last character
       
   308         }
       
   309 
       
   310         lastCharWasVirama =  keyChar == KEY_SIGN_VIRAMA && !lastCharWasVirama;
       
   311 
       
   312         totalChars -= committedChars;
       
   313         committedChars = 0;
       
   314         // state now text[0] = last character
       
   315         // totalChars = ( last character committed )? 0 : 1;
       
   316         // committedChars = 0;
       
   317 
       
   318         kevent.consume();// prevent client from getting this event.
       
   319     }//dispatchEvent()
       
   320 
       
   321     void endComposition() {
       
   322         if( totalChars != 0 ) {// if some character is not committed.
       
   323             ACIText aText = new ACIText( text, 0, totalChars, totalChars );
       
   324             context.dispatchInputMethodEvent( InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
       
   325                                 aText, totalChars, null, null );
       
   326             totalChars = committedChars = 0;
       
   327             text[0] = INVALID_CHAR;
       
   328             lastCharWasVirama = false;
       
   329         }//end if
       
   330     }//endComposition()
       
   331 
       
   332     // custom AttributedCharacterIterator -- much lightweight since currently there is no
       
   333     // attribute defined on the text being generated by the input method.
       
   334     private class ACIText implements AttributedCharacterIterator {
       
   335         private char [] text = null;
       
   336         private int committed = 0;
       
   337         private int index = 0;
       
   338 
       
   339         ACIText( char [] chArray, int offset, int length, int committed ) {
       
   340             this.text = new char[length];
       
   341             this.committed = committed;
       
   342             System.arraycopy( chArray, offset, text, 0, length );
       
   343         }//c'tor
       
   344 
       
   345         // CharacterIterator methods.
       
   346         public char first() {
       
   347             return _setIndex( 0 );
       
   348         }
       
   349 
       
   350         public char last() {
       
   351             if( text.length == 0 ) {
       
   352                 return _setIndex( text.length );
       
   353             }
       
   354             return _setIndex( text.length - 1 );
       
   355         }
       
   356 
       
   357         public char current() {
       
   358             if( index == text.length )
       
   359                 return DONE;
       
   360             return text[index];
       
   361         }
       
   362 
       
   363         public char next() {
       
   364             if( index == text.length ) {
       
   365                 return DONE;
       
   366             }
       
   367             return _setIndex( index + 1 );
       
   368         }
       
   369 
       
   370         public char previous() {
       
   371             if( index == 0 )
       
   372                 return DONE;
       
   373             return _setIndex( index - 1 );
       
   374         }
       
   375 
       
   376         public char setIndex(int position) {
       
   377             if( position < 0 || position > text.length ) {
       
   378                 throw new IllegalArgumentException();
       
   379             }
       
   380             return _setIndex( position );
       
   381         }
       
   382 
       
   383         public int getBeginIndex() {
       
   384             return 0;
       
   385         }
       
   386 
       
   387         public int getEndIndex() {
       
   388             return text.length;
       
   389         }
       
   390 
       
   391         public int getIndex() {
       
   392             return index;
       
   393         }
       
   394 
       
   395         public Object clone() {
       
   396             try {
       
   397                 ACIText clone = (ACIText) super.clone();
       
   398                 return clone;
       
   399             } catch (CloneNotSupportedException e) {
       
   400                 throw new InternalError();
       
   401             }
       
   402         }
       
   403 
       
   404 
       
   405         // AttributedCharacterIterator methods.
       
   406         public int getRunStart() {
       
   407             return index >= committed ? committed : 0;
       
   408         }
       
   409 
       
   410         public int getRunStart(AttributedCharacterIterator.Attribute attribute) {
       
   411             return (index >= committed &&
       
   412                 attribute == TextAttribute.INPUT_METHOD_UNDERLINE) ? committed : 0;
       
   413         }
       
   414 
       
   415         public int getRunStart(Set<? extends Attribute> attributes) {
       
   416             return (index >= committed &&
       
   417                     attributes.contains(TextAttribute.INPUT_METHOD_UNDERLINE)) ? committed : 0;
       
   418         }
       
   419 
       
   420         public int getRunLimit() {
       
   421             return index < committed ? committed : text.length;
       
   422         }
       
   423 
       
   424         public int getRunLimit(AttributedCharacterIterator.Attribute attribute) {
       
   425             return (index < committed &&
       
   426                     attribute == TextAttribute.INPUT_METHOD_UNDERLINE) ? committed : text.length;
       
   427         }
       
   428 
       
   429         public int getRunLimit(Set<? extends Attribute> attributes) {
       
   430             return (index < committed &&
       
   431                     attributes.contains(TextAttribute.INPUT_METHOD_UNDERLINE)) ? committed : text.length;
       
   432         }
       
   433 
       
   434         public Map getAttributes() {
       
   435             Hashtable result = new Hashtable();
       
   436             if (index >= committed && committed < text.length) {
       
   437                 result.put(TextAttribute.INPUT_METHOD_UNDERLINE,
       
   438                            TextAttribute.UNDERLINE_LOW_ONE_PIXEL);
       
   439             }
       
   440             return result;
       
   441         }
       
   442 
       
   443         public Object getAttribute(AttributedCharacterIterator.Attribute attribute) {
       
   444             if (index >= committed &&
       
   445                 committed < text.length &&
       
   446                 attribute == TextAttribute.INPUT_METHOD_UNDERLINE) {
       
   447 
       
   448                 return TextAttribute.UNDERLINE_LOW_ONE_PIXEL;
       
   449             }
       
   450             return null;
       
   451         }
       
   452 
       
   453         public Set getAllAttributeKeys() {
       
   454             HashSet result = new HashSet();
       
   455             if (committed < text.length) {
       
   456                 result.add(TextAttribute.INPUT_METHOD_UNDERLINE);
       
   457             }
       
   458             return result;
       
   459         }
       
   460 
       
   461         // private methods
       
   462 
       
   463         /**
       
   464          * This is always called with valid i ( 0 < i <= text.length )
       
   465          */
       
   466         private char _setIndex( int i ) {
       
   467             index = i;
       
   468             if( i == text.length ) {
       
   469                 return DONE;
       
   470             }
       
   471             return text[i];
       
   472         }//_setIndex()
       
   473 
       
   474     }//end of inner class
       
   475 }