Cocoa Break Logo Top Soft Develop  脱力空間 Logo
Apple Web Badge
made by mi

概要 翻訳 ソース リンク がらくた

概要 Examples ADC Samples 3rd Parties etc CBOriginals

KIM2

以下は、Mac OS X 10.5.8 上の Xcode 3.1.4 で説明しています。作成しながら、解説を書くという形で進めていきますので、後で文章が修正されることもあるので気をつけてください。

目次:
1 目標        2 キーボード配列作成        3 入力メソッド実装開始
4 入力モード追加        5 環境設定とコマンド処理         
4.1 モードの指定
4.2 モード対応の実装
4.3 メニュー
4.4 テスト

4 入力モード追加

一意変換をとりあえず実装できたので、次はカタカナや英文字の入力切替のためのモードをサポートすることにします。

英字入力時には、欧米語キーボード(私の場合は Dovrak - Qwerty キーボード)を使うように、モード切替を実装するつもりで、最終的には環境設定から、英字入力時のキーボードを選択できるようにするつもりでした。しかし、私の OS X v10.5 では、Dovrak - Qwerty キーボードがこわれています。そのため、すべてのモードで同じキーボードを使っています。上記の機能を実装するためには、モード切替メソッドでキーボードも変更する必要があるので注意してください。

さて、KIM2 では「ひらがな」「カタカナ」「全角英字」「半角英字」の4つのモードをサポートするつもりです。「ことえり」には半角カナがありますが、めったに使わないので全角半角変換で対応します。私はいままでキャプスロック押下でかなと英字を切り替えていたので、そのように実装します。

「英数」「かな」キーを「ひらがな・カタカナ」と「全角・半角」の切り替えに使おうと思ったのですが、これらのキーはイベントを受けとる前に、システム側で入力メソッドの切替が行われるようで、うまく受けとるのが難しいものです。この 2 つのキーに関しては、入力モード選択メニューからの入力と同じように考え、キーボード上に存在するものとは考えないほうが対応が簡単なようです。

4.1 モードの指定

まず、Info.plist を指定する必要があります。入力モードディクショナリを指す ComponentInputModeDict キーを追加し、入力モードリスト の tsInputModeListKey キーを追加し、その項目として、各モードを記載していきます。例として「ひらがな」モードを挙げます。

Info.plist
... <key>ComponentInputModeDict</key> <dict> <key>tsInputModeListKey</key> <dict> <key>com.apple.inputmethod.Japanese</key> <!-- 他と共通使用されるモードID --> <dict> <key>TISInputSourceID</key> <!-- この入力メソッド独自のモードID --> <string>com.kamluck.inputmethod.KIM2.Japanese</string> <key>TISIntendedLanguage</key> <string>ja</string> <!-- 対象言語 --> <key>tsInputModeAlternateMenuIconFileKey</key> <!-- 代替アイコン --> <string>KIM2HiraganaSelected.tif</string> <key>tsInputModeDefaultStateKey</key> <!-- デフォルト状態 --> <true/> <!-- デフォルトで有効になる --> <key>tsInputModeIsVisibleKey</key> <!-- 入力モード表示 --> <true/> <!-- システム UI 内に表示される --> <key>JISKeyboardShortcut</key> <!-- かな英数キーでの選択 --> <integer>1</integer> <!-- かなキーで選択 --> <key>tsInputModeKeyEquivalentKey</key> <!-- ショートカット文字 --> <string>A</string> <key>tsInputModeKeyEquivalentModifiersKey</key> <!-- ショートカット修飾キー --> <integer>4096</integer> <!-- コントロールキー --> <key>tsInputModeMenuIconFileKey</key> <!-- メニューアイコン --> <string>KIM2Hiragana.tif</string> <key>tsInputModePaletteIconFileKey</key> <!-- パレットアイコン --> <string>KIM2HiraganaButton.tif</string> <key>tsInputModePrimaryInScriptKey</key> <!-- スクリプト内で主要か --> <true/> <!-- このスクリプト内で主要(優先される) --> <key>tsInputModeScriptKey</key> <!-- スクリプト --> <string>smJapanese</string> </dict> </dict> ... </dict> ...

