| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.intellij.util.xml.tree; |
| |
| import com.intellij.ide.util.treeView.AbstractTreeBuilder; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.actionSystem.*; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Disposer; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.pom.Navigatable; |
| import com.intellij.psi.xml.XmlElement; |
| import com.intellij.psi.xml.XmlFile; |
| import com.intellij.ui.components.panels.Wrapper; |
| import com.intellij.ui.treeStructure.SimpleNode; |
| import com.intellij.ui.treeStructure.SimpleTree; |
| import com.intellij.ui.treeStructure.SimpleTreeStructure; |
| import com.intellij.ui.treeStructure.WeightBasedComparator; |
| import com.intellij.ui.treeStructure.actions.CollapseAllAction; |
| import com.intellij.ui.treeStructure.actions.ExpandAllAction; |
| import com.intellij.util.ui.tree.TreeUtil; |
| import com.intellij.util.xml.*; |
| import com.intellij.util.xml.highlighting.DomElementAnnotationsManager; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.event.TreeExpansionEvent; |
| import javax.swing.event.TreeExpansionListener; |
| import javax.swing.tree.DefaultMutableTreeNode; |
| import javax.swing.tree.DefaultTreeModel; |
| import javax.swing.tree.TreeSelectionModel; |
| import java.awt.*; |
| |
| public class DomModelTreeView extends Wrapper implements DataProvider, Disposable { |
| public static final DataKey<DomModelTreeView> DATA_KEY = DataKey.create("DOM_MODEL_TREE_VIEW_KEY"); |
| @Deprecated @NonNls public static String DOM_MODEL_TREE_VIEW_KEY = DATA_KEY.getName(); |
| @NonNls public static String DOM_MODEL_TREE_VIEW_POPUP = "DOM_MODEL_TREE_VIEW_POPUP"; |
| |
| private final SimpleTree myTree; |
| private final AbstractTreeBuilder myBuilder; |
| private DomManager myDomManager; |
| @Nullable private DomElement myRootElement; |
| |
| public DomModelTreeView(@NotNull DomElement rootElement) { |
| this(rootElement, rootElement.getManager(), new DomModelTreeStructure(rootElement)); |
| } |
| |
| protected DomModelTreeView(DomElement rootElement, DomManager manager, SimpleTreeStructure treeStructure) { |
| myDomManager = manager; |
| myRootElement = rootElement; |
| myTree = new SimpleTree(new DefaultTreeModel(new DefaultMutableTreeNode())); |
| myTree.setRootVisible(isRootVisible()); |
| myTree.setShowsRootHandles(true); |
| myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); |
| |
| ToolTipManager.sharedInstance().registerComponent(myTree); |
| TreeUtil.installActions(myTree); |
| |
| myBuilder = new AbstractTreeBuilder(myTree, (DefaultTreeModel)myTree.getModel(), treeStructure, WeightBasedComparator.INSTANCE, false); |
| Disposer.register(this, myBuilder); |
| |
| myBuilder.setNodeDescriptorComparator(null); |
| |
| myBuilder.initRootNode(); |
| |
| add(myTree, BorderLayout.CENTER); |
| |
| myTree.addTreeExpansionListener(new TreeExpansionListener() { |
| @Override |
| public void treeExpanded(TreeExpansionEvent event) { |
| final SimpleNode simpleNode = myTree.getNodeFor(event.getPath()); |
| |
| if (simpleNode instanceof AbstractDomElementNode) { |
| ((AbstractDomElementNode)simpleNode).setExpanded(true); |
| } |
| } |
| |
| @Override |
| public void treeCollapsed(TreeExpansionEvent event) { |
| final SimpleNode simpleNode = myTree.getNodeFor(event.getPath()); |
| |
| if (simpleNode instanceof AbstractDomElementNode) { |
| ((AbstractDomElementNode)simpleNode).setExpanded(false); |
| simpleNode.update(); |
| } |
| } |
| }); |
| |
| myDomManager.addDomEventListener(new DomChangeAdapter() { |
| @Override |
| protected void elementChanged(DomElement element) { |
| if (element.isValid()) { |
| queueUpdate(DomUtil.getFile(element).getVirtualFile()); |
| } else if (element instanceof DomFileElement) { |
| final XmlFile xmlFile = ((DomFileElement)element).getFile(); |
| queueUpdate(xmlFile.getVirtualFile()); |
| } |
| } |
| }, this); |
| |
| |
| final Project project = myDomManager.getProject(); |
| DomElementAnnotationsManager.getInstance(project).addHighlightingListener(new DomElementAnnotationsManager.DomHighlightingListener() { |
| @Override |
| public void highlightingFinished(@NotNull DomFileElement element) { |
| if (element.isValid()) { |
| queueUpdate(DomUtil.getFile(element).getVirtualFile()); |
| } |
| } |
| }, this); |
| |
| myTree.setPopupGroup(getPopupActions(), DOM_MODEL_TREE_VIEW_POPUP); |
| } |
| |
| protected boolean isRightFile(final VirtualFile file) { |
| return myRootElement == null || (myRootElement.isValid() && file.equals(DomUtil.getFile(myRootElement).getVirtualFile())); |
| } |
| |
| private void queueUpdate(final VirtualFile file) { |
| if (file == null) return; |
| if (getProject().isDisposed()) return; |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| if (getProject().isDisposed()) return; |
| if (!file.isValid() || isRightFile(file)) { |
| myBuilder.updateFromRoot(); |
| } |
| } |
| }); |
| } |
| |
| protected boolean isRootVisible() { |
| return true; |
| } |
| |
| public final void updateTree() { |
| myBuilder.updateFromRoot(); |
| } |
| |
| public DomElement getRootElement() { |
| return myRootElement; |
| } |
| |
| protected final Project getProject() { |
| return myDomManager.getProject(); |
| } |
| |
| public AbstractTreeBuilder getBuilder() { |
| return myBuilder; |
| } |
| |
| @Override |
| public void dispose() { |
| } |
| |
| public SimpleTree getTree() { |
| return myTree; |
| } |
| |
| protected ActionGroup getPopupActions() { |
| DefaultActionGroup group = new DefaultActionGroup(); |
| |
| group.add(ActionManager.getInstance().getAction("DomElementsTreeView.TreePopup")); |
| group.addSeparator(); |
| |
| group.add(new ExpandAllAction(myTree)); |
| group.add(new CollapseAllAction(myTree)); |
| |
| return group; |
| } |
| |
| @Override |
| @Nullable |
| public Object getData(String dataId) { |
| if (DomModelTreeView.DATA_KEY.is(dataId)) { |
| return this; |
| } |
| final SimpleNode simpleNode = getTree().getSelectedNode(); |
| if (simpleNode instanceof AbstractDomElementNode) { |
| final DomElement domElement = ((AbstractDomElementNode)simpleNode).getDomElement(); |
| if (domElement != null && domElement.isValid()) { |
| if (CommonDataKeys.NAVIGATABLE_ARRAY.is(dataId)) { |
| final XmlElement tag = domElement.getXmlElement(); |
| if (tag instanceof Navigatable) { |
| return new Navigatable[] { (Navigatable)tag }; |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| public void setSelectedDomElement(final DomElement domElement) { |
| if (domElement == null) return; |
| |
| final SimpleNode node = getNodeFor(domElement); |
| |
| if (node != null) { |
| getTree().setSelectedNode(getBuilder(), node, true); |
| } |
| } |
| |
| @Nullable |
| private SimpleNode getNodeFor(final DomElement domElement) { |
| return visit((SimpleNode)myBuilder.getTreeStructure().getRootElement(), domElement); |
| } |
| |
| @Nullable |
| private SimpleNode visit(SimpleNode simpleNode, DomElement domElement) { |
| boolean validCandidate = false; |
| if (simpleNode instanceof AbstractDomElementNode) { |
| final DomElement nodeElement = ((AbstractDomElementNode)simpleNode).getDomElement(); |
| if (nodeElement != null) { |
| validCandidate = !(simpleNode instanceof DomElementsGroupNode); |
| if (validCandidate && nodeElement.equals(domElement)) { |
| return simpleNode; |
| } |
| if (!(nodeElement instanceof MergedObject) && !isParent(nodeElement, domElement)) { |
| return null; |
| } |
| } |
| } |
| final Object[] childElements = myBuilder.getTreeStructure().getChildElements(simpleNode); |
| if (childElements.length == 0 && validCandidate) { // leaf |
| return simpleNode; |
| } |
| for (Object child: childElements) { |
| SimpleNode result = visit((SimpleNode)child, domElement); |
| if (result != null) { |
| return result; |
| } |
| } |
| return validCandidate ? simpleNode : null; |
| } |
| |
| private static boolean isParent(final DomElement potentialParent, final DomElement domElement) { |
| DomElement currParent = domElement; |
| while (currParent != null) { |
| if (currParent.equals(potentialParent)) return true; |
| |
| currParent = currParent.getParent(); |
| } |
| return false; |
| } |
| |
| } |
| |