みなさんこんにちは、プラグイン担当のPです。最近CPU競争が熱いですね。私は年明け早々PentiumIII-733MHzをン万円で購入したのですが、年末にはもう初心者級マシンになりそうです。重量級アプリもブンブン動く超パワフルなマシンが、1年もしないうちに「安価な入門機」として売られるようになるというのは、Java使いにとって非常に喜ばしいニュースです。その一方で、常にとんがった部分を追い求めるヒートシーカーは、なんともいえない切ない気分にさせられるのですよね…。

 さて、プラグイン作成講座第3回は、一太郎Ark製品版のCD-ROMに収録されているプラグインサンプル2と3を題材にします。

 プラグイン作成講座第1回の図6で、プラグインが処理を実行できるタイミングを一覧表にまとめています。このうち「初期化」「動作開始」「動作停止」「破棄」「リソース差し替え」の5つについては前回説明しました。今回は「アクション実行」「メニュー項目追加」の2つについて解説することになります。

 プラグインサンプル2をもとに、メニュー項目の追加と処理の記述方法を説明します。さらに、次回に入れる予定だったプラグインサンプル3について、ダイアログの出し方、プラグイン設定値の変更と保存を、概略だけざっと説明します。

盛りだくさんの内容になりましたが、気合いを入れてまいりましょう。

※実際にプラグインを作るためにはJDK1.2.2が必要ですので、入手しておいてください。



○プラグインサンプル2の概要


 プラグインサンプル2の機能概要をまとめます。

・このプラグインは、メニューバーの「ツール」に2つのメニューを追加する。
・2つのメニューはカスケードし、それぞれ3つと2つのメニュー項目が現れる。
・上の3つには、「カット」「コピー」「ペースト」の機能が割り当てられる。
・下の2つには、プラグインが提供する「機能1」「機能2」が割り当てられる。
・「機能1」「機能2」は、コンソールにメッセージを出す。

図1:追加されたメニュー

 まずはソースをざっと眺めてみましょう。(プラグインサンプル2のソース)

 24行目で、「implements UIPlugin」と宣言しています。これによりPluginSample2クラスがUIPluginとしての役割を持つことになります。UIPluginについての詳しい説明は後述しますが、簡単に言うと「Ark本体がプラグインに対して、メニューバーに追加すべき項目などを問い合わせるためのインターフェイス」です。

 97行目で、ArkPluginContextを通じてUIPluginを登録しています。109行目は登録抹消です。動作開始のタイミングでメニューバー項目を追加し、動作停止のタイミングでメニューバー項目を削除するというものです。

 133行目〜165行目がUIPluginインターフェイスの実装です。前半は「Ark本体に登録する『Action』を返すmethod」、後半は「Ark本体に追加する『メニュー構造』を返すmethod」です。

 Actionとしては、166行目〜195行目に内部クラスとして記述されている2つを返しています。このプラグインサンプル2が一太郎Ark本体のメニューバーに項目を追加し、ユーザがその項目を選択すると、メニュー項目に関連づけられたActionが起動されます。つまり、最終的に177行目〜180行目および190行目〜193行目に処理が移ってくるということです。現状では、コンソールにメッセージを出力する処理を行っています。

 メニュー構造は、UIPlugin.MenuItemクラス(とその派生クラス)を使って表現します。ちょっとわかりにくい書き方をしていますが、第一階層が「RK_MENU1」「RK_MENU2」で、それぞれにカスケードメニューがぶら下がるという構造になっています。

第1階層 第2階層
カット&ペースト(C) カット(T)
コピー(C)
ペースト(P)
私のメニュー(M) アクション1(1)
アクション2(2)
図2:メニュー階層

 では、プラグインサンプル2のミソである「アクション登録」「メニュー項目追加」について、詳しく見ていきましょう。




○一太郎Arkのメニュー処理

 まずは、一太郎Arkがメニューコマンドを実行する仕組みから説明します。

 一太郎Arkは、「名前を付けて保存」や「表挿入」などのコマンドを、すべて「アクション」(javax.swing.Action)として実装しています。各コマンドを実装したアクションには、固有の名前が付けられています。

 アクション名の一覧表は、一太郎Ark製品版CD-ROM内に収録されています。\CONTRIB\SAMPLE\KEY_CFG.ZIP内のactionName.txtに書かれていますので、ご利用ください。

 一方、メニューバーの階層構造情報は一太郎Ark内に埋め込まれています。この階層構造情報の中には、各メニュー項目の「リソースキー」と「アクション名」が含まれています。このリソースキーを使ってメニュー表示用文字列やアイコンを取得し、アクション名を使ってメニューが選択されたときに実行すべきアクションを取得します。

 つまり、メニュー側には「実行すべきアクションの名前」が保持されており、この名前を使ってアクションオブジェクトを引き当て、そのアクションの中に書かれている処理を実行するという仕組みです。「アクション名」が仲介役になっているわけです。

 一太郎Arkでは、メニューバーに限らず、ツールバー、キーボードアクセラレータ(CTRL+Sなど)、コンテキストメニュー、ESCメニューなどもすべて、アクション名で処理本体を引き当てるしくみを使っています。



