blob: 8d33d80a525fb1da7856501ce50447db2958835f [file] [log] [blame]
/*
* 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.openapi.fileChooser.ex;
import com.intellij.ide.util.treeView.AbstractTreeBuilder;
import com.intellij.ide.util.treeView.AbstractTreeStructure;
import com.intellij.ide.util.treeView.NodeDescriptor;
import com.intellij.ide.util.treeView.NodeRenderer;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.DataKey;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileChooser.FileElement;
import com.intellij.openapi.fileChooser.FileSystemTree;
import com.intellij.openapi.fileChooser.impl.FileComparator;
import com.intellij.openapi.fileChooser.impl.FileTreeBuilder;
import com.intellij.openapi.fileChooser.impl.FileTreeStructure;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
import com.intellij.openapi.vfs.newvfs.RefreshQueue;
import com.intellij.ui.*;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.Function;
import com.intellij.util.NullableFunction;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Convertor;
import com.intellij.util.ui.tree.TreeUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.*;
public class FileSystemTreeImpl implements FileSystemTree {
private final Tree myTree;
private final FileTreeStructure myTreeStructure;
private final AbstractTreeBuilder myTreeBuilder;
private final Project myProject;
private final ArrayList<Runnable> myOkActions = new ArrayList<Runnable>(2);
private final FileChooserDescriptor myDescriptor;
private final List<Listener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private final MyExpansionListener myExpansionListener = new MyExpansionListener();
private final Set<VirtualFile> myEverExpanded = new THashSet<VirtualFile>();
public FileSystemTreeImpl(@Nullable final Project project, final FileChooserDescriptor descriptor) {
this(project, descriptor, new Tree(), null, null, null);
myTree.setRootVisible(descriptor.isTreeRootVisible());
myTree.setShowsRootHandles(true);
}
public FileSystemTreeImpl(@Nullable final Project project,
final FileChooserDescriptor descriptor,
final Tree tree,
@Nullable TreeCellRenderer renderer,
@Nullable final Runnable onInitialized,
@Nullable final Convertor<TreePath, String> speedSearchConverter) {
myProject = project;
myTreeStructure = new FileTreeStructure(project, descriptor);
myDescriptor = descriptor;
myTree = tree;
final DefaultTreeModel treeModel = new DefaultTreeModel(new DefaultMutableTreeNode());
myTree.setModel(treeModel);
myTree.addTreeExpansionListener(myExpansionListener);
myTreeBuilder = createTreeBuilder(myTree, treeModel, myTreeStructure, FileComparator.getInstance(), descriptor, new Runnable() {
public void run() {
myTree.expandPath(new TreePath(treeModel.getRoot()));
if (onInitialized != null) {
onInitialized.run();
}
}
});
Disposer.register(myTreeBuilder, new Disposable() {
public void dispose() {
myTree.removeTreeExpansionListener(myExpansionListener);
}
});
if (project != null) {
Disposer.register(project, myTreeBuilder);
}
myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(final TreeSelectionEvent e) {
processSelectionChange();
}
});
if (speedSearchConverter != null) {
new TreeSpeedSearch(myTree, speedSearchConverter);
} else {
new TreeSpeedSearch(myTree);
}
myTree.setLineStyleAngled();
TreeUtil.installActions(myTree);
myTree.getSelectionModel().setSelectionMode(
descriptor.isChooseMultiple() ? TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION : TreeSelectionModel.SINGLE_TREE_SELECTION
);
registerTreeActions();
if (renderer == null) {
renderer = new NodeRenderer() {
public void customizeCellRenderer(JTree tree,
Object value,
boolean selected,
boolean expanded,
boolean leaf,
int row,
boolean hasFocus) {
super.customizeCellRenderer(tree, value, selected, expanded, leaf, row, hasFocus);
final Object userObject = ((DefaultMutableTreeNode)value).getUserObject();
if (userObject instanceof FileNodeDescriptor) {
String comment = ((FileNodeDescriptor)userObject).getComment();
if (comment != null) {
append(comment, SimpleTextAttributes.REGULAR_ATTRIBUTES);
}
}
}
};
}
myTree.setCellRenderer(renderer);
}
protected AbstractTreeBuilder createTreeBuilder(final JTree tree, DefaultTreeModel treeModel, final AbstractTreeStructure treeStructure,
final Comparator<NodeDescriptor> comparator, FileChooserDescriptor descriptor,
@Nullable final Runnable onInitialized) {
return new FileTreeBuilder(tree, treeModel, treeStructure, comparator, descriptor, onInitialized);
}
private void registerTreeActions() {
myTree.registerKeyboardAction(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
performEnterAction(true);
}
}, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_FOCUSED
);
new DoubleClickListener() {
@Override
protected boolean onDoubleClick(MouseEvent e) {
performEnterAction(false);
return true;
}
}.installOn(myTree);
}
private void performEnterAction(boolean toggleNodeState) {
TreePath path = myTree.getSelectionPath();
if (path != null) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
if (node != null && node.isLeaf()) {
fireOkAction();
}
else if (toggleNodeState) {
if (myTree.isExpanded(path)) {
myTree.collapsePath(path);
}
else {
myTree.expandPath(path);
}
}
}
}
public void addOkAction(Runnable action) { myOkActions.add(action); }
private void fireOkAction() {
for (Runnable action : myOkActions) {
action.run();
}
}
public void registerMouseListener(final ActionGroup group) {
PopupHandler.installUnknownPopupHandler(myTree, group, ActionManager.getInstance());
}
public boolean areHiddensShown() {
return myTreeStructure.areHiddensShown();
}
public void showHiddens(boolean showHidden) {
myTreeStructure.showHiddens(showHidden);
updateTree();
}
public void updateTree() {
myTreeBuilder.queueUpdate();
}
public void dispose() {
if (myTreeBuilder != null) {
Disposer.dispose(myTreeBuilder);
}
myEverExpanded.clear();
}
public AbstractTreeBuilder getTreeBuilder() {
return myTreeBuilder;
}
public void select(VirtualFile file, @Nullable final Runnable onDone) {
select(new VirtualFile[]{file}, onDone);
}
public void select(VirtualFile[] file, @Nullable final Runnable onDone) {
Object[] elements = new Object[file.length];
for (int i = 0; i < file.length; i++) {
VirtualFile eachFile = file[i];
elements[i] = getFileElementFor(eachFile);
}
myTreeBuilder.select(elements, onDone);
}
public void expand(final VirtualFile file, @Nullable final Runnable onDone) {
myTreeBuilder.expand(getFileElementFor(file), onDone);
}
@Nullable
private static FileElement getFileElementFor(@NotNull VirtualFile file) {
VirtualFile selectFile;
if ((file.getFileSystem() instanceof JarFileSystem) && file.getParent() == null) {
selectFile = JarFileSystem.getInstance().getVirtualFileForJar(file);
if (selectFile == null) {
return null;
}
}
else {
selectFile = file;
}
return new FileElement(selectFile, selectFile.getName());
}
public Exception createNewFolder(final VirtualFile parentDirectory, final String newFolderName) {
final Exception[] failReason = new Exception[] { null };
CommandProcessor.getInstance().executeCommand(
myProject, new Runnable() {
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
try {
VirtualFile parent = parentDirectory;
for (String name : StringUtil.tokenize(newFolderName, "\\/")) {
VirtualFile folder = parent.createChildDirectory(this, name);
updateTree();
select(folder, null);
parent = folder;
}
}
catch (IOException e) {
failReason[0] = e;
}
}
});
}
},
UIBundle.message("file.chooser.create.new.folder.command.name"),
null
);
return failReason[0];
}
public Exception createNewFile(final VirtualFile parentDirectory, final String newFileName, final FileType fileType, final String initialContent) {
final Exception[] failReason = new Exception[] { null };
CommandProcessor.getInstance().executeCommand(
myProject, new Runnable() {
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
try {
final String newFileNameWithExtension = newFileName.endsWith('.'+fileType.getDefaultExtension())? newFileName : newFileName+'.'+fileType.getDefaultExtension();
final VirtualFile file = parentDirectory.createChildData(this, newFileNameWithExtension);
VfsUtil.saveText(file, initialContent != null ? initialContent : "");
updateTree();
select(file, null);
}
catch (IOException e) {
failReason[0] = e;
}
}
});
}
},
UIBundle.message("file.chooser.create.new.file.command.name"),
null
);
return failReason[0];
}
public JTree getTree() { return myTree; }
@Nullable
public VirtualFile getSelectedFile() {
final TreePath path = myTree.getSelectionPath();
if (path == null) return null;
final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
if (!(node.getUserObject() instanceof FileNodeDescriptor)) return null;
final FileElement element = ((FileNodeDescriptor)node.getUserObject()).getElement();
return element.getFile();
}
@Nullable
public VirtualFile getNewFileParent() {
final VirtualFile selected = getSelectedFile();
if (selected != null) return selected;
final List<VirtualFile> roots = myDescriptor.getRoots();
return roots.size() == 1 ? roots.get(0) : null;
}
public <T> T getData(DataKey<T> key) {
return myDescriptor.getUserData(key);
}
@NotNull
public VirtualFile[] getSelectedFiles() {
final List<VirtualFile> files = collectSelectedElements(new NullableFunction<FileElement, VirtualFile>() {
@Override
public VirtualFile fun(final FileElement element) {
final VirtualFile file = element.getFile();
return file != null && file.isValid() ? file : null;
}
});
return VfsUtilCore.toVirtualFileArray(files);
}
private <T> List<T> collectSelectedElements(final Function<FileElement, T> converter) {
final TreePath[] paths = myTree.getSelectionPaths();
if (paths == null) return Collections.emptyList();
final List<T> elements = ContainerUtil.newArrayList();
for (TreePath path : paths) {
final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
final Object userObject = node.getUserObject();
if (userObject instanceof FileNodeDescriptor) {
final T element = converter.fun(((FileNodeDescriptor)userObject).getElement());
if (element != null) {
elements.add(element);
}
}
}
return elements;
}
public boolean selectionExists() {
TreePath[] selectedPaths = myTree.getSelectionPaths();
return selectedPaths != null && selectedPaths.length != 0;
}
@Override
public boolean isUnderRoots(@NotNull VirtualFile file) {
final List<VirtualFile> roots = myDescriptor.getRoots();
if (roots.size() == 0) return true;
for (VirtualFile root : roots) {
if (root != null && VfsUtilCore.isAncestor(root, file, false)) {
return true;
}
}
return false;
}
public void addListener(final Listener listener, final Disposable parent) {
myListeners.add(listener);
Disposer.register(parent, new Disposable() {
public void dispose() {
myListeners.remove(listener);
}
});
}
private void fireSelection(List<VirtualFile> selection) {
for (Listener each : myListeners) {
each.selectionChanged(selection);
}
}
private void processSelectionChange() {
if (myListeners.size() == 0) return;
List<VirtualFile> selection = new ArrayList<VirtualFile>();
final TreePath[] paths = myTree.getSelectionPaths();
if (paths != null) {
for (TreePath each : paths) {
final Object last = each.getLastPathComponent();
if (last instanceof DefaultMutableTreeNode) {
final Object object = ((DefaultMutableTreeNode)last).getUserObject();
if (object instanceof FileNodeDescriptor) {
final FileElement element = ((FileNodeDescriptor)object).getElement();
final VirtualFile file = element.getFile();
if (file != null) {
selection.add(file);
}
}
}
}
}
fireSelection(selection);
}
private class MyExpansionListener implements TreeExpansionListener {
public void treeExpanded(final TreeExpansionEvent event) {
if (myTreeBuilder == null || !myTreeBuilder.isNodeBeingBuilt(event.getPath())) return;
TreePath path = event.getPath();
DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
if (node.getUserObject() instanceof FileNodeDescriptor) {
FileNodeDescriptor nodeDescriptor = (FileNodeDescriptor)node.getUserObject();
final FileElement fileDescriptor = nodeDescriptor.getElement();
final VirtualFile virtualFile = fileDescriptor.getFile();
if (virtualFile != null) {
if (!myEverExpanded.contains(virtualFile)) {
if (virtualFile instanceof NewVirtualFile) {
((NewVirtualFile)virtualFile).markDirty();
}
myEverExpanded.add(virtualFile);
}
AbstractTreeStructure treeStructure = myTreeBuilder.getTreeStructure();
boolean async = treeStructure != null && treeStructure.isToBuildChildrenInBackground(virtualFile);
if (virtualFile instanceof NewVirtualFile) {
RefreshQueue.getInstance().refresh(async, false, null, ModalityState.stateForComponent(myTree), virtualFile);
}
else {
virtualFile.refresh(async, false);
}
}
}
}
public void treeCollapsed(TreeExpansionEvent event) { }
}
}