blob: dd46f6496c4678e36418965dc43c04b0bddc2a25 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.ide.projectView;
import com.intellij.ide.UiActivity;
import com.intellij.ide.UiActivityMonitor;
import com.intellij.ide.favoritesTreeView.FavoritesTreeNodeDescriptor;
import com.intellij.ide.util.treeView.AbstractTreeBuilder;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.ide.util.treeView.AbstractTreeStructure;
import com.intellij.ide.util.treeView.NodeDescriptor;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Progressive;
import com.intellij.openapi.progress.util.StatusBarProgress;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.*;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.FocusRequestor;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.Consumer;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public abstract class BaseProjectTreeBuilder extends AbstractTreeBuilder {
protected final Project myProject;
public BaseProjectTreeBuilder(@NotNull Project project,
@NotNull JTree tree,
@NotNull DefaultTreeModel treeModel,
@NotNull AbstractTreeStructure treeStructure,
@Nullable Comparator<NodeDescriptor> comparator) {
init(tree, treeModel, treeStructure, comparator, DEFAULT_UPDATE_INACTIVE);
myProject = project;
}
@NotNull
@Override
public AsyncResult<Object> revalidateElement(Object element) {
final AsyncResult<Object> result = new AsyncResult<Object>();
if (element instanceof AbstractTreeNode) {
AbstractTreeNode node = (AbstractTreeNode)element;
final Object value = node.getValue();
final ActionCallback callback = new ActionCallback();
final VirtualFile virtualFile = PsiUtilCore.getVirtualFile(ObjectUtils.tryCast(value, PsiElement.class));
final FocusRequestor focusRequestor = IdeFocusManager.getInstance(myProject).getFurtherRequestor();
batch(new Progressive() {
@Override
public void run(@NotNull ProgressIndicator indicator) {
final Ref<Object> target = new Ref<Object>();
_select(value, virtualFile, false, Conditions.<AbstractTreeNode>alwaysTrue(), callback, indicator, target, focusRequestor, false);
callback.doWhenDone(new Runnable() {
@Override
public void run() {
result.setDone(target.get());
}
}).doWhenRejected(new Runnable() {
@Override
public void run() {
result.setRejected();
}
});
}
});
}
else {
result.setRejected();
}
return result;
}
@Override
protected boolean isAlwaysShowPlus(NodeDescriptor nodeDescriptor) {
return ((AbstractTreeNode)nodeDescriptor).isAlwaysShowPlus();
}
@Override
protected boolean isAutoExpandNode(NodeDescriptor nodeDescriptor) {
return nodeDescriptor.getParentDescriptor() == null || ((AbstractTreeNode)nodeDescriptor).isAlwaysExpand();
}
@Override
protected final void expandNodeChildren(@NotNull final DefaultMutableTreeNode node) {
final NodeDescriptor userObject = (NodeDescriptor)node.getUserObject();
if (userObject == null) return;
Object element = userObject.getElement();
VirtualFile virtualFile = getFileToRefresh(element);
super.expandNodeChildren(node);
if (virtualFile != null) {
virtualFile.refresh(true, false);
}
}
private static VirtualFile getFileToRefresh(Object element) {
Object object = element;
if (element instanceof AbstractTreeNode) {
object = ((AbstractTreeNode)element).getValue();
}
return object instanceof PsiDirectory
? ((PsiDirectory)object).getVirtualFile()
: object instanceof PsiFile ? ((PsiFile)object).getVirtualFile() : null;
}
@NotNull
private static List<AbstractTreeNode> collectChildren(@NotNull DefaultMutableTreeNode node) {
int childCount = node.getChildCount();
List<AbstractTreeNode> result = new ArrayList<AbstractTreeNode>(childCount);
for (int i = 0; i < childCount; i++) {
TreeNode childAt = node.getChildAt(i);
DefaultMutableTreeNode defaultMutableTreeNode = (DefaultMutableTreeNode)childAt;
if (defaultMutableTreeNode.getUserObject() instanceof AbstractTreeNode) {
AbstractTreeNode treeNode = (AbstractTreeNode)defaultMutableTreeNode.getUserObject();
result.add(treeNode);
}
else if (defaultMutableTreeNode.getUserObject() instanceof FavoritesTreeNodeDescriptor) {
AbstractTreeNode treeNode = ((FavoritesTreeNodeDescriptor)defaultMutableTreeNode.getUserObject()).getElement();
result.add(treeNode);
}
}
return result;
}
@NotNull
public ActionCallback select(Object element, VirtualFile file, final boolean requestFocus) {
return _select(element, file, requestFocus, Conditions.<AbstractTreeNode>alwaysTrue());
}
public ActionCallback selectInWidth(final Object element,
final boolean requestFocus,
final Condition<AbstractTreeNode> nonStopCondition) {
return _select(element, null, requestFocus, nonStopCondition);
}
@NotNull
private ActionCallback _select(final Object element,
final VirtualFile file,
final boolean requestFocus,
final Condition<AbstractTreeNode> nonStopCondition) {
final ActionCallback result = new ActionCallback();
final FocusRequestor requestor = IdeFocusManager.getInstance(myProject).getFurtherRequestor();
UiActivityMonitor.getInstance().addActivity(myProject, new UiActivity.AsyncBgOperation("projectViewSelect"), getUpdater().getModalityState());
cancelUpdate().doWhenDone(new Runnable() {
@Override
public void run() {
batch(new Progressive() {
@Override
public void run(@NotNull ProgressIndicator indicator) {
_select(element, file, requestFocus, nonStopCondition, result, indicator, null, requestor, false);
UiActivityMonitor.getInstance().removeActivity(myProject, new UiActivity.AsyncBgOperation("projectViewSelect"));
}
});
}
});
return result;
}
private void _select(final Object element,
final VirtualFile file,
final boolean requestFocus,
final Condition<AbstractTreeNode> nonStopCondition,
final ActionCallback result,
@NotNull final ProgressIndicator indicator,
@Nullable final Ref<Object> virtualSelectTarget,
final FocusRequestor focusRequestor,
final boolean isSecondAttempt) {
final AbstractTreeNode alreadySelected = alreadySelectedNode(element);
final Runnable onDone = new Runnable() {
@Override
public void run() {
if (requestFocus && virtualSelectTarget == null && getUi().isReady()) {
focusRequestor.requestFocus(getTree(), true);
}
result.setDone();
}
};
final Condition<AbstractTreeNode> condition = new Condition<AbstractTreeNode>() {
@Override
public boolean value(AbstractTreeNode abstractTreeNode) {
return !result.isProcessed() && nonStopCondition.value(abstractTreeNode);
}
};
if (alreadySelected == null) {
expandPathTo(file, (AbstractTreeNode)getTreeStructure().getRootElement(), element, condition, indicator, virtualSelectTarget)
.doWhenDone(new Consumer<AbstractTreeNode>() {
@Override
public void consume(AbstractTreeNode node) {
if (virtualSelectTarget == null) {
select(node, onDone);
}
else {
onDone.run();
}
}
}).doWhenRejected(new Runnable() {
@Override
public void run() {
if (isSecondAttempt) {
result.setRejected();
} else {
_select(file, file, requestFocus, nonStopCondition, result, indicator, virtualSelectTarget, focusRequestor, true);
}
}
});
}
else if (virtualSelectTarget == null && getTree().getSelectionPaths().length == 1) {
select(alreadySelected, onDone);
}
else {
onDone.run();
}
}
private AbstractTreeNode alreadySelectedNode(final Object element) {
final TreePath[] selectionPaths = getTree().getSelectionPaths();
if (selectionPaths == null || selectionPaths.length == 0) {
return null;
}
for (TreePath selectionPath : selectionPaths) {
Object selected = selectionPath.getLastPathComponent();
if (elementIsEqualTo(selected, element)) {
return ((AbstractTreeNode)((DefaultMutableTreeNode)selected).getUserObject());
}
}
return null;
}
private static boolean elementIsEqualTo(final Object node, final Object element) {
if (node instanceof DefaultMutableTreeNode) {
final Object userObject = ((DefaultMutableTreeNode)node).getUserObject();
if (userObject instanceof ProjectViewNode) {
final AbstractTreeNode projectViewNode = (ProjectViewNode)userObject;
return projectViewNode.canRepresent(element);
}
}
return false;
}
@NotNull
private AsyncResult<AbstractTreeNode> expandPathTo(final VirtualFile file,
@NotNull final AbstractTreeNode root,
final Object element,
@NotNull final Condition<AbstractTreeNode> nonStopCondition,
@NotNull final ProgressIndicator indicator,
@Nullable final Ref<Object> target) {
final AsyncResult<AbstractTreeNode> async = new AsyncResult<AbstractTreeNode>();
if (root.canRepresent(element)) {
if (target == null) {
expand(root, new Runnable() {
@Override
public void run() {
async.setDone(root);
}
});
}
else {
target.set(root);
async.setDone(root);
}
return async;
}
if (root instanceof ProjectViewNode && file != null && !((ProjectViewNode)root).contains(file)) {
async.setRejected();
return async;
}
if (target == null) {
expand(root, new Runnable() {
@Override
public void run() {
indicator.checkCanceled();
final DefaultMutableTreeNode rootNode = getNodeForElement(root);
if (rootNode != null) {
final List<AbstractTreeNode> kids = collectChildren(rootNode);
expandChild(kids, 0, nonStopCondition, file, element, async, indicator, target);
}
else {
async.setRejected();
}
}
});
}
else {
if (indicator.isCanceled()) {
async.setRejected();
}
else {
final DefaultMutableTreeNode rootNode = getNodeForElement(root);
final ArrayList<AbstractTreeNode> kids = new ArrayList<AbstractTreeNode>();
if (rootNode != null && getTree().isExpanded(new TreePath(rootNode.getPath()))) {
kids.addAll(collectChildren(rootNode));
}
else {
List<Object> list = Arrays.asList(getTreeStructure().getChildElements(root));
for (Object each : list) {
kids.add((AbstractTreeNode)each);
}
}
yield(new Runnable() {
@Override
public void run() {
if (isDisposed()) return;
expandChild(kids, 0, nonStopCondition, file, element, async, indicator, target);
}
});
}
}
return async;
}
private void expandChild(@NotNull final List<AbstractTreeNode> kids,
int i,
@NotNull final Condition<AbstractTreeNode> nonStopCondition,
final VirtualFile file,
final Object element,
@NotNull final AsyncResult<AbstractTreeNode> async,
@NotNull final ProgressIndicator indicator,
final Ref<Object> virtualSelectTarget) {
while (i < kids.size()) {
final AbstractTreeNode eachKid = kids.get(i);
final boolean[] nodeWasCollapsed = {true};
final DefaultMutableTreeNode nodeForElement = getNodeForElement(eachKid);
if (nodeForElement != null) {
nodeWasCollapsed[0] = getTree().isCollapsed(new TreePath(nodeForElement.getPath()));
}
if (nonStopCondition.value(eachKid)) {
final AsyncResult<AbstractTreeNode> result = expandPathTo(file, eachKid, element, nonStopCondition, indicator, virtualSelectTarget);
result.doWhenDone(new Consumer<AbstractTreeNode>() {
@Override
public void consume(AbstractTreeNode abstractTreeNode) {
indicator.checkCanceled();
async.setDone(abstractTreeNode);
}
});
if (!result.isProcessed()) {
final int next = i + 1;
result.doWhenRejected(new Runnable() {
@Override
public void run() {
indicator.checkCanceled();
if (nodeWasCollapsed[0] && virtualSelectTarget == null) {
collapseChildren(eachKid, null);
}
expandChild(kids, next, nonStopCondition, file, element, async, indicator, virtualSelectTarget);
}
});
return;
} else {
if (result.isRejected()) {
indicator.checkCanceled();
if (nodeWasCollapsed[0] && virtualSelectTarget == null) {
collapseChildren(eachKid, null);
}
i++;
} else {
return;
}
}
} else {
//filter tells us to stop here (for instance, in case of module nodes)
break;
}
}
async.setRejected();
}
@Override
protected boolean validateNode(final Object child) {
if (child == null) {
return false;
}
if (child instanceof ProjectViewNode) {
final ProjectViewNode projectViewNode = (ProjectViewNode)child;
return projectViewNode.validate();
}
return true;
}
@Override
@NotNull
protected ProgressIndicator createProgressIndicator() {
return new StatusBarProgress();
}
}