○プラグインからアクションを追加する

 UIPluginインターフェイスを使うと、プラグインが新たにアクションを追加することができます。これを利用して、プラグインが実行したい処理をアクションとして記述し、あらかじめ一太郎Arkに登録した上で、このアクションと関連付けたメニューを追加すれば、ユーザがそのメニューを選択したときに好きな処理を行うことができます。

 もし既存のアクションと同じ名前でアクションを追加すると、一太郎Ark側の内蔵アクションを上書きすることになります。自分なりの「印刷アクション」や「表挿入アクション」を記述し、標準の処理と差し替えることも可能なのです。

 また、メニューを追加するとき内蔵アクション名と関連付けておくこともできます。プラグインサンプル2の「カット&ペースト」メニュー以下の3つのメニューアイテムは、この方法でカット&ペーストの処理を行っています。

 アクション名は、プラグインサンプル2ではアクションのコンストラクタで指定しています。各アクション内での処理の記述方法は、プラグインサンプル3の解説で説明します。




○メニューの追加方法

 一太郎Arkのプラグインは、メニューバーの「ツール」以下にメニュー項目を追加することができます。メニューの階層構造は「UIPlugin.MenuItem」クラスのオブジェクトツリーで表現し、UIPluginインターフェイスを通じて一太郎Ark本体からアクセスできるようにします。

 具体的には、前回説明したArkPluginContextの「addUIPlugin(UIPlugin aPlugin)」メソッドを使って、UIPluginインターフェイスを実装したクラスを登録します。

 ArkPluginContextに登録するのが「メニュー」や「アクション」そのものではなくて、「メニュー」や「アクション」を取得するためのインターフェイスであることに注意してください。今回のプラグインサンプル2では、自分自身がインターフェイスを実装していますが、もちろん別クラスにすることも可能です。

 また、メニューを表現するのにUIPlugin.MenuItemなんていう独自のクラスを使わないで、JMenuItemでいいじゃないかと思われるかもしれません。しかし、一太郎Arkでは各フレームウインドウごとにメニューの言語を変更できるため、表示用の文字列がリソースとして分離されているような仕組みが必要でした。

 そのため、例えばUIPlugin.MenuItemのコンストラクタは「リソースキー」「アクション名」を引数に取ります。「リソースキー」ではなく「表示用メッセージ」を渡してしまうと、それはある特定の言語専用のメニューオブジェクトになってしまいます。リソースキーを元に、現在のメニュー用言語に対応するリソースから表示用メッセージやアイコンファイル名を取得するという方法が、国際化の面から見るとすっきりしたしくみであると思います。


○メニュー階層の表現方法

 メニュー階層を表現するクラス群は、以下の5つです。

・UIPlugin.MenuItem
・UIPlugin.Menu
・UIPlugin.CheckBoxMenuItem
・UIPlugin.RadioButtonMenuItem
・UIPlugin.MenuSeparator

 これらのクラスを、通常のメニューコンポーネントと同じ感覚で組み合わせてオブジェクトツリーを構築します。

 コンストラクタに渡すのは「リソースキー」と「アクション名」です。一太郎Ark側は、本体内のメニューと同じく、このリソースキーを使ってメニュー表示用文字列やアイコンを取得し、アクション名を使ってメニューが選択されたときに実行すべきアクションを取得します。

 リソースキーは、混乱を避けるため、先頭にプラグインIDをつけるようにしてください。

 ところで、今回公開された「Look and Feel切り替えプラグイン」では、ラジオボタンメニューアイテムを使っています。このプラグインのように、起動時からメニューアイテムに選択マークを入れておきたい場合には、メニューアイテムではなく、メニューに関連付けられたActionオブジェクトの「selected」プロパティをjava.jang.Boolean.TRUEに設定してください。(図3)

if    (defaultLAF.equals(METAL_LAF_CLASS)) {
  getActions()[0].putValue("selected", Boolean.TRUE);
} else if (defaultLAF.equals(MOTIF_LAF_CLASS)) {
  getActions()[1].putValue("selected", Boolean.TRUE);
} else if (defaultLAF.equals(WINDOWS_LAF_CLASS)) {
  getActions()[2].putValue("selected", Boolean.TRUE);
}
図3:LAFChangerプラグインのソースコード抜粋


