blob: 9a53d07f9dd7e9afb1e16b82b1046579a89aa272 [file] [log] [blame]
/*
* Copyright 2000-2011 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.util.treeWithCheckedNodes;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vcs.impl.CollectionsDelta;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.PairProcessor;
import com.intellij.util.PlusMinus;
import com.intellij.util.Processor;
import com.intellij.util.TreeNodeState;
import com.intellij.util.containers.Convertor;
import org.jetbrains.annotations.Nullable;
import javax.swing.tree.DefaultMutableTreeNode;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* @author irengrig
* Date: 2/7/11
* Time: 10:43 AM
*
* see {@link SelectedState}
*/
public class SelectionManager {
private final SelectedState<VirtualFile> myState;
private final Convertor<DefaultMutableTreeNode, VirtualFile> myNodeConvertor;
@Nullable
private PlusMinus<VirtualFile> mySelectionChangeListener;
public SelectionManager(int selectedSize, int queueSize, final Convertor<DefaultMutableTreeNode, VirtualFile> nodeConvertor) {
myNodeConvertor = nodeConvertor;
myState = new SelectedState<VirtualFile>(selectedSize, queueSize);
}
public void toggleSelection(final DefaultMutableTreeNode node) {
final StateWorker stateWorker = new StateWorker(node, myNodeConvertor);
final VirtualFile vf = stateWorker.getVf();
if (vf == null) return;
final TreeNodeState state = getStateImpl(stateWorker);
if (TreeNodeState.HAVE_SELECTED_ABOVE.equals(state)) return;
if (TreeNodeState.CLEAR.equals(state) && (! myState.canAddSelection())) return;
final HashSet<VirtualFile> old = new HashSet<VirtualFile>(myState.getSelected());
final TreeNodeState futureState =
myState.putAndPass(vf, TreeNodeState.SELECTED.equals(state) ? TreeNodeState.CLEAR : TreeNodeState.SELECTED);
// for those possibly duplicate nodes (i.e. when we have root for module and root for VCS root, each file is shown twice in a tree ->
// clear all suspicious cached)
if (! TreeNodeState.SELECTED.equals(futureState)) {
myState.clearAllCachedMatching(new Processor<VirtualFile>() {
@Override
public boolean process(VirtualFile virtualFile) {
return VfsUtil.isAncestor(virtualFile, vf, false);
}
});
}
stateWorker.iterateParents(myState, new PairProcessor<VirtualFile, TreeNodeState>() {
@Override
public boolean process(VirtualFile virtualFile, TreeNodeState state) {
if (TreeNodeState.SELECTED.equals(futureState)) {
myState.putAndPass(virtualFile, TreeNodeState.HAVE_SELECTED_BELOW);
} else {
myState.remove(virtualFile);
}
return true;
}
});
// todo vf, vf - what is correct?
myState.clearAllCachedMatching(new Processor<VirtualFile>() {
@Override
public boolean process(VirtualFile vf) {
return VfsUtil.isAncestor(stateWorker.getVf(), vf, false);
}
});
for (VirtualFile selected : myState.getSelected()) {
if (VfsUtil.isAncestor(stateWorker.getVf(), selected, true)) {
myState.remove(selected);
}
}
final Set<VirtualFile> selectedAfter = myState.getSelected();
if (mySelectionChangeListener != null && ! old.equals(selectedAfter)) {
final Set<VirtualFile> removed = CollectionsDelta.notInSecond(old, selectedAfter);
final Set<VirtualFile> newlyAdded = CollectionsDelta.notInSecond(selectedAfter, old);
if (newlyAdded != null) {
for (VirtualFile file : newlyAdded) {
if (mySelectionChangeListener != null) {
mySelectionChangeListener.plus(file);
}
}
}
if (removed != null) {
for (VirtualFile file : removed) {
if (mySelectionChangeListener != null) {
mySelectionChangeListener.minus(file);
}
}
}
}
}
public boolean canAddSelection() {
return myState.canAddSelection();
}
public void setSelection(Collection<VirtualFile> files) {
myState.setSelection(files);
for (VirtualFile file : files) {
if (mySelectionChangeListener != null) {
mySelectionChangeListener.plus(file);
}
}
}
public TreeNodeState getState(final DefaultMutableTreeNode node) {
return getStateImpl(new StateWorker(node, myNodeConvertor));
}
private TreeNodeState getStateImpl(final StateWorker stateWorker) {
if (stateWorker.getVf() == null) return TreeNodeState.CLEAR;
final TreeNodeState stateSelf = myState.get(stateWorker.getVf());
if (stateSelf != null) return stateSelf;
final Ref<TreeNodeState> result = new Ref<TreeNodeState>();
stateWorker.iterateParents(myState, new PairProcessor<VirtualFile, TreeNodeState>() {
@Override
public boolean process(VirtualFile virtualFile, TreeNodeState state) {
if (state != null) {
if (TreeNodeState.SELECTED.equals(state) || TreeNodeState.HAVE_SELECTED_ABOVE.equals(state)) {
result.set(myState.putAndPass(stateWorker.getVf(), TreeNodeState.HAVE_SELECTED_ABOVE));
}
return false; // exit
}
return true;
}
});
if (! result.isNull()) return result.get();
for (VirtualFile selected : myState.getSelected()) {
if (VfsUtil.isAncestor(stateWorker.getVf(), selected, true)) {
return myState.putAndPass(stateWorker.getVf(), TreeNodeState.HAVE_SELECTED_BELOW);
}
}
return TreeNodeState.CLEAR;
}
public void removeSelection(final VirtualFile elementAt) {
myState.remove(elementAt);
myState.clearAllCachedMatching(new Processor<VirtualFile>() {
@Override
public boolean process(VirtualFile virtualFile) {
return VfsUtil.isAncestor(virtualFile, elementAt, false) || VfsUtil.isAncestor(elementAt, virtualFile, false);
}
});
if (mySelectionChangeListener != null) {
mySelectionChangeListener.minus(elementAt);
}
}
private static class StateWorker {
private final DefaultMutableTreeNode myNode;
private final Convertor<DefaultMutableTreeNode, VirtualFile> myConvertor;
private VirtualFile myVf;
private StateWorker(DefaultMutableTreeNode node, final Convertor<DefaultMutableTreeNode, VirtualFile> convertor) {
myNode = node;
myConvertor = convertor;
myVf = myConvertor.convert(node);
}
public VirtualFile getVf() {
return myVf;
}
public void iterateParents(final SelectedState<VirtualFile> states, final PairProcessor<VirtualFile, TreeNodeState> parentsProcessor) {
DefaultMutableTreeNode current = (DefaultMutableTreeNode) myNode.getParent();
// up cycle
while (current != null) {
final VirtualFile file = myConvertor.convert(current);
if (file == null) return;
final TreeNodeState state = states.get(file);
if (! parentsProcessor.process(file, state)) return;
current = (DefaultMutableTreeNode)current.getParent();
}
}
}
public PlusMinus<VirtualFile> getSelectionChangeListener() {
return mySelectionChangeListener;
}
public void setSelectionChangeListener(PlusMinus<VirtualFile> selectionChangeListener) {
mySelectionChangeListener = selectionChangeListener;
}
}