IMEと入力モードの変更
sennという名前のWindowsで動くIMEを自作しています。 sennの言語バーには入力モードを表示・切り替えるボタンがあり、このボタンをクリックするとひらがな入力と直接入力とを切り替えることができます。 この振る舞いは単純なのですが、内部実装はちょっと複雑になっているためここにメモしておきます。
sennはネットワークでつながったフロントエンドとバックエンドで構成されています。 sennの状態はすべてバックエンドで保持されており、入力モードもバックエンド側で管理してます。 フロントエンドは画面の表示のみを担当しています。
入力モードを表示・切り替えるボタンにはITfLangBarItemButtonを利用しています。 ユーザーがこのボタンをクリックすると、以下のように処理が進みます。
- Windowsが(?)フロントエンドにあるボタンの
ITfLangBarItemButton::OnClick
メソッドを実行する - フロントエンドがバックエンドに対して入力モードを切り替えるメソッド (
ToggleInputMode
) を実行する - フロントエンドがWindowsに (?) 対してアイコンアップデートメソッド (
ITfLangBarItemSink::OnUpdate(TF_LBI_ICON)
)を実行する - Windowsが(?)フロントエンドにあるボタンの
GetIcon
メソッドを実行する - ボタン (のコールバック) がバックエンドに対して現在の入力モードを問い合わせる (
GetInputMode
) - フロントエンドが入力モードに応じてアイコンを返却する
シーケンス図は次の通りです。
ITfLangBarItemButtonのアイコンを更新するにはボタン自身にGetIcon
メソッドでアイコンを返却させるという仕組みになっているようで、
外側からsetterでボタンのアイコンを指定するようなことはできなさそうでした。
この事情により、バックエンドはToggleInputMode
メソッドとGetInputMode
メソッドを提供しており、
ToggleInputMode
では更新後の入力モードを返却せず、
GetIcon
メソッド用のGetInputMode
メソッドが別途入力モードを返却するようにしています。
おわりに
ネットワーク越しに状態を更新し、参照するのは、大げさな感がありますが、面白いです。
なお、バックエンドのToggleInputMode
やGetIcon
の処理を担当しているのはstateful-imeです。
IMEでの状態の扱い
はじめに
sennという名前のWindowsで動くIMEを個人で開発しています。 IMEの状態の扱いがなかなか複雑なので、ここでメモしてみます。
stateful-ime
sennにはstateful-imeというオブジェクトがあります。キー入力の処理等、ユーザーと対話するのはこのstateful-ime (の派生) です。 stateful-imeは、stateとstatelessな機能の組み合わせです。 stateは画面の状態等、いろいろな状態を保持しています。
stateful-imeはstateの保持、更新を担当するのみで、具体的な機能はstatelessのほうが担当します。 stateful-imeのメソッドが呼び出されると、引数と保持している状態とを合わせてstatelessな機能に処理を委譲します。 stateful-imeはstatelessから返り値と新しい状態を受け取り、受け取った状態で現在の状態を更新したのちに呼び出し側に返り値を渡します。
たとえば、下はprocess-input
の定義です。このメソッドはユーザーのキー入力を処理します。
(defun process-input (stateful-ime key) (with-accessors ((input-state state-input-state) (input-mode state-input-mode)) (stateful-ime-state stateful-ime) (let ((result (senn.win.im.process-input:execute stateful-ime input-state input-mode key))) (destructuring-bind (can-process view &key state) result ;; update application state (when state (setf input-state state)) (format nil "~A ~A~%" (if (and can-process view) 1 0) (or view ""))))))
stateful-imeはstateful-ime-state
で自身の状態を取り出し、statelessなsenn.win.im.process-input:execute
に渡します。
senn.win.im.process-input:execute
が新しいinput-stateを返却すると、それでstateful-imeの状態を更新します。
(format nil ...
の部分は返り値で、この情報はC++実装のフロントエンドが画面を更新するために利用します。
テストを書くときは、このstateful-imeのprocess-input
に対してのみテストを書けばよく、senn.win.im.process-input:execute
のテストは書かなくていいかなと思っています。
stateful-imeは、ユーザーと対話しているため、ユーザーの行動をテストで模擬し、ちゃんと対話できているのかを確認するのは意味があると思っています。
一方、senn.win.im.process-input:execute
は内部処理であったり、stateの定義変更により影響を受けやすいため、stateful-imeほどテストする意味はないと思っています。
stateful-imeは状態をカプセル化しているため、stateの定義が変わっていてもテストケースは変化しないはずです。
おわりに
以上、IMEの状態の扱い方の方針でした。
つくってみました
Common Lisp関連のことを書いてみます。