

みなさんこんにちは、プラグイン担当のPです。寒さが和らぎ、花粉のとびかう季節がやってきました。私は初春の花粉をいくら浴びても平気なのですが、どういうわけか6月下旬がダメなんです。私の花「粉」言葉はブタクサ?紫陽花?なんにせよ目を使う職業には憂鬱な季節です。
さて今回は、まずプラグインサンプル3を使って、プラグインオプションの使い方と、ダイアログの表示方法を詳しく説明します。次に、新登場のプラグインサンプル4を使って、ファイル入出力プラグインの作り方について説明します。
今回も盛りだくさんの内容になりました。細部まで説明できていない部分があるかもしれませんので、読みながらわからない内容に出会ったらメールでお気軽にご質問くださいませ。残念ながら一対一の回答は保証できませんが(と書いておかなければならないのがキビシイところですけど)、どこかのタイミングで回答したいと考えています。

前回ざっと説明したプラグインサンプル3について、より詳しく見ていくことにします。プラグインサンプル3のミソである、プラグインオプションの使い方と、ダイアログの表示方法を説明します。
○プラグインオプション
プラグインは多くの場合、自分自身の動作を制御するための「設定値」を持つことがあります。例えば仮にメール送信プラグインを考えてみると、メールサーバのアドレスやポート番号を設定値として保持するでしょう。
プラグインがこのような設定値を保持しておいたり、ユーザが変更できるようにするためのしくみとして、一太郎Arkには「プロパティマネージャ」と「プラグインオプション」という2つの機能が用意されています。
・プロパティマネージャについて
プロパティマネージャは、一太郎Arkのさまざまな設定値を保持しておくためのしくみで、一太郎Arkに1つだけ存在しています。その内容は、一太郎Arkの終了時に「ark.properties」というファイルとして保存され、起動時に読み込まれます。したがって、プラグインの設定値をプロパティマネージャに記入しておけば、次回起動時にも設定値がそのまま残っています。また、アクセス制限などはかかっていませんので、プロパティマネージャを仲介として他のプラグインから設定値を参照することが可能です。
プラグインからは ArkPluginContext#getPropertyManager() によって interface PropertyManager を参照することができます。プラグインサンプル3では、141行目でプロパティマネージャを取得し、142行目で文字列型のプロパティを取得しています。
各プロパティを特定するためのキー文字列は、名前の衝突を防ぐため、先頭にプラグインIDをつけてください。
・プラグインオプションについて
プラグインオプションとは、ちょっと変なネーミングですが、「プラグインのプロパティ」のダイアログ内に設定値を表示し、ユーザに変更させるためのしくみです(図1)。
図1:「プラグインのプロパティ」ダイアログ
プラグインのプロパティダイアログが表示される直前に、ArkPlugin#getPluginOption()が呼ばれます。このとき、ダイアログ内に出すためのルールを定義した interface PluginOption の配列を返すように実装します。ダイアログがOKボタンで閉じられると、ArkPlugin#setPluginOption()が呼ばれます。キャンセルで閉じられたときは呼ばれません。
実際には、このインターフェイスを実装したクラスが2種類(選択型・編集型)用意されていますので、それを使うのが楽です。
選択型オプションは、あらかじめ用意されている選択肢から設定値を選ぶものです(図2)。
図2:選択型プラグインオプション(日本語)
選択肢として表示するメッセージと、プログラム中で使う実際の値は別々のものです。表示用メッセージについては国際化する必要があります(図3)。
図3:選択型プラグインオプション(英語)
プラグインサンプル3では、111行目で選択型プラグインオプション(PluginOptionChooser)を生成しています。コンストラクタが要求する3つの引数の、1番目はこのオプション項目の名前(この場合は「曜日指定」)を表示するためのリソースキー、2番目は初期状態で何番目の選択肢が選ばれているかの値、3番目は選択肢(PluginOptionChooser.Itemの配列)です。
選択肢のメッセージは国際化する必要があるので、PluginOptionChooser.Itemのコンストラクタでは表示用のリソースキーとプログラム中で使う値を両方渡します。プラグインサンプル3の112〜118行目です。
編集型オプションは、ユーザが設定値を編集するものです(図4)。
図4:編集型プラグインオプション
編集領域のフォントは、UIの言語として設定されているものが使われます。UIが日本語/日本(ja-JP)であれば日本語/日本(ja-JP)のUIフォント、UIが英語/アメリカ(en-US)であれば英語/アメリカ(en-US)のUIフォントが使われます。
プラグインサンプル3では、109〜110行目で編集型プラグインオプション(PluginOptionEditor)を生成しています。コンストラクタが要求する2つの引数の、1番目はこのオプション項目の名前を表示するためのリソースキー、2番目は初期状態での内容です。
プラグインが起動したとき、前回の設定値をプロパティマネージャから取得して、選ばれているべき選択肢を指示する必要があります。プラグインサンプル3では140〜153行目です。逆に、終了時にプロパティマネージャへ保存する処理は173〜177行目です。
ところで、プラグインサンプルのオプションまわりの実装にはバグがあります。このままでは、「プラグインのプロパティ」ダイアログをキャンセルボタンで閉じたときでも、ユーザの変更内容が設定されてしまいます。このバグを修正するには、PluginOptionをArkPlugin#init()で作るのではなく、ArkPlugin#getPluginOptions()で作るようにします。そして、ArkPlugin#setPluginOptions()でプロパティマネージャに値を保存します。プラグインの処理中に設定値が必要になったら、プロパティマネージャから取得するようにします。
なお、プラグインの設定値はプラグインのプロパティダイアログでしか変更できないというわけではなく、もちろん自前のダイアログを開いてユーザに変更させるよう実装することも可能です。たとえば印刷プラグインは、独自の設定ダイアログ(ページスタイル設定)を開きます(図5)。
図5:印刷プラグインが開く設定ダイアログ
余談ですが、一太郎Arkの印刷に関する設定はすべてCSS規格に準拠しています。「ページスタイル設定」ダイアログで色々と設定し、「文書に反映」ボタンを押してから文書を保存し、出力されるソースを眺めてみてください。
○ダイアログを出す
プラグインからダイアログを出すにはどうしたらいいでしょうか?一番手っ取り早いのは、Swingのしくみをそのまま使ってJDialogを表示する方法ですが、ここでは一太郎Arkでの「推奨」ダイアログ表示方法について説明します。
・JDADialog
一太郎Arkは「JDesktop」というフレームワークの上で動いています。JDesktopというのは、その名のごとくJavaで書かれたデスクトップ環境です。1998年12月のプロトタイプデモでは実際に使われましたので、画面を見たことがある方もいらっしゃるでしょう。開発中のある時期には、一部の開発メンバーが「Just Window for Java」と呼んでいたこともありました。しかし諸般の事情により、JDesktopは一太郎Ark製品版では使われておらず、現在のところ幻のプロダクトになっています。
ところで、JDesktopフレームワークの特徴の1つに、JDesktop上のアプリケーション(JDA=JDesktop Application)は独立したJFrameとしてもJDesktop内のJInternalFrameとしても動作する、というのがありました。つまり、現在の一太郎Arkのような起動形態のほか、Javaで書かれたデスクトップ環境が1枚の(OS上での)ウインドウとして立ち上がり、すべてのアプリケーションはその中だけで動く、ということも可能です。
このような仕様を実現させるためには、デスクトップ型で動くときはJInternalFrame、独立ウインドウ型で動くときはJFrameやJDialogを使わなければなりません。そこで、JDesktopフレームワークでは「フレーム」と「ダイアログ」を抽象化して「JDAFrame」と「JDADialog」というインターフェイスを定義し、実際のクラスの生成はJDesktop側で行う設計にしました。
以上により、JDesktopフレームワーク上でアプリケーションがダイアログを出すときは、JDesktopシステム側にお願いしてダイアログを作ってもらう必要があるとわかりました。
・ArkFrameWrapperとArkActionEvent
一太郎Arkではどうするかというと、ダイアログを出すときは、一太郎Arkのアプリケーションフレームを表すクラスである「ArkFrameWrapper」にお願いすることになります。ArkFrameWrapperの内部でJDesktop側とやりとりして、自分自身の表すフレームの子供としてのダイアログを生成してくれる手はずになっています。
では、ArkFrameWrapperはどのように取得したらいいのでしょう?これは、どのアプリケーションフレームにダイアログを出すのか?という問題と関連しています。
一太郎ArkはSDI設計ですから、同時に複数の一太郎Arkアプリケーションフレームが開いていることもあります。このときプラグインサンプル3のActionが呼ばれたら、どのフレームの子供としてダイアログを開いたらよいのでしょう。
- アクティブなフレーム。言い換えると、なにか文字キーを押したとき本文中に文字が入力されていくフレーム。
- イベントが起こったフレーム。言い換えると、ユーザがメニューからActionを選んだのなら、そのメニューが所属するフレーム。
一見どちらでも動作に違いはないように見えますが、将来的にはマクロ機能によってActionを非同期に実行する事も考えれられます。このようなとき、1の方法では問題が発生することがわかります。そこで2の方法をとり、イベントが起こったフレームを特定し、そのフレームの子供としてダイアログを表示することにします。
イベントが発生したフレームを知るために、ArkではActionEventを拡張し、ArkActionEventクラスを定義しています。ArkActionEventのstatic methodである getTargetFrame(ev) を利用すると、このイベントが発生したフレームのArkFrameWrapperが手に入ります。
プラグインサンプル3では、256〜260行目に相当します。256行目で、ArkActionEventに対応するArkFrameWrapperを取得します。260行目で、このArkFrameWrapperに対応するJDADialogを生成してもらっています。モードレスダイアログですから、親フレームが存在するあいだ、常に親フレームより前面に表示され、操作できるというものです。
一旦JDADialogを手に入れたら、あとは普通のJDialogとほぼ同様に扱うことができます。プラグインサンプル3では、293〜308行目で作成したメモリ使用量モニタパネルを、279行目でダイアログに貼りつけています。そして、288行目でダイアログを出現させています。
・モードレスダイアログを閉じる
プラグインサンプル3のモードレスダイアログに貼りつけられているメモリ使用量モニタは、明示的に使用開始と使用停止を指示してやらなければなりません。使用開始処理はダイアログが出現するタイミングを取れば問題ありません。使用停止処理は、ダイアログが閉じられるタイミングとプラグインが使用停止されるタイミングを取ればよいのですが、こちらは少々わかりにくくなっています。
プラグインの使用開始・使用停止のタイミングでの処理は、133行目と166行目です。これは、ダイアログが出たまま「プラグインの設定」ダイアログが操作された場合の処理ですので、メモリ使用量モニタの停止だけでなく、ダイアログの破棄も行います。
モードレスダイアログが、ウインドウタイトルバー上の「×」ボタンで閉じられたときは、262行目からのmethodに処理が移ってきます。ここでも、モードレスダイアログの破棄処理を行います。
最後に296行目からですが、これはモードレスダイアログを出したまま親フレームを「×」で閉じたときのための処理です。本当はここを書かなくても動きそうなのですが、どういうわけか上の262行目からの処理とここと両方なければうまく動いてくれませんでした。ちょっと書き方が悪いのかもしれません。
図6:プラグインサンプル3
さて、以上でプラグインサンプル3の説明は終わりです。このプラグインのソースコードをテンプレートにすれば、一太郎Ark用のちょっとしたゲームプラグインなんてのもできそうですね。

