/* * Copyright 2000-2016 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.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.PairProcessor; import com.intellij.openapi.vcs.changes.ui.PlusMinus; 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; private PlusMinus<VirtualFile> mySelectionChangeListener; public SelectionManager(int selectedSize, int queueSize, final Convertor<DefaultMutableTreeNode, VirtualFile> nodeConvertor) { myNodeConvertor = nodeConvertor; myState = new SelectedState<>(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<>(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(virtualFile -> VfsUtilCore.isAncestor(virtualFile, vf, false)); } stateWorker.iterateParents(myState, (virtualFile, state1) -> { 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(vf1 -> VfsUtilCore.isAncestor(stateWorker.getVf(), vf1, false)); for (VirtualFile selected : myState.getSelected()) { if (VfsUtilCore.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<>(); stateWorker.iterateParents(myState, (virtualFile, 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 (VfsUtilCore.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(f -> VfsUtilCore.isAncestor(f, elementAt, false) || VfsUtilCore.isAncestor(elementAt, f, 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 void setSelectionChangeListener(@Nullable PlusMinus<VirtualFile> selectionChangeListener) { mySelectionChangeListener = selectionChangeListener; } }