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
;
33 import javax
.swing
.tree
.DefaultMutableTreeNode
;
34 import javax
.swing
.tree
.DefaultTreeModel
;
35 import javax
.swing
.tree
.TreePath
;
36 import javax
.swing
.tree
.TreeSelectionModel
;
38 import java
.text
.MessageFormat
;
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() {
67 public boolean hasPreviousOccurence() {
71 public OccurenceInfo
goNextOccurence() {
75 public OccurenceInfo
goPreviousOccurence() {
79 public String
getNextOccurenceActionName() {
83 public String
getPreviousOccurenceActionName() {
88 public MethodHierarchyBrowserBase(final Project project
, final PsiElement method
) {
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
){
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());
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() {
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());
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);
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
);
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
);
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
);
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() {
199 myRefreshAction
.unregisterCustomShortcutSet(tree
);
203 new TreeSpeedSearch(tree
);
204 TreeUtil
.installActions(tree
);
205 TreeToolTipHandler
.install(tree
);
206 myAutoScrollToSourceHandler
.install(tree
);
210 private void setHierarchyBase(final PsiElement method
) {
211 mySmartPsiElementPointer
= SmartPointerManager
.getInstance(myProject
).createSmartPsiElementPointer(method
);
214 public final void setContent(final Content content
) {
218 private void restoreCursor() {
219 /*int n =*/ myAlarm
.cancelAllRequests();
221 setCursor(Cursor
.getDefaultCursor());
225 private void setWaitCursor() {
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
)) {
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
)) {
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) {
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
);
274 final TreePath path
= new TreePath(node
.getPath());
275 tree
.expandPath(path
);
276 TreeUtil
.selectPath(tree
, path
);
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
,
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
)) {
359 final String
[] name
= new String
[]{myCurrentViewName
};
361 setHierarchyBase(element
);
363 ApplicationManager
.getApplication().invokeLater(new Runnable() {
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
)) {
407 final String
[] name
= new String
[]{myCurrentViewName
};
409 setHierarchyBase(element
);
411 ApplicationManager
.getApplication().invokeLater(new Runnable() {
414 if (storedInfo
!= null) {
415 final HierarchyTreeBuilder builder
= myBuilders
.get(myCurrentViewName
);
416 builder
.restoreExpandedAndSelectedInfo(storedInfo
[0]);
422 public final void update(final AnActionEvent 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
);
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
)) {
477 else if (METHOD_HIERARCHY_BROWSER_DATA_CONSTANT
.equals(dataId
)) {
480 else if (DataConstants
.HELP_ID
.equals(dataId
)) {
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()]);
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();
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
) {
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
)) {
553 final String
[] name
= new String
[]{browser
.myCurrentViewName
};
555 browser
.setHierarchyBase(selectedElement
);
557 ApplicationManager
.getApplication().invokeLater(new Runnable() {
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);
579 final PsiElement selectedElement
= browser
.getSelectedElement();
580 if (!isApplicableElement(selectedElement
)) {
581 presentation
.setEnabled(false);
582 presentation
.setVisible(false);
586 presentation
.setVisible(true);
588 if (!selectedElement
.equals(browser
.mySmartPsiElementPointer
.getElement()) &&
589 selectedElement
.isValid()
591 presentation
.setEnabled(true);
594 presentation
.setEnabled(false);
598 protected abstract boolean isApplicableElement(final PsiElement psiElement
);
601 public final HierarchyNodeDescriptor
[] getSelectedDescriptors() {
602 final JTree tree
= getCurrentTree();
604 return new HierarchyNodeDescriptor
[0];
606 final TreePath
[] paths
= tree
.getSelectionPaths();
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()]);