第10章 メモを書いたら貼りつけろ


この章では、基本的なテキストエディタ機能を備えたアプリケーションのサンプルを使い、BeOSのAPIが提供しているテキストエンジンの利用方法を説明します。また、ファイルの読み出しと書き込みのためのプログラミング手順についても解説します。さらに、BeOSの「レプリカント」機構を利用して、テキストエディタ部品だけをウィンドウからドラッグしてコピーし、デスクトップに貼りつけて動かす方法についても解説します。つまり、主な題材として以下のものをとりあげます。

◇テキストの表示と編集機能を持つアプリケーションを作る
◇指定されたテキストファイルを開き、その内容を表示する
◇エディタで編集したテキストの内容を、ファイルに保存する
◇エディタに表示するテキストのフォントやサイズを変更する
◇テキストエディタ部品をレプリカント化し、デスクトップに貼りつけられるようにする

以上の題材をプログラミングするために、BeOSのAPIが提供しているクラスのうち、主に以下のものを利用します。

●BTextView(Interface Kit)
●BFont(Interface Kit)
●BEntry(Storage Kit)
●BFile(Storage Kit)
●BNodeInfo(Storage Kit)
●BFilePanel(Storage Kit)
●BDragger(Interface Kit)


これらのクラスの間の階層関係を、図10.1に示します。

図 第10章で主に扱うクラス間の階層図
図[10.1] 第10章で主に扱うクラス間の階層図

プログラミングの説明に使うサンプルアプリケーションは、二つ用意しています。9.1節では、基本的な機能を持つテキストエディタのサンプルを示します。9.3節で、そのアプリケーションをレプリカント対応させ、テキストエディタ部品をデスクトップに貼りつけられるようにしたバージョンを示します。


10.1  エディタを作ろう

この節では、BeOSのAPIが提供しているテキストエンジン、つまりテキストエディタ部品を利用して、テキストエディタのアプリケーションを作る場合のプログラミングを説明します。6.2節でInterface KitのBTextViewクラスを紹介しましたが、これがテキストエディタ部品です。MacOSを始めとして、GUI環境を備えたOSプラットフォームでは、組み込みのテキストエンジンが提供されているのが普通ですが、BeOSでもそれは同じです。これから見るように、テキストエディタを作るのは難しいプログラミングではありません。

10.1.1組み込みのテキストエンジン
まず、BTextViewクラスが持っているテキストエディタ機能を説明します。BTextViewクラスでは、テキストエンジンとして以下の機能を備えています。

■マルチスタイルのテキスト表示
テキスト全体を単一のフォントとスタイルで表示するのか、あるいはマルチスタイルで表示するのかを切り替えることができます。当然ながら、MacOSの“TextEdit”のように扱えるテキストデータのサイズが最大32KBまで、といった制限はありません。

■ワードラップによる折り返し表示
ワードラップを行うかどうかの設定ができます。ワードラップ機能をオンにした場合、テキストを改行表示すべき位置を自動的に計算し、適切な折り返し表示を行ってくれます。

■テキストの入力と基本的な編集機能
キーボードによるテキスト入力、および基本的な編集操作を行えます。つまり、テキストの選択部分に対するカットとコピー、およびクリア(削除)操作を行うメソッドを持っています。また、ペーストと全体選択を行うメソッドもあります。

ユーザのキー入力によって生じたメッセージの処理は、BeOSのAPIが提供するアプリケーションフレームワークによって処理されるため、アプリケーション側では何も行う必要がありません。

■編集メニュー用のショートカットとクリップボード対応
編集メニュー用のショートカットキーの処理も、BeOSのアプリケーションフレームワークによって処理されます。従って、これもアプリケーション側で処理を行う必要はありません。また、システムのクリップボードからテキストをペースト可能かどうかを判定するメソッドが用意されており、それを利用すれば、編集メニュー項目のディム化を制御する処理が簡単になります。

■印刷処理への対応
BTextViewクラス自体は、特に印刷処理のための機能は持っていません。しかし、BeOSのアプリケーションフレームワークには印刷用の機能も組み込まれています。BViewクラスのDraw()メソッドは、再描画の必要が生じた時に呼び出されるフック関数だと5.2節で説明しましたが、このメソッドは、スクリーンに対する描画だけではなく、印刷のための描画にも使われます。したがって、Draw()メソッドを再定義して描画機能を実装したビュークラスであれば、特に何もしなくても印刷処理を行えます。

この章ではBTextViewクラスを使ったサンプルを示しますが、実は、既に第8章のサンプルでBTextViewを利用しています。8.3節の説明に使った“Scriptor”アプリケーションで利用したBTextControlクラスは、内部にBTextViewクラスのインスタンスを持ち、テキストの表示と入力を任せているのです。Scriptorアプリケーションを動かしてみれば、ウィンドウの入力フィールドに対し、ショートカットキーによるカットやペーストを行えるのが分かるでしょう。これらのショートカットキー入力はBTextViewクラスによって処理されるため、BTextViewを利用する側では何もする必要がないのです。

以上の説明を読むと分かるように、BTextViewクラスはWindowsでいう「リッチテキストボックス」に相当するものです(注10-1)。つまり、MacOSの“TextEdit”よりは高機能なテキストエンジンです。なお、現在のBTextViewクラスでは、日本語フォントで表示を行うように設定すれば日本語テキストの表示が可能ですが、日本語入力機能はサポートされていません。日本語入力用の変換エンジンと通信する機能や、また変換入力のインライン表示機能などは、1998年9月リリース予定のBeOS R4で追加される予定です。

このように、BTextViewクラスによってテキストエンジン機能が提供されていますから、簡単なテキストエディタであれば、いくつかの機能をプログラミングするだけでできてしまいます。以下に、それらの項目について述べます。

■テキストデータの保存と読み込み
BTextViewを使って入力したテキストデータの内容は、ファイルに保存しない限りアプリケーションを終了すると同時に消えてしまいます。テキストデータをファイルに保存する機能、および保存したテキストデータをファイルから読み込む機能を実装しなければ、エディタとして使うことはできません。

■編集メニューの制御とフォントメニューの実装
選択部分のカットやコピーなど、テキストに対する編集操作のショートカットキー入力は、BTextViewが処理してくれます。しかし、テキストエディタとして完成させるのであれば、編集メニューをウィンドウに付け、メニュー選択によって同じ編集操作を行えるようにすべきです。また、フォントメニューをウィンドウに付け、テキストを表示するフォントのファミリーとスタイル、サイズなどを設定できるようにする必要もあります。

■印刷処理
印刷機能も、エディタに求められるものの一つです。BeOSのAPIでは、印刷処理を行うための“BPrintJob”というクラスをInterface Kitに用意しています。このクラスは、2.3節で説明したprint_serverと接続し、描画内容をプリンタに渡してくれます。また、用紙設定や印刷するページの指定を行う設定パネルを開き、ユーザが入力した結果を返すメソッドも備えています。

BPrintJobクラスを使えば印刷機能を実現できるのですが、本書のサンプルでは印刷処理をとりあげません。その一番の理由は、筆者のオフィスにBeOSによる印刷のテストを行うための環境が整っていないことです。また、BeOSでの印刷処理機能は未だ十分なものではなく、BPrintJobクラスが提供するAPIも今後大きく変更される可能性があると思われるのも、理由の一つです。

10.1.3から説明する“MemoApp”というサンプルアプリケーションでは、上に挙げたもののうち印刷処理以外の項目を実装し、テキストエディタとしての基本機能を実現しています。興味がある人は、このサンプルを参考にして、より完成されたなテキストエディタ作りに挑戦してみて下さい。

なお、単純なテキストエディタではなく、図形や画像データの貼り込みや段組み処理など、本格的な機能を持ったワープロソフトを開発する場合には、BTextViewクラスを利用するよりは、むしろテキストエンジン自体を自分で開発する必要があるでしょう。BTextViewクラスは、基本的なテキスト表示と編集処理に主眼を置いて設計されているものですから、その設計の前提を超えた高度な機能を必要とする場合には、専用のテキストエンジンを開発することを検討すべきです。これは、他のOSプラットフォームに用意されたテキストエンジンの場合でも同様ですね。

(注)10-1
Windows95/NTのリッチテキストボックスにはOLEの埋め込み機能がありますが、BTextViewクラスは、これに相当する機能はありません。

10.1.2データの保存と読み込み
前の章のサンプルアプリケーション(InfoChest)では、テキスト形式のファイルに対して入出力を行うのにANSI標準のファイル操作関数を利用しました。それと同じ関数を使うこともできますが、この章では、BeOSのStorage Kitが提供しているファイル操作APIを利用することにします。BeOSのAPIを使ったプログラミングを解説するのが本書の目的ですし、また、Storage KitのAPIを利用すれば、よりBeOSの機能を活かしたアプリケーションを作れるからです。

ここでは、この章で扱うStorage KitのAPIのうち、中心となる次の二つについて説明します。

・BFileクラス
9.1節で説明したBNodeのサブクラスで、ファイルの実体を表わしたクラスです。また、データの入出力機能を与えるために、BNodeクラスに加えてSupport KitのBPositionIOクラスも継承しています。ファイルに対する入出力操作を行うときは、このクラスのインスタンスを生成してファイルと関連づけます。

・BFilePanelクラス
データの保存や読み込みを行う場合に、ユーザがファイルを指定するのに使う標準ダイアログのクラスです。保存用のセーブダイアログと読み込み用のオープンダイアログのどちらにするかを、コンストラクタに渡す引数で指定して切り替えます。

まず、BFileクラスの方から見ていきましょう。上に書いたように、このクラスはファイル実体に格納されたデータにアクセスするためのものです。エントリ情報やパス名を渡してファイルと関連づけると、自動的にファイルを開き、ファイルに対してデータを入出力できるようにしてくれます。ファイル実体との関連づけはコンストラクタまたはSetTo()メソッドで行い、ファイルを開くモードを引数で指定します。開いたファイルは、BFileのインスタンスを解放するか、またはSetTo()メソッドで別のファイルと関連づけると自動的に閉じられます。

ファイルに対するデータの入出力を行うには、Read()メソッドとWrite()メソッドを使います。これらは、親クラスのBPositionIOが仮想関数として定義しているもので、それぞれデータの読み出しと書き込みを行います。BFileクラスを使ってファイルの読み出しと書き込みを行う例を、リスト10.1に示します。

[リスト10.1] BFileを使ったファイルの読み書き

