Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

GEFでグラフィック編集プラグインを作る

GEFによるアプリケーション作成のための基礎知識

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2005/03/25 00:00

GEFの概略と、GEFを使用する方法を、簡単なグラフィック編集アプリケーションのの作成を通して説明します。

目次
サンプルGEFプラグイン
サンプルGEFプラグイン

はじめに

 GEF(Graphical Editing Framework)は、その名の通り、モデルを「グラフィカル」に「編集」するアプリケーション(またはプラグイン)を作成するためのフレームワークです。この記事では、単純なGEFアプリケーションの作成を通して、GEFの概略と、その使用法を説明します。

対象読者

 Eclipseプラグイン、およびDraw2Dを使用したアプリケーションを作成したことがある方。Draw2D、またはEclipseのプラグインについては、Eclipseのヘルプ、およびAPIドキュメントを参照してください。また、Draw2Dに関する詳細については、GEF SDKに付属するDraw2Dのプログラマーズ・ガイドを参照してください。

必要な環境

 サンプルは、Eclipse SDK 3.0.1、およびGEF SDK 3.0.1を使用して作成しました。それ以前のバージョンでは動作しません。

GEFアプリケーションの概要

 冒頭でも触れましたが、GEFはモデルをグラフィカルに編集するアプリケーションを作成するためのフレームワークです。GEFは、UMLダイアグラムのように、グラフィカルに表示・編集されることに適したモデルを持つアプリケーションを作成するために使用されます。

 GEFアプリケーションは、MVC(Model-View-Controller)アーキテクチャーに基づいて作成された、「EditPartViewer」と呼ばれるビューアーを中心に作成します。MVCアーキテクチャーは、GUIアプリケーションを「モデル」、「ビュー」、「コントローラ」の3つの要素に分割する考え方です。

 また、EditPartViewerにはビューとしてDraw2Dのフィギュアを使用する「グラフィカル・ビューアー」と、SWTのTreeItemを使用する「ツリー・ビューアー」の2種類が用意されています。この内、ツリー・ビューアーは、主にGEFアプリケーションにEclipseの「アウトライン」ビューを提供するために使用されるものです。

 MVCアーキテクチャーにおいて、モデルはアプリケーションで扱うデータです。また、ビューはモデルの情報をユーザーに表示するためのオブジェクト、つまり、モデルの外観です。コントローラは、モデルとビューを結びつけ、ユーザーからの入力により、モデルの変更を行う、といった役割を担います。

 GEFは、モデルの形態に依存しないビューとコントローラ、およびアプリケーションの動作に必要な、その他の部品で構成されています。つまり、GEFアプリケーションを作成するには、自分でモデルを持ち込む必要があるということになります。

 これに対して、ビューとコントローラに関しては、GEFに用意されている部品をモデルに合わせてカスタマイズして使用します。GEFアプリケーションにおけるモデル、ビュー、およびコントローラの概略は以下の通りです。

モデル

 モデルは、GEFアプリケーションで表示・編集されるデータそのものです。GEFアプリケーションにおいて、保持され続けるデータは、モデルだけです。ビュー、またはコントローラは、編集動作によっては、破棄、再作成されるので、一時的なデータ以外の情報を持たせることはできません。同様の理由で、モデルは、ビュー、およびコントローラについての情報を参照しないようにする必要があります。

 また、モデルは自身のプロパティが変更された場合に、それを通知する機能を含んでいる必要があります。前述の通り、GEFはモデルの形態に依存しないので、上記の条件を満たせば、モデルの定義は基本的に自由に行うことができますし、既存のモデルを持ち込む、といったこともできます。

ビュー

 ビューは、モデルのプロパティをユーザーに表示する役割を担います。GEFでは、グラフィカル・ビューアー内ではDraw2Dのフィギュア(あるいは、フィギュアを複数組み合わせたもの)がビューとして使用され、ツリー・ビューアー内では、SWTのTreeItemがビューとして使用されます。GEFアプリケーションでのビューは、モデルのプロパティを表示するためだけのものなので、モデルやコントローラについての情報は何も持たせないようにします。

コントローラ(EditPart)

 GEFでは、コントローラのことを「EditPart」と呼びます。EditPartは、モデルの情報を基にビューを作成します。また、モデルの情報に変更があった場合は、それをビューに反映させる役割も担います。

 MVCアーキテクチャーでのコントローラは、これ以外にもユーザーからの入力を処理して、モデルを変更する、といった役割も担いますが、GEFでは、モデルの編集に関する機能の多くは、EditPartから分離されています。

GEFでのMVCの概略
GEFでのMVCの概略

 上図のように、EditPartは、モデルとビューのマッピングを行い、これらを制御しますが、ユーザーからのモデルの変更要求は、EditPartが管理する全ての「EditPolicy」に渡されます。EditPolicyは、ユーザーからのモデルの変更要求を処理して、モデルの変更を行うための「コマンド」を作成します。実際にモデルに変更を加えるのは、コマンドの役目です。

 つまり、モデルの編集機能は、EditPartではなく、EditPartにインストールされる、EditPolicyにあるということになります。

モデルの編集

 GEFアプリケーションでのモデルの編集は、「ツール」、およびアクションによって行われます。ツールは、ユーザーが行ったマウス操作や、キー操作を「Request」と呼ばれる、モデルの変更要求を表すオブジェクトに変換します。同様に、アクションも実行された時点でRequestを作成します。

 作成されたRequestは、その時点で選択されている(アクティブな)EditPartに送信されます。EditPartは、送られてきたRequestを、自身にインストールされている、全てのEditPolicy(各EditPolicyは、Roleと呼ばれる文字列をキーとしてインストールされます)に渡します。Requestを渡されたEditPolicyは、そのRequestが表す、モデルの変更要求に応じたコマンドを作成します。その後、コマンドが実行され、モデルの変更が行われることになります。GEFアプリケーションでモデルに変更を加えることができるのは、コマンドだけです。

モデルの変更までの流れ
モデルの変更までの流れ

 モデルの変更は、上図のような流れで行われます。次に、モデルの編集に関する、GEFの要素について取り上げます。

Request

 Requestは、ツールやアクションによって作成される、モデルの変更要求を表すオブジェクトです。Requestは、モデルに対して、どのような編集動作を行うかを示すタイプと、その編集動作に必要な情報を持っています。

 GEFには、一般的な編集動作に関するRequestタイプと、そのタイプのRequestを処理するために必要な情報を設定することのできる、いくつかのRequestクラスが用意されています。

 例えば、「モデル作成ツール」を使用して、クリックを行った場合は、モデル作成ツールによって、クリックしたポイントに、モデルを新しく作成するためのRequestが作成されます。この時、作成されるRequestは、REQ_CREATEタイプのRequestです。また、このタイプのRequestには、新しく作成されるモデルのインスタンスと、モデルの作成位置や、サイズといった情報が設定されています。REQ_CREATEタイプのRequestは、このような情報を取得するために、CreateRequestクラスにキャストすることができます。

 また、デフォルトのドラッグ・ツールを使用して、EditPartをドラッグした場合は、ドラッグ・ツールによって、モデルの位置情報を変更するためのRequestが作成されます。この時作成されるRequestは、REQ_MOVE、あるいは、REQ_MOVE_CHILDRENタイプで、ChangeBoundsRequestクラスにキャストすることで、移動先の位置情報を取得することができます。

 このようなRequestのタイプや、その操作を行うために必要な情報は、EditPolicyがコマンドを作成するときに使用されます。

 GEFには、この他にも、いくつかのRequestタイプと、そのタイプのRequestを処理するために必要な情報を設定することのできる、いくつかのRequestクラスが用意されています。全てのRequestタイプ、およびRequestクラスについては、GEF SDKに付属する「GEF Developers Guide」の「Programmer's Guide」とAPIドキュメントを参照してください。

 また、アプリケーションによっては、独自のRequestタイプと、Requestクラスが必要になることもあります。Requestタイプは、単なる文字列なので、RequestConstantsインターフェイスに定義された、既存のタイプ文字列と重複しないようにすれば、独自のRequestタイプを定義することができます。また、Requestクラスに関しては、org.eclipse.gef.Requestクラスのサブクラスを作成して、そのタイプのRequestを処理するために必要な情報を保持できるようにします。

EditPolicy

 EditPolicyは、EditPartが何を行うか、あるいは、EditPartがモデルをどのように編集するか、といったEditPartの動作を定義するためのプラガブル・オブジェクトです。EditPartは、EditPolicyをインストールすることで、モデルの編集機能を持つようになります。

 具体的には、EditPolicyは、ツール、またはアクションによって作成されたモデルの変更要求(Request)を処理して、モデルを変更するためのコマンドを作成する役割と、必要に応じて、ユーザーの操作の内容を視覚的に表現するための「フィードバック」を表示する役割を担います。

フィードバックの例
フィードバックの例

 EditPolicyは、1つのEditPartに対して、複数インストールすることができます。それぞれのEditPolicyは、「Role」と呼ばれる文字列によって識別されます。

 前述の通り、EditPolicyには、編集動作の種類によって、様々なタイプのRequestが送信されてくることになります。全てのタイプのRequestを1つのEditPolicyで処理することも可能ですが、RoleをキーとしてEditPolicyをインストールすれば、Requestの処理を、関連するRequestタイプごとに、グループとして分割し、複数のEditPolicyに分けて行うことができます。

 また、EditPolicyはRoleをキーとして、動的に入れ替えたり、アンインストールすることができます。したがって、モデルの状態によって、モデルの編集方針を変更する、といったようなこともできます。

 例えば、モデルのプロパティによって、レイアウトの方法をXYLayoutからFlowLayoutに変更するような場合があったとします。その場合、XYLayoutであれば、子の移動は、モデルの位置情報の変更を意味しますが、FlowLayoutの場合は、子の移動は、モデルの順番の変更を意味するはずです。したがって、子の移動要求を表すRequestREQ_MOVE_CHILDREN)を処理する方法も、実行すべきコマンドも違うものになるでしょう。

 このような場合、レイアウトに関連するRequestをまとめて処理するEditPolicyXYLayout用のものと、FlowLayout用のものを作成しておけば、レイアウトの変更が行われた際、これらのEditPolicyを動的に入れ替えて、モデルの編集方針を変更することができます。また、レイアウトに影響を受けないモデルの変更要求を、別のRoleをキーとしてインストールされるEditPolicyで処理するようにしておけば、上記のようなEditPolicyの入れ替えが行われたとしても、その部分の編集機能には、全く影響がありません。

 また、共通する編集動作が必要なEditPartがあれば、スーパークラスで共通する編集動作を定義したEditPolicyをインストールして、サブクラスで、それぞれに固有の編集動作を定義したEditPolicyをインストールする、といったことも可能になります。

 このように、処理するRequestタイプを、お互いに関連性のあるグループに分けて、グループごとに複数のEditPolicyに分割して処理するようにしておけば、EditPartに柔軟性を持たせることができます。

 GEFには、このようなRequestのグループ分けを行うための指針として、いくつかのRoleが定義されています。また、そのRoleで処理するべきRequestのタイプと、それらのRequestを処理するのに便利なEditPolicyも用意されています。GEFが用意している全ての定義済みのRole、およびEditPolicyクラスに関しては、GEF SDKのドキュメントの「Programmer's Guide」の「Using EditPolicies, Requests, and Roles」セクション以下の表に示されています。

 GEFのドキュメント記述されているRoleおよび、そのRoleが処理すべきRequestタイプは、どのようにRequestタイプをグループ分けするのかを示した指針なので、完全にこれに従う必要はありません。また、Roleは、単なる文字列ですから、EditPolicyインターフェイスに定義されている、定義済みのRole文字列と重複しなければ、独自のRoleを定義することもできます。独自のEditPolicyが必要であれば、AbstractEditPolicyクラスなどを拡張することもできます。