まず、注意するのが tsInputModeListKey の項目キーと TISInputSourceID の違いについてです。ここでは、項目キーは「ことえり」からコピーした値になっています。そして、TISInputSourceID は、この入力メソッド名が入った、より限定された形になっています。これについては、Carbon フレームワークの HIToolbox フレームワーク内の TextInputSources.h 内で説明されています。

"TISInputSourceID" note: For input modes this is a string that begins with the parent input method's InputSourceID or BundleID, followed by something that identifies the mode. For example, "com.apple.Kotoeri.Japanese.Katakana". In general it is not necessarily the same as the InputModeID, since a particular InputModeID such as "com.apple.inputmethod.Japanese.Katakana" may be used by multiple input methods. If this key is not specified, an InputSourceID will be constructed by combining the BundleID with an InputModeID suffix formed by deleting any prefix that matches the BundleID or that ends in ".inputmethod."

(Cocoa Break! 訳:「TISInputSourceID」についての注意:入力モードに対しては、これは親となる入力メソッドの InputSourceID または BundleID で始まり、そのモードを識別する何かが続く文字列です。たとえば、「com.apple.Kotoeri.Japanese.Katakana」。一般的に、これは InputModeID と同じである必要はありません。なぜなら、「com.apple.inputmethod.Japanese.Katakana」のような特定の InputModeID は、複数の入力メソッドによって使われるかもしれないからです。このキーが指定されていなければ、InputSourceID は、BundleID と、BundleID に一致するか、または、「.inputmethod」で終わる接頭句を除去することによって形作られた InputModeID 接尾句とを組み合わせることによって構築されることになるでしょう。)

このように、「ひらがな」「カタカナ」といった汎用性のあるモード名が複数の入力メソッドで共通して使えるようになっていて、それを担当する実際の入力モード ID を TISInputSourceID で指定することになっています。これを指定していない入力メソッドの実装もあるようですが、KIM2 では、上の説明にしたがって、入力モード名を「com.apple.inputmethod.Japanese(これは「ひらがな」を指す、使用画像名から確認できる)」とし、TISInputSourceIDcom.kamluck.inputmethod.KIM2.Japanese とすることにします。

じつは、上のヘッダには入力メソッドについても、TISInputSourceID を指定する必要があることが書かれています。

The new keys keys "TISInputSourceID" and "TISIntendedLanguage" and their associated values are added at the top level of the Info.plist file. "TISInputSourceID" note: For input methods this is typically the same as the BundleID, and if this key is not specified the BundleID will be used as the InputSourceID.

(Cocoa Break! 訳:新しいキーである「TISInputSourceID」と「TISIntendedLanguage」と、関連する値は Info.plist ファイルのトップレベルに追加されます。入力メソッドに対しては、これ(訳者註:TISInputSourceID)は通常、BundleID と同じであり、このキーが指定されていなければ、BundleIDInputSourceID として使われることになるでしょう。)

KIM2 の最初の Info.plist は「ことえり」を参考にしたため、TISIntendedLanguage はすでに追加してあります。TISInputSourceID は指定していませんが、バンドル ID が使われるので問題ないようです。

tsInputModeAlternateMenuIconFileKeytsInputModeMenuIconFileKeytsInputModePaletteIconFileKey キーは、「ことえり」ではそれぞれ HiraganaSelected.tif、Hiragana.tif、HiraganaButton.tif が使われています。どれもサイズは 22 × 16 です。HiraganaSelected.tif は、白地に灰色の「あ」が描画され、Hiragana.tif は、黒地に白色の「あ」が描画されています。HiraganaButton.tif は、透明な背景に黒色の「あ」が描画されています。サイズは必ずしもこのとおりでなくてもいいかもしれませんが、これに準じて作成することにします。

ショートカット関連の tsInputModeJISKeyboardShortcutKeytsInputModeKeyEquivalentKeytsInputModeKeyEquivalentModifiersKey は、最初のものが「かな」「英数」キーで選択されるかどうかを指定します。0 はなし、1 が「かな」、2 が「カナ」、3 が「英数」です。これについては、システム側がこのキーの値を各入力メソッドの入力モードやキーボードを調べて処理するので、入力メソッド内で横取りをするのが難しいようです。何か方法があるのかもしれませんが、いろいろ試した結果、「かな」「英数」キーは考慮しないのが一番という結論にたどりつきました。

