blob: 139f49b02385d25f2c7fb1b48ed3a00b300156f8 [file] [log] [blame]
/*
* 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.ide.projectView.impl;
import com.intellij.ProjectTopics;
import com.intellij.ide.CopyPasteUtil;
import com.intellij.ide.bookmarks.Bookmark;
import com.intellij.ide.bookmarks.BookmarksListener;
import com.intellij.ide.projectView.BaseProjectTreeBuilder;
import com.intellij.ide.projectView.ProjectViewNode;
import com.intellij.ide.projectView.ProjectViewPsiTreeChangeListener;
import com.intellij.ide.util.treeView.AbstractTreeStructure;
import com.intellij.ide.util.treeView.AbstractTreeUpdater;
import com.intellij.ide.util.treeView.NodeDescriptor;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModuleRootAdapter;
import com.intellij.openapi.roots.ModuleRootEvent;
import com.intellij.openapi.vcs.FileStatusListener;
import com.intellij.openapi.vcs.FileStatusManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.problems.WolfTheProblemSolver;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiManager;
import com.intellij.util.Alarm;
import com.intellij.util.SmartList;
import com.intellij.util.messages.MessageBusConnection;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import java.util.Collection;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Set;
public class ProjectTreeBuilder extends BaseProjectTreeBuilder {
public ProjectTreeBuilder(@NotNull Project project,
@NotNull JTree tree,
@NotNull DefaultTreeModel treeModel,
@Nullable Comparator<NodeDescriptor> comparator,
@NotNull ProjectAbstractTreeStructureBase treeStructure) {
super(project, tree, treeModel, treeStructure, comparator);
final MessageBusConnection connection = project.getMessageBus().connect(this);
connection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() {
@Override
public void rootsChanged(ModuleRootEvent event) {
queueUpdate();
}
});
connection.subscribe(BookmarksListener.TOPIC, new MyBookmarksListener());
PsiManager.getInstance(project).addPsiTreeChangeListener(createPsiTreeChangeListener(project), this);
FileStatusManager.getInstance(project).addFileStatusListener(new MyFileStatusListener(), this);
CopyPasteManager.getInstance().addContentChangedListener(new CopyPasteUtil.DefaultCopyPasteListener(getUpdater()), this);
WolfTheProblemSolver.getInstance(project).addProblemListener(new MyProblemListener(), this);
setCanYieldUpdate(true);
initRootNode();
}
/**
* Creates psi tree changes listener. This method will be invoked in constructor of ProjectTreeBuilder
* thus builder object will be not completely initialized
* @param project Project
* @return Listener
*/
protected ProjectViewPsiTreeChangeListener createPsiTreeChangeListener(final Project project) {
return new ProjectTreeBuilderPsiListener(project);
}
protected class ProjectTreeBuilderPsiListener extends ProjectViewPsiTreeChangeListener {
public ProjectTreeBuilderPsiListener(final Project project) {
super(project);
}
@Override
protected DefaultMutableTreeNode getRootNode(){
return ProjectTreeBuilder.this.getRootNode();
}
@Override
protected AbstractTreeUpdater getUpdater() {
return ProjectTreeBuilder.this.getUpdater();
}
@Override
protected boolean isFlattenPackages(){
AbstractTreeStructure structure = getTreeStructure();
return structure instanceof AbstractProjectTreeStructure && ((AbstractProjectTreeStructure)structure).isFlattenPackages();
}
}
private final class MyBookmarksListener implements BookmarksListener {
@Override
public void bookmarkAdded(@NotNull Bookmark b) {
updateForFile(b.getFile());
}
@Override
public void bookmarkRemoved(@NotNull Bookmark b) {
updateForFile(b.getFile());
}
@Override
public void bookmarkChanged(@NotNull Bookmark b) {
updateForFile(b.getFile());
}
private void updateForFile(@NotNull VirtualFile file) {
PsiElement element = findPsi(file);
if (element != null) {
queueUpdateFrom(element, false);
}
}
}
private final class MyFileStatusListener implements FileStatusListener {
@Override
public void fileStatusesChanged() {
queueUpdate(false);
}
@Override
public void fileStatusChanged(@NotNull VirtualFile vFile) {
queueUpdate(false);
}
}
private PsiElement findPsi(@NotNull VirtualFile vFile) {
if (!vFile.isValid()) return null;
PsiManager psiManager = PsiManager.getInstance(myProject);
return vFile.isDirectory() ? psiManager.findDirectory(vFile) : psiManager.findFile(vFile);
}
private class MyProblemListener extends WolfTheProblemSolver.ProblemListener {
private final Alarm myUpdateProblemAlarm = new Alarm();
private final Collection<VirtualFile> myFilesToRefresh = new THashSet<VirtualFile>();
@Override
public void problemsAppeared(@NotNull VirtualFile file) {
queueUpdate(file);
}
@Override
public void problemsDisappeared(@NotNull VirtualFile file) {
queueUpdate(file);
}
private void queueUpdate(@NotNull VirtualFile fileToRefresh) {
synchronized (myFilesToRefresh) {
if (myFilesToRefresh.add(fileToRefresh)) {
myUpdateProblemAlarm.cancelAllRequests();
myUpdateProblemAlarm.addRequest(new Runnable() {
@Override
public void run() {
if (!myProject.isOpen()) return;
Set<VirtualFile> filesToRefresh;
synchronized (myFilesToRefresh) {
filesToRefresh = new THashSet<VirtualFile>(myFilesToRefresh);
}
final DefaultMutableTreeNode rootNode = getRootNode();
if (rootNode != null) {
updateNodesContaining(filesToRefresh, rootNode);
}
synchronized (myFilesToRefresh) {
myFilesToRefresh.removeAll(filesToRefresh);
}
}
}, 200, ModalityState.NON_MODAL);
}
}
}
}
private void updateNodesContaining(@NotNull Collection<VirtualFile> filesToRefresh, @NotNull DefaultMutableTreeNode rootNode) {
if (!(rootNode.getUserObject() instanceof ProjectViewNode)) return;
ProjectViewNode node = (ProjectViewNode)rootNode.getUserObject();
Collection<VirtualFile> containingFiles = null;
for (VirtualFile virtualFile : filesToRefresh) {
if (!virtualFile.isValid()) {
addSubtreeToUpdate(rootNode); // file must be deleted
return;
}
if (node.contains(virtualFile)) {
if (containingFiles == null) containingFiles = new SmartList<VirtualFile>();
containingFiles.add(virtualFile);
}
}
if (containingFiles != null) {
updateNode(rootNode);
Enumeration children = rootNode.children();
while (children.hasMoreElements()) {
DefaultMutableTreeNode child = (DefaultMutableTreeNode)children.nextElement();
updateNodesContaining(containingFiles, child);
}
}
}
}