Macintosh Developer Online (MDOnline)


2001年12月27日発行号 - まだまだ続くCocoa-Java



ニュースがないので解説記事を書いていますが、多分明日も配信することになるかと思います。年末といえば年賀状ですが、まだ1枚も書いていません。住所録の整備をしたまでになっています。年末らしいことをしたといえば、ゴミ回収のスケジュールがあるので、古い雑誌類をまとめたことくらいです。本棚は少しゆとりができましたけど、腰が痛くなってしまった(笑)。今年の後半は仕事が忙しかったのに加えて寝込んだこともあって、雑誌類がもうあふれてしまっていました。整理しながら記事を見ていたのですが、インターネット系の雑誌となると、もうここ2年くらはそれほどネタ的に激動している雰囲気はないというところでしょうか。Mac系ではやっぱり雑誌だとMac OS 9が主体なので、なんか大きな変化が感じられるところです。年賀状も作らないといけないのですが、今年はなんといってもMac OS Xの年でもあるので、唯一ネイティブ対応している宛名職人はいちおう買っていますが、結局、ファイルメーカーProで印刷かなというところです。そちらで住所録が完備していますし、10年以上使い続けたデータベースはフォームもばっちりなんですよね。それに、仕事関係ではMac系の人が多いので、「見たことがある図柄だ」と思われるのもなんですから、ちょっとした奥の手を使います(笑)。それは、Windowsにしかない「はがきスタジオ」でデザインするって方法です。いずれにしても、すでに昨年からうちの会社では、会社としてまとまって年賀状は出さないということになっています。印刷などにはアウトソーシングもしていますが、裏が白紙の年賀状を必要枚数支給してもらい、自分で勝手に年賀状を出すということもできます。面倒は面倒ですけど、送ったかどうかの管理とかが訳が分からない状態になっていたし、えらい早い時期に抽出をしないといけなかったことを考えれば、手間はあまり変わらずだし、自由にできるのはいいことです。やっぱり、年賀状にはMDOnlineのロゴを付けたいとうのがありますからね。
(新居雅行 msyk@mdonline.jp


今から始めるCocoaプログラミング》文書ファイルを扱うアプリケーションを作る(8)新規にウインドウを開くときの処理

前回の説明で忘れ物があった。文書ファイルに対応したnibファイルのMyDocument.nibファイルに、Windowというインスタンスがあり、そこにNSTextViewを配置したが、その属性を一部変更しておいてもらいたい。

◇NSTextViewのAttributesを変更する
 

Windowsの中にあるNSTextViewを選択すると、InfoパレットにNSTextViewの設定が表示される。Attributesをポップアップメニューから選択し、設定を行う。ここでGraphics allowedとUndo allowedのボックスがオフになっているがそれを入れておく。いずれにしても、テキスト編集領域の設定をここで行えるが、属性についてはもちろん、プログラムで変更ができる。
Editableはもちろん、編集作業が可能かどうかで、たとえばテキストの表示だけを行うのなら、このチェックははずせばいいだろう。もし、このチェックをはずせばSelectableが選択できるようになり、編集はできないが選択してコピーなどの作業ができるようになる。Multiple fonts allowedは文字単位でフォント設定を変化させるような書式設定を可能とするかどうかだ。Graphics allowedは、インライングラフィックスの挿入を可能するかどうかを示す。Undo allowedはUndoが可能かどうかだが、実はRevertメニューが使えるかどうもここのチェックに関係しているようである。

続いてプログラムに移りたいが、今のところはまだ、1行だけしか追加していないMyDocument.javaを見て見よう。すでにいくつかのメソッドが定義されている。

◇MyDocument.javaの最初の状態
 

ここで改めて、ドキュメントベースのアプリケーションで稼働しているオブジェクト階層を見ながら解説をしよう。以前にも示したが、以下の図だ。

◇ドキュメントベースのアプリケーション
 

ここで、ドキュメントを管理するNSDocmentのサブクラスとして、MyDocomentクラスが定義されている。NSDocumentのサブクラスは必ず作らないといけないと説明したが、そのサブクラスで必ずオーバーライドしなければないメソッドがある。それぞれ、各メソッドで組み込まないといけない機能は以下の通りだ。

☆ウインドウに表示されている文書データを、ファイルに保存できる形式に整える(Override)
 NSData dataRepresentationOfType(String aType);
 戻り値:文書データをNSData型にしたもの。nullを戻すと保存のキャンセル
 引数:aType:保存する文書の型を示す文字列(書類タイプの「名前」)

☆文書ファイルから読み取ったデータをどこかに保存しておく(Override)
 boolean loadDataRepresentation( NSData docData, String docType);
 戻り値:読み込み動作をキャンセルするなfalse、続けるならtrueを戻す
 引数:docData:ファイルから読み取ったデータが得られる
    aType:ファイルの文書の型を示す文字列(書類タイプの「名前」)

☆このクラスに関連付けられているnibファイルの名前を戻す(Override)
 String windowNibName()
 戻り値:nibファイルのファイル名(拡張子は不要)

◇NSDocument(com.apple.cocoa.application)
 http://devworld.apple.com/techpubs/macosx/Cocoa/Reference/ApplicationKit/Java/Classes/NSDocument.html

これらのメソッドを定義しないといけないのであるが、すでにテンプレートの状態でメソッドの外枠は作られている。(NSDataについてはこの記事の末尾に解説する。)後は中身を書くだけということになっている。

それぞれのプログラムを示す前に、すでに何もプログラミングをしなくても動いている部分を見て見たい。アプリケーションを起動したり、あるいはFileメニューからNewを選択すると、新たに文書ウインドウが作られる。まだ、文書ファイルとの対応は取られていないが、とにかく設計したユーザインタフェースのウインドウがでてくるのである。ここでは、次のような流れがフレームワーク内で発生している。

(1)起動やメニュー操作によって新しいドキュメントを作るというイベントが発生する。
(2)First Responderによって受け取られる。結果的にそれがNSDocumentControllerで受け取られて処理が行われる。
(3)アプリケーション情報をもとに一連のドキュメント対応オブジェクトを構築する。まず、「書類のタイプ」の設定の最初の項目から、文書ファイルはMyDocumentクラスで管理することが分かる。(複数ある場合は最初の項目を取り出す)
(4)MyDocumentの引数のないコンストラクタが呼び出されて、MyDocumentオブジェクトが生成される。
(5)MyDocumentのwindowNibNameメソッドが呼び出される。これによって、NSDocumentControllerがロードすべきnibファイルを知ることができる。そして、ここではwindowNibNameで得られた名前をもとに、MyDocument.nibファイルがロードされる。
(6)MyDocument.nibの定義に従ってウインドウが表示され、そこに定義したユーザインタフェースなどが定義通りに画面に表示される。
(7)nibファイルをロード後にMyDocumentのwindowControllerDidLoadNibメソッドが呼び出される。

最初でのFirst Responderについては別途説明するが、簡単に言えば、さまざまなイベントの流れを一元的に受け付けるオブジェクトだと考えれば良い。そして、フレームワークの中で、そのイベントを受け付ける流れができている。たとえば、テキスト編集中はテキストフィールドが受け付けるがそこで処理されないイベントはアプリケーションなどに引き継がれるといった連鎖が自動的に内部で構築されているのである。
テンプレートからプロジェクトを作成すると、すでにいろいろなところで「MyDocument」という設定がなされているが、上記の流れをもとに、どのデータをもとにしてどのデータが取り出されるかという関連付けをチェックしておいてもらいたい。複数のドキュメントクラスやnibファイルを扱う場合にはそうした知識がないといけないだろう。

 ̄ ̄ ̄ ̄NSDataについて____
NSDocumentでのオーバーライドしなければならなかったメソッドでは、データをNSData型で取り扱わないといけない。これは、CocoaのFoundationに定義されたクラスであるが、要はバイト型データの配列を保持してパッケージとして扱うためのクラスである。以下は、コンストラクタと利用するメソッドをまとめておいた。いずれにしても、データはbyte型の配列で得られるので、byte配列のラッパークラスということである。

☆NSDataクラスのインスタンスを生成するコンストラクタ
 NSData()
 NSData( byte[] bytes, int start, int length)
 NSData(byte[] bytes)
 NSData(java.io.File aFile)
 NSData(java.net.URL anURL)
 NSData(NSData aData)
 NSData(String aString)
 戻り値:生成したNSDataオブジェクトへの参照
 引数:bytes:データが入っているbyte型配列
    start:取り出す最初の位置(ないものはbyte配列全部のNSDataを作る)
    length:取り出すバイト数(ないものはbyte配列全部のNSDataを作る)
    aFile、anURL:ファイルやURLから取り出したデータをもとにNSDataを作成
    aString:文字列をもとにしたNSDataを作成

(以下、《》はそのクラスのインスタンスへの参照を指定することを意味する。)

☆NSDataに含まれているデータの長さを求める
 int 《NSData》.length();
 戻り値:含まれているデータのバイト数

☆NSDataに含まれているデータを取り出す
 byte[] 《NSData》.bytes(int start, int length);
 戻り値:含まれているデータ
 引数:start:取り出す最初の位置
    length:取り出すバイト数

◇NSDocument(com.apple.cocoa.foundation)
 http://devworld.apple.com/techpubs/macosx/Cocoa/Reference/Foundation/Java/Classes/NSData.html

(この項、続く)

カテゴリ:ユーザインタフェース, Cocoa, 今から始めるCocoaプログラミング


今から始めるCocoaプログラミング》文書ファイルを扱うアプリケーションを作る(9)NSTextViewの使い方

実際にプログラムを行う前に、今回のメインのコンポーネントであるNSTextViewの使い方を調べておこう。基本的なドキュメントは、以下のページにある。

◇NSTextView
 http://devworld.apple.com/techpubs/macosx/Cocoa/Reference/ApplicationKit/Java/Classes/NSTextView.html

このドキュメントの最初にあるように、NSTextViewはいくつかのクラス階層を経て定義されている。階層的には以下のようになっている。ここでの実際のテキスト処理は、NSTextViewよりもその基底クラスであるNSTextという抽象クラスで定義されたメソッドを使うことが多くなる。

NSTextView←NSText←NSView←NSResponder←NSObject

まず、NSTextViewからのテキストの取り出しや設定を行うには、以下のようなメソッドが用意されている。単純にテキストのやりとりだけならメソッドもシンプルだ。

(以下、《》はそのクラスのインスタンスへの参照を指定することを意味する。)

☆編集領域にあるテキストデータだけを取り出す
 String 《NSText》.string();
 戻り値:NSTextViewに表示されているテキスト

☆編集領域にテキストを設定する
 void 《NSText》.setString(String aString);
 引数:aString:設定する文字列

一方、スタイル付きのテキストやあるいはグラフィックスを埋め込んだテキストについては、次のように、Ritch Text形式でのデータの出し入れができるようになっている。こちらは汎用的な処理ができるように、NSTextViewの中の部分文字列に対する処理となっている。
NSTextViewで扱えるRitch Textは2種類ある。通常のRitch Text(便宜上、「通常」とする)の方と、RTFDという形式だ。RTFDは通常のRitch Textに加えて画像ファイルの中身もそのままパッケージしたデータである。実際にそれぞれで作業をしてみると、通常のRitch Textの方はペーストしたグラフィックスが保存されないのに対して、RTFDは保存がされる。ただ、通常のRitch TextだとWordで読めたのだが、RTFDは読み込めなかった。今回はいずれにしても独自の拡張子を付けているが、NeXT時代からの作法に従うとそれぞれ、.rtf、.rtfdという拡張子のファイルに保存するようである。

☆編集領域からRitch Textでデータを取り出す
 NSData 《NSTextView》.RTFFromRange(NSRange aRange);
 戻り値:取り出したRitch TextデータのNSData型データ
 引数:aRange:テキストを取り出す範囲

☆編集領域からRTFD形式でデータを取り出す
 NSData 《NSTextView》.RTFDFromRange(NSRange aRange);
 戻り値:取り出したRTFDデータのNSData型データ
 引数:aRange:テキストを取り出す範囲

☆編集領域の一部分にRitch Textデータを設定する
 void 《NSTextView》.replaceCharactersInRangeWithRTF(NSRange aRange, NSData rtfData);
 引数:aRange:設定する範囲
    rtfData:設定するRitch TextデータをNSData型で与える

☆編集領域の一部分にRTFD形式のデータを設定する
 void 《NSTextView》.replaceCharactersInRangeWithRTFD(NSRange aRange, NSData rtfData);
 引数:aRange:設定する範囲
    rtfData:設定するRTFDデータをNSData型で与える

なお、範囲を示す引数にはNSRangeというクラスを使う。これはCocoaのFoundationで定義されているクラスであるが、以下にコンストラクタを紹介しておこう。要は、どこから何文字ということを記録するオブジェクトである。オブジェクトの生成を行うのがほとんどだろう。

☆NSRangeクラスのインスタンスを生成するコンストラクタ
 NSRange();
 NSRange(int location, int length);
 NSRange(NSRange aRange);
 戻り値:生成したNSRangeオブジェクトへの参照
 引数:location:範囲の最初のポイント
    length:範囲の長さ
    aRange:もとになるNSRangeオブジェクト

◇NSRange(com.apple.cocoa.foundation)
 http://devworld.apple.com/techpubs/macosx/Cocoa/Reference/Foundation/Java/Classes/NSRange.html

以上の知識をもとに、まずはファイルへの保存を組み込みたい。ファイルへの保存は、Cocoaフレームワークの中では次のような流れで作業を行なう。

(1)起動やメニュー操作によって保存イベントが発生する。
(2)First Responderによって受け取られる。結果的にそれがMyDocoumentのsaveDocumentメソッドで受け取られるが、そのメソッドのオーバーライドがされていないのなら、もとになったNSDocumentで処理が行われる。
(3)NSDocumentのsaveDocumentメソッドでは、必要に応じて、ファイルを保存するときのシートを表示する。文書ファイルの種類を参照して、扱えないタイプのファイルは一覧でグレーになるなどの処理は自動的に組み込まれている。
(4)ファイルを指定するかあるいはすでにファイルが確定されているとする。するとファイルへの書き込み処理が始まる。まず最初に、dataRepresentationOfTypeメソッドが呼び出される。dataRepresentationOfTypeメソッドはMyDocumentクラスでオーバーライドしているので、そこで書かれたメソッドが処理される。
(5)dataRepresentationOfTypeメソッドが終了するとNSData型データでフレームワークに保存すべきデータが戻されるので、指定したファイルにそのデータをまるごと保存する。

ここで、dataRepresentationOfTypeメソッドをMyDocoment.javaの中に次のように定義をした。引数のaTypeには保存すべきデータの形式が入って呼び出される。

public NSData dataRepresentationOfType(String aType) {
if(aType.compareTo("MOSAEditor Document") == 0) {
int textLength = docTextView.string().length();
NSRange allRange = new NSRange(0, textLength);
return docTextView.RTFDFromRange(allRange);
}
if(aType.compareTo("Text Document") == 0)
try {
return new NSData(docTextView.string().getBytes("x-sjis"));
}
catch(Exception e) {
System.out.println(e.getMessage());
return null;
}
else
return null;
}

実際にアプリケーションを起動して保存をすると、次の図のように保存するフォルダとファイル名を指定するシートが表示される。ファイルの形式では、Project Builderでのアプリケーション設定で指定した「書類タイプ」の名前が一覧されている。

◇保存のシートで見られる書類タイプの名前
 

ここで、どちらかのフォーマットを指定してファイルを保存すると、dataRepresentationOfTypeが呼びされるが、具体的にはここでは、aType引数は「MOSAEditor Document」ないしは「Text Document」のいずれかの文字列になっているはずである。そこで、それらの文字列ごとに処理を分岐させているというのがdataRepresentationOfTypeメソッドの大枠である。
Text Documentの場合には、stringメソッドでNSTextViewからテキストを取り出せばよい。これで入力したテキストが全部取り出される。ただし、それはUnicodeのテキストである。ここではシフトJISコードで保存したいので、getBytesメソッドでコード変換をしつつさらにbyte型配列にしている。そしてそのNSDataオブジェクトを生成して戻しているということだ。なお、文字コード変換を伴う場合には例外の取得が必要になるが、エラーになったらいちおうnullを戻している。
一方、MOSAEditor Documentなら、NSTextViewの中身をRTFD形式で取り出したいが、NSRangeで範囲を指定しなければならない。「全部」という指定はないから、ここではNSTextViewの長さをチェックしないといけない。そのためには、stringメソッドで文字列を取り出し、その長さをlengthメソッドで得る。これでグラフィクスを含めての範囲が得られるので、それをもとにNSRangeオブジェクトを生成する。なお、NSTextViewでの最初の文字位置は0となる。あとは、RTFDFromRangeメソッドを使えば、必要なデータが得られるということになる。このメソッドはいきなりNSData型が得られるので、それ以上することはないということである。
(この項、続く)

カテゴリ:ユーザインタフェース, Cocoa, 今から始めるCocoaプログラミング