○メニューリソースの書き方

 メニュー用リソースは、各言語(ロケール)別に用意します。表示用の文字列、アイコンファイル名、ニーモニックを記述します。

キー 内容
リソースキー 表示用メッセージ
リソースキー.nem ニーモニック
リソースキー.icn アイコンファイル名
図4:メニューリソースの書き方


 日本語用リソースは、基本となる英語用との共通部分を省略することができます。また、SJISで書いた場合はJavaが扱えるUnicode形式に変換する必要があります。これらは前回までに説明したリソースの書き方に従ってください。

 プラグインサンプル2でのリソース記述を抜粋します。


plugin_sample2.item13   =Paste
plugin_sample2.item13.nem =P
plugin_sample2.item13.icn =Ark_c04s.gif
plugin_sample2.menu2   =My Menu
plugin_sample2.menu2.nem =M
plugin_sample2.item21   =Action1
plugin_sample2.item21.nem =1

図5:英語用リソース抜粋(MainRes.properties)


plugin_sample2.item13 =ペースト(P)
plugin_sample2.menu2 =私のメニュー(M)
plugin_sample2.item21 =アクション1(1)

図6:日本語用リソース抜粋(MainRes.properties.sjis)




○プラグインサンプル3の概要

 サンプルプラグイン3では、アクションの中に実際に機能を書いています。機能概要をまとめてみましょう。

・このプラグインは、メニューバーの「ツール」にメニュー項目を1つ追加する。
・そのメニューを選択すると、モードレスダイアログが出現する。
・モードレスダイアログは、ウインドウについている「閉じるボタン」で閉じる。
・ほかのJARファイル内からクラス(メモリ使用量モニタ)を生成し、モードレスダイアログ内部に貼り付ける。
・プラグインのプロパティダイアログに、設定項目を出す。
・変更された値はark.propertiesに記憶し、次回起動時も覚えている。

図7:追加されたメニューとモードレスダイアログ(メモリ使用量モニタ)


図8:プラグイン設定値の変更



○ほかのJARファイルを利用するには


 このプラグインは、JDK1.2.2付属の「Java2Demo.jar」というファイルを利用します。このファイルは「C:\jdk1.2.2\demo\jfc\Java2D」にあります。(JDK1.2.2を「c:\」直下にインストールした場合)

 自分以外のJarファイル内にあるクラスを利用するには、4通りの代表的な方法があります。


1.JarファイルにCLASSPATHを通してもらう

 MS-DOS時代で言うところの「インストールしたらPATHを通してください」という方法です。あまりスマートではありませんが、どのクラスからも確実に、無条件で、利用可能になります。


2.インストール型機能拡張を使う

 Java2の新機能である「インストール型機能拡張」を使います。<java.home>/lib/ext/というディレクトリ内にJarファイルを置くだけで、自動的にCLASSPATHが通るというものです。ClassLoaderの関係図はプラグイン作成講座第1回を参照してください。

 一太郎Arkでは、ヘルププラグインの利用するJavaHelpがこの方法を使っています。


3.バンドル型機能拡張を使う

 Java2の新機能である「バンドル型機能拡張」を使います。プラグインのJarファイルに入れるmanifest.mfに、利用したいJarファイル名を書くと、このプラグインからだけ利用可能になるというものです。
 プラグインサンプル3はこの方法を利用しています。実行時には、ライブラリ的に使うJava2Demo.jarを、PluginSample3.jarと同じくプラグインディレクトリに入れます。コンパイル時には、compile.batを参考に、ark10.jarだけでなくJava2Demo.jarにもパスを通してください。

Manifest-Version: 1.0
Class-Path: Java2Demo.jar


図9:manifest.mf(抜粋)

4.クラスローダとリフレクションを使う

 もっとも低レベル(?)で、しかし高度かつ複雑で、けれども自由度がもっとも高い手法です。ライブラリとして使うJarファイルを担当するClassLoaderを作り、目的とするクラスを生成して、リフレクションによりmethodを得て呼び出します。相手のJarファイルが存在しなかったときなど、連携時のエラーに対して柔軟に対応できることが特徴の1つです。

 一太郎Arkでは、Zip圧縮ファイルサポートプラグインが、一太郎形式のファイルを読むとき、一太郎8〜10形式コンバータプラグインに対して行っています。



Class clazz = classLoader.loadClass("jp.co.justsystem.io.dom.JTDParser");
Class[] paramTypes = new Class[] {java.io.InputStream.class,jp.co.justsystem.ark.io.FileContext.class,java.util.Hashtable.class };
Method method = clazz.getMethod("parse", paramTypes);
Object obj = clazz.newInstance();
Object[] params = new Object[3];
params[0] = zin;
params[1] = context;
params[2] = fileTypeOptions;
Object retval = method.invoke(obj, params);
if (retval instanceof IOResult) {
  result = (IOResult)retval;
}


