タイトル今から始めるCocoaプログラミング》文書ファイルを扱うアプリケーションを作る(13)Revertを組み込むカテゴリーユーザインタフェース, Cocoa, 今から始めるCocoaプログラミング
作成日2001/12/28 16:27:28作成者新居雅行
続いて、Revertの機能を組み込んでみたい。ちなみに、FileメニューからRevertを選択すると、次の図のように、Revertしますかということをシートで問い合わせてくる。

◇Revertの確認ダイアログボックス
 

この一連の動作は、NSDocumentのrevertDocumentToSavedメソッドですでに定義されている。その動作としては次のようになる。

(1)Revertメニューを選択するなどして、First ResponderにRevertのメッセージが送られる。それが結果的には、MyDocumentが受け取るが、そこではメソッドをオーバーライドしていないのでNSDocumentクラスのメソッドが呼び出される。
(2)変更されているかどうかを確認して、変更されていれば、Revertするかどうかを問い合わせるダイアログボックスを表示する。変更されていなければ、これ以上は何もしない。
(3)Revertの動作に入る。すでに文書ファイルなどは確定しているし、nibファイルもロードされている。ここで、nibファイルの再ロードは行わないのがポイントだ。
(4)MyDocumentのloadDataRepresentationメソッドが呼び出される。

nibファイルはすでにロードされているものを利用する。もちろん、MyDocumentはすでにインスタンス化されているから、コンストラクタも呼び出されない。つまり、nibファイルがロードされた状態でloadDataRepresentationが呼び出されるのである。ファイルのOpenだと、nibファイルがロードされていない状態でloadDataRepresentationが呼び出されるのと大きく違っている。そこで、こうした動作を両立させるために、まずは、MyDocumentクラスのメンバー変数に、nibファイルがロード済みかどうかをチェックするフラグを設定する。メンバー変数の定義部分は次のようになる。あわせて、windowControllerDidLoadNibメソッドに新たにその変数に値を設定するステートメントを加えた。

public class MyDocument extends NSDocument {

public NSTextView docTextView; //ウインドウ内のテキストエリアを参照
private NSData fileContents; //読み込んだファイルの中身を記録する
private String fileType; //読み込んだファイルの種類を記録する
private boolean isNibLoaded = false; //nibファイルがロードされたかどうか

public void windowControllerDidLoadNib(NSWindowController aController) {
super.windowControllerDidLoadNib(aController);
setupWindowFromData();
docTextView.window().makeFirstResponder(docTextView);
isNibLoaded = true;
}

}

次に、loadDataRepresentationを以下のように改造する。ここで、変数に応じて、OpenなのかRevertなのかを判別できることを利用している。Openの場合には、ここではNSDataなどへの参照を記録するだけだが、Revertでは実際にデータをNSTextVeiwへ設定するsetupWindowFromDataを呼び出すということにする。

public boolean loadDataRepresentation(NSData data, String aType) {
fileContents = data;
fileType = aType;
if(isNibLoaded)
setupWindowFromData();
return true;
}

これで、Revertの動作も可能となった。

これで概ねはOKなのだが、文書作成アプリケーションとしては次のような不備がある。まず、適当に文書に文字を入れて保存をすると保存はできるが、その後にキータイプをしても、ウインドウの左上にあるクローズの赤ボタンに黒丸が入らない。つまり、Windowsに文書が変更されたと言うマーキングが入らない。
さらに、保存もなにもしないで、ウインドウを閉じると、ファイルを保存するかどうかをきちんと聞いてくるのはOKだろう。もう細かくは説明しないが、保存しないのに閉じようとしたときに保存するかどうかを問い合わせるメカニズムもCocoaには組み込まれている。だが、現状では、一度保存をした後、キータイプをしてそのままウインドウを閉じると、何の警告もなくてウインドウが閉じる。そして、保存後にキータイプした結果は残っていないのである。つまり、ドキュメントに変更があるという情報がどうもセットされていないようなのである。
これらを解決する必要があるのだが、MOSAEditorではNSTextViewだけを使っているので、とりあえず、NSTextViewが変更されたときに何らかの処理がされるようにしておけば良いと言うことが分かる。そこで、NSTextViewそしてNSTextViewのドキュメントを探ると、textDidChangeというデリゲート向けのメソッドがあることが分かる。このメソッドはキータイプなどでの編集があったときに呼び出されるが、setStringなどでは呼び出されない。おそらく、ユーザの変更作業があった場合に呼び出されるということでよさそうだ。

