Eclipseのプラグイン開発の自作DMDLエディターでアウトラインを表示できるようにしてみる。
|
|
アウトライン(アウトラインページ)は、Javaエディターで言えば(Eclipse3.7の右側に出ている)クラスやフィールド・メソッドの一覧のやつ。
アウトラインは要するにツリーなので、データとしてツリー構造を用意する。
アウトラインを作るには、org.eclipse.ui.views.contentoutline.ContentOutlinePageクラスを使用する。
これをimport文に指定して“ContentOutlinePageが見つからない”というコンパイルエラーになる場合は、「依存関係」の「インストール済みパッケージ」に「org.eclipse.ui.views.contentoutline」を追加する。
もしくは、「依存関係」の「必須プラグイン」で「org.eclipse.ui.views」を追加する。
アウトラインを作るには、ドキュメント(エディター上のテキスト)を構文解析してアウトラインに表示するものを決める必要がある。
DMDLエディターの場合は、データモデル名と、その中で定義されているプロパティー名を出すのが良さそう。
↓こんなイメージ
なお、ここでは詳細は割愛するが、アウトラインやフォールディングで使えるように、簡単なパーサー(DMDLSimpleParser)を作成した。
アウトラインのツリー構造を作るのに このパーサーを利用する。
中核となるアウトラインページクラス。
→インスタンス生成箇所
import jp.hishidama.eclipse_plugin.dmdl_editor.editors.DMDLEditor; import jp.hishidama.eclipse_plugin.dmdl_editor.parser.DMDLSimpleParser; import jp.hishidama.eclipse_plugin.dmdl_editor.parser.token.ModelList; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.views.contentoutline.ContentOutlinePage;
public class OutlinePage extends ContentOutlinePage {
protected RootData root = new RootData();
protected DMDLEditor editor;
public OutlinePage(DMDLEditor editor) {
this.editor = editor;
}
コンストラクターでDMDLEditorのインスタンスを保持しておく。
@Override
public void createControl(Composite parent) {
super.createControl(parent);
TreeViewer viewer = getTreeViewer();
viewer.setContentProvider(new DMDLContentProvider());
viewer.setLabelProvider(new DMDLLabelProvider());
viewer.addSelectionChangedListener(new OutlineSelectionChangedListener(editor));
viewer.setInput(root);
IDocument document = editor.getDocumentProvider().getDocument(editor.getEditorInput());
DMDLSimpleParser parser = new DMDLSimpleParser();
ModelList models = parser.parse(document);
refresh(models);
}
createControl()でアウトライン関連クラスの初期化を行う。
viewer.setInput()でアウトラインのツリーのデータを管理するオブジェクトを指定する。
public void refresh(ModelList models) {
root.element = models;
getTreeViewer().refresh();
}
}
refresh()は、アウトラインのツリーの再描画を行うメソッド。
外部から呼べるようにpublicにしておく。
アウトラインのデータ(ツリーのルート)を保持する為のクラスを用意する。
import jp.hishidama.eclipse_plugin.dmdl_editor.parser.token.ModelList;
public class RootData {
public ModelList element;
}
element要素を持っているだけ。非常にシンプル(笑)
(ModelListは、自作したDMDLSimpleParserが返すクラス。構文解析結果を保持している)
ContentProviderは、アウトラインのツリーの内容を管理するクラス。
OutlinePageにてviewer.setContentProvider()で設定する。
import java.util.List; import jp.hishidama.eclipse_plugin.dmdl_editor.parser.token.ModelToken; import jp.hishidama.eclipse_plugin.dmdl_editor.parser.token.PropertyToken; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.Viewer;
public class DMDLContentProvider implements ITreeContentProvider {
@Override
public Object[] getElements(Object inputElement) {
RootData root = (RootData) inputElement;
if (root.element == null) {
return new Object[] {};
}
return root.element.getModelList().toArray();
}
getElements()でツリーのルート直下に表示されるデータ一覧を返すようにする。
ここに渡ってくるinputElementは、OutlinePage#createControl()にてviewer.setInput()でセットしたもの。
@Override
public boolean hasChildren(Object element) {
if (element instanceof ModelToken) {
ModelToken model = (ModelToken) element;
List<PropertyToken> list = model.getPropertyList();
return !list.isEmpty();
}
return false;
}
hasChildren()は、引数のelementの子要素があるかどうかを返す。
@Override
public Object[] getChildren(Object parentElement) {
if (parentElement instanceof ModelToken) {
ModelToken model = (ModelToken) parentElement;
List<PropertyToken> list = model.getPropertyList();
return list.toArray();
}
return null;
}
getChildren()は、引数のparentElementの子要素一覧を返す。
hasChildren()の引数名はelementなのに、なんでgetChildren()の引数名はparentElementなんだ^^;
@Override
public Object getParent(Object element) {
return null;
}
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
@Override
public void dispose() {
}
}
LabelProviderは、ツリーの要素を実際に表示する際の文言やイメージ(画像)を管理するクラス。
OutlinePageにてviewer.setLabelProvider()で設定する。
import java.net.URL; import jp.hishidama.eclipse_plugin.dmdl_editor.parser.token.ModelToken; import jp.hishidama.eclipse_plugin.dmdl_editor.parser.token.PropertyToken; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.swt.graphics.Image;
public class DMDLLabelProvider extends LabelProvider {
@Override
public Image getImage(Object element) {
return null;
}
@Override
public String getText(Object element) {
if (element instanceof ModelToken) {
ModelToken token = (ModelToken) element;
return token.getModelName();
}
if (element instanceof PropertyToken) {
PropertyToken token = (PropertyToken) element;
String name = token.getPropertyName();
String type = token.getDataType();
if (type == null) {
return name;
}
return name + " : " + type;
}
return element.toString();
}
}
DMDLエディターでは、ツリーの要素がデータモデルを表している場合はモデル名、プロパティーの場合は「プロパティー名 :
データ型」を表示するようにしてみた。
SelectionChangedListenerは、アウトライン上でデータ(要素・ノード)がクリックされたときに呼ばれるリスナークラス。
OutlinePageにてviewer.addSelectionChangedListener()で設定する。
import jp.hishidama.eclipse_plugin.dmdl_editor.editors.DMDLEditor; import jp.hishidama.eclipse_plugin.dmdl_editor.parser.token.DMDLToken; import jp.hishidama.eclipse_plugin.dmdl_editor.parser.token.ModelToken; import jp.hishidama.eclipse_plugin.dmdl_editor.parser.token.PropertyToken; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent;
public class OutlineSelectionChangedListener implements ISelectionChangedListener {
protected DMDLEditor editor;
public OutlineSelectionChangedListener(DMDLEditor editor) {
this.editor = editor;
}
@Override
public void selectionChanged(SelectionChangedEvent event) {
IStructuredSelection sel = (IStructuredSelection) event.getSelection();
Object element = sel.getFirstElement();
DMDLToken token;
if (element instanceof ModelToken) {
ModelToken model = (ModelToken) element;
token = model.getModelNameToken();
} else if (element instanceof PropertyToken) {
PropertyToken prop = (PropertyToken) element;
token = prop.getPropertyNameToken();
} else {
token = (DMDLToken) element;
}
if (token != null) {
int offset = token.getStart();
int length = token.getLength();
editor.selectAndReveal(offset, length);
}
}
}
アウトライン上でデータがクリックされるとselectionChanged()が呼ばれる。
ちなみに、selectAndReveal()のlengthに0を指定するとカーソルがその位置に移動するだけとなる。
DMDLEditorでアウトラインの設定を行う。
import jp.hishidama.eclipse_plugin.dmdl_editor.editors.folding.FoldingManager; import jp.hishidama.eclipse_plugin.dmdl_editor.editors.outline.OutlinePage; import jp.hishidama.eclipse_plugin.dmdl_editor.parser.DMDLSimpleParser;
public class DMDLEditor extends TextEditor implements IPropertyChangeListener {
protected OutlinePage outlinePage;
@Override
public Object getAdapter(@SuppressWarnings("rawtypes") Class adapter) {
if (IContentOutlinePage.class.equals(adapter)) {
if (outlinePage == null) {
outlinePage = new OutlinePage(this);
}
return outlinePage;
}
// フォールディング
Object obj = foldingManager.getAdapter(adapter);
if (obj != null) {
return obj;
}
return super.getAdapter(adapter);
}
アウトラインでは、getAdapter()でContentOutlinePageインスタンスを生成して返すようにする。
@Override
public void doSaveAs() {
super.doSaveAs();
update();
}
@Override
public void doSave(IProgressMonitor progressMonitor) {
super.doSave(progressMonitor);
update();
}
private void update() {
IDocument document = getDocumentProvider().getDocument(getEditorInput());
DMDLSimpleParser parser = new DMDLSimpleParser();
ModelList models = parser.parse(document);
// フォールディング範囲を最新状態に更新する
foldingManager.updateFolding(document, models);
// アウトラインを最新状態に更新する
if (outlinePage != null) {
outlinePage.refresh(models);
}
}
フォールディング処理と同様に、 ファイル保存時にアウトラインを再描画することにする。