1 package com
.intellij
.openapi
.fileChooser
.ex
;
3 import com
.intellij
.ide
.util
.treeView
.NodeRenderer
;
4 import com
.intellij
.ide
.IdeBundle
;
5 import com
.intellij
.idea
.ActionsBundle
;
6 import com
.intellij
.openapi
.Disposable
;
7 import com
.intellij
.openapi
.actionSystem
.*;
8 import com
.intellij
.openapi
.application
.ApplicationManager
;
9 import com
.intellij
.openapi
.fileChooser
.FileChooserDescriptor
;
10 import com
.intellij
.openapi
.fileChooser
.FileChooserDialog
;
11 import com
.intellij
.openapi
.fileChooser
.FileElement
;
12 import com
.intellij
.openapi
.fileChooser
.FileSystemTree
;
13 import com
.intellij
.openapi
.fileChooser
.impl
.FileChooserFactoryImpl
;
14 import com
.intellij
.openapi
.project
.Project
;
15 import com
.intellij
.openapi
.ui
.DialogWrapper
;
16 import com
.intellij
.openapi
.ui
.Messages
;
17 import com
.intellij
.openapi
.ui
.TitlePanel
;
18 import com
.intellij
.openapi
.util
.Disposer
;
19 import com
.intellij
.openapi
.vfs
.LocalFileSystem
;
20 import com
.intellij
.openapi
.vfs
.VirtualFile
;
21 import com
.intellij
.ui
.UIBundle
;
22 import com
.intellij
.ui
.components
.labels
.LinkLabel
;
23 import com
.intellij
.ui
.components
.labels
.LinkListener
;
24 import com
.intellij
.util
.containers
.HashMap
;
25 import com
.intellij
.util
.ui
.update
.MergingUpdateQueue
;
26 import com
.intellij
.util
.ui
.update
.UiNotifyConnector
;
27 import com
.intellij
.util
.ui
.update
.Update
;
28 import org
.jetbrains
.annotations
.NonNls
;
29 import org
.jetbrains
.annotations
.NotNull
;
32 import javax
.swing
.border
.EmptyBorder
;
33 import javax
.swing
.event
.TreeExpansionEvent
;
34 import javax
.swing
.event
.TreeExpansionListener
;
35 import javax
.swing
.event
.TreeSelectionEvent
;
36 import javax
.swing
.event
.TreeSelectionListener
;
37 import javax
.swing
.tree
.DefaultMutableTreeNode
;
38 import javax
.swing
.tree
.TreePath
;
40 import java
.util
.ArrayList
;
41 import java
.util
.Arrays
;
42 import java
.util
.List
;
45 public class FileChooserDialogImpl
extends DialogWrapper
implements FileChooserDialog
, FileLookup
{
46 private final FileChooserDescriptor myChooserDescriptor
;
47 protected FileSystemTreeImpl myFileSystemTree
;
49 private static VirtualFile ourLastFile
;
50 private Project myProject
;
51 private VirtualFile
[] myChosenFiles
= VirtualFile
.EMPTY_ARRAY
;
53 private final List
<Disposable
> myDisposables
= new ArrayList
<Disposable
>();
54 private JPanel myNorthPanel
;
56 private static boolean ourToShowTextField
= true;
57 private FileChooserDialogImpl
.TextFieldAction myTextFieldAction
;
59 protected FileTextFieldImpl myPathTextField
;
61 private JComponent myPathTextFieldWrapper
;
63 private MergingUpdateQueue myUiUpdater
;
65 private boolean myTreeIsUpdating
;
67 public static DataKey
<FileChooserDialogImpl
> KEY
= DataKey
.create("FileChooserDialog");
69 public FileChooserDialogImpl(FileChooserDescriptor chooserDescriptor
, Project project
) {
72 myChooserDescriptor
= chooserDescriptor
;
73 setTitle(UIBundle
.message("file.chooser.default.title"));
76 public FileChooserDialogImpl(FileChooserDescriptor chooserDescriptor
, Component parent
) {
78 myChooserDescriptor
= chooserDescriptor
;
79 setTitle(UIBundle
.message("file.chooser.default.title"));
83 public VirtualFile
[] choose(VirtualFile toSelect
, Project project
) {
86 VirtualFile selectFile
= null;
88 if (toSelect
== null && ourLastFile
== null) {
89 if (project
!= null && project
.getBaseDir() != null) {
90 selectFile
= project
.getBaseDir();
93 selectFile
= (toSelect
== null) ? ourLastFile
: toSelect
;
96 final VirtualFile file
= selectFile
;
98 if (file
!= null && file
.isValid()) {
99 myFileSystemTree
.select(file
, new Runnable() {
101 if (!file
.equals(myFileSystemTree
.getSelectedFile())) {
102 VirtualFile parent
= file
.getParent();
103 if (parent
!= null) {
104 myFileSystemTree
.select(parent
, null);
113 return myChosenFiles
;
116 protected DefaultActionGroup
createActionGroup() {
117 registerFileChooserShortcut(IdeActions
.ACTION_DELETE
, "FileChooser.Delete");
118 registerFileChooserShortcut(IdeActions
.ACTION_SYNCHRONIZE
, "FileChooser.Refresh");
120 return (DefaultActionGroup
) ActionManager
.getInstance().getAction("FileChooserToolbar");
123 private void registerFileChooserShortcut(@NonNls final String baseActionId
, @NonNls final String fileChooserActionId
) {
124 final JTree tree
= myFileSystemTree
.getTree();
125 final AnAction syncAction
= ActionManager
.getInstance().getAction(fileChooserActionId
);
127 AnAction original
= ActionManager
.getInstance().getAction(baseActionId
);
128 syncAction
.registerCustomShortcutSet(original
.getShortcutSet(), tree
);
129 myDisposables
.add(new Disposable() {
130 public void dispose() {
131 syncAction
.unregisterCustomShortcutSet(tree
);
136 private void addToGroup(DefaultActionGroup group
, AnAction action
) {
138 if (action
instanceof Disposable
) {
139 myDisposables
.add(((Disposable
)action
));
143 protected final JComponent
createTitlePane() {
144 return new TitlePanel(myChooserDescriptor
.getTitle(), myChooserDescriptor
.getDescription());
147 protected JComponent
createCenterPanel() {
148 JPanel panel
= new MyPanel();
150 myUiUpdater
= new MergingUpdateQueue("FileChooserUpdater", 200, false, panel
);
151 Disposer
.register(myDisposable
, myUiUpdater
);
152 new UiNotifyConnector(panel
, myUiUpdater
);
154 panel
.setBorder(BorderFactory
.createEmptyBorder(0, 0, 0, 0));
158 final DefaultActionGroup group
= createActionGroup();
159 ActionToolbar toolBar
= ActionManager
.getInstance().createActionToolbar(ActionPlaces
.UNKNOWN
, group
, true);
161 final JPanel toolbarPanel
= new JPanel(new BorderLayout());
162 toolbarPanel
.add(toolBar
.getComponent(), BorderLayout
.CENTER
);
164 myTextFieldAction
= new TextFieldAction();
165 toolbarPanel
.add(myTextFieldAction
, BorderLayout
.EAST
);
167 myPathTextFieldWrapper
= new JPanel(new BorderLayout());
168 myPathTextFieldWrapper
.setBorder(new EmptyBorder(0, 0, 2, 0));
169 myPathTextField
= new FileTextFieldImpl
.Vfs(myChooserDescriptor
, myFileSystemTree
.areHiddensShown(), myUiUpdater
,
170 FileChooserFactoryImpl
.getMacroMap()) {
171 protected void onTextChanged(final String newValue
) {
172 updateTreeFromPath(newValue
);
175 myDisposables
.add(myPathTextField
);
176 myPathTextFieldWrapper
.add(myPathTextField
.getField(), BorderLayout
.CENTER
);
178 myNorthPanel
= new JPanel(new BorderLayout());
179 myNorthPanel
.add(toolbarPanel
, BorderLayout
.NORTH
);
182 updateTextFieldShowing();
184 panel
.add(myNorthPanel
, BorderLayout
.NORTH
);
186 registerMouseListener(group
);
188 JScrollPane scrollPane
= new JScrollPane(myFileSystemTree
.getTree());
189 //scrollPane.setBorder(BorderFactory.createLineBorder(new Color(148, 154, 156)));
190 panel
.add(scrollPane
, BorderLayout
.CENTER
);
191 panel
.setPreferredSize(new Dimension(400, 400));
194 panel
.add(new JLabel("<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>", JLabel
.CENTER
), BorderLayout
.SOUTH
);
200 public JComponent
getPreferredFocusedComponent() {
201 if (ourToShowTextField
) {
202 return myPathTextField
!= null ? myPathTextField
.getField() : null;
204 return myFileSystemTree
!= null ? myFileSystemTree
.getTree() : null;
208 public final void dispose() {
209 for (Disposable disposable
: myDisposables
) {
210 Disposer
.dispose(disposable
);
212 myDisposables
.clear();
213 myFileSystemTree
.dispose();
214 LocalFileSystem
.getInstance().removeWatchedRoots(myRequests
.values());
218 private boolean isTextFieldActive() {
219 return myPathTextField
.getField().getRootPane() != null;
222 protected void doOKAction() {
223 if (!isOKActionEnabled()) {
227 if (isTextFieldActive()) {
228 final String text
= myPathTextField
.getTextFieldText();
229 if (text
== null || myPathTextField
.getFile() == null || !myPathTextField
.getFile().exists()) {
230 setErrorText("Specified path cannot be found");
236 final VirtualFile
[] selectedFiles
= getSelectedFiles();
238 myChooserDescriptor
.validateSelectedFiles(selectedFiles
);
240 catch (Exception e
) {
241 Messages
.showErrorDialog(getContentPane(), e
.getMessage(), UIBundle
.message("file.chooser.default.title"));
245 myChosenFiles
= selectedFiles
;
246 if (selectedFiles
.length
== 0) {
247 close(CANCEL_EXIT_CODE
);
250 ourLastFile
= selectedFiles
[selectedFiles
.length
- 1];
255 public final void doCancelAction() {
256 myChosenFiles
= VirtualFile
.EMPTY_ARRAY
;
257 super.doCancelAction();
260 protected JTree
createTree() {
261 myFileSystemTree
= new FileSystemTreeImpl(myProject
, myChooserDescriptor
);
263 myFileSystemTree
.addOkAction(new Runnable() {
264 public void run() {doOKAction(); }
266 JTree tree
= myFileSystemTree
.getTree();
267 tree
.setCellRenderer(new NodeRenderer());
268 tree
.addTreeSelectionListener(new FileTreeSelectionListener());
269 tree
.addTreeExpansionListener(new FileTreeExpansionListener());
270 setOKActionEnabled(false);
272 myFileSystemTree
.addListener(new FileSystemTree
.Listener() {
273 public void selectionChanged(final List
<VirtualFile
> selection
) {
274 updatePathFromTree(selection
, false);
278 new FileDrop(tree
, new FileDrop
.Target() {
279 public FileChooserDescriptor
getDescriptor() {
280 return myChooserDescriptor
;
283 public boolean isHiddenShown() {
284 return myFileSystemTree
.areHiddensShown();
287 public void dropFiles(final List
<VirtualFile
> files
) {
288 if (!myChooserDescriptor
.isChooseMultiple() && files
.size() > 0) {
289 selectInTree(new VirtualFile
[] {files
.get(0)}, true);
291 selectInTree(files
.toArray(new VirtualFile
[files
.size()]), true);
299 protected final void registerMouseListener(final ActionGroup group
) {
300 myFileSystemTree
.registerMouseListener(group
);
303 protected VirtualFile
[] getSelectedFiles() {
304 return myFileSystemTree
.getChoosenFiles();
307 private final Map
<String
, LocalFileSystem
.WatchRequest
> myRequests
= new HashMap
<String
, LocalFileSystem
.WatchRequest
>();
309 private final class FileTreeExpansionListener
implements TreeExpansionListener
{
310 public void treeExpanded(TreeExpansionEvent event
) {
311 final Object
[] path
= event
.getPath().getPath();
312 if (path
.length
== 2) {
313 // top node has been expanded => watch disk recursively
314 final DefaultMutableTreeNode node
= (DefaultMutableTreeNode
)path
[1];
315 Object userObject
= node
.getUserObject();
316 if (userObject
instanceof FileNodeDescriptor
) {
317 final VirtualFile file
= ((FileNodeDescriptor
)userObject
).getElement().getFile();
318 if (file
!= null && file
.isDirectory()) {
319 final String rootPath
= file
.getPath();
320 if (myRequests
.get(rootPath
) == null) {
321 final LocalFileSystem
.WatchRequest watchRequest
= LocalFileSystem
.getInstance().addRootToWatch(rootPath
, true);
322 myRequests
.put(rootPath
, watchRequest
);
329 public void treeCollapsed(TreeExpansionEvent event
) {
330 //Do not unwatch here!!!
334 private final class FileTreeSelectionListener
implements TreeSelectionListener
{
335 public void valueChanged(TreeSelectionEvent e
) {
336 TreePath
[] paths
= e
.getPaths();
338 boolean enabled
= true;
339 for (TreePath treePath
: paths
) {
340 if (!e
.isAddedPath(treePath
)) {
343 DefaultMutableTreeNode node
= (DefaultMutableTreeNode
)treePath
.getLastPathComponent();
344 Object userObject
= node
.getUserObject();
345 if (!(userObject
instanceof FileNodeDescriptor
)) {
349 FileElement descriptor
= ((FileNodeDescriptor
)userObject
).getElement();
350 VirtualFile file
= descriptor
.getFile();
351 enabled
= file
!= null && myChooserDescriptor
.isFileSelectable(file
);
353 setOKActionEnabled(enabled
);
357 protected final class MyPanel
extends JPanel
implements DataProvider
{
359 super(new BorderLayout(0, 0));
362 public Object
getData(String dataId
) {
363 if (DataConstants
.VIRTUAL_FILE_ARRAY
.equals(dataId
)) {
364 return getSelectedFiles();
365 } else if (KEY
.getName().equals(dataId
)) {
366 return FileChooserDialogImpl
.this;
368 else if (FileSystemTree
.DATA_KEY
.getName().equals(dataId
)) {
369 return myFileSystemTree
;
371 return myChooserDescriptor
.getUserData(dataId
);
375 public void toggleShowTextField() {
376 ourToShowTextField
=!ourToShowTextField
;
377 updateTextFieldShowing();
380 private void updateTextFieldShowing() {
381 myTextFieldAction
.update();
382 myNorthPanel
.remove(myPathTextFieldWrapper
);
383 if (ourToShowTextField
) {
384 if (myFileSystemTree
.getSelectedFile() != null) {
385 final ArrayList
<VirtualFile
> selection
= new ArrayList
<VirtualFile
>();
386 selection
.add(myFileSystemTree
.getSelectedFile());
387 updatePathFromTree(selection
, true);
389 myNorthPanel
.add(myPathTextFieldWrapper
, BorderLayout
.CENTER
);
393 myPathTextField
.getField().requestFocus();
395 myNorthPanel
.revalidate();
396 myNorthPanel
.repaint();
400 private class TextFieldAction
extends LinkLabel
implements LinkListener
{
401 public TextFieldAction() {
403 setListener(this, null);
407 protected void onSetActive(final boolean active
) {
408 final String tooltip
= AnAction
409 .createTooltipText(ActionsBundle
.message("action.FileChooser.TogglePathShowing.text"), ActionManager
.getInstance().getAction("FileChooser.TogglePathShowing"));
410 setToolTipText(tooltip
);
413 protected String
getStatusBarText() {
414 return ActionsBundle
.message("action.FileChooser.TogglePathShowing.text");
417 public void update() {
419 setText(ourToShowTextField ? IdeBundle
.message("file.chooser.hide.path") : IdeBundle
.message("file.chooser.show.path"));
422 public void linkSelected(final LinkLabel aSource
, final Object aLinkData
) {
423 toggleShowTextField();
427 private void updatePathFromTree(final List
<VirtualFile
> selection
, boolean now
) {
428 if (!ourToShowTextField
|| myTreeIsUpdating
) return;
431 if (selection
.size() > 0) {
432 final VirtualFile vFile
= selection
.get(0);
433 if (vFile
.isInLocalFileSystem()) {
434 text
= vFile
.getPresentableUrl();
436 text
= vFile
.getUrl();
440 myPathTextField
.setText(text
, now
, new Runnable() {
442 myPathTextField
.getField().selectAll();
448 private void updateTreeFromPath(final String text
) {
449 if (!ourToShowTextField
) return;
450 if (myPathTextField
.isPathUpdating()) return;
451 if (text
== null) return;
453 myUiUpdater
.queue(new Update("treeFromPath.1") {
455 ApplicationManager
.getApplication().executeOnPooledThread(new Runnable() {
457 final LocalFsFinder
.VfsFile toFind
= (LocalFsFinder
.VfsFile
)myPathTextField
.getFile();
458 if (toFind
== null || !toFind
.exists()) return;
460 myUiUpdater
.queue(new Update("treeFromPath.2") {
462 selectInTree(toFind
.getFile(), text
);
471 private void selectInTree(final VirtualFile vFile
, String fromText
) {
472 if (vFile
!= null && vFile
.isValid()) {
473 if (fromText
== null || fromText
.equalsIgnoreCase(myPathTextField
.getTextFieldText())) {
474 selectInTree(new VirtualFile
[] {vFile
}, false);
477 reportFileNotFound();
481 private void selectInTree(final VirtualFile
[] vFile
, final boolean requestFocus
) {
482 myTreeIsUpdating
= true;
483 if (!Arrays
.asList(myFileSystemTree
.getSelectedFiles()).contains(vFile
)) {
484 myFileSystemTree
.select(vFile
, new Runnable() {
486 myTreeIsUpdating
= false;
489 SwingUtilities
.invokeLater(new Runnable() {
491 myFileSystemTree
.getTree().requestFocus();
498 myTreeIsUpdating
= false;
503 private void reportFileNotFound() {
504 myTreeIsUpdating
= false;