void
CopyFile (const char srcFilePath[], const char dstFilePath[])
{
    status_t sts;
    BFile    srcFile, dstFile;
    char     dataBuf[16 * 1024];
    ssize_t  rwBytes;
	
    /* コピー元のファイルを開く */
    sts = srcFile.SetTo(srcFilePath, B_READ_ONLY);
    if (sts != B_OK)
        DoError(sts);  /* エラー処理 */
	
    /* コピー先のファイルを作って開く */
    sts = dstFile.SetTo(dstFilePath, B_WRITE_ONLY|B_CREATE_FILE);
    if (sts != B_OK)
        DoError(sts);  /* エラー処理 */
	
    /* ファイル内容のコピーを実行 */
    while ((rwBytes = srcFile.Read(dataBuf, sizeof(dataBuf))) > 0) {
        (void)dstFile.Write(dataBuf, rwBytes);
    }
	
    return;
/*
 * 注意:“srcFile”と“dstFile”に関連づけられた二つのファイルは、この手続
 *    きの呼び出しが終了してsrcFileとdstFileが解放されるとき、BFileクラス
 *    のデストラクタによって閉じられる。BFileクラスには、ファイルのオープ
 *    ンやクローズだけを行うメソッドはない。ファイルのオープンとクローズは、
 *    コンストラクタとデストラクタ、およびSetTo()メソッドの中で行われる。
 */
}


リスト10.1では、パス名で指定された二つのファイルを開き、一方の内容を他方にコピーします。コピー元のファイルを開くときは、BFileクラスのSetTo()メソッドに“B_READ_ONLY”を渡し、リードオンリー・モードを指定しています(注10-2)。また、コピー先のファイルを開くときは、“B_WRITE_ONLY”と“B_CREATE_FILE”を組み合わせたものを渡し、ファイルを作ったうえでライトオンリー・モードで開くように指定しています。SetTo()メソッドによってファイル実体をBFileオブジェクトと関連づけ、ファイルを開くのに成功したら、Read()メソッドでコピー元のファイルからデータを読み出し、それをWrite()メソッドでコピー先のファイルに書き込んでいます。

この例では、コピー用の中間バッファを用意してファイルのデータを一部ずつ読み出しています。すべてのデータを一度に読み出す必要がある場合には、Seek()メソッドを利用してファイルのサイズを調べ、それと同じ大きさの領域を確保すればよいでしょう。BFileクラスの各メソッドの詳細については、APIリファレンスでStorage Kitの章を参照して下さい。

次は、BFilePanelクラスです。このクラスは、セーブダイアログにもオープンダイアログにも使える便利なものですが、一つ注意すべきことがあります。これらのダイアログ、すなわちファイルパネルを使ってユーザが指定したファイルの情報は、メッセージによって通知されるのです。つまり、メソッドの戻り値として返されるのではありません。したがって、データの保存や読み込みを行うファイルをユーザに指定してもらう場合は、まずファイルパネルを開き、それからメッセージが届くのを待つようにプログラミングする必要があります。以下に、ファイルパネルを開いてファイルを指定してもらうための手順を示します。

(1)データを保存する場合(セーブダイアログ)
1.)BFilePanelクラスのコンストラクタに、パネル種類として“B_SAVE_PANEL”を渡してインスタンスを生成する。これで、セーブダイアログが作られる。

2.)BFilePanelクラスのShow()メソッドを呼び出し、セーブダイアログを表示する。

3.)ユーザがセーブダイアログを使って保存先を指定すると、あらかじめ設定した送り先(ターゲット)に“B_SAVE_REQUESTED”メッセージが送られる。ターゲットの設定はBFilePanelクラスのSetTarget()メソッドで行うか、またはコンストラクタの引数で指定する。何も設定しなかった場合は、アプリケーションオブジェクトにメッセージが送られる。

4.)B_SAVE_REQUESTEDメッセージには、保存先のファイルを置くディレクトリのエントリ情報と、ファイルの名前が付随データとして格納されている。この二つの項目で指定されたファイルを開き、データを保存する。

(2)データを読み込む場合(オープンダイアログ)
1.)BFilePanelクラスのコンストラクタに、パネル種類として“B_OPEN_PANEL”を渡してインスタンスを生成する。これで、オープンダイアログが作られる。

2.)BFilePanelクラスのShow()メソッドを呼び出し、オープンダイアログを表示する。

3.)ユーザがオープンダイアログを使ってファイルを指定すると、あらかじめ設定したターゲットに“B_REFS_RECEIVED”メッセージが送られる。ターゲットの設定はセーブダイアログの場合と同様。なお、アプリケーションオブジェクトがB_REFS_RECEIVEDを受け取った場合、BApplicationクラスがフック関数として提供しているRefsReceived()メソッドが呼び出される。

4.)B_REFS_RECEIVEDメッセージには、開くべきファイルのエントリ情報が付随データとして格納されている。エントリ情報で指定されたファイルを開き、データを読み込む。なお、オープンダイアログで複数のファイルを指定すると、複数のエントリ情報が渡される。メッセージに格納された全てのエントリ情報を取り出し、指定された全てのファイルについてデータの読み込み処理を行う。

このように、ファイルパネルの使い方は単純です。とはいえ、その背後には少しばかり複雑な問題が隠れています。3.1節で、BeOSのファイルパネはモーダル動作しないのが普通だと述べました。これは、ファイルパネルを開いている間も他のウィンドウを操作できるということです。たとえば、ユーザがテキストエディタのウィンドウを閉じようとした時に、編集結果を保存するかどうかを問い合わせ、セーブダイアログを開いたとしましょう。セーブダイアログがモーダル動作するのであれば、それを開いている間は他のウィンドウを操作できませんから、テキストエディタのアプリケーション側ではダイアログに対するユーザの応答を待つだけです。しかし、モーダル動作しないとなると、セーブダイアログを開いているにも拘わらず他のウィンドウを操作できるのです。ウィンドウのメニューから「ファイルを開く」を選択したり、それどころか、もう一度ウィンドウを閉じようとすることすらできてしまいます。

この状況は、一見無茶苦茶で解決の手段がないように思えます。しかし、実はそうでもありません。メッセージ応答の処理さえ正しくプログラミングしていれば、たとえファイルパネルがモーダル動作しなくても、けっして難しいことにはならないのです。

以下に、ファイルパネルを利用するうえで気をつけるべきことや、利用の仕方を述べます。これは一種の定石として示すものであり、必ず守らなければいけないものではありません。ファイルパネルについて理解してしまったら、これを踏台にして自分なりの定石を考えてみるのがよいでしょう。また、この章の説明に使うテキストエディタのサンプル(MemoApp)でも以下の定石を採用していますので、ソースコードを読む際の参考にしてみて下さい。

■ファイルパネルのポインタを保管する
ファイルパネルが閉じられても、BFilePanelオブジェクトは解放されません。また、ファイルパネルのウィンドウも解放されず、単に画面から隠されるだけです。したがって、BFilePanelオブジェクトを生成したらポインタを保管しておき、不要になった時点で解放する必要があります。

なお、ファイルパネルが閉じられたら直ちにBFilePanelオブジェクトを解放するよりも、画面から隠したままとっておいて再利用する方が便利です。そうすれば、ファイルパネルの状態がそのまま保存され、再び表示する時にそれを復元する手間が省けるからです。ファイルパネルはリサイズできるので、その状態を保存しておけるのは、ユーザにとって便利です。

画面から隠されたファイルパネルを再び表示するには、BFilePanelオブジェクトに対してShow()メソッドを呼び出します。また、ファイルパネルが画面に表示されているかどうかを調べるにはIsShowing()メソッドを使います。

■オープンダイアログはアプリケーションに一つ
次に述べるセーブダイアログととは違い、オープンダイアログ用のBFilePanelオブジェクトは一つしか生成しないようにします。つまり、オープンダイアログを表示している時に“File”メニューの“Open”が選択されたら、オープンダイアログをもう一枚開くのではなく、既に表示しているダイアログを最前面に出すようにします。特に、テキストエディタのようにファイルとウィンドウが一対一に結び付く「ドキュメント処理」タイプのアプリケーションでは、そうする方がユーザにとっても自然です。それらのアプリケーションでは、オープンダイアログは特定の「ドキュメント」ウィンドウと結び付くものではなく、むしろ、新たにウィンドウを開くための道具として働くからです。

また、オープンダイアログをアプリケーションクラスのデータメンバとして持たせ、メッセージの送り先(ターゲット)もアプリケーションオブジェクトに設定しておくと便利です。BApplicationクラスでは、オープンダイアログが送ってくるB_REFS_RECEIVEDメッセージを受け取ると、フック関数のRefsReceived()メソッドを呼び出します。実は、アプリケーションのアイコンにファイルのアイコンをドラッグ&ドロップした時も、これと同じメッセージが送られます。したがって、オープンダイアログでファイルを開く処理と、アプリケーションのアイコンに対するドラッグ&ドロップによってファイルを開く処理とが自然に共通化されるのです(注10-3)。

■セーブダイアログはウィンドウごとに一つ
テキストエディタのような「ドキュメント処理」タイプのアプリケーションでは、それぞれのウィンドウにセーブダイアログ用のBFilePanelオブジェクトを持たせます。つまり、セーブダイアログを表示している時に、別のウィンドウで“File”メニューの“Save”が選択されたら、もう一枚セーブダイアログを開くのです。ただし、既にセーブダイアログを開いたウィンドウに対してもう一度同じ操作が行われたら、そのダイアログを最前面に出すだけです。

また、オープンダイアログをアプリケーションクラスのデータメンバとして持たせるのに対し、セーブダイアログはウィンドウクラスのデータメンバにします。もちろん、ターゲットもウィンドウオブジェクトに設定しておきます。セーブダイアログで指定されたファイルに保存すべきデータを知っているのは、それぞれのウィンドウオブジェクトです。したがって、ウィンドウオブジェクトにメッセージを送って保存処理を行わせるようにする方が、プログラミングが自然になります。

(注)10-2
“B_WRITE_ONLY”や“B_CREATE_FILE”はで定義されているもので、POSIX APIのファイルオープンモードに対する別名です。つまり、“O_WRONLY”や“O_CREAT”と同じです。

(注)10-3
9.3節の説明に使った“InfoChest”アプリケーションでは、ドキュメント一覧ウィンドウにファイルをドラッグ&ドロップした場合と、オープンダイアログでファイルを選択した場合の応答処理を共通化しています。このため、ドキュメント一覧ウィンドウのビューオブジェクト(DocumentsView)をオープンダイアログのターゲットにしています。

10.1.3MemoAppが利用するAPI
テキストエディタのサンプルアプリケーションについて説明する前に、それが利用している主なAPIを紹介しておきます。最も中心となるBTextViewクラス、およびBFileクラスとBFilePanelクラスについては既に説明しましたが、それ以外のクラスと合わせ、ここでまとめておくことにします。

先にStorage Kitのクラスを見ておきます。この節のサンプル(MemoApp)では、主に以下のクラスを利用しています。