次の 2 つは、単に入力モードメニューに表示されるショートカットです。ここでは、コントロール押下にしていますが、このままだとたいていのテキストエディタのショートカットと衝突します。ことえりではシフトを追加した形になっています。

同様にして、カタカナ、全角英字、半角英字モードも指定します。最後に各モードの順番を指定して終わりです。残りで注意するのは、半角英字は「ことえり」では TISIntendedLanguageen(英語)になっていて、tsInputModeScriptKeysmRoman で、tsInputModePrimaryInScriptKeytrue であることです。これにより、半角英数だけの入力限定されるフィールドでも使用できます。また、カタカナと全角英字の tsInputModePrimaryInScriptKeyfalse に設定されています。全角英字は当然ながら、対象言語等が日本語になっていることも注意してください。

各項目の指定が終わったら、tsVisibleInputModeOrderedArrayKey キーを使って、表示順を指定します。

Info.plist
... <key>tsVisibleInputModeOrderedArrayKey</key> <array> <string>com.apple.inputmethod.Japanese</string> <string>com.apple.inputmethod.Japanese.Katakana</string> <string>com.apple.inputmethod.Japanese.FullWidthRoman</string> <string>com.apple.inputmethod.Roman</string> </array> ..

余談ですが、「ことえり」の Info.plist では、表示されているモードの他にも姓、名前、地名のモードが定義されています。これらは tsInputModeDefaultStateKeytrue ですが、tsInputModeIsVisibleKeyfalse になっていて、ユーザーの目には入らないようになっています。このように内部的に使う入力モードも定義できます。

4.2 モード対応の実装

NumberInput では、ConversionEngine クラスでモード関連のメソッドが実装されていましたが、KIM2 では入力文字が変わるだけですので、変換エンジン内でモード対応は全く必要ありません。そのため、KIM2Controller クラス内でのみ、関連メソッドを実装すれば OK です。

当然のことながら、現在の入力モードを保持したり、各メソッドも入力モード対応に変更しなければなりません。関連メソッドを見る前に、そのあたりの変更点をまず見てみます。

4.2.1 KIM2Controller.h

ヘッダファイルに対する追加は、以下のようになります。

