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
.dnd
.DnDAction
;
7 import com
.intellij
.ide
.dnd
.DnDDragStartBean
;
8 import com
.intellij
.ide
.dnd
.DnDManager
;
9 import com
.intellij
.ide
.dnd
.DnDSource
;
10 import com
.intellij
.ide
.dnd
.aware
.DnDAwareTree
;
11 import com
.intellij
.ide
.projectView
.impl
.AbstractProjectViewPane
;
12 import com
.intellij
.ide
.util
.treeView
.NodeDescriptor
;
13 import com
.intellij
.openapi
.actionSystem
.*;
14 import com
.intellij
.openapi
.application
.ApplicationManager
;
15 import com
.intellij
.openapi
.diagnostic
.Logger
;
16 import com
.intellij
.openapi
.fileEditor
.OpenFileDescriptor
;
17 import com
.intellij
.openapi
.project
.Project
;
18 import com
.intellij
.openapi
.util
.Disposer
;
19 import com
.intellij
.openapi
.util
.IconLoader
;
20 import com
.intellij
.openapi
.util
.Pair
;
21 import com
.intellij
.openapi
.util
.Ref
;
22 import com
.intellij
.openapi
.vfs
.VirtualFile
;
23 import com
.intellij
.pom
.Navigatable
;
24 import com
.intellij
.psi
.*;
25 import com
.intellij
.ui
.treeStructure
.Tree
;
26 import com
.intellij
.util
.Alarm
;
27 import com
.intellij
.util
.EditSourceOnDoubleClickHandler
;
28 import com
.intellij
.util
.ui
.tree
.TreeUtil
;
29 import org
.jetbrains
.annotations
.NonNls
;
30 import org
.jetbrains
.annotations
.NotNull
;
31 import org
.jetbrains
.annotations
.Nullable
;
34 import javax
.swing
.tree
.DefaultMutableTreeNode
;
35 import javax
.swing
.tree
.DefaultTreeModel
;
36 import javax
.swing
.tree
.TreeNode
;
37 import javax
.swing
.tree
.TreePath
;
39 import java
.text
.MessageFormat
;
42 public abstract class HierarchyBrowserBaseEx
extends HierarchyBrowserBase
implements OccurenceNavigator
{
44 private static final Logger LOG
= Logger
.getInstance("#com.intellij.ide.hierarchy.HierarchyBrowserBaseEx");
46 @NonNls private static final String HELP_ID
= "reference.toolWindows.hierarchy";
48 protected final Hashtable
<String
, HierarchyTreeBuilder
> myBuilders
= new Hashtable
<String
, HierarchyTreeBuilder
>();
49 protected final Hashtable
<String
, JTree
> myType2TreeMap
= new Hashtable
<String
, JTree
>();
51 private final RefreshAction myRefreshAction
= new RefreshAction();
52 private final Alarm myAlarm
= new Alarm(Alarm
.ThreadToUse
.SHARED_THREAD
);
53 private SmartPsiElementPointer mySmartPsiElementPointer
;
54 private final CardLayout myCardLayout
;
55 private final JPanel myTreePanel
;
56 protected String myCurrentViewType
;
58 private boolean myCachedIsValidBase
= false;
60 private final java
.util
.List
<Runnable
> myRunOnDisposeList
= new ArrayList
<Runnable
>();
61 private final HashMap
<String
, OccurenceNavigator
> myOccurrenceNavigators
= new HashMap
<String
, OccurenceNavigator
>();
63 private static final OccurenceNavigator EMPTY_NAVIGATOR
= new OccurenceNavigator() {
64 public boolean hasNextOccurence() {
68 public boolean hasPreviousOccurence() {
72 public OccurenceInfo
goNextOccurence() {
76 public OccurenceInfo
goPreviousOccurence() {
80 public String
getNextOccurenceActionName() {
84 public String
getPreviousOccurenceActionName() {
89 public HierarchyBrowserBaseEx(final Project project
, final PsiElement element
) {
92 setHierarchyBase(element
);
94 myCardLayout
= new CardLayout();
95 myTreePanel
= new JPanel(myCardLayout
);
97 createTrees(myType2TreeMap
);
99 final Enumeration
<String
> keys
= myType2TreeMap
.keys();
100 while (keys
.hasMoreElements()) {
101 final String key
= keys
.nextElement();
102 final JTree tree
= myType2TreeMap
.get(key
);
103 myOccurrenceNavigators
.put(key
, new OccurenceNavigatorSupport(tree
) {
105 protected Navigatable
createDescriptorForNode(DefaultMutableTreeNode node
) {
106 final HierarchyNodeDescriptor descriptor
= getDescriptor(node
);
107 if (descriptor
== null) return null;
108 PsiElement psiElement
= getOpenFileElementFromDescriptor(descriptor
);
109 if (psiElement
== null || !psiElement
.isValid()) return null;
110 final VirtualFile virtualFile
= psiElement
.getContainingFile().getVirtualFile();
111 if (virtualFile
== null) return null;
112 return new OpenFileDescriptor(psiElement
.getProject(), virtualFile
, psiElement
.getTextOffset());
115 public String
getNextOccurenceActionName() {
116 return HierarchyBrowserBaseEx
.this.getNextOccurenceActionNameImpl();
119 public String
getPreviousOccurenceActionName() {
120 return HierarchyBrowserBaseEx
.this.getPrevOccurenceActionNameImpl();
123 myTreePanel
.add(new JScrollPane(tree
), key
);
126 final JPanel legendPanel
= createLegendPanel();
127 final JPanel contentPanel
;
128 if (legendPanel
!= null) {
129 contentPanel
= new JPanel(new BorderLayout());
130 contentPanel
.add(myTreePanel
, BorderLayout
.CENTER
);
131 contentPanel
.add(legendPanel
, BorderLayout
.SOUTH
);
134 contentPanel
= myTreePanel
;
136 buildUi(createToolbar(getActionPlace(), HELP_ID
).getComponent(), contentPanel
);
140 protected PsiElement
getOpenFileElementFromDescriptor(@NotNull HierarchyNodeDescriptor descriptor
) {
141 return getElementFromDescriptor(descriptor
);
145 protected abstract PsiElement
getElementFromDescriptor(@NotNull HierarchyNodeDescriptor descriptor
);
148 protected abstract String
getPrevOccurenceActionNameImpl();
151 protected abstract String
getNextOccurenceActionNameImpl();
153 protected abstract void createTrees(@NotNull Map
<String
, JTree
> trees
);
156 protected abstract JPanel
createLegendPanel();
158 protected abstract boolean isApplicableElement(@NotNull PsiElement element
);
161 protected abstract HierarchyTreeStructure
createHierarchyTreeStructure(@NotNull String type
, @NotNull PsiElement psiElement
);
164 protected abstract Comparator
<NodeDescriptor
> getComparator();
167 protected abstract String
getActionPlace();
170 protected abstract String
getBrowserDataKey();
172 protected final JTree
createTree(boolean dndAware
) {
175 tree
= new DnDAwareTree(new DefaultTreeModel(new DefaultMutableTreeNode("")));
176 if (!ApplicationManager
.getApplication().isHeadlessEnvironment()) {
177 ((DnDAwareTree
)tree
).enableDnd(this);
178 DnDManager
.getInstance().registerSource(new DnDSource() {
179 public boolean canStartDragging(final DnDAction action
, final Point dragOrigin
) {
180 return getSelectedElements().length
> 0;
183 public DnDDragStartBean
startDragging(final DnDAction action
, final Point dragOrigin
) {
184 return new DnDDragStartBean(new AbstractProjectViewPane
.TransferableWrapper() {
185 public TreeNode
[] getTreeNodes() {
186 return tree
.getSelectedNodes(TreeNode
.class, null);
189 public PsiElement
[] getPsiElements() {
190 return getSelectedElements();
195 public Pair
<Image
, Point
> createDraggedImage(final DnDAction action
, final Point dragOrigin
) {
199 public void dragDropEnd() {
202 public void dropActionChanged(final int gestureModifiers
) {
208 tree
= new Tree(new DefaultTreeModel(new DefaultMutableTreeNode("")));
211 EditSourceOnDoubleClickHandler
.install(tree
);
212 myRefreshAction
.registerShortcutOn(tree
);
213 myRunOnDisposeList
.add(new Runnable() {
215 myRefreshAction
.unregisterCustomShortcutSet(tree
);
222 protected void setHierarchyBase(final PsiElement element
) {
223 mySmartPsiElementPointer
= SmartPointerManager
.getInstance(myProject
).createSmartPsiElementPointer(element
);
226 private void restoreCursor() {
227 myAlarm
.cancelAllRequests();
228 setCursor(Cursor
.getDefaultCursor());
231 private void setWaitCursor() {
232 myAlarm
.addRequest(new Runnable() {
234 setCursor(Cursor
.getPredefinedCursor(Cursor
.WAIT_CURSOR
));
239 public final void changeView(@NotNull final String typeName
) {
240 myCurrentViewType
= typeName
;
242 final PsiElement element
= mySmartPsiElementPointer
.getElement();
243 if (element
== null || !isApplicableElement(element
)) {
247 if (myContent
!= null) {
248 final String displayName
= getContentDisplayName(typeName
, element
);
249 if (displayName
!= null) {
250 myContent
.setDisplayName(displayName
);
254 myCardLayout
.show(myTreePanel
, typeName
);
256 if (!myBuilders
.containsKey(typeName
)) {
260 final JTree tree
= myType2TreeMap
.get(typeName
);
261 final DefaultTreeModel model
= new DefaultTreeModel(new DefaultMutableTreeNode(""));
262 tree
.setModel(model
);
264 PsiDocumentManager
.getInstance(myProject
).commitAllDocuments();
265 final HierarchyTreeStructure structure
= createHierarchyTreeStructure(typeName
, element
);
266 if (structure
== null) {
269 final Comparator
<NodeDescriptor
> comparator
= getComparator();
270 final HierarchyTreeBuilder builder
= new HierarchyTreeBuilder(myProject
, tree
, model
, structure
, comparator
);
272 myBuilders
.put(typeName
, builder
);
274 final HierarchyNodeDescriptor baseDescriptor
= structure
.getBaseDescriptor();
275 builder
.buildNodeForElement(baseDescriptor
);
276 final DefaultMutableTreeNode node
= builder
.getNodeForElement(baseDescriptor
);
278 final TreePath path
= new TreePath(node
.getPath());
279 tree
.expandPath(path
);
280 TreeUtil
.selectPath(tree
, path
);
288 getCurrentTree().requestFocus();
292 protected String
getContentDisplayName(@NotNull String typeName
, @NotNull PsiElement element
) {
293 if (element
instanceof PsiNamedElement
) {
294 return MessageFormat
.format(typeName
, ((PsiNamedElement
)element
).getName());
302 protected void appendActions(@NotNull DefaultActionGroup actionGroup
, String helpID
) {
303 actionGroup
.add(myRefreshAction
);
304 super.appendActions(actionGroup
, helpID
);
308 public boolean hasNextOccurence() {
309 return getOccurrenceNavigator().hasNextOccurence();
312 private OccurenceNavigator
getOccurrenceNavigator() {
313 if (myCurrentViewType
== null) {
314 return EMPTY_NAVIGATOR
;
316 final OccurenceNavigator navigator
= myOccurrenceNavigators
.get(myCurrentViewType
);
317 return navigator
!= null ? navigator
: EMPTY_NAVIGATOR
;
320 public boolean hasPreviousOccurence() {
321 return getOccurrenceNavigator().hasPreviousOccurence();
324 public OccurenceNavigator
.OccurenceInfo
goNextOccurence() {
325 return getOccurrenceNavigator().goNextOccurence();
328 public OccurenceNavigator
.OccurenceInfo
goPreviousOccurence() {
329 return getOccurrenceNavigator().goPreviousOccurence();
332 public String
getNextOccurenceActionName() {
333 return getOccurrenceNavigator().getNextOccurenceActionName();
336 public String
getPreviousOccurenceActionName() {
337 return getOccurrenceNavigator().getPreviousOccurenceActionName();
340 protected HierarchyTreeBuilder
getCurrentBuilder() {
341 return myBuilders
.get(myCurrentViewType
);
344 protected final boolean isValidBase() {
345 if (PsiDocumentManager
.getInstance(myProject
).getUncommittedDocuments().length
> 0) {
346 return myCachedIsValidBase
;
349 final PsiElement element
= mySmartPsiElementPointer
.getElement();
350 myCachedIsValidBase
= element
!= null && isApplicableElement(element
) && element
.isValid();
351 return myCachedIsValidBase
;
354 protected JTree
getCurrentTree() {
355 if (myCurrentViewType
== null) return null;
356 return myType2TreeMap
.get(myCurrentViewType
);
359 public String
getCurrentViewType() {
360 return myCurrentViewType
;
363 private PsiElement
[] getSelectedElements() {
364 HierarchyNodeDescriptor
[] descriptors
= getSelectedDescriptors();
365 ArrayList
<PsiElement
> elements
= new ArrayList
<PsiElement
>();
366 for (HierarchyNodeDescriptor descriptor
: descriptors
) {
367 PsiElement element
= getElementFromDescriptor(descriptor
);
368 if (element
!= null) elements
.add(element
);
370 return elements
.toArray(new PsiElement
[elements
.size()]);
373 public Object
getData(final String dataId
) {
374 if (getBrowserDataKey().equals(dataId
)) {
377 else if (DataConstants
.HELP_ID
.equals(dataId
)) {
380 return super.getData(dataId
);
383 public void dispose() {
384 final Collection
<HierarchyTreeBuilder
> builders
= myBuilders
.values();
385 for (final HierarchyTreeBuilder builder
: builders
) {
386 Disposer
.dispose(builder
);
388 for (final Runnable aRunOnDisposeList
: myRunOnDisposeList
) {
389 aRunOnDisposeList
.run();
391 myRunOnDisposeList
.clear();
395 protected void doRefresh(boolean currentBuilderOnly
) {
396 if (currentBuilderOnly
) LOG
.assertTrue(myCurrentViewType
!= null);
398 if (!isValidBase()) return;
400 if (getCurrentBuilder() == null) return; // seems like we are in the middle of refresh already
402 final Ref
<Object
> storedInfo
= new Ref
<Object
>();
403 if (myCurrentViewType
!= null) {
404 final HierarchyTreeBuilder builder
= getCurrentBuilder();
405 storedInfo
.set(builder
.storeExpandedAndSelectedInfo());
408 final PsiElement element
= mySmartPsiElementPointer
.getElement();
409 if (element
== null || !isApplicableElement(element
)) {
412 final String currentViewType
= myCurrentViewType
;
414 if (currentBuilderOnly
) {
415 Disposer
.dispose(getCurrentBuilder());
416 myBuilders
.remove(myCurrentViewType
);
421 setHierarchyBase(element
);
423 ApplicationManager
.getApplication().invokeLater(new Runnable() {
425 changeView(currentViewType
);
426 if (storedInfo
!= null) {
427 final HierarchyTreeBuilder builder
= getCurrentBuilder();
428 builder
.restoreExpandedAndSelectedInfo(storedInfo
.get());
434 protected class AlphaSortAction
extends ToggleAction
{
435 public AlphaSortAction() {
436 super(IdeBundle
.message("action.sort.alphabetically"), IdeBundle
.message("action.sort.alphabetically"),
437 IconLoader
.getIcon("/objectBrowser/sorted.png"));
440 public final boolean isSelected(final AnActionEvent event
) {
441 return HierarchyBrowserManager
.getInstance(myProject
).getState().SORT_ALPHABETICALLY
;
444 public final void setSelected(final AnActionEvent event
, final boolean flag
) {
445 final HierarchyBrowserManager hierarchyBrowserManager
= HierarchyBrowserManager
.getInstance(myProject
);
446 hierarchyBrowserManager
.getState().SORT_ALPHABETICALLY
= flag
;
447 final Comparator
<NodeDescriptor
> comparator
= getComparator();
448 final Collection
<HierarchyTreeBuilder
> builders
= myBuilders
.values();
449 for (final HierarchyTreeBuilder builder
: builders
) {
450 builder
.setNodeDescriptorComparator(comparator
);
454 public final void update(final AnActionEvent event
) {
456 final Presentation presentation
= event
.getPresentation();
457 presentation
.setEnabled(isValidBase());
461 protected static class BaseOnThisElementAction
extends AnAction
{
462 private final String myActionId
;
463 private final String myBrowserDataKey
;
465 public BaseOnThisElementAction(String text
, String actionId
, String browserDataKey
) {
467 myActionId
= actionId
;
468 myBrowserDataKey
= browserDataKey
;
471 public final void actionPerformed(final AnActionEvent event
) {
472 final DataContext dataContext
= event
.getDataContext();
473 final HierarchyBrowserBaseEx browser
= (HierarchyBrowserBaseEx
)dataContext
.getData(myBrowserDataKey
);
474 if (browser
== null) return;
476 final PsiElement selectedElement
= browser
.getSelectedElement();
477 if (selectedElement
== null || !browser
.isApplicableElement(selectedElement
)) return;
479 final String currentViewType
= browser
.myCurrentViewType
;
481 browser
.setHierarchyBase(selectedElement
);
483 ApplicationManager
.getApplication().invokeLater(new Runnable() {
485 browser
.changeView(correctViewType(browser
, currentViewType
));
490 protected String
correctViewType(HierarchyBrowserBaseEx browser
, String viewType
) {
494 public final void update(final AnActionEvent event
) {
495 final Presentation presentation
= event
.getPresentation();
497 registerCustomShortcutSet(ActionManager
.getInstance().getAction(myActionId
).getShortcutSet(), null);
499 final DataContext dataContext
= event
.getDataContext();
500 final HierarchyBrowserBaseEx browser
= (HierarchyBrowserBaseEx
)dataContext
.getData(myBrowserDataKey
);
501 if (browser
== null) {
502 presentation
.setVisible(false);
503 presentation
.setEnabled(false);
507 presentation
.setVisible(true);
509 final PsiElement selectedElement
= browser
.getSelectedElement();
510 if (selectedElement
== null || !browser
.isApplicableElement(selectedElement
)) {
511 presentation
.setEnabled(false);
512 presentation
.setVisible(false);
516 presentation
.setEnabled(isEnabled(browser
, selectedElement
));
517 String nonDefaultText
= getNonDefaultText(browser
, selectedElement
);
518 if (nonDefaultText
!= null) {
519 presentation
.setText(nonDefaultText
);
523 protected boolean isEnabled(@NotNull HierarchyBrowserBaseEx browser
, @NotNull PsiElement element
) {
524 return !element
.equals(browser
.mySmartPsiElementPointer
.getElement()) && element
.isValid();
528 protected String
getNonDefaultText(@NotNull HierarchyBrowserBaseEx browser
, @NotNull PsiElement element
) {
533 protected class RefreshAction
extends com
.intellij
.ide
.actions
.RefreshAction
{
534 public RefreshAction() {
535 super(IdeBundle
.message("action.refresh"), IdeBundle
.message("action.refresh"), IconLoader
.getIcon("/actions/sync.png"));
538 public final void actionPerformed(final AnActionEvent e
) {
542 public final void update(final AnActionEvent event
) {
543 final Presentation presentation
= event
.getPresentation();
544 presentation
.setEnabled(isValidBase());