■BFile
テキストデータの保存と読み込みを行うのに使っています。データの保存は10.1.2で説明したようにWrite()メソッドを使って行いますが、テキストデータを読み込む処理は、BTextViewクラスに任せています。

■BNodeInfo
テキストファイルに対し、ファイルタイプ属性の取得と設定を行うのに利用しています。ファイルタイプ属性というのは、3.4節で説明したようにファイルタイプ情報を格納したノード属性のことです。テキストファイルを作成してデータを保存する際、ファイルタイプ属性を設定するのに利用しています。また、オープンダイアログに表示するエントリをフィルタリングする処理で、指定されたエントリのファイルタイプ属性を取得するのに利用しています。

■BDirectory
BFileと同じくBNodeクラスを継承しており、ディレクトリの実体を表わしたクラスです。保存先のファイル情報を受け取った際、ファイル名と合わせてファイルのエントリ情報を作成するのに利用しています。

■BFilePanel
先ほど説明した通り、ファイルパネルのクラスです。MemoAppでは、オープンダイアログをアプリケーションクラスに持たせる一方、セーブダイアログはウィンドウクラスに持たせています。これは、先ほど述べた「定石」の通りです。

■BRefFilter
ファイルパネルと連携して動作するクラスです。ファイルパネルがエントリを一覧表示する際に、表示したくないエントリをふるい落とすためのフィルタリングに使います。このクラスは、フィルタ関数として働く仮想メンバ関数を定義しただけの純仮想クラスなので、サブクラスを実装しないと使えません。MemoAppでは、テキストファイルだけを通過させる“MyTextRefFilter”というサブクラスを実装しています。

次はInterface Kitの方です。MemoAppでは、主に以下のクラスを利用しています。

■BTextView
10.1.1で説明した通り、テキストエンジン機能を提供するクラスです。

■BFont
フォントを扱うためのクラスです。フォントのファミリーとスタイル、およびサイズなど、フォントの属性情報をデータメンバに持ちます。BTextViewに表示するテキストのフォントを設定するには、表示に使うフォント属性をBFontオブジェクトにセットし、それをBTextViewクラスのSetFontAndColor()メソッドの引数に渡します。

■BScreen
解像度や画面サイズなど、スクリーン情報にアクセスするためのクラスです。MemoAppでは、スクリーンサイズを取得するのに利用しています。MemoAppでは、新しくウィンドウを開くごとに表示位置を右下にずらしていくのですが、ウィンドウがスクリーンからはみ出ししてしまい、見えなくなってしまわないように、スクリーンのフレーム矩型とウィンドウの位置関係を調べ、必要な場合はウィンドウを開く位置を補正します。

以上が、MemoAppアプリケーションで利用している主なAPIです。次に、BFontクラスを使ってBTextViewの表示フォントを設定する手順を説明します。以下は、表示フォントのファミリーとスタイル、およびサイズを設定するための手順です。

1.)BFontクラスのインスタンスを生成する。

2.)生成したBFontオブジェクトに対してSetFontAndFamily()メソッドを呼び出し、表示に使うフォントファミリーの名前とスタイル名をセットする。

3.)続けてSetSize()メソッドを呼び出し、表示に使うフォントサイズをセットする。

4.)必要なフォント属性をセットし終わったら、BTextViewオブジェクトに対してSetFontAndColor()メソッドを呼び出し、そのBFontオブジェクトを渡す。なお、SetFontAndColor()には、BFontオブジェクトの他にフォント設定を変更するテキストの範囲を示すオフセット値を渡さなければならない。

具体例を見るために、現在選択されている部分のテキストのフォントを、“Bitstream Cyberbit”ファミリーの“Roman”スタイルに変更する場合について、リスト10.2にコーディング例を示します。

[リスト10.2] BTextViewに対するフォントの設定例
    BTextView* theTextView = GetTextView();  /* BTextViewを取得 */
    BFont      theFont;
    int32      selStart, selEnd;
	
    /* 選択部分のオフセット値を取得 */
    theTextView->GetSelection(&selStart, &selEnd);
	
    /* 設定するフォントファミリーとスタイルをBFontにセット */
    theFont.SetFamilyAndStyle("Bitstream Cyberbit", "Roman");
	
    /* 選択部分のフォントを変更 */
    theTextView->SetFontAndColor(selStart, selEnd, &theFont, B_FONT_FAMILY_AND_STYLE);

リスト10.2では、BTextViewクラスのGetSelection()メソッドを使ってテキストの選択範囲を調べています。テキスト全体のフォントを変更する場合は、変更する範囲として0からテキストの終わりまでを指定するだけです。テキストの終わりを示すオフセットは、TextLength()の戻り値によって与えられます。ただし、BTextViewオブジェクトをモノスタイル・モードで動かしている場合には、指定した範囲だけを変更することはできず、テキスト全体のフォントが変わってしまいます。マルチスタイル・モードで動かすためには、前もってSetStylable()メソッドを呼び出しておく必要があるので注意して下さい。

なお、選択部分のテキストに関するフォントの取得や設定を行う場合は、GetFondAndColor()とSetFontAndColor()にオフセット値を渡さなくても構いません。この二つのメソッドは多重定義されており、オフセット値を受け取らないバージョンでは、選択部分のテキストに対して働くからです。詳細は、APIリファレンスを参照して下さい。

さて、リスト10.2では、フォントファミリーに"Bitstream Cyberbit"を指定しています。これは第1章で紹介した日本語フォントですから、選択部分のテキストが日本語であれば、日本語が表示されるはずです。すぐ後で示すMemoAppアプリケーションのスクリーンショットでは、このフォントを使って日本語を表示しています。

10.1.4MemoAppの外部仕様とモジュール構成
サンプルアプリケーションに関する説明の手始めとして、その外部使仕様、つまりユーザに提供する機能を述べます。図10.2が、これから説明する“MemoApp”という名前のサンプルを動かした様子です。

図 MemoAppのスクリーンショット
図[10.2] MemoAppのスクリーンショット

このアプリケーションの大まかな外部仕様は、以下の通りです。

・テキスト編集機能を持ったウィンドウを開き、オープンダイアログで指定したテキストファイルの内容を表示する。また、アプリケーションのアイコンにテキストファイルのアイコンをドラッグ&ドロップしても、同じように開くことができる。

・編集した結果のテキストデータを、指定したファイルにセーブする。

・表示したテキストの選択部分に対し、フォントメニューで指定したフォントファミリーやフォントサイズを設定できる。

・ウィンドウをリサイズすると、それに合わせてテキストの折り返し表示幅を変更するモードで動かすことができる。

この外部仕様と図10.2のスクリーンショットだけではMemoAppアプリケーションの動きが分からない場合は、実際に動かしてみて下さい。MemoAppのソースファイルは、付録に付けたサンプルコード集の“10.1_MemoApp”というフォルダに入っています。このアプリケーションの機能は、テキストエディタとして一般的なものであり、BeOS付属の“StyledEdit”とよく似ています。ですから、操作手順については特に説明しません。実は、StyledEditを真似て作ったのがMemoAppなのです。実際に動かしてみれば、印刷など一部の機能が削られていることを除き、だいたい同じようなものだということが分かるでしょう。

MemoAppアプリケーションの動きが分かったら、次はその内部を見てみましょう。図10.3に、MemoAppのモジュール構成を示します。

図 MemoAppのモジュール構成
図[10.3] MemoAppのモジュール構成

以下に、図10.3に示したクラスの概要を述べます。

■MemoApp
MemoAppのアプリケーションクラス。テキスト編集ウィンドウの管理を行います。また、オープンダイアログ用のファイルパネルオブジェクト(BFilePanel)を内部に持ち、ユーザのメニュー操作に応じて開きます。また、オープンダイアログで指定されたテキストファイルの内容を読み出し、テキスト編集ウィンドウを開いて表示します。

■MemoWindow
テキスト編集ウィンドウのクラス。自分に貼りつけたテキストビュー(BTextViewオブジェクト)と連携し、編集メニューやフォントメニューに対するユーザの操作に応答します。つまり、ユーザのメニュー操作に応じてテキストビューのメソッドを呼び出し、必要な処理を行います。また、テキストデータの内容をファイルに保存する機能を持ちます。保存すべきファイルが存在していない場合には、内部に持つセーブダイアログを開いて保存先の指定をユーザから受け取ります。

■MyTextView
BTextViewのサブクラス。ユーザの入力操作によって、表示しているテキストの内容に変更が生じた場合は、内部のフラグにそのことを記録します。MemoWindowクラスは、このフラグにアクセスしてテキストの状態を知り、ファイルメニューの項目の状態を制御します。また、ウィンドウのリサイズによって自分の大きさが変わったとき、それに合わせてテキストの折り返し表示幅を調節する機能を持っています。

10.1.5MemoAppのソースコード
リスト10.3~8に、MemoAppアプリケーションを構成するクラスのうち、MemoAppとMemoWindow、およびMyRefFilterのソースを示します。

[リスト10.3] MyTextRefFilter
/* テキストファイル用のフィルタクラス */
class MyTextRefFilter : public BRefFilter {
public:
    bool	Filter(const entry_ref*, BNode*, struct stat*,
            const char* mimetype);
};

/*
 * MyTextRefFilterのフィルタ関数;
 */
bool
MyTextRefFilter::Filter (const entry_ref* ref, BNode* /* node */,
    struct stat* /* st */, const char* mimetype)
{
    BEntry		theEntry(ref, true);  /* シンボリックリンクを解決 */
    BMimeType	theType(mimetype);
    entry_ref	orgRef;

    /* ディレクトリは通す */	
    if (theEntry.IsDirectory())
        return true;

    /* シンボリックリンクが指す先のファイルタイプ情報を取得 */
//  (void)theEntry.GetRef(&orgRef);
    if (theEntry.GetRef(&orgRef) != B_OK) {  /* for R3's symbolick link */
        orgRef = *ref;
    }
    if (orgRef != *ref) {
        char		orgType[B_MIME_TYPE_LENGTH];
        BNode		orgNode(&orgRef);
        BNodeInfo	orgNodeInfo(&orgNode);
		
        (void)orgNodeInfo.GetType(orgType);
        (void)theType.SetTo(orgType);
    }

    /* "text"タイプのファイルは全て通す */
    if (strcmp(theType.Type(), "text") == 0)
        return true;
    else {
        BMimeType	theSuperType;
		
        if (theType.GetSupertype(&theSuperType) == B_OK
                && strcmp(theSuperType.Type(), "text") == 0)
            return true;
    }
	
    return false;
}
[リスト10.4] MemoApp.h
#ifndef _MEMO_APP_H_
#define _MEMO_APP_H_