KIM2Controller.h
... enum { // 内部のモード保持用 KIM2HiraganaMode = 1, KIM2KatakanaMode = 2, KIM2FullWidthMode = 3, KIM2HalfWidthMode = 4 }; // 文字列定数 NSString* KIM2HiraganaModeName = @"com.apple.inputmethod.Japanese"; NSString* KIM2KatakanaModeName = @"com.apple.inputmethod.Japanese.Katakana"; NSString* KIM2FullWidthModeName = @"com.apple.inputmethod.Japanese.FullWidthRoman"; NSString* KIM2HalfWidthModeName = @"com.apple.inputmethod.Roman"; NSString* KIM2KeyboardName = @"KIM2Special"; @interface KIM2Controller : IMKInputController { ... NSInteger _capsOnMode; // キャプスロックオン時のモード NSInteger _capsOffMode; // キャプスロックオフ時のモード NSInteger _currentMode; // 現在のモード } ... - (void)changeToMode:(NSInteger)mode client:(id)sender; // 内部的なモード変更で使用 ... }

内部的な列挙定数は、モード判定や代入を簡単にするためです。_capsOnMode と _capsOffMode は、通常の入力メソッドでは必要ないでしょう。キャプスロックによる切替を行っているので、各状態でのモードを記憶しています。

4.2.2 以前からのメソッドの変更点

initWithServer: delegate: client: で、各インスタンス変数の初期値を入力します。handleEvent: client: は、以下のようになります。

KIM2Controller.m > handleEvent: client:
- (BOOL)handleEvent:(NSEvent*)event client:(id)sender { BOOL handled = NO; unsigned int modifiers = [event modifierFlags]; unsigned int cmdFlags = (NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask); unsigned short keyCode = [event keyCode]; if ([event type] == NSKeyDown) { // キー押下イベントの場合 NSString *chars = [event characters]; UniChar unicharCode = 0; if (modifiers & NSAlphaShiftKeyMask) { // キャプスロックオンの場合 if (_currentMode != _capsOnMode) { [self changeToMode:_capsOnMode client:sender]; } } else { if (_currentMode != _capsOffMode) { // キャプスロックオフの場合 [self changeToMode:_capsOffMode client:sender]; } } if ([chars length] > 0) { // 入力文字があれば unicharCode = [chars characterAtIndex:0]; // 1文字目を取得 NSLog(@"KIM2: handleEvent code = %x string=%@", unicharCode, chars); // ここでコマンド入力をチェックする if (modifiers & cmdFlags) { // コマンド関連なら if (modifiers & NSControlKeyMask) { NSLog(@"handleEvent: receives control key"); switch (unicharCode) { case 0x61: // a [self changeToMode:KIM2HiraganaMode client:sender]; handled = YES; break; case 0x64: // d [self changeToMode:KIM2HalfWidthMode client:sender]; handled = YES; break; case 0x66: // f [self changeToMode:KIM2FullWidthMode client:sender]; handled = YES; break; case 0x73: // s [self changeToMode:KIM2KatakanaMode client:sender]; handled = YES; break; default: break; } } // 通常入力の場合を処理 } else if (unicharCode > 0x3040) { // ひらがな以上なら if (_didConvert) { // 変換中なら [self commitComposition:sender]; // 先に確定しておく } switch (unicharCode) { case 0x309B: // 濁点 handled = [self convrtToDakuonWithSender:sender]; if (handled == NO) { [self originalBufferAppend:chars client:sender]; // 入力を追加する handled = YES; } break; case 0x309C: // 半濁点 handled = [self convrtToHanDakuonWithSender:sender]; if (handled == NO) { [self originalBufferAppend:chars client:sender]; // 入力を追加する handled = YES; } break; default: if (_currentMode == KIM2KatakanaMode) { // カタカナモードなら chars = [self convertToKatakanaFrom:chars]; } [self originalBufferAppend:chars client:sender]; // 入力を追加する handled = YES; break; } } else { // ひらがな以外なら if (unicharCode < 0x20) { // ノンプリンティングキャラクタなら switch (unicharCode) { case 0x08: // バックスペースキー [self deleteBackward:sender]; handled = YES; break; case 0x09: // TABキー if (_useBuffer) { // バッファ内に文字があれば [self commitComposition:sender]; // 先に確定しておく } // 処理はクライアントにまかせる break; case 0x03: case 0x0D: // RETURN if (_useBuffer) { // バッファ内に文字があれば [self commitComposition:sender]; // 確定する handled = YES; } // 他の場合はクライアントにまかせる break; default: if (_useBuffer) { // バッファ内に文字があれば [self commitComposition:sender]; // 先に確定しておく } // 処理はクライアントにまかせる break; } } else { // 英数字プリンティングキャラクタなら switch (unicharCode) { case 0x7F: // DELETE // 現在のところ、処理はクライアントにまかせる // 変換中のカーソル移動をサポートした後は処理が必要!!!!! break; case 0x20: // SPACE KEY if (_didConvert == NO) { // 変換中でないなら handled = [self uniqueConvertWithClient:sender]; // 一意変換を試みる if (handled == NO) { if (_useBuffer) { // バッファ内に文字があれば [self commitComposition:sender]; // 先に確定しておく } [sender insertText:chars replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; // 入力を直接利用側に送る } } break; default: // 通常の英数字プリンティングキャラクタ if (_useBuffer) { // バッファ内に文字があれば [self commitComposition:sender]; // 先に確定しておく } if (_currentMode == KIM2FullWidthMode) { // 全角モードなら chars = [self convertToFullWidthFrom:chars]; } [sender insertText:chars replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; // 入力を直接利用側に送る handled = YES; break; } } } } } return handled; }

当初、キャプスロックは if ([event type] == NSFlagsChanged) { で、押下や離されたのを判定していました。注意するのは、これを受けとるためには、IMKStateSetting プロトコルの recognizedEvents: を実装する必要があることです。デフォルトでは、このメソッドは認識するイベントとして NSKeyDownMask だけを返します。これに加えて NSKeyUpNSFlagsChanged も認識することを表明しなければなりません。NSFlagsChanged だけだと、キャプスロックが離された時に反応しないので注意してください。

その後、さまざまな実装を経て、結局、毎回キャプスロックをチェックする形へと落ち着きました。特に、この入力メソッドではキャプスロックで日本語と英字入力を切り替えているので、そのあたりを調整するのがややこしくなってしまい、毎回チェックするほうが単純でエラーが少なく、効率もいいだろうと考えました。

余談ですが、最初の形だと、キャプスロック状態を保持して押されたり離されたりした時に、保持している状態を変更することになります。入力時はキャプスロック判定できますが、初期化時などはどうすればいいのでしょう。これを行うには IOKit を使います。IOKit で接続機器を調べられるので、それを調べてキーボードタイプの機器を取り出して、キャプスロックの状態をチェックできます。

これで OK だといいのですが、USB ポートは複数ありチェーンも行えます。つまり、同時にキーボードが複数接続されていて、それぞれのキャプスロック状態が違う、ということもありうるわけです。実際に、私が他の人の PC をいじった際、キーボードが小さすぎるので、別のキーボードを差して操作したことがあります。こういう状況に対処していなければ、保持している状態と実際のキャプスロック状態にずれが生じることになります。そのため、接続機器の状態をチェックしなければならず、結局、毎回入力時に判定するほうが効率がいいと考えたわけです。

コマンド処理は、とりあえずの形です。独自のショートカットを設定できるようにするためには、環境設定とあわせて実装する必要があります。また、キーバインディングに対応したり、それとの衝突を避けるなら、もっと違う手段が必要になるでしょう。一般化された形にコマンド入力をパッケージ化し、それを別メソッドに送信して、そこで判定するのが良いと思いますが、ここでは、やっつけ仕事をしています。

後は、ひらがな処理と英字処理において、モードをチェックして必要ならカタカナや全角へと変換しているだけです。

changeToMode: client: は後で説明しますが、内部的にモード変更を行う場合に呼び出すメソッドです。convertToKatakanaFrom:convertToFullWidthFrom: は、それぞれ、カタカナと全角へと変換するメソッドです。

4.2.3 入力モード関連メソッド

さて、次はユーザーがメニューから入力モードを選択した場合に呼ばれるメソッドです。

KIM2Controller.m > setValue: forTag: client:
-(void)setValue:(id)value forTag:(unsigned long)tag client:(id)sender { NSLog(@"setValue:%@ forTag:%d client:",value, tag); if (tag == kTSMDocumentInputModePropertyTag) { // モード変更なら NSString* newModeString = (NSString*)value; // モード文字列値をキャスト if ( [newModeString isEqual:KIM2HiraganaModeName] ) { _capsOnMode = KIM2HiraganaMode; _currentMode = KIM2HiraganaMode; } else if ( [newModeString isEqual:KIM2KatakanaModeName] ) { _capsOnMode = KIM2KatakanaMode; _currentMode = KIM2KatakanaMode; } else if ( [newModeString isEqual:KIM2FullWidthModeName] ) { _capsOffMode = KIM2FullWidthMode; _currentMode = KIM2FullWidthMode; } else if ( [newModeString isEqual:KIM2HalfWidthModeName] ) { _capsOffMode = KIM2HalfWidthMode; _currentMode = KIM2HalfWidthMode; } [sender overrideKeyboardWithKeyboardNamed:KIM2KeyboardName]; } }

リファレンスには、このメソッドがどのようなタグ値で呼び出されるかについて説明されていません。一度このメソッドを実装した後で、ログ出力されたタグ値を 4 文字コードへと変換し、それを検索することで、TextServices.h で宣言されていることを見つけ、『Text Services Manager リファレンス』で「定数」の項の「ドキュメントのプロパティタグ」で説明されていることがわかりました。

このメソッドが呼び出される時には、メニューの選択などの処理は行われているので内部的な情報を設定しているだけです。キーボードを選択しなおしていますが、スクリプトの変更などモード切替の際にキーボードが違うものになってしまうことがあるので、ここで設定しています。

これとは逆に、ユーザー選択ではなく、内部的に入力モードを変更したい場合、IMKTextInput プロトコル の selectInputMode: を呼び出すことになります。内部的な情報もあわせて設定するため、以下のメソッドを実装します。

KIM2Controller.m > changeToMode: client:
- (void)changeToMode:(NSInteger)mode client:(id)sender; { NSString* newModeString; switch (mode) { case KIM2HiraganaMode: newModeString = KIM2HiraganaModeName; break; case KIM2KatakanaMode: newModeString = KIM2KatakanaModeName; break; case KIM2FullWidthMode: newModeString = KIM2FullWidthModeName; break; case KIM2HalfWidthMode: newModeString = KIM2HalfWidthModeName; break; default: NSLog(@"KIM2Controller error: unknown mode !!!"); break; } NSLog(@"KIM2Controller changeToMode:%@", newModeString); [sender selectInputMode:newModeString]; }

とりあえずの実装です。このように、selectInputMode: で文字列を渡してやれば、入力メソッドメニューの選択状態も変更されます。

4.2.4 入力モードにおける処理

次は、カタカナモードや全角モードで使われていた変換メソッドです。

KIM2Controller.m > convertToKatakanaFrom:
- (NSString*)convertToKatakanaFrom:(NSString*)string { NSMutableString* mstring = [string mutableCopyWithZone:nil]; NSString* result = string; if (mstring) { if (CFStringTransform ((CFMutableStringRef)mstring, NULL, kCFStringTransformHiraganaKatakana, FALSE)) { result = [mstring autorelease]; } } return result; }

このように、CFMutableString の文字種変換機能を使っているだけです。失敗した場合は、元のままの文字列が返ることになります。全角変換も同様になりますが、渡す定数が kCFStringTransformFullwidthHalfwidth(全角から半角へ)になるので、CFStringTransform の 4 つ目の引数が TRUE となり、逆変換を指定することになることに注意してください。

4.3 メニュー

さて、いままでメニューを実装していなかったので、実行時に例外が発生していました。挙動がおかしくなったりして、デバッグにも支障をきたすので、これを実装しておきます。環境設定については、現時点では必要ないので、まだ実装しません。

まず、MainMenu.nib 内で、Menu を作成します。

KIM2ApplicationDelegate.h 内で、アウトレットとして _menu と、メニューを返すメソッドを宣言します。

KIM2ApplicationDelegate.h
@interface KIM2ApplicationDelegate : NSObject { IBOutlet KIM2UniqueConversionEngine* _uniqueConversionEngine; IBOutlet NSMenu* _menu; // メニュー } -(KIM2UniqueConversionEngine*)uniqueConversionEngine; -(NSMenu*)menu; @end

実装ファイルで、以下のようにメソッドを実装します。

KIM2ApplicationDelegate.m > menu
-(NSMenu*)menu { return _menu; }

そして、IB で Menu インスタンスを、Application Delegate の _menu アウトレットに接続します。これでメニューが表示されるようになりました。この時点では、返されるメニューには、アクションが 1 つも設定されていないため、表示させた場合、灰色の無効表示になることに注意してください。

メニューコマンドをどう扱うかについては、次の段階で考えることにします。環境設定とのかねあいで、ショートカット設定をどう保存し、どう使用するか等、他の要因を考える必要があるからです。ここでは、このメニューのうち、上から 4 つにアクションを設定し、それらを有効にするだけにしておきます。

メニューは、KIM2ApplicationDelegate でアウトレットとして保持されていますが、IMKInputController クラスに menu コマンドがあります。実際のメニューは、ここで提供されることになります。この時に、必要ならメニュー項目の有効・無効化なども行います。

KIM2Controller.m > menu
-(NSMenu *)menu { NSMenu* nibMenu = [[NSApp delegate] menu]; NSString* text = (_didConvert ? _composedBuffer : _originalBuffer); if ([text length] > 0) { NSMenuItem* item = [nibMenu itemWithTag:1]; // 項目を取得 if (item) { [item setAction:@selector(convertToHiragana:)]; // その項目にアクションを設定 } item = [nibMenu itemWithTag:2]; if (item) { [item setAction:@selector(convertToKatakana:)]; } item = [nibMenu itemWithTag:3]; if (item) { [item setAction:@selector(convertToHalfWidth:)]; } item = [nibMenu itemWithTag:4]; if (item) { [item setAction:@selector(convertToFullWidth:)]; } } return nibMenu; }

このように、バッファ内に変換可能な文字列がある場合に、アクションを割り当てています。それぞれのアクションは、基本的には以前に説明したカタカナモードなどの文字種変換メソッドと同じです。

KIM2Controller.m > convertToKatakana:
- (void)convertToKatakana:(id)sender { NSLog(@"convertToKatakana:"); NSString *source = (_didConvert ? _composedBuffer : _originalBuffer); NSMutableString* mstring = [source mutableCopyWithZone:nil]; if (mstring) { if (CFStringTransform ((CFMutableStringRef)mstring, NULL, kCFStringTransformHiraganaKatakana, FALSE)) { [_composedBuffer setString:mstring]; [[self client] setMarkedText:_composedBuffer selectionRange:NSMakeRange(_insertionIndex, 0) replacementRange:NSMakeRange(NSNotFound,NSNotFound)]; _didConvert = YES; } [mstring release]; } }

上のメソッドで注意すべきは、sender を利用せずに、[self client] を使って、直接利用側へと文字列を渡していることです。sender へと文字列を渡すと例外が発生します。これはメニュー選択を扱うオブジェクトに対して、テキスト処理関連コマンドが送信されてしまうなどの理由によるものだと思われます。実装途中でのテストによる推測なので、別の部分のコードに不備があって、例外が発生した可能性もありますが、こうしておきます。

コマンドを実装することで、_didConvert が実際に使用されることになりました。基本的に変換後は確定された後で次の文字が追加されることになるので、_didConvertYES の場合は _composedBuffer を取得して、それに対して操作を行うようにすればいいだけです。厳密には、_insertionIndex_composedBuffer 内での位置を指すように変更する必要がありますが、ここでは文字種変換メソッドのため、それを行っていません。もしかすると、例外的な場合がありうるかもしれませんが…。

4.4 テスト

この状態で、ビルドしてテストしてみます。まずシステムの言語環境設定を見てみます。

このように、作成したモードが表示されているのがわかります。さらに入力メソッド選択メニューはつぎのようになっています。

テストとして、ひらがなモードで「きと」を入力し、キーボードからのコマンドでカタカナモードに切替えて「きと」を入力し、キャプロックを外し「きと」と同じキーを押し、全角英字に切替えて同じキーを押します。その後、ひらがなモードに切替えて「きと」を入力し、メニューからカタカナに変換します。各段階ごとにリターンで確定しています。この時、カタカナ変換をする時のメニュー表示は、きちんと有効になっています。

また、テスト終了後は、次のような状態になっています。

コンソール表示は、次のようになります。

入力メソッドを選択 initWithServer activateServer setValue:com.apple.inputmethod.Japanese forTag:1768778093 client: initWithServer 入力開始 handleEvent code = 304d string=き handleEvent code = 3068 string=と handleEvent code = d string=^M commitComposition キーボードからカタカナに切替 handleEvent code = 73 string=s handleEvent: receives control key KIM2Controller changeToMode:com.apple.inputmethod.Japanese.Katakana setValue:com.apple.inputmethod.Japanese.Katakana forTag:1768778093 client: 入力開始 handleEvent code = 304d string=き handleEvent code = 3068 string=と handleEvent code = d string=^M commitComposition キャプスロック外す KIM2Controller changeToMode:com.apple.inputmethod.Roman setValue:com.apple.inputmethod.Roman forTag:1768778093 client: handleEvent code = 74 string=t handleEvent code = 68 string=h メニューから全角英字に切替 setValue:com.apple.inputmethod.Japanese.FullWidthRoman forTag:1768778093 client: handleEvent code = 74 string=t handleEvent code = 68 string=h メニューからひらがなに切替 setValue:com.apple.inputmethod.Japanese forTag:1768778093 client: handleEvent code = 304d string=き handleEvent code = 3068 string=と メニューからカタカナ変換を選択 convertToKatakana: handleEvent code = d string=^M commitComposition handleEvent code = 304d string=き handleEvent code = 3068 string=と キーボードからカタカナ変換ショートカット convertToKatakana: handleEvent code = d string=^M commitComposition

注意すべきは、内部的なモード変換の後で、通常のメニュー選択時のメソッドが呼び出されていることです。また、キーボードからショートカットを入力したとき、handleEvent の前にアクションが呼ばれていることもわかります。

(以下、次ファイルへと続く。)


前へ 次へ

管理人:神吉 秀典 E-mail:puer@ba.wakwak.com