blob: 5f4417c920f693a2169e4f7a342890bdb1106e2a [file] [log] [blame]
/*
* Copyright 2000-2013 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.util.treeView;
import com.intellij.ide.projectView.PresentationData;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Progressive;
import com.intellij.openapi.util.ActionCallback;
import com.intellij.openapi.util.AsyncResult;
import com.intellij.openapi.util.Condition;
import com.intellij.reference.SoftReference;
import com.intellij.util.Processor;
import com.intellij.util.containers.TransferToEDTQueue;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.update.MergingUpdateQueue;
import org.jetbrains.annotations.NonNls;
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.lang.ref.WeakReference;
import java.util.*;
public class AbstractTreeBuilder implements Disposable {
private AbstractTreeUi myUi;
@NonNls private static final String TREE_BUILDER = "TreeBuilder";
public static final boolean DEFAULT_UPDATE_INACTIVE = true;
private final TransferToEDTQueue<Runnable>
myLaterInvocator = new TransferToEDTQueue<Runnable>("Tree later invocator", new Processor<Runnable>() {
@Override
public boolean process(Runnable runnable) {
runnable.run();
return true;
}
}, new Condition<Object>() {
@Override
public boolean value(Object o) {
return isDisposed();
}
}, 200);
public AbstractTreeBuilder(@NotNull JTree tree,
@NotNull DefaultTreeModel treeModel,
AbstractTreeStructure treeStructure,
@Nullable Comparator<NodeDescriptor> comparator) {
this(tree, treeModel, treeStructure, comparator, DEFAULT_UPDATE_INACTIVE);
}
public AbstractTreeBuilder(@NotNull JTree tree,
@NotNull DefaultTreeModel treeModel,
AbstractTreeStructure treeStructure,
@Nullable Comparator<NodeDescriptor> comparator,
boolean updateIfInactive) {
init(tree, treeModel, treeStructure, comparator, updateIfInactive);
}
protected AbstractTreeBuilder() {
}
protected void init(@NotNull JTree tree,
@NotNull DefaultTreeModel treeModel,
AbstractTreeStructure treeStructure,
@Nullable final Comparator<NodeDescriptor> comparator,
final boolean updateIfInactive) {
tree.putClientProperty(TREE_BUILDER, new WeakReference<AbstractTreeBuilder>(this));
myUi = createUi();
getUi().init(this, tree, treeModel, treeStructure, comparator, updateIfInactive);
setPassthroughMode(isUnitTestingMode());
}
@NotNull
protected AbstractTreeUi createUi() {
return new AbstractTreeUi();
}
public final void select(final Object element) {
if (isDisposed()) return;
getUi().userSelect(new Object[]{element}, null, false, true);
}
public final void select(final Object element, @Nullable final Runnable onDone) {
if (isDisposed()) return;
getUi().userSelect(new Object[]{element}, new UserRunnable(onDone), false, true);
}
public final void select(final Object element, @Nullable final Runnable onDone, boolean addToSelection) {
if (isDisposed()) return;
getUi().userSelect(new Object[]{element}, new UserRunnable(onDone), addToSelection, true);
}
public final void select(final Object[] elements, @Nullable final Runnable onDone) {
if (isDisposed()) return;
getUi().userSelect(elements, new UserRunnable(onDone), false, true);
}
public final void select(final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection) {
if (isDisposed()) return;
getUi().userSelect(elements, new UserRunnable(onDone), addToSelection, true);
}
public final void expand(Object element, @Nullable Runnable onDone) {
if (isDisposed()) return;
getUi().expand(element, new UserRunnable(onDone));
}
public final void expand(Object[] element, @Nullable Runnable onDone) {
if (isDisposed()) return;
getUi().expand(element, new UserRunnable(onDone));
}
public final void collapseChildren(Object element, @Nullable Runnable onDone) {
if (isDisposed()) return;
getUi().collapseChildren(element, new UserRunnable(onDone));
}
@NotNull
protected AbstractTreeNode createSearchingTreeNodeWrapper() {
return new AbstractTreeNodeWrapper();
}
@NotNull
public final AbstractTreeBuilder setClearOnHideDelay(final long clearOnHideDelay) {
if (isDisposed()) return this;
getUi().setClearOnHideDelay(clearOnHideDelay);
return this;
}
@Nullable
protected AbstractTreeUpdater createUpdater() {
if (isDisposed()) return null;
AbstractTreeUpdater updater = new AbstractTreeUpdater(this);
updater.setModalityStateComponent(MergingUpdateQueue.ANY_COMPONENT);
return updater;
}
@Nullable
protected final AbstractTreeUpdater getUpdater() {
if (isDisposed()) return null;
return getUi().getUpdater();
}
public final boolean addSubtreeToUpdateByElement(Object element) {
if (isDisposed()) return false;
AbstractTreeUpdater updater = getUpdater();
return updater != null && updater.addSubtreeToUpdateByElement(element);
}
public final void addSubtreeToUpdate(DefaultMutableTreeNode node) {
if (isDisposed()) return;
getUi().addSubtreeToUpdate(node);
}
public final void addSubtreeToUpdate(DefaultMutableTreeNode node, Runnable afterUpdate) {
if (isDisposed()) return;
getUi().addSubtreeToUpdate(node, afterUpdate);
}
@Nullable
public final DefaultMutableTreeNode getRootNode() {
if (isDisposed()) return null;
return getUi().getRootNode();
}
public final void setNodeDescriptorComparator(Comparator<NodeDescriptor> nodeDescriptorComparator) {
if (isDisposed()) return;
getUi().setNodeDescriptorComparator(nodeDescriptorComparator);
}
/**
* node descriptor getElement contract is as follows:
* 1.TreeStructure always returns & receives "treeStructure" element returned by getTreeStructureElement
* 2.Paths contain "model" element returned by getElement
*/
protected Object getTreeStructureElement(NodeDescriptor nodeDescriptor) {
return nodeDescriptor.getElement();
}
protected void updateNode(final DefaultMutableTreeNode node) {
if (isDisposed()) return;
getUi().doUpdateNode(node);
}
protected boolean validateNode(final Object child) {
return !isDisposed() && getUi().getTreeStructure().isValid(child);
}
protected boolean isDisposeOnCollapsing(NodeDescriptor nodeDescriptor) {
return true;
}
public final JTree getTree() {
if (isDisposed()) return null;
return getUi().getTree();
}
public final AbstractTreeStructure getTreeStructure() {
if (isDisposed()) return null;
return getUi().getTreeStructure();
}
public final void setTreeStructure(@NotNull AbstractTreeStructure structure) {
if (isDisposed()) return;
getUi().setTreeStructure(structure);
}
@Nullable
public Object getRootElement() {
AbstractTreeStructure structure = getTreeStructure();
return structure == null ? null : structure.getRootElement();
}
/**
* @see #queueUpdateFrom
* @deprecated
*/
public void updateFromRoot() {
queueUpdate();
}
public void initRootNode() {
if (isDisposed()) return;
getUi().initRootNode();
}
/**
* @see #queueUpdateFrom
* @deprecated
*/
@NotNull
protected ActionCallback updateFromRootCB() {
return queueUpdate();
}
@NotNull
public final ActionCallback queueUpdate() {
return queueUpdate(true);
}
@NotNull
public final ActionCallback queueUpdate(boolean withStructure) {
return queueUpdateFrom(getRootElement(), true, withStructure);
}
@NotNull
public final ActionCallback queueUpdateFrom(final Object element, final boolean forceResort) {
return queueUpdateFrom(element, forceResort, true);
}
@NotNull
public ActionCallback queueUpdateFrom(final Object element, final boolean forceResort, final boolean updateStructure) {
if (getUi() == null) return new ActionCallback.Rejected();
final ActionCallback result = new ActionCallback();
getUi().invokeLaterIfNeeded(false, new Runnable() {
@Override
public void run() {
if (updateStructure) {
if (forceResort) {
getUi().incComparatorStamp();
}
getUi().queueUpdate(element, true).notify(result);
}
else {
getUi().queueUpdate(element, false).notify(result);
}
}
});
return result;
}
/**
* @param element
* @deprecated
*/
public void buildNodeForElement(Object element) {
if (isDisposed()) return;
getUi().buildNodeForElement(element);
}
/**
* @param element
* @return
* @deprecated
*/
@Nullable
public DefaultMutableTreeNode getNodeForElement(Object element) {
if (isDisposed()) return null;
return getUi().getNodeForElement(element, false);
}
public void cleanUp() {
if (isDisposed()) return;
getUi().doCleanUp();
}
@Nullable
protected ProgressIndicator createProgressIndicator() {
return null;
}
protected void expandNodeChildren(@NotNull DefaultMutableTreeNode node) {
if (isDisposed()) return;
getUi().doExpandNodeChildren(node);
}
protected boolean isAutoExpandNode(final NodeDescriptor nodeDescriptor) {
return !isDisposed() && getRootElement() == getTreeStructureElement(nodeDescriptor);
}
protected boolean isAlwaysShowPlus(final NodeDescriptor descriptor) {
return false;
}
protected boolean isSmartExpand() {
return true;
}
public final boolean isDisposed() {
return getUi() == null || getUi().isReleaseRequested();
}
/**
* @param node
* @deprecated
*/
public final void updateSubtree(final DefaultMutableTreeNode node) {
if (isDisposed()) return;
getUi().updateSubtree(node, true);
}
public final boolean wasRootNodeInitialized() {
return !isDisposed() && getUi().wasRootNodeInitialized();
}
public final boolean isNodeBeingBuilt(final TreePath path) {
return !isDisposed() && getUi().isNodeBeingBuilt(path);
}
/**
* @param path
* @deprecated
*/
public final void buildNodeForPath(final Object[] path) {
if (isDisposed()) return;
getUi().buildNodeForPath(path);
}
/**
* @deprecated
*/
@Nullable
public final DefaultMutableTreeNode getNodeForPath(final Object[] path) {
if (isDisposed()) return null;
return getUi().getNodeForPath(path);
}
@Nullable
protected Object findNodeByElement(final Object element) {
if (isDisposed()) return null;
return getUi().findNodeByElement(element);
}
public static boolean isLoadingNode(final DefaultMutableTreeNode node) {
return AbstractTreeUi.isLoadingNode(node);
}
public boolean isChildrenResortingNeeded(NodeDescriptor descriptor) {
return true;
}
@SuppressWarnings("SpellCheckingInspection")
protected void runOnYeildingDone(Runnable onDone) {
if (isDisposed()) return;
if (myUi.isPassthroughMode() || SwingUtilities.isEventDispatchThread()) {
onDone.run();
}
else {
myLaterInvocator.offer(onDone);
}
}
protected void yield(@NotNull Runnable runnable) {
if (isDisposed()) return;
if (myUi.isPassthroughMode()) {
runnable.run();
}
else {
myLaterInvocator.offer(runnable);
}
}
public boolean isToYieldUpdateFor(DefaultMutableTreeNode node) {
return true;
}
public boolean isToEnsureSelectionOnFocusGained() {
return true;
}
protected void runBackgroundLoading(@NotNull final Runnable runnable) {
if (isDisposed()) return;
final Application app = ApplicationManager.getApplication();
if (app != null) {
app.runReadAction(new Runnable() {
@Override
public void run() {
runnable.run();
}
});
}
else {
runnable.run();
}
}
protected void updateAfterLoadedInBackground(@NotNull Runnable runnable) {
if (isDisposed()) return;
if (myUi.isPassthroughMode()) {
runnable.run();
}
else {
UIUtil.invokeLaterIfNeeded(runnable);
}
}
@SuppressWarnings({"UnusedDeclaration", "SpellCheckingInspection"})
@Deprecated
@NotNull
/**
* @deprecated use {@link #getInitialized()}
* to remove in IDEA 14
*/
public final ActionCallback getIntialized() {
return getInitialized();
}
@NotNull
public final ActionCallback getInitialized() {
if (isDisposed()) {
return new ActionCallback.Rejected();
}
return myUi.getInitialized();
}
@NotNull
public final ActionCallback getReady(Object requestor) {
if (isDisposed()) return new ActionCallback.Rejected();
return myUi.getReady(requestor);
}
protected void sortChildren(Comparator<TreeNode> nodeComparator, DefaultMutableTreeNode node, ArrayList<TreeNode> children) {
Collections.sort(children, nodeComparator);
}
public void setPassthroughMode(boolean passthrough) {
if (isDisposed()) return;
myUi.setPassthroughMode(passthrough);
}
public void expandAll(@Nullable Runnable onDone) {
if (isDisposed()) return;
getUi().expandAll(onDone);
}
@NotNull
public ActionCallback cancelUpdate() {
if (isDisposed()) return new ActionCallback.Rejected();
return getUi().cancelUpdate();
}
@NotNull
public ActionCallback batch(@NotNull Progressive progressive) {
if (isDisposed()) return new ActionCallback.Rejected();
return getUi().batch(progressive);
}
@NotNull
public AsyncResult<Object> revalidateElement(Object element) {
if (isDisposed()) return new AsyncResult.Rejected<Object>();
AbstractTreeStructure structure = getTreeStructure();
if (structure == null) return new AsyncResult.Rejected<Object>();
return structure.revalidateElement(element);
}
public static class AbstractTreeNodeWrapper extends AbstractTreeNode<Object> {
public AbstractTreeNodeWrapper() {
super(null, null);
}
@Override
@NotNull
public Collection<AbstractTreeNode> getChildren() {
return Collections.emptyList();
}
@Override
public void update(PresentationData presentation) {
}
}
public final AbstractTreeUi getUi() {
return myUi;
}
@Override
public void dispose() {
if (isDisposed()) return;
myUi.requestRelease();
}
void releaseUi() {
myUi = null;
}
protected boolean updateNodeDescriptor(@NotNull NodeDescriptor descriptor) {
if (isDisposed()) return false;
AbstractTreeUi ui = getUi();
return ui != null && descriptor.update();
}
@Nullable
public final DefaultTreeModel getTreeModel() {
if (isDisposed()) return null;
JTree tree = getTree();
return tree == null ? null : (DefaultTreeModel)tree.getModel();
}
@NotNull
public final Set<Object> getSelectedElements() {
if (isDisposed()) return Collections.emptySet();
return getUi().getSelectedElements();
}
@NotNull
public final <T> Set<T> getSelectedElements(@NotNull Class<T> elementClass) {
Set<T> result = new LinkedHashSet<T>();
for (Object o : getSelectedElements()) {
Object each = transformElement(o);
if (elementClass.isInstance(each)) {
//noinspection unchecked
result.add((T)each);
}
}
return result;
}
protected Object transformElement(Object object) {
return object;
}
public final void setCanYieldUpdate(boolean yield) {
if (isDisposed()) return;
getUi().setCanYield(yield);
}
@Nullable
public static AbstractTreeBuilder getBuilderFor(@NotNull JTree tree) {
final WeakReference ref = (WeakReference)tree.getClientProperty(TREE_BUILDER);
return (AbstractTreeBuilder)SoftReference.dereference(ref);
}
@Nullable
public final <T> Object accept(@NotNull Class<?> nodeClass, @NotNull TreeVisitor<T> visitor) {
return accept(nodeClass, getRootElement(), visitor);
}
@Nullable
private <T> Object accept(@NotNull Class<?> nodeClass, Object element, @NotNull TreeVisitor<T> visitor) {
if (element == null) {
return null;
}
//noinspection unchecked
if (nodeClass.isAssignableFrom(element.getClass()) && visitor.visit((T)element)) {
return element;
}
final Object[] children = getTreeStructure().getChildElements(element);
for (Object each : children) {
final Object childObject = accept(nodeClass, each, visitor);
if (childObject != null) return childObject;
}
return null;
}
public <T> boolean select(@NotNull Class nodeClass, @NotNull TreeVisitor<T> visitor, @Nullable Runnable onDone, boolean addToSelection) {
final Object element = accept(nodeClass, visitor);
if (element != null) {
select(element, onDone, addToSelection);
return true;
}
return false;
}
public void scrollSelectionToVisible(@Nullable Runnable onDone, boolean shouldBeCentered) {
if (isDisposed()) return;
myUi.scrollSelectionToVisible(onDone, shouldBeCentered);
}
private static boolean isUnitTestingMode() {
Application app = ApplicationManager.getApplication();
return app != null && app.isUnitTestMode();
}
public static boolean isToPaintSelection(@NotNull JTree tree) {
AbstractTreeBuilder builder = getBuilderFor(tree);
return builder == null || builder.getUi() == null || builder.getUi().isToPaintSelection();
}
class UserRunnable implements Runnable {
private final Runnable myRunnable;
public UserRunnable(Runnable runnable) {
myRunnable = runnable;
}
@Override
public void run() {
if (myRunnable != null) {
AbstractTreeUi ui = getUi();
if (ui != null) {
ui.executeUserRunnable(myRunnable);
}
else {
myRunnable.run();
}
}
}
}
public boolean isSelectionBeingAdjusted() {
AbstractTreeUi ui = getUi();
return ui != null && ui.isSelectionBeingAdjusted();
}
}