#include <app/Application.h>

/* 関連クラス・構造体 */
class	MemoWindow;
class	BAlert;
class	BFilePanel;
class	BRefFilter;


/*
 * MemoAppクラスの定義
 */
class MemoApp : public BApplication {
// メソッド
public:
    // 初期化と解放
    MemoApp(void);
    ~MemoApp(void);

private:
    // 起動・終了時処理
    void	ReadyToRun(void);
	
    // メッセージ応答
    void	MessageReceived(BMessage* message);
    void	RefsReceived(BMessage* message);
    void	AboutRequested(void);
	
    // ドキュメントウィンドウの管理
    MemoWindow*	NewDocument(bool doShow = true);
    void	OpenDocument(const entry_ref& inRef);
    void	ShowOpenPanel(void);
    void	DocumentClosed(void);
	
    // フォントメニュー作成
    void	ConfigureFontMenu(void);

// データメンバ
private:
    BAlert*     fAboutBox;        /* アバウトダイアログ */
    int32       fCreatedDocs;     /* 生成したドキュメントウィンドウの数 */
    int32       fNumDocWindow;    /* 開いているドキュメントウィンドウの数 */
    BMessage*   fFontMenuArchive;	/* 作成済みのフォントメニュー */
    BFilePanel* fOpenPanel;       /* オープンダイアログ */
    BRefFilter* fRefFilter;       /* テキストファイル用のフィルタ */
};


#endif  /* _MEMO_APP_H_ */
[リスト10.5] MemoApp.cp
void
MemoApp::ShowOpenPanel (void)
{
    /* 必要ならオープンダイアログを生成 */
    if (fOpenPanel == NULL) {
        fOpenPanel = new BFilePanel(B_OPEN_PANEL);
        fRefFilter = new MyTextRefFilter();
        fOpenPanel->SetRefFilter(fRefFilter);
    }
	
    /* 既に表示している場合は最前面に出すだけ */
    if (! fOpenPanel->IsShowing())
        fOpenPanel->Show();  /* 表示 */
    else
        fOpenPanel->Window()->Activate();  /* 最前面へ */
	
    return;
}

void
MemoApp::RefsReceived (BMessage* message)
{
    status_t  sts;
    entry_ref theRef;
    BEntry    entryObj;
	
    /* 必要ならフォントメニューを作成 */
    if (fFontMenuArchive == NULL)
        this->ConfigureFontMenu();
	
    for (int32 i = 0;; ++i) {
        sts = message->FindRef("refs", i, &theRef);
        if (sts != B_OK)
            break;
        (void)entryObj.SetTo(&theRef, true);  /* シンボリックリンクを解決 */
        if (entryObj.IsFile()) {
            (void)entryObj.GetRef(&theRef);   /* シンボリックリンク対策 */
            this->OpenDocument(theRef);
        }
    }
	
    return;
}
[リスト10.6] MemoWindow.h
#ifndef _MEMO_WINDOW_H_
#define _MEMO_WINDOW_H_

#include <interface/Window.h>

/* 関連クラス・構造体 */
class	BFilePanel;
class	MyTextView;


/*
 * MemoWindowクラスの定義
 */
class MemoWindow : public BWindow {
// メソッド
public:
    // 初期化と解放
    MemoWindow(BRect frame, const char* inTitle, uint32 quitMsg);
    ~MemoWindow(void);
    void	InitContent(BMenuBar* inMenuBar, bool doShow);
	
    // テキストファイルの読み込みと保存
    void	LoadFile(const entry_ref& inRef);
    bool	GetEntryRef(entry_ref* outRef) const;
private:
    static status_t	CreateDocument(const entry_ref& inRef);
    void	SaveDocumentTo(const entry_ref& inNewRef);
    void	ShowSavePanel(void);
	
    // メッセージ応答
    void	MessageReceived(BMessage* message);
    void	SaveRequested(BMessage* message);
	
    // 終了時処理
    bool	QuitRequested(void);
    void	Quit(void);
	
    // メニュー応答
    void	DoSave(void);
    void	DoClear(void);
    void	SetFontFamily(BMessage* message);
    void	SetFontStyle(BMessage* message);
    void	SetFontSize(BMessage* message);
    void	ToggleLineAdjust(BMessage* message);
	
    // メニュー調節
    void	MenusBeginning(void);
    void	AdjustFileMenu(BMenuBar* inMenuBar);
    void	AdjustEditMenu(BMenuBar* inMenuBar);
    void	AdjustFontMenu(BMenuBar* inMenuBar);
	
    // テキストビューの取得
    MyTextView*	GetTextView(void);

// データメンバ
private:
    entry_ref   fFileRef;    /* データファイル */
    bool        fHasFile;    /* fFileRefが有効か */
    bool        fInQuitting;	/* 終了処理の途中か */
    uint32      fQuitMsg;    /* 終了通知メッセージ */
    BFilePanel* fSavePanel;  /* セーブダイアログ */
	
    // クラスデータ
    static BPoint	sNextLeftTop;	/* 次にウィンドウを開く位置 */
};


#endif  /* _MEMO_WINDOW_H_ */
[リスト10.7] MemoWindow.cp-1
void
MemoWindow::ShowSavePanel (void)
{
    /* 必要ならセーブダイアログを生成 */
    if (fSavePanel == NULL) {
        BMessenger	messenger(this);
        fSavePanel = new BFilePanel(B_SAVE_PANEL, &messenger);
    }
	
    /* セーブダイアログを表示 */
    if (fSavePanel->IsShowing())
        fSavePanel->Window()->Activate();
    else {
        BWindow*		theWindow = fSavePanel->Window();
        BTextControl*	theEditField;
        BAutolock		lock(theWindow);

        theEditField = cast_as(
            theWindow->FindView("text view"), BTextControl
        );
        theEditField->SetText(this->Title());
        theEditField->TextView()->SelectAll();
        fSavePanel->Show();
    }
	
    return;
}

void
MemoWindow::SaveRequested (BMessage* message)
{
    status_t   sts;
    entry_ref  theDirRef, theFileRef;
    BDirectory theDir;
    BEntry     theEntry;
    char*      theFileName;
	
    /* 保存先のエントリ情報を取得 */
    sts = message->FindRef(kDirArg, &theDirRef);
    if (sts != B_OK)
        goto err;
    sts = message->FindString(kNameArg, &theFileName);
    if (sts != B_OK)
        goto err;
	
    /* 保存先ファイルのエントリ情報を作成 */
    sts = theDir.SetTo(&theDirRef);
    if (sts != B_OK)
        goto err;
    sts = theEntry.SetTo(&theDir, theFileName);
    if (sts != B_OK)
        goto err;
    sts = theEntry.GetRef(&theFileRef);
    if (sts != B_OK)
        goto err;

    /* 保存処理を実行 */
    this->SaveDocumentTo(theFileRef);

    return;
err:
    ::Error("MemoWindow::SaveRequested", sts);
    return;
}

void
MemoWindow::SaveDocumentTo (const entry_ref& inNewRef)
{
    status_t    sts;
    BFile       myFile;
    MyTextView* theTextView = this->GetTextView();
	
    /* 必要なら保存先のファイルを作成 */
    if (!fHasFile || inNewRef != fFileRef) {
        BFile	oldFile;

        /* 他のファイルを置き換える場合は、先に削除 */
        if (oldFile.SetTo(&inNewRef, B_READ_ONLY) == B_OK) {
            BEntry	theEntry(&inNewRef);
			
            sts = theEntry.Remove();
            if (sts != B_OK)
                goto err;  /* 削除に失敗 */
            oldFile.Unset();
        }
		
        /* 新しいファイルを作成 */
        sts = CreateDocument(inNewRef);
        if (sts != B_OK)
            goto err;  /* 作成に失敗 */
		
        /* ファイル情報を記録 */
        fHasFile = true;
        fFileRef = inNewRef;
        this->SetTitle(inNewRef.name);
    }
	
    /* テキストデータをファイルに書き込む */
    sts = myFile.SetTo(&fFileRef, B_WRITE_ONLY);
    if (sts == B_ENTRY_NOT_FOUND) {
        /* ファイルが見当たらなければ作り直す */
        sts = CreateDocument(fFileRef);
        if (sts == B_OK)
            sts = myFile.SetTo(&fFileRef, B_WRITE_ONLY);
    }
    if (sts != B_OK)
        goto err;  /* ファイルを開けない */
    (void)myFile.Write(theTextView->Text(), theTextView->TextLength());
    theTextView->MakeClean();  /* 変更内容を保存した */
	
    /* 必要ならば自身に対する終了要求を発行 */
    if (fInQuitting)
        this->PostMessage(B_QUIT_REQUESTED);
	
    return;
err:
    ::Error("MemoWindow::SaveDocumentTo", sts);
    return;
}

status_t
MemoWindow::CreateDocument (const entry_ref& inRef)
{
    status_t  sts;
    BFile     newFile;
    BNodeInfo newNodeInfo;

    /* ファイルを新規作成 */	
    sts = newFile.SetTo(&inRef, B_WRITE_ONLY|B_CREATE_FILE);
    if (sts != B_OK)
        goto err;  /* 作成に失敗 */	

    /* ファイルタイプ属性を設定 */
    sts = newNodeInfo.SetTo(&newFile);
    if (sts != B_OK)
        goto err;
    sts = newNodeInfo.SetType("text/plain");
    if (sts != B_OK)
        goto err;
    sts = newNodeInfo.SetPreferredApp("application/x-vnd.FtGUN-MemoApp");
    if (sts != B_OK)
        goto err;
	
    return B_OK;
err:
    ::Error("MemoWindow::CreateDocument", sts);
    return sts;
}

void
MemoWindow::LoadFile (const entry_ref& inRef)
{
    BAutolock  lock(this);  /* 自身をロック */
    BTextView* theTextView = this->GetTextView();
    BFile      fileObj(&inRef, B_READ_ONLY);
    off_t      fileSize;

    /* テキストビューにファイルの内容をセット */
    fileObj.GetSize(&fileSize);
    theTextView->SetText(&fileObj, 0, fileSize);
	
    /* ファイル情報を更新 */
    fFileRef = inRef;
    fHasFile = true;
    this->SetTitle(inRef.name);
	
    /* まだ表示されていないなら表示 */
    if (this->IsHidden())
        this->Show();
	
    return;
/*
 * 注意:本当は、指定されたファイルのパーミッションをチェックして書き込
 *    み可能かどうかを調べた方がよいが、ここでは省略している。
 *    ('98. 5/11, koga@ftgun.co.jp)
 */
}

