NumberInput_IMKit_Sample
以下は、Mac OS X 10.5.5 上の Xcode 3.1 で説明しています。このサンプルは、NumberInput 0 から NumberInput 4 までの 5 段階に分かれていて、何もしないものから次第に高度な機能をもつものへと拡張されていきます。
3 NumberInput 2
3.1 ReadMe の日本語訳
NumberInput プロジェクトのこのバージョンは、変換モードを持っています。
NumberInput 入力メソッドは、NSNumberFormatter クラスによってサポートされているすべての NSNumberFormatterStyle をサポートします(file:///Developer/Documentation/DocSets/com.apple.ADC_Reference_Library.
CoreReference.docset/Contents/Resources/Documents/documentation/Cocoa/Reference/
Foundation/Classes/NSNumberFormatter_Class/index.html を見てください。)
NSNumberFormatterStyle は、テキスト入力メニューから入力モードを選択することで設定されます。
入力メソッドにモードのサポートを追加するには、以下を行うことが必要です。
Info.plist ファイルに ComponentInputModeDict ディクショナリを追加します。ComponentInputModeDict ディクショナリは、テキスト入力メニューと、言語環境環境設定パネル内にどのようにモードが表示されるかを記述します。
-(void)setValue:(id)value forTag:(unsigned long)tag client:(id)sender メソッドを実装します。このメソッドはモード文字列をとり、現在のモードを設定するためにそれを使います。NumberInput の場合、文字列モードを NSNumberFormatterStyle 定数へと対応付け、それから変換エンジンに新しいモードを知らせます。
これらの変更を行った後で、入力メソッドをビルドし、それを /Library/Input Methods/ へとコピーします。モードを追加したので、ログアウトして再びログインする必要があるかもしれません。
ログアウトして再びログインした後で、言語環境の入力メニューパネルを開き、チェックを入れます。ここにすべてのモードのリストがあるはずです。ここで、これらのモードを選択、または選択解除できます。
これで、モードを選択した場合、数値変換は設定した書式設定タイプに変換を行うことになるでしょう。
3.2 使ってみる
ビルドとインストール方法は、前回と同じです。このサンプルでは、ReadMe で説明されているように、モードが追加されています。そのため環境設定で、以下のように表示が変わります。
テキストエディットに移動し、テキスト入力メニューから「Decimal」モードを選んだとき、入力メニューの状態は以下のようになります。
この状態で、「12345.6789」と入力し変換キー(スペースキー)を押します。そしてリターンで改行し、それからモードを次に変更し、同じ文字並びを入力し、以下同様にします。Spellout(書き出し)モードまで行ったときの結果は、以下のようになります。
Currency(通貨)モードと Percent(パーセント)モードでは丸めが起こっていることがわかります。また、日本語環境なので Spellout(書き出し)モードでは、英語ではなく、漢数字になっていて、ちゃんと「万」が入っていることに注目してください。コンソールを見てみると、以下のようなログが出力されています。
NumberInput[151] set mode to 1
NumberInput[151] convert:
number: 12345.6789
string: 12345.6789
NumberInput[151] set mode to 2
NumberInput[151] convert:
number: 12345.6789
string: 12345.6789
...
3.3 グループとファイル
プロジェクトの構成は、以下のようになっています。
モード用の画像などが追加されているだけで、ファイルそのものは前と同じです。クラスも前回と同じです。MainMenu.nib が同じであることに注意してください。入力モードがテキスト入力メニューに表示されていますが、これらは情報プロパティリストの定義によって自動的に作成されています。
3.4 Info.plist ファイルと InfoPlist.strings ファイル
ReadMe で書かれているように、Info.plist ファイルにモード用の項目が追加されています。それ以外の部分は同じなので、変わった部分だけを示します。
 |
