blob: ae9f0142f246d90fce3314156e372a3880125172 [file] [log] [blame]
/*
* Copyright 2000-2012 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.projectRoots.ui;
import com.intellij.icons.AllIcons;
import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileTypes.FileTypes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.ex.http.HttpFileSystem;
import com.intellij.ui.*;
import com.intellij.ui.components.JBList;
import com.intellij.util.PlatformIcons;
import com.intellij.util.containers.HashSet;
import com.intellij.util.ui.UIUtil;
import gnu.trove.TIntArrayList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
/**
* @author MYakovlev
*/
public class PathEditor {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.projectRoots.ui.PathEditor");
public static final Color INVALID_COLOR = new JBColor(new Color(210, 0, 0), JBColor.RED);
protected JPanel myPanel;
private JBList myList;
private final DefaultListModel myModel;
private final Set<VirtualFile> myAllFiles = new HashSet<VirtualFile>();
private boolean myModified = false;
protected boolean myEnabled = false;
private final FileChooserDescriptor myDescriptor;
private VirtualFile myAddBaseDir;
public PathEditor(final FileChooserDescriptor descriptor) {
myDescriptor = descriptor;
myModel = createListModel();
}
public void setAddBaseDir(@Nullable VirtualFile addBaseDir) {
myAddBaseDir = addBaseDir;
}
protected void setEnabled(boolean enabled) {
myEnabled = enabled;
}
protected void setModified(boolean modified) {
myModified = modified;
}
public boolean isModified() {
return myModified;
}
public VirtualFile[] getRoots() {
final int count = getRowCount();
if (count == 0) {
return VirtualFile.EMPTY_ARRAY;
}
final VirtualFile[] roots = new VirtualFile[count];
for (int i = 0; i < count; i++) {
roots[i] = getValueAt(i);
}
return roots;
}
public void resetPath(@NotNull List<VirtualFile> paths) {
keepSelectionState();
clearList();
setEnabled(true);
for (VirtualFile file : paths) {
addElement(file);
}
setModified(false);
}
public JComponent createComponent() {
myList = new JBList(getListModel());
myList.setCellRenderer(createListCellRenderer(myList));
ToolbarDecorator toolbarDecorator = ToolbarDecorator.createDecorator(myList).disableUpDownActions()
.setAddAction(new AnActionButtonRunnable() {
@Override
public void run(AnActionButton button) {
final VirtualFile[] added = doAdd();
if (added.length > 0) {
setModified(true);
}
requestDefaultFocus();
setSelectedRoots(added);
}
}).setRemoveAction(new AnActionButtonRunnable() {
@Override
public void run(AnActionButton button) {
int[] idxs = myList.getSelectedIndices();
doRemoveItems(idxs, myList);
}
}).setAddActionUpdater(new AnActionButtonUpdater() {
@Override
public boolean isEnabled(AnActionEvent e) {
return myEnabled;
}
}).setRemoveActionUpdater(new AnActionButtonUpdater() {
@Override
public boolean isEnabled(AnActionEvent e) {
Object[] values = getSelectedRoots();
return values.length > 0 && myEnabled;
}
});
addToolbarButtons(toolbarDecorator);
myPanel = toolbarDecorator.createPanel();
myPanel.setBorder(null);
return myPanel;
}
protected void addToolbarButtons(ToolbarDecorator toolbarDecorator) {
}
protected void doRemoveItems(int[] idxs, JList list) {
List removedItems = ListUtil.removeIndices(list, idxs);
itemsRemoved(removedItems);
}
protected DefaultListModel createListModel() {
return new DefaultListModel();
}
protected ListCellRenderer createListCellRenderer(JBList list) {
return new MyCellRenderer();
}
protected void itemsRemoved(List removedItems) {
myAllFiles.removeAll(removedItems);
if (removedItems.size() > 0) {
setModified(true);
}
requestDefaultFocus();
}
protected VirtualFile[] doAdd() {
Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(myPanel));
VirtualFile[] files = FileChooser.chooseFiles(myDescriptor, myPanel, project, myAddBaseDir);
files = adjustAddedFileSet(myPanel, files);
List<VirtualFile> added = new ArrayList<VirtualFile>(files.length);
for (VirtualFile vFile : files) {
if (addElement(vFile)) {
added.add(vFile);
}
}
return VfsUtil.toVirtualFileArray(added);
}
/**
* Implement this method to adjust adding behavior, this method is called right after the files
* or directories are selected for added. This method allows adding UI that modify file set.
* <p/>
* The default implementation returns a value passed the parameter files and does nothing.
*
* @param component a component that could be used as a parent.
* @param files a selected file set
* @return adjusted file set
*/
protected VirtualFile[] adjustAddedFileSet(final Component component, final VirtualFile[] files) {
return files;
}
protected boolean isUrlInserted() {
if (getRowCount() > 0) {
return ((VirtualFile)getListModel().lastElement()).getFileSystem() instanceof HttpFileSystem;
}
return false;
}
protected void requestDefaultFocus() {
if (myList != null) {
myList.requestFocus();
}
}
public void addPaths(VirtualFile... paths) {
boolean added = false;
keepSelectionState();
for (final VirtualFile path : paths) {
if (addElement(path)) {
added = true;
}
}
if (added) {
setModified(true);
}
}
public void removePaths(VirtualFile... paths) {
final Set<VirtualFile> pathsSet = new java.util.HashSet<VirtualFile>(Arrays.asList(paths));
int size = getRowCount();
final TIntArrayList indicesToRemove = new TIntArrayList(paths.length);
for (int idx = 0; idx < size; idx++) {
VirtualFile path = getValueAt(idx);
if (pathsSet.contains(path)) {
indicesToRemove.add(idx);
}
}
final List list = ListUtil.removeIndices(myList, indicesToRemove.toNativeArray());
itemsRemoved(list);
}
/**
* Method adds element only if it is not added yet.
*/
protected boolean addElement(VirtualFile item) {
if (item == null) {
return false;
}
if (myAllFiles.contains(item)) {
return false;
}
if (isUrlInserted()) {
getListModel().insertElementAt(item, getRowCount() - 1);
}
else {
getListModel().addElement(item);
}
myAllFiles.add(item);
return true;
}
protected DefaultListModel getListModel() {
return myModel;
}
protected void setSelectedRoots(Object[] roots) {
ArrayList<Object> rootsList = new ArrayList<Object>(roots.length);
for (Object root : roots) {
if (root != null) {
rootsList.add(root);
}
}
myList.getSelectionModel().clearSelection();
int rowCount = getRowCount();
for (int i = 0; i < rowCount; i++) {
Object currObject = getValueAt(i);
LOG.assertTrue(currObject != null);
if (rootsList.contains(currObject)) {
myList.getSelectionModel().addSelectionInterval(i, i);
}
}
}
private void keepSelectionState() {
final Object[] selectedItems = getSelectedRoots();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (selectedItems != null) {
setSelectedRoots(selectedItems);
}
}
});
}
protected Object[] getSelectedRoots() {
return myList.getSelectedValues();
}
protected int getRowCount() {
return getListModel().getSize();
}
protected VirtualFile getValueAt(int row) {
return (VirtualFile)getListModel().get(row);
}
public void clearList() {
getListModel().clear();
myAllFiles.clear();
setModified(true);
}
private static boolean isJarFile(final VirtualFile file) {
return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
@Override
public Boolean compute() {
VirtualFile tempFile = file;
if ((file.getFileSystem() instanceof JarFileSystem) && file.getParent() == null) {
//[myakovlev] It was bug - directories with *.jar extensions was saved as files of JarFileSystem.
// so we can not just return true, we should filter such directories.
String path = file.getPath().substring(0, file.getPath().length() - JarFileSystem.JAR_SEPARATOR.length());
tempFile = LocalFileSystem.getInstance().findFileByPath(path);
}
if (tempFile != null && !tempFile.isDirectory()) {
return Boolean.valueOf(tempFile.getFileType().equals(FileTypes.ARCHIVE));
}
return Boolean.FALSE;
}
}).booleanValue();
}
/**
* @return icon for displaying parameter (ProjectRoot or VirtualFile)
* If parameter is not ProjectRoot or VirtualFile, returns empty icon "/nodes/emptyNode.png"
*/
private static Icon getIconForRoot(Object projectRoot) {
if (projectRoot instanceof VirtualFile) {
final VirtualFile file = (VirtualFile)projectRoot;
if (!file.isValid()) {
return AllIcons.Nodes.PpInvalid;
}
else if (isHttpRoot(file)) {
return PlatformIcons.WEB_ICON;
}
else {
return isJarFile(file) ? PlatformIcons.JAR_ICON : PlatformIcons.FILE_ICON;
}
}
return AllIcons.Nodes.EmptyNode;
}
private static boolean isHttpRoot(VirtualFile virtualFileOrProjectRoot) {
if (virtualFileOrProjectRoot != null) {
return (virtualFileOrProjectRoot.getFileSystem() instanceof HttpFileSystem);
}
return false;
}
private final class MyCellRenderer extends DefaultListCellRenderer {
private String getPresentableString(final Object value) {
return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
@Override
public String compute() {
//noinspection HardCodedStringLiteral
return (value instanceof VirtualFile) ? ((VirtualFile)value).getPresentableUrl() : "UNKNOWN OBJECT";
}
});
}
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, getPresentableString(value), index, isSelected, cellHasFocus);
if (isSelected) {
setForeground(UIUtil.getListSelectionForeground());
}
else {
if (value instanceof VirtualFile) {
VirtualFile file = (VirtualFile)value;
if (!file.isValid()) {
setForeground(INVALID_COLOR);
}
}
}
setIcon(getIconForRoot(value));
return this;
}
}
}