[リスト10.8] MemoWindow.cp-2
void
MemoWindow::SetFontFamily (BMessage* message)
{
    BTextView* theTextView = this->GetTextView();
    BFont      theFont;
    BMenuItem* theFamilyItem;
    BMenuItem* theStyleItem;
	
    /* フォントメニューの選択項目を取得 */
    (void)message->FindPointer(kSourceArg, &theFamilyItem);
    theStyleItem = theFamilyItem->Submenu()->FindMarked();
    if (theStyleItem == NULL)
        theStyleItem = theFamilyItem->Submenu()->ItemAt(0);
	
    /* 選択部分のフォントファミリーとスタイルを変更 */
    theFont.SetFamilyAndStyle(
        theFamilyItem->Label(), theStyleItem->Label()
    );
    theTextView->SetFontAndColor(&theFont, B_FONT_FAMILY_AND_STYLE);
	
    return;
}

void
MemoWindow::SetFontStyle (BMessage* message)
{
    BTextView* theTextView = this->GetTextView();
    BFont      theFont;
    BMenuItem* theFamilyItem;
    BMenuItem* theStyleItem;
	
    /* フォントメニューの選択項目を取得 */
    (void)message->FindPointer(kSourceArg, &theStyleItem);
    theFamilyItem = theStyleItem->Menu()->Superitem();
	
    /* 選択部分のフォントファミリーとスタイルを変更 */
    theFont.SetFamilyAndStyle(
        theFamilyItem->Label(), theStyleItem->Label()
    );
    theTextView->SetFontAndColor(&theFont, B_FONT_FAMILY_AND_STYLE);

	return;
/*
 * 注意:フォントメニューの選択項目を取得した後の処理は、SetFontFamily()
 *    と同じである。なので、その部分を一つにまとめる方が良い。
 *    ('98. 5/11, koga@ftgun.co.jp)
 */
}

void
MemoWindow::SetFontSize (BMessage* message)
{
    BTextView* theTextView = this->GetTextView();
    BFont      theFont;
    BMenuItem* theSizeItem;
	
    /* フォントメニューの選択項目を取得 */
    (void)message->FindPointer(kSourceArg, &theSizeItem);

    /* 選択部分のフォントサイズを変更 */
    theFont.SetSize(atoi(theSizeItem->Label()));
    theTextView->SetFontAndColor(&theFont, B_FONT_SIZE);

    return;
}

void
MemoWindow::MenusBeginning (void)
{
    BMenuBar*	theMenuBar = this->KeyMenuBar();

    this->AdjustFileMenu(theMenuBar);
    this->AdjustEditMenu(theMenuBar);
    this->AdjustFontMenu(theMenuBar);

    return;
}

void
MemoWindow::AdjustFileMenu (BMenuBar* inMenuBar)
{
    bool	isDirty = this->GetTextView()->IsDirty();

    /* "Save"と"Save As..."を調節 */	
    inMenuBar->FindItem(SAVE_DOC)->SetEnabled(isDirty);
    inMenuBar->FindItem(SAVE_DOC_AS)->SetEnabled(isDirty || fHasFile);
	
    return;
}

void
MemoWindow::AdjustEditMenu (BMenuBar* inMenuBar)
{
    bool       isValidSel;
    int32      selStart, selEnd;
    BTextView* theTextView = this->GetTextView();
	
    /* テキストの選択範囲をチェック */
    theTextView->GetSelection(&selStart, &selEnd);
    isValidSel = (selStart != selEnd);
	
    /* "Cut", "Copy", "Clear"を調節 */
    inMenuBar->FindItem(B_CUT)->SetEnabled(isValidSel);
    inMenuBar->FindItem(B_COPY)->SetEnabled(isValidSel);
    inMenuBar->FindItem(EDIT_CLEAR)->SetEnabled(isValidSel);
	
    /* "Paste"を調節 */
    inMenuBar->FindItem(B_PASTE)->SetEnabled(
        theTextView->AcceptsPaste(be_clipboard)
    );
	
    return;
}

void
MemoWindow::AdjustFontMenu (BMenuBar* inMenuBar)
{
    BTextView*	theTextView = this->GetTextView();
    BFont		theFont;
    uint32		theMode;
    font_family	theFamily;
    font_style	theStyle;
    char		sizeStr[32];
    BMenuItem*	theMenuItem;
    BMenu*		theFontMenu = inMenuBar->SubmenuAt(2);

    /* 各項目の選択を解除 */
    theMenuItem = theFontMenu->SubmenuAt(0)->FindMarked();
    if (theMenuItem != NULL)  /* サイズ指定項目 */
        theMenuItem->SetMarked(false);
    theMenuItem = theFontMenu->FindMarked();
    if (theMenuItem != NULL) {  /* ファミリー指定項目 */
        theMenuItem->SetMarked(false);
        theMenuItem = theMenuItem->Submenu()->FindMarked();
        if (theMenuItem != NULL)  /* スタイル指定項目 */
            theMenuItem->SetMarked(false);
    }

    /* 選択部分のフォントを取得 */
    theTextView->GetFontAndColor(&theFont, &theMode);
    theFont.GetFamilyAndStyle(&theFamily, &theStyle);
	
    /* "Size"サブメニューの選択項目を調節 */
    if (theMode & B_FONT_SIZE) {
        sprintf(sizeStr, "%d", (int)theFont.Size());
        theMenuItem = theFontMenu->SubmenuAt(0)->FindItem(sizeStr);
        if (theMenuItem != NULL)
            theMenuItem->SetMarked(true);
    }

    /* フォントファミリーとスタイル指定の選択項目を調節 */	
    if (theMode & B_FONT_FAMILY_AND_STYLE) {
        /* フォントファミリーの選択項目を調節 */
        theMenuItem = theFontMenu->FindItem(theFamily);
        theMenuItem->SetMarked(true);

        /* フォントスタイルの選択項目を調節 */
        theMenuItem = theMenuItem->Submenu()->FindItem(theStyle);
        theMenuItem->SetMarked(true);
    }
	
	return;
/*
 * 注意:選択部分の中で、たとえば複数のサイズを含む場合はサイズサブメ
 *    ニューの項目が選ばれないようにしている。これは、選択した部分
 *    においてはその属性が一意でないことを表わすためである。
 *    ('98. 5/11, koga@ftgun.co.jp)
 */
}

以上のソースについて、その内容を簡単に説明します。

・リスト10.3
MemoAppクラスが内部で使っている、テキストファイル用のフィルタクラスです。BRefFilterのサブクラスを作る例として見て下さい。リスト10.5に載せたMemoAppクラスのShowOpenPanel()メソッドでは、オープンダイアログに対してSetRefFilter()メソッドを呼び出し、MyTextRefFilterのインスタンスをセットしています。

・リスト10.4
MemoAppクラスの定義を書いた、インタフェースファイルです。

・リスト10.5
MemoAppクラスのメソッド定義から、一部を抜き出したものです。このリストには、ShowOpenPanel()とRefsReceived()だけを載せています。オープンダイアログの呼び出しと、それに対するメッセージ応答処理の例として見て下さい。

・リスト10.6
MemoWindowクラスの定義を書いた、インタフェースファイルです。

・リスト10.7
MemoWindowクラスのメソッド定義のうち、主にテキストデータの保存と読み込みに関するものを選んで載せました。セーブダイアログの呼び出しと、それに対するメッセージ応答の例が、ShowSavePanel()とSaveRequested()です。また、ファイルを作って、テキストビュー(BTextView)から取り出したテキストを保存する例が、SaveDocumentTo()とCreateDocument()です。最後のLoadFile()は、ファイルから読み出したテキストをテキストビューにセットする例です。

・リスト10.8
MemoWindowクラスのメソッド定義のうち、編集メニューの実行やメニュー調節を行うものを選んで載せました。フォントメニューの選択操作に従い、テキストのフォントを変更する例が、SetFontFamily(), SetFontStyle(), およびSetFontSize()です。これらのメソッドは、フォントメニューの選択によって送られたメッセージの内容に応じ、MessageReceived()から呼び出されます。また、テキストビューの状態に応じてメニュー調節を行う例として、AdjustFileMenu(), AdjustEditMenu(), およびAdjustFontMenu()を載せました。これらは、MenusBeginning()メソッドから呼び出されます。なお、カットやコピーなど、BTextViewクラスがショートカットキーを受け付け可能なメニュー項目は、ターゲットをテキストビューにセットするだけで、あとの処理はテキストビューに任せています。


それから、もう一点補足しておきます。アプリケーションアイコンにファイルのアイコンをドラッグ&ドロップした時、そのファイルを開けるようにするには、アプリケーションクラスのRefsReceived()メソッドを再定義しただけでは不足です。これは、アプリケーションのファイルタイプ属性を設定する必要があります。

4.3節で標準リソースファイルの作り方を説明した際、“Supported Types”項目について説明しました。MemoAppアプリケーションでは、テキストタイプの全てのファイルを開けることにしていますので、そのことを設定しておく必要があります。この項目には、“text/plain”に加えて“text”を設定し、すべてのテキストタイプを受け入れ可能であることをシステムに知らせます。付録のサンプルコード集に入っているMemoAppのリソースファイル(MemoApp.rsrc)を開き、“Supported Types”項目の設定内容を確認してみて下さい。





10.2  データを保存するには

この節では、ファイルに対するデータの保存について補足します。前の節では、BFileクラスを使ってファイルにデータを保存しました。つまり、BFileクラスにデータファイルのエントリ情報を渡して初期化し、データファイルを開いてテキストデータを書き込みました。実は、ファイルに対してデータを保存する方法は、これ以外にもあります。それが、ファイルのノード属性を利用するやり方です。

BeOSのファイルシステム(BeFS)が持つ、ノード属性の設定機能については既に説明してきました。また、前の章では、ノード属性に対するインデックスを作成し、それを利用したドキュメント管理ツールのサンプルもとりあげました。ここでは、ファイルの本体に格納するデータに対し、付属的なデータをノード属性として保存するやり方を示します。付属データの例として、テキストのスタイルデータについて考えてみましょう。

MemoAppアプリケーションを実際に動かしてみた人は、このアプリケーションの重大な欠陥に気付いたかも知れません。実は、MemoAppのフォントメニューを使ってテキストのフォントを設定しても、ファイルに保存する時にそれが失われてしまうのです。それは当然で、リスト10.7でMemoWindowクラスのSaveDocumentTo()メソッドを見れば分かるように、テキストデータしかファイルに保存していないのが原因です。この欠陥を改善するには、テキストビューからスタイルデータを取り出し、それを一緒に保存する必要があります。しかし、どうやって保存すればよいのでしょうか?

たとえば、データファイルの内部を二つに分け、先頭にテキストデータを格納し、その後にスタイルデータを書き込むというやり方が考えられます。しかし、そのようにしてデータを保存したファイルは、もはやテキストファイルではありません。そのファイルは、MemoApp以外のテキストエディタで開くことはできないでしょう。ノード属性を利用すると便利なのは、こういう場合です。つまり、ファイル本体に格納するわけにはいかない付属的なデータは、ノード属性に入れてしまえばよいのです。

実際、BeOS付属のテキストエディタ(StyledEdit)は、スタイルデータをノード属性として保存しています。こうしておけば、そのノード属性のことを知らない他のテキストエディタでファイルを開いても、単にスタイルデータが無視されるだけです。スタイルデータが無視されても、本体のテキストデータを読み出すのには全く問題ありません。このように、他のアプリケーションと交換可能なフォーマットでデータを保存する場合、アプリケーションに固有な付属データの格納場所としてノード属性を利用します。BeFSのノード属性は、MacOSでいうリソースフォークに似た使い方ができるのです。

リスト10.9に、MemoAppアプリケーションの改良版から抜き出したソースを示します。この改良版では、テキストのスタイルデータをファイルのノード属性として保存します。そのファイルを開いたときは、ファイル本体に格納されているテキストデータと一緒に、ノード属性からスタイルデータを読み出し、テキストビューにセットします。さらに、スタイルデータの保存形式と属性名をStyledEditに合わせているため、両者の間でスタイル付きテキストファイルを交換することが可能です。

[リスト10.9] MemoWindow.cp(改訂版)
static void
SaveStyleRunArray (BFile& inFile, BTextView* inTextView)
{
    text_run_array* theRunArray;
    void*           theData;
    int32           theSize;

    /* スタイル情報を取得してフラット化 */
    theRunArray = inTextView->RunArray(0, inTextView->TextLength());
    theData = BTextView::FlattenRunArray(theRunArray, &theSize);
	
    /* スタイル情報をノード属性として保存 */
    (void)inFile.WriteAttr(
        "styles",'RAWT', 0, theData, theSize
    );
	
    /* 不要領域を解放 */
    free(theData);
    free(theRunArray);
	
    return;
}

static void
LoadStyleRunArray (BFile& inFile, BTextView* inTextView)
{
    struct attr_info  theInfo;

    /* スタイル情報のノード属性があればロード */
    if (inFile.GetAttrInfo("styles", &theInfo) == B_OK) {
        text_run_array*  theRunArray;
        void*            theData = malloc(theInfo.size);

        /* スタイル情報をロード */
        (void)inFile.ReadAttr(
            "styles", theInfo.type, 0, theData, theInfo.size
        );
		
        /* フラット化を解除してテキストビューにセット */
        theRunArray = BTextView::UnflattenRunArray(theData);
        inTextView->SetRunArray(0, inTextView->TextLength(), theRunArray);
		
        /* 不要領域を解放 */
        free(theRunArray);
        free(theData);
    }
	
    return;
}


リスト10.9には、スタイルデータの保存と読み出しを行う手続きだけを載せています。この手続きは、改良したMemoWindowクラスが利用しています。改良版のMemoWindowは、SaveDocumentTo()メソッドからリスト10.9のSaveStyleRunArray()を呼び出し、テキストビューから取り出したスタイルデータをファイルのノード属性として保存します。また、LoadFile()メソッドからLoadStyleRunArray()を呼び出し、ノード属性として保存されたスタイルデータを読み出してテキストビューにセットします。改良版MemoWindowクラスのソースファイルは、付録に付けたサンプルコード集の“10.1_MemoApp”フォルダに“MemoWindow.cp.NEW”という名前で入っています。オリジナルのソースと入れ替えてアプリケーションを作り直し、スタイルデータを保存できることを確認してみて下さい。


10.3  レプリカント誕生秘話

この節では、10.1節で説明したテキストエディタのサンプル(MemoApp)を改造し、レプリカント対応機能を追加したバージョンを示します。この改造バージョンは“StickyMemo”という名前で、ウィンドウからテキストビュー部分をドラッグしてコピーし、デスクトップに貼りつけることができます。図10.4は、StickyMemoを動かした様子を撮ったものです。レプリカント化したテキストビューを、デスクトップに貼りつけているのが分かるでしょうか。

図 StickyMemoのスクリーンショット
図[10.4] StickyMemoのスクリーンショット

このスクリーンショットを撮ったときは、StickyMemoのメニューに使うラベルを日本語化してみました。メニューを日本語化するには、単に日本語のラベル文字列を指定し、メニューオブジェクトに日本語フォントを設定するだけです。興味がある人は、自分でアプリケーションを改造してみて下さい。

10.3.1add-onと転生するオブジェクト
StickyMemoについて説明する前に、それが利用しているレプリカントの仕組みを紹介しておきます。2.5節でadd-onの説明を行ったときに、「レプリカントはadd-onの仕組を利用したものだ」と述べました。ここでは、そのことについて説明します。

まず、レプリカント機構の基礎となっているadd-onの仕組を、ここでおさらいします。2.5節で述べたように、add-onとは動的リンクライブラリ、つまり共有ライブラリの一種です。通常の共有ライブラリと違うのは、実行ファイルのロードとリンクをOSが行うのではなく、add-onを呼び出すアプリケーション自身が行うという点です。Kernel Kitには、add-onを呼び出すための二つのAPI手続きが用意されています。それが、以下に述べるload_add_on()とget_image_symbol()です。

■load_add_on()
引数で指定した実行ファイルをロードします。アプリケーションの起動に使うload_image()とは違い、チームやスレッドは生成しません。その代わり、ロードした実行ファイルの内容、つまり実行コードにアクセスして呼び出せるようにします。

■get_image_symbol()
load_add_on()でロードした実行ファイルの内容をアクセスする手続きです。実行ファイルの中にある実行コードから、クラスのメソッドや関数を指すポインタを取り出すことができます。

アプリケーションがadd-onを呼び出すときは、この二つの手続きを組み合わせて使います。つまり、まずload_add_on()でadd-onの実行ファイルをロードし、それからget_image_symbol()で関数ポインタを取り出します。関数ポインタを取り出したら、あとは普通のメソッドや関数の場合と同様に、それを呼び出します。

たとえば、“/boot/home/config/add-ons/MyApps/MyAddOn”というパスで指定されるadd-onをロードし、そのadd-onが提供している“DoOurJob()”という名前の関数を呼び出す場合の手順は、次のようになります。

1.)load_add_on()の引数に/boot/home/config/add-ons/MyApps/MyAddOnを渡し、そのadd-on、つまり“MyAddOn”という名前の実行ファイルをロードする。

2.)load_add_on()の戻り値として受け取った「イメージID」と、そのポインタを取り出したい関数の名前、つまり“DoOurJob”を引数にして、get_image_symbol()を呼び出す。これによって、“MyAddOn”内部で定義されている関数“DoOurJob()”の関数ポインタが得られる。

3.)取り出した関数ポインタを使って、関数呼び出しを行う。

この手順は、通常の共有ライブラリの場合はコンパイラとリンカ、およびOSによって行われるものです。つまり、関数“DoOurJob”が共有ライブラリで定義されている場合に、プログラムのソースコードで

/* DoOurJob()の呼び出し */
DoOurJob();

という行を書くと、できたプログラムが実行されるときに、C/C++言語の処理系とOSの働きによって(1)~(3)と同様のことが行われるのです。言い換えると、add-onの呼び出しというのは、本来は言語処理系とOSによって行われる動的リンクライブラリの呼び出し手順のうち、一部をプログラム(アプリケーション)自身で行うというものです。このように、本来はシステムによって行われる動的リンクライブラリの呼び出しをアプリケーション自身が行うことにより、ロードのタイミングを自由に制御できるのです。これが、add-onの原理です。

さて、通常の共有ライブラリは、そのライブラリが提供している関数やクラスのインタフェースが分かっていれば、どのアプリケーションからも平等に呼び出すことができます。add-onでもこれは変わりません。add-onと共有ライブラリは、本質的には変わらないものなです。いえ、もっとはっきり言えば、両者の違いは、それを利用するアプリケーションがどう呼び出すかにしかなく、その実体は同じものなのです。つまり、共有ライブラリをadd-onとして呼び出すことも可能です(注10-4)。

そう。勘のいい人なら既に気付いたでしょうけれど、add-onと共有ライブラリの関係と同じことが、アプリケーションについてもいえるのです。すなわち、add-onとアプリケーションの間に本質的な違いはなく、アプリケーションをadd-onとして呼び出すことが可能です。これが、レプリカント機構を支える基本原理なのです。

レプリカント対応したアプリケーションでは、自分が定義したクラスのオブジェクトを他のアプリケーションへ渡し、そのオブジェクトを「再生」してもらうことができます。これは、オブジェクトのデータメンバ情報と一緒に自分の実行ファイル情報を伝えることによって行います。レプリカントを受け取った方のアプリケーションは、以下の手順でオブジェクトを再生します。

1.)渡された実行ファイル情報をもとに、元のアプリケーションをadd-onとして呼び出す。つまり、load_add_on()を使って元のアプリケーションの実行ファイルをロードする。

2.)get_image_symbol()を使い、受け取ったオブジェクトを生成する手続きの関数ポインタを取り出す。取り出したら、その手続きを呼び出してオブジェクトを生成する。

3.)オブジェクトを生成すると同時に、受け取ったデータメンバ情報でそれを初期化する。

デスクトップに貼りつけたレプリカントが動き出すまでには、このような処理が行われているのです。そして、いったんレプリカントを受け取ってしまえば、他のアプリケーションにレプリカントを転送することが可能です。レプリカントの産みの親にあたるアプリケーションの実行ファイル情報と、レプリカント化したオブジェクトの生成手続き名さえ分かっていれば、あとは上と同じ手順でオブジェクトを再生できるのです。つまり、レプリカントはアプリケーションの間を渡り歩き、次々に複製を作って「転生」してゆくことが可能です。これが「レプリカント(replicants)」と名付けられた所以です。

BeOSのデモアプリケーションの中に“Container”というのがありますが、このアプリケーションのウィンドウにレプリカントを貼りつけることができるのは知っているでしょう。そして、Containerのウィンドウとデスクトップの間でレプリカントをドラッグ&ドロップすると、いくらでも増殖させることができます。ここまでの説明を読んで、その原理が分かったことと思います。

(注)10-4
本書のサンプルでは扱っていませんし、あまり現実的ではありませんが、もし共有ライブラリをadd-onとして使うとすれば、その場合は通常とは違うプログラムの書き方をしなければいけません。つまり、通常の形式でライブラリの関数呼び出しを行うことができないのです。上の(1)~(3)で述べたadd-onの呼び出し手順に従い、get_image_symbol()に関数名を文字列として渡し、関数ポインタを取り出して呼び出しを行う必要があります。

