Hierarchy providers refactored
[fedora-idea.git] / lang-impl / src / com / intellij / ide / hierarchy / HierarchyBrowserBaseEx.java
blob825a601659f17cb66757644ef994f6f6cc697fa8
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;
33 import javax.swing.*;
34 import javax.swing.tree.DefaultMutableTreeNode;
35 import javax.swing.tree.DefaultTreeModel;
36 import javax.swing.tree.TreeNode;
37 import javax.swing.tree.TreePath;
38 import java.awt.*;
39 import java.text.MessageFormat;
40 import java.util.*;
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() {
65 return false;
68 public boolean hasPreviousOccurence() {
69 return false;
72 public OccurenceInfo goNextOccurence() {
73 return null;
76 public OccurenceInfo goPreviousOccurence() {
77 return null;
80 public String getNextOccurenceActionName() {
81 return "";
84 public String getPreviousOccurenceActionName() {
85 return "";
89 public HierarchyBrowserBaseEx(final Project project, final PsiElement element) {
90 super(project);
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) {
104 @Nullable
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);
133 else {
134 contentPanel = myTreePanel;
136 buildUi(createToolbar(getActionPlace(), HELP_ID).getComponent(), contentPanel);
139 @Nullable
140 protected PsiElement getOpenFileElementFromDescriptor(@NotNull HierarchyNodeDescriptor descriptor) {
141 return getElementFromDescriptor(descriptor);
144 @Nullable
145 protected abstract PsiElement getElementFromDescriptor(@NotNull HierarchyNodeDescriptor descriptor);
147 @NotNull
148 protected abstract String getPrevOccurenceActionNameImpl();
150 @NotNull
151 protected abstract String getNextOccurenceActionNameImpl();
153 protected abstract void createTrees(@NotNull Map<String, JTree> trees);
155 @Nullable
156 protected abstract JPanel createLegendPanel();
158 protected abstract boolean isApplicableElement(@NotNull PsiElement element);
160 @Nullable
161 protected abstract HierarchyTreeStructure createHierarchyTreeStructure(@NotNull String type, @NotNull PsiElement psiElement);
163 @Nullable
164 protected abstract Comparator<NodeDescriptor> getComparator();
166 @NotNull
167 protected abstract String getActionPlace();
169 @NotNull
170 protected abstract String getBrowserDataKey();
172 protected final JTree createTree(boolean dndAware) {
173 final Tree tree;
174 if (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) {
196 return null;
199 public void dragDropEnd() {
202 public void dropActionChanged(final int gestureModifiers) {
204 }, tree);
207 else {
208 tree = new Tree(new DefaultTreeModel(new DefaultMutableTreeNode("")));
210 configureTree(tree);
211 EditSourceOnDoubleClickHandler.install(tree);
212 myRefreshAction.registerShortcutOn(tree);
213 myRunOnDisposeList.add(new Runnable() {
214 public void run() {
215 myRefreshAction.unregisterCustomShortcutSet(tree);
219 return 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() {
233 public void run() {
234 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
236 }, 100);
239 public final void changeView(@NotNull final String typeName) {
240 myCurrentViewType = typeName;
242 final PsiElement element = mySmartPsiElementPointer.getElement();
243 if (element == null || !isApplicableElement(element)) {
244 return;
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)) {
257 try {
258 setWaitCursor();
259 // create builder
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) {
267 return;
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);
277 if (node != null) {
278 final TreePath path = new TreePath(node.getPath());
279 tree.expandPath(path);
280 TreeUtil.selectPath(tree, path);
283 finally {
284 restoreCursor();
288 getCurrentTree().requestFocus();
291 @Nullable
292 protected String getContentDisplayName(@NotNull String typeName, @NotNull PsiElement element) {
293 if (element instanceof PsiNamedElement) {
294 return MessageFormat.format(typeName, ((PsiNamedElement)element).getName());
296 else {
297 return null;
301 @Override
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)) {
375 return this;
377 else if (DataConstants.HELP_ID.equals(dataId)) {
378 return HELP_ID;
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();
392 myBuilders.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)) {
410 return;
412 final String currentViewType = myCurrentViewType;
414 if (currentBuilderOnly) {
415 Disposer.dispose(getCurrentBuilder());
416 myBuilders.remove(myCurrentViewType);
418 else {
419 dispose();
421 setHierarchyBase(element);
422 validate();
423 ApplicationManager.getApplication().invokeLater(new Runnable() {
424 public void run() {
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) {
455 super.update(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) {
466 super(text);
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;
480 browser.dispose();
481 browser.setHierarchyBase(selectedElement);
482 browser.validate();
483 ApplicationManager.getApplication().invokeLater(new Runnable() {
484 public void run() {
485 browser.changeView(correctViewType(browser, currentViewType));
490 protected String correctViewType(HierarchyBrowserBaseEx browser, String viewType) {
491 return 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);
504 return;
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);
513 return;
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();
527 @Nullable
528 protected String getNonDefaultText(@NotNull HierarchyBrowserBaseEx browser, @NotNull PsiElement element) {
529 return null;
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) {
539 doRefresh(false);
542 public final void update(final AnActionEvent event) {
543 final Presentation presentation = event.getPresentation();
544 presentation.setEnabled(isValidBase());