図10:Zip圧縮一太郎形式読み込み処理の一部(参考)


○ソース解説

 今回はソースをざっと眺めるだけにします。だいぶ行数が増えてきました。(プラグインサンプル3のソース)

 107〜120行目では、「プラグインオプション」を作成しています。プラグインオプションというのはプラグインの動作を調節するための設定値で、プラグインのプロパティダイアログ(図8)からユーザが変更できます。

 ヘルパークラスとして、値を自由に編集できる「PluginOptionEditor」と、あらかじめ決められたアイテムの中からどれかを選択する「PluginOptionChooser」が用意されています。PluginOptionChooserの各アイテムは、実際の値とは別に国際化された表示用メッセージを持たなければならないため、生成時にはリソースキーを渡します。

 140〜153行目では、そのプラグインオプションの初期値を「プロパティマネージャ」からロードしています。173〜177行目はセーブです。

 プロパティマネージャというのは、Windowsでいうところの「レジストリ」の役割を果たすもので、一太郎Arkの各種実行時情報を保持し、起動・終了時にark.propertiesから読み込み・保存を行います。したがって、このプラグインオプションは、起動時にark.propertiesから読み込まれ、終了時にark.propertiesへ保存されることになります。

 199〜214行目はオプションの取得・設定用methodの実装です。getPluginOptions()は、プラグインのプロパティダイアログが開くときに呼ばれます。setPluginOptions(…)は、このダイアログがOKボタンで閉じられたときに呼ばれます。もしこのダイアログがキャンセルボタンで閉じられたなら、setPluginOptions(…)は呼ばれません。

 実は、サンプルプラグイン3の、この部分の実装には問題があります。PluginOptionEditorやPluginOptionChooserの保持する内部値は、ダイアログ上でユーザが値を変更したとき、同時に変更されてしまいます。そのため、たとえキャンセルボタンでダイアログを閉じたとしても、変更した値が有効になってしまうのです。

 これを防ぐには、プラグイン内で設定値を保持するためにPluginOptionEditorとPluginOptionChooserを使わないことです。getPluginOptionsのタイミングでプロパティマネージャの値からヘルパークラスを作り、setPluginOptionsのタイミングでヘルパークラスからプロパティマネージャに書き込めばいいということになります。

 131〜139行目、164〜172行目、263〜271行目、296〜305行目は、メモリ使用量モニタの停止とモードレスダイアログの後始末です。同じようなことを4カ所に分散して書いてしまっています。(ここももっときれいに書き直せるでしょう)

 なぜ4カ所もあるのかというと、このダイアログが閉じたときだけでなく、ダイアログが所属するフレームウインドウが閉じられたり、プラグインが停止させられたりといったタイミングでも、メモリ使用量モニタを停止しなければならないからです。

 245〜309行目は、ミソとなるアクション処理です。

 256行目で、アクションイベントからターゲットとなるフレームを取得します。イベントからフレームを取るのは、複数のフレームが存在したとき、選ばれたメニューの所属するフレームを特定するためです。

 このイベントですが、ArkActionEventという、拡張されたActionEventを使っています。詳しくは次回に説明したいと思います。

 258行目で、ターゲットフレームからリソースを取得します。一太郎ArkはフレームごとにUIの言語(ロケール)を指定できるため、ターゲットフレームから現在のロケールに対応するリソースを取得する必要があります。

 260行目で、フレームをもとにダイアログ作成します。このプラグインサンプルではモードレスダイアログを作成しています。引数はダイアログのタイトルです。戻り値は「jp.co.justsystem.jd.JDADialog」というクラスですが、これは「JDialog」と「JInternalFrame」を統合する目的で作られたクラスです。現在の一太郎Arkでは、基本的に「JDialog」と同じように扱うことができます。詳しい説明は次の機会に譲ります。

 278行目で、なにやらフォントを設定していますが、これは国際化関連の処理です。取得するリソースが親フレームの言語(ロケール)に対応したものなので、それをきっちり表示するためにはその言語(ロケール)のUI用フォントとして設定されているフォントファイルを使わなければなりません。315〜326行目がフォント設定の処理本体です。



 

 最後の方はかなり駆け足で、中途半端になってしまいました。

 プラグイン作成講座第4回となる次回は、プラグインサンプル3をもとに、ダイアログの表示、設定変更と保存について詳しく解説します。

 さらに、ファイル入出力を行うプラグインの作成方法について解説する予定です。



○JavaDoc形式のドキュメント


○ソースファイル