Info.plist > 追加部分
<!-- 入力メソッドがモードを含むなら、ComponentInputModeDict を含めなければならない -->
<!-- ComponentInputModeDict についての詳細は、tech note TN2128 を見てください -->
<!-- この ComponentInputModeDict は、tech note から直接とられていることに注意 -->
<key>ComponentInputModeDict</key>
// コンポーネント入力モードディクショナリ
<dict>
<key>tsInputModeListKey</key>
// 入力モードリスト
<dict>
<key>com.apple.inputmethod.decimal</key>
// 十進モードの識別子
<dict>
<key>tsInputModeAlternateMenuIconFileKey</key>
// 代替アイコン
<string>Decimal.tiff</string>
<key>tsInputModeDefaultStateKey</key>
// デフォルト状態
<true/>
// 言語環境でデフォルトで有効になる
<key>tsInputModeIsVisibleKey</key>
// 入力モード表示
<true/>
// システム UI 内に表示される
<key>tsInputModeKeyEquivalentKey</key>
// ショートカット
<string>D</string>
<key>tsInputModeKeyEquivalentModifiersKey</key>
// ショートカット修飾キー
<integer>4608</integer>
// コントロール・シフト
<key>tsInputModeMenuIconFileKey</key>
// メニューアイコン
<string>Decimal.tiff</string>
<key>tsInputModePaletteIconFileKey</key>
// パレットアイコン
<string>Decimal.tiff</string>
<key>tsInputModePrimaryInScriptKey</key>
// スクリプト内で主要か
<true/>
// このスクリプト内で主要(優先される)
<key>tsInputModeScriptKey</key>
// スクリプト
<string>smUnicodeScript</string>
</dict>
<key>com.apple.inputmethod.currency</key>
... 以下同様 ...
</dict>
<key>tsVisibleInputModeOrderedArrayKey</key>
// 入力モードの表示順配列
<array>
<string>com.apple.inputmethod.decimal</string>
<string>com.apple.inputmethod.currency</string>
<string>com.apple.inputmethod.percent</string>
<string>com.apple.inputmethod.scientific</string>
<string>com.apple.inputmethod.spellout</string>
</array>
tsInputModeListKey キーの値であるディクショナリ内で、各入力モードについて記述し、後の tsVisibleInputModeOrderedArrayKey キーの値である配列で、それらの入力モードの表示順を指定します。com.apple.inputmethod.decimal は、既定義の値のように見えますが、同じ形をもった独自に定義した入力モードです。コメントに書いているように、詳しいことは『技術ノート TN2128: テキストサービスマネージャー (TSM) についてよく聞かれる質問』(正規訳なし、当サイトで翻訳済)を見てください。また、『Text Services Manager リファレンス』にさらに詳しい説明があります。こちらを見ると、他にもキーがあることがわかります。「入力モードのディクショナリキー」から「個々の入力モードキー」に説明されていますが、関数 CopyTextServiceInputModeList の「解説」のリスト内にも説明が書かれています。既定義の入力モード()については、Carbon.framework 内の HIToolbox/TextServices.h に記載されています。
さて、『技術ノート TN2128: テキストサービスマネージャー (TSM) についてよく聞かれる質問』で説明されているように、入力メソッドバンドル名や、入力モード名に対して InfoPlist.strings ファイルを使ってローカル化を提供できます。このファイルを見てみましょう。
InfoPlist.strings
/* Info.plist キーのローカル化バージョン */
NSHumanReadableCopyright = "© Apple Computer Inc., 2006";
/* ここにモード文字列を含める。詳細は TN2128 を見てください。 */
com.apple.inputmethod.decimal = "Decimal";
com.apple.inputmethod.currency = "Currency";
com.apple.inputmethod.percent = "Percent";
com.apple.inputmethod.scientific = "Scientific";
com.apple.inputmethod.spellout = "Spellout";
CFBundleName = "NumberInput";
CFBundleDisplayName = "NumberInput";
CFBundleShortVersionString = "NumberInput version 0.1";
CFBundleGetInfoString = "NumberInput version 0.1, Copyright 2006, Apple Computer Inc.";
プロジェクトには「English」しかありませんが、「ローカリゼーションを追加」で他の地域用を追加できます。
さて、それではクラスファイルを見ていきます。前回と同じ順番で、細部から全体へと見ていきます。
3.5 ConversionEngine クラス
ヘッダには、-setConversionMode: が追加されいるだけで、他は同じです。前回は書式設定スタイルは、十進モードのみでしたが、入力モードが選択できるようになったため、書式設定スタイル用のインスタンス変数が実際に利用されています。そのため、-conversionMode メソッドも修正されています。また、実装ファイルには、nib から読み込まれた時に、デフォルトの書式設定スタイルで初期化するために -awakeFromNib も追加されています。また、-convert: メソッドも少し修正されています。まず、-awakeFromNib を見てみます。
ConversionEngine.m > awakeFromNib
-(void)awakeFromNib
{
[self setConversionMode:NSNumberFormatterDecimalStyle];
}
単に、書式設定スタイルをアクセサを使って設定しているだけです。ここで十進スタイルを選択していますが、これは、情報プロパティリスト内で、これがスクリプト内での主要入力モードになっていたからです。この 2 つは自動で連動しないので、気をつけて同じものを指定する必要があります。つぎに、-conversionMode メソッドを見てみます。前回は、単に十進スタイル定数を返していただけでした。
ConversionEngine.m > conversionMode
-(NSNumberFormatterStyle)conversionMode {
return conversionMode;
}
今度は、定数ではなく、インスタンス変数を返しています。これに対応する設定メソッドは、以下のようになります。
ConversionEngine.m > setConversionMode:
-(void)setConversionMode:(NSNumberFormatterStyle)mode
{
conversionMode = mode;
NSLog(@"set mode to %ld", conversionMode);
}
インスタンス変数に直接代入しています。ちなみに NSNumberFormatterStyle は NSUInteger なので問題ありません。同時にログ出力しています。これは上に示した入力モードメニューを選択した場合のログ出力に対応します。つぎは、修正された -convert: メソッドです。前回と同じコメント部分は省いています。
ConversionEngine.m > convert:
-(NSString*)convert:(NSString*)string
{
if ( formatter == nil ) {
[NSNumberFormatter setDefaultFormatterBehavior:NSNumberFormatterBehavior10_4];
formatter = [[NSNumberFormatter alloc] init];
}
[formatter setNumberStyle:
NSNumberFormatterNoStyle];
// 文字列から数値へはスタイルなしで変換
NSNumber* number = [formatter numberFromString:string];
NSLog(@"convert:\n\tnumber: %@\n\tstring: %@", number, string); // ログ出力
[formatter setNumberStyle:[self conversionMode]];
// 先にスタイルなしにしたので数値から文字列に変換する前に現在のスタイルを設定
return [formatter stringFromNumber:number];
}
赤い部分が修正された所です。スタイル変更にかかわる部分が修正されていることがわかります。
つぎは、NumberInputApplicationDelegate クラスですが、これは前回から全く変更されていません。そのため、入力コントローラークラスへと進みます。
3.6 NumberInputController クラス
ヘッダファイルの修正点は、インターフェース宣言部の前に、定数が置かれたことです。
NumberInputController.h > 追加定数
const NSString* kDecimalMode = @"com.apple.inputmethod.decimal";
const NSString* kCurrencyMode = @"com.apple.inputmethod.currency";
const NSString* kPercentMode = @"com.apple.inputmethod.percent";
const NSString* kScientificMode = @"com.apple.inputmethod.scientific";
const NSString* kSpelloutMode = @"com.apple.inputmethod.spellout";
これらは、入力モードそれぞれの識別子で、情報プロパティリスト内のキーと対応しています。
つぎに実装ファイルですが、まず commitComposition: が少し修正されています。
NumberInputController.m > commitComposition:
-(void)commitComposition:(id)sender
{
NSString* text = [self composedBuffer];
if ( text == nil || [text length] == 0 ) {
text = [self originalBuffer];
}
[sender insertText:text replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
[self setComposedBuffer:@""];
[self setOriginalBuffer:@""];
_insertionIndex = 0;
_didConvert = NO;
}
赤字の部分が追加されました。これは単なるバグ修正で、入力モード追加とは関係ありません。また、convert: client: も少し修正されています。
NumberInputController.m > convert: client:
- (BOOL)convert:(NSString*)trigger client:(id)sender
{
NSString* originalText = [self originalBuffer];
NSString* convertedString
= [self composedBuffer];
BOOL handled = NO;
if ( _didConvert && convertedString && [convertedString length] > 0 ) {
NSString* completeString = [convertedString stringByAppendingString:trigger];
[sender insertText:completeString
replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
[self setComposedBuffer:@""];
[self setOriginalBuffer:@""];
_insertionIndex = 0;
_didConvert = NO;
handled = YES;
}
else if ( originalText && [originalText length] > 0 ) {
convertedString = [[[NSApp delegate] conversionEngine] convert:originalText];
[self setComposedBuffer:convertedString];
if ( [trigger isEqual: @" "] ) {
[sender setMarkedText:convertedString
selectionRange:NSMakeRange(_insertionIndex, 0)
replacementRange:NSMakeRange(NSNotFound,NSNotFound)];
_didConvert = YES;
}
else {
[self commitComposition:sender];
[sender insertText:trigger
replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
}
handled = YES;
}
return handled;
}
これは、入力が十進数字でない場合に呼ばれたメソッドでした。これも基本的にバグ修正です。前回は、変換済かどうかのチェックの後に変換済みバッファが取得されていましたが、今回は最初に取得されています。そして、変換済みかどうかのチェックがその後のチェックといっしょにされ、全体に入れ子のレベルが少なくされています。
さて、前回存在してエラーを起こしていた menu メソッドが除去されています。そして、setValue: forTag: client: が追加されています。これは、IMKStateSetting プロトコルで宣言されているメソッドです。
NumberInputController.m > setValue: forTag: client:
// このメソッドは、ユーザーがテキスト入力メニューから新しい入力モードを選択した時、
// InputMethodKit によって呼ばれる
-(void)setValue:(id)value forTag:(unsigned long)tag client:(id)sender
{
NSString* newModeString = [(NSString*)value retain];
// 新しいモード文字列値を保持
NSNumberFormatterStyle currentMode
= [[[NSApp delegate] conversionEngine] conversionMode];
// 現在のモードを取得
NSNumberFormatterStyle newMode;
// 新しいモードの書式設定スタイル格納用
if ( [newModeString isEqual:kDecimalMode] ) {
// ヘッダで定義した定数と比較
newMode = NSNumberFormatterDecimalStyle;
// スタイルを格納
}
else if ( [newModeString isEqual:kCurrencyMode] ) {
// 以下同様
newMode = NSNumberFormatterCurrencyStyle;
}
else if ( [newModeString isEqual:kPercentMode] ) {
newMode = NSNumberFormatterPercentStyle;
}
else if ( [newModeString isEqual:kScientificMode] ) {
newMode = NSNumberFormatterScientificStyle;
}
else if ( [newModeString isEqual:kSpelloutMode] ) {
newMode = NSNumberFormatterSpellOutStyle;
}
if ( currentMode != newMode ) {
// 変更前のモードと違っていたら
[[[NSApp delegate] conversionEngine] setConversionMode:newMode];
// 変換エンジンに新しいモード設定しておく
}
}
コメントで十分わかると思います。リファレンスの説明では、いまいち判らなかった、このメソッドが利用法がこれでわかります。入ってきた値を保持していて、対応する解放 (release) メソッドがないですが、これは単なる間違いだと思われます。isEqual: を呼んでいる間、存在していることを確実にしたいためでしょう。最後のチェック前に解放して OK だと思います。ここでは、新しい入力モードに対して変換用の書式設定スタイルを設定し、前のモードと違っていたら、変換用エンジンに新しいスタイルを設定しているだけです。
さて、これでこのサンプルの説明は終わりました。TN2128 を読むと、入力モードに対応するためには、たくさんの事を行わなければなりませんでしたが、このサンプルで追加されたコードはごくわずかです。入力メソッドキットが、他の必要な作業を行ってくれているのだと思われます。コンポーネントを利用したものに比べて、非常に簡単に入力モードが使えるようになったようです。(以下、次ファイルへと続く。)
管理人:神吉 秀典 E-mail: