同じ型の属性が複数あるクラスをルートとするダイアグラムエディタを作成する

id:kojihashi:20070709でも紹介したように、GMFを用いて作成するエディタの編集対象となるモデルには必ずルートクラスが必要で、そのルートクラス(のインスタンス)がキャンバスに対応することになります。GMFを用いて作成したエディタ上のパレットには一般に、モデルを構成する各クラスに対応するボタンが配置され、そのボタンをクリックしてからキャンバス上で再度クリックすることにより、そのボタンに対応するクラスのオブジェクトがルートクラス(のインスタンス)の属性に追加されます。

しかし、そのルートクラスに同じ型(クラス)の属性が複数ある場合にはどうなるでしょうか?さきほどと同様に、パレット上に配置されたそのクラスに対応するボタンをクリックしキャンバス上で再度クリックしたとき、そのクラスのオブジェクトをどの属性に追加すれば良いか、このままではGMFエディタは判断することができません。

GMFでは、パレット上に配置するボタンに複数の役割をマッピングできます。つまり、同一のボタンで例えば異なるクラスのオブジェクトをキャンバス上に配置することも可能です。そこで、この機能を使って上記問題を解決する方法を紹介します。

作業の概要は以下の通りです。

  1. .gmfgraph、.gmftoolを作成する
  2. .gmfmapを作成する。このとき、同一ボタンに対しルートクラスの各属性に追加する役割をマッピングする
  3. .gmfgenを生成し、diagramコードを生成する
  4. 生成されたdiagramコードのうち、XXVisualIDRegistry.javaを修正する

以下では、XML Schema Part 0: Primer Second Editionにおいて例として使われているPurchase Orderモデルを簡略化した以下のモデルを用いて説明します。


ご覧の通り、ルートクラスとなるPurchaseOrderクラスにbillToとshipToという、どちらもAddress型の属性があります。

まず、AddressおよびItemをそれぞれノードとしてキャンバス上に表現するよう、.gmfgraphファイルを作成します。GMFが提供する.gmfgraph作成用ウィザードにおいてPurchaseOrderクラスをルートに指定すれば、あとは自動で作成されるでしょう。

次に、AddressおよびItemを作成するボタンをパレットに配置するよう、.gmftoolファイルを作成します。これも、GMFが提供する.gmftool作成用ウィザードにおいてPurchaseOrderクラスをルートに指定すれば、あとは自動で作成されるでしょう。

次に、.gmfmapを作成します。GMFが提供する.gmfmap作成用ウィザードにおいてPurchaseOrderクラスをルートに指定すれば、とりあえずbillToあるいはshipToにAddressクラスのオブジェクトが追加されるようなマッピングを生成してくれるはずです。ここから、billToおよびshipTo両方にオブジェクトを選択的に追加できるよう、以下のようにマッピングを修正します。

  • TopNodeReference(items/Item用グラフ/Item用ツール)
  • TopNodeReference(billTo/Address用グラフ/Address用ツール)
  • TopNodeReference(shipTo/Address用グラフ/Address用ツール)

このように、billToおよびshipToへのマッピングに対しAddress用グラフとAddress用ツールを共有するところがポイントです。

この.gmfmapファイルから.gmfgenファイルを生成し、diagramコードを生成します。

ここで、生成されたdiagramコードのうち、XX.diagram.partパッケージにあるXXVisualIDRegistry.java内にあるgetNodeVisualID()というメソッドは以下のように生成されているはずです。

/**
 * @generated
 */
public static int getNodeVisualID(View containerView, EObject domainElement) {
	if (domainElement == null
			|| !PurchaseOrderEditPart.MODEL_ID
					.equals(po.diagram.part.PoVisualIDRegistry
							.getModelID(containerView))) {
		return -1;
	}
	switch (po.diagram.part.PoVisualIDRegistry.getVisualID(containerView)) {
	case PurchaseOrderEditPart.VISUAL_ID:
		if (PoPackage.eINSTANCE.getItem().isSuperTypeOf(
				domainElement.eClass())) {
			return ItemEditPart.VISUAL_ID;
		}
		if (PoPackage.eINSTANCE.getAddress().isSuperTypeOf(
				domainElement.eClass())) {
			return AddressEditPart.VISUAL_ID;
		}
		if (PoPackage.eINSTANCE.getAddress().isSuperTypeOf(
				domainElement.eClass())) {
			return Address2EditPart.VISUAL_ID;
		}
		break;
	}
	return -1;
}

上記コードの後半にあるswitch文の中にある3つのif文に注目すると、最後の3つ目には絶対到達しないことがわかります。実は、このせいで、いくら.gmfmapファイルにおいてbillToおよびshipTo両方にオブジェクトを選択的に追加できるようマッピングを定義しても、billToにしか追加できないことになってしまいます。もし仮に3つ目のif文に到達するとAddress2EditPart.VISUAL_IDがリターンされますが、これがshipToへの追加に対応しています。

そこで、以下のように修正することで正しく動作します。

/**
 * @generated NOT
 */
public static int getNodeVisualID(View containerView, EObject domainElement) {
	if (domainElement == null
			|| !PurchaseOrderEditPart.MODEL_ID
					.equals(po.diagram.part.PoVisualIDRegistry
							.getModelID(containerView))) {
		return -1;
	}
	switch (po.diagram.part.PoVisualIDRegistry.getVisualID(containerView)) {
	case PurchaseOrderEditPart.VISUAL_ID:
		if (PoPackage.eINSTANCE.getItem().isSuperTypeOf(
				domainElement.eClass())) {
			return ItemEditPart.VISUAL_ID;
		}
		if (PoPackage.eINSTANCE.getAddress().isSuperTypeOf(
				domainElement.eClass())) {
			switch (domainElement.eContainingFeature().getFeatureID()) {
			case PoPackage.PURCHASE_ORDER__SHIP_TO:
				return AddressEditPart.VISUAL_ID;	
			case PoPackage.PURCHASE_ORDER__BILL_TO:
				return Address2EditPart.VISUAL_ID;
			}
		}
		break;
	}
	return -1;
}

ポイントは最後のif文の中にあるswitch文です。このメソッドの二つ目の引数に与えられているオブジェクトは、キャンバス上に追加しようとしているモデルオブジェクト(つまりAddressクラスまたはItemクラスのオブジェクト)です。パレット上でボタンをクリックしキャンバス上で再度クリックした時に起動されるリスナーのコンテキストにおいて、このメソッドに到達したときには実はルートクラス(のインスタンス)の属性にオブジェクトは追加し終えていて、あとはキャンバス上のグラフィカルな部分の処理を行っている段階にいます。したがって、二つ目の引数に与えられたオブジェクトが追加されている属性を調べれば、正しくAddress2EditPart.VISUAL_IDをリターンすることができます。

このように修正すると、以下のようにキャンバス上で再度クリックしたときにポップアップメニューが表示され、billToに追加するのかshipToに追加するのか選択できて、正しく動作します。(ポップアップメニューがわかりにくいですが、このメニューの順番は、.gmfmapファイルにおけるTopNodeReferenceの順番となります。上記の例でいくとbillTo->shipToの順番なので、上側のメニューを選択するとbillTo、下側のメニューを選択するとshipToにオブジェクトが追加されるはずです。)