10.3.2レプリカントの身元引き受け
次に、アプリケーションをレプリカント対応させるために必要な作業項目を紹介します。ここでは大まかな手順だけを述べますので、詳細についてはAPIリファレンスを参照して下さい。

■レプリカント化可能なビュークラスの定義と実装
まず、レプリカント化させるビュークラスを定義します。なお、Interface Kitが提供している標準のビュークラスをレプリカント化させるのであれば、何もする必要はありません。独自のビュークラスを使う場合は、最低限次のことを行って下さい。

a.)BMessageオブジェクトを受け取るバージョンのコンストラクタを実装する。
b.)BMessageオブジェクトを受け取ってBArchivableクラスのポインタを返す、“Instantiate()”という名前のクラスメソッドを実装する。このクラスメソッドでは、(a)で実装したコンストラクタで生成したインスタンスを返すようにします。
c.)Archive()メソッドを再定義します。このメソッドは、オブジェクトをアーカイブするためにBViewクラスが継承しているBArchivableクラスで定義されているものです。再定義したArchive()メソッドでは、親クラスのメソッドを呼び出した後、“add-on”という文字列型のデータ項目を追加します。このデータ項目の値として、アプリケーションのシグネチャをセットします。

■BDraggerオブジェクトの貼りつけ
レプリカント化させるビュークラスのインスタンスをウィンドウに貼りつけたら、その上にBDraggerオブジェクトを貼りつけます。BDraggerオブジェクトは、レプリカントをドラッグ&ドロップするためのつまみを表示し、自分が貼りついたスーパービューをドラッグさせる機能を持ったクラスです。詳しくは、APIリファレンスでInterface Kitの章を参照して下さい。

■シンボルの公開
レプリカントを受け取ったアプリケーションがオブジェクトを再生するためには、そのオブジェクトを生成する手続きの関数ポインタを取り出さなければいけません。これは、先ほどレプリカントの再生手順で述べた通りです。関数ポインタの取り出しはget_image_symbol()で行いますが、一つ条件があります。get_image_symbol()で関数ポインタを取り出せるのは、「公開」された関数や手続きだけなのです。したがって、アプリケーションを作成する時に、レプリカント化させるオブジェクトの生成手続き、つまり、そのビュークラスのInstantiate()メソッドが公開されるように、コンパイラとリンカに対する指示を行う必要があります。レプリカント用にシンボルを公開するには、__declspec()マクロを使うか、またはコンパイラに対するpragmaを使います。__declspec()マクロを使う場合は、“dllexport”を引数に与え、クラス名を公開します。また、pragmaを使う場合は“export on”を指定し、クラス定義全体を公開するか、またはInstantiate()メソッドを公開します。詳しい手順は、サンプルコード(リスト10.10)のコメントを参考にして下さい。

以上の作業項目を行えば、アプリケーションをレプリカント対応させ、ビューオブジェクトをレプリカント化することができます。この後で説明するサンプルアプリケーション(StickyMemo)では、スクロールビューをレプリカント化し、その中に入っているテキストビューごと他のアプリケーションへ持っていけるようにしています。

StickyMemoの説明へ移る前に、レプリカントに関連するBeOSのAPIについて補足しておきます。ここまでの説明と合わせ、StickyMemoのソースを読む際の参考にしてみて下さい。レプリカント機構は、BeOSのAPIが提供しているクラスのうち以下に挙げるものが大きく関係しています。

・BShelf
・BDragger
・BArchivable
・BMessage

ここでは、それぞれが果たしている役割を簡単に述べます。

■BShelf
レプリカントを受け取る機能を持ったビュークラスです。ドラッグ&ドロップによって受け取ったBMessageオブジェクトから、レプリカントの情報を抜き出し、オブジェクトを再生します。先ほど述べたオブジェクトの再生手順を実行してくれるため、このクラスを使うだけで、アプリケーションがレプリカントを受け取れるようになります。なお、本書ではBShelfを使ったサンプルは扱っていません。このクラスの使い方は、BeOS付属のサンプルアプリケーション(Container)のソースが参考になるでしょう。

■BDragger
レプリカント化するビューオブジェクトをドラッグ&ドロップできるようにするためのビュークラスです。レプリカント化させるビューオブジェクトに貼りつけて使います。ドラッグ&ドロップ用のつまみを表示し、ドラッグが開始されたら、自分が貼りついているビューオブジェクトに対してArchive()メソッドを呼び出してレプリカント情報を作成します。レプリカント情報を伝えるためのメッセージはBDraggerの働きによって作られるため、これを貼りつけておくだけでよいのです。

■BArchivable
レプリカント化するオブジェクトを再生するためには、そのデータメンバをアーカイブしたものが必要です。BViewクラスは、BArchivableクラスを継承することでアーカイブ機能を受け継いでいます。なお、R3以降のBeOSでは、ビュークラスに限らずBArchivableクラスのオブジェクトをレプリカント化し、他のアプリケーションへ渡すことも可能になっています。

■BMessage
レプリカント化するオブジェクトをBDraggerがアーカイブし、それを受け取ったBShelfが復元するのには、BMessageクラスが備えるデータコンテナとしての機能が大きな役割を果たしています。

10.3.3StickyMemoのモジュール構成とソースコード
必要な説明が終わったので、サンプルアプリケーションの説明に移ります。この三サンプル、つまりStickyMemoは10.1節で使ったMemoAppを改造しただけのものですから、外部仕様は殆ど同じです。違うのは、ウィンドウの左下隅にBDraggerが貼りついており、それをドラッグしてデスクトップへコピーできることだけです。図10.5に二つのアプリケーションのウィンドウを並べた様子を示しますので、違いを見比べて下さい。

図 MemoAppとStickyMemo
図[10.5] MemoAppとStickyMemo

なお、StickyMemoのウィンドウにBDraggerのつまみが表示されない場合は、DeskBarのBeメニューで、“Show Replicants”を選択して下さい。これで表示されるようになるはずです。

さて、StickyMemoとMemoAppのソースは殆ど同じですので、モジュール構成図は省略します。ただし、MemoAppとは違う三つのビュークラスを使っているので、それを図10.6に示します。

図 StickyMemoのカスタムビュー
図[10.6] StickyMemoのカスタムビュー

図10.6に示した三つのビュークラスについて、MemoAppとの違いを以下に述べます。

■MyDragger
BDraggerのサブクラスです。MemoAppでは、レプリカント対応していないのでBDraggerを使う必要はありませんでした。StickyMemoでは、Draw()メソッドを再定義したBDraggerのサブクラスを使い、つまみ部分の背景をグレーで塗りつぶすようにしています。これが、MyDraggerです。

■MyScrollView
BScrollViewのサブクラスです。StickyMemoでは、スクロールビューにBDraggerオブジェクト(実際はMyDraggerのインスタンス)を貼りつけ、スクロールビューをレプリカント化しています。スクロールビューがレプリカントとして貼りつけられるとき、そのサブビューも一緒にコピーされますので、自動的にテキストビューもレプリカント化されます。テキストビューだけをレプリカント化しようとしても、BTextViewはテキスト入力に応じて自動的にリサイズしてしまうため、うまくいきません。そこで、スクロールビューごとレプリカント化しているのです。これと同様のことが、BeOS付属のWebブラウザ(NetPositive)でも行なわれています。

■MyTextView
MemoAppで使っていたものと基本的に同じです。変更したのは、レプリカント化させるためにInstantiate()メソッドとArchive()メソッドを追加したことだけです。

StickyMemoクラスが使っている三つのビュークラスのうち、MyScrollViewのソースだけをリスト10.10と11に示します。それ以外のクラスについては省略しますので、興味がある人は、付録に付けたサンプルコード集から取り出して読んでみて下さい。MemoAppのソースは、サンプルコード集の“10.3_StickyMemo”に収録しています。

[リスト10.10] MyScrollView.h
#ifndef _MY_SCROLL_VIEW_H_
#define _MY_SCROLL_VIEW_H_

#include <interface/ScrollView.h>

/* 関連クラス・構造体 */
class	BDragger;


#if _PR2_COMPATIBLE_
#else
class __declspec(dllexport) MyScrollView;
#endif
/*
 * Intel版では、このように__delcspec()マクロを使ってクラス名を公開しま
 * す。クラス名を公開しないと、レプリカントを受け取った側のアプリケー
 * ションがInstantiate()メソッドの関数ポインタを取り出すことができませ
 * ん。
 */


/*
 * MyScrollViewクラスの定義
 */
class MyScrollView : public BScrollView {
// メソッド
public:
    // 初期化と解放
    MyScrollView(
        const char* name, BView* target, uint32 resizeMask, uint32 flags);
    MyScrollView(BMessage* message);
    ~MyScrollView(void);

    /* アーカイビング */	
#if _PR2_COMPATIBLE_
#pragma export on
#endif
    static BArchivable*	Instantiate(BMessage* message);
#if _PR2_COMPATIBLE_
#pragma export reset
#endif
/*
 * PPC版では、今のところこのように#pragma exportを使ってクラスのシン
 * ボルを公開します。クラス情報を公開しないと、レプリカントを受け取っ
 * た側のアプリケーションがInstantiate()メソッドの関数ポインタを取り
 * 出すことができません。
 * なお、#pragma exportを使ってシンボルを公開した場合、CodeWarriorの
 * プロジェクト設定ウィンドウで“Linker”オプションの“PEF”項目を選
 * び、“Use #pragma”を指定しておく必要があります。
 */
    status_t	Archive(BMessage* data, bool deep = true) const;

private:
    void	MessageReceived(BMessage* message);
    void	AboutRequested(void);

    // マウス応答
    void	MouseDown(BPoint where);
    static int32	DoDrag(void* data);
	
// データメンバ
private:
    BDragger* fDragger;
    BPoint    fStartPos;
};


#endif  /* _MY_SCROLL_VIEW_H_ */
[リスト10.11] MyScrollView.cp
#include "MyScrollView.h"
#include "MyDragger.h"

#include <interface/Alert.h>
#include <support/Debug.h>


const char	kAboutMsg[]	= "MyScrollView(from StickyMemo app)\n\n"
    "Copyright " B_UTF8_COPYRIGHT " 1998 Fort Gunnison, Inc.\n"
    "Author: Shin'ya Koga (koga@ftgun.co.jp)";


/* MyScrollViewクラスの非公開メソッド */
void
MyScrollView::MessageReceived (BMessage* message)
{
    switch (message->what) {
    case B_ABOUT_REQUESTED:
        this->AboutRequested();	break;
    default:
        BScrollView::MessageReceived(message);
    }
	
    return;
}