コマンド

 コマンドは、モデルに対して行う編集操作をカプセル化したもので、ユーザーからのモデルの変更要求を実際に実行するためのものです。GEFのCommandクラスには、execute()undo()redo()といったコマンドの実行、およびコマンドの取り消し、やり直しを行うためのメソッドが定義されています。したがって、コマンド・クラスは、これらのメソッドをオーバーライドして、モデルを変更したり、変更を取り消したりするだけの単純なものになります。

 また、全てのコマンドは、「コマンド・スタック」によって管理されます。コマンド・スタックは、コマンドが実行された場合には、execute()メソッドを呼び出してから、Undo用のスタックにコマンドをプッシュし、コマンドが取り消された場合は、Undo用のスタックからコマンドをポップして、undo()メソッドを呼び出すといったような方法で、編集操作の取り消し・やり直しといった機能や、ドキュメントのダーティ・チェック機能をGEFアプリケーションに提供しています。

GEFアプリケーションの作成

 ここからは、単純な図を編集するためのサンプル・アプリケーションを使用して、GEFアプリケーションの実装方法を段階ごとに説明します。GEFアプリケーションは基本的に、以下のような手順で実装します。

  1. モデルの定義
  2. ビューの定義
  3. EditPartの作成
  4. モデルの編集機能の追加(EditPolicyの作成とインストール)
  5. EditPartViewerの実装

モデルの定義

 前述の通り、モデルの定義は基本的に自由です。この記事のサンプルGEFアプリケーションでは、下図のようなモデルが定義されています。

モデルのクラス図
モデルのクラス図

 モデルの構造は単純で、ExampleDiagramクラスが、NodeElementを管理し、NodeElementが、ConnectionElementを管理するようになっています。ExampleDiagramクラスは、全てのモデル・クラスに対して、直接的、あるいは間接的にアクセスすることができます。このような「トップレベル」のモデル・クラスは、EditPartViewerに対する入力として扱われます。

モデルの変更通知機能の追加

 前述の通り、モデルは、自身の変更をEditPartに通知する機能を持つ必要があります。サンプルで使用しているモデル・クラスでは、PropertyChangeSupportを使用してモデルの変更を通知する機能を実現しています。

「ModelElement.java」の抜粋
abstract public class ModelElement
    implements IPropertySource, Serializable {
...
    // モデルの状態変化を通知するリスナのリスト
    transient private PropertyChangeSupport listeners
        = new PropertyChangeSupport(this);

    public void
        addPropertyChangeListener(PropertyChangeListener l) {
        listeners.addPropertyChangeListener(l);
    }
    
    protected void firePropertyChange(
        String propName, Object oldValue, Object newValue) {
        listeners.firePropertyChange(propName, oldValue, newValue);
    }

    public void removePropertyChangeListener
        (PropertyChangeListener l) {
        listeners.removePropertyChangeListener(l);
    }
...
}

プロパティ・ソース・サポートの追加

 サンプルで使用しているモデル・クラスは、IPropertySourceインターフェイスを実装しています。これにより、GEFアプリケーションにプロパティ・ソース・サポートを追加しています。

 Eclipseの[プロパティ]ビューは、ワークベンチ上の現在の選択に対して、プロパティ・ソース(IPropertySourceインターフェイスを実装するクラス)を要求します。GEFアプリケーションでは、EditPartが現在の選択として、ワークベンチのセレクション・サービスに提供されることになるので、EditPartがプロパティ・ソースを返す役割を担います。

 デフォルトでは、EditPartがプロパティ・ソースの要求を受けたとき、そのEditPartに設定されているモデルが、IPropertySourceインターフェイスを実装していれば、モデルをプロパティ・ソースとして返します。また、モデルがIAdaptableインターフェイスを実装しているクラスであれば、getAdapterメソッドを通して、IPropertySource型のアダプターを要求し、その結果を返します。

 これ以外の方法で、プロパティ・ソース・サポートを追加するには、EditPartクラスでgetAdapterメソッドをオーバーライドして、IPropertySource型のアダプターを返すようにします。

 また、プロパティ・ソースの詳細については、このサンプルのソース、およびEclipse SDKの「プロパティ・シート・サンプル」等を参照してください。


  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • TAK_A(タカハシアキラ)

    Eclipseに関するコアな情報を提供する「ObserveEclipse」の管理人。なぜか海外の開発者から英訳してほしいとの依頼が日本語のメールで舞い込むことがあるとのこと。

All contents copyright © 2005-2020 Shoeisha Co., Ltd. All rights reserved. ver.1.5