blob: 4c7866edd361a9834c301eac73a02228a36faad5 [file] [log] [blame]
/*
* 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.slicer;
import com.intellij.icons.AllIcons;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.actions.CloseTabToolbarAction;
import com.intellij.ide.actions.RefreshAction;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.openapi.wm.ex.ToolWindowManagerEx;
import com.intellij.openapi.wm.ex.ToolWindowManagerListener;
import com.intellij.pom.Navigatable;
import com.intellij.ui.*;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewBundle;
import com.intellij.usages.Usage;
import com.intellij.usages.UsageViewPresentation;
import com.intellij.usages.UsageViewSettings;
import com.intellij.usages.impl.UsagePreviewPanel;
import com.intellij.util.EditSourceOnDoubleClickHandler;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.tree.TreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import javax.swing.*;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author cdr
*/
public abstract class SlicePanel extends JPanel implements TypeSafeDataProvider, Disposable {
private final SliceTreeBuilder myBuilder;
private final JTree myTree;
private final AutoScrollToSourceHandler myAutoScrollToSourceHandler = new AutoScrollToSourceHandler() {
@Override
protected boolean isAutoScrollMode() {
return isAutoScroll();
}
@Override
protected void setAutoScrollMode(final boolean state) {
setAutoScroll(state);
}
};
private UsagePreviewPanel myUsagePreviewPanel;
private final Project myProject;
private boolean isDisposed;
private final ToolWindow myToolWindow;
public SlicePanel(@NotNull final Project project,
boolean dataFlowToThis,
@NotNull SliceNode rootNode,
boolean splitByLeafExpressions,
@NotNull final ToolWindow toolWindow) {
super(new BorderLayout());
myToolWindow = toolWindow;
final ToolWindowManagerListener listener = new ToolWindowManagerListener() {
ToolWindowAnchor myAnchor = toolWindow.getAnchor();
@Override
public void toolWindowRegistered(@NotNull String id) {
}
@Override
public void stateChanged() {
if (!project.isOpen()) return;
if (toolWindow.getAnchor() != myAnchor) {
myAnchor = myToolWindow.getAnchor();
layoutPanel();
}
}
};
ToolWindowManagerEx.getInstanceEx(project).addToolWindowManagerListener(listener);
Disposer.register(this, new Disposable() {
@Override
public void dispose() {
ToolWindowManagerEx.getInstanceEx(project).removeToolWindowManagerListener(listener);
}
});
ApplicationManager.getApplication().assertIsDispatchThread();
myProject = project;
myTree = createTree();
myBuilder = new SliceTreeBuilder(myTree, project, dataFlowToThis, rootNode, splitByLeafExpressions);
myBuilder.setCanYieldUpdate(!ApplicationManager.getApplication().isUnitTestMode());
Disposer.register(this, myBuilder);
myBuilder.addSubtreeToUpdate((DefaultMutableTreeNode)myTree.getModel().getRoot(), new Runnable() {
@Override
public void run() {
if (isDisposed || myBuilder.isDisposed() || myProject.isDisposed()) return;
final SliceNode rootNode = myBuilder.getRootSliceNode();
myBuilder.expand(rootNode, new Runnable() {
@Override
public void run() {
if (isDisposed || myBuilder.isDisposed() || myProject.isDisposed()) return;
myBuilder.select(rootNode.myCachedChildren.get(0)); //first there is ony one child
}
});
treeSelectionChanged();
}
});
layoutPanel();
}
private void layoutPanel() {
if (myUsagePreviewPanel != null) {
Disposer.dispose(myUsagePreviewPanel);
}
removeAll();
JScrollPane pane = ScrollPaneFactory.createScrollPane(myTree);
if (isPreview()) {
pane.setBorder(IdeBorderFactory.createBorder(SideBorder.LEFT | SideBorder.RIGHT));
boolean vertical = myToolWindow.getAnchor() == ToolWindowAnchor.LEFT || myToolWindow.getAnchor() == ToolWindowAnchor.RIGHT;
Splitter splitter = new Splitter(vertical, UsageViewSettings.getInstance().PREVIEW_USAGES_SPLITTER_PROPORTIONS);
splitter.setFirstComponent(pane);
myUsagePreviewPanel = new UsagePreviewPanel(myProject, new UsageViewPresentation());
myUsagePreviewPanel.setBorder(IdeBorderFactory.createBorder(SideBorder.LEFT));
Disposer.register(this, myUsagePreviewPanel);
splitter.setSecondComponent(myUsagePreviewPanel);
add(splitter, BorderLayout.CENTER);
}
else {
pane.setBorder(IdeBorderFactory.createBorder(SideBorder.LEFT));
add(pane, BorderLayout.CENTER);
}
add(createToolbar().getComponent(), BorderLayout.WEST);
myTree.getParent().setBackground(UIManager.getColor("Tree.background"));
revalidate();
}
@Override
public void dispose() {
if (myUsagePreviewPanel != null) {
UsageViewSettings.getInstance().PREVIEW_USAGES_SPLITTER_PROPORTIONS = ((Splitter)myUsagePreviewPanel.getParent()).getProportion();
myUsagePreviewPanel = null;
}
isDisposed = true;
ToolTipManager.sharedInstance().unregisterComponent(myTree);
}
@NotNull
private JTree createTree() {
DefaultMutableTreeNode root = new DefaultMutableTreeNode();
final Tree tree = new Tree(new DefaultTreeModel(root))/* {
@Override
protected void paintComponent(Graphics g) {
DuplicateNodeRenderer.paintDuplicateNodesBackground(g, this);
super.paintComponent(g);
}
}*/;
tree.setOpaque(false);
tree.setToggleClickCount(-1);
SliceUsageCellRenderer renderer = new SliceUsageCellRenderer();
renderer.setOpaque(false);
tree.setCellRenderer(renderer);
UIUtil.setLineStyleAngled(tree);
tree.setRootVisible(false);
tree.setShowsRootHandles(true);
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.setSelectionPath(new TreePath(root.getPath()));
//ActionGroup group = (ActionGroup)ActionManager.getInstance().getAction(IdeActions.GROUP_METHOD_HIERARCHY_POPUP);
//PopupHandler.installPopupHandler(tree, group, ActionPlaces.METHOD_HIERARCHY_VIEW_POPUP, ActionManager.getInstance());
EditSourceOnDoubleClickHandler.install(tree);
new TreeSpeedSearch(tree);
TreeUtil.installActions(tree);
ToolTipManager.sharedInstance().registerComponent(tree);
myAutoScrollToSourceHandler.install(tree);
tree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(final TreeSelectionEvent e) {
treeSelectionChanged();
}
});
tree.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (KeyEvent.VK_ENTER == e.getKeyCode()) {
List<Navigatable> navigatables = getNavigatables();
if (navigatables.isEmpty()) return;
for (Navigatable navigatable : navigatables) {
if (navigatable instanceof AbstractTreeNode && ((AbstractTreeNode)navigatable).getValue() instanceof Usage) {
navigatable = (Usage)((AbstractTreeNode)navigatable).getValue();
}
if (navigatable.canNavigateToSource()) {
navigatable.navigate(false);
if (navigatable instanceof Usage) {
((Usage)navigatable).highlightInEditor();
}
}
}
e.consume();
}
}
});
tree.addTreeWillExpandListener(new TreeWillExpandListener() {
@Override
public void treeWillCollapse(TreeExpansionEvent event) {
}
@Override
public void treeWillExpand(TreeExpansionEvent event) {
TreePath path = event.getPath();
SliceNode node = fromPath(path);
node.calculateDupNode();
}
});
return tree;
}
private void treeSelectionChanged() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (isDisposed) return;
List<UsageInfo> infos = getSelectedUsageInfos();
if (infos != null && myUsagePreviewPanel != null) {
myUsagePreviewPanel.updateLayout(infos);
}
}
});
}
private static SliceNode fromPath(TreePath path) {
Object lastPathComponent = path.getLastPathComponent();
if (lastPathComponent instanceof DefaultMutableTreeNode) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)lastPathComponent;
Object userObject = node.getUserObject();
if (userObject instanceof SliceNode) {
return (SliceNode)userObject;
}
}
return null;
}
@Nullable
private List<UsageInfo> getSelectedUsageInfos() {
TreePath[] paths = myTree.getSelectionPaths();
if (paths == null) return null;
final ArrayList<UsageInfo> result = new ArrayList<UsageInfo>();
for (TreePath path : paths) {
SliceNode sliceNode = fromPath(path);
if (sliceNode != null) {
result.add(sliceNode.getValue().getUsageInfo());
}
}
if (result.isEmpty()) return null;
return result;
}
@Override
public void calcData(DataKey key, DataSink sink) {
if (key == CommonDataKeys.NAVIGATABLE_ARRAY) {
List<Navigatable> navigatables = getNavigatables();
if (!navigatables.isEmpty()) {
sink.put(CommonDataKeys.NAVIGATABLE_ARRAY, navigatables.toArray(new Navigatable[navigatables.size()]));
}
}
}
@NotNull
private List<Navigatable> getNavigatables() {
TreePath[] paths = myTree.getSelectionPaths();
if (paths == null) return Collections.emptyList();
final ArrayList<Navigatable> navigatables = new ArrayList<Navigatable>();
for (TreePath path : paths) {
Object lastPathComponent = path.getLastPathComponent();
if (lastPathComponent instanceof DefaultMutableTreeNode) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)lastPathComponent;
Object userObject = node.getUserObject();
if (userObject instanceof Navigatable) {
navigatables.add((Navigatable)userObject);
}
else if (node instanceof Navigatable) {
navigatables.add((Navigatable)node);
}
}
}
return navigatables;
}
@NotNull
private ActionToolbar createToolbar() {
final DefaultActionGroup actionGroup = new DefaultActionGroup();
actionGroup.add(new MyRefreshAction(myTree));
if (isToShowAutoScrollButton()) {
actionGroup.add(myAutoScrollToSourceHandler.createToggleAction());
}
if (isToShowCloseButton()) {
actionGroup.add(new CloseAction());
}
if (isToShowPreviewButton()) {
actionGroup.add(new ToggleAction(UsageViewBundle.message("preview.usages.action.text"), "preview", AllIcons.Actions.PreviewDetails) {
@Override
public boolean isSelected(AnActionEvent e) {
return isPreview();
}
@Override
public void setSelected(AnActionEvent e, boolean state) {
setPreview(state);
layoutPanel();
}
});
}
if (myBuilder.dataFlowToThis) {
actionGroup.add(new GroupByLeavesAction(myBuilder));
actionGroup.add(new CanItBeNullAction(myBuilder));
}
//actionGroup.add(new ContextHelpAction(HELP_ID));
return ActionManager.getInstance().createActionToolbar(ActionPlaces.TYPE_HIERARCHY_VIEW_TOOLBAR, actionGroup, false);
}
public boolean isToShowAutoScrollButton() {return true;}
public abstract boolean isAutoScroll();
public abstract void setAutoScroll(boolean autoScroll);
public boolean isToShowCloseButton() {return true;}
public boolean isToShowPreviewButton() {return true;}
public abstract boolean isPreview();
public abstract void setPreview(boolean preview);
private class CloseAction extends CloseTabToolbarAction {
@Override
public final void actionPerformed(final AnActionEvent e) {
close();
}
}
protected void close() {
final ProgressIndicator progress = myBuilder.getUi().getProgress();
if (progress != null) {
progress.cancel();
}
}
private final class MyRefreshAction extends RefreshAction {
private MyRefreshAction(JComponent tree) {
super(IdeBundle.message("action.refresh"), IdeBundle.message("action.refresh"), AllIcons.Actions.Refresh);
registerShortcutOn(tree);
}
@Override
public final void actionPerformed(final AnActionEvent e) {
SliceNode rootNode = (SliceNode)myBuilder.getRootNode().getUserObject();
rootNode.setChanged();
myBuilder.addSubtreeToUpdate(myBuilder.getRootNode());
}
@Override
public final void update(final AnActionEvent event) {
final Presentation presentation = event.getPresentation();
presentation.setEnabled(true);
}
}
@TestOnly
public SliceTreeBuilder getBuilder() {
return myBuilder;
}
}