2 * Copyright 2000-2016 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.util.ui.tree;
19 import com.intellij.icons.AllIcons;
20 import com.intellij.ide.CommonActionsManager;
21 import com.intellij.ide.DefaultTreeExpander;
22 import com.intellij.openapi.project.Project;
23 import com.intellij.openapi.roots.ProjectFileIndex;
24 import com.intellij.openapi.roots.ProjectRootManager;
25 import com.intellij.openapi.ui.Messages;
26 import com.intellij.openapi.util.Comparing;
27 import com.intellij.openapi.vfs.VfsUtilCore;
28 import com.intellij.openapi.vfs.VirtualFile;
29 import com.intellij.openapi.vfs.VirtualFileFilter;
30 import com.intellij.ui.SimpleColoredComponent;
31 import com.intellij.ui.TableUtil;
32 import com.intellij.ui.TreeTableSpeedSearch;
33 import com.intellij.ui.speedSearch.SpeedSearchUtil;
34 import com.intellij.ui.treeStructure.treetable.TreeTable;
35 import com.intellij.ui.treeStructure.treetable.TreeTableCellRenderer;
36 import com.intellij.ui.treeStructure.treetable.TreeTableModel;
37 import com.intellij.util.IconUtil;
38 import com.intellij.util.PlatformIcons;
39 import com.intellij.util.containers.Convertor;
40 import com.intellij.util.containers.HashMap;
41 import com.intellij.util.ui.UIUtil;
42 import gnu.trove.THashMap;
43 import org.jetbrains.annotations.NotNull;
44 import org.jetbrains.annotations.Nullable;
47 import javax.swing.table.TableColumn;
48 import javax.swing.tree.*;
51 import java.util.List;
53 public class AbstractFileTreeTable<T> extends TreeTable {
54 private final MyModel<T> myModel;
55 private final Project myProject;
57 public AbstractFileTreeTable(@NotNull Project project,
58 @NotNull Class<T> valueClass,
59 @NotNull String valueTitle,
60 @NotNull VirtualFileFilter filter,
61 boolean showProjectNode) {
62 this(project, valueClass, valueTitle, filter, showProjectNode, true);
66 * Due to historical reasons, passed filter does not perform all jobs - fileIndex.isInContent is checked in addition.
67 * Flag showContentFilesOnly allows you to disable such behavior.
69 public AbstractFileTreeTable(@NotNull Project project,
70 @NotNull Class<T> valueClass,
71 @NotNull String valueTitle,
72 @NotNull VirtualFileFilter filter,
73 boolean showProjectNode,
74 boolean showContentFilesOnly) {
75 super(new MyModel<T>(project, valueClass, valueTitle, showContentFilesOnly ? new ProjectContentFileFilter(project, filter) : filter));
78 //noinspection unchecked
79 myModel = (MyModel)getTableModel();
80 myModel.setTreeTable(this);
82 new TreeTableSpeedSearch(this, new Convertor<TreePath, String>() {
84 public String convert(final TreePath o) {
85 final DefaultMutableTreeNode node = (DefaultMutableTreeNode)o.getLastPathComponent();
86 final Object userObject = node.getUserObject();
87 if (userObject == null) {
88 return getProjectNodeText();
90 if (userObject instanceof VirtualFile) {
91 return ((VirtualFile)userObject).getName();
93 return node.toString();
96 final DefaultTreeExpander treeExpander = new DefaultTreeExpander(getTree());
97 CommonActionsManager.getInstance().createExpandAllAction(treeExpander, this);
98 CommonActionsManager.getInstance().createCollapseAllAction(treeExpander, this);
100 getTree().setShowsRootHandles(true);
101 getTree().setLineStyleAngled();
102 getTree().setRootVisible(showProjectNode);
103 final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
104 getTree().setCellRenderer(new DefaultTreeCellRenderer() {
106 public Component getTreeCellRendererComponent(JTree tree,
113 SimpleColoredComponent c = new SimpleColoredComponent();
114 if (value instanceof ProjectRootNode) {
115 c.append(getProjectNodeText());
116 c.setIcon(AllIcons.Nodes.Project);
119 FileNode fileNode = (FileNode)value;
120 VirtualFile file = fileNode.getObject();
121 c.append(fileNode.getParent() instanceof FileNode ? file.getName() : file.getPresentableUrl());
122 Icon icon = file.isDirectory()
123 ? fileIndex.isExcluded(file) ? AllIcons.Modules.ExcludeRoot
124 : PlatformIcons.DIRECTORY_CLOSED_ICON : IconUtil.getIcon(file, 0, null);
127 SpeedSearchUtil.applySpeedSearchHighlighting(AbstractFileTreeTable.this, c, false, selected);
131 getTableHeader().setReorderingAllowed(false);
133 setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
134 setPreferredScrollableViewportSize(new Dimension(300, getRowHeight() * 10));
136 getColumnModel().getColumn(0).setPreferredWidth(280);
137 getColumnModel().getColumn(1).setPreferredWidth(60);
140 protected boolean isNullObject(final T value) {
144 private static String getProjectNodeText() {
148 public Project getProject() {
152 public TableColumn getValueColumn() {
153 return getColumnModel().getColumn(1);
156 protected boolean isValueEditableForFile(final VirtualFile virtualFile) {
160 public static void press(final Container comboComponent) {
161 if (comboComponent instanceof JButton) {
162 ((JButton)comboComponent).doClick();
165 for (int i = 0; i < comboComponent.getComponentCount(); i++) {
166 Component child = comboComponent.getComponent(i);
167 if (child instanceof Container) {
168 press((Container)child);
174 public boolean clearSubdirectoriesOnDemandOrCancel(final VirtualFile parent, final String message, final String title) {
175 Map<VirtualFile, T> mappings = myModel.myCurrentMapping;
176 Map<VirtualFile, T> subdirectoryMappings = new THashMap<VirtualFile, T>();
177 for (VirtualFile file : mappings.keySet()) {
178 if (file != null && (parent == null || VfsUtilCore.isAncestor(parent, file, true))) {
179 subdirectoryMappings.put(file, mappings.get(file));
182 if (subdirectoryMappings.isEmpty()) {
185 int ret = Messages.showYesNoCancelDialog(myProject, message, title, "Override", "Do Not Override", "Cancel",
186 Messages.getWarningIcon());
187 if (ret == Messages.YES) {
188 for (VirtualFile file : subdirectoryMappings.keySet()) {
189 myModel.setValueAt(null, new DefaultMutableTreeNode(file), 1);
192 return ret != Messages.CANCEL;
196 public Map<VirtualFile, T> getValues() {
197 return myModel.getValues();
201 public TreeTableCellRenderer createTableRenderer(TreeTableModel treeTableModel) {
202 TreeTableCellRenderer tableRenderer = super.createTableRenderer(treeTableModel);
203 UIUtil.setLineStyleAngled(tableRenderer);
204 tableRenderer.setRootVisible(false);
205 tableRenderer.setShowsRootHandles(true);
207 return tableRenderer;
210 public void reset(@NotNull Map<VirtualFile, T> mappings) {
211 myModel.reset(mappings);
212 myModel.nodeChanged((TreeNode)myModel.getRoot());
213 getTree().setModel(null);
214 getTree().setModel(myModel);
215 TreeUtil.expandRootChildIfOnlyOne(getTree());
218 public void select(@Nullable final VirtualFile toSelect) {
219 if (toSelect != null) {
220 select(toSelect, (TreeNode)myModel.getRoot());
224 private void select(@NotNull VirtualFile toSelect, final TreeNode root) {
225 for (int i = 0; i < root.getChildCount(); i++) {
226 TreeNode child = root.getChildAt(i);
227 VirtualFile file = ((FileNode)child).getObject();
228 if (VfsUtilCore.isAncestor(file, toSelect, false)) {
229 if (Comparing.equal(file, toSelect)) {
230 TreeUtil.selectNode(getTree(), child);
231 getSelectionModel().clearSelection();
232 addSelectedPath(TreeUtil.getPathFromRoot(child));
233 TableUtil.scrollSelectionToVisible(this);
236 select(toSelect, child);
243 private static class MyModel<T> extends DefaultTreeModel implements TreeTableModel {
244 private final Map<VirtualFile, T> myCurrentMapping = new HashMap<VirtualFile, T>();
245 private final Class<T> myValueClass;
246 private final String myValueTitle;
247 private AbstractFileTreeTable<T> myTreeTable;
249 private MyModel(@NotNull Project project, @NotNull Class<T> valueClass, @NotNull String valueTitle, @NotNull VirtualFileFilter filter) {
250 super(new ProjectRootNode(project, filter));
251 myValueClass = valueClass;
252 myValueTitle = valueTitle;
255 private Map<VirtualFile, T> getValues() {
256 return new HashMap<VirtualFile, T>(myCurrentMapping);
260 public void setTree(JTree tree) {
264 public int getColumnCount() {
269 public String getColumnName(final int column) {
272 return "File/Directory";
276 throw new RuntimeException("invalid column " + column);
281 public Class getColumnClass(final int column) {
284 return TreeTableModel.class;
288 throw new RuntimeException("invalid column " + column);
293 public Object getValueAt(final Object node, final int column) {
294 Object userObject = ((DefaultMutableTreeNode)node).getUserObject();
295 if (userObject instanceof Project) {
300 return myCurrentMapping.get(null);
303 VirtualFile file = (VirtualFile)userObject;
308 return myCurrentMapping.get(file);
310 throw new RuntimeException("invalid column " + column);
315 public boolean isCellEditable(final Object node, final int column) {
320 final Object userObject = ((DefaultMutableTreeNode)node).getUserObject();
321 return !(userObject instanceof VirtualFile || userObject == null) || myTreeTable.isValueEditableForFile((VirtualFile)userObject);
323 throw new RuntimeException("invalid column " + column);
328 public void setValueAt(final Object aValue, final Object node, final int column) {
329 final Object userObject = ((DefaultMutableTreeNode)node).getUserObject();
330 if (userObject instanceof Project) {
334 final VirtualFile file = (VirtualFile)userObject;
335 @SuppressWarnings("unchecked")
337 if (t == null || myTreeTable.isNullObject(t)) {
338 myCurrentMapping.remove(file);
341 myCurrentMapping.put(file, t);
343 fireTreeNodesChanged(this, new Object[]{getRoot()}, null, null);
346 public void reset(@NotNull Map<VirtualFile, T> mappings) {
347 myCurrentMapping.clear();
348 myCurrentMapping.putAll(mappings);
349 ((ProjectRootNode)getRoot()).clearCachedChildren();
352 void setTreeTable(final AbstractFileTreeTable<T> treeTable) {
353 myTreeTable = treeTable;
357 public static class ProjectRootNode extends ConvenientNode<Project> {
358 private final VirtualFileFilter myFilter;
360 public ProjectRootNode(@NotNull Project project) {
361 this(project, VirtualFileFilter.ALL);
364 public ProjectRootNode(@NotNull Project project, @NotNull VirtualFileFilter filter) {
370 protected void appendChildrenTo(@NotNull final Collection<ConvenientNode> children) {
371 Project project = getObject();
372 VirtualFile[] roots = ProjectRootManager.getInstance(project).getContentRoots();
375 for (VirtualFile root : roots) {
376 for (VirtualFile candidate : roots) {
377 if (VfsUtilCore.isAncestor(candidate, root, true)) continue NextRoot;
379 if (myFilter.accept(root)) {
380 children.add(new FileNode(root, project, myFilter));
386 public abstract static class ConvenientNode<T> extends DefaultMutableTreeNode {
387 private final T myObject;
389 private ConvenientNode(T object) {
393 public T getObject() {
397 protected abstract void appendChildrenTo(@NotNull Collection<ConvenientNode> children);
400 public int getChildCount() {
402 return super.getChildCount();
406 public TreeNode getChildAt(final int childIndex) {
408 return super.getChildAt(childIndex);
412 public Enumeration children() {
414 return super.children();
417 private void init() {
418 if (getUserObject() == null) {
419 setUserObject(myObject);
420 final List<ConvenientNode> children = new ArrayList<ConvenientNode>();
421 appendChildrenTo(children);
422 Collections.sort(children, new Comparator<ConvenientNode>() {
424 public int compare(final ConvenientNode node1, final ConvenientNode node2) {
425 Object o1 = node1.getObject();
426 Object o2 = node2.getObject();
427 if (o1 == o2) return 0;
428 if (o1 instanceof Project) return -1;
429 if (o2 instanceof Project) return 1;
430 VirtualFile file1 = (VirtualFile)o1;
431 VirtualFile file2 = (VirtualFile)o2;
432 if (file1.isDirectory() != file2.isDirectory()) {
433 return file1.isDirectory() ? -1 : 1;
435 return file1.getName().compareTo(file2.getName());
439 for (ConvenientNode child : children) {
445 public void clearCachedChildren() {
446 if (children != null) {
447 for (Object child : children) {
448 //noinspection unchecked
449 ((ConvenientNode<T>)child).clearCachedChildren();
457 public static class FileNode extends ConvenientNode<VirtualFile> {
458 private final Project myProject;
459 private final VirtualFileFilter myFilter;
461 public FileNode(@NotNull VirtualFile file, @NotNull final Project project) {
462 this(file, project, VirtualFileFilter.ALL);
465 public FileNode(@NotNull VirtualFile file, @NotNull final Project project, @NotNull VirtualFileFilter filter) {
472 protected void appendChildrenTo(@NotNull final Collection<ConvenientNode> children) {
473 for (VirtualFile child : getObject().getChildren()) {
474 if (myFilter.accept(child)) {
475 children.add(new FileNode(child, myProject, myFilter));