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);
}
}
フォールディング処理と同様に、 ファイル保存時にアウトラインを再描画することにする。