これで、ツールの 3 つの主要なメソッド init、dispose and getComponent を実装する準備が整いました。
これがコードです(コメント付きで、参照してください ! ):
init(project)->void {
this.project = project;
}
dispose(project)->void {
// disconnect from message bus -- connected in getComponent()
if ( this.messageBusConn != null ) this.messageBusConn.disconnect();
}
getComponent()->JComponent {
// create a new JTree with a custom cell renderer (for rendering custom icons)
JTree tree = new JTree(new Object[0]);
tree.setCellRenderer(new OutlineTreeRenderer(tree.getCellRenderer()));
// create a new synchronizer. It needs the tree to notify it of updates.
this.synchronizer = new ToolSynchronizer(tree);
// connect to the message bus. The synchronizer receives editor change events
// and pushes them on to the tree. The synchronizer implements
// FileEditorChangeListener to be able to make this work
this.messageBusConn = this.project.getMessageBus().connect();
this.messageBusConn.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER,
this.synchronizer);
// we finally return a ScrollPane with the tree in it
return new JScrollPane(tree);
}
public void selectionChanged(FileEditorManagerEvent event) {
// read action is required since we access the model
read action {
FileEditor oldEditor = event.getOldEditor();
// grab the old editor and clean it up if there is one
if (oldEditor != null) {
this.cleanupOldEditor(oldEditor);
}
// call a helper method that sets up the new editor
this.newEditorActivated(event.getNewEditor());
}
}
private void cleanupOldEditor(FileEditor oldEditor) {
// Downcast from IDEA level to MPS specifics and
// grab the NodeEditor component
IEditor oldNodeEditor = ((MPSFileNodeEditor) oldEditor).getNodeEditor();
if (oldNodeEditor != null && this.editorSelectionListener != null) {
// remove the selection listener from the old editor
oldNodeEditor.getCurrentEditorComponent().getSelectionManager().
removeSelectionListener(this.editorSelectionListener);
// grab the descriptor of the model edited by the old editor
// and remove the model listener (cleanup!)
SModelDescriptor descriptor = oldNodeEditor.getEditorContext().getModel().
getModelDescriptor();
descriptor.removeModelListener(modelLifecycleListener);
// remove the model edited by the old editor from the events collector
// ...we are not interested anymore.
eventsCollector.remove(descriptor);
}
}
public void newEditorActivated(FileEditor fileEditor) {
if (fileEditor != null) {
// remember the current editor
this.currentEditor = ((MPSFileNodeEditor) fileEditor);
// grab the root node of that new editor...
SNode rootNode = this.currentEditor.getNodeEditor().
getCurrentlyEditedNode().getNode();
// ...wrap it in an SNodePointer...
SNodePointer treeRoot = new SNodePointer(rootNode);
// and create a new outline tree model
OutlineModel model = new OutlineModel(treeRoot);
tree.setModel(model);
// create a new selection listener and hook it up
// with the newly selected editor
this.editorSelectionListener = new EditorSelectionListener(tree,
outlineSelectionListener);
SelectionManager selectionManager = this.currentEditor.getNodeEditor().
getCurrentEditorComponent().getSelectionManager();
selectionManager.addSelectionListener(this.editorSelectionListener);
// This is needed to detect reloading of a model
((MPSFileNodeEditor) fileEditor).getNodeEditor().
getEditorContext().getModel().getModelDescriptor().
addModelListener(modelLifecycleListener);
eventsCollector.add(this.currentEditor.getNodeEditor().
getEditorContext().getModel().getModelDescriptor());
} else {
tree.setModel(new OutlineModel(null));
}
}
モデルライフサイクルの追跡
MPS によって提供される ModelLifecycleListener extends the SModelAdapter クラス。モデルの置き換えに関心があるため、modelReplaced メソッドをオーバーロードします。現在保持されているモデルが新しいモデルに置き換えられるたびに呼び出されます。VCS リバー操作中。
protected void selectionChangedTo(EditorComponent component,
SingularSelection selection) {
// do nothing is disabled -- prevents cyclic, never ending updates
if (disabled) { return; }
// read action, because we access the model
read action {
// gran the current selection
SNode selectedNode = selection.getSelectedNodes().get(0);
// ... only if it has changed...
if (selectedNode != lastSelection) {
lastSelection = selectedNode;
// disable the tree selection listener, once again to prevent cyclic
// never ending updates
treeSelectionListener.disable();
// select the actual node in the tree
tree.setSelectionPath(((OutlineModel) tree.getModel()).
gtPathTo(selectedNode));
treeSelectionListener.enable();
}
}
}
public void visitPropertyEvent(SModelPropertyEvent event) {
OutlineModel outlineModel = ((OutlineModel) tree.getModel());
foreach l in outlineModel.getListeners() {
l.treeNodesChanged(new TreeModelEvent(this,
outlineModel.getPathTo(event.getNode())));
}
}
また、visitChildEvent を上書きして、ノードの子の追加 / 削除の通知を受け取ります。JTree の API が少し煩わしいことを除いて、次のコメント付きコードは、それが何をするかについて明確にする必要があります。
@Override
public void visitChildEvent(SModelChildEvent event) {
// grab the model
OutlineModel outlineModel = ((OutlineModel) tree.getModel());
// we need the following arrays later for the JTree API
Object[] child = new Object[]{event.getChild()};
int[] childIndex = new int[]{event.getChildIndex()};
// we create a tree path to the parent notify all listeners
// of adding or removing children
TreePath path = outlineModel.getPathTo(event.getParent());
if (path == null) { return; }
// notify the tree model's listeners about what happened
foreach l in outlineModel.getListeners() {
if (event.isAdded()) {
l.treeNodesInserted(new TreeModelEvent(this, path, childIndex, child));
} else if (event.isRemoved()) {
l.treeNodesRemoved(new TreeModelEvent(this, path, childIndex, child));
}
}
}
帰り道: 追跡木の選択
JTree の選択の追跡は、Swing の TreeSelectionListener and overwriting it's valueChanged メソッドを次の方法で実装することによって行われます。
public void valueChanged(TreeSelectionEvent event) {
// don't do anything if disabled --- preventing cyclic updates!
if (!(disableEditorUpdate)) {
JTree tree = ((JTree) event.getSource());
if (editorActivationListener.currentEditor != null &&
tree.getLastSelectedPathComponent() instanceof SNodePointer) {
// grab the selected treee node
SNodePointer pointer = ((SNodePointer) tree.
getLastSelectedPathComponent());
// disable the editor selection listener to prevent
// cyclic, never ending updates
editorActivationListener.editorSelectionListener.disable();
// update the selection in the editor
editorActivationListener.currentEditor.getNodeEditor().
getCurrentEditorComponent().selectNode(pointer.getNode());
editorActivationListener.editorSelectionListener.enable();
}
}
}