レベル: 初級 Terry Chan, Software engineer, IBM Canada Ltd.
2002年 10月 01日 独立したSwingベース・エディターをプラグインとしてEclipse Platformに統合する方法を学びましょう。簡単なテクニックを利用するだけで、SwingツールやEclipse Platform、および各種のSWTウィジェットの間でリソースを共用できます。そしてこれらのリソースは、相互に認識することによって通信することが可能です。Eclipseベース開発ツールを最小限の再コーディングで市場に出したいと考えるツール・ベンダーにとっても、この記事は役に立つでしょう。
はじめに
Eclipse Platformは、ツール開発のための堅固なサービスとAPIのセットを提供します。さまざまなベンダーのツール間の統合を円滑にし、さまざまな種類の開発作業にシームレスな環境を提供します。
Eclipse Platformのソフトウェア・コンポーネントの1つは、SWTです。SWTは、このPlatformの中心的なコンポーネント・セットではありませんが、製品やプラグインの開発者にJavaベースのGUIウィジェット・セットを提供するために不可欠なものです。SWTは、オペレーティング・システムから独立した移植性の高いものですが、その基礎を成すJNIインターフェースは、ネイティブPlatformのルック・アンド・フィールとパフォーマンスを提供します。
一般にSWTは、Platformのさまざまなフレームワークで適切に動作し、見た目にも魅力的なプラグインの作成を望む開発者とツール・ベンダーに対して優れたソリューションを提供します。ただし、SWTには、JavaのSwingGUIウィジェットとの相互運用性のレベルがかなり低いという問題があります。たとえば、SwingとSWTは、完全に異なるイベント処理メカニズムを採用しています。この違いのため、しばしばSwingとSWTのハイブリッドのGUIが利用出来なくなってしまいます。
ある程度の互換性を与えるため、SwingとSWT間のインターフェースを提供しようとするいくつかの試みがなされています。例えば、org.eclipse.swt.internal.swt.win32.SWT_AWTユーティリティ・クラスは、開発者がSwingのウィジェットをSWTに組み込めるようにするものです。しかし、こうした方法はまだ実験段階にあり、公式にサポートされていません。そのためパッケージ名には、「internal」という名前が追加されています。
この相互運用性の低さは、Eclipseプロジェクトとツール・ベンダーの両方への不運な障害となります。現在、数多くのソフトウェア開発ツールやテスト・ツールが、Swingで作成されたユーザー・インターフェースを提供していますが、高度なSwingGUIを伴う既存のツールをSWTに移植するには、ベンダーにはかなりの時間と投資が必要です。EclipsePlatformに固有のさまざまな利点にもかかわらず、SwingとSWT間の相互運用性が低いため、開発作業への関心が失われがちです。
この記事では、以下の方法について説明します。
- Swingベースのエディターを起動し、Eclipse Platform Workbenchで「ThirdParty.java」という名前を用いて任意のJavaファイルを編集する
- Swingエディターで行われたソース・コードの変更をWorkbenchに戻す
- Preference Pageフレームワークを使用して、Swingエディターの属性を制御する
- Swingエディターを「Workbench-aware」にする
- SwingエディターからSWTウィジェットを起動する
この記事では、サポートされていないAPIを使用せずに上記の方法を実行する、いくつかのシンプルなテクニックについて紹介します。内部クラスを参照することはなく、すべての一般的なプラグインのルールに従います。これらのテクニックを最大限に活用するためには、プラグインの作成とプラグイン開発環境の使用に関する基礎知識を有し、Swingベース・エディターのソース・コードにアクセスできる必要があります。
仮想上のSwingエディター: Ed
現実のツール統合シナリオをシミュレートするために、「Ed」という仮想上のSwingベースのエディターを使用することにします。以下は、Edの特性の一部です。
- Swingベースのエディターである。
- JFrameを拡張する。
- ThirdParty.javaという特定の名前のJavaファイルでのみ動作する。
- プライベート・フィールドとしてJEditorPane とJButton を持っている。JEditorPaneは、ThirdParty.javaのソースを表示し、JButton は、ソース・コードを保存します。
-
main() メソッドを持つ独立したJavaアプリケーションである。
- コマンド・プロンプトから実行されるよう設計されている。Edは、Eclipse Platform Workbenchを認識しません。
基本概念
SwingとSWTの相互運用性の制限により、これらの間で直接通信を行うこと (イベント処理など) は困難です。サポートされていないAPIを使用したり、プラグイン開発のルールを曲げたりせずに、それを行う方法の1つは、それらを互いに組み込むのではなく、それぞれに個別のフレームを持たせることです。図1 に示すように、プラグイン・クラス、つまりSingletonユーティリティー・クラスがそれらの間の通信を処理します。たとえば、EdのJButton はSWT Shellを表示して、編集されたThirdParty.javaのWorkbench固有のいくつかの属性 (Project Referencesなど) を表示させることができます。
図1. Singletonユーティリティー・クラス
エディターの統合
統合の主な目的は、WorkbenchにあるThirdParty.javaファイル用のデフォルトのエディターとしてEdを使用するプラグインを開発することです。
プラグイン・プロジェクトの作成
Workbenchで、新しいプラグイン・プロジェクト「org.eclipse.jumpstart.editorintegration」を作成し、Create plug-in projectウィザードで「Create plug-in using a template wizard」オプションを選択します。Next をクリックします。「Add default instance access」オプションにチェックを入れ、Finish をクリックします。WorkbenchがPlug-in Development Perspectiveに切り替わります。空白のプラグイン・マニフェスト・ファイルと、AbstractUIPlugin を拡張するEditorintegrationPlugin プラグイン・クラスが自動的に作成されます。このプラグイン・クラスのプライベートの静的インスタンスとgetterメソッドも生成されます。
プラグイン・マニフェスト・ファイル・エディターが開きます。開かない場合は、plugin.xmlをダブルクリックして起動します。
このプラグインには、以下のライブラリーが必要です。これらをプラグイン・プロジェクトのJava Build Pathに追加します。
- ECLIPSE_HOME/plugins/org.eclipse.core.resources/resources.jar
- ECLIPSE_HOME/plugins/org.eclipse.jdt.core/jdtcore.jar
- ECLIPSE_HOME/plugins/org.eclipse.jdt.ui/jdt.jar
- ECLIPSE_HOME/plugins/org.eclipse.swt/swt.jar
- ECLIPSE_HOME/plugins/org.eclipse.ui/workbench.jar
プラグイン・マニフェスト・ファイル
このプラグインは、ThirdParty.javaという名前のJavaファイルでのみ動作するので、それ用のエディターを指定する必要があります。プラグイン・マニフェスト・ファイル・エディターで、Extensionsタブに切り替え、拡張ポイント「Internal and External Editors」を追加します。デフォルトを「true」に、名前を「Ed - Swing Editor」に、ファイル名を「ThirdParty.java」に、ランチャーを「org.eclipse.jumpstart.editorintegration.EdLauncher」に設定します。追加された拡張ポイントのソースは、リスト1のようになるはずです。
リスト1. 拡張ポイントの追加
<extension point="org.eclipse.ui.editors">
<editor
name="Ed - Swing Editor"
default="true"
icon="icons/thirdparty.gif"
filenames="ThirdParty.java"
launcher="org.eclipse.jumpstart.editorintegration.EdLauncher"
id="org.eclipse.jumpstart.editorintegration.ed">
</editor>
</extension>
|
これで、Edは、図2 に示すように、すべてのThirdParty.javaファイルに対するデフォルトのエディターとなりました。
図2. すべてのThirdParty.javaファイルに対するデフォルトのエディターであるEd
注:icons/thirdparty.gifファイルを必ず入れてください。このファイルが、「Open With」メニューで、すべてのThirdParty.javaファイルに対するデフォルトのエディターとして表示されます。
Edソース・コードの統合
Edのソース・コードをプラグイン・プロジェクトにインポートします。どのようにEdを起動するのかは、選択することが出来ます。プラグイン・クラスは、パブリックなgetterメソッドを用意して、プライベート・フィールドとしてEdを持つことができます。
リスト2. プライベート・フィールドとしてのEd
private Ed ed = null;
public Ed getEd()
{
if (ed == null)
{
ed = new Ed ();
}
return ed;
}
|
また、プラグイン・クラスは、起動されたThirdParty.javaファイルごとに、個別のEdのインスタンスを返すようにすることもできます。いずれかのアプローチを、プラグインによって維持され、提供されるSingletonユーティリティー・クラスに実装できます。
エディター・ランチャー
プラグインは、拡張ポイントorg.eclipse.ui.editors を使用するため、マニフェスト・ファイルで指定されたエディター・ランチャー・クラスをEclipse Platformに提供しなければなりません。
IEditorLauncherインターフェースを実装するためのorg.eclipse.jumpstart.editorintegration.EdLauncher クラスを作成します (このインターフェースがない場合は、workbench.jarファイルがProject Pathに含まれるようにします。プラグイン・プロジェクトの作成を参照)。ウィザードで「Inherited abstract methods」オプションに必ずチェックを入れてください。
ThirdParty.javaファイルをダブルクリックするたびに、Eclipse PlatformがEdLauncher.open(IFile) を実行し、このファイル・タイプのデフォルトのエディターを起動します。Platformは、クリックされたアーティファクトをIFileとしてメソッドに渡します。この場合、IFileはJavaソース・ファイルなので、ICompilationUnitとしてキャストできます。
EdはJDTオブジェクトと連携するようには設計されていないため、表示をするには、ICompilationUnitからソース・コンテンツを抜き出して、それをEdに渡さなければなりません。
EditorintegrationPlugin.getDefault().getEd().getEditorPane().setText
(ICompilationUnit.getSource());
EditorintegrationPlugin.getDefault().getEd().show(); |
show() メソッドの実行時に、Edは、Workbenchのメイン・ウィンドウの外にJFrameとして表示されます (図3 を参照)。編集されたThirdParty.javaのプロジェクト名とパッケージ名は、プラグインによって記録されます。この情報は、Edで行われた変更を保存する際に不可欠となります。
図3. Workbenchの外に表示されるSwingエディター
ラウンド・トリッピング: ソースの変更をWorkbenchに戻す
従来のエディターは、バイナリー・リポジトリーのフラット・ファイルにソース・コードを保存するか、ソース・コード制御システムへ保存します。エディターとしてのEdには、表示するソース・コードへの変更を保存するいくつかの方法が必要です。
Edには、Swingエディター: Ed で説明した「Save」ボタン (JButton) があります。これを押すと、actionPerformed() メソッドが呼び出され、Saveボタンがイベントを起動します。イベント・リスナーを実装するオブジェクトは、イベントを受け取り、ソースの保存操作を実行します。
Singletonユーティリティー・クラス (エディター・ランチャーを参照) は、イベント・リスナーを実装するオブジェクトとして使用できます。Saveボタンからイベント・オブジェクトを受け取ると、このユーティリティー・クラスがEdからソースを抽出して、対応するWorkbenchオブジェクトにそれを送りこみます。ファイル・システムに保存する実際の作業は、EclipsePlatformが行います。
複数のファイルがWorkbenchで同じ名前を持つ可能性があります。そのような場合は、ThirdParty.javaのプロジェクト名とパッケージ名が役に立ちます。この情報は、プラグインによって保存されますが、実装アプローチそのものはみなさんに任されています。エディターがこの情報を保存すると想定すると、ユーティリティー・クラスの以下のようなコードを使用できます。
リスト3. ファイル名の管理
public void saveButtonPressed() {
try {
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IProject myProj = root.getProject(getEd().getProjectname());
IFolder myFolder = myProj.getFolder(getEd().getPackageName());
IJavaElement myPackageFragment = JavaCore.create(myFolder);
if (myPackageFragment != null) {
IPackageFragment packageFrag = (IPackageFragment)myPackageFragment;
String sourceFromEd = getEd().getJEditorPane1().getText();
ICompilationUnit icu = packageFrag.getCompilationUnit("ThirdParty.java");
icu.getBuffer().setContents(sourceFromValidator);
icu.save(null, true);
}
else {
System.out.println("myPackageFragment is null.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
|
リバース・ラウンド・トリッピング
リスト3は、「順方向」のラウンド・トリッピングを管理しています。Eclipse PlatformのJDT JavaエディターによってThirdParty.javaで行われた変更をEdに戻すには、「逆方向」のラウンド・トリッピングも必要です。
ユーティリティー・クラスは、org.eclipse.jdt.core.IElementChangedListener インターフェースを実装でき、これを使用して、ICompilationUnitなどのIElementに対して行われた変更を追跡できます。ソース変更がWorkbenchのJavaファイルに導入されるときにelementChanged(ElementChangedEvent) メソッドが呼び出されます。
Edプラグインに関係のないIElement変更は、フィルターにかけて除外する必要があります。フィルターリングの1つの方法は、IElementChangedEvent 引数からIJavaElementDelta オブジェクトを抽出し、調べることです。たとえば、以下のステートメントは、Edプラグインのコンテキストから見て関係のないソース変更をフィルターにかけます。
リスト4. 無関係なソース変更のフィルターリング
IJavaElementDelta delta = event.getDelta();
if (delta != null) {
if(delta.getElement().getElementName().equalsIgnoreCase("ThirdParty.java")) {
//code to update Ed's editor panel.
}
}
|
Java以外のアーティファクトのエディターでは、Workbenchで行われた変更をIElementChangedListenerでキャプチャーすることはできません。Eclipse Platformは、Java以外のリソースに対して行われた変更を処理するorg.eclipse.core.resources.IResourceChangeListener インターフェースを提供します。
プリファレンス・ページ
簡単に使用できるさまざまな機能をユーザーに提供するために、ツールは、設定可能なオプションを提供すべきです。それらはスタートアップ・パラメータを通してか、エディターの基本的なインターフェースとは分離したGUIによって操作します。Eclipse Platform用のプラグインの場合は、これらのオプションが、PlatformのPreference Pageフレームワーク (Window -> Preferences) を介して設定出来るようにすることが強く勧められます。
例として、Platformのプリファレンス・ページを使用して、Edのカラーを設定可能なオプションとして制御してみましょう。
プラグイン・マニフェスト・ファイルでプリファレンス・ページ拡張ポイントを追加する
プリファレンス・ページは、Eclipse Platformで拡張ポイントとして定義されています。これを使用するには、プラグイン・マニフェスト・ファイル・エディターにそれを追加するか、または、以下のコードをplugin.xmlに追加します。
リスト5. plugin.xmlにプリファレンス・ページを追加する
<extension
id="org.eclipse.jumpstart.editorintegration.pref"
name="Ed Preference"
point="org.eclipse.ui.preferencePages">
<page
name="Swing Editor Preference Page"
class="org.eclipse.jumpstart.editorintegration.EdPreferencePage1"
id="Swing Editor Preference Page"
</page>
</extension>
|
プリファレンス・ページ・クラス
プリファレンス・ページは、org.eclipse.jface.preference.PreferencePage を拡張します。以下の例では、シンプルなプリファレンス・ページが、最大値255の3つのスライダー・バーで構成され、Edのjava.awt.Color オブジェクトのカラー (赤、緑、青) を示しています。
マニフェスト・ファイルで指定されたように、プラグイン・プロジェクトでorg.eclipse.jumpstart.editorintegration.EdPreferencePage1 クラスを作成します。これは、org.eclipse.jface.preference.PreferencePage を拡張し、org.eclipse.ui.IWorkbenchPreferencePage インターフェースを実装しなければなりません。
プリファレンス・ページには、JFace/SWTとSwingがどのように通信するかという、エディター・ランチャーと同様のコーディング上の課題があります。幸い、同じアプローチが適用できます。たとえば、performApply() メソッドは以下のようになります。
リスト6. performApply() メソッド
protected void performApply() {
int red = redSWTSlider.getSelection();
int green = greenSWTSlider.getSelection();
int blue = blueSWTSlider.getSelection();
java.awt.Color newColor = new java.awt.Color(red, green, blue);
EditorintegrationPlugin.getDefault().getEd().getContentPane().setBackground(
newColor);
}
|
このプラグインは、他のプラグインと同様に、PlatformのPreference Storeメカニズムを使用して設定値を保存する必要があります。performOk() メソッドは以下のようになります。
リスト7. performOk() メソッド
public boolean performOk() {
getPreferenceStore().setValue("redValue", redSWTSlider.getSelection();
getPreferenceStore().setValue("greenValue", greenSWTSlider.getSelection());
getPreferenceStore().setValue("blueValue", blueSWTSlider.getSelection());
return true;
}
|
図4 に、プリファレンス・ページからSwingエディターのカラーを制御する方法を示します。
図4. プリファレンス・ページからSwingエディターのカラーを制御する方法
Workbench認識
ほとんどのエディターは、本来、独立したJavaアプリケーションとして設計されたため、Workbenchの存在を認識しません。Platformの環境属性の一部を処理できない可能性があり、エディターとPlatformの統合関係のレベルが制限されます。ユーザーが、よりスムーズに整合性を保って開発できるように、開発者とプラグイン・ベンダーは、Workbenchを認識するように既存のSwingツールを改善することを真剣に検討すべきです。
たとえば、Edは、ファイル・システム・ベースのJavaファイルと直接連携できるようにコード化されました。そのため、PlatformのJava ProjectとProject Referenceの概念は、Edとは関係ありません。以下のセクションでは、編集されたThirdParty.javaの参照プロジェクトを表示するSWTダイアログ・ボックスを起動するためのJButton をEdに追加することにします。ユーザーからは、Swingウィジェットをクリックすると、Workbench固有情報を表示するSWTウィンドウが起動され、Swingエディター、SWT、およびWorkbenchが緊密に相互作用しているように見えます。
エディターの拡張
Edソース・コードにアクセスできると想定すると、追加のWorkbench認識機能用に特別のSwingウィジェットを追加できます。JButton をエディターのメイン・コンテンツ・ペインに追加し、SWTダイアログを起動します。JButton のテキストを「Referenced Project」に設定します。
Referenced Projectボタンのイベント処理メカニズムは、Saveボタンのそれと同様に機能します (ラウンド・トリッピング: ソースの変更をWorkbenchに戻すを参照)。プラグイン・ユーティリティー・クラスが、このボタンのイベントを監視し、Referenced Projectボタンによって起動されたイベント・オブジェクトを受け取ると、必要な操作を実行して、プロジェクト参照情報を検索し、SWTでそれを表示します。
プロジェクト参照情報の検索
SWTダイアログが表示される前に、プラグインは、Workbenchのどのプロジェクトが、編集されたThirdParty.javaを含むプロジェクトによって参照されているかを把握している必要があります。これはプラグイン・クラスのジョブであり、リスト8に示すようなメソッドを使用します。ここで、渡されている文字列(String) の引数はプロジェクト名です。
リスト8. プロジェクト参照情報の検索
private String[] getReferencedProjectArray(String arg) {
String[] projectNameArray = null;
try {
IProject[] referencedProjects = ResourcesPlugin.getWorkspace().getRoot().getProject(
arg).getReferencedProjects();
int referencedProjectsLength = referencedProjects.length;
if (referencedProjectsLength == 0) {
projectNameArray = new String[1];
projectNameArray[0] = "none";
}
else {
projectNameArray = new String[referencedProjectsLength];
for (int i=0; i < referencedProjectsLength; i++) {
projectNameArray[i] = referencedProjects[i].getName();
}
}
return projectNameArray;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
|
SWTダイアログ
Project Referenced SWTダイアログの正確な外観は、プラグインGUI設計者に任されています。以下の例では、(参照プロジェクトを表示する)List オブジェクトを持つシンプルなSWT Shellで十分でしょう。
リスト9. Listオブジェクトを持つSWT Shel
public class SWTProjectReferenceFrame implements Runnable {
private Shell shell;
private Display display;
Thread myThread;
public void run() {
open();
}
public void open() {
display = new Display();
shell = new Shell(display);
shell.setLayout(new org.eclipse.swt.layout.GridLayout());
shell.setText("Projects Referenced - SWT Frame");
shell.setSize(400, 400);
createListGroup();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
EditorintegrationPlugin.getDefault().getEd().repaint();
display.sleep();
}
}
myThread = null; // disposing the thread when the SWT window is disposed.
}
// Other methods appear here ...
}
|
createListGroup() メソッドがList オブジェクトを作成し、projectNameArrayを含めるようその内容を設定します (プロジェクト参照情報の検索を参照)。
リスト10. Listオブジェクトの作成
private void createListGroup() {
Group listGroup = new Group(shell, SWT.NULL);
listGroup.setLayout(new GridLayout());
listGroup.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL |
GridData.HORIZONTAL_ALIGN_FILL |
GridData.VERTICAL_ALIGN_FILL));
listGroup.setText("listGroup");
List list = new List(listGroup, SWT.V_SCROLL);
list.setItems(projectNameArray);
}
|
SWTダイアログの起動方法によっては、別のスレッドでSWTウィンドウを実行して (リスト10のmyThread オブジェクトが示すように)、Swingエディターの再描画の問題を回避する必要があります。
図5 に、Swing ボタンからSWTフレームを起動する方法を示します。
図5. SwingボタンからSWTフレームを起動する方法
結論
この記事で示されたテクニックは、SwingベースのツールをEclipse Platformに迅速に統合できる一時的なソリューションを提供します。ただし、可能であれば、既存のSwingウィジェットではなく、緊密に統合されたSWT/JFaceコンポーネントを使用するとよいでしょう。たとえば、エディターは、個別のプリファレンス・ダイアログ・フレームを使用して、ユーザーごとに設定を処理するのではなく、Eclipse PlatformのPreference Pageフレームワークを、プラグインを設定する中央エントリー・ポイントとして使用するとよいでしょう。
この記事の概念は、比較的シンプルで実装しやすいものですが、たとえそうであっても、Swingウィジェットをプラグインの恒久的な部品としないでください。Eclipseプロジェクトのすべてのサービスを利用するには、Eclipseプロジェクトによって提供されるさまざまなフレームワークをサポートするプラグインにおけるレガシーのSwingコード量を徐々に減らしていくとよいでしょう。
参考文献
著者について  | |  | Terry Chan氏は、オンタリオ州トロントのIBMに所属するソフトウェア・エンジニアです。氏は、OS/2コア・ダンプ分析を専門として、IBM Global Servicesのアナリストとして勤務した後、IBM Toronto Software Laboratoryに加わり、VisualAge for Javaのカスタマー・サービス・アナリストとして勤務しました。2001年には、WebSphere Studio Application Developer (WSAD) Jumpstartチームに加わりました。チームの主な目的は、WSADに基づいて製品を開発するISVの支援です。 |
記事の評価
|