☆NSText(NSTextView)で内容が変更されたときに呼び出される(Delegate)
 public void textDidChange(NSNotification aNotification)
 引数:aNotification:変更されたNSTextViewへの参照

そこで、NSTextViewでのデリゲートの受付先を設定しておく。新たにクラスを作ってもいいのだが、ここでは簡単のために、MyDocument、つまりnibファイルではFile’s Ownerを指定することいしよう。Project BuilderからMyDocument.nibファイルをダブルクリックして開く。Interface Builderで作業をするが、ウインドウの中にあるNSTextViewからリンクをしないといけない。そのためには、NSTextViewのコンポーネントをダブルクリックして、controlキーを押しながらFile’s Ownerにドラッグする。ここでクリックだけだと、NSTextViewを包含しているNSScrollViewが選択されるのでダブルクリックして、そしてその状態でボックスの内部からcontrolキーを押しながらドラッグする。そして、Outletでdelegateが選択されているのを確認して、Connectボタンをクリックする。

◇NSTextViewのデリゲート先を指定する
 

こうして、NSTextViewでユーザが編集作業を行うと、MyDocumentクラスのtextDidChangeメソッドが呼び出される。そこで、MyDocumentクラスに、以下のようなメソッドを付け加えておく。新たにでてきたメソッドとともに解説しておこう。

public void textDidChange(NSNotification aNotification) {
docTextView.window().setDocumentEdited(true); //実は不要
updateChangeCount(ChangeDone);
}

☆ウインドウに表示された文書が変更されたかどうかをセットする
 void 《NSWindow》.setDocumentEdited(boolean flag)
 引数:flag:trueなら変更されたことになる

☆ドキュメントの変更に関するカウントを更新する
 void 《NSDocument》.updateChangeCount(int changeType)
 引数:changeType:以下の定義定数を指定する
       ChangeDone:カウンターを増加
       ChangeUndone:カウンターを減少
       ChangeCleared:カウンターを0にする

ここで、NSDocumentでは、文書を変更したかどうかをカウンターで管理している。変更があれば、カウンターをアップし、Undoによりカウンターをダウンするといった動作を行っている。そして、カウンターが0なら修正はされていないという判断ができるという具合だ。ここでは、とりあえずはキータイプすればカウンターがアップすれば変更したことが伝えられるのでそのようにしておく。なお、updateChangeCountの呼び出しを行えば、内部的にsetDocumentEditedメソッドも呼び出されているようで、setDocumentEditedは利用しなくてもかまわない。
なお、定義定数はそのまま「ChangeDone」として使える。Java的には「NSDocument.ChangeDone」なのであろうけども、MyDocumentはNSDocumentのサブクラスだから、クラスの指定は不要なのである。ちょっと楽ができるみたいな気分(笑)が味わえる。

これで概ね動作上はOKなところまで来たと思われる。保存していないウインドウを複数開いて、Command+Qで終了をしてもらいたい。すると、変更結果を破棄するとか、各ウインドウで変更するかどうかを問い合わせるかを選択するダイアログボックスも、Cocoaのフレームワークで自動的にでてくる。Undoについても、ある程度は動くのであるが、NSTextViewの動作上の問題と思われるが、日本語のテキストの場合はきちんと動作しないということも分かる。また、Undoは1レベルまでとなっている。多段階のUndoといった高度なことは自分で作成するしかないようだ。
(この項、来年に続く)
関連リンク