/* * Copyright 2000-2014 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.commander; import com.intellij.ide.CopyPasteUtil; import com.intellij.ide.projectView.ProjectViewNode; import com.intellij.ide.util.treeView.AbstractTreeNode; import com.intellij.ide.util.treeView.AbstractTreeStructure; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.ide.CopyPasteManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.vcs.FileStatusListener; import com.intellij.openapi.vcs.FileStatusManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.util.PsiModificationTracker; import com.intellij.util.Alarm; import org.jetbrains.annotations.NotNull; import java.awt.datatransfer.Transferable; import java.util.ArrayList; import java.util.Comparator; import java.util.List; public class ProjectListBuilder extends AbstractListBuilder { private final MyPsiTreeChangeListener myPsiTreeChangeListener; private final MyFileStatusListener myFileStatusListener; private final CopyPasteManager.ContentChangedListener myCopyPasteListener; private final Alarm myUpdateAlarm; public ProjectListBuilder(final Project project, final CommanderPanel panel, final AbstractTreeStructure treeStructure, final Comparator comparator, final boolean showRoot) { super(project, panel.getList(), panel.getModel(), treeStructure, comparator, showRoot); myList.setCellRenderer(new ColoredCommanderRenderer(panel)); myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD, myProject); myPsiTreeChangeListener = new MyPsiTreeChangeListener(); PsiManager.getInstance(myProject).addPsiTreeChangeListener(myPsiTreeChangeListener); myFileStatusListener = new MyFileStatusListener(); FileStatusManager.getInstance(myProject).addFileStatusListener(myFileStatusListener); myCopyPasteListener = new MyCopyPasteListener(); CopyPasteManager.getInstance().addContentChangedListener(myCopyPasteListener); buildRoot(); } @Override protected void updateParentTitle() { if (myParentTitle == null) return; AbstractTreeNode node = getParentNode(); if (node instanceof ProjectViewNode) { myParentTitle.setText(((ProjectViewNode)node).getTitle()); } else { myParentTitle.setText(null); } } @Override protected boolean shouldEnterSingleTopLevelElement(Object rootChild) { return true; } @Override protected boolean nodeIsAcceptableForElement(AbstractTreeNode node, Object element) { return Comparing.equal(node.getValue(), element); } @Override protected List<AbstractTreeNode> getAllAcceptableNodes(final Object[] childElements, VirtualFile file) { ArrayList<AbstractTreeNode> result = new ArrayList<>(); for (int i = 0; i < childElements.length; i++) { ProjectViewNode childElement = (ProjectViewNode)childElements[i]; if (childElement.contains(file)) result.add(childElement); } return result; } @Override public void dispose() { super.dispose(); PsiManager.getInstance(myProject).removePsiTreeChangeListener(myPsiTreeChangeListener); FileStatusManager.getInstance(myProject).removeFileStatusListener(myFileStatusListener); CopyPasteManager.getInstance().removeContentChangedListener(myCopyPasteListener); } public void addUpdateRequest() { addUpdateRequest(false); } public void addUpdateRequest(final boolean shouldRefreshSelection) { final Runnable request = new Runnable() { @Override public void run() { if (!myProject.isDisposed()) { // Rely on project view to commit PSI and wait until it's updated. if (myTreeStructure.hasSomethingToCommit() ) { myUpdateAlarm.cancelAllRequests(); myUpdateAlarm.addRequest(this, 300, ModalityState.stateForComponent(myList)); return; } updateList(shouldRefreshSelection); } } }; if (!ApplicationManager.getApplication().isUnitTestMode()) { myUpdateAlarm.cancelAllRequests(); myUpdateAlarm.addRequest(request, 300, ModalityState.stateForComponent(myList)); } else { request.run(); } } public void updateList(final boolean shouldRefreshSelection) { updateList(); if (shouldRefreshSelection) { refreshSelection(); } } protected void refreshSelection() {} private final class MyPsiTreeChangeListener extends PsiTreeChangeAdapter { private final PsiModificationTracker myModificationTracker; private long myOutOfCodeBlockModificationCount; private MyPsiTreeChangeListener() { myModificationTracker = PsiManager.getInstance(myProject).getModificationTracker(); myOutOfCodeBlockModificationCount = myModificationTracker.getOutOfCodeBlockModificationCount(); } @Override public void childRemoved(@NotNull final PsiTreeChangeEvent event) { final PsiElement child = event.getOldChild(); if (child instanceof PsiWhiteSpace) return; //optimization childrenChanged(); } @Override public void childAdded(@NotNull final PsiTreeChangeEvent event) { final PsiElement child = event.getNewChild(); if (child instanceof PsiWhiteSpace) return; //optimization childrenChanged(); } @Override public void childReplaced(@NotNull final PsiTreeChangeEvent event) { final PsiElement oldChild = event.getOldChild(); final PsiElement newChild = event.getNewChild(); if (oldChild instanceof PsiWhiteSpace && newChild instanceof PsiWhiteSpace) return; //optimization childrenChanged(); } @Override public void childMoved(@NotNull final PsiTreeChangeEvent event) { childrenChanged(); } @Override public void childrenChanged(@NotNull final PsiTreeChangeEvent event) { childrenChanged(); } private void childrenChanged() { long newModificationCount = myModificationTracker.getOutOfCodeBlockModificationCount(); if (newModificationCount == myOutOfCodeBlockModificationCount) return; myOutOfCodeBlockModificationCount = newModificationCount; addUpdateRequest(); } @Override public void propertyChanged(@NotNull final PsiTreeChangeEvent event) { final String propertyName = event.getPropertyName(); if (propertyName.equals(PsiTreeChangeEvent.PROP_ROOTS)) { addUpdateRequest(); } else if (propertyName.equals(PsiTreeChangeEvent.PROP_WRITABLE)){ childrenChanged(); } else if (propertyName.equals(PsiTreeChangeEvent.PROP_FILE_NAME) || propertyName.equals(PsiTreeChangeEvent.PROP_DIRECTORY_NAME)){ childrenChanged(); } else if (propertyName.equals(PsiTreeChangeEvent.PROP_FILE_TYPES)){ addUpdateRequest(); } } } private final class MyFileStatusListener implements FileStatusListener { @Override public void fileStatusesChanged() { addUpdateRequest(); } @Override public void fileStatusChanged(@NotNull final VirtualFile vFile) { final PsiManager manager = PsiManager.getInstance(myProject); if (vFile.isDirectory()) { final PsiDirectory directory = manager.findDirectory(vFile); if (directory != null) { myPsiTreeChangeListener.childrenChanged(); } } else { final PsiFile file = manager.findFile(vFile); if (file != null){ myPsiTreeChangeListener.childrenChanged(); } } } } private final class MyCopyPasteListener implements CopyPasteManager.ContentChangedListener { @Override public void contentChanged(final Transferable oldTransferable, final Transferable newTransferable) { updateByTransferable(oldTransferable); updateByTransferable(newTransferable); } private void updateByTransferable(final Transferable t) { final PsiElement[] psiElements = CopyPasteUtil.getElementsInTransferable(t); for (int i = 0; i < psiElements.length; i++) { myPsiTreeChangeListener.childrenChanged(); } } } }