common code moved to MethodHierarchyBrowserBase
[fedora-idea.git] / lang-impl / src / com / intellij / ide / hierarchy / MethodHierarchyBrowserBase.java
blobc9947bb3cb8d26c022a1ba531a1cfa7617035362
1 package com.intellij.ide.hierarchy;
3 import com.intellij.ide.IdeBundle;
4 import com.intellij.ide.OccurenceNavigator;
5 import com.intellij.ide.OccurenceNavigatorSupport;
6 import com.intellij.ide.actions.CloseTabToolbarAction;
7 import com.intellij.ide.actions.ContextHelpAction;
8 import com.intellij.ide.util.treeView.NodeDescriptor;
9 import com.intellij.openapi.Disposable;
10 import com.intellij.openapi.actionSystem.*;
11 import com.intellij.openapi.application.ApplicationManager;
12 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
13 import com.intellij.openapi.project.Project;
14 import com.intellij.openapi.ui.MultiLineLabelUI;
15 import com.intellij.openapi.util.Disposer;
16 import com.intellij.openapi.util.IconLoader;
17 import com.intellij.openapi.vfs.VirtualFile;
18 import com.intellij.pom.Navigatable;
19 import com.intellij.psi.*;
20 import com.intellij.ui.AutoScrollToSourceHandler;
21 import com.intellij.ui.TreeSpeedSearch;
22 import com.intellij.ui.TreeToolTipHandler;
23 import com.intellij.ui.content.Content;
24 import com.intellij.ui.treeStructure.Tree;
25 import com.intellij.util.Alarm;
26 import com.intellij.util.EditSourceOnDoubleClickHandler;
27 import com.intellij.util.ui.UIUtil;
28 import com.intellij.util.ui.tree.TreeUtil;
29 import org.jetbrains.annotations.NonNls;
30 import org.jetbrains.annotations.Nullable;
32 import javax.swing.*;
33 import javax.swing.tree.DefaultMutableTreeNode;
34 import javax.swing.tree.DefaultTreeModel;
35 import javax.swing.tree.TreePath;
36 import javax.swing.tree.TreeSelectionModel;
37 import java.awt.*;
38 import java.text.MessageFormat;
39 import java.util.*;
40 import java.util.List;
42 public abstract class MethodHierarchyBrowserBase extends JPanel implements DataProvider, OccurenceNavigator, Disposable, HierarchyBrowser {
43 @NonNls private static final String HELP_ID = "reference.toolWindows.hierarchy";
45 protected final Project myProject;
46 private Content myContent;
47 protected final Hashtable<String, HierarchyTreeBuilder> myBuilders = new Hashtable<String, HierarchyTreeBuilder>();
48 private final Hashtable<String,JTree> myTrees = new Hashtable<String, JTree>();
50 private final RefreshAction myRefreshAction = new RefreshAction();
51 private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
52 private SmartPsiElementPointer mySmartPsiElementPointer;
53 private final CardLayout myCardLayout;
54 private final JPanel myTreePanel;
55 protected String myCurrentViewName;
57 private final AutoScrollToSourceHandler myAutoScrollToSourceHandler;
59 @NonNls public static final String METHOD_HIERARCHY_BROWSER_DATA_CONSTANT = "com.intellij.ide.hierarchy.MethodHierarchyBrowserBase";
60 private final List<Runnable> myRunOnDisposeList = new ArrayList<Runnable>();
61 private final HashMap<String, OccurenceNavigator> myOccurrenceNavigators = new HashMap<String, OccurenceNavigator>();
62 private static final OccurenceNavigator EMPTY_NAVIGATOR = new OccurenceNavigator() {
63 public boolean hasNextOccurence() {
64 return false;
67 public boolean hasPreviousOccurence() {
68 return false;
71 public OccurenceInfo goNextOccurence() {
72 return null;
75 public OccurenceInfo goPreviousOccurence() {
76 return null;
79 public String getNextOccurenceActionName() {
80 return "";
83 public String getPreviousOccurenceActionName() {
84 return "";
88 public MethodHierarchyBrowserBase(final Project project, final PsiElement method) {
89 myProject = project;
91 myAutoScrollToSourceHandler = new AutoScrollToSourceHandler() {
92 protected boolean isAutoScrollMode() {
93 return HierarchyBrowserManager.getInstance(myProject).getState().IS_AUTOSCROLL_TO_SOURCE;
96 protected void setAutoScrollMode(final boolean state) {
97 HierarchyBrowserManager.getInstance(myProject).getState().IS_AUTOSCROLL_TO_SOURCE = state;
101 setHierarchyBase(method);
102 setLayout(new BorderLayout());
104 final ActionToolbar toolbar = createToolbar();
105 add(toolbar.getComponent(), BorderLayout.NORTH);
107 myCardLayout = new CardLayout();
108 myTreePanel = new JPanel(myCardLayout);
110 createTrees(myTrees);
112 final Enumeration<String> keys = myTrees.keys();
113 while (keys.hasMoreElements()) {
114 final String key = keys.nextElement();
115 final JTree tree = myTrees.get(key);
116 myOccurrenceNavigators.put(key, new OccurenceNavigatorSupport(tree){
117 @Nullable
118 protected Navigatable createDescriptorForNode(DefaultMutableTreeNode node) {
119 final PsiElement psiElement = getElementFromNode(node);
120 if (psiElement == null || !psiElement.isValid()) return null;
121 final VirtualFile virtualFile = psiElement.getContainingFile().getVirtualFile();
122 if (virtualFile != null) {
123 return new OpenFileDescriptor(psiElement.getProject(), virtualFile, psiElement.getTextOffset());
125 return null;
128 public String getNextOccurenceActionName() {
129 return IdeBundle.message("hierarchy.method.next.occurence.name");
132 public String getPreviousOccurenceActionName() {
133 return IdeBundle.message("hierarchy.method.prev.occurence.name");
136 myTreePanel.add(new JScrollPane(tree), key);
138 add(myTreePanel, BorderLayout.CENTER);
140 add(createLegendPanel(), BorderLayout.SOUTH);
143 protected abstract void createTrees(final Hashtable<String, JTree> trees);
145 protected abstract PsiElement getElementFromNode(final DefaultMutableTreeNode node);
147 public JComponent getComponent() {
148 return this;
151 protected abstract JPanel createLegendPanel();
153 protected static JPanel createStandardLegendPanel(final String methodDefinedText,
154 final String methodNotDefinedLegallyText,
155 final String methodShouldBeDefined) {
156 final JPanel panel = new JPanel(new GridBagLayout());
158 JLabel label;
159 final GridBagConstraints gc = new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.WEST,
160 GridBagConstraints.HORIZONTAL, new Insets(3, 5, 0, 5), 0, 0);
162 label =
163 new JLabel(methodDefinedText, IconLoader.getIcon("/hierarchy/methodDefined.png"),
164 SwingConstants.LEFT);
165 label.setUI(new MultiLineLabelUI());
166 label.setIconTextGap(10);
167 panel.add(label, gc);
169 gc.gridy++;
170 label =
171 new JLabel(methodNotDefinedLegallyText,
172 IconLoader.getIcon("/hierarchy/methodNotDefined.png"), SwingConstants.LEFT);
173 label.setUI(new MultiLineLabelUI());
174 label.setIconTextGap(10);
175 panel.add(label, gc);
177 gc.gridy++;
178 label =
179 new JLabel(methodShouldBeDefined,
180 IconLoader.getIcon("/hierarchy/shouldDefineMethod.png"), SwingConstants.LEFT);
181 label.setUI(new MultiLineLabelUI());
182 label.setIconTextGap(10);
183 panel.add(label, gc);
185 return panel;
188 protected JTree createTreeWithoutActions() {
189 final Tree tree = new Tree(new DefaultTreeModel(new DefaultMutableTreeNode("")));
190 tree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
191 tree.setToggleClickCount(-1);
192 tree.setCellRenderer(new HierarchyNodeRenderer());
193 UIUtil.setLineStyleAngled(tree);
194 EditSourceOnDoubleClickHandler.install(tree);
196 myRefreshAction.registerShortcutOn(tree);
197 myRunOnDisposeList.add(new Runnable() {
198 public void run() {
199 myRefreshAction.unregisterCustomShortcutSet(tree);
203 new TreeSpeedSearch(tree);
204 TreeUtil.installActions(tree);
205 TreeToolTipHandler.install(tree);
206 myAutoScrollToSourceHandler.install(tree);
207 return tree;
210 private void setHierarchyBase(final PsiElement method) {
211 mySmartPsiElementPointer = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(method);
214 public final void setContent(final Content content) {
215 myContent = content;
218 private void restoreCursor() {
219 /*int n =*/ myAlarm.cancelAllRequests();
220 // if (n == 0) {
221 setCursor(Cursor.getDefaultCursor());
222 // }
225 private void setWaitCursor() {
226 myAlarm.addRequest(
227 new Runnable() {
228 public void run() {
229 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
236 public final void changeView(final String typeName) {
237 myCurrentViewName = typeName;
239 final PsiElement element = mySmartPsiElementPointer.getElement();
240 if (!isApplicableElement(element)) {
241 return;
244 if (myContent != null) {
245 if (element instanceof PsiNamedElement) {
246 myContent.setDisplayName(MessageFormat.format(typeName, ((PsiNamedElement)element).getName()));
250 myCardLayout.show(myTreePanel, typeName);
252 if (!myBuilders.containsKey(typeName)) {
253 try {
254 setWaitCursor();
255 // create builder
256 final JTree tree = myTrees.get(typeName);
257 final DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode(""));
258 tree.setModel(model);
260 PsiDocumentManager.getInstance(myProject).commitAllDocuments();
261 final HierarchyTreeStructure structure = createHierarchyTreeStructure(typeName, element);
262 if (structure == null) {
263 return;
265 final Comparator<NodeDescriptor> comparator = getComparator();
266 final HierarchyTreeBuilder builder = new HierarchyTreeBuilder(myProject, tree, model, structure, comparator);
268 myBuilders.put(typeName, builder);
270 final HierarchyNodeDescriptor baseDescriptor = structure.getBaseDescriptor();
271 builder.buildNodeForElement(baseDescriptor);
272 final DefaultMutableTreeNode node = builder.getNodeForElement(baseDescriptor);
273 if (node != null) {
274 final TreePath path = new TreePath(node.getPath());
275 tree.expandPath(path);
276 TreeUtil.selectPath(tree, path);
279 finally {
280 restoreCursor();
284 getCurrentTree().requestFocus();
287 protected abstract boolean isApplicableElement(final PsiElement element);
289 protected abstract HierarchyTreeStructure createHierarchyTreeStructure(final String typeName, final PsiElement psiElement);
291 protected abstract Comparator<NodeDescriptor> getComparator();
293 private ActionToolbar createToolbar() {
294 final DefaultActionGroup actionGroup = new DefaultActionGroup();
296 actionGroup.add(new AlphaSortAction());
297 actionGroup.add(new ShowImplementationsOnlyAction());
298 actionGroup.add(myRefreshAction);
299 actionGroup.add(myAutoScrollToSourceHandler.createToggleAction());
300 actionGroup.add(new CloseAction());
301 actionGroup.add(new ContextHelpAction(HELP_ID));
303 final ActionToolbar toolBar = ActionManager.getInstance().createActionToolbar(ActionPlaces.METHOD_HIERARCHY_VIEW_TOOLBAR,
304 actionGroup, true);
305 return toolBar;
308 public boolean hasNextOccurence() {
309 return getOccurrenceNavigator().hasNextOccurence();
312 private OccurenceNavigator getOccurrenceNavigator() {
313 if (myCurrentViewName == null) {
314 return EMPTY_NAVIGATOR;
316 final OccurenceNavigator navigator = myOccurrenceNavigators.get(myCurrentViewName);
317 return navigator != null? navigator : EMPTY_NAVIGATOR;
320 public boolean hasPreviousOccurence() {
321 return getOccurrenceNavigator().hasPreviousOccurence();
324 public OccurenceInfo goNextOccurence() {
325 return getOccurrenceNavigator().goNextOccurence();
328 public OccurenceInfo goPreviousOccurence() {
329 return getOccurrenceNavigator().goPreviousOccurence();
332 public String getNextOccurenceActionName() {
333 return getOccurrenceNavigator().getNextOccurenceActionName();
336 public String getPreviousOccurenceActionName() {
337 return getOccurrenceNavigator().getPreviousOccurenceActionName();
340 final class RefreshAction extends com.intellij.ide.actions.RefreshAction {
341 public RefreshAction() {
342 super(IdeBundle.message("action.refresh"),
343 IdeBundle.message("action.refresh"), IconLoader.getIcon("/actions/sync.png"));
346 public final void actionPerformed(final AnActionEvent e) {
347 if (!isValidBase()) return;
349 final Object[] storedInfo = new Object[1];
350 if (myCurrentViewName != null) {
351 final HierarchyTreeBuilder builder = myBuilders.get(myCurrentViewName);
352 storedInfo[0] = builder.storeExpandedAndSelectedInfo();
355 final PsiElement element = mySmartPsiElementPointer.getElement();
356 if (!isApplicableElement(element)) {
357 return;
359 final String[] name = new String[]{myCurrentViewName};
360 dispose();
361 setHierarchyBase(element);
362 validate();
363 ApplicationManager.getApplication().invokeLater(new Runnable() {
364 public void run() {
365 changeView(name[0]);
366 if (storedInfo != null) {
367 final HierarchyTreeBuilder builder = myBuilders.get(myCurrentViewName);
368 builder.restoreExpandedAndSelectedInfo(storedInfo[0]);
374 public final void update(final AnActionEvent event) {
375 final Presentation presentation = event.getPresentation();
376 presentation.setEnabled(isValidBase());
380 private boolean isValidBase() {
381 final PsiElement element = mySmartPsiElementPointer.getElement();
382 return isApplicableElement(element) && element.isValid();
385 final class ShowImplementationsOnlyAction extends ToggleAction {
386 public ShowImplementationsOnlyAction() {
387 super(IdeBundle.message("action.hide.non.implementations"), null, IconLoader.getIcon("/ant/filter.png")); // TODO[anton] use own icon!!!
390 public final boolean isSelected(final AnActionEvent event) {
391 return HierarchyBrowserManager.getInstance(myProject).getState().HIDE_CLASSES_WHERE_METHOD_NOT_IMPLEMENTED;
394 public final void setSelected(final AnActionEvent event, final boolean flag) {
395 HierarchyBrowserManager.getInstance(myProject).getState().HIDE_CLASSES_WHERE_METHOD_NOT_IMPLEMENTED = flag;
397 final Object[] storedInfo = new Object[1];
398 if (myCurrentViewName != null) {
399 final HierarchyTreeBuilder builder = myBuilders.get(myCurrentViewName);
400 storedInfo[0] = builder.storeExpandedAndSelectedInfo();
403 final PsiElement element = mySmartPsiElementPointer.getElement();
404 if (!isApplicableElement(element)) {
405 return;
407 final String[] name = new String[]{myCurrentViewName};
408 dispose();
409 setHierarchyBase(element);
410 validate();
411 ApplicationManager.getApplication().invokeLater(new Runnable() {
412 public void run() {
413 changeView(name[0]);
414 if (storedInfo != null) {
415 final HierarchyTreeBuilder builder = myBuilders.get(myCurrentViewName);
416 builder.restoreExpandedAndSelectedInfo(storedInfo[0]);
422 public final void update(final AnActionEvent event) {
423 super.update(event);
424 final Presentation presentation = event.getPresentation();
425 presentation.setEnabled(isValidBase());
429 private JTree getCurrentTree() {
430 if (myCurrentViewName == null) return null;
431 final JTree tree = myTrees.get(myCurrentViewName);
432 return tree;
435 public final class CloseAction extends CloseTabToolbarAction {
436 public final void actionPerformed(final AnActionEvent e) {
437 myContent.getManager().removeContent(myContent, true);
441 private PsiElement getSelectedElement() {
442 final DefaultMutableTreeNode node = getSelectedNode();
443 return getElementFromNode(node);
446 protected abstract PsiElement[] getSelectedMethods();
448 private PsiElement[] getSelectedElements() {
449 HierarchyNodeDescriptor[] descriptors = getSelectedDescriptors();
450 ArrayList<PsiElement> elements = new ArrayList<PsiElement>();
451 for (HierarchyNodeDescriptor descriptor : descriptors) {
452 PsiElement element = getElementFromDescriptor(descriptor);
453 elements.add(element);
455 return elements.toArray(new PsiElement[elements.size()]);
458 protected abstract PsiElement getElementFromDescriptor(final HierarchyNodeDescriptor descriptor);
460 private DefaultMutableTreeNode getSelectedNode() {
461 final JTree tree = getCurrentTree();
462 if (tree == null) return null;
463 final TreePath path = tree.getSelectionPath();
464 if (path == null) return null;
465 final Object lastPathComponent = path.getLastPathComponent();
466 if (!(lastPathComponent instanceof DefaultMutableTreeNode)) return null;
467 return (DefaultMutableTreeNode)lastPathComponent;
470 public final Object getData(final String dataId) {
471 if (DataConstants.PSI_ELEMENT.equals(dataId)) {
472 return getSelectedElement();
474 else if (DataConstants.DELETE_ELEMENT_PROVIDER.equals(dataId)) {
475 return null;
477 else if (METHOD_HIERARCHY_BROWSER_DATA_CONSTANT.equals(dataId)) {
478 return this;
480 else if (DataConstants.HELP_ID.equals(dataId)) {
481 return HELP_ID;
483 else if (DataConstants.PSI_ELEMENT_ARRAY.equals(dataId)) {
484 return getSelectedMethods();
485 } else if (DataConstants.NAVIGATABLE_ARRAY.equals(dataId)) {
486 final PsiElement[] selectedElements = getSelectedElements();
487 if (selectedElements == null || selectedElements.length == 0) return null;
488 final ArrayList<Navigatable> navigatables = new ArrayList<Navigatable>();
489 for (PsiElement selectedElement : selectedElements) {
490 if (selectedElement instanceof Navigatable && selectedElement.isValid()) {
491 navigatables.add((Navigatable)selectedElement);
494 return navigatables.toArray(new Navigatable[navigatables.size()]);
496 return null;
499 public final void dispose() {
500 final Collection<HierarchyTreeBuilder> builders = myBuilders.values();
501 for (final HierarchyTreeBuilder builder : builders) {
502 Disposer.dispose(builder);
504 for (final Runnable aRunOnDisposeList : myRunOnDisposeList) {
505 aRunOnDisposeList.run();
507 myRunOnDisposeList.clear();
508 myBuilders.clear();
511 private final class AlphaSortAction extends ToggleAction {
512 public AlphaSortAction() {
513 super(IdeBundle.message("action.sort.alphabetically"),
514 IdeBundle.message("action.sort.alphabetically"), IconLoader.getIcon("/objectBrowser/sorted.png"));
517 public final boolean isSelected(final AnActionEvent event) {
518 return HierarchyBrowserManager.getInstance(myProject).getState().SORT_ALPHABETICALLY;
521 public final void setSelected(final AnActionEvent event, final boolean flag) {
522 final HierarchyBrowserManager hierarchyBrowserManager = HierarchyBrowserManager.getInstance(myProject);
523 hierarchyBrowserManager.getState().SORT_ALPHABETICALLY = flag;
524 final Comparator<NodeDescriptor> comparator = getComparator();
525 final Collection<HierarchyTreeBuilder> builders = myBuilders.values();
526 for (final HierarchyTreeBuilder builder : builders) {
527 builder.setNodeDescriptorComparator(comparator);
531 public final void update(final AnActionEvent event) {
532 super.update(event);
533 final Presentation presentation = event.getPresentation();
534 presentation.setEnabled(isValidBase());
538 public static abstract class BaseOnThisMethodAction extends AnAction {
539 public BaseOnThisMethodAction() {
540 super(IdeBundle.message("action.base.on.this.method"));
543 public final void actionPerformed(final AnActionEvent event) {
544 final DataContext dataContext = event.getDataContext();
545 final MethodHierarchyBrowserBase browser = (MethodHierarchyBrowserBase)dataContext.getData(METHOD_HIERARCHY_BROWSER_DATA_CONSTANT);
546 if (browser == null) return;
548 final PsiElement selectedElement = browser.getSelectedElement();
549 if (!isApplicableElement(selectedElement)) {
550 return;
553 final String[] name = new String[]{browser.myCurrentViewName};
554 browser.dispose();
555 browser.setHierarchyBase(selectedElement);
556 browser.validate();
557 ApplicationManager.getApplication().invokeLater(new Runnable() {
558 public void run() {
559 browser.changeView(name[0]);
564 public final void update(final AnActionEvent event) {
565 final Presentation presentation = event.getPresentation();
566 presentation.setText(IdeBundle.message("action.base.on.this.method"));
568 registerCustomShortcutSet(
569 ActionManager.getInstance().getAction(IdeActions.ACTION_METHOD_HIERARCHY).getShortcutSet(), null);
571 final DataContext dataContext = event.getDataContext();
572 final MethodHierarchyBrowserBase browser = (MethodHierarchyBrowserBase)dataContext.getData(METHOD_HIERARCHY_BROWSER_DATA_CONSTANT);
573 if (browser == null) {
574 presentation.setVisible(false);
575 presentation.setEnabled(false);
576 return;
579 final PsiElement selectedElement = browser.getSelectedElement();
580 if (!isApplicableElement(selectedElement)) {
581 presentation.setEnabled(false);
582 presentation.setVisible(false);
583 return;
586 presentation.setVisible(true);
588 if (!selectedElement.equals(browser.mySmartPsiElementPointer.getElement()) &&
589 selectedElement.isValid()
591 presentation.setEnabled(true);
593 else {
594 presentation.setEnabled(false);
598 protected abstract boolean isApplicableElement(final PsiElement psiElement);
601 public final HierarchyNodeDescriptor[] getSelectedDescriptors() {
602 final JTree tree = getCurrentTree();
603 if (tree == null) {
604 return new HierarchyNodeDescriptor[0];
606 final TreePath[] paths = tree.getSelectionPaths();
607 if (paths == null) {
608 return new HierarchyNodeDescriptor[0];
610 final ArrayList<HierarchyNodeDescriptor> list = new ArrayList<HierarchyNodeDescriptor>(paths.length);
611 for (final TreePath path : paths) {
612 final Object lastPathComponent = path.getLastPathComponent();
613 if (lastPathComponent instanceof DefaultMutableTreeNode) {
614 final DefaultMutableTreeNode node = (DefaultMutableTreeNode)lastPathComponent;
615 final Object userObject = node.getUserObject();
616 if (userObject instanceof HierarchyNodeDescriptor) {
617 list.add((HierarchyNodeDescriptor)userObject);
621 return list.toArray(new HierarchyNodeDescriptor[list.size()]);