/* * Copyright 2000-2009 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.openapi.vcs.changes.committed; import com.intellij.ide.util.treeView.TreeState; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.util.BooleanGetter; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.VcsBundle; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.ChangesUtil; import com.intellij.openapi.vcs.changes.ui.ChangesBrowserNode; import com.intellij.openapi.vcs.changes.ui.ChangesBrowserNodeRenderer; import com.intellij.openapi.vcs.changes.ui.TreeModelBuilder; import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; import com.intellij.ui.ScrollPaneFactory; import com.intellij.ui.treeStructure.Tree; import com.intellij.util.containers.ContainerUtil; import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreePath; import java.util.*; import java.util.stream.Collectors; /** * @author yole */ public class StructureFilteringStrategy implements ChangeListFilteringStrategy { private final List<ChangeListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private MyUI myUI; private final Project myProject; private final List<FilePath> mySelection = new ArrayList<>(); public StructureFilteringStrategy(final Project project) { myProject = project; } @Override public CommittedChangesFilterKey getKey() { return new CommittedChangesFilterKey(toString(), CommittedChangesFilterPriority.STRUCTURE); } public String toString() { return VcsBundle.message("filter.structure.name"); } @Nullable public JComponent getFilterUI() { if (myUI == null) { myUI = new MyUI(); } return myUI.getComponent(); } public void setFilterBase(List<CommittedChangeList> changeLists) { // todo cycle here if (myUI == null) { myUI = new MyUI(); } myUI.reset(); myUI.append(changeLists); } public void addChangeListener(ChangeListener listener) { myListeners.add(listener); } public void removeChangeListener(ChangeListener listener) { myListeners.remove(listener); } public void resetFilterBase() { myUI.reset(); } public void appendFilterBase(List<CommittedChangeList> changeLists) { myUI.append(changeLists); } @NotNull public List<CommittedChangeList> filterChangeLists(List<CommittedChangeList> changeLists) { if (mySelection.size() == 0) { return changeLists; } final ArrayList<CommittedChangeList> result = new ArrayList<>(); for (CommittedChangeList list : changeLists) { if (listMatchesSelection(list)) { result.add(list); } } return result; } private boolean listMatchesSelection(final CommittedChangeList list) { for (Change change : list.getChanges()) { FilePath path = ChangesUtil.getFilePath(change); for (FilePath selPath : mySelection) { if (path.isUnder(selPath, false)) { return true; } } } return false; } private class MyUI { private final JComponent myScrollPane; private final Tree myStructureTree; private boolean myRendererInitialized; private final Set<FilePath> myFilePaths = new HashSet<>(); private TreeState myState; public MyUI() { myStructureTree = new Tree(); myStructureTree.setRootVisible(false); myStructureTree.setShowsRootHandles(true); myStructureTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { public void valueChanged(final TreeSelectionEvent e) { final List<FilePath> filePaths = new ArrayList<>(mySelection); mySelection.clear(); final TreePath[] selectionPaths = myStructureTree.getSelectionPaths(); if (selectionPaths != null) { for (TreePath selectionPath : selectionPaths) { mySelection.addAll(getFilePathsUnder((ChangesBrowserNode<?>)selectionPath.getLastPathComponent())); } } if (Comparing.haveEqualElements(filePaths, mySelection)) return; for (ChangeListener listener : myListeners) { listener.stateChanged(new ChangeEvent(this)); } } }); myScrollPane = ScrollPaneFactory.createScrollPane(myStructureTree); } @NotNull private List<FilePath> getFilePathsUnder(@NotNull ChangesBrowserNode<?> node) { List<FilePath> result = Collections.emptyList(); Object userObject = node.getUserObject(); if (userObject instanceof FilePath) { result = ContainerUtil.list(((FilePath)userObject)); } else if (userObject instanceof Module) { result = Arrays.stream(ModuleRootManager.getInstance((Module)userObject).getContentRoots()) .map(VcsUtil::getFilePath) .collect(Collectors.toList()); } return result; } public void initRenderer() { if (!myRendererInitialized) { myRendererInitialized = true; myStructureTree.setCellRenderer(new ChangesBrowserNodeRenderer(myProject, BooleanGetter.FALSE, false)); } } public JComponent getComponent() { return myScrollPane; } public void reset() { myFilePaths.clear(); myState = TreeState.createOn(myStructureTree, (DefaultMutableTreeNode)myStructureTree.getModel().getRoot()); myStructureTree.setModel(TreeModelBuilder.buildEmpty(myProject)); } public void append(final List<CommittedChangeList> changeLists) { final TreeState localState = myState != null && myFilePaths.isEmpty() ? myState : TreeState.createOn(myStructureTree, (DefaultMutableTreeNode)myStructureTree.getModel().getRoot()); for (CommittedChangeList changeList : changeLists) { for (Change change : changeList.getChanges()) { final FilePath path = ChangesUtil.getFilePath(change); if (path.getParentPath() != null) { myFilePaths.add(path.getParentPath()); } } } myStructureTree.setModel(TreeModelBuilder.buildFromFilePaths(myProject, false, myFilePaths)); localState.applyTo(myStructureTree, (DefaultMutableTreeNode)myStructureTree.getModel().getRoot()); myStructureTree.revalidate(); myStructureTree.repaint(); initRenderer(); } } }