○このプラグインは?
プラグイン作成講座の最後は、新登場のプラグインサンプル4です。
このサンプルは、プレーンテキストファイルを読み込む機能を持ったプラグインです。一太郎Ark本体にも同様の機能がありますが、このサンプルは説明のために機能を大幅に簡略化し、構造を改変しています。保存はできませんが、テキスト1行1行をPREタグの中に入れるため、タブが有効になるという利点を持っています。
○ソース概説
まずは、ソースをざっと眺めてみましょう。
26行目のクラス宣言で「FileTypePlugin」というのが出てきました。これは、前回までに説明したUIPluginと同類のものです。UIPluginはActionやMenuItemを一太郎Arkに渡すためのインターフェイスでしたが、FileTypePluginはファイルダイアログに出てくる「ファイル形式」を渡すためのインターフェイスです。
72行目のstart()のタイミングでFileTypePluginを登録し、81行目のstop()のタイミングで登録を抹消しています。実際の処理は112行目にあり、init()のタイミングで作ってある、内部クラスPlainTextSupportを返しています。
そのPlainTextSupportは120〜249行目で定義されています。このクラスはインターフェイス「FileTypeSupport」を実装したクラスです。Actionインターフェイスを実装したクラスがUI処理のキモとなっていたように、FileTypeSupportインターフェイスを実装したクラスがファイル入出力のキモとなります。詳しくは後で説明します。
このPlainTextSupportクラスはほとんどが空のmethodですが、156〜191行目にあるload()というmethodが読み込み処理の本体です。
前半の159〜172行目は、読み込み対象のストリームからリーダーを作り、1行1行読みながらHTML形式に直していく部分です。つまり、プレーンテキストがHTML形式のテキストになったわけです。結果はStringBufferクラスのオブジェクトに格納されます。
後半の173〜184行目は、HTMLソースが格納されたStringBufferを読み上げるリーダーを作り、これを使ってDOMツリーを構築します。その後DOMTreeNormalizerを通して、一太郎Arkの内部形式として整合性の取れた形になります。つまり、HTML形式のテキストがDOMツリーになったわけです。
あとの部分は例外処理などを行っています。ここでは説明を省きます。
○一太郎Arkの入出力
まずは、一太郎Arkの入出力の仕組みを説明します。
さきほど「ActionがUI処理のキモであるように、FileTypeSupportがファイル入出力処理のキモである」と説明しました。もう少し正確にいうと、FileTypeSupportは「ファイル形式処理」のキモです。
一太郎Arkの入出力まわりは、「StorageSupport」と「FileSupport」の二層に分かれています。
StorageSupportは、ローカルのファイルシステムやURLによるhttpアクセスなどを抽象化したインターフェイスです。開発中のFTPサポートプラグインもここに属します。ファイル名を渡されたとき、対応するInputstreamやOutputStreamを返すことができるという性質を持ちます。したがって、HTMLファイル中に相対参照の画像ファイルが埋め込まれていたときなどは、ここの層に問い合わせることによって実体を得ることになります。
FileTypeSupportは、HTML形式や一太郎形式のファイルなどを抽象化したものです。InputStreamが渡されたとき、中身を解析して一太郎Arkの内部形式であるDOMを作ることができたり、DOMの内容をOutputStreamに出力できるという性質を持ちます。直接DOMを構築してもいいのですが、場合によってはプラグインサンプル4のように、HTMLのソース形式で作っておいて既存のHTML→DOMコンバータへ処理をつなげることもできます。
少々余談になりますが、分類的に微妙なのが「Zip圧縮ファイルサポート」プラグインです。このプラグインは一見するとFileTypeSupportなのですが、Zip圧縮されたHTMLの中で画像の相対参照があった場合、それはStorageSupportではなく、Zip圧縮のFileTypeSupport内で解決すべきものであるため、ちょっと複雑です。
一太郎Arkではこの問題を解決するために、ResourceResolverなるしくみを使っています。これは相対参照の外部実体を解決するための機能をまとめたクラスです。通常はStorageSupportが用意しますが、Zip圧縮ファイルサポートのようにFileTypeSupportのレイヤーが提供する場合もあります。
マイクロソフト社のOLE Compound Fileも似ていますが、こちらは相対参照の恐れがないため、一太郎形式やWORD形式を読み込むプラグインから使うための単なるライブラリという設計になっています。
このような役割分担になっているStorageSupportとFileTypeSupportはどちらも、UI処理を行うためのActionと同様に、名前をつけて一カ所で管理されています。ファイルダイアログには、そのとき一太郎Arkに登録されているすべてのFileTypeSupportが表示されるしくみです。
プラグインからそこにFileTypeSupportを追加するためのインターフェイスがFileTypePluginです。一太郎Ark本体側は必要なときにこのインターフェイスを呼び、プラグイン側が用意したFileTypeSupportを吸い上げてくれるしくみになっています。
○FileTypeSupport
FileTypeSupportインターフェイスには、13個のmethodがあります。このうち、読み込みに重要なものだけ説明します。
getID()は、このFileTypeSupportを識別するためのIDを返します。もし、一太郎Arkに内蔵されているFileTypeSupportと同じ値を返したら、それを置き換えることになります。
getName()は、とりあえず英語名を返してください。言語情報がわたってくるかのようなmethodですが、国際化はリソースの方でおこなうため、この言語情報は使われていません。
読み込み処理が可能なFileTypeSupportは、canLoad()に対してtrueを返します。これによって、読み込み時のファイルダイアログのファイル形式一覧に出てくるようになります。
実際の読み込み処理はload()で行います。この引数のFileContextは、一太郎Ark内で「1つのファイル」を表すオブジェクトで、DOMツリーやファイル名、ResourceResolverなどが入っています。
load()の中で発生した例外やエラーは、ArkIOResultのオブジェクトで返します。これによって、読み込みエラーのダイアログが表示されます。
getDefaultOptionsForLoad()は、ファイルダイアログに出てくる、FileTypeSupport用の設定値を返すものです。(図7)
図7:一太郎7テキスト抽出の設定値
ここで使われているFileTypeOptionインターフェイスは、PluginOptionインターフェイスとほとんど同じしくみになっています。
以上でプラグインサンプル4の説明は終わりです。FileTypeSupport関連は、特に保存時の処理などは非常に複雑であるため、これだけの説明ではまったく不十分なのですが、残念ながら今回説明することができません。いつか説明する機会があれば、より詳しく解説したいと思います。

4回にわたって連載してきたプラグイン作成講座も、これにて終了です。かなり駆け足で流してきたのと、担当者Pの力不足により、わかりにくいところが多々ありましたことをお詫び申し上げます。
次回、約1ヶ月後の更新時に、番外編その2として補講をお届けしたいと考えています。
○プラグインサンプル3
PluginSample3.java 本体プログラムソース
○プラグインサンプル4
PluginSample4.java 本体プログラムソース
PluginSample4.zip 付属ファイルなどすべて
○JavaDoc形式のドキュメント
PropertyManager
ResourceManager
PluginOption
PluginOptionChooser
PluginOptionChooser.Item
PluginOptionEditor
FileTypePlugin
FileTypeSupport