diff --git a/platform/usageView/src/com/intellij/usages/impl/Node.java b/platform/usageView/src/com/intellij/usages/impl/Node.java index a2663621486a..efca5fabef99 100644 --- a/platform/usageView/src/com/intellij/usages/impl/Node.java +++ b/platform/usageView/src/com/intellij/usages/impl/Node.java @@ -17,6 +17,7 @@ package com.intellij.usages.impl; import com.intellij.openapi.util.Comparing; import com.intellij.usages.UsageView; +import org.intellij.lang.annotations.MagicConstant; import org.jetbrains.annotations.NotNull; import javax.swing.tree.DefaultMutableTreeNode; @@ -26,11 +27,28 @@ import javax.swing.tree.DefaultTreeModel; * @author max */ public abstract class Node extends DefaultMutableTreeNode { - private boolean myIsValid = true; protected final DefaultTreeModel myTreeModel; - private Boolean myIsReadOnly = null; - private boolean myExcluded = false; - private String myText = null; + private String myText; + + private byte flags; + private static final int INVALID_FLAG = 0; + private static final int READ_ONLY_FLAG = 1; + private static final int READ_ONLY_COMPUTED_FLAG = 2; + private static final int EXCLUDED_FLAG = 3; + private static final int UPDATED_FLAG = 4; + + @MagicConstant(intValues = {INVALID_FLAG, READ_ONLY_FLAG, READ_ONLY_COMPUTED_FLAG, EXCLUDED_FLAG, UPDATED_FLAG}) + @interface FlagConstant {} + + private boolean isFlagSet(@FlagConstant int flag) { + int state = flags >> flag; + return (state & 1) != 0; + } + + private void setFlag(@FlagConstant int flag, boolean value) { + int state = value ? 1 : 0; + flags = (byte)(flags & ~(1 << flag) | state << flag); + } protected Node(@NotNull DefaultTreeModel model) { myTreeModel = model; @@ -44,35 +62,57 @@ public abstract class Node extends DefaultMutableTreeNode { protected abstract boolean isDataValid(); protected abstract boolean isDataReadOnly(); protected abstract boolean isDataExcluded(); - protected abstract String getText(UsageView view); + protected abstract String getText(@NotNull UsageView view); public final boolean isValid() { - return myIsValid; + return !isFlagSet(INVALID_FLAG); } public final boolean isReadOnly() { - if (myIsReadOnly == null) myIsReadOnly = Boolean.valueOf(isDataReadOnly()); - return myIsReadOnly.booleanValue(); + boolean result; + boolean computed = isFlagSet(READ_ONLY_COMPUTED_FLAG); + if (computed) { + result = isFlagSet(READ_ONLY_FLAG); + } + else { + result = isDataReadOnly(); + setFlag(READ_ONLY_COMPUTED_FLAG, true); + setFlag(READ_ONLY_FLAG, result); + } + return result; } public final boolean isExcluded() { - return myExcluded; + return isFlagSet(EXCLUDED_FLAG); } - public final void update(UsageView view) { + public final void update(@NotNull UsageView view) { boolean isDataValid = isDataValid(); boolean isReadOnly = isDataReadOnly(); boolean isExcluded = isDataExcluded(); String text = getText(view); - if (isDataValid != myIsValid || myIsReadOnly == null || isReadOnly != myIsReadOnly.booleanValue() || isExcluded != myExcluded || - !Comparing.equal(myText, text)) { - myIsValid = isDataValid; - myExcluded = isExcluded; - myIsReadOnly = Boolean.valueOf(isReadOnly); + + boolean cachedValid = isValid(); + boolean cachedExcluded = isFlagSet(EXCLUDED_FLAG); + boolean cachedReadOnly = isFlagSet(READ_ONLY_FLAG); + + if (isDataValid != cachedValid || isReadOnly != cachedReadOnly || isExcluded != cachedExcluded || !Comparing.equal(myText, text)) { + setFlag(INVALID_FLAG, !isDataValid); + setFlag(READ_ONLY_FLAG, isReadOnly); + setFlag(EXCLUDED_FLAG, isExcluded); + myText = text; updateNotify(); myTreeModel.nodeChanged(this); } + setFlag(UPDATED_FLAG, true); + } + + public void markNeedUpdate() { + setFlag(UPDATED_FLAG, false); + } + public boolean needsUpdate() { + return !isFlagSet(UPDATED_FLAG); } /** diff --git a/platform/usageView/src/com/intellij/usages/impl/UsageViewImpl.java b/platform/usageView/src/com/intellij/usages/impl/UsageViewImpl.java index 95354cb16be0..8825c3746be7 100644 --- a/platform/usageView/src/com/intellij/usages/impl/UsageViewImpl.java +++ b/platform/usageView/src/com/intellij/usages/impl/UsageViewImpl.java @@ -65,10 +65,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.event.TreeSelectionEvent; -import javax.swing.event.TreeSelectionListener; +import javax.swing.event.*; import javax.swing.tree.*; import java.awt.*; import java.awt.datatransfer.StringSelection; @@ -143,10 +140,10 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra private final Object lock = new Object(); private Splitter myPreviewSplitter; - public UsageViewImpl(@NotNull final Project project, - @NotNull UsageViewPresentation presentation, - @NotNull UsageTarget[] targets, - Factory usageSearcherFactory) { + UsageViewImpl(@NotNull final Project project, + @NotNull UsageViewPresentation presentation, + @NotNull UsageTarget[] targets, + Factory usageSearcherFactory) { myPresentation = presentation; myTargets = targets; myUsageSearcherFactory = usageSearcherFactory; @@ -416,6 +413,24 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra TreeUtil.selectFirstNode(myTree); PopupHandler.installPopupHandler(myTree, IdeActions.GROUP_USAGE_VIEW_POPUP, ActionPlaces.USAGE_VIEW_POPUP); + + myTree.addTreeExpansionListener(new TreeExpansionListener() { + @Override + public void treeExpanded(TreeExpansionEvent event) { + TreePath path = event.getPath(); + Object component = path.getLastPathComponent(); + if (!(component instanceof Node)) return; + Node node = (Node)component; + if (node.needsUpdate()) { + checkNodeValidity(node, path); + } + } + + @Override + public void treeCollapsed(TreeExpansionEvent event) { + } + }); + //TODO: install speed search. Not in openapi though. It makes sense to create a common TreeEnchancer service. } @@ -541,7 +556,6 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra actionsManager.createPrevOccurenceAction(myRootPanel), actionsManager.createNextOccurenceAction(myRootPanel), actionsManager.installAutoscrollToSourceHandler(myProject, myTree, new MyAutoScrollToSourceOptionProvider()), - //createImportToFavorites(), actionsManager.createExportToTextFileAction(myTextFileExporter), actionsManager.createHelpAction(HELP_ID) }; @@ -906,7 +920,8 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra private void updateImmediately() { if (myProject.isDisposed()) return; - checkNodeValidity((DefaultMutableTreeNode)myTree.getModel().getRoot()); + TreeNode root = (TreeNode)myTree.getModel().getRoot(); + checkNodeValidity(root, new TreePath(root)); updateOnSelectionChanged(); } @@ -917,12 +932,20 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra } } - private void checkNodeValidity(@NotNull DefaultMutableTreeNode node) { - Enumeration enumeration = node.children(); - while (enumeration.hasMoreElements()) { - checkNodeValidity((DefaultMutableTreeNode)enumeration.nextElement()); + private void checkNodeValidity(@NotNull TreeNode node, @NotNull TreePath path) { + if (node instanceof Node && node != getModelRoot()) { + ((Node)node).update(this); + } + if (myTree.isCollapsed(path)) { + if (node instanceof Node) { + ((Node)node).markNeedUpdate(); + } + return; // optimization: do not call expensive update() on invisible node + } + for (int i=0; i < node.getChildCount(); i++) { + TreeNode child = node.getChildAt(i); + checkNodeValidity(child, path.pathByAddingChild(child)); } - if (node instanceof Node && node != getModelRoot()) ((Node)node).update(this); } private void updateLater() {