FileChooserDialog now supports several files
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / fileChooser / ex / FileChooserDialogImpl.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.openapi.fileChooser.ex;
17
18 import com.intellij.icons.AllIcons;
19 import com.intellij.ide.IdeEventQueue;
20 import com.intellij.ide.SaveAndSyncHandlerImpl;
21 import com.intellij.ide.util.PropertiesComponent;
22 import com.intellij.ide.util.treeView.NodeRenderer;
23 import com.intellij.openapi.actionSystem.*;
24 import com.intellij.openapi.application.ApplicationActivationListener;
25 import com.intellij.openapi.application.ApplicationManager;
26 import com.intellij.openapi.application.ModalityState;
27 import com.intellij.openapi.fileChooser.*;
28 import com.intellij.openapi.fileChooser.impl.FileChooserFactoryImpl;
29 import com.intellij.openapi.fileChooser.impl.FileChooserUtil;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.ui.DialogWrapper;
32 import com.intellij.openapi.ui.Messages;
33 import com.intellij.openapi.ui.popup.JBPopupFactory;
34 import com.intellij.openapi.util.Disposer;
35 import com.intellij.openapi.util.Iconable;
36 import com.intellij.openapi.util.text.StringUtil;
37 import com.intellij.openapi.vfs.LocalFileSystem;
38 import com.intellij.openapi.vfs.VfsUtil;
39 import com.intellij.openapi.vfs.VfsUtilCore;
40 import com.intellij.openapi.vfs.VirtualFile;
41 import com.intellij.openapi.wm.IdeFrame;
42 import com.intellij.ui.*;
43 import com.intellij.ui.components.JBList;
44 import com.intellij.ui.components.labels.LinkLabel;
45 import com.intellij.util.ArrayUtil;
46 import com.intellij.util.Consumer;
47 import com.intellij.util.IconUtil;
48 import com.intellij.util.containers.HashMap;
49 import com.intellij.util.ui.UIUtil;
50 import com.intellij.util.ui.update.MergingUpdateQueue;
51 import com.intellij.util.ui.update.UiNotifyConnector;
52 import com.intellij.util.ui.update.Update;
53 import org.jetbrains.annotations.NonNls;
54 import org.jetbrains.annotations.NotNull;
55 import org.jetbrains.annotations.Nullable;
56
57 import javax.swing.*;
58 import javax.swing.border.EmptyBorder;
59 import javax.swing.event.TreeExpansionEvent;
60 import javax.swing.event.TreeExpansionListener;
61 import javax.swing.event.TreeSelectionEvent;
62 import javax.swing.event.TreeSelectionListener;
63 import javax.swing.tree.DefaultMutableTreeNode;
64 import javax.swing.tree.TreePath;
65 import java.awt.*;
66 import java.awt.event.KeyEvent;
67 import java.awt.event.MouseEvent;
68 import java.io.File;
69 import java.util.ArrayList;
70 import java.util.Arrays;
71 import java.util.List;
72 import java.util.Map;
73
74 public class FileChooserDialogImpl extends DialogWrapper implements FileChooserDialog, PathChooserDialog, FileLookup {
75   @NonNls public static final String FILE_CHOOSER_SHOW_PATH_PROPERTY = "FileChooser.ShowPath";
76   public static final String RECENT_FILES_KEY = "file.chooser.recent.files";
77   private final FileChooserDescriptor myChooserDescriptor;
78   protected FileSystemTreeImpl myFileSystemTree;
79   private Project myProject;
80   private VirtualFile[] myChosenFiles = VirtualFile.EMPTY_ARRAY;
81
82   private JPanel myNorthPanel;
83   private TextFieldAction myTextFieldAction;
84   protected FileTextFieldImpl myPathTextField;
85   private JComponent myPathTextFieldWrapper;
86
87   private MergingUpdateQueue myUiUpdater;
88   private boolean myTreeIsUpdating;
89
90   public static DataKey<PathField> PATH_FIELD = DataKey.create("PathField");
91
92   public FileChooserDialogImpl(@NotNull final FileChooserDescriptor descriptor, @Nullable Project project) {
93     super(project, true);
94     myChooserDescriptor = descriptor;
95     myProject = project;
96     setTitle(getChooserTitle(descriptor));
97   }
98
99   public FileChooserDialogImpl(@NotNull final FileChooserDescriptor descriptor, @NotNull Component parent) {
100     this(descriptor, parent, null);
101   }
102
103   public FileChooserDialogImpl(@NotNull final FileChooserDescriptor descriptor, @NotNull Component parent, @Nullable Project project) {
104     super(parent, true);
105     myChooserDescriptor = descriptor;
106     myProject = project;
107     setTitle(getChooserTitle(descriptor));
108   }
109
110   private static String getChooserTitle(final FileChooserDescriptor descriptor) {
111     final String title = descriptor.getTitle();
112     return title != null ? title : UIBundle.message("file.chooser.default.title");
113   }
114
115   @Override
116   @NotNull
117   public VirtualFile[] choose(@Nullable final Project project, @NotNull final VirtualFile... toSelect) {
118     init();
119     if ((myProject == null) && (project != null)) {
120       myProject = project;
121     }
122     if (toSelect.length == 1) {
123       restoreSelection(toSelect[0]);
124     }
125     else {
126       selectInTree(toSelect, true);
127     }
128
129     show();
130     return myChosenFiles;
131   }
132
133
134   @NotNull
135   @Override
136   public VirtualFile[] choose(@Nullable final VirtualFile toSelect, @Nullable final Project project) {
137     return choose(project, toSelect);
138   }
139
140   @Override
141   public void choose(@Nullable VirtualFile toSelect, @NotNull Consumer<List<VirtualFile>> callback) {
142     init();
143     restoreSelection(toSelect);
144     show();
145     if (myChosenFiles.length > 0) {
146       callback.consume(Arrays.asList(myChosenFiles));
147     }
148     else if (callback instanceof FileChooser.FileChooserConsumer) {
149       ((FileChooser.FileChooserConsumer)callback).cancelled();
150     }
151   }
152
153   protected void restoreSelection(@Nullable VirtualFile toSelect) {
154     final VirtualFile lastOpenedFile = FileChooserUtil.getLastOpenedFile(myProject);
155     final VirtualFile file = FileChooserUtil.getFileToSelect(myChooserDescriptor, myProject, toSelect, lastOpenedFile);
156
157     if (file != null && file.isValid()) {
158       myFileSystemTree.select(file, new Runnable() {
159         public void run() {
160           if (!file.equals(myFileSystemTree.getSelectedFile())) {
161             VirtualFile parent = file.getParent();
162             if (parent != null) {
163               myFileSystemTree.select(parent, null);
164             }
165           }
166           else if (file.isDirectory()) {
167             myFileSystemTree.expand(file, null);
168           }
169         }
170       });
171     }
172   }
173
174   protected void storeSelection(@Nullable VirtualFile file) {
175     FileChooserUtil.setLastOpenedFile(myProject, file);
176     if (file != null && file.getFileSystem() instanceof LocalFileSystem) {
177       saveRecent(file.getPath());
178     }
179   }
180
181   protected void saveRecent(String path) {
182     final List<String> files = new ArrayList<String>(Arrays.asList(getRecentFiles()));
183     files.remove(path);
184     files.add(0, path);
185     while (files.size() > 30) {
186       files.remove(files.size() - 1);
187     }
188     PropertiesComponent.getInstance().setValues(RECENT_FILES_KEY, ArrayUtil.toStringArray(files));
189   }
190
191   @NotNull
192   private String[] getRecentFiles() {
193     final String[] recent = PropertiesComponent.getInstance().getValues(RECENT_FILES_KEY);
194     if (recent != null) {
195       if (recent.length > 0 && myPathTextField.getField().getText().replace('\\', '/').equals(recent[0])) {
196         final String[] pathes = new String[recent.length - 1];
197         System.arraycopy(recent, 1, pathes, 0, recent.length - 1);
198         return pathes;
199       }
200       return recent;
201     }
202     return ArrayUtil.EMPTY_STRING_ARRAY;
203   }
204
205
206   protected JComponent createHistoryButton() {
207     JLabel label = new JLabel(AllIcons.Actions.Get);
208     label.setToolTipText("Recent files");
209     new ClickListener() {
210       @Override
211       public boolean onClick(@NotNull MouseEvent event, int clickCount) {
212         showRecentFilesPopup();
213         return true;
214       }
215     }.installOn(label);
216
217     new AnAction() {
218       @Override
219       public void actionPerformed(AnActionEvent e) {
220         showRecentFilesPopup();
221       }
222
223       @Override
224       public void update(AnActionEvent e) {
225         e.getPresentation().setEnabled(!IdeEventQueue.getInstance().isPopupActive());
226       }
227     }.registerCustomShortcutSet(KeyEvent.VK_DOWN, 0, myPathTextField.getField());
228     return label;
229   }
230
231   private void showRecentFilesPopup() {
232     final JBList files = new JBList(getRecentFiles()) {
233       @Override
234       public Dimension getPreferredSize() {
235         return new Dimension(myPathTextField.getField().getWidth(), super.getPreferredSize().height);
236       }
237     };
238     files.setCellRenderer(new ColoredListCellRenderer() {
239       @Override
240       protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) {
241         final String path = value.toString();
242         append(path);
243         final VirtualFile file = LocalFileSystem.getInstance().findFileByIoFile(new File(path));
244         if (file != null) {
245           setIcon(IconUtil.getIcon(file, Iconable.ICON_FLAG_READ_STATUS, null));
246         }
247       }
248     });
249     JBPopupFactory.getInstance()
250       .createListPopupBuilder(files)
251       .setItemChoosenCallback(new Runnable() {
252         @Override
253         public void run() {
254           myPathTextField.getField().setText(files.getSelectedValue().toString());
255         }
256       }).createPopup().showUnderneathOf(myPathTextField.getField());
257   }
258
259
260   protected DefaultActionGroup createActionGroup() {
261     registerFileChooserShortcut(IdeActions.ACTION_DELETE, "FileChooser.Delete");
262     registerFileChooserShortcut(IdeActions.ACTION_SYNCHRONIZE, "FileChooser.Refresh");
263
264     return (DefaultActionGroup)ActionManager.getInstance().getAction("FileChooserToolbar");
265   }
266
267   private void registerFileChooserShortcut(@NonNls final String baseActionId, @NonNls final String fileChooserActionId) {
268     final JTree tree = myFileSystemTree.getTree();
269     final AnAction syncAction = ActionManager.getInstance().getAction(fileChooserActionId);
270
271     AnAction original = ActionManager.getInstance().getAction(baseActionId);
272     syncAction.registerCustomShortcutSet(original.getShortcutSet(), tree, myDisposable);
273   }
274
275   @Nullable
276   protected final JComponent createTitlePane() {
277     final String description = myChooserDescriptor.getDescription();
278     if (StringUtil.isEmptyOrSpaces(description)) return null;
279
280     final JLabel label = new JLabel(description);
281     label.setBorder(BorderFactory.createCompoundBorder(
282       new SideBorder(UIUtil.getPanelBackground().darker(), SideBorder.BOTTOM),
283       BorderFactory.createEmptyBorder(0, 5, 10, 5)));
284     return label;
285   }
286
287   protected JComponent createCenterPanel() {
288     JPanel panel = new MyPanel();
289
290     myUiUpdater = new MergingUpdateQueue("FileChooserUpdater", 200, false, panel);
291     Disposer.register(myDisposable, myUiUpdater);
292     new UiNotifyConnector(panel, myUiUpdater);
293
294     panel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
295
296     createTree();
297
298     final DefaultActionGroup group = createActionGroup();
299     ActionToolbar toolBar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true);
300     toolBar.setTargetComponent(panel);
301
302     final JPanel toolbarPanel = new JPanel(new BorderLayout());
303     toolbarPanel.add(toolBar.getComponent(), BorderLayout.CENTER);
304
305     myTextFieldAction = new TextFieldAction() {
306       public void linkSelected(final LinkLabel aSource, final Object aLinkData) {
307         toggleShowTextField();
308       }
309     };
310     toolbarPanel.add(myTextFieldAction, BorderLayout.EAST);
311
312     myPathTextFieldWrapper = new JPanel(new BorderLayout());
313     myPathTextFieldWrapper.setBorder(new EmptyBorder(0, 0, 2, 0));
314     myPathTextField = new FileTextFieldImpl.Vfs(
315       FileChooserFactoryImpl.getMacroMap(), getDisposable(),
316       new LocalFsFinder.FileChooserFilter(myChooserDescriptor, myFileSystemTree)) {
317       protected void onTextChanged(final String newValue) {
318         myUiUpdater.cancelAllUpdates();
319         updateTreeFromPath(newValue);
320       }
321     };
322     Disposer.register(myDisposable, myPathTextField);
323     myPathTextFieldWrapper.add(myPathTextField.getField(), BorderLayout.CENTER);
324     if (getRecentFiles().length > 0) {
325       myPathTextFieldWrapper.add(createHistoryButton(), BorderLayout.EAST);
326     }
327
328     myNorthPanel = new JPanel(new BorderLayout());
329     myNorthPanel.add(toolbarPanel, BorderLayout.NORTH);
330
331
332     updateTextFieldShowing();
333
334     panel.add(myNorthPanel, BorderLayout.NORTH);
335
336     registerMouseListener(group);
337
338     JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myFileSystemTree.getTree());
339     //scrollPane.setBorder(BorderFactory.createLineBorder(new Color(148, 154, 156)));
340     panel.add(scrollPane, BorderLayout.CENTER);
341     panel.setPreferredSize(new Dimension(400, 400));
342
343
344     panel.add(new JLabel(
345       "<html><center><small><font color=gray>Drag and drop a file into the space above to quickly locate it in the tree.</font></small></center></html>",
346       SwingConstants.CENTER), BorderLayout.SOUTH);
347
348
349     ApplicationManager.getApplication().getMessageBus().connect(getDisposable())
350       .subscribe(ApplicationActivationListener.TOPIC, new ApplicationActivationListener.Adapter() {
351         @Override
352         public void applicationActivated(IdeFrame ideFrame) {
353           SaveAndSyncHandlerImpl.getInstance().maybeRefresh(ModalityState.current());
354         }
355       });
356
357     return panel;
358   }
359
360
361   public JComponent getPreferredFocusedComponent() {
362     if (isToShowTextField()) {
363       return myPathTextField != null ? myPathTextField.getField() : null;
364     }
365     else {
366       return myFileSystemTree != null ? myFileSystemTree.getTree() : null;
367     }
368   }
369
370   public final void dispose() {
371     LocalFileSystem.getInstance().removeWatchedRoots(myRequests.values());
372     super.dispose();
373   }
374
375   private boolean isTextFieldActive() {
376     return myPathTextField.getField().getRootPane() != null;
377   }
378
379   protected void doOKAction() {
380     if (!isOKActionEnabled()) {
381       return;
382     }
383
384     if (isTextFieldActive()) {
385       final String text = myPathTextField.getTextFieldText();
386       final LookupFile file = myPathTextField.getFile();
387       if (text == null || file == null || !file.exists()) {
388         setErrorText("Specified path cannot be found");
389         return;
390       }
391     }
392
393     final List<VirtualFile> selectedFiles = Arrays.asList(getSelectedFilesInt());
394     final VirtualFile[] files = VfsUtilCore.toVirtualFileArray(FileChooserUtil.getChosenFiles(myChooserDescriptor, selectedFiles));
395     if (files.length == 0) {
396       myChosenFiles = VirtualFile.EMPTY_ARRAY;
397       close(CANCEL_EXIT_CODE);
398       return;
399     }
400
401     try {
402       myChooserDescriptor.validateSelectedFiles(files);
403     }
404     catch (Exception e) {
405       Messages.showErrorDialog(getContentPane(), e.getMessage(), getTitle());
406       return;
407     }
408
409     myChosenFiles = files;
410     storeSelection(files[files.length - 1]);
411
412     super.doOKAction();
413   }
414
415   public final void doCancelAction() {
416     myChosenFiles = VirtualFile.EMPTY_ARRAY;
417     super.doCancelAction();
418   }
419
420   protected JTree createTree() {
421     myFileSystemTree = new FileSystemTreeImpl(myProject, myChooserDescriptor);
422     Disposer.register(myDisposable, myFileSystemTree);
423
424     myFileSystemTree.addOkAction(new Runnable() {
425       public void run() {
426         doOKAction();
427       }
428     });
429     JTree tree = myFileSystemTree.getTree();
430     tree.setCellRenderer(new NodeRenderer());
431     tree.getSelectionModel().addTreeSelectionListener(new FileTreeSelectionListener());
432     tree.addTreeExpansionListener(new FileTreeExpansionListener());
433     setOKActionEnabled(false);
434
435     myFileSystemTree.addListener(new FileSystemTree.Listener() {
436       public void selectionChanged(final List<VirtualFile> selection) {
437         updatePathFromTree(selection, false);
438       }
439     }, myDisposable);
440
441     new FileDrop(tree, new FileDrop.Target() {
442       public FileChooserDescriptor getDescriptor() {
443         return myChooserDescriptor;
444       }
445
446       public boolean isHiddenShown() {
447         return myFileSystemTree.areHiddensShown();
448       }
449
450       public void dropFiles(final List<VirtualFile> files) {
451         if (!myChooserDescriptor.isChooseMultiple() && files.size() > 0) {
452           selectInTree(new VirtualFile[]{files.get(0)}, true);
453         }
454         else {
455           selectInTree(VfsUtilCore.toVirtualFileArray(files), true);
456         }
457       }
458     });
459
460     return tree;
461   }
462
463   protected final void registerMouseListener(final ActionGroup group) {
464     myFileSystemTree.registerMouseListener(group);
465   }
466
467   private VirtualFile[] getSelectedFilesInt() {
468     if (myTreeIsUpdating || !myUiUpdater.isEmpty()) {
469       if (isTextFieldActive() && !StringUtil.isEmpty(myPathTextField.getTextFieldText())) {
470         LookupFile toFind = myPathTextField.getFile();
471         if (toFind instanceof LocalFsFinder.VfsFile && toFind.exists()) {
472           VirtualFile file = ((LocalFsFinder.VfsFile)toFind).getFile();
473           if (file != null) {
474             return new VirtualFile[]{file};
475           }
476         }
477       }
478       return VirtualFile.EMPTY_ARRAY;
479     }
480
481     return myFileSystemTree.getSelectedFiles();
482   }
483
484   private final Map<String, LocalFileSystem.WatchRequest> myRequests = new HashMap<String, LocalFileSystem.WatchRequest>();
485
486   private static boolean isToShowTextField() {
487     return PropertiesComponent.getInstance().getBoolean(FILE_CHOOSER_SHOW_PATH_PROPERTY, true);
488   }
489
490   private static void setToShowTextField(boolean toShowTextField) {
491     PropertiesComponent.getInstance().setValue(FILE_CHOOSER_SHOW_PATH_PROPERTY, Boolean.toString(toShowTextField));
492   }
493
494   private final class FileTreeExpansionListener implements TreeExpansionListener {
495     public void treeExpanded(TreeExpansionEvent event) {
496       final Object[] path = event.getPath().getPath();
497       if (path.length == 2) {
498         // top node has been expanded => watch disk recursively
499         final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path[1];
500         Object userObject = node.getUserObject();
501         if (userObject instanceof FileNodeDescriptor) {
502           final VirtualFile file = ((FileNodeDescriptor)userObject).getElement().getFile();
503           if (file != null && file.isDirectory()) {
504             final String rootPath = file.getPath();
505             if (myRequests.get(rootPath) == null) {
506               final LocalFileSystem.WatchRequest watchRequest = LocalFileSystem.getInstance().addRootToWatch(rootPath, true);
507               myRequests.put(rootPath, watchRequest);
508             }
509           }
510         }
511       }
512     }
513
514     public void treeCollapsed(TreeExpansionEvent event) {
515     }
516   }
517
518   private final class FileTreeSelectionListener implements TreeSelectionListener {
519     public void valueChanged(TreeSelectionEvent e) {
520       TreePath[] paths = e.getPaths();
521
522       boolean enabled = true;
523       for (TreePath treePath : paths) {
524         if (!e.isAddedPath(treePath)) {
525           continue;
526         }
527         DefaultMutableTreeNode node = (DefaultMutableTreeNode)treePath.getLastPathComponent();
528         Object userObject = node.getUserObject();
529         if (!(userObject instanceof FileNodeDescriptor)) {
530           enabled = false;
531           break;
532         }
533         FileElement descriptor = ((FileNodeDescriptor)userObject).getElement();
534         VirtualFile file = descriptor.getFile();
535         enabled = file != null && myChooserDescriptor.isFileSelectable(file);
536       }
537       setOKActionEnabled(enabled);
538     }
539   }
540
541   protected final class MyPanel extends JPanel implements DataProvider {
542     public MyPanel() {
543       super(new BorderLayout(0, 0));
544     }
545
546     public Object getData(String dataId) {
547       if (CommonDataKeys.VIRTUAL_FILE_ARRAY.is(dataId)) {
548         return myFileSystemTree.getSelectedFiles();
549       }
550       else if (PATH_FIELD.is(dataId)) {
551         return new PathField() {
552           public void toggleVisible() {
553             toggleShowTextField();
554           }
555         };
556       }
557       else if (FileSystemTree.DATA_KEY.is(dataId)) {
558         return myFileSystemTree;
559       }
560       return myChooserDescriptor.getUserData(dataId);
561     }
562   }
563
564   public void toggleShowTextField() {
565     setToShowTextField(!isToShowTextField());
566     updateTextFieldShowing();
567   }
568
569   private void updateTextFieldShowing() {
570     myTextFieldAction.update();
571     myNorthPanel.remove(myPathTextFieldWrapper);
572     if (isToShowTextField()) {
573       final ArrayList<VirtualFile> selection = new ArrayList<VirtualFile>();
574       if (myFileSystemTree.getSelectedFile() != null) {
575         selection.add(myFileSystemTree.getSelectedFile());
576       }
577       updatePathFromTree(selection, true);
578       myNorthPanel.add(myPathTextFieldWrapper, BorderLayout.CENTER);
579     }
580     else {
581       setErrorText(null);
582     }
583     myPathTextField.getField().requestFocus();
584
585     myNorthPanel.revalidate();
586     myNorthPanel.repaint();
587   }
588
589
590   private void updatePathFromTree(final List<VirtualFile> selection, boolean now) {
591     if (!isToShowTextField() || myTreeIsUpdating) return;
592
593     String text = "";
594     if (selection.size() > 0) {
595       text = VfsUtil.getReadableUrl(selection.get(0));
596     }
597     else {
598       final List<VirtualFile> roots = myChooserDescriptor.getRoots();
599       if (!myFileSystemTree.getTree().isRootVisible() && roots.size() == 1) {
600         text = VfsUtil.getReadableUrl(roots.get(0));
601       }
602     }
603
604     myPathTextField.setText(text, now, new Runnable() {
605       public void run() {
606         myPathTextField.getField().selectAll();
607         setErrorText(null);
608       }
609     });
610   }
611
612   private void updateTreeFromPath(final String text) {
613     if (!isToShowTextField()) return;
614     if (myPathTextField.isPathUpdating()) return;
615     if (text == null) return;
616
617     myUiUpdater.queue(new Update("treeFromPath.1") {
618       public void run() {
619         ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
620           public void run() {
621             final LocalFsFinder.VfsFile toFind = (LocalFsFinder.VfsFile)myPathTextField.getFile();
622             if (toFind == null || !toFind.exists()) return;
623
624             myUiUpdater.queue(new Update("treeFromPath.2") {
625               public void run() {
626                 selectInTree(toFind.getFile(), text);
627               }
628             });
629           }
630         });
631       }
632     });
633   }
634
635   private void selectInTree(final VirtualFile vFile, String fromText) {
636     if (vFile != null && vFile.isValid()) {
637       if (fromText == null || fromText.equalsIgnoreCase(myPathTextField.getTextFieldText())) {
638         selectInTree(new VirtualFile[]{vFile}, false);
639       }
640     }
641     else {
642       reportFileNotFound();
643     }
644   }
645
646   private void selectInTree(final VirtualFile[] array, final boolean requestFocus) {
647     myTreeIsUpdating = true;
648     final List<VirtualFile> fileList = Arrays.asList(array);
649     if (!Arrays.asList(myFileSystemTree.getSelectedFiles()).containsAll(fileList)) {
650       myFileSystemTree.select(array, new Runnable() {
651         public void run() {
652           if (!myFileSystemTree.areHiddensShown() && !Arrays.asList(myFileSystemTree.getSelectedFiles()).containsAll(fileList)) {
653             myFileSystemTree.showHiddens(true);
654             selectInTree(array, requestFocus);
655             return;
656           }
657
658           myTreeIsUpdating = false;
659           setErrorText(null);
660           if (requestFocus) {
661             //noinspection SSBasedInspection
662             SwingUtilities.invokeLater(new Runnable() {
663               public void run() {
664                 myFileSystemTree.getTree().requestFocus();
665               }
666             });
667           }
668         }
669       });
670     }
671     else {
672       myTreeIsUpdating = false;
673       setErrorText(null);
674     }
675   }
676
677   private void reportFileNotFound() {
678     myTreeIsUpdating = false;
679     setErrorText(null);
680   }
681
682   @Override
683   protected String getDimensionServiceKey() {
684     return "FileChooserDialogImpl";
685   }
686
687   @Override
688   protected String getHelpId() {
689     return "select.path.dialog";
690   }
691 }