From 3ed4564502a9d45458e0b260b1130e1baa84df41 Mon Sep 17 00:00:00 2001 From: irengrig Date: Mon, 7 Feb 2011 14:45:29 +0300 Subject: [PATCH] git log: test for checkbox tree for structure chooser (tree where you can select only limited number of nodes) --- .../src/com/intellij/util/TreeNodeState.java | 25 +++ .../treeWithCheckedNodes/SelectedState.java | 94 +++++++++++ .../SelectionManager.java | 151 ++++++++++++++++++ 3 files changed, 270 insertions(+) create mode 100644 platform/util/src/com/intellij/util/TreeNodeState.java create mode 100644 platform/vcs-impl/src/com/intellij/util/treeWithCheckedNodes/SelectedState.java create mode 100644 platform/vcs-impl/src/com/intellij/util/treeWithCheckedNodes/SelectionManager.java diff --git a/platform/util/src/com/intellij/util/TreeNodeState.java b/platform/util/src/com/intellij/util/TreeNodeState.java new file mode 100644 index 000000000000..0609d5cb7ebf --- /dev/null +++ b/platform/util/src/com/intellij/util/TreeNodeState.java @@ -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 +} diff --git a/platform/vcs-impl/src/com/intellij/util/treeWithCheckedNodes/SelectedState.java b/platform/vcs-impl/src/com/intellij/util/treeWithCheckedNodes/SelectedState.java new file mode 100644 index 000000000000..76fd0120a5e5 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/util/treeWithCheckedNodes/SelectedState.java @@ -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 { + private final Set mySelected; + private final SLRUMap myCache; + private final int mySelectedSize; + + public SelectedState(final int selectedSize, final int queueSize) { + mySelectedSize = selectedSize; + assert queueSize > 0; + mySelected = new HashSet(); + myCache = new SLRUMap(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 processor) { + final Set> entries = myCache.entrySet(); + for (Map.Entry 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 getSelected() { + return Collections.unmodifiableSet(mySelected); + } +} diff --git a/platform/vcs-impl/src/com/intellij/util/treeWithCheckedNodes/SelectionManager.java b/platform/vcs-impl/src/com/intellij/util/treeWithCheckedNodes/SelectionManager.java new file mode 100644 index 000000000000..c5daa37a0793 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/util/treeWithCheckedNodes/SelectionManager.java @@ -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 myState; + private final Convertor myNodeConvertor; + + public SelectionManager(int selectedSize, int queueSize, final Convertor nodeConvertor) { + myNodeConvertor = nodeConvertor; + myState = new SelectedState(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() { + @Override + public boolean process(VirtualFile virtualFile) { + return VfsUtil.isAncestor(virtualFile, stateWorker.getVf(), false); + } + }); + } + stateWorker.iterateParents(myState, new PairProcessor() { + @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() { + @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 result = new Ref(); + stateWorker.iterateParents(myState, new PairProcessor() { + @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 myConvertor; + private VirtualFile myVf; + + private StateWorker(DefaultMutableTreeNode node, final Convertor convertor) { + myNode = node; + myConvertor = convertor; + myVf = myConvertor.convert(node); + } + + public VirtualFile getVf() { + return myVf; + } + + public void iterateParents(final SelectedState states, final PairProcessor 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(); + } + } + } +}