| /* |
| * 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.ide.structureView.newStructureView; |
| |
| import com.intellij.ide.CommonActionsManager; |
| import com.intellij.ide.CopyPasteDelegator; |
| import com.intellij.ide.DataManager; |
| import com.intellij.ide.PsiCopyPasteManager; |
| import com.intellij.ide.structureView.*; |
| import com.intellij.ide.structureView.impl.StructureViewFactoryImpl; |
| import com.intellij.ide.structureView.impl.StructureViewState; |
| import com.intellij.ide.structureView.impl.common.PsiTreeElementBase; |
| import com.intellij.ide.ui.customization.CustomizationUtil; |
| import com.intellij.ide.util.FileStructurePopup; |
| import com.intellij.ide.util.treeView.*; |
| import com.intellij.ide.util.treeView.smartTree.*; |
| import com.intellij.ide.util.treeView.smartTree.TreeModel; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.actionSystem.*; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.fileEditor.FileEditor; |
| import com.intellij.openapi.fileEditor.FileEditorManager; |
| import com.intellij.openapi.project.IndexNotReadyException; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.SimpleToolWindowPanel; |
| import com.intellij.openapi.util.*; |
| import com.intellij.openapi.wm.IdeFocusManager; |
| import com.intellij.pom.Navigatable; |
| import com.intellij.psi.PsiDocumentManager; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.impl.source.tree.CompositeElement; |
| import com.intellij.psi.util.PsiUtilCore; |
| import com.intellij.ui.*; |
| import com.intellij.ui.treeStructure.actions.CollapseAllAction; |
| import com.intellij.ui.treeStructure.actions.ExpandAllAction; |
| import com.intellij.ui.treeStructure.filtered.FilteringTreeStructure; |
| import com.intellij.util.*; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.Convertor; |
| import com.intellij.util.ui.UIUtil; |
| import com.intellij.util.ui.tree.TreeUtil; |
| import gnu.trove.THashSet; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.tree.*; |
| import java.awt.*; |
| import java.awt.event.KeyAdapter; |
| import java.awt.event.KeyEvent; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| |
| public class StructureViewComponent extends SimpleToolWindowPanel implements TreeActionsOwner, DataProvider, StructureView.Scrollable { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.ide.structureView.newStructureView.StructureViewComponent"); |
| @NonNls private static final String ourHelpID = "viewingStructure.fileStructureView"; |
| |
| private AbstractTreeBuilder myAbstractTreeBuilder; |
| |
| private FileEditor myFileEditor; |
| private final TreeModelWrapper myTreeModelWrapper; |
| |
| private StructureViewState myStructureViewState; |
| private boolean myAutoscrollFeedback; |
| |
| private final Alarm myAutoscrollAlarm = new Alarm(); |
| |
| private final CopyPasteDelegator myCopyPasteDelegator; |
| private final MyAutoScrollToSourceHandler myAutoScrollToSourceHandler; |
| private final AutoScrollFromSourceHandler myAutoScrollFromSourceHandler; |
| |
| private static final Key<StructureViewState> STRUCTURE_VIEW_STATE_KEY = Key.create("STRUCTURE_VIEW_STATE"); |
| private final Project myProject; |
| private final StructureViewModel myTreeModel; |
| private static int ourSettingsModificationCount; |
| |
| public StructureViewComponent(final FileEditor editor, |
| @NotNull StructureViewModel structureViewModel, |
| @NotNull Project project, |
| final boolean showRootNode) { |
| super(true, true); |
| |
| myProject = project; |
| myFileEditor = editor; |
| myTreeModel = structureViewModel; |
| myTreeModelWrapper = new TreeModelWrapper(myTreeModel, this); |
| |
| SmartTreeStructure treeStructure = new SmartTreeStructure(project, myTreeModelWrapper){ |
| @Override |
| public void rebuildTree() { |
| if (!isDisposed()) { |
| super.rebuildTree(); |
| } |
| } |
| |
| @Override |
| public boolean isToBuildChildrenInBackground(final Object element) { |
| return getRootElement() == element; |
| } |
| |
| @Override |
| protected TreeElementWrapper createTree() { |
| return new StructureViewTreeElementWrapper(myProject, myModel.getRoot(), myModel); |
| } |
| |
| @Override |
| public String toString() { |
| return "structure view tree structure(model=" + myTreeModel + ")"; |
| } |
| }; |
| |
| final DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode(treeStructure.getRootElement())); |
| JTree tree = new JBTreeWithHintProvider(model); |
| tree.setRootVisible(showRootNode); |
| tree.setShowsRootHandles(true); |
| |
| myAbstractTreeBuilder = new StructureTreeBuilder(project, tree, |
| (DefaultTreeModel)tree.getModel(),treeStructure,myTreeModelWrapper) { |
| @Override |
| protected boolean validateNode(Object child) { |
| return isValid(child); |
| } |
| }; |
| Disposer.register(this, myAbstractTreeBuilder); |
| Disposer.register(myAbstractTreeBuilder, new Disposable() { |
| @Override |
| public void dispose() { |
| storeState(); |
| } |
| }); |
| |
| setContent(ScrollPaneFactory.createScrollPane(myAbstractTreeBuilder.getTree())); |
| |
| myAbstractTreeBuilder.getTree().setCellRenderer(new NodeRenderer()); |
| |
| myAutoScrollToSourceHandler = new MyAutoScrollToSourceHandler(); |
| myAutoScrollFromSourceHandler = new MyAutoScrollFromSourceHandler(myProject, this); |
| |
| if (getSettings().SHOW_TOOLBAR) { |
| setToolbar(createToolbar()); |
| } |
| |
| installTree(); |
| |
| myCopyPasteDelegator = new CopyPasteDelegator(myProject, getTree()) { |
| @Override |
| @NotNull |
| protected PsiElement[] getSelectedElements() { |
| return getSelectedPsiElements(); |
| } |
| }; |
| } |
| |
| public void showToolbar() { |
| setToolbar(createToolbar()); |
| } |
| |
| private JComponent createToolbar() { |
| return ActionManager.getInstance().createActionToolbar(ActionPlaces.STRUCTURE_VIEW_TOOLBAR, createActionGroup(), true).getComponent(); |
| } |
| |
| private void installTree() { |
| getTree().getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); |
| myAutoScrollToSourceHandler.install(getTree()); |
| myAutoScrollFromSourceHandler.install(); |
| |
| TreeUtil.installActions(getTree()); |
| |
| new TreeSpeedSearch(getTree(), new Convertor<TreePath, String>() { |
| @Override |
| public String convert(final TreePath treePath) { |
| final DefaultMutableTreeNode node = (DefaultMutableTreeNode)treePath.getLastPathComponent(); |
| final Object userObject = node.getUserObject(); |
| if (userObject != null) { |
| return FileStructurePopup.getSpeedSearchText(userObject); |
| } |
| return null; |
| } |
| }); |
| |
| addTreeKeyListener(); |
| addTreeMouseListeners(); |
| restoreState(); |
| } |
| |
| private PsiElement[] getSelectedPsiElements() { |
| return filterPsiElements(getSelectedElements()); |
| } |
| |
| @NotNull |
| private static PsiElement[] filterPsiElements(Object[] selectedElements) { |
| if (selectedElements == null) { |
| return PsiElement.EMPTY_ARRAY; |
| } |
| ArrayList<PsiElement> psiElements = new ArrayList<PsiElement>(); |
| |
| for (Object selectedElement : selectedElements) { |
| if (selectedElement instanceof PsiElement) { |
| psiElements.add((PsiElement)selectedElement); |
| } |
| } |
| return PsiUtilCore.toPsiElementArray(psiElements); |
| } |
| |
| private Object[] getSelectedElements() { |
| final JTree tree = getTree(); |
| return tree != null ? convertPathsToValues(tree.getSelectionPaths()): ArrayUtil.EMPTY_OBJECT_ARRAY; |
| } |
| |
| @Nullable |
| private Object[] getSelectedTreeElements() { |
| final JTree tree = getTree(); |
| return tree != null ? convertPathsToTreeElements(tree.getSelectionPaths()) : null; |
| } |
| |
| |
| private static Object[] convertPathsToValues(@Nullable TreePath[] selectionPaths) { |
| if (selectionPaths == null) return null; |
| List<Object> result = new ArrayList<Object>(); |
| for (TreePath selectionPath : selectionPaths) { |
| ContainerUtil.addIfNotNull(result, getNodeTreeValue((DefaultMutableTreeNode)selectionPath.getLastPathComponent())); |
| } |
| return ArrayUtil.toObjectArray(result); |
| } |
| |
| @Nullable |
| private static Object[] convertPathsToTreeElements(TreePath[] selectionPaths) { |
| if (selectionPaths == null) return null; |
| List<Object> result = new ArrayList<Object>(); |
| for (TreePath selectionPath : selectionPaths) { |
| ContainerUtil.addIfNotNull(result, getNodeValue((DefaultMutableTreeNode)selectionPath.getLastPathComponent())); |
| } |
| return ArrayUtil.toObjectArray(result); |
| } |
| |
| @Nullable |
| private static Object getNodeValue(DefaultMutableTreeNode mutableTreeNode) { |
| Object userObject = mutableTreeNode.getUserObject(); |
| if (userObject instanceof FilteringTreeStructure.FilteringNode) { |
| userObject = ((FilteringTreeStructure.FilteringNode)userObject).getDelegate(); |
| } |
| return userObject instanceof AbstractTreeNode ? ((AbstractTreeNode)userObject).getValue() : null; |
| } |
| |
| @Nullable |
| private static Object getNodeTreeValue(DefaultMutableTreeNode mutableTreeNode) { |
| Object value = getNodeValue(mutableTreeNode); |
| return value instanceof StructureViewTreeElement ? ((StructureViewTreeElement)value).getValue() : null; |
| } |
| |
| private void addTreeMouseListeners() { |
| EditSourceOnDoubleClickHandler.install(getTree()); |
| CustomizationUtil.installPopupHandler(getTree(), IdeActions.GROUP_STRUCTURE_VIEW_POPUP, ActionPlaces.STRUCTURE_VIEW_POPUP); |
| } |
| |
| private void addTreeKeyListener() { |
| getTree().addKeyListener( |
| new KeyAdapter() { |
| @Override |
| public void keyPressed(KeyEvent e) { |
| if (KeyEvent.VK_ENTER == e.getKeyCode()) { |
| DataContext dataContext = DataManager.getInstance().getDataContext(getTree()); |
| OpenSourceUtil.openSourcesFrom(dataContext, false); |
| } |
| else if (KeyEvent.VK_ESCAPE == e.getKeyCode()) { |
| if (e.isConsumed()) |
| { |
| return; |
| } |
| PsiCopyPasteManager copyPasteManager = PsiCopyPasteManager.getInstance(); |
| boolean[] isCopied = new boolean[1]; |
| if (copyPasteManager.getElements(isCopied) != null && !isCopied[0]) { |
| copyPasteManager.clear(); |
| e.consume(); |
| } |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public void storeState() { |
| if (!isDisposed()) { |
| myStructureViewState = getState(); |
| myFileEditor.putUserData(STRUCTURE_VIEW_STATE_KEY, myStructureViewState); |
| } |
| } |
| |
| public StructureViewState getState() { |
| StructureViewState structureViewState = new StructureViewState(); |
| if (getTree() != null) { |
| structureViewState.setExpandedElements(getExpandedElements()); |
| structureViewState.setSelectedElements(getSelectedElements()); |
| } |
| return structureViewState; |
| } |
| |
| private Object[] getExpandedElements() { |
| final JTree tree = getTree(); |
| if (tree == null) return ArrayUtil.EMPTY_OBJECT_ARRAY; |
| final List<TreePath> expandedPaths = TreeUtil.collectExpandedPaths(tree); |
| return convertPathsToValues(expandedPaths.toArray(new TreePath[expandedPaths.size()])); |
| } |
| |
| |
| @Override |
| public void restoreState() { |
| myStructureViewState = myFileEditor.getUserData(STRUCTURE_VIEW_STATE_KEY); |
| if (myStructureViewState == null) { |
| TreeUtil.expand(getTree(), 2); |
| } |
| else { |
| expandStoredElements(); |
| selectStoredElements(); |
| myFileEditor.putUserData(STRUCTURE_VIEW_STATE_KEY, null); |
| myStructureViewState = null; |
| } |
| } |
| |
| private void selectStoredElements() { |
| Object[] selectedPsiElements = null; |
| |
| if (myStructureViewState != null) { |
| selectedPsiElements = myStructureViewState.getSelectedElements(); |
| } |
| |
| if (selectedPsiElements == null) { |
| getTree().setSelectionPath(new TreePath(getRootNode().getPath())); |
| } |
| else { |
| for (Object element : selectedPsiElements) { |
| if (element instanceof PsiElement && !((PsiElement)element).isValid()) { |
| continue; |
| } |
| addSelectionPathTo(element); |
| } |
| } |
| } |
| |
| public void addSelectionPathTo(final Object element) { |
| DefaultMutableTreeNode node = myAbstractTreeBuilder.getNodeForElement(element); |
| if (node != null) { |
| final JTree tree = getTree(); |
| final TreePath path = new TreePath(node.getPath()); |
| if (node == tree.getModel().getRoot() && !tree.isExpanded(path)) tree.expandPath(path); |
| tree.addSelectionPath(path); |
| } |
| } |
| |
| private DefaultMutableTreeNode getRootNode() { |
| return (DefaultMutableTreeNode)getTree().getModel().getRoot(); |
| } |
| |
| private void expandStoredElements() { |
| Object[] expandedPsiElements = null; |
| |
| if (myStructureViewState != null) { |
| expandedPsiElements = myStructureViewState.getExpandedElements(); |
| } |
| |
| if (expandedPsiElements == null) { |
| getTree().expandPath(new TreePath(getRootNode().getPath())); |
| } |
| else { |
| for (Object element : expandedPsiElements) { |
| if (element instanceof PsiElement && !((PsiElement)element).isValid()) { |
| continue; |
| } |
| expandPathToElement(element); |
| } |
| } |
| } |
| |
| public ActionGroup getGearActions() { |
| DefaultActionGroup group = createActionGroup(true); |
| group.addAction(new ToggleAction("Show Toolbar") { |
| @Override |
| public boolean isDumbAware() { |
| return true; |
| } |
| |
| @Override |
| public boolean isSelected(AnActionEvent e) { |
| return getSettings().SHOW_TOOLBAR; |
| } |
| |
| @Override |
| public void setSelected(AnActionEvent e, boolean state) { |
| setToolbar(state ? createToolbar() : null); |
| getSettings().SHOW_TOOLBAR = state; |
| } |
| }).setAsSecondary(true); |
| return group; |
| } |
| |
| private StructureViewFactoryImpl.State getSettings() { |
| return ((StructureViewFactoryImpl)StructureViewFactory.getInstance(myProject)).getState(); |
| } |
| |
| public AnAction[] getTitleActions() { |
| return new AnAction[]{ |
| CommonActionsManager.getInstance().createExpandAllHeaderAction(getTree()), |
| CommonActionsManager.getInstance().createCollapseAllHeaderAction(getTree()), |
| }; |
| } |
| |
| protected ActionGroup createActionGroup() { |
| return createActionGroup(false); |
| } |
| |
| protected DefaultActionGroup createActionGroup(boolean togglesOnly) { |
| DefaultActionGroup result = new DefaultActionGroup(); |
| Sorter[] sorters = myTreeModel.getSorters(); |
| for (final Sorter sorter : sorters) { |
| if (sorter.isVisible()) { |
| result.add(new TreeActionWrapper(sorter, this)); |
| } |
| } |
| if (sorters.length > 0) { |
| result.addSeparator(); |
| } |
| |
| addGroupByActions(result); |
| |
| Filter[] filters = myTreeModel.getFilters(); |
| for (Filter filter : filters) { |
| result.add(new TreeActionWrapper(filter, this)); |
| } |
| if (myTreeModel instanceof ProvidingTreeModel) { |
| final Collection<NodeProvider> providers = ((ProvidingTreeModel)myTreeModel).getNodeProviders(); |
| for (NodeProvider provider : providers) { |
| result.add(new TreeActionWrapper(provider, this)); |
| } |
| } |
| |
| if (!togglesOnly) { |
| result.add(new ExpandAllAction(getTree())); |
| result.add(new CollapseAllAction(getTree())); |
| } |
| |
| if (showScrollToFromSourceActions()) { |
| result.addSeparator(); |
| |
| result.add(myAutoScrollToSourceHandler.createToggleAction()); |
| result.add(myAutoScrollFromSourceHandler.createToggleAction()); |
| } |
| return result; |
| } |
| |
| protected void addGroupByActions(DefaultActionGroup result) { |
| Grouper[] groupers = myTreeModel.getGroupers(); |
| for (Grouper grouper : groupers) { |
| result.add(new TreeActionWrapper(grouper, this)); |
| } |
| } |
| |
| protected boolean showScrollToFromSourceActions() { |
| return true; |
| } |
| |
| @Override |
| public FileEditor getFileEditor() { |
| return myFileEditor; |
| } |
| |
| public AsyncResult<AbstractTreeNode> expandPathToElement(Object element) { |
| if (myAbstractTreeBuilder == null) return new AsyncResult.Rejected<AbstractTreeNode>(); |
| |
| ArrayList<AbstractTreeNode> pathToElement = getPathToElement(element); |
| if (pathToElement.isEmpty()) return new AsyncResult.Rejected<AbstractTreeNode>(); |
| |
| final AsyncResult<AbstractTreeNode> result = new AsyncResult<AbstractTreeNode>(); |
| final AbstractTreeNode toExpand = pathToElement.get(pathToElement.size() - 1); |
| myAbstractTreeBuilder.expand(toExpand, new Runnable() { |
| @Override |
| public void run() { |
| result.setDone(toExpand); |
| } |
| }); |
| |
| return result; |
| } |
| |
| public boolean select(final Object element, final boolean requestFocus) { |
| myAbstractTreeBuilder.getReady(this).doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| expandPathToElement(element).doWhenDone(new Consumer<AbstractTreeNode>() { |
| @Override |
| public void consume(AbstractTreeNode abstractTreeNode) { |
| myAbstractTreeBuilder.select(abstractTreeNode, new Runnable() { |
| @Override |
| public void run() { |
| if (requestFocus) { |
| IdeFocusManager.getInstance(myProject).requestFocus(myAbstractTreeBuilder.getTree(), false); |
| } |
| } |
| }); |
| } |
| }); |
| } |
| }); |
| return true; |
| } |
| |
| private ArrayList<AbstractTreeNode> getPathToElement(Object element) { |
| ArrayList<AbstractTreeNode> result = new ArrayList<AbstractTreeNode>(); |
| final AbstractTreeStructure treeStructure = myAbstractTreeBuilder.getTreeStructure(); |
| if (treeStructure != null) { |
| addToPath((AbstractTreeNode)treeStructure.getRootElement(), element, result, new THashSet<Object>()); |
| } |
| return result; |
| } |
| |
| private static boolean addToPath(AbstractTreeNode<?> rootElement, Object element, ArrayList<AbstractTreeNode> result, Collection<Object> processedElements) { |
| Object value = rootElement.getValue(); |
| if (value instanceof StructureViewTreeElement) { |
| value = ((StructureViewTreeElement) value).getValue(); |
| } |
| if (!processedElements.add(value)){ |
| return false; |
| } |
| |
| if (Comparing.equal(value, element)){ |
| result.add(0, rootElement); |
| return true; |
| } |
| |
| Collection<? extends AbstractTreeNode> children = rootElement.getChildren(); |
| for (AbstractTreeNode child : children) { |
| if (addToPath(child, element, result, processedElements)) { |
| result.add(0, rootElement); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| private static DefaultMutableTreeNode findInChildren(DefaultMutableTreeNode currentTreeNode, AbstractTreeNode topPathElement) { |
| for (int i = 0; i < currentTreeNode.getChildCount(); i++) { |
| TreeNode child = currentTreeNode.getChildAt(i); |
| if (((DefaultMutableTreeNode)child).getUserObject().equals(topPathElement)) |
| { |
| return (DefaultMutableTreeNode)child; |
| } |
| } |
| return null; |
| } |
| |
| private void scrollToSelectedElement() { |
| if (myAutoscrollFeedback) { |
| myAutoscrollFeedback = false; |
| return; |
| } |
| |
| if (!getSettings().AUTOSCROLL_FROM_SOURCE) { |
| return; |
| } |
| |
| myAutoscrollAlarm.cancelAllRequests(); |
| myAutoscrollAlarm.addRequest( |
| new Runnable() { |
| @Override |
| public void run() { |
| if (myAbstractTreeBuilder == null) return; |
| if (UIUtil.isFocusAncestor(StructureViewComponent.this)) return; |
| scrollToSelectedElementInner(); |
| } |
| }, 1000); |
| } |
| |
| private void scrollToSelectedElementInner() { |
| try { |
| PsiDocumentManager.getInstance(myProject).commitAllDocuments(); |
| final Object currentEditorElement = myTreeModel.getCurrentEditorElement(); |
| if (currentEditorElement != null) { |
| select(currentEditorElement, false); |
| } |
| } |
| catch (IndexNotReadyException ignore) { |
| } |
| } |
| |
| @Override |
| public void dispose() { |
| LOG.assertTrue(EventQueue.isDispatchThread(), Thread.currentThread().getName()); |
| myAbstractTreeBuilder = null; |
| // this will also dispose wrapped TreeModel |
| myTreeModelWrapper.dispose(); |
| myFileEditor = null; |
| } |
| |
| public boolean isDisposed() { |
| return myAbstractTreeBuilder == null; |
| } |
| |
| @Override |
| public void centerSelectedRow() { |
| TreePath path = getTree().getSelectionPath(); |
| if (path == null) return; |
| |
| myAutoScrollToSourceHandler.setShouldAutoScroll(false); |
| TreeUtil.showRowCentered(getTree(), getTree().getRowForPath(path), false); |
| myAutoScrollToSourceHandler.setShouldAutoScroll(true); |
| } |
| |
| @Override |
| public void setActionActive(String name, boolean state) { |
| StructureViewFactoryEx.getInstanceEx(myProject).setActiveAction(name, state); |
| rebuild(); |
| TreeUtil.expand(getTree(), 2); |
| } |
| |
| protected void rebuild() { |
| storeState(); |
| ++ourSettingsModificationCount; |
| ((SmartTreeStructure)myAbstractTreeBuilder.getTreeStructure()).rebuildTree(); |
| myAbstractTreeBuilder.updateFromRoot(); |
| restoreState(); |
| } |
| |
| @Override |
| public boolean isActionActive(String name) { |
| return !myProject.isDisposed() && StructureViewFactoryEx.getInstanceEx(myProject).isActionActive(name); |
| } |
| |
| public AbstractTreeStructure getTreeStructure() { |
| return myAbstractTreeBuilder.getTreeStructure(); |
| } |
| |
| public JTree getTree() { |
| return myAbstractTreeBuilder.getTree(); |
| } |
| |
| public AbstractTreeBuilder getTreeBuilder() { |
| return myAbstractTreeBuilder; |
| } |
| |
| //public void setTreeBuilder(AbstractTreeBuilder treeBuilder) { |
| // myAbstractTreeBuilder = treeBuilder; |
| //} |
| |
| private final class MyAutoScrollToSourceHandler extends AutoScrollToSourceHandler { |
| private boolean myShouldAutoScroll = true; |
| |
| public void setShouldAutoScroll(boolean shouldAutoScroll) { |
| myShouldAutoScroll = shouldAutoScroll; |
| } |
| |
| @Override |
| protected boolean isAutoScrollMode() { |
| return myShouldAutoScroll && !myProject.isDisposed() |
| && getSettings().AUTOSCROLL_MODE; |
| } |
| |
| @Override |
| protected void setAutoScrollMode(boolean state) { |
| getSettings().AUTOSCROLL_MODE = state; |
| } |
| |
| @Override |
| protected void scrollToSource(Component tree) { |
| if (myAbstractTreeBuilder == null) return; |
| myAutoscrollFeedback = true; |
| |
| Navigatable editSourceDescriptor = CommonDataKeys.NAVIGATABLE.getData(DataManager.getInstance().getDataContext(getTree())); |
| if (myFileEditor != null && editSourceDescriptor != null && editSourceDescriptor.canNavigateToSource()) { |
| editSourceDescriptor.navigate(false); |
| } |
| } |
| } |
| |
| private class MyAutoScrollFromSourceHandler extends AutoScrollFromSourceHandler { |
| private FileEditorPositionListener myFileEditorPositionListener; |
| |
| private MyAutoScrollFromSourceHandler(Project project, Disposable parentDisposable) { |
| super(project, getTree(), parentDisposable); |
| } |
| |
| @Override |
| protected void selectElementFromEditor(@NotNull FileEditor editor) { |
| } |
| |
| @Override |
| public void install() { |
| addEditorCaretListener(); |
| } |
| |
| @Override |
| public void dispose() { |
| myTreeModel.removeEditorPositionListener(myFileEditorPositionListener); |
| } |
| |
| private void addEditorCaretListener() { |
| myFileEditorPositionListener = new FileEditorPositionListener() { |
| @Override |
| public void onCurrentElementChanged() { |
| scrollToSelectedElement(); |
| } |
| }; |
| myTreeModel.addEditorPositionListener(myFileEditorPositionListener); |
| |
| if (isAutoScrollEnabled()) { |
| //otherwise on any tab switching selection will be staying at the top file node until we made a first caret move |
| scrollToSelectedElement(); |
| } |
| } |
| |
| @Override |
| protected boolean isAutoScrollEnabled() { |
| return getSettings().AUTOSCROLL_FROM_SOURCE; |
| } |
| |
| @Override |
| protected void setAutoScrollEnabled(boolean state) { |
| getSettings().AUTOSCROLL_FROM_SOURCE = state; |
| final FileEditor[] selectedEditors = FileEditorManager.getInstance(myProject).getSelectedEditors(); |
| if (selectedEditors.length > 0 && state) { |
| scrollToSelectedElementInner(); |
| } |
| } |
| } |
| |
| @Override |
| public Object getData(String dataId) { |
| if (CommonDataKeys.PSI_ELEMENT.is(dataId)) { |
| TreePath path = getSelectedUniquePath(); |
| if (path == null) return null; |
| DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); |
| Object element = getNodeValue(node); |
| if (element instanceof StructureViewTreeElement) { |
| element = ((StructureViewTreeElement)element).getValue(); |
| } |
| if (!(element instanceof PsiElement)) return null; |
| if (!((PsiElement)element).isValid()) return null; |
| return element; |
| } |
| if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) { |
| return convertToPsiElementsArray(getSelectedElements()); |
| } |
| if (PlatformDataKeys.FILE_EDITOR.is(dataId)) { |
| return myFileEditor; |
| } |
| if (PlatformDataKeys.CUT_PROVIDER.is(dataId)) { |
| return myCopyPasteDelegator.getCutProvider(); |
| } |
| if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) { |
| return myCopyPasteDelegator.getCopyProvider(); |
| } |
| if (PlatformDataKeys.PASTE_PROVIDER.is(dataId)) { |
| return myCopyPasteDelegator.getPasteProvider(); |
| } |
| if (CommonDataKeys.NAVIGATABLE.is(dataId)) { |
| Object[] selectedElements = getSelectedTreeElements(); |
| if (selectedElements == null || selectedElements.length == 0) return null; |
| if (selectedElements[0] instanceof Navigatable) { |
| return selectedElements[0]; |
| } |
| } |
| if (PlatformDataKeys.HELP_ID.is(dataId)) { |
| return getHelpID(); |
| } |
| if (CommonDataKeys.PROJECT.is(dataId)) { |
| return myProject; |
| } |
| return super.getData(dataId); |
| } |
| |
| @Nullable |
| private static PsiElement[] convertToPsiElementsArray(final Object[] selectedElements) { |
| if (selectedElements == null) return null; |
| ArrayList<PsiElement> psiElements = new ArrayList<PsiElement>(); |
| for (Object selectedElement : selectedElements) { |
| if (selectedElement instanceof PsiElement && ((PsiElement)selectedElement).isValid()) { |
| psiElements.add((PsiElement)selectedElement); |
| } |
| } |
| return PsiUtilCore.toPsiElementArray(psiElements); |
| } |
| |
| @Nullable |
| private TreePath getSelectedUniquePath() { |
| JTree tree = getTree(); |
| if (tree == null) return null; |
| TreePath[] paths = tree.getSelectionPaths(); |
| return paths == null || paths.length != 1 ? null : paths[0]; |
| } |
| |
| @Override |
| @NotNull |
| public StructureViewModel getTreeModel() { |
| return myTreeModel; |
| } |
| |
| @Override |
| public boolean navigateToSelectedElement(boolean requestFocus) { |
| return select(myTreeModel.getCurrentEditorElement(), requestFocus); |
| } |
| |
| public void doUpdate() { |
| assert ApplicationManager.getApplication().isUnitTestMode(); |
| myAbstractTreeBuilder.queueUpdate(true); |
| } |
| |
| //todo [kirillk] dirty hack for discovering invalid psi elements, to delegate it to a proper place after 8.1 |
| public static boolean isValid(Object treeElement) { |
| if (treeElement instanceof StructureViewTreeElementWrapper) { |
| final StructureViewTreeElementWrapper wrapper = (StructureViewTreeElementWrapper)treeElement; |
| if (wrapper.getValue() instanceof PsiTreeElementBase) { |
| final PsiTreeElementBase psiNode = (PsiTreeElementBase)wrapper.getValue(); |
| return psiNode.isValid(); |
| } |
| } |
| return true; |
| } |
| |
| public static class StructureViewTreeElementWrapper extends TreeElementWrapper implements NodeDescriptorProvidingKey { |
| private long childrenStamp = -1; |
| private long modificationCountForChildren = ourSettingsModificationCount; |
| |
| public StructureViewTreeElementWrapper(Project project, TreeElement value, TreeModel treeModel) { |
| super(project, value, treeModel); |
| } |
| |
| @Override |
| @NotNull |
| public Object getKey() { |
| StructureViewTreeElement element = (StructureViewTreeElement)getValue(); |
| if (element instanceof NodeDescriptorProvidingKey) return ((NodeDescriptorProvidingKey)element).getKey(); |
| Object value = element.getValue(); |
| return value == null ? this : value; |
| } |
| |
| |
| |
| @Override |
| @NotNull |
| public Collection<AbstractTreeNode> getChildren() { |
| if (ourSettingsModificationCount != modificationCountForChildren) { |
| resetChildren(); |
| modificationCountForChildren = ourSettingsModificationCount; |
| } |
| |
| final Object o = unwrapValue(getValue()); |
| long currentStamp; |
| if (o instanceof PsiElement && |
| ((PsiElement)o).getNode() instanceof CompositeElement && |
| childrenStamp != (currentStamp = ((CompositeElement)((PsiElement)o).getNode()).getModificationCount()) || |
| o instanceof ModificationTracker && |
| childrenStamp != (currentStamp = ((ModificationTracker)o).getModificationCount()) |
| ) { |
| resetChildren(); |
| childrenStamp = currentStamp; |
| } |
| try { |
| return super.getChildren(); |
| } |
| catch (IndexNotReadyException ignore) { |
| return Collections.emptyList(); |
| } |
| } |
| |
| @Override |
| public boolean isAlwaysShowPlus() { |
| StructureViewModel.ElementInfoProvider elementInfoProvider = getElementInfoProvider(); |
| return elementInfoProvider == null || elementInfoProvider.isAlwaysShowsPlus((StructureViewTreeElement)getValue()); |
| } |
| |
| @Override |
| public boolean isAlwaysLeaf() { |
| StructureViewModel.ElementInfoProvider elementInfoProvider = getElementInfoProvider(); |
| return elementInfoProvider != null && elementInfoProvider.isAlwaysLeaf((StructureViewTreeElement)getValue()); |
| } |
| |
| @Nullable |
| private StructureViewModel.ElementInfoProvider getElementInfoProvider() { |
| if (myTreeModel instanceof StructureViewModel.ElementInfoProvider) { |
| return (StructureViewModel.ElementInfoProvider)myTreeModel; |
| } |
| if (myTreeModel instanceof TreeModelWrapper) { |
| StructureViewModel model = ((TreeModelWrapper)myTreeModel).getModel(); |
| if (model instanceof StructureViewModel.ElementInfoProvider) { |
| return (StructureViewModel.ElementInfoProvider)model; |
| } |
| } |
| |
| return null; |
| } |
| |
| @Override |
| protected TreeElementWrapper createChildNode(@NotNull final TreeElement child) { |
| return new StructureViewTreeElementWrapper(myProject, child, myTreeModel); |
| } |
| |
| @Override |
| protected GroupWrapper createGroupWrapper(final Project project, @NotNull Group group, final TreeModel treeModel) { |
| return new StructureViewGroup(project, group, treeModel); |
| } |
| |
| public boolean equals(Object o) { |
| if (o instanceof StructureViewTreeElementWrapper) { |
| return Comparing.equal( |
| unwrapValue(getValue()), |
| unwrapValue(((StructureViewTreeElementWrapper)o).getValue()) |
| ); |
| } else if (o instanceof StructureViewTreeElement) { |
| return Comparing.equal( |
| unwrapValue(getValue()), |
| ((StructureViewTreeElement)o).getValue() |
| ); |
| } |
| return false; |
| } |
| |
| private static Object unwrapValue(Object o) { |
| if (o instanceof StructureViewTreeElement) { |
| return ((StructureViewTreeElement)o).getValue(); |
| } |
| return o; |
| } |
| |
| public int hashCode() { |
| final Object o = unwrapValue(getValue()); |
| |
| return o != null ? o.hashCode() : 0; |
| } |
| |
| private class StructureViewGroup extends GroupWrapper { |
| public StructureViewGroup(Project project, Group group, TreeModel treeModel) { |
| super(project, group, treeModel); |
| } |
| |
| @Override |
| protected TreeElementWrapper createChildNode(@NotNull TreeElement child) { |
| return new StructureViewTreeElementWrapper(getProject(), child, myTreeModel); |
| } |
| |
| |
| @Override |
| protected GroupWrapper createGroupWrapper(Project project, @NotNull Group group, TreeModel treeModel) { |
| return new StructureViewGroup(project, group, treeModel); |
| } |
| |
| @Override |
| public boolean isAlwaysShowPlus() { |
| return true; |
| } |
| } |
| } |
| |
| public String getHelpID() { |
| return ourHelpID; |
| } |
| |
| @Override |
| public Dimension getCurrentSize() { |
| return getTree().getSize(); |
| } |
| |
| @Override |
| public void setReferenceSizeWhileInitializing(Dimension size) { |
| _setRefSize(size); |
| |
| if (size != null) { |
| myAbstractTreeBuilder.getReady(this).doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| _setRefSize(null); |
| } |
| }); |
| } |
| } |
| |
| private void _setRefSize(Dimension size) { |
| JTree tree = getTree(); |
| tree.setPreferredSize(size); |
| tree.setMinimumSize(size); |
| tree.setMaximumSize(size); |
| |
| tree.revalidate(); |
| tree.repaint(); |
| } |
| } |