blob: 416d584441b024b6ea152fd0ec3edaf1180d2672 [file] [log] [blame]
/*
* Copyright 2000-2013 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.ui.tree;
import com.intellij.icons.AllIcons;
import com.intellij.ide.CommonActionsManager;
import com.intellij.ide.DefaultTreeExpander;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileFilter;
import com.intellij.ui.TableUtil;
import com.intellij.ui.TreeTableSpeedSearch;
import com.intellij.ui.treeStructure.treetable.TreeTable;
import com.intellij.ui.treeStructure.treetable.TreeTableCellRenderer;
import com.intellij.ui.treeStructure.treetable.TreeTableModel;
import com.intellij.util.IconUtil;
import com.intellij.util.PlatformIcons;
import com.intellij.util.containers.Convertor;
import com.intellij.util.containers.HashMap;
import com.intellij.util.ui.UIUtil;
import gnu.trove.THashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.table.TableColumn;
import javax.swing.tree.*;
import java.awt.*;
import java.util.*;
import java.util.List;
public abstract class AbstractFileTreeTable<T> extends TreeTable {
private final MyModel<T> myModel;
private final Project myProject;
public AbstractFileTreeTable(@NotNull Project project,
@NotNull Class<T> valueClass,
@NotNull String valueTitle,
@NotNull VirtualFileFilter filter,
boolean showProjectNode) {
super(new MyModel<T>(project, valueClass, valueTitle, filter));
myProject = project;
myModel = (MyModel)getTableModel();
myModel.setTreeTable(this);
new TreeTableSpeedSearch(this, new Convertor<TreePath, String>() {
@Override
public String convert(final TreePath o) {
final DefaultMutableTreeNode node = (DefaultMutableTreeNode)o.getLastPathComponent();
final Object userObject = node.getUserObject();
if (userObject == null) {
return getProjectNodeText();
}
if (userObject instanceof VirtualFile) {
return ((VirtualFile)userObject).getName();
}
return node.toString();
}
});
final DefaultTreeExpander treeExpander = new DefaultTreeExpander(getTree());
CommonActionsManager.getInstance().createExpandAllAction(treeExpander, this);
CommonActionsManager.getInstance().createCollapseAllAction(treeExpander, this);
getTree().setShowsRootHandles(true);
getTree().setLineStyleAngled();
getTree().setRootVisible(showProjectNode);
getTree().setCellRenderer(new DefaultTreeCellRenderer() {
@Override
public Component getTreeCellRendererComponent(final JTree tree, final Object value, final boolean sel, final boolean expanded,
final boolean leaf, final int row, final boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
if (value instanceof ProjectRootNode) {
setText(getProjectNodeText());
setIcon(AllIcons.Nodes.Project);
return this;
}
FileNode fileNode = (FileNode)value;
VirtualFile file = fileNode.getObject();
if (fileNode.getParent() instanceof FileNode) {
setText(file.getName());
}
else {
setText(file.getPresentableUrl());
}
Icon icon = file.isDirectory() ? PlatformIcons.DIRECTORY_CLOSED_ICON : IconUtil.getIcon(file, 0, null);
setIcon(icon);
return this;
}
});
getTableHeader().setReorderingAllowed(false);
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
setPreferredScrollableViewportSize(new Dimension(300, getRowHeight() * 10));
getColumnModel().getColumn(0).setPreferredWidth(280);
getColumnModel().getColumn(1).setPreferredWidth(60);
}
protected boolean isNullObject(final T value) {
return false;
}
private static String getProjectNodeText() {
return "Project";
}
public Project getProject() {
return myProject;
}
public TableColumn getValueColumn() {
return getColumnModel().getColumn(1);
}
protected boolean isValueEditableForFile(final VirtualFile virtualFile) {
return true;
}
public static void press(final Container comboComponent) {
if (comboComponent instanceof JButton) {
final JButton button = (JButton)comboComponent;
button.doClick();
}
else {
for (int i = 0; i < comboComponent.getComponentCount(); i++) {
Component child = comboComponent.getComponent(i);
if (child instanceof Container) {
press((Container)child);
}
}
}
}
public boolean clearSubdirectoriesOnDemandOrCancel(final VirtualFile parent, final String message, final String title) {
Map<VirtualFile, T> mappings = myModel.myCurrentMapping;
Map<VirtualFile, T> subdirectoryMappings = new THashMap<VirtualFile, T>();
for (VirtualFile file : mappings.keySet()) {
if (file != null && (parent == null || VfsUtilCore.isAncestor(parent, file, true))) {
subdirectoryMappings.put(file, mappings.get(file));
}
}
if (subdirectoryMappings.isEmpty()) {
return true;
}
int ret = Messages.showYesNoCancelDialog(myProject, message, title, "Override", "Do Not Override", "Cancel",
Messages.getWarningIcon());
if (ret == Messages.YES) {
for (VirtualFile file : subdirectoryMappings.keySet()) {
myModel.setValueAt(null, new DefaultMutableTreeNode(file), 1);
}
}
return ret != Messages.CANCEL;
}
@NotNull
public Map<VirtualFile, T> getValues() {
return myModel.getValues();
}
@Override
public TreeTableCellRenderer createTableRenderer(TreeTableModel treeTableModel) {
TreeTableCellRenderer tableRenderer = super.createTableRenderer(treeTableModel);
UIUtil.setLineStyleAngled(tableRenderer);
tableRenderer.setRootVisible(false);
tableRenderer.setShowsRootHandles(true);
return tableRenderer;
}
public void reset(@NotNull Map<VirtualFile, T> mappings) {
myModel.reset(mappings);
final TreeNode root = (TreeNode)myModel.getRoot();
myModel.nodeChanged(root);
getTree().setModel(null);
getTree().setModel(myModel);
TreeUtil.expandRootChildIfOnlyOne(getTree());
}
public void select(@Nullable final VirtualFile toSelect) {
if (toSelect != null) {
select(toSelect, (TreeNode)myModel.getRoot());
}
}
private void select(@NotNull VirtualFile toSelect, final TreeNode root) {
for (int i = 0; i < root.getChildCount(); i++) {
TreeNode child = root.getChildAt(i);
VirtualFile file = ((FileNode)child).getObject();
if (VfsUtilCore.isAncestor(file, toSelect, false)) {
if (Comparing.equal(file, toSelect)) {
TreeUtil.selectNode(getTree(), child);
getSelectionModel().clearSelection();
addSelectedPath(TreeUtil.getPathFromRoot(child));
TableUtil.scrollSelectionToVisible(this);
}
else {
select(toSelect, child);
}
return;
}
}
}
private static class MyModel<T> extends DefaultTreeModel implements TreeTableModel {
private final Map<VirtualFile, T> myCurrentMapping = new HashMap<VirtualFile, T>();
private final Class<T> myValueClass;
private final String myValueTitle;
private AbstractFileTreeTable<T> myTreeTable;
private MyModel(@NotNull Project project, @NotNull Class<T> valueClass, @NotNull String valueTitle, @NotNull VirtualFileFilter filter) {
super(new ProjectRootNode(project, filter));
myValueClass = valueClass;
myValueTitle = valueTitle;
}
private Map<VirtualFile, T> getValues() {
return new HashMap<VirtualFile, T>(myCurrentMapping);
}
@Override
public void setTree(JTree tree) {
}
@Override
public int getColumnCount() {
return 2;
}
@Override
public String getColumnName(final int column) {
switch (column) {
case 0:
return "File/Directory";
case 1:
return myValueTitle;
default:
throw new RuntimeException("invalid column " + column);
}
}
@Override
public Class getColumnClass(final int column) {
switch (column) {
case 0:
return TreeTableModel.class;
case 1:
return myValueClass;
default:
throw new RuntimeException("invalid column " + column);
}
}
@Override
public Object getValueAt(final Object node, final int column) {
Object userObject = ((DefaultMutableTreeNode)node).getUserObject();
if (userObject instanceof Project) {
switch (column) {
case 0:
return userObject;
case 1:
return myCurrentMapping.get(null);
}
}
VirtualFile file = (VirtualFile)userObject;
switch (column) {
case 0:
return file;
case 1:
return myCurrentMapping.get(file);
default:
throw new RuntimeException("invalid column " + column);
}
}
@Override
public boolean isCellEditable(final Object node, final int column) {
switch (column) {
case 0:
return false;
case 1:
final Object userObject = ((DefaultMutableTreeNode)node).getUserObject();
return !(userObject instanceof VirtualFile || userObject == null) || myTreeTable.isValueEditableForFile((VirtualFile)userObject);
default:
throw new RuntimeException("invalid column " + column);
}
}
@Override
public void setValueAt(final Object aValue, final Object node, final int column) {
final DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)node;
final Object userObject = treeNode.getUserObject();
if (userObject instanceof Project) return;
final VirtualFile file = (VirtualFile)userObject;
final T t = (T)aValue;
if (t == null || myTreeTable.isNullObject(t)) {
myCurrentMapping.remove(file);
}
else {
myCurrentMapping.put(file, t);
}
fireTreeNodesChanged(this, new Object[]{getRoot()}, null, null);
}
public void reset(@NotNull Map<VirtualFile, T> mappings) {
myCurrentMapping.clear();
myCurrentMapping.putAll(mappings);
((ProjectRootNode)getRoot()).clearCachedChildren();
}
void setTreeTable(final AbstractFileTreeTable<T> treeTable) {
myTreeTable = treeTable;
}
}
public static class ProjectRootNode extends ConvenientNode<Project> {
private final VirtualFileFilter myFilter;
public ProjectRootNode(@NotNull Project project) {
this(project, VirtualFileFilter.ALL);
}
public ProjectRootNode(@NotNull Project project, @NotNull VirtualFileFilter filter) {
super(project);
myFilter = filter;
}
@Override
protected void appendChildrenTo(@NotNull final Collection<ConvenientNode> children) {
Project project = getObject();
VirtualFile[] roots = ProjectRootManager.getInstance(project).getContentRoots();
NextRoot:
for (VirtualFile root : roots) {
for (VirtualFile candidate : roots) {
if (VfsUtilCore.isAncestor(candidate, root, true)) continue NextRoot;
}
if (myFilter.accept(root)) {
children.add(new FileNode(root, project, myFilter));
}
}
}
}
public abstract static class ConvenientNode<T> extends DefaultMutableTreeNode {
private final T myObject;
private ConvenientNode(T object) {
myObject = object;
}
public T getObject() {
return myObject;
}
protected abstract void appendChildrenTo(@NotNull Collection<ConvenientNode> children);
@Override
public int getChildCount() {
init();
return super.getChildCount();
}
@Override
public TreeNode getChildAt(final int childIndex) {
init();
return super.getChildAt(childIndex);
}
@Override
public Enumeration children() {
init();
return super.children();
}
private void init() {
if (getUserObject() == null) {
setUserObject(myObject);
final List<ConvenientNode> children = new ArrayList<ConvenientNode>();
appendChildrenTo(children);
Collections.sort(children, new Comparator<ConvenientNode>() {
@Override
public int compare(final ConvenientNode node1, final ConvenientNode node2) {
Object o1 = node1.getObject();
Object o2 = node2.getObject();
if (o1 == o2) return 0;
if (o1 instanceof Project) return -1;
if (o2 instanceof Project) return 1;
VirtualFile file1 = (VirtualFile)o1;
VirtualFile file2 = (VirtualFile)o2;
if (file1.isDirectory() != file2.isDirectory()) {
return file1.isDirectory() ? -1 : 1;
}
return file1.getName().compareTo(file2.getName());
}
});
int i = 0;
for (ConvenientNode child : children) {
insert(child, i++);
}
}
}
public void clearCachedChildren() {
if (children != null) {
for (Object child : children) {
ConvenientNode<T> node = (ConvenientNode<T>)child;
node.clearCachedChildren();
}
}
removeAllChildren();
setUserObject(null);
}
}
public static class FileNode extends ConvenientNode<VirtualFile> {
private final Project myProject;
private final VirtualFileFilter myFilter;
public FileNode(@NotNull VirtualFile file, @NotNull final Project project) {
this(file, project, VirtualFileFilter.ALL);
}
public FileNode(@NotNull VirtualFile file, @NotNull final Project project, @NotNull VirtualFileFilter filter) {
super(file);
myProject = project;
myFilter = filter;
}
@Override
protected void appendChildrenTo(@NotNull final Collection<ConvenientNode> children) {
VirtualFile[] childrenf = getObject().getChildren();
ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
for (VirtualFile child : childrenf) {
if (myFilter.accept(child) && fileIndex.isInContent(child)) {
children.add(new FileNode(child, myProject, myFilter));
}
}
}
}
}