git log: test for checkbox tree for structure chooser (tree where you can select only limited number of nodes)

This commit is contained in:
irengrig
2011-02-07 14:45:29 +03:00
parent e2e4719f73
commit 3ed4564502
3 changed files with 270 additions and 0 deletions
@@ -0,0 +1,25 @@
/*
* 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;
/**
* @author irengrig
* Date: 2/4/11
* Time: 3:36 PM
*/
public enum TreeNodeState {
SELECTED,CLEAR,HAVE_SELECTED_ABOVE,HAVE_SELECTED_BELOW
}
@@ -0,0 +1,94 @@
/*
* 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.util.Processor;
import com.intellij.util.TreeNodeState;
import com.intellij.util.containers.SLRUMap;
import com.intellij.util.containers.hash.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
/**
* @author irengrig
* Date: 2/7/11
* Time: 10:40 AM
*
* We can have only limited number of nodes selected in a tree;
* and children of selected nodes (any level) cannot change their state
* used together with {@link SelectionManager}
*/
public class SelectedState<T> {
private final Set<T> mySelected;
private final SLRUMap<T, TreeNodeState> myCache;
private final int mySelectedSize;
public SelectedState(final int selectedSize, final int queueSize) {
mySelectedSize = selectedSize;
assert queueSize > 0;
mySelected = new HashSet<T>();
myCache = new SLRUMap<T, TreeNodeState>(queueSize,queueSize);
}
@Nullable
public TreeNodeState get(final T node) {
if (mySelected.contains(node)) return TreeNodeState.SELECTED;
return myCache.get(node);
}
public void clear(final T node) {
myCache.remove(node);
mySelected.remove(node);
}
public void clearAllCachedMatching(final Processor<T> processor) {
final Set<Map.Entry<T, TreeNodeState>> entries = myCache.entrySet();
for (Map.Entry<T, TreeNodeState> entry: entries){
if(processor.process(entry.getKey())) {
myCache.remove(entry.getKey());
}
}
}
public void remove(final T node) {
mySelected.remove(node);
myCache.remove(node);
}
@NotNull
public TreeNodeState putAndPass(final T node, @NotNull final TreeNodeState state) {
if (TreeNodeState.SELECTED.equals(state)) {
mySelected.add(node);
myCache.remove(node);
} else {
mySelected.remove(node);
myCache.put(node, state);
}
return state;
}
public boolean canAddSelection() {
return mySelected.size() < mySelectedSize;
}
public Set<T> getSelected() {
return Collections.unmodifiableSet(mySelected);
}
}
@@ -0,0 +1,151 @@
/*
* 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.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.PairProcessor;
import com.intellij.util.Processor;
import com.intellij.util.TreeNodeState;
import com.intellij.util.containers.Convertor;
import javax.swing.tree.DefaultMutableTreeNode;
/**
* @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;
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);
if (stateWorker.getVf() == null) return;
final TreeNodeState state = getStateImpl(stateWorker);
if (TreeNodeState.HAVE_SELECTED_ABOVE.equals(state)) return;
if (TreeNodeState.CLEAR.equals(state) && (! myState.canAddSelection())) return;
final TreeNodeState futureState =
myState.putAndPass(stateWorker.getVf(), 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, stateWorker.getVf(), 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;
}
});
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);
}
}
}
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;
}
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();
}
}
}
}