| /* |
| * 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.lang.ant.config.execution; |
| |
| import com.intellij.ide.CopyProvider; |
| import com.intellij.ide.DataManager; |
| import com.intellij.ide.OccurenceNavigator; |
| import com.intellij.ide.OccurenceNavigatorSupport; |
| import com.intellij.lang.ant.AntBundle; |
| import com.intellij.lang.ant.config.AntBuildFile; |
| import com.intellij.lang.ant.config.AntBuildModelBase; |
| import com.intellij.lang.ant.config.AntBuildTargetBase; |
| import com.intellij.lang.ant.config.AntConfigurationBase; |
| import com.intellij.lang.ant.config.impl.BuildTask; |
| import com.intellij.openapi.actionSystem.*; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.fileEditor.OpenFileDescriptor; |
| import com.intellij.openapi.ide.CopyPasteManager; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.Computable; |
| 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.PopupHandler; |
| import com.intellij.ui.treeStructure.Tree; |
| import com.intellij.util.EditSourceOnDoubleClickHandler; |
| import com.intellij.util.OpenSourceUtil; |
| import com.intellij.util.StringBuilderSpinAllocator; |
| import com.intellij.util.ui.tree.TreeUtil; |
| 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.datatransfer.StringSelection; |
| import java.awt.event.KeyAdapter; |
| import java.awt.event.KeyEvent; |
| import java.util.ArrayList; |
| import java.util.Enumeration; |
| import java.util.StringTokenizer; |
| |
| public final class TreeView implements AntOutputView, OccurenceNavigator { |
| private Tree myTree; |
| private DefaultTreeModel myTreeModel; |
| private TreePath myParentPath = null; |
| private final ArrayList<MessageNode> myMessageItems = new ArrayList<MessageNode>(); |
| private final JPanel myPanel; |
| private boolean myActionsEnabled = true; |
| private String myCurrentTaskName; |
| |
| private final Project myProject; |
| private final AntBuildFile myBuildFile; |
| private DefaultMutableTreeNode myStatusNode; |
| private final AutoScrollToSourceHandler myAutoScrollToSourceHandler; |
| private OccurenceNavigatorSupport myOccurenceNavigatorSupport; |
| @NonNls public static final String ROOT_TREE_USER_OBJECT = "root"; |
| @NonNls public static final String JUNIT_TASK_NAME = "junit"; |
| |
| public TreeView(final Project project, final AntBuildFile buildFile) { |
| myProject = project; |
| myBuildFile = buildFile; |
| myAutoScrollToSourceHandler = new AutoScrollToSourceHandler() { |
| protected boolean isAutoScrollMode() { |
| return AntConfigurationBase.getInstance(myProject).isAutoScrollToSource(); |
| } |
| |
| protected void setAutoScrollMode(boolean state) { |
| AntConfigurationBase.getInstance(myProject).setAutoScrollToSource(state); |
| } |
| }; |
| myPanel = createPanel(); |
| } |
| |
| @Override |
| public String getId() { |
| return "_tree_view_"; |
| } |
| |
| public JComponent getComponent() { |
| return myPanel; |
| } |
| |
| private JPanel createPanel() { |
| createModel(); |
| myTree = new MyTree(); |
| myTree.setLineStyleAngled(); |
| myTree.setRootVisible(false); |
| myTree.setShowsRootHandles(true); |
| myTree.updateUI(); |
| myTree.setLargeModel(true); |
| |
| myTree.addKeyListener(new KeyAdapter() { |
| public void keyPressed(KeyEvent e) { |
| if (e.getKeyCode() == KeyEvent.VK_ENTER) { |
| OpenSourceUtil.openSourcesFrom(DataManager.getInstance().getDataContext(myTree), false); |
| } |
| } |
| }); |
| |
| myTree.addMouseListener(new PopupHandler() { |
| public void invokePopup(Component comp, int x, int y) { |
| popupInvoked(comp, x, y); |
| } |
| }); |
| |
| EditSourceOnDoubleClickHandler.install(myTree); |
| |
| myAutoScrollToSourceHandler.install(myTree); |
| |
| myOccurenceNavigatorSupport = new OccurenceNavigatorSupport(myTree) { |
| protected Navigatable createDescriptorForNode(DefaultMutableTreeNode node) { |
| if (!(node instanceof MessageNode)) { |
| return null; |
| } |
| MessageNode messageNode = (MessageNode)node; |
| AntBuildMessageView.MessageType type = messageNode.getType(); |
| |
| if (type != AntBuildMessageView.MessageType.MESSAGE && type != AntBuildMessageView.MessageType.ERROR) { |
| return null; |
| } |
| |
| if (!isValid(messageNode.getFile())) { |
| return null; |
| } |
| |
| return new OpenFileDescriptor(myProject, messageNode.getFile(), messageNode.getOffset()); |
| } |
| |
| @Nullable |
| public String getNextOccurenceActionName() { |
| return AntBundle.message("ant.execution.next.error.warning.action.name"); |
| } |
| |
| @Nullable |
| public String getPreviousOccurenceActionName() { |
| return AntBundle.message("ant.execution.previous.error.warning.action.name"); |
| } |
| }; |
| |
| JPanel panel = new JPanel(new BorderLayout()); |
| |
| JScrollPane scrollPane = MessageTreeRenderer.install(myTree); |
| panel.add(scrollPane, BorderLayout.CENTER); |
| return panel; |
| } |
| |
| private void createModel() { |
| DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(ROOT_TREE_USER_OBJECT); |
| myTreeModel = new DefaultTreeModel(rootNode); |
| myParentPath = new TreePath(rootNode); |
| } |
| |
| public void setActionsEnabled(boolean actionsEnabled) { |
| myActionsEnabled = actionsEnabled; |
| if (actionsEnabled) { |
| myTreeModel.reload(); |
| } |
| } |
| |
| public Object addMessage(AntMessage message) { |
| MessageNode messageNode = createMessageNode(message); |
| |
| MutableTreeNode parentNode = (MutableTreeNode)myParentPath.getLastPathComponent(); |
| myTreeModel.insertNodeInto(messageNode, parentNode, parentNode.getChildCount()); |
| myMessageItems.add(messageNode); |
| |
| handleExpansion(); |
| return messageNode; |
| } |
| |
| public void addMessages(AntMessage[] messages) { |
| DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)myParentPath.getLastPathComponent(); |
| int[] indices = new int[messages.length]; |
| for (int i = 0; i < messages.length; i++) { |
| AntMessage message = messages[i]; |
| MessageNode messageNode = createMessageNode(message); |
| indices[i] = parentNode.getChildCount(); |
| parentNode.insert(messageNode, indices[i]); |
| myMessageItems.add(messageNode); |
| } |
| myTreeModel.nodesWereInserted(parentNode, indices); |
| handleExpansion(); |
| } |
| |
| private MessageNode createMessageNode(AntMessage message) { |
| String text = message.getText(); |
| |
| boolean allowToShowPosition = true; |
| if (JUNIT_TASK_NAME.equals(myCurrentTaskName)) { |
| HyperlinkUtil.PlaceInfo info = HyperlinkUtil.parseJUnitMessage(myProject, text); |
| if (info != null) { |
| message = new AntMessage(message.getType(), message.getPriority(), text, info.getFile(), 1, 1); |
| allowToShowPosition = false; |
| } |
| } |
| |
| return new MessageNode(message, myProject, allowToShowPosition); |
| } |
| |
| private void handleExpansion() { |
| if (myActionsEnabled && !myTree.hasBeenExpanded(myParentPath)) { |
| myTree.expandPath(myParentPath); |
| } |
| } |
| |
| void scrollToLastMessage() { |
| if (myTree == null) return; |
| int count = myTree.getRowCount(); |
| if (count > 0) { |
| int row = count - 1; |
| TreeUtil.selectPath(myTree, myTree.getPathForRow(row)); |
| } |
| } |
| |
| public void addJavacMessage(AntMessage message, String url) { |
| final StringBuilder builder = StringBuilderSpinAllocator.alloc(); |
| try { |
| final VirtualFile file = message.getFile(); |
| if (message.getLine() > 0) { |
| if (file != null) { |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| public void run() { |
| String presentableUrl = file.getPresentableUrl(); |
| builder.append(presentableUrl); |
| builder.append(' '); |
| } |
| }); |
| } |
| else if (url != null) { |
| builder.append(url); |
| builder.append(' '); |
| } |
| builder.append('('); |
| builder.append(message.getLine()); |
| builder.append(':'); |
| builder.append(message.getColumn()); |
| builder.append(") "); |
| } |
| addJavacMessageImpl(new AntMessage(message.getType(), message.getPriority(), builder.toString() + message.getText(), |
| message.getFile(), message.getLine(), message.getColumn())); |
| } |
| finally { |
| StringBuilderSpinAllocator.dispose(builder); |
| } |
| } |
| |
| private void addJavacMessageImpl(AntMessage message) { |
| MutableTreeNode parentNode = (MutableTreeNode)myParentPath.getLastPathComponent(); |
| MessageNode messageNode = new MessageNode(message, myProject, false); |
| |
| myTreeModel.insertNodeInto(messageNode, parentNode, parentNode.getChildCount()); |
| myMessageItems.add(messageNode); |
| |
| handleExpansion(); |
| } |
| |
| public void addException(AntMessage exception, boolean showFullTrace) { |
| MessageNode exceptionRootNode = null; |
| |
| StringTokenizer tokenizer = new StringTokenizer(exception.getText(), "\r\n"); |
| while (tokenizer.hasMoreElements()) { |
| String line = (String)tokenizer.nextElement(); |
| if (exceptionRootNode == null) { |
| AntMessage newMessage = new AntMessage(exception.getType(), exception.getPriority(), line, exception.getFile(), exception.getLine(), |
| exception.getColumn()); |
| exceptionRootNode = new MessageNode(newMessage, myProject, true); |
| myMessageItems.add(exceptionRootNode); |
| } |
| else if (showFullTrace) { |
| if (StringUtil.startsWithChar(line, '\t')) { |
| line = line.substring(1); |
| } |
| |
| HyperlinkUtil.PlaceInfo info = HyperlinkUtil.parseStackLine(myProject, '\t' + line); |
| VirtualFile file = info != null ? info.getFile() : null; |
| int lineNumber = info != null ? info.getLine() : 0; |
| int column = info != null ? info.getColumn() : 1; |
| AntMessage newMessage = new AntMessage(exception.getType(), exception.getPriority(), line, file, lineNumber, column); |
| MessageNode child = new MessageNode(newMessage, myProject, false); |
| exceptionRootNode.add(child); |
| myMessageItems.add(child); |
| } |
| } |
| if (exceptionRootNode == null) return; |
| |
| MutableTreeNode parentNode = (MutableTreeNode)myParentPath.getLastPathComponent(); |
| myTreeModel.insertNodeInto(exceptionRootNode, parentNode, parentNode.getChildCount()); |
| |
| handleExpansion(); |
| } |
| |
| 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); |
| } |
| } |
| |
| public void clearAllMessages() { |
| for (MessageNode messageItem : myMessageItems) { |
| messageItem.clearRangeMarker(); |
| } |
| myMessageItems.clear(); |
| myStatusNode = null; |
| createModel(); |
| myTree.setModel(myTreeModel); |
| } |
| |
| public void startBuild(AntMessage message) { |
| } |
| |
| public void buildFailed(AntMessage message) { |
| addMessage(message); |
| } |
| |
| public void startTarget(AntMessage message) { |
| collapseTargets(); |
| MessageNode targetNode = (MessageNode)addMessage(message); |
| myParentPath = myParentPath.pathByAddingChild(targetNode); |
| } |
| |
| private void collapseTargets() { |
| DefaultMutableTreeNode root = (DefaultMutableTreeNode)myTreeModel.getRoot(); |
| for (int i = 0; i < root.getChildCount(); i++) { |
| DefaultMutableTreeNode node = (DefaultMutableTreeNode)root.getChildAt(i); |
| myTree.collapsePath(new TreePath(node.getPath())); |
| } |
| } |
| |
| public void startTask(AntMessage message) { |
| myCurrentTaskName = message.getText(); |
| MessageNode taskNode = (MessageNode)addMessage(message); |
| myParentPath = myParentPath.pathByAddingChild(taskNode); |
| } |
| |
| private void popupInvoked(Component component, int x, int y) { |
| final TreePath path = myTree.getLeadSelectionPath(); |
| if (path == null) return; |
| if (!(path.getLastPathComponent()instanceof MessageNode)) return; |
| if (getData(CommonDataKeys.NAVIGATABLE_ARRAY.getName()) == null) return; |
| DefaultActionGroup group = new DefaultActionGroup(); |
| group.add(ActionManager.getInstance().getAction(IdeActions.ACTION_EDIT_SOURCE)); |
| ActionPopupMenu menu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.ANT_MESSAGES_POPUP, group); |
| menu.getComponent().show(component, x, y); |
| } |
| |
| @Nullable |
| private MessageNode getSelectedItem() { |
| TreePath path = myTree.getSelectionPath(); |
| if (path == null) return null; |
| if (!(path.getLastPathComponent()instanceof MessageNode)) return null; |
| return (MessageNode)path.getLastPathComponent(); |
| } |
| |
| @Nullable |
| public Object getData(String dataId) { |
| if (CommonDataKeys.NAVIGATABLE.is(dataId)) { |
| MessageNode item = getSelectedItem(); |
| if (item == null) return null; |
| if (isValid(item.getFile())) { |
| return new OpenFileDescriptor(myProject, item.getFile(), item.getOffset()); |
| } |
| if (item.getType() == AntBuildMessageView.MessageType.TARGET) { |
| final OpenFileDescriptor descriptor = getDescriptorForTargetNode(item); |
| if (descriptor != null && isValid(descriptor.getFile())) { |
| return descriptor; |
| } |
| } |
| if (item.getType() == AntBuildMessageView.MessageType.TASK) { |
| final OpenFileDescriptor descriptor = getDescriptorForTaskNode(item); |
| if (descriptor != null && isValid(descriptor.getFile())) { |
| return descriptor; |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| private OpenFileDescriptor getDescriptorForTargetNode(MessageNode node) { |
| final String targetName = node.getText()[0]; |
| final AntBuildTargetBase target = (AntBuildTargetBase)myBuildFile.getModel().findTarget(targetName); |
| return (target == null) ? null : target.getOpenFileDescriptor(); |
| } |
| |
| private |
| @Nullable |
| OpenFileDescriptor getDescriptorForTaskNode(MessageNode node) { |
| final String[] text = node.getText(); |
| if (text == null || text.length == 0) return null; |
| final String taskName = text[0]; |
| final TreeNode parentNode = node.getParent(); |
| if (!(parentNode instanceof MessageNode)) return null; |
| final MessageNode messageNode = (MessageNode)parentNode; |
| if (messageNode.getType() != AntBuildMessageView.MessageType.TARGET) return null; |
| final BuildTask task = ((AntBuildModelBase)myBuildFile.getModel()).findTask(messageNode.getText()[0], taskName); |
| return (task == null) ? null : task.getOpenFileDescriptor(); |
| } |
| |
| private static boolean isValid(final VirtualFile file) { |
| return file != null && ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() { |
| public Boolean compute() { |
| return file.isValid(); |
| } |
| }).booleanValue(); |
| } |
| |
| public void finishBuild(String messageText) { |
| collapseTargets(); |
| DefaultMutableTreeNode root = (DefaultMutableTreeNode)myTreeModel.getRoot(); |
| myStatusNode = new DefaultMutableTreeNode(messageText); |
| myTreeModel.insertNodeInto(myStatusNode, root, root.getChildCount()); |
| } |
| |
| public void scrollToStatus() { |
| if (myStatusNode != null) { |
| TreeUtil.selectPath(myTree, new TreePath(myStatusNode.getPath())); |
| } |
| } |
| |
| public void finishTarget() { |
| final TreePath parentPath = myParentPath.getParentPath(); |
| if (parentPath != null) { |
| myParentPath = parentPath; |
| } |
| } |
| |
| public void finishTask() { |
| myCurrentTaskName = null; |
| final TreePath parentPath = myParentPath.getParentPath(); |
| if (parentPath != null) { |
| myParentPath = parentPath; |
| } |
| } |
| |
| @Nullable |
| private static TreePath getFirstErrorPath(TreePath treePath) { |
| TreeNode treeNode = (TreeNode)treePath.getLastPathComponent(); |
| if (treeNode instanceof MessageNode) { |
| AntBuildMessageView.MessageType type = ((MessageNode)treeNode).getType(); |
| if (type == AntBuildMessageView.MessageType.ERROR) { |
| return treePath; |
| } |
| } |
| |
| if (treeNode.getChildCount() == 0) { |
| return null; |
| } |
| for (int i = 0; i < treeNode.getChildCount(); i++) { |
| TreeNode childNode = treeNode.getChildAt(i); |
| TreePath childPath = treePath.pathByAddingChild(childNode); |
| TreePath usagePath = getFirstErrorPath(childPath); |
| if (usagePath != null) { |
| return usagePath; |
| } |
| } |
| return null; |
| } |
| |
| public void scrollToFirstError() { |
| TreePath path = getFirstErrorPath(new TreePath(myTreeModel.getRoot())); |
| if (path != null) { |
| TreeUtil.selectPath(myTree, path); |
| } |
| } |
| |
| public static final class TreeSelection { |
| public String mySelectedTarget; |
| public String mySelectedTask; |
| |
| public boolean isEmpty() { |
| return mySelectedTarget == null && mySelectedTask == null; |
| } |
| } |
| |
| public TreeSelection getSelection() { |
| TreeSelection selection = new TreeSelection(); |
| |
| TreePath path = myTree.getSelectionPath(); |
| if (path == null) return selection; |
| |
| Object[] paths = path.getPath(); |
| for (Object o : paths) { |
| if (o instanceof MessageNode) { |
| MessageNode messageNode = (MessageNode)o; |
| AntBuildMessageView.MessageType type = messageNode.getType(); |
| if (type == AntBuildMessageView.MessageType.TARGET) { |
| selection.mySelectedTarget = messageNode.getText()[0]; |
| } |
| else if (type == AntBuildMessageView.MessageType.TASK) { |
| selection.mySelectedTask = messageNode.getText()[0]; |
| } |
| } |
| } |
| |
| return selection; |
| } |
| |
| public boolean restoreSelection(TreeSelection treeSelection) { |
| if (treeSelection.isEmpty()) return false; |
| |
| DefaultMutableTreeNode root = (DefaultMutableTreeNode)myTreeModel.getRoot(); |
| for (int i = 0; i < root.getChildCount(); i++) { |
| TreeNode node = root.getChildAt(i); |
| if (node instanceof MessageNode) { |
| MessageNode messageNode = (MessageNode)node; |
| String[] text = messageNode.getText(); |
| if (text.length == 0) continue; |
| if (Comparing.equal(treeSelection.mySelectedTarget, text[0])) { |
| TreePath pathToSelect = new TreePath(messageNode.getPath()); |
| for (Enumeration enumeration = messageNode.children(); enumeration.hasMoreElements();) { |
| Object o = enumeration.nextElement(); |
| if (o instanceof MessageNode) { |
| messageNode = (MessageNode)o; |
| if (Comparing.equal(treeSelection.mySelectedTask, text[0])) { |
| pathToSelect = new TreePath(messageNode.getPath()); |
| break; |
| } |
| } |
| } |
| TreeUtil.selectPath(myTree, pathToSelect); |
| myTree.expandPath(pathToSelect); |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| ToggleAction createToggleAutoscrollAction() { |
| return myAutoScrollToSourceHandler.createToggleAction(); |
| } |
| |
| public String getNextOccurenceActionName() { |
| return myOccurenceNavigatorSupport.getNextOccurenceActionName(); |
| } |
| |
| public String getPreviousOccurenceActionName() { |
| return myOccurenceNavigatorSupport.getPreviousOccurenceActionName(); |
| } |
| |
| public OccurenceNavigator.OccurenceInfo goNextOccurence() { |
| return myOccurenceNavigatorSupport.goNextOccurence(); |
| } |
| |
| public OccurenceNavigator.OccurenceInfo goPreviousOccurence() { |
| return myOccurenceNavigatorSupport.goPreviousOccurence(); |
| } |
| |
| public boolean hasNextOccurence() { |
| return myOccurenceNavigatorSupport.hasNextOccurence(); |
| } |
| |
| public boolean hasPreviousOccurence() { |
| return myOccurenceNavigatorSupport.hasPreviousOccurence(); |
| } |
| |
| private class MyTree extends Tree implements DataProvider { |
| public MyTree() { |
| super(myTreeModel); |
| } |
| |
| public void setRowHeight(int i) { |
| super.setRowHeight(0); |
| // this is needed in order to make UI calculate the height for each particular row |
| } |
| |
| public void updateUI() { |
| super.updateUI(); |
| TreeUtil.installActions(this); |
| } |
| |
| public Object getData(String dataId) { |
| if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) { |
| return new CopyProvider() { |
| public boolean isCopyEnabled(@NotNull DataContext dataContext) { |
| return getSelectionPath() != null; |
| } |
| |
| public boolean isCopyVisible(@NotNull DataContext dataContext) { |
| return true; |
| } |
| |
| public void performCopy(@NotNull DataContext dataContext) { |
| TreePath selection = getSelectionPath(); |
| Object value = selection.getLastPathComponent(); |
| String text; |
| if (value instanceof MessageNode) { |
| MessageNode messageNode = ((MessageNode)value); |
| String[] lines = messageNode.getText(); |
| text = ""; |
| for (String line : lines) { |
| text += line + "\n"; |
| } |
| } |
| else { |
| text = value.toString(); |
| } |
| CopyPasteManager.getInstance().setContents(new StringSelection(text)); |
| } |
| }; |
| } |
| return null; |
| } |
| } |
| } |
| |