<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    JAVA InputMethod輸入實現紀要

    Posted on 2008-11-14 15:15 英雄 閱讀(2068) 評論(0)  編輯  收藏
     

    JAVA InputMethod 輸入實現紀要

    Jre1.7對輸入法的支持使得java開發者能夠方便地使用JAVA編寫輸入法,并整合適配了本地輸入法,然后提供出一個在所有輸入法中切換的菜單界面,并在以后的編輯文本過程中實現了一個高效的事件處理框架,最終實現了方便地利用輸入法進行輸入的用戶體驗。

    一.編寫Java輸入法及切換輸入法支持。

    利用JAVA編寫輸入法只需實現兩個核心接口:InputMethod,InputMethodDescriptor,然后將jar包放在jre/lib/ext下即可被檢測到;本地系統輸入法雖然可能安裝多個(比如流行的谷歌輸入法,紫光拼音輸入法),但通過InputMethodAdapter只適配為一個輸入法;各個java編寫的輸入法與這個本地輸入法一起,將在顯示輸入法切換菜單InputMethodPopupMenu時作為其中的菜單項。

    檢測建立各個輸入法的過程在任何一個窗口被初始化時完成,具體在:WFramePeer/WDialogPeer在被Toolkit.create時即會要求延遲創建InputMethodManager實例-InputMethodManager.getInstance(),整個JVM中將僅存在它一個全局單例,用來管理所有輸入法,真正創建時將首先通過inputmethodadapter建立本地輸入法,再檢測ext中是否存在JAVA編寫的輸入法jar包并逐次進行加載建立,還會開啟一個dameon線程:AWT-InputMethodManager。這個線程實現了顯示切換輸入法菜單的控制。具體為它將不斷檢查是否有請求顯示切換輸入法的菜單-InputMethodPopupMenu,發現該請求即會在該窗口上顯示該菜單;請求來自下述兩個方面:

    第一是Toolkit.createPeer時還要InputMethodManager.getTriggerMenuString

    (屬性”AWT.InputMethodSelectionMenu”)來獲取切換輸入法的菜單名稱,這個是作為peer窗口基本菜單的一項(Windows系統下點擊JFrame左上角圖標即顯示該菜單),點擊此菜單項將請求AWT-InputMethodManager顯示輸入法切換菜單InputMethodPopupMenu

    第二是系統會監聽某定義好的切換輸入法的熱鍵(在用戶preference中存在/java/awt/im/selectionKey路徑下定義),熱鍵產生時也將請求AWT-InputMethodManager顯示輸入法切換菜單。

    最后要注意的是如果沒有java輸入法的jar,則AWT-InputMethodManager線程是不會建立的,同樣也不會有那個窗口基本菜單項,包括熱鍵響應,總之,沒有java輸入法,就不會存在切換的實現支持。

    二.輸入法參與的事件響應框架

    a.每一個組件Component都將持有一個InputContext對象,該對象將記錄當前正在使用的輸入法,并在發生輸入法切換或組件焦點切換的時候得到通知以調整輸入法,最終協調輸入法幫助完成組件的文本輸入。InputContext被組件通過getInputContext()方法延遲創建。

    注意對每一個Window組件,調此getInputContext方法將實實在在地延遲創建一個sun.awt.im.InputMethodContext并私自持有,而一般組件將調用parent.getInputContext。所以,同一個window下的所有組件共享一個InputContext

    b.當前輸入法總是服務于當前組件Component,按照Java輸入法規范,將Component分為5種情況,組件所屬種類不同,輸入法參與的事件響應也有所不同。

    1.    Active-client on-the-spot

    2.    Active-client below-the-spot

    3.    Passive-Clients

    4.    Non-Clients

    5.    Peer-text-component

    Active-client是實現了getInputMethodRequests方法返回一個非空對象的Component,并且該component提供InputMethodListener即,這些組件將和inputmethod進行交互響應;on-the-spot還是below-the-spot是由系統屬性確定,具體由InputContext在類加載初始化時確定:

    static {

            // check whether we should use below-the-spot input

            // get property from command line

            String inputStyle = (String) AccessController.doPrivileged

                    (new GetPropertyAction("java.awt.im.style", null));

            // get property from awt.properties file

            if (inputStyle == null) {

                inputStyle = Toolkit.getDefaultToolkit().

                        getProperty("java.awt.im.style", null);

            }

            belowTheSpotInputRequested = "below-the-spot".equals(inputStyle);

    }

     

    on-the-spot是直接利用組件界面作為輸入組裝的style;而below-the-spot則是利用一個新的文本組裝窗體compositionarea作為輸入文本組裝的style,并且該窗體還將隨組件文本錄入而自動位置尾隨。

    Non-Clients是那些enableInputMethods=false的組件,是要求不能使用輸入法進行輸入的組件;

    Peer-text-component則是那些對等組件,他們的輸入法相關行為依托于具體的底層系統,這里不再考慮。

    Passive-Clients就是那些enableInputMethods=true,但是getInputMethodRequests又返回一個空的組件,它可以接受輸入法的處理結果,但只相信這只是一般的鍵盤輸入而不認為存在什么輸入法。

    c.輸入法參與的事件響應場景具體如下:

    首先區分當前輸入法的兩種情況:java輸入法和本地輸入法。對java輸入法,其本身可能需要使用額外的窗口組件來做輸入輔助,像多音漢字需要提供選擇列,但是一定采用window來作為容器,并且該windowfocusenable=false,所以焦點總是不會轉移到輸入法的這些組件上。這樣當一個text組件聚焦后即使鼠標點擊輸入法給出的框框板板,焦點仍然不會遷移。既然目標組件保留著焦點,則后續的鍵盤事件也將target到該text組件上來進行處理;對本地輸入法,如果是windows系統,輸入法事件也會target到該組件上來。比如WInputMethod在激活后將在awt-windows線程里對底層事件處理,其處理后將postEDT,并target到當前clientcomponent

    所以事件響應都入口到當前組件的component.dispatchEventImpl的這個方法中來:

    if (areInputMethodsEnabled()) {//non-client不會得到輸入法處理,直接后續進入組件的listener處理

                // We need to pass on InputMethodEvents since some host

                // input method adapters send them through the Java

                // event queue instead of directly to the component,

                // and the input context also handles the Java composition window

                if(((e instanceof InputMethodEvent) && !(thisinstanceof CompositionArea)) //如果本地輸入法激活則輸入法已經處理基本輸入事件并post合成的InputMethodEvent到這里

                   ||

                   // Otherwise, we only pass on input and focus events, because

                   // a) input methods shouldn't know about semantic or component-level events

                   // b) passing on the events takes time

                   // c) isConsumed() is always true for semantic events.

                   (e instanceof InputEvent) || (e instanceof FocusEvent)) {//如果Java輸入法激活將在此處接收基本輸入事件

                    InputContext inputContext = getInputContext();//輸入事件將交由InputContext進行處理。

                    if (inputContext != null) {

                        inputContext.dispatchEvent(e);

                        if (e.isConsumed()) {

                            if ((e instanceof FocusEvent) && focusLog.isLoggable(Level.FINEST)) {

                                focusLog.log(Level.FINEST, "3579: Skipping " + e);

                            }

                            return;

                        }

                    }

                }

            }

    /*

             * 6. Deliver event for normal processing

             */

            if (newEventsOnly) {

                // Filtering needs to really be moved to happen at a lower

                // level in order to get maximum performance gain; it is

                // here temporarily to ensure the API spec is honored.

                //

                if (eventEnabled(e)) {

                    processEvent(e); //inputmethodevent或者其他inputevent在這里交給組件的Listener處理

                }

            }

    在整個事件處理過程中,按java inputmethod規范產生了2次事件流轉分支。下面舉個具體分析,其中系統輸入法假定使用Windows本地輸入法winputmethodjava輸入法假定使用CodePointInputMethod(對unicode編碼解析成對應字符的一種輸入法)。Winputmethod在激活后將在awt-windows直接處理輸入底層輸入事件,并包裝成InputMethodEvent遞交給EDT進入Component做處理(注意雖然將底層事件處理合成了InputMethodEvent,但仍然會有鍵盤事件或鼠標事件遞交給EDT)。Component會將這種InputMethodEvent,以及各個InputEvent, FocusEvent都發送給sun.awt.im. InputMethodContextComponent.getInputContext.dispatchEvent,在那里對于那些InputMethodEvent做一層過濾處理,即判斷clientcomponent是否Active-below-the-spotPassive就產生了分支,若是則直接getCompositionAreaHandler(true).processInputMethodEvent,若否則和一般的InputEvent, FocusEvent一樣都將提交給sun.awt.im. InputContext.dispatchEvent,在那里將把這個InputMethodEvent(of Active-on-the-spot-Client)直接推給componetinputmethodlistener進行處理完事,對FocusGained,FocusLost,以及可能的切換輸入法的HotKey會集中精力處理,對那些InputEvent則將調用當前輸入法進行處理getCurrentInputMethod.dispatchEvent.如當前輸入法是windows本地輸入法winputmethod,這時再次交給本地代碼做一些處理handleNativeIMEEvent (awtFocussedComponentPeer, e)后隨即consume;若是java輸入法CodePointInputMethod,將根據自身的輸入法規則進行真正的輸入法拼寫轉換處理,之后該基本輸入事件consume掉,并根據情況調用sun.awt.im.InputMethodContext.dispatchInputMethodEvent,在那里將合成InputMethodEvent(此時這個合成的InputMethodEvent相當于本地輸入法一早生成的那個InputMethodEvent),再判斷clientcomponent是否Active-on-the-spot-Client后產生分支,若是則直接推送給組件的對應inputmethodlistener處理;若否即Active-below-the-spotPassive的則getCompositionAreaHandler(true).processInputMethodEvent

    那些交給CompositionAreaHandler處理的所有InputMethodEvent事件會被分類處理,對caretchange(插入符位置改變)的情況將直接處理并consume掉舊事件但不會生成新事件,對commitedTextChange(輸入文本改變)的情況將consume掉舊事件,并繼續根據client性質不同第二次分支合成新事件,即對Active-below-the-spot-Client合成InputMethodEvent,對Passive-Client合成KeyEvent,這兩種事件將后續進入Component的對應InputMethodListener/KeyEventListener得到對應處理。

    整個流程的圖形流轉可以參見javainputmethod實現規范。

    注意:由上述分析可見,jre1.7按照Java輸入法規范實現的這種事件流,對于本地輸入法與java輸入法還是有區分的。區分在本地輸入法在awt-windows中底層處理基本輸入時即產生InputMethodEvent;而java輸入法則要在EDT中得到基本的輸入事件,具體為InputEvent經過了InputContext的處理后再進入java輸入法,java輸入法再借助InputMethodContext合成了InputMethodEvent

    注意:第一個分支存在的原因是因為Active-On-the-spot不需要使用額外的CompositionArea,所以不需要將事件流轉到對應的CompositionAreaHandler,而直接進入ComponentListener。第二個分支存在的原因是因為passive既然不提供request對象,那么也不能期望其實現了Inputmethodlistener,所以只能給它的keyeventlistener進行處理。

    補充:

    對于java輸入法-CodePointInputMethod,在得到基本輸入事件dispatchEvent時是大顯身手的時候。現在對CodePointInputMethod分析這一段處理過程以獲悉開發java輸入法的一般思路:

    CodePointInputMethod. dispatchEvent

            if (!(event instanceof KeyEvent)) {//忽略KeyEvent之外的事件

                return;

            }

            KeyEvent e = (KeyEvent) event;

            int eventID = event.getID();

            boolean notInCompositionMode = buffer.length() == 0;

            if (eventID == KeyEvent.KEY_PRESSED) {//如果當前沒有在拼寫狀態,忽略該事件

                if (notInCompositionMode) {

                    return;

                }

                switch (e.getKeyCode()) {//處理左右移動鍵,將提交CARET_POSITION_CHANGED

                    case KeyEvent.VK_LEFT:

                        moveCaretLeft();

                        break;

                    case KeyEvent.VK_RIGHT:

                        moveCaretRight();

                        break;

                }

            } elseif (eventID == KeyEvent.KEY_TYPED) {

                char c = e.getKeyChar();

                if (notInCompositionMode) {

                    if (c != '""') {//如果當前沒有在拼寫狀態,忽略該事件

                        return;

                    }

                    startComposition();//但是如果輸入的是'""',進入拼寫狀態,并提交    //INPUT_METHOD_TEXT_CHANGED

                } switch (c) {

                    case' '://輸入空格則要求轉換unicode編碼,生成對應unicode字符,并將此unicode字符提交

                                //INPUT_METHOD_TEXT_CHANGED

                        finishComposition();

                        break;

                    case'"u007f':     //輸入DEL鍵則刪除當前編碼字符并提交   //INPUT_METHOD_TEXT_CHANGED

                        deleteCharacter();

                        break;

                    case'"b':         //輸入BACKSPACE鍵則刪除前一個編碼字符并提交     //INPUT_METHOD_TEXT_CHANGED

                       deletePreviousCharacter();

                        break;

                    case'"u001b':     // 輸入Escape鍵清空編碼字符并提交    //INPUT_METHOD_TEXT_CHANGED

                        cancelComposition();

                        break;

                    case'"n':          //輸入回車

                    case'"t':         //輸入TAB鍵發送當前所有編碼字符并提交//INPUT_METHOD_TEXT_CHANGED

                        sendCommittedText();

                        break;

                    default:

                        composeUnicodeEscape(c); //unicode規則接受當前輸入字符作為編碼字符并提交     //INPUT_METHOD_TEXT_CHANGED

                        break;

                    }

                }

            } else

                //如果當前沒有在拼寫狀態,忽略KEY_RELEASED事件

                if (notInCompositionMode) {

                    return;

                }

            }

            //沒有忽略的事件都將被consume掉。

    a.      consume();

    CodePointInputMethod處理邏輯概括地說基本上為維持一個StringBuffer buffer,int insertionPoint ,在active時進行初始化。此后當dispatchEvent時,首先判斷buffer是否為空,為空則期待輸入'""',如果輸入的不是'""'則直接忽略過去(被忽略的事件將繼續進入組件的keylistener得到處理);如果是'""'則存入buffer,并insertionPoint++,然后以INPUT_METHOD_TEXT_CHANGED提交給InputMethodContext---即將buffer包裝成AttributedString,并且置屬性INPUT_METHOD_HIGHLIGHT,最后就會調用InputMethodContext. dispatchInputMethodEvent,在其中包裝成InputMethodEvent進行分支發送事件。那么如果buffer不為空,則說明已經開始收集unicode編碼了,這時將期待收到合理的unicode編碼字符,因此對每一個收到的字符進行合理性檢查,合理的將和上述'""'一樣處理,不合理的會通過Toolkit讓機器發出的一聲提醒就拉倒(不會再傳給keylistener,除此期待,還會考慮那些DelBackSpace,空格,回車等控制符,比如Del將會刪掉bufferinsertionPoint的字符,然后提交InputMethodContext,至于空格,則是要求對bufferunicode編碼進行轉換,得到對應的字符codePoint,覆蓋buffer的現有內容后再提交InputMethodContext。至于左右鍵,則會引起insertionPoint的增減,并且以CARET_POSITION_CHANGED提交給InputMethodContext

    CodePointInputMethod提交InputMethodContext后的處理將進行分支,對Active-below-the-spot以及passive將經過context.getCompositionAreaHandler(true)(延遲創建單例)來處理,其處理邏輯為:首先根據ComposedTextcarsetCompositionArea畫出來,如果有CommittedText,將會再次inputMethodContext.dispatchCommittedText,其將繼續根據Active-below-the-spot以及passive分支,前者產生InputMethodEvent提交給組件,后者產生KeyEvent提交給組件。最后,各類組件的對應listener里根據監聽到的事件的信息來更新私有成員text等并做重畫處理。

    注意:CodePointInputMethod是一個很簡單的輸入法,不需要打開什么文件資源,也沒有打開任何窗口界面,只需實現dispatchEvent方法就OK,頂多補充了一些基本的方法實現,比如isCompositionEnabled恒返回true來告訴輸入上下文將一直支持解析。如果我們要實現更復雜的輸入法,可借助InputMethodContext.createInputMethodWindowcreateInputMethodJFrame建立窗口界面,通過對InputMethodContext.enableClientWindowNotification來獲得客戶組件窗口的變化通知,然后在inputmethod里要在active,deactivehideWindowsremovenotifydispose等方法來實現資源獲取釋放的邏輯,而在notifyClientWindowChange方法里響應客戶組件窗口的變化。如果需要更多支持,reconvert()用來支持文本組件回轉拼寫,getControlObject()用來支持對輸入法的設置。

    d.輸入事件歸集到組件上,組件如果enableInputMethodgetInputContext將負責下一步處理,InputContext處理過程中要利用當前指定InputMethod進行協調處理,最后將InputMethod對輸入事件轉換拼寫輸入后的處理結果-純碎的文本輸入結果再交給組件的監聽進行下一步處理。 InputContext還要維系當前InputMethod,并在發生焦點轉移,請求輸入法切換的時候處理好相關事宜。具體過程為:

    InputContext在構造時即通過InputMethodManager.getDefaultKeyboardLocale獲取系統默認locale并據此locale完成初始輸入法的選擇。選擇過程主要委托給InputMethodManager.findInputMethod(Locale locale),在那里將首先根據用戶preference來查找,然后將從本地系統輸入法中查找,最后才從java輸入法找出支持此localeInputMethodLocator。當找到合適的InputMethodLocator后將設置該輸入法為當前輸入法。設置動作通過InputContext.changeInputMethod方法中完成。changeInputMethod將首先判斷當前inputMethodLocator是否為空(在初始化的情況肯定是空的),若為空則只是賦值即可返回。而當輸入法切換菜單被點擊時,菜單actionPerform將再次調用InputMethodManager.changeInputMethod方法,經由InputMethodManager記錄此作為用戶preference輸入法后,轉到當前InputContext. changeInputMethod,此時InputContext.inputMethodLocator將是上一次的輸入法,需要判斷待切換的輸入法是否和舊輸入法相同,如果相同需要替換成新的locator,再,如果舊inputmethod實例已經存在,則將此輸入法重置狀態;以上都不滿足的情況下,即舊的輸入法是不同的輸入法,將需要清理舊輸入法,替換為新輸入法。

    注意:InputContext總是要盡量延遲inputmethod實例的創建,因為inputmethod實例的創建將可能是一個耗時操作,所以在定下來要用某一個輸入法作為當前輸入法時,首先考察這個輸入法和上一個輸入法是否是同一種輸入法以便重復利用;即使是新的輸入法也只維持一個inputmethodlocator,只有當需要激活此輸入法時,才要去創建inputmethod的實例,而即使這時的創建也首先從usedInputMethods緩存中查找出來,只有緩存中不存在的情況才去通過descriptor.createInputMethod去創建實例(并馬上setCharacterSubsets)并以后會緩存下來。

    如果當前組件失去焦點,該focus_lost事件會流轉到inputcontext中進行處理,處理邏輯主要是deactivateInputMethod, setCompositionAreaVisible(false);

    如果當前組件獲得焦點, focus_gained事件會流轉到inputcontext中進行處理,處理邏輯主要是將將輸入法轉換輸入提交給上一個組件,activeMethod,并根據情況setCompositionAreaVisible(true);

    注意:InputContext在上述切換,焦點變化的過程中要處理好三個方面:一個是屬性延續的效果,即本輸入法要盡量沿襲上一個輸入法的locale,active, CompositionEnabled等屬性,并要應用歷史的clientWindowNotificationEnabled屬性;一個是既然多窗口多InputContext,盡量達到窗口切換各不影響輸入法的效果;另一個是清理上一個輸入法要徹底,包括endComposition,通知deactivateInputMethodsetClientComponentnull , 緩存此輸入法,并保存clientWindowNotificationEnabled屬性,hideWindows等等.


    只有注冊用戶登錄后才能發表評論。


    網站導航:
    博客園   IT新聞   Chat2DB   C++博客   博問  
     
    主站蜘蛛池模板: 国产大片线上免费看| 亚洲最大激情中文字幕| 一级做a爱片特黄在线观看免费看| 奇米影视亚洲春色| 99在线精品视频观看免费| 黄色免费在线观看网址| 亚洲国产精品久久久久婷婷老年| 国产真人无遮挡作爱免费视频| 在线免费视频你懂的| 99999久久久久久亚洲| 久久影视综合亚洲| 久久精品女人天堂AV免费观看| 国产特黄特色的大片观看免费视频| 亚洲白色白色永久观看| 亚洲精品在线视频| 歪歪漫画在线观看官网免费阅读 | 4hu四虎免费影院www| 亚洲国产日产无码精品| 国产成人精品日本亚洲专区61| 无码中文在线二区免费| 一级毛片成人免费看免费不卡| 国产精品亚洲а∨天堂2021| 亚洲成AV人片久久| 亚洲精品中文字幕乱码三区| 色www永久免费视频| 免费A级毛片无码A∨免费| 一本到卡二卡三卡免费高 | 永久免费在线观看视频| 无码精品人妻一区二区三区免费 | 免费看黄网站在线看| 日韩亚洲国产高清免费视频| 久久亚洲精品国产精品黑人| 凹凸精品视频分类国产品免费| 国产麻豆视频免费观看| 精品一区二区三区免费毛片爱 | 亚洲网站在线观看| 亚洲精品无码久久久影院相关影片| 国产在线98福利播放视频免费| 免费a级毛片高清视频不卡| 99re免费99re在线视频手机版| 99在线视频免费观看|