/* * Copyright 2000-2015 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.ide.IdeBundle; import com.intellij.ide.projectView.BaseProjectTreeBuilder; import com.intellij.ide.projectView.ProjectViewNode; import com.intellij.ide.projectView.TreeStructureProvider; import com.intellij.ide.projectView.impl.AbstractProjectTreeStructure; import com.intellij.ide.projectView.impl.ProjectAbstractTreeStructureBase; import com.intellij.ide.projectView.impl.ProjectTreeBuilder; import com.intellij.ide.projectView.impl.nodes.PsiFileNode; import com.intellij.ide.util.gotoByName.ChooseByNameModel; import com.intellij.ide.util.gotoByName.ChooseByNamePanel; import com.intellij.ide.util.gotoByName.ChooseByNamePopupComponent; import com.intellij.ide.util.gotoByName.GotoFileCellRenderer; import com.intellij.ide.util.treeView.AlphaComparator; import com.intellij.ide.util.treeView.NodeRenderer; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ex.WindowManagerEx; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.search.FileTypeIndex; import com.intellij.psi.search.FilenameIndex; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.ui.DoubleClickListener; import com.intellij.ui.ScrollPaneFactory; import com.intellij.ui.TabbedPaneWrapper; import com.intellij.ui.TreeSpeedSearch; import com.intellij.ui.treeStructure.Tree; import com.intellij.util.ArrayUtil; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import java.awt.*; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.util.*; import java.util.List; /** * @author Anton Katilin * @author Vladimir Kondratyev */ public final class TreeFileChooserDialog extends DialogWrapper implements TreeFileChooser { private Tree myTree; private PsiFile mySelectedFile = null; private final Project myProject; private BaseProjectTreeBuilder myBuilder; private TabbedPaneWrapper myTabbedPane; private ChooseByNamePanel myGotoByNamePanel; @Nullable private final PsiFile myInitialFile; @Nullable private final PsiFileFilter myFilter; @Nullable private final FileType myFileType; private final boolean myDisableStructureProviders; private final boolean myShowLibraryContents; private boolean mySelectSearchByNameTab = false; public TreeFileChooserDialog(final Project project, String title, @Nullable final PsiFile initialFile, @Nullable FileType fileType, @Nullable PsiFileFilter filter, final boolean disableStructureProviders, final boolean showLibraryContents) { super(project, true); myInitialFile = initialFile; myFilter = filter; myFileType = fileType; myDisableStructureProviders = disableStructureProviders; myShowLibraryContents = showLibraryContents; setTitle(title); myProject = project; init(); if (initialFile != null) { // dialog does not exist yet SwingUtilities.invokeLater(() -> selectFile(initialFile)); } SwingUtilities.invokeLater(() -> handleSelectionChanged()); } @Override protected JComponent createCenterPanel() { final DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode()); myTree = new Tree(model); final ProjectAbstractTreeStructureBase treeStructure = new AbstractProjectTreeStructure(myProject) { @Override public boolean isFlattenPackages() { return false; } @Override public boolean isShowMembers() { return false; } @Override public boolean isHideEmptyMiddlePackages() { return true; } @Override public Object[] getChildElements(final Object element) { return filterFiles(super.getChildElements(element)); } @Override public boolean isAbbreviatePackageNames() { return false; } @Override public boolean isShowLibraryContents() { return myShowLibraryContents; } @Override public boolean isShowModules() { return false; } @Override public List<TreeStructureProvider> getProviders() { return myDisableStructureProviders ? null : super.getProviders(); } }; myBuilder = new ProjectTreeBuilder(myProject, myTree, model, AlphaComparator.INSTANCE, treeStructure); myTree.setRootVisible(false); myTree.expandRow(0); myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); myTree.setCellRenderer(new NodeRenderer()); UIUtil.setLineStyleAngled(myTree); final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTree); scrollPane.setPreferredSize(JBUI.size(500, 300)); myTree.addKeyListener(new KeyAdapter() { @Override public void keyPressed(final KeyEvent e) { if (KeyEvent.VK_ENTER == e.getKeyCode()) { doOKAction(); } } }); new DoubleClickListener() { @Override protected boolean onDoubleClick(MouseEvent e) { final TreePath path = myTree.getPathForLocation(e.getX(), e.getY()); if (path != null && myTree.isPathSelected(path)) { doOKAction(); return true; } return false; } }.installOn(myTree); myTree.addTreeSelectionListener( new TreeSelectionListener() { @Override public void valueChanged(final TreeSelectionEvent e) { handleSelectionChanged(); } } ); new TreeSpeedSearch(myTree); myTabbedPane = new TabbedPaneWrapper(getDisposable()); final JPanel dummyPanel = new JPanel(new BorderLayout()); String name = null; if (myInitialFile != null) { name = myInitialFile.getName(); } PsiElement context = myInitialFile == null ? null : myInitialFile; myGotoByNamePanel = new ChooseByNamePanel(myProject, new MyGotoFileModel(), name, true, context) { @Override protected void close(final boolean isOk) { super.close(isOk); if (isOk) { doOKAction(); } else { doCancelAction(); } } @Override protected void initUI(final ChooseByNamePopupComponent.Callback callback, final ModalityState modalityState, boolean allowMultipleSelection) { super.initUI(callback, modalityState, allowMultipleSelection); dummyPanel.add(myGotoByNamePanel.getPanel(), BorderLayout.CENTER); //IdeFocusTraversalPolicy.getPreferredFocusedComponent(myGotoByNamePanel.getPanel()).requestFocus(); if (mySelectSearchByNameTab) { myTabbedPane.setSelectedIndex(1); } } @Override protected void showTextFieldPanel() { } @Override protected void chosenElementMightChange() { handleSelectionChanged(); } }; myTabbedPane.addTab(IdeBundle.message("tab.chooser.project"), scrollPane); myTabbedPane.addTab(IdeBundle.message("tab.chooser.search.by.name"), dummyPanel); SwingUtilities.invokeLater(() -> myGotoByNamePanel.invoke(new MyCallback(), ModalityState.stateForComponent(getRootPane()), false)); myTabbedPane.addChangeListener( new ChangeListener() { @Override public void stateChanged(final ChangeEvent e) { handleSelectionChanged(); } } ); return myTabbedPane.getComponent(); } public void selectSearchByNameTab() { mySelectSearchByNameTab = true; } private void handleSelectionChanged(){ final PsiFile selection = calcSelectedClass(); setOKActionEnabled(selection != null); } @Override protected void doOKAction() { mySelectedFile = calcSelectedClass(); if (mySelectedFile == null) return; super.doOKAction(); } @Override public void doCancelAction() { mySelectedFile = null; super.doCancelAction(); } @Override public PsiFile getSelectedFile(){ return mySelectedFile; } @Override public void selectFile(@NotNull final PsiFile file) { // Select element in the tree ApplicationManager.getApplication().invokeLater(() -> { if (myBuilder != null) { myBuilder.select(file, file.getVirtualFile(), true); } }, ModalityState.stateForComponent(getWindow())); } @Override public void showDialog() { show(); } private PsiFile calcSelectedClass() { if (myTabbedPane.getSelectedIndex() == 1) { return (PsiFile)myGotoByNamePanel.getChosenElement(); } else { final TreePath path = myTree.getSelectionPath(); if (path == null) return null; final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); final Object userObject = node.getUserObject(); if (!(userObject instanceof ProjectViewNode)) return null; ProjectViewNode pvNode = (ProjectViewNode) userObject; VirtualFile vFile = pvNode.getVirtualFile(); if (vFile != null && !vFile.isDirectory()) { return PsiManager.getInstance(myProject).findFile(vFile); } return null; } } @Override public void dispose() { if (myBuilder != null) { Disposer.dispose(myBuilder); myBuilder = null; } super.dispose(); } @Override protected String getDimensionServiceKey() { return "#com.intellij.ide.util.TreeFileChooserDialog"; } @Override public JComponent getPreferredFocusedComponent() { return myTree; } private final class MyGotoFileModel implements ChooseByNameModel, DumbAware { private final int myMaxSize = WindowManagerEx.getInstanceEx().getFrame(myProject).getSize().width; @Override @NotNull public Object[] getElementsByName(final String name, final boolean checkBoxState, final String pattern) { GlobalSearchScope scope = myShowLibraryContents ? GlobalSearchScope.allScope(myProject) : GlobalSearchScope.projectScope(myProject); final PsiFile[] psiFiles = FilenameIndex.getFilesByName(myProject, name, scope); return filterFiles(psiFiles); } @Override public String getPromptText() { return IdeBundle.message("prompt.filechooser.enter.file.name"); } @Override public String getCheckBoxName() { return null; } @Override public char getCheckBoxMnemonic() { return 0; } @Override public String getNotInMessage() { return ""; } @Override public String getNotFoundMessage() { return ""; } @Override public boolean loadInitialCheckBoxState() { return true; } @Override public void saveInitialCheckBoxState(final boolean state) { } @Override public PsiElementListCellRenderer getListCellRenderer() { return new GotoFileCellRenderer(myMaxSize); } @Override @NotNull public String[] getNames(final boolean checkBoxState) { final String[] fileNames; if (myFileType != null && myProject != null) { GlobalSearchScope scope = myShowLibraryContents ? GlobalSearchScope.allScope(myProject) : GlobalSearchScope.projectScope(myProject); Collection<VirtualFile> virtualFiles = FileTypeIndex.getFiles(myFileType, scope); fileNames = ContainerUtil.map2Array(virtualFiles, String.class, file -> file.getName()); } else { fileNames = FilenameIndex.getAllFilenames(myProject); } final Set<String> array = new THashSet<>(); for (String fileName : fileNames) { if (!array.contains(fileName)) { array.add(fileName); } } final String[] result = ArrayUtil.toStringArray(array); Arrays.sort(result); return result; } @Override public boolean willOpenEditor() { return true; } @Override public String getElementName(final Object element) { if (!(element instanceof PsiFile)) return null; return ((PsiFile)element).getName(); } @Override @Nullable public String getFullName(final Object element) { if (element instanceof PsiFile) { final VirtualFile virtualFile = ((PsiFile)element).getVirtualFile(); return virtualFile != null ? virtualFile.getPath() : null; } return getElementName(element); } @Override public String getHelpId() { return null; } @Override @NotNull public String[] getSeparators() { return new String[] {"/", "\\"}; } @Override public boolean useMiddleMatching() { return false; } } private final class MyCallback extends ChooseByNamePopupComponent.Callback { @Override public void elementChosen(final Object element) { mySelectedFile = (PsiFile)element; close(OK_EXIT_CODE); } } private Object[] filterFiles(final Object[] list) { Condition<PsiFile> condition = psiFile -> { if (myFilter != null && !myFilter.accept(psiFile)) { return false; } boolean accepted = myFileType == null || psiFile.getFileType() == myFileType; VirtualFile virtualFile = psiFile.getVirtualFile(); if (virtualFile != null && !accepted) { accepted = virtualFile.getFileType() == myFileType; } return accepted; }; final List<Object> result = new ArrayList<>(list.length); for (Object o : list) { final PsiFile psiFile; if (o instanceof PsiFile) { psiFile = (PsiFile)o; } else if (o instanceof PsiFileNode) { psiFile = ((PsiFileNode)o).getValue(); } else { psiFile = null; } if (psiFile != null && !condition.value(psiFile)) { continue; } else { if (o instanceof ProjectViewNode) { final ProjectViewNode projectViewNode = (ProjectViewNode)o; if (!projectViewNode.canHaveChildrenMatching(condition)) { continue; } } } result.add(o); } return ArrayUtil.toObjectArray(result); } }