Cocoa で独自の Text View を実装する

2008/11/08 3:00am

iPhone も盛り上がっていることだし、ひさしぶりに Cocoa プログラミングを始めてみる。

Mac Dev Center をつらつらと眺めてみると、Managing Concurrency with NSOperation という記事に興味が湧く。NSOperationNSOperationQueue(優先順位付きキュー)を使うことで、タスクのスケジューリングと並行処理が手軽に行えるようになったようだ。Java の concurrency パッケージに感銘を受けた身としては、こういう拡張は嬉しい。今後、マルチスレッドが必要になったときに、改めて詳しく調べてみよう。

テキストシステム周りも、ひところに比べると充実してきている。Mac OS X 10.3 では NSATSTypesetter が公開された。更に Mac OS X 10.5 では NSTextInputClient プロトコルと Input Method Kit が追加されている(他にもあるかも)。

さて、Cocoa プログラミングのリハビリとして、今回は独自の Text View を実装してみようと思う。

ここでいう Text View とは NSTextView のように、IM からの入力を受け取り、テキストを表示、編集できるクラスのことだ。エディタやワープロといったアプリケーションを開発するときには必要になってくるだろう。

NSView のサブクラス

ドキュメントによると、独自の Text View を実装する場合は NSTextView のサブクラスか、NSView のサブクラスを作るようなので、今回は NSView のサブクラスとして作成することにする。

NSView のサブクラスを作るとして、名前は単純に MyTextView としよう。まずは MyTextView.h で、クラスの @interface を書く。

#import <Cocoa/Cocoa.h>

@interface MyTextView : NSView {
  NSMutableAttributedString *_text;
}
@end

インスタンス変数として NSMutableAttributedString をひとつ持つ。ここに表示、編集するテキストの内容を保持するわけだ(MVC も糞もない設計だが、今回のはあくまで例なので単純さを優先する)。

First Responder

最初にすることは、このクラスがキー入力を受け取れるようにすることだ。そのためには、キー入力やマウスクリックなどのイベントを受け取るれるように、acceptsFirstResponder をオーバーライドする必要がある。

- (BOOL) acceptsFirstResponder {
  return YES;
}

さて、これでキー入力イベントを受け取れるようになったので、今度は keyDown: を実装しよう。

- (void) keyDown: (NSEvent *) theEvent {
  [self interpretKeyEvents: [NSArray arrayWithObject: theEvent]];
}

これだけ。そのまま interpretKeyEvents: に投げている。

interpretKeyEvents:

interpretKeyEvents:NSInputManager を通して、最終的に、ビューの insertText:insertNewline: などを呼び出すので、あとはこれらのメソッドを実装すればいい。

今回は改行、タブ、削除、通常のテキスト入力など、最低限必要なメソッドだけを実装している。

- (void) deleteBackward: (id) sender {
  const NSUInteger length = [_text length];

  if (length > 0) {
    [_text deleteCharactersInRange: NSMakeRange(length - 1, 1)];
    [self setNeedsDisplay: YES];
  }
}

- (void) insertNewline: (id) sender {
  [self insertText: @"\n"];
}

- (void) insertTab: (id) sender {
  [self insertText: @"\t"];
}

- (void) insertText: (id) aString {
  [[_text mutableString] appendString: aString];
  [_text addAttribute: NSFontAttributeName
                value: [NSFont userFontOfSize: 18.0f]
                range: NSMakeRange(0, [_text length])];
  [self setNeedsDisplay: YES];
}

なお、insertText: に渡される aStringNSString とは限らず、NSAttributedString の可能性もあるので、本来はそれも考慮したコードにするべきだ。

テキストの表示

最後に、描画部分を実装しよう。テキストのレイアウトと描画は NSAttributedStringdrawInRect: に任せてしまう。

- (void) drawRect: (NSRect) theRect {
  [[NSColor whiteColor] set];
  NSFrameRect(theRect);

  NSRectFill(theRect);
  [[NSColor grayColor] set];
  NSFrameRect(theRect);

  [_text drawInRect: NSMakeRect(
      theRect.origin.x + 5.0f, theRect.origin.y,
      theRect.size.width, theRect.size.height)];
}

以上で、簡単ではあるが、テキストの入力と削除が可能な Text View が出来た。

日本語変換、入力など、更に複雑な操作をサポートするためには、NSTextInput や、それを拡張した NSTextInputClient を実装する必要がある。更に、独自のレイアウトも実装することになるだろう。実用的な Text View を開発するのはかなりの労力がいりそうだ。

なお、今回のソースコードは gist:23017 に置いてある。

参考