blob: 122c09668f8760bd6bace8fc514ebabc0061a81d [file] [log] [blame]
/*
* Copyright 2000-2009 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.errorTreeView;
import com.intellij.icons.AllIcons;
import com.intellij.ide.*;
import com.intellij.ide.actions.*;
import com.intellij.ide.errorTreeView.impl.ErrorTreeViewConfiguration;
import com.intellij.ide.errorTreeView.impl.ErrorViewTextExporter;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.Navigatable;
import com.intellij.ui.AutoScrollToSourceHandler;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.PopupHandler;
import com.intellij.ui.SideBorder;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.MessageView;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.Alarm;
import com.intellij.util.EditSourceOnDoubleClickHandler;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.MutableErrorTreeView;
import com.intellij.util.ui.StatusText;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.tree.TreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Collections;
import java.util.List;
public class NewErrorTreeViewPanel extends JPanel implements DataProvider, OccurenceNavigator, MutableErrorTreeView, CopyProvider {
protected static final Logger LOG = Logger.getInstance("#com.intellij.ide.errorTreeView.NewErrorTreeViewPanel");
private volatile String myProgressText = "";
private volatile float myFraction = 0.0f;
private final boolean myCreateExitAction;
private final ErrorViewStructure myErrorViewStructure;
private final ErrorViewTreeBuilder myBuilder;
private final Alarm myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
private volatile boolean myIsDisposed = false;
private final ErrorTreeViewConfiguration myConfiguration;
public interface ProcessController {
void stopProcess();
boolean isProcessStopped();
}
private ActionToolbar myLeftToolbar;
private ActionToolbar myRightToolbar;
private final TreeExpander myTreeExpander = new MyTreeExpander();
private final ExporterToTextFile myExporterToTextFile;
protected Project myProject;
private final String myHelpId;
protected Tree myTree;
private final JPanel myMessagePanel;
private ProcessController myProcessController;
private JLabel myProgressLabel;
private JPanel myProgressPanel;
private final AutoScrollToSourceHandler myAutoScrollToSourceHandler;
private final MyOccurrenceNavigatorSupport myOccurrenceNavigatorSupport;
public NewErrorTreeViewPanel(Project project, String helpId) {
this(project, helpId, true);
}
public NewErrorTreeViewPanel(Project project, String helpId, boolean createExitAction) {
this(project, helpId, createExitAction, true);
}
public NewErrorTreeViewPanel(Project project, String helpId, boolean createExitAction, boolean createToolbar) {
this(project, helpId, createExitAction, createToolbar, null);
}
public NewErrorTreeViewPanel(Project project, String helpId, boolean createExitAction, boolean createToolbar, @Nullable Runnable rerunAction) {
myProject = project;
myHelpId = helpId;
myCreateExitAction = createExitAction;
myConfiguration = ErrorTreeViewConfiguration.getInstance(project);
setLayout(new BorderLayout());
myAutoScrollToSourceHandler = new AutoScrollToSourceHandler() {
@Override
protected boolean isAutoScrollMode() {
return myConfiguration.isAutoscrollToSource();
}
@Override
protected void setAutoScrollMode(boolean state) {
myConfiguration.setAutoscrollToSource(state);
}
};
myMessagePanel = new JPanel(new BorderLayout());
myErrorViewStructure = new ErrorViewStructure(project, canHideWarnings());
DefaultMutableTreeNode root = new DefaultMutableTreeNode();
root.setUserObject(myErrorViewStructure.createDescriptor(myErrorViewStructure.getRootElement(), null));
final DefaultTreeModel treeModel = new DefaultTreeModel(root);
myTree = new Tree(treeModel) {
@Override
public void setRowHeight(int i) {
super.setRowHeight(0);
// this is needed in order to make UI calculate the height for each particular row
}
};
myBuilder = new ErrorViewTreeBuilder(myTree, treeModel, myErrorViewStructure);
myExporterToTextFile = new ErrorViewTextExporter(myErrorViewStructure);
myOccurrenceNavigatorSupport = new MyOccurrenceNavigatorSupport(myTree);
myAutoScrollToSourceHandler.install(myTree);
TreeUtil.installActions(myTree);
UIUtil.setLineStyleAngled(myTree);
myTree.setRootVisible(false);
myTree.setShowsRootHandles(true);
myTree.setLargeModel(true);
JScrollPane scrollPane = NewErrorTreeRenderer.install(myTree);
scrollPane.setBorder(IdeBorderFactory.createBorder(SideBorder.LEFT));
myMessagePanel.add(scrollPane, BorderLayout.CENTER);
if (createToolbar) {
add(createToolbarPanel(rerunAction), BorderLayout.WEST);
}
add(myMessagePanel, BorderLayout.CENTER);
myTree.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
navigateToSource(false);
}
}
});
myTree.addMouseListener(new PopupHandler() {
@Override
public void invokePopup(Component comp, int x, int y) {
popupInvoked(comp, x, y);
}
});
EditSourceOnDoubleClickHandler.install(myTree);
}
@Override
public void dispose() {
myIsDisposed = true;
myErrorViewStructure.clear();
myUpdateAlarm.cancelAllRequests();
Disposer.dispose(myUpdateAlarm);
Disposer.dispose(myBuilder);
}
@Override
public void performCopy(@NotNull DataContext dataContext) {
List<ErrorTreeNodeDescriptor> descriptors = getSelectedNodeDescriptors();
if (!descriptors.isEmpty()) {
CopyPasteManager.getInstance().setContents(new StringSelection(StringUtil.join(descriptors, new Function<ErrorTreeNodeDescriptor, String>() {
@Override
public String fun(ErrorTreeNodeDescriptor descriptor) {
ErrorTreeElement element = descriptor.getElement();
return NewErrorTreeRenderer.calcPrefix(element) + StringUtil.join(element.getText(), "\n");
}
}, "\n")));
}
}
@Override
public boolean isCopyEnabled(@NotNull DataContext dataContext) {
return !getSelectedNodeDescriptors().isEmpty();
}
@Override
public boolean isCopyVisible(@NotNull DataContext dataContext) {
return true;
}
@NotNull public StatusText getEmptyText() {
return myTree.getEmptyText();
}
@Override
public Object getData(String dataId) {
if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) {
return this;
}
if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
final NavigatableMessageElement selectedMessageElement = getSelectedMessageElement();
return selectedMessageElement != null ? selectedMessageElement.getNavigatable() : null;
}
else if (PlatformDataKeys.HELP_ID.is(dataId)) {
return myHelpId;
}
else if (PlatformDataKeys.TREE_EXPANDER.is(dataId)) {
return myTreeExpander;
}
else if (PlatformDataKeys.EXPORTER_TO_TEXT_FILE.is(dataId)) {
return myExporterToTextFile;
}
else if (CURRENT_EXCEPTION_DATA_KEY.is(dataId)) {
NavigatableMessageElement selectedMessageElement = getSelectedMessageElement();
return selectedMessageElement != null ? selectedMessageElement.getData() : null;
}
return null;
}
public void selectFirstMessage() {
final ErrorTreeElement firstError = myErrorViewStructure.getFirstMessage(ErrorTreeElementKind.ERROR);
if (firstError != null) {
selectElement(firstError, new Runnable() {
@Override
public void run() {
if (shouldShowFirstErrorInEditor()) {
navigateToSource(false);
}
}
});
}
else {
ErrorTreeElement firstWarning = myErrorViewStructure.getFirstMessage(ErrorTreeElementKind.WARNING);
if (firstWarning == null) firstWarning = myErrorViewStructure.getFirstMessage(ErrorTreeElementKind.NOTE);
if (firstWarning != null) {
selectElement(firstWarning, null);
}
else {
TreeUtil.selectFirstNode(myTree);
}
}
}
private void selectElement(final ErrorTreeElement element, final Runnable onDone) {
myBuilder.select(element, onDone);
}
protected boolean shouldShowFirstErrorInEditor() {
return false;
}
public void updateTree() {
if (!myIsDisposed) {
myBuilder.updateTree();
}
}
@Override
public void addMessage(int type, @NotNull String[] text, @Nullable VirtualFile file, int line, int column, @Nullable Object data) {
addMessage(type, text, null, file, line, column, data);
}
@Override
public void addMessage(int type,
@NotNull String[] text,
@Nullable VirtualFile underFileGroup,
@Nullable VirtualFile file,
int line,
int column,
@Nullable Object data) {
if (myIsDisposed) {
return;
}
myErrorViewStructure.addMessage(ErrorTreeElementKind.convertMessageFromCompilerErrorType(type), text, underFileGroup, file, line, column, data);
myBuilder.updateTree();
}
@Override
public void addMessage(int type,
@NotNull String[] text,
@Nullable String groupName,
@NotNull Navigatable navigatable,
@Nullable String exportTextPrefix,
@Nullable String rendererTextPrefix,
@Nullable Object data) {
if (myIsDisposed) {
return;
}
VirtualFile file = data instanceof VirtualFile ? (VirtualFile)data : null;
if (file == null && navigatable instanceof OpenFileDescriptor) {
file = ((OpenFileDescriptor)navigatable).getFile();
}
final String exportPrefix = exportTextPrefix == null ? "" : exportTextPrefix;
final String renderPrefix = rendererTextPrefix == null ? "" : rendererTextPrefix;
final ErrorTreeElementKind kind = ErrorTreeElementKind.convertMessageFromCompilerErrorType(type);
myErrorViewStructure.addNavigatableMessage(groupName, navigatable, kind, text, data, exportPrefix, renderPrefix, file);
myBuilder.updateTree();
}
public ErrorViewStructure getErrorViewStructure() {
return myErrorViewStructure;
}
public static String createExportPrefix(int line) {
return line < 0 ? "" : IdeBundle.message("errortree.prefix.line", line);
}
public static String createRendererPrefix(int line, int column) {
if (line < 0) return "";
if (column < 0) return "(" + line + ")";
return "(" + line + ", " + column + ")";
}
@Override
@NotNull
public JComponent getComponent() {
return this;
}
@Nullable
private NavigatableMessageElement getSelectedMessageElement() {
final ErrorTreeElement selectedElement = getSelectedErrorTreeElement();
return selectedElement instanceof NavigatableMessageElement ? (NavigatableMessageElement)selectedElement : null;
}
@Nullable
public ErrorTreeElement getSelectedErrorTreeElement() {
final ErrorTreeNodeDescriptor treeNodeDescriptor = getSelectedNodeDescriptor();
return treeNodeDescriptor == null? null : treeNodeDescriptor.getElement();
}
@Nullable
public ErrorTreeNodeDescriptor getSelectedNodeDescriptor() {
List<ErrorTreeNodeDescriptor> descriptors = getSelectedNodeDescriptors();
return descriptors.size() == 1 ? descriptors.get(0) : null;
}
private List<ErrorTreeNodeDescriptor> getSelectedNodeDescriptors() {
TreePath[] paths = myTree.getSelectionPaths();
if (paths == null) {
return Collections.emptyList();
}
List<ErrorTreeNodeDescriptor> result = ContainerUtil.newArrayList();
for (TreePath path : paths) {
DefaultMutableTreeNode lastPathNode = (DefaultMutableTreeNode)path.getLastPathComponent();
Object userObject = lastPathNode.getUserObject();
if (userObject instanceof ErrorTreeNodeDescriptor) {
result.add((ErrorTreeNodeDescriptor)userObject);
}
}
return result;
}
private void navigateToSource(final boolean focusEditor) {
NavigatableMessageElement element = getSelectedMessageElement();
if (element == null) {
return;
}
final Navigatable navigatable = element.getNavigatable();
if (navigatable.canNavigate()) {
navigatable.navigate(focusEditor);
}
}
public static String getQualifiedName(final VirtualFile file) {
return file.getPresentableUrl();
}
private void popupInvoked(Component component, int x, int y) {
final TreePath path = myTree.getLeadSelectionPath();
if (path == null) {
return;
}
DefaultActionGroup group = new DefaultActionGroup();
if (getData(CommonDataKeys.NAVIGATABLE.getName()) != null) {
group.add(ActionManager.getInstance().getAction(IdeActions.ACTION_EDIT_SOURCE));
}
group.add(ActionManager.getInstance().getAction(IdeActions.ACTION_COPY));
addExtraPopupMenuActions(group);
ActionPopupMenu menu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.COMPILER_MESSAGES_POPUP, group);
menu.getComponent().show(component, x, y);
}
protected void addExtraPopupMenuActions(DefaultActionGroup group) {
}
public void setProcessController(ProcessController controller) {
myProcessController = controller;
}
public void stopProcess() {
myProcessController.stopProcess();
}
public boolean canControlProcess() {
return myProcessController != null;
}
public boolean isProcessStopped() {
return myProcessController.isProcessStopped();
}
public void close() {
MessageView messageView = MessageView.SERVICE.getInstance(myProject);
Content content = messageView.getContentManager().getContent(this);
if (content != null) {
messageView.getContentManager().removeContent(content, true);
}
}
public void setProgress(final String s, float fraction) {
initProgressPanel();
myProgressText = s;
myFraction = fraction;
updateProgress();
}
public void setProgressText(final String s) {
initProgressPanel();
myProgressText = s;
updateProgress();
}
public void setFraction(final float fraction) {
initProgressPanel();
myFraction = fraction;
updateProgress();
}
public void clearProgressData() {
if (myProgressPanel != null) {
myProgressText = " ";
myFraction = 0.0f;
updateProgress();
}
}
private void updateProgress() {
if (myIsDisposed) {
return;
}
myUpdateAlarm.cancelAllRequests();
myUpdateAlarm.addRequest(new Runnable() {
@Override
public void run() {
final float fraction = myFraction;
final String text = myProgressText;
if (fraction > 0.0f) {
myProgressLabel.setText((int)(fraction * 100 + 0.5) + "% " + text);
}
else {
myProgressLabel.setText(text);
}
}
}, 50, ModalityState.NON_MODAL);
}
private void initProgressPanel() {
if (myProgressPanel == null) {
myProgressPanel = new JPanel(new GridLayout(1, 2));
myProgressLabel = new JLabel();
myProgressPanel.add(myProgressLabel);
//JLabel secondLabel = new JLabel();
//myProgressPanel.add(secondLabel);
myMessagePanel.add(myProgressPanel, BorderLayout.SOUTH);
myMessagePanel.validate();
}
}
public void collapseAll() {
TreeUtil.collapseAll(myTree, 2);
}
public void expandAll() {
TreePath[] selectionPaths = myTree.getSelectionPaths();
TreePath leadSelectionPath = myTree.getLeadSelectionPath();
int row = 0;
while (row < myTree.getRowCount()) {
myTree.expandRow(row);
row++;
}
if (selectionPaths != null) {
// restore selection
myTree.setSelectionPaths(selectionPaths);
}
if (leadSelectionPath != null) {
// scroll to lead selection path
myTree.scrollPathToVisible(leadSelectionPath);
}
}
private JPanel createToolbarPanel(@Nullable Runnable rerunAction) {
AnAction closeMessageViewAction = new CloseTabToolbarAction() {
@Override
public void actionPerformed(AnActionEvent e) {
close();
}
};
DefaultActionGroup leftUpdateableActionGroup = new DefaultActionGroup();
if (rerunAction != null) {
leftUpdateableActionGroup.add(new RerunAction(rerunAction, closeMessageViewAction));
}
leftUpdateableActionGroup.add(new StopAction());
if (myCreateExitAction) {
leftUpdateableActionGroup.add(closeMessageViewAction);
}
leftUpdateableActionGroup.add(new PreviousOccurenceToolbarAction(this));
leftUpdateableActionGroup.add(new NextOccurenceToolbarAction(this));
leftUpdateableActionGroup.add(new ExportToTextFileToolbarAction(myExporterToTextFile));
leftUpdateableActionGroup.add(new ContextHelpAction(myHelpId));
DefaultActionGroup rightUpdateableActionGroup = new DefaultActionGroup();
fillRightToolbarGroup(rightUpdateableActionGroup);
JPanel toolbarPanel = new JPanel(new GridLayout(1, 2));
final ActionManager actionManager = ActionManager.getInstance();
myLeftToolbar =
actionManager.createActionToolbar(ActionPlaces.COMPILER_MESSAGES_TOOLBAR, leftUpdateableActionGroup, false);
toolbarPanel.add(myLeftToolbar.getComponent());
myRightToolbar =
actionManager.createActionToolbar(ActionPlaces.COMPILER_MESSAGES_TOOLBAR, rightUpdateableActionGroup, false);
toolbarPanel.add(myRightToolbar.getComponent());
return toolbarPanel;
}
protected void fillRightToolbarGroup(DefaultActionGroup group) {
group.add(CommonActionsManager.getInstance().createExpandAllAction(myTreeExpander, this));
group.add(CommonActionsManager.getInstance().createCollapseAllAction(myTreeExpander, this));
if (canHideWarnings()) {
group.add(new HideWarningsAction());
}
group.add(myAutoScrollToSourceHandler.createToggleAction());
}
@Override
public OccurenceInfo goNextOccurence() {
return myOccurrenceNavigatorSupport.goNextOccurence();
}
@Override
public OccurenceInfo goPreviousOccurence() {
return myOccurrenceNavigatorSupport.goPreviousOccurence();
}
@Override
public boolean hasNextOccurence() {
return myOccurrenceNavigatorSupport.hasNextOccurence();
}
@Override
public boolean hasPreviousOccurence() {
return myOccurrenceNavigatorSupport.hasPreviousOccurence();
}
@Override
public String getNextOccurenceActionName() {
return myOccurrenceNavigatorSupport.getNextOccurenceActionName();
}
@Override
public String getPreviousOccurenceActionName() {
return myOccurrenceNavigatorSupport.getPreviousOccurenceActionName();
}
private class RerunAction extends DumbAwareAction {
private final Runnable myRerunAction;
private final AnAction myCloseAction;
public RerunAction(@NotNull Runnable rerunAction, @NotNull AnAction closeAction) {
super(IdeBundle.message("action.refresh"), null, AllIcons.Actions.Rerun);
myRerunAction = rerunAction;
myCloseAction = closeAction;
}
@Override
public void actionPerformed(AnActionEvent e) {
myCloseAction.actionPerformed(e);
myRerunAction.run();
}
@Override
public void update(AnActionEvent event) {
final Presentation presentation = event.getPresentation();
presentation.setEnabled(canControlProcess() && isProcessStopped());
}
}
private class StopAction extends DumbAwareAction {
public StopAction() {
super(IdeBundle.message("action.stop"), null, AllIcons.Actions.Suspend);
}
@Override
public void actionPerformed(AnActionEvent e) {
if (canControlProcess()) {
stopProcess();
}
myLeftToolbar.updateActionsImmediately();
myRightToolbar.updateActionsImmediately();
}
@Override
public void update(AnActionEvent event) {
Presentation presentation = event.getPresentation();
presentation.setEnabled(canControlProcess() && !isProcessStopped());
presentation.setVisible(canControlProcess());
}
}
protected boolean canHideWarnings() {
return true;
}
private class HideWarningsAction extends ToggleAction implements DumbAware {
public HideWarningsAction() {
super(IdeBundle.message("action.hide.warnings"), null, AllIcons.General.HideWarnings);
}
@Override
public boolean isSelected(AnActionEvent event) {
return isHideWarnings();
}
@Override
public void setSelected(AnActionEvent event, boolean flag) {
if (isHideWarnings() != flag) {
myConfiguration.setHideWarnings(flag);
myBuilder.updateTree();
}
}
}
public boolean isHideWarnings() {
return myConfiguration.isHideWarnings();
}
private class MyTreeExpander implements TreeExpander {
@Override
public void expandAll() {
NewErrorTreeViewPanel.this.expandAll();
}
@Override
public boolean canExpand() {
return true;
}
@Override
public void collapseAll() {
NewErrorTreeViewPanel.this.collapseAll();
}
@Override
public boolean canCollapse() {
return true;
}
}
private static class MyOccurrenceNavigatorSupport extends OccurenceNavigatorSupport {
public MyOccurrenceNavigatorSupport(final Tree tree) {
super(tree);
}
@Override
protected Navigatable createDescriptorForNode(DefaultMutableTreeNode node) {
Object userObject = node.getUserObject();
if (!(userObject instanceof ErrorTreeNodeDescriptor)) {
return null;
}
final ErrorTreeNodeDescriptor descriptor = (ErrorTreeNodeDescriptor)userObject;
final ErrorTreeElement element = descriptor.getElement();
if (element instanceof NavigatableMessageElement) {
return ((NavigatableMessageElement)element).getNavigatable();
}
return null;
}
@Override
public String getNextOccurenceActionName() {
return IdeBundle.message("action.next.message");
}
@Override
public String getPreviousOccurenceActionName() {
return IdeBundle.message("action.previous.message");
}
}
@Override
public List<Object> getGroupChildrenData(final String groupName) {
return myErrorViewStructure.getGroupChildrenData(groupName);
}
@Override
public void removeGroup(final String name) {
myErrorViewStructure.removeGroup(name);
}
@Override
public void addFixedHotfixGroup(String text, List<SimpleErrorData> children) {
myErrorViewStructure.addFixedHotfixGroup(text, children);
}
@Override
public void addHotfixGroup(HotfixData hotfixData, List<SimpleErrorData> children) {
myErrorViewStructure.addHotfixGroup(hotfixData, children, this);
}
@Override
public void reload() {
myBuilder.updateTree();
}
}