blob: 371f2b5d5f20c8850185e48b19f32106402c825f [file] [log] [blame]
/*
* Copyright 2000-2009 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.util;
import com.intellij.icons.AllIcons;
import com.intellij.ide.util.gotoByName.ChooseByNamePanel;
import com.intellij.ide.util.gotoByName.ChooseByNamePopupComponent;
import com.intellij.ide.util.gotoByName.GotoClassModel2;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.roots.FileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.TabbedPaneWrapper;
import com.intellij.util.ArrayUtil;
import com.intellij.util.PlatformIcons;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class DirectoryChooser extends DialogWrapper {
@NonNls private static final String FILTER_NON_EXISTING = "filter_non_existing";
private static final String DEFAULT_SELECTION = "last_directory_selection";
private final DirectoryChooserView myView;
private boolean myFilterExisting;
private PsiDirectory myDefaultSelection;
private final List<ItemWrapper> myItems = new ArrayList<ItemWrapper>();
private PsiElement mySelection;
private final TabbedPaneWrapper myTabbedPaneWrapper;
private final ChooseByNamePanel myChooseByNamePanel;
public DirectoryChooser(@NotNull Project project){
this(project, new DirectoryChooserModuleTreeView(project));
}
public DirectoryChooser(@NotNull Project project, @NotNull DirectoryChooserView view){
super(project, true);
myView = view;
final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance();
myFilterExisting = propertiesComponent.isValueSet(FILTER_NON_EXISTING) && propertiesComponent.isTrueValue(FILTER_NON_EXISTING);
myTabbedPaneWrapper = new TabbedPaneWrapper(getDisposable());
myChooseByNamePanel = new ChooseByNamePanel(project, new GotoClassModel2(project){
@NotNull
@Override
public String[] getNames(boolean checkBoxState) {
return super.getNames(false);
}
}, "", false, null) {
@Override
protected void showTextFieldPanel() {
}
@Override
protected void close(boolean isOk) {
super.close(isOk);
if (isOk) {
final List<Object> elements = getChosenElements();
if (elements != null && elements.size() > 0) {
myActionListener.elementChosen(elements.get(0));
}
doOKAction();
}
else {
doCancelAction();
}
}
};
Disposer.register(myDisposable, myChooseByNamePanel);
init();
}
@Override
protected void doOKAction() {
PropertiesComponent.getInstance().setValue(FILTER_NON_EXISTING, String.valueOf(myFilterExisting));
if (myTabbedPaneWrapper.getSelectedIndex() == 1) {
setSelection(myChooseByNamePanel.getChosenElement());
}
final ItemWrapper item = myView.getSelectedItem();
if (item != null) {
final PsiDirectory directory = item.getDirectory();
if (directory != null) {
PropertiesComponent.getInstance(directory.getProject()).setValue(DEFAULT_SELECTION, directory.getVirtualFile().getPath());
}
}
super.doOKAction();
}
@Override
protected JComponent createCenterPanel(){
final JPanel panel = new JPanel(new BorderLayout());
final DefaultActionGroup actionGroup = new DefaultActionGroup();
actionGroup.add(new FilterExistentAction());
final JComponent toolbarComponent = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, actionGroup, true).getComponent();
toolbarComponent.setBorder(null);
panel.add(toolbarComponent, BorderLayout.NORTH);
final Runnable runnable = new Runnable() {
@Override
public void run() {
enableButtons();
}
};
myView.onSelectionChange(runnable);
final JComponent component = myView.getComponent();
final JScrollPane jScrollPane = ScrollPaneFactory.createScrollPane(component);
//noinspection HardCodedStringLiteral
int prototypeWidth = component.getFontMetrics(component.getFont()).stringWidth("X:\\1234567890\\1234567890\\com\\company\\system\\subsystem");
jScrollPane.setPreferredSize(new Dimension(Math.max(300, prototypeWidth),300));
installEnterAction(component);
panel.add(jScrollPane, BorderLayout.CENTER);
myTabbedPaneWrapper.addTab("Directory Structure", panel);
myChooseByNamePanel.invoke(new ChooseByNamePopupComponent.Callback() {
@Override
public void elementChosen(Object element) {
setSelection(element);
}
}, ModalityState.stateForComponent(getRootPane()), false);
myTabbedPaneWrapper.addTab("Choose By Neighbor Class", myChooseByNamePanel.getPanel());
return myTabbedPaneWrapper.getComponent();
}
private void setSelection(Object element) {
if (element instanceof PsiElement) {
mySelection = (PsiElement)element;
}
}
private void installEnterAction(final JComponent component) {
final KeyStroke enterKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0);
final InputMap inputMap = component.getInputMap();
final ActionMap actionMap = component.getActionMap();
final Object oldActionKey = inputMap.get(enterKeyStroke);
final Action oldAction = oldActionKey != null ? actionMap.get(oldActionKey) : null;
inputMap.put(enterKeyStroke, "clickButton");
actionMap.put("clickButton", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
if (isOKActionEnabled()) {
doOKAction();
}
else if (oldAction != null) {
oldAction.actionPerformed(e);
}
}
});
}
@Override
protected String getDimensionServiceKey() {
return "chooseDestDirectoryDialog";
}
private void buildFragments() {
ArrayList<String[]> pathes = new ArrayList<String[]>();
for (int i = 0; i < myView.getItemsSize(); i++) {
ItemWrapper item = myView.getItemByIndex(i);
pathes.add(ArrayUtil.toStringArray(FileUtil.splitPath(item.getPresentableUrl())));
}
FragmentBuilder headBuilder = new FragmentBuilder(pathes){
@Override
protected void append(String fragment, StringBuffer buffer) {
buffer.append(mySeparator);
buffer.append(fragment);
}
@Override
protected int getFragmentIndex(String[] path, int index) {
return path.length > index ? index : -1;
}
};
String commonHead = headBuilder.execute();
final int headLimit = headBuilder.getIndex();
FragmentBuilder tailBuilder = new FragmentBuilder(pathes){
@Override
protected void append(String fragment, StringBuffer buffer) {
buffer.insert(0, fragment + mySeparator);
}
@Override
protected int getFragmentIndex(String[] path, int index) {
int result = path.length - 1 - index;
return result > headLimit ? result : -1;
}
};
String commonTail = tailBuilder.execute();
int tailLimit = tailBuilder.getIndex();
for (int i = 0; i < myView.getItemsSize(); i++) {
ItemWrapper item = myView.getItemByIndex(i);
String special = concat(pathes.get(i), headLimit, tailLimit);
item.setFragments(createFragments(commonHead, special, commonTail));
}
}
@Nullable
private static String concat(String[] strings, int headLimit, int tailLimit) {
if (strings.length <= headLimit + tailLimit) return null;
StringBuffer buffer = new StringBuffer();
String separator = "";
for (int i = headLimit; i < strings.length - tailLimit; i++) {
buffer.append(separator);
buffer.append(strings[i]);
separator = File.separator;
}
return buffer.toString();
}
private static PathFragment[] createFragments(String head, String special, String tail) {
ArrayList<PathFragment> list = new ArrayList<PathFragment>(3);
if (head != null) {
if (special != null || tail != null) list.add(new PathFragment(head + File.separatorChar, true));
else return new PathFragment[]{new PathFragment(head, true)};
}
if (special != null) {
if (tail != null) list.add(new PathFragment(special + File.separatorChar, false));
else list.add(new PathFragment(special, false));
}
if (tail != null) list.add(new PathFragment(tail, true));
return list.toArray(new PathFragment[list.size()]);
}
private static abstract class FragmentBuilder {
private final ArrayList<String[]> myPaths;
private final StringBuffer myBuffer = new StringBuffer();
private int myIndex;
protected String mySeparator = "";
public FragmentBuilder(ArrayList<String[]> pathes) {
myPaths = pathes;
myIndex = 0;
}
public int getIndex() { return myIndex; }
@Nullable
public String execute() {
while (true) {
String commonHead = getCommonFragment(myIndex);
if (commonHead == null) break;
append(commonHead, myBuffer);
mySeparator = File.separator;
myIndex++;
}
return myIndex > 0 ? myBuffer.toString() : null;
}
protected abstract void append(String fragment, StringBuffer buffer);
@Nullable
private String getCommonFragment(int count) {
String commonFragment = null;
for (String[] path : myPaths) {
int index = getFragmentIndex(path, count);
if (index == -1) return null;
if (commonFragment == null) {
commonFragment = path[index];
continue;
}
if (!Comparing.strEqual(commonFragment, path[index], SystemInfo.isFileSystemCaseSensitive)) return null;
}
return commonFragment;
}
protected abstract int getFragmentIndex(String[] path, int index);
}
public static class ItemWrapper {
final PsiDirectory myDirectory;
private PathFragment[] myFragments;
private final String myPostfix;
private String myRelativeToProjectPath = null;
public ItemWrapper(PsiDirectory directory, String postfix) {
myDirectory = directory;
myPostfix = postfix != null && postfix.length() > 0 ? postfix : null;
}
public PathFragment[] getFragments() { return myFragments; }
public void setFragments(PathFragment[] fragments) {
myFragments = fragments;
}
public Icon getIcon(FileIndex fileIndex) {
if (myDirectory != null) {
VirtualFile virtualFile = myDirectory.getVirtualFile();
if (fileIndex.isInTestSourceContent(virtualFile)){
return PlatformIcons.MODULES_TEST_SOURCE_FOLDER;
}
else if (fileIndex.isInSourceContent(virtualFile)){
return PlatformIcons.MODULES_SOURCE_FOLDERS_ICON;
}
}
return PlatformIcons.FOLDER_ICON;
}
public String getPresentableUrl() {
String directoryUrl;
if (myDirectory != null) {
directoryUrl = myDirectory.getVirtualFile().getPresentableUrl();
final VirtualFile baseDir = myDirectory.getProject().getBaseDir();
if (baseDir != null) {
final String projectHomeUrl = baseDir.getPresentableUrl();
if (directoryUrl.startsWith(projectHomeUrl)) {
directoryUrl = "..." + directoryUrl.substring(projectHomeUrl.length());
}
}
}
else {
directoryUrl = "";
}
return myPostfix != null ? directoryUrl + myPostfix : directoryUrl;
}
public PsiDirectory getDirectory() {
return myDirectory;
}
public String getRelativeToProjectPath() {
if (myRelativeToProjectPath == null) {
final PsiDirectory directory = getDirectory();
final VirtualFile virtualFile = directory != null ? directory.getVirtualFile() : null;
myRelativeToProjectPath = virtualFile != null
? ProjectUtil.calcRelativeToProjectPath(virtualFile, directory.getProject(), true, false, true)
: getPresentableUrl();
}
return myRelativeToProjectPath;
}
}
@Override
public JComponent getPreferredFocusedComponent(){
return myView.getComponent();
}
public void fillList(PsiDirectory[] directories, @Nullable PsiDirectory defaultSelection, Project project, String postfixToShow) {
fillList(directories, defaultSelection, project, postfixToShow, null);
}
public void fillList(PsiDirectory[] directories, @Nullable PsiDirectory defaultSelection, Project project, Map<PsiDirectory,String> postfixes) {
fillList(directories, defaultSelection, project, null, postfixes);
}
private void fillList(PsiDirectory[] directories, @Nullable PsiDirectory defaultSelection, Project project, String postfixToShow, Map<PsiDirectory,String> postfixes) {
if (myView.getItemsSize() > 0){
myView.clearItems();
}
if (defaultSelection == null) {
defaultSelection = getDefaultSelection(directories, project);
if (defaultSelection == null && directories.length > 0) {
defaultSelection = directories[0];
}
}
int selectionIndex = -1;
for(int i = 0; i < directories.length; i++){
PsiDirectory directory = directories[i];
if (directory.equals(defaultSelection)) {
selectionIndex = i;
break;
}
}
if (selectionIndex < 0 && directories.length == 1) {
selectionIndex = 0;
}
if (selectionIndex < 0) {
// find source root corresponding to defaultSelection
final PsiManager manager = PsiManager.getInstance(project);
VirtualFile[] sourceRoots = ProjectRootManager.getInstance(project).getContentSourceRoots();
for (VirtualFile sourceRoot : sourceRoots) {
if (sourceRoot.isDirectory()) {
PsiDirectory directory = manager.findDirectory(sourceRoot);
if (directory != null && isParent(defaultSelection, directory)) {
defaultSelection = directory;
break;
}
}
}
}
int existingIdx = 0;
for(int i = 0; i < directories.length; i++){
PsiDirectory directory = directories[i];
final String postfixForDirectory;
if (postfixes == null) {
postfixForDirectory = postfixToShow;
}
else {
postfixForDirectory = postfixes.get(directory);
}
final ItemWrapper itemWrapper = new ItemWrapper(directory, postfixForDirectory);
myItems.add(itemWrapper);
if (myFilterExisting) {
if (selectionIndex == i) selectionIndex = -1;
if (postfixForDirectory != null && directory.getVirtualFile().findFileByRelativePath(StringUtil.trimStart(postfixForDirectory, File.separator)) == null) {
if (isParent(directory, defaultSelection)) {
myDefaultSelection = directory;
}
continue;
}
}
myView.addItem(itemWrapper);
if (selectionIndex < 0 && isParent(directory, defaultSelection)) {
selectionIndex = existingIdx;
}
existingIdx++;
}
buildFragments();
myView.listFilled();
if (myView.getItemsSize() > 0) {
if (selectionIndex != -1) {
myView.selectItemByIndex(selectionIndex);
} else {
myView.selectItemByIndex(0);
}
}
else {
myView.clearSelection();
}
enableButtons();
myView.getComponent().repaint();
}
@Nullable
private static PsiDirectory getDefaultSelection(PsiDirectory[] directories, Project project) {
final String defaultSelectionPath = PropertiesComponent.getInstance(project).getValue(DEFAULT_SELECTION);
if (defaultSelectionPath != null) {
final VirtualFile directoryByDefault = LocalFileSystem.getInstance().findFileByPath(defaultSelectionPath);
if (directoryByDefault != null) {
final PsiDirectory directory = PsiManager.getInstance(project).findDirectory(directoryByDefault);
return directory != null && ArrayUtil.find(directories, directory) > -1 ? directory : null;
}
}
return null;
}
private static boolean isParent(PsiDirectory directory, PsiDirectory parentCandidate) {
while (directory != null) {
if (directory.equals(parentCandidate)) return true;
directory = directory.getParentDirectory();
}
return false;
}
private void enableButtons() {
setOKActionEnabled(myView.getSelectedItem() != null);
}
@Nullable
public PsiDirectory getSelectedDirectory() {
if (mySelection != null) {
final PsiFile file = mySelection.getContainingFile();
if (file != null){
return file.getContainingDirectory();
}
}
ItemWrapper wrapper = myView.getSelectedItem();
if (wrapper == null) return null;
return wrapper.myDirectory;
}
public static class PathFragment {
private final String myText;
private final boolean myCommon;
public PathFragment(String text, boolean isCommon) {
myText = text;
myCommon = isCommon;
}
public String getText() {
return myText;
}
public boolean isCommon() {
return myCommon;
}
}
private class FilterExistentAction extends ToggleAction {
public FilterExistentAction() {
super(RefactoringBundle.message("directory.chooser.hide.non.existent.checkBox.text"),
UIUtil.removeMnemonic(RefactoringBundle.message("directory.chooser.hide.non.existent.checkBox.text")),
AllIcons.General.Filter);
}
@Override
public boolean isSelected(AnActionEvent e) {
return myFilterExisting;
}
@Override
public void setSelected(AnActionEvent e, boolean state) {
myFilterExisting = state;
final ItemWrapper selectedItem = myView.getSelectedItem();
PsiDirectory directory = selectedItem != null ? selectedItem.getDirectory() : null;
if (directory == null && myDefaultSelection != null) {
directory = myDefaultSelection;
}
myView.clearItems();
int idx = 0;
int selectionId = -1;
for (ItemWrapper item : myItems) {
if (myFilterExisting) {
if (item.myPostfix != null &&
item.getDirectory().getVirtualFile().findFileByRelativePath(StringUtil.trimStart(item.myPostfix, File.separator)) == null) {
continue;
}
}
if (item.getDirectory() == directory) {
selectionId = idx;
}
idx++;
myView.addItem(item);
}
buildFragments();
myView.listFilled();
if (selectionId < 0) {
myView.clearSelection();
if (myView.getItemsSize() > 0) {
myView.selectItemByIndex(0);
}
}
else {
myView.selectItemByIndex(selectionId);
}
enableButtons();
myView.getComponent().repaint();
}
}
}