/* * Copyright 2000-2010 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.refactoring.changeSignature; import com.intellij.codeInsight.highlighting.HighlightManager; import com.intellij.ide.highlighter.HighlighterFactory; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Splitter; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; import com.intellij.psi.search.LocalSearchScope; import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.refactoring.RefactoringBundle; import com.intellij.ui.*; import com.intellij.ui.treeStructure.Tree; import com.intellij.util.Alarm; import com.intellij.util.Consumer; import com.intellij.util.Function; import com.intellij.util.Query; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashSet; import org.jetbrains.annotations.NotNull; import javax.swing.*; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import java.awt.*; import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; import java.util.Set; public abstract class CallerChooserBase<M extends PsiElement> extends DialogWrapper { private final M myMethod; private final Alarm myAlarm = new Alarm(); private MethodNodeBase<M> myRoot; protected final Project myProject; private Tree myTree; private final Consumer<Set<M>> myCallback; private TreeSelectionListener myTreeSelectionListener; private Editor myCallerEditor; private Editor myCalleeEditor; private boolean myInitDone; private final String myFileName; protected abstract MethodNodeBase<M> createTreeNode(M method, HashSet<M> called, Runnable cancelCallback); protected abstract M[] findDeepestSuperMethods(M method); public CallerChooserBase(M method, Project project, String title, Tree previousTree, String fileName, Consumer<Set<M>> callback) { super(true); myMethod = method; myProject = project; myTree = previousTree; myFileName = fileName; myCallback = callback; setTitle(title); init(); myInitDone = true; } public Tree getTree() { return myTree; } @Override protected JComponent createCenterPanel() { Splitter splitter = new Splitter(false, (float)0.6); JPanel result = new JPanel(new BorderLayout()); if (myTree == null) { myTree = createTree(); } else { final CheckedTreeNode root = (CheckedTreeNode)myTree.getModel().getRoot(); myRoot = (MethodNodeBase)root.getFirstChild(); } myTreeSelectionListener = new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { final TreePath path = e.getPath(); if (path != null) { final MethodNodeBase<M> node = (MethodNodeBase)path.getLastPathComponent(); myAlarm.cancelAllRequests(); myAlarm.addRequest(new Runnable() { @Override public void run() { updateEditorTexts(node); } }, 300); } } }; myTree.getSelectionModel().addTreeSelectionListener(myTreeSelectionListener); JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTree); splitter.setFirstComponent(scrollPane); final JComponent callSitesViewer = createCallSitesViewer(); TreePath selectionPath = myTree.getSelectionPath(); if (selectionPath == null) { selectionPath = new TreePath(myRoot.getPath()); myTree.getSelectionModel().addSelectionPath(selectionPath); } final MethodNodeBase<M> node = (MethodNodeBase)selectionPath.getLastPathComponent(); updateEditorTexts(node); splitter.setSecondComponent(callSitesViewer); result.add(splitter); return result; } private void updateEditorTexts(final MethodNodeBase<M> node) { final MethodNodeBase<M> parentNode = (MethodNodeBase)node.getParent(); final String callerText = node != myRoot ? getText(node.getMethod()) : ""; final Document callerDocument = myCallerEditor.getDocument(); final String calleeText = node != myRoot ? getText(parentNode.getMethod()) : ""; final Document calleeDocument = myCalleeEditor.getDocument(); ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { callerDocument.setText(callerText); calleeDocument.setText(calleeText); } }); final M caller = node.getMethod(); final PsiElement callee = parentNode != null ? parentNode.getElementToSearch() : null; if (caller != null && caller.isPhysical() && callee != null) { HighlightManager highlighter = HighlightManager.getInstance(myProject); EditorColorsManager colorManager = EditorColorsManager.getInstance(); TextAttributes attributes = colorManager.getGlobalScheme().getAttributes(EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES); int start = getStartOffset(caller); for (PsiElement element : findElementsToHighlight(caller, callee)) { highlighter.addRangeHighlight(myCallerEditor, element.getTextRange().getStartOffset() - start, element.getTextRange().getEndOffset() - start, attributes, false, null); } } } protected Collection<PsiElement> findElementsToHighlight(M caller, PsiElement callee) { Query<PsiReference> references = ReferencesSearch.search(callee, new LocalSearchScope(caller), false); return ContainerUtil.mapNotNull(references, new Function<PsiReference, PsiElement>() { @Override public PsiElement fun(PsiReference psiReference) { return psiReference.getElement(); } }); } @Override public void dispose() { if (myTree != null) { myTree.removeTreeSelectionListener(myTreeSelectionListener); EditorFactory.getInstance().releaseEditor(myCallerEditor); EditorFactory.getInstance().releaseEditor(myCalleeEditor); } super.dispose(); } private String getText(final M method) { if (method == null) return ""; final PsiFile file = method.getContainingFile(); Document document = PsiDocumentManager.getInstance(myProject).getDocument(file); if (document != null) { final int start = document.getLineStartOffset(document.getLineNumber(method.getTextRange().getStartOffset())); final int end = document.getLineEndOffset(document.getLineNumber(method.getTextRange().getEndOffset())); return document.getText().substring(start, end); } return ""; } private int getStartOffset(@NotNull final M method) { final PsiFile file = method.getContainingFile(); Document document = PsiDocumentManager.getInstance(myProject).getDocument(file); return document.getLineStartOffset(document.getLineNumber(method.getTextRange().getStartOffset())); } private JComponent createCallSitesViewer() { Splitter splitter = new Splitter(true); myCallerEditor = createEditor(); myCalleeEditor = createEditor(); final JComponent callerComponent = myCallerEditor.getComponent(); callerComponent.setBorder(IdeBorderFactory.createTitledBorder(RefactoringBundle.message("caller.chooser.caller.method"), false)); splitter.setFirstComponent(callerComponent); final JComponent calleeComponent = myCalleeEditor.getComponent(); calleeComponent.setBorder(IdeBorderFactory.createTitledBorder(RefactoringBundle.message("caller.chooser.callee.method"), false)); splitter.setSecondComponent(calleeComponent); splitter.setBorder(IdeBorderFactory.createRoundedBorder()); return splitter; } private Editor createEditor() { final EditorFactory editorFactory = EditorFactory.getInstance(); final Document document = editorFactory.createDocument(""); final Editor editor = editorFactory.createViewer(document, myProject); ((EditorEx)editor).setHighlighter(HighlighterFactory.createHighlighter(myProject, myFileName)); return editor; } private Tree createTree() { final Runnable cancelCallback = new Runnable() { @Override public void run() { if (myInitDone) { close(CANCEL_EXIT_CODE); } else { throw new ProcessCanceledException(); } } }; final CheckedTreeNode root = createTreeNode(null, new HashSet<M>(), cancelCallback); myRoot = createTreeNode(myMethod, new HashSet<M>(), cancelCallback); root.add(myRoot); final CheckboxTree.CheckboxTreeCellRenderer cellRenderer = new CheckboxTree.CheckboxTreeCellRenderer(true, false) { @Override public void customizeRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { if (value instanceof MethodNodeBase) { ((MethodNodeBase)value).customizeRenderer(getTextRenderer()); } } }; Tree tree = new CheckboxTree(cellRenderer, root, new CheckboxTreeBase.CheckPolicy(false, true, true, false)); tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); tree.getSelectionModel().setSelectionPath(new TreePath(myRoot.getPath())); return tree; } private void getSelectedMethods(Set<M> methods) { MethodNodeBase<M> node = myRoot; getSelectedMethodsInner(node, methods); methods.remove(node.getMethod()); } private void getSelectedMethodsInner(final MethodNodeBase<M> node, final Set<M> allMethods) { if (node.isChecked()) { M method = node.getMethod(); final M[] superMethods = findDeepestSuperMethods(method); if (superMethods.length == 0) { allMethods.add(method); } else { allMethods.addAll(Arrays.asList(superMethods)); } final Enumeration children = node.children(); while (children.hasMoreElements()) { getSelectedMethodsInner((MethodNodeBase)children.nextElement(), allMethods); } } } @Override protected void doOKAction() { final Set<M> selectedMethods = new HashSet<M>(); getSelectedMethods(selectedMethods); myCallback.consume(selectedMethods); super.doOKAction(); } @Override public JComponent getPreferredFocusedComponent() { return myTree; } }