void
MyScrollView::AboutRequested (void)
{
    BAlert*	alertPanel;
	
    alertPanel = new BAlert("about box", kAboutMsg, "OK");
    (void)alertPanel->Go(NULL);

    return;
}

/*
 * マウス応答; MyScrollView
 */
void
MyScrollView::MouseDown (BPoint where)
{
    BPoint    prevPos;
    uint32    buttons;
    int32     theInt;
    thread_id dragThread;

    this->Window()->CurrentMessage()->FindInt32("buttons", &theInt);
    buttons = theInt;
    if (buttons != B_PRIMARY_MOUSE_BUTTON)
        return;

    fStartPos = where;
    dragThread = ::spawn_thread(
        DoDrag, "drag_thread", B_NORMAL_PRIORITY, this
    );
    (void)::resume_thread(dragThread);
    ::snooze(20 * 1000);
	
    return;
}

int32
MyScrollView::DoDrag (void* data)
{
    MyScrollView* theObj = (MyScrollView*)data;
    BWindow*      theWin = theObj->Window();
    BPoint        prevPos, where;
    uint32        buttons;

    prevPos = theObj->fStartPos;
    do {
        ::snooze(20 * 1000);
        theWin->Lock();
        theObj->GetMouse(&where, &buttons, true);
        theObj->ResizeBy(where.x - prevPos.x, where.y - prevPos.y);
        theWin->Unlock();
        prevPos = where;
    } while (buttons & B_PRIMARY_MOUSE_BUTTON);
	
    return B_OK;
}


/* MyScrollViewクラスの公開メソッド */
/*
 * 初期化と解放; MyScrollView
 */
MyScrollView::MyScrollView (const char* name, BView* target,
		uint32 resizeMask, uint32 flags)
	: BScrollView(name, target, resizeMask, flags, true, true)
{
    BScrollBar*	theScrollBar = this->ScrollBar(B_HORIZONTAL);
    BRect		draggerRect;
	
    ASSERT(theScrollBar);

    theScrollBar->ResizeBy(-15, 0);
    theScrollBar->MoveBy(15, 0);
	
    draggerRect = theScrollBar->Frame();
    draggerRect.right = draggerRect.left;
    draggerRect.left -= 15;
    draggerRect.right  -= 1;
	
    fDragger = new MyDragger(
        draggerRect, this, B_FOLLOW_LEFT | B_FOLLOW_BOTTOM);
    this->AddChild(fDragger);
}

MyScrollView::MyScrollView (BMessage* message)
	: BScrollView(message)
{  /* do nothing */  }

MyScrollView::~MyScrollView (void)
{  /* do nothing */  }

/*
 * アーカイビング; MyScrollView
 */
BArchivable*
MyScrollView::Instantiate (BMessage* message)
{
    return (new MyScrollView(message));
}

status_t
MyScrollView::Archive (BMessage* data, bool deep) const
{
    BScrollView::Archive(data, deep);
    data->ReplaceInt32(
        "_resize_mode", B_FOLLOW_LEFT|B_FOLLOW_TOP
    );
    data->AddString("add_on", "application/x-vnd.FtGUN-StickyMemo");
	
    return B_OK;
}

以下に、MyScrollViewのメソッドうち、主なものについて説明します。ソースを読む際の参考にして下さい。

・MessageReceived()
B_ABOUT_REQUESTEDメッセージを受け取ると、AboutRequested()メソッドを呼び出してアバウトダイアログを表示します。レプリカントを貼りつけた時、ControlキーとCommandキー(またはAltキー)ど同時に押しながらBDraggerのつまみ部分をクリックすると、ポップアップメニューが表示されます。このメニューから“About”を選ぶと、レプリカントのビューオブジェクトに対してB_ABOUT_REQUESTEDメッセージが送られます(注10-5)。MyScrollViewクラスでは、このメッセージに応答してアバウトダイアログを開き、元のアプリケーション、つまりStickyMemoの情報を表示するようにしています。

・MouseDown()
スクロールビューの右下隅部分をクリックしてドラッグすればリサイズできるように、マウスの追跡処理を行っています。BScrollViewクラスには、このようなリサイズのための機能がありませんので、追加で実装しました。BeOS付属のレプリカント対応アプリケーションのうち、NetPositiveのレプリカントにも同様の機能がありますが、内部では同じようなことをやっていると思われます。

なお、マウスの追跡処理は専用のスレッドを起動して行わせていますが、これは、マウスをドラッグしてリサイズしている間も、リサイズ通知のメッセージを受け取ってスクロールバーの表示を更新できるようにするためです。別のスレッドを使わないと、マウスの追跡処理を行っている間はウィンドウのメッセージループが止ってしまい、リサイズ通知メッセージを処理することができません。

・コンストラクタ
水平方向のスクロールバーのサイズを縮め、空いたスペースにBDraggerオブジェクトを貼りつけています。なお、BDraggerクラスをそのまま使うと再描画時の問題があるのは、先ほどMyDraggerの説明で述べた通りです。リスト10.12では、BDraggerの代わりにMyDraggerクラスのインスタンスを生成しています。

・Archive()
add-on情報、つまりStickyMemoのシグネチャをメッセージに追加しています。また、BViewクラスのArchive()メソッドによって追加された項目のうち、リサイズフラグを示す“_resize_mode”の値を置き換え、レプリカントを貼りつけたビューのサイズが変わっても追従しないようにしています。もとのウィンドウに貼りついていた状態では“B_FOLLOW_ALL”に設定していますから、ここで変更しています。こうしておかないと、デスクトップに貼りつけた状態でマシンを再起動すると勝手にリサイズされてしまい、ちょっと面倒なことになるので注意して下さい(注10-6)。

以上で、StickyMemoに関する説明を終わります。

(注)10-5
レプリカントを削除する場合にも、BDraggerが表示するメニューを使います。削除する場合は、このメニューから“Delete”を選んで下さい。

(注)10-6
レプリカント化したビューのリサイズフラグが“B_FOLLOW_ALL”になっていると、マシンが起動してデスクトップが表示された時に、それに合わせて拡がってしまいます。その結果、BDraggerのつまみがスクリーンからはみ出してしまい、削除することすらできなくなってしまう場合があります。


10.4  まとめと練習問題

この章では、最初に挙げた題材をプログラミングするために、次のような解決手段を用いました:

■テキストの表示と編集機能を持つアプリケーションを作る
→BTextViewクラスをウィンドウに貼りつけ、テキストファイルの読み出しや保存処理を実装する。開きたいファイルをユーザに指定してもらうには、BFilePanelクラスのインスタンスをB_OPEN_PANELモードで生成して表示し、B_REFS_RECEIVEDメッセージを受け取る。編集したテキストを保存するときは、BFilePanelクラスのインスタンスをB_SAVE_PANELモードで生成して表示し、B_SAVE_REQUESTEDメッセージを受け取る。

■指定されたテキストファイルを読み出し、その内容を表示する
→B_REFS_RECEIVEDメッセージを受け取ったら、メッセージからファイルのエントリ情報(entry_ref構造体)を取り出す。取り出したエントリ情報でBFileオブジェクトを初期化したら、ファイルの内容を読み出してBTextViewオブジェクトにセットする。また、“text/plain”タイプのファイルをサポートに加え、アプリケーションアイコンにテキストファイルをドラッグ&ドロップできるようにする。

■エディタで編集したテキストの内容を、ファイルに保存する
→B_SAVE_REQUESTEDメッセージを受け取ったら、メッセージから保存先ファイルの親ディレクトリ情報と名前を取り出す。取り出した情報でBFileオブジェクトを初期化したら、BTextViewオブジェクトのテキストをファイルに書き込む。ファイルを新規作成した時は、BNodeInfoクラスを利用してファイルタイプ属性をセットする。

■エディタに表示するテキストのフォントやサイズを変更する
→BFontオブジェクトにフォントやサイズ情報を設定し、BTextViewクラスのSetFontAndColor()メソッドに渡す。マルチスタイルのテキストを扱う場合には、BTextViewクラスのSetStylable()メソッドにtrueを渡す。

■テキストエディタ部品をレプリカント化し、デスクトップに貼りつけられるよう
にする
→BTextViewオブジェクトを囲ったスクロールビューにBDraggerオブジェクトを貼りつける。テキストエディタ部品を構成するビュークラスのうち、自作したもの全てについてArchive()メソッドとInstantiate()メソッドを実装する。さらに、それらのクラスの情報を#pragma export文やdeclspec()文によって公開する。

この章で説明したことや、説明に使ったサンプルアプリケーションに対する理解を深めるために、以下の練習問題について考えてみて下さい。

練習問題 1
10.1の説明に使ったMemoAppアプリケーションを改変してフォントメニューの項目を追加し、テキストの表示色を設定できるようにしてみましょう。テキストの表示色を設定するのも、BTextViewクラスのSetFontAndColor()メソッドで行います。このメソッドの引数についてAPIリファレンスを調べ、どんな値を渡せばよいのか確認して下さい。

練習問題 2
MemoAppアプリケーション用にドキュメントアイコンを作成し、標準リソースに設定してアプリケーションを作り直してみましょう。アイコンの設定が終わったら、MemoAppでファイルの保存操作を行い、ファイルを作って下さい。MemoAppで作ったファイルのアイコンが、自分で作ったドキュメントアイコンになることを確認してみて下さい。

練習問題 3
MemoAppアプリケーションを改変し、ウィンドウのサイズと表示位置をファイルのノード属性として保存し、ファイルを開いた時にそれを復元できるようにしてみましょう。これは、Trackerがフォルダウィンドウを表示するのに使っているやり方の真似です。

練習問題 4
第6章~第9章までのサンプルアプリケーションを改変し、レプリカント対応させてみましょう。第9章までのサンプルアプリケーションは、ウィンドウのトップレベルビューとして貼りつけたビューオブジェクトで殆どの処理を行うようになっています。このため、レプリカント化するのもそれほど難しくないはずです。挑戦してみて下さい。

練習問題 5
BFilePanelクラスのWindow()メソッドを使うと、ファイルパネルのウィンドウにアクセスすることができます。このウィンドウに対してAddChild()メソッドを呼び出してビューオブジェクトを渡せば、ファイルパネルに表示されるビュー部品を追加できるはずです。つまり、ファイルパネルのカスタマイズができるのです。セーブダイアログをカスタマイズして、第9章の“InfoChest”アプリケーションで登録したカテゴリを一覧表示できるようにしてみて下さい。それができたら、ファイルを保存すると同時にカテゴリも一緒に設定されるようにしてみましょう。ドキュメント管理ツールと連動するテキストエディタの完成です!

練習問題 6
BPrintJobクラスについて調べ、MemoAppアプリケーションに印刷機能を追加してみましょう。



Art of BeOS Programming
koga@stprec.co.jp