5e06e89d9bd962eb49ac4f1753ccc5e4b1fa147a
[idea/community.git] / platform / platform-impl / src / com / intellij / ui / mac / MacFileChooserDialog.java
1 /*
2  * Copyright 2000-2010 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.ui.mac;
17
18 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
19 import com.intellij.openapi.fileChooser.FileChooserDialog;
20 import com.intellij.openapi.project.Project;
21 import com.intellij.openapi.vfs.LocalFileSystem;
22 import com.intellij.openapi.vfs.VirtualFile;
23 import com.intellij.openapi.wm.WindowManager;
24 import com.intellij.ui.mac.foundation.Foundation;
25 import com.intellij.ui.mac.foundation.ID;
26 import com.sun.jna.Callback;
27 import com.sun.jna.Pointer;
28 import org.jetbrains.annotations.NotNull;
29 import org.jetbrains.annotations.Nullable;
30
31 import javax.swing.*;
32 import java.awt.*;
33 import java.util.ArrayList;
34 import java.util.List;
35
36 /**
37  * @author spleaner
38  */
39 public class MacFileChooserDialog implements FileChooserDialog {
40   private static final int OK = 1;
41
42   private static JDialog myFakeDialog;
43   private static List<VirtualFile> myResultFiles;
44
45   private static FileChooserDescriptor myChooserDescriptor;
46   private static boolean myFileChooserActive = false;
47
48   private Project myProject;
49
50   private static final Callback SHOULD_SHOW_FILENAME_CALLBACK = new Callback() {
51       public boolean callback(ID self, String selector, ID panel, ID filename) {
52         final String fileName = Foundation.toStringViaUTF8(filename);
53         final VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(fileName);
54         return virtualFile != null && (virtualFile.isDirectory() || getDescriptor().isFileSelectable(virtualFile));
55       }
56     };
57
58   private static final Callback IS_VALID_FILENAME_CALLBACK = new Callback() {
59       public boolean callback(ID self, String selector, ID panel, ID filename) {
60         final String fileName = Foundation.toStringViaUTF8(filename);
61         final VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(fileName);
62         return virtualFile != null && (!virtualFile.isDirectory() || getDescriptor().isFileSelectable(virtualFile));
63       }
64     };
65
66   private static final Callback MAIN_THREAD_RUNNABLE = new Callback() {
67     public void callback(ID self, String selector, ID toSelect) {
68       final ID chooser = invoke("NSOpenPanel", "openPanel");
69
70       invoke(chooser, "setPrompt:", Foundation.cfString("Choose"));
71       invoke(chooser, "setCanChooseFiles:", myChooserDescriptor.isChooseFiles());
72       invoke(chooser, "setCanChooseDirectories:", myChooserDescriptor.isChooseFolders());
73       invoke(chooser, "setAllowsMultipleSelection:", myChooserDescriptor.isChooseMultiple());
74       if (Foundation.isClassRespondsToSelector(Foundation.getClass("NSOpenPanel"), Foundation.createSelector("_setIncludeNewFolderButton:"))) {
75         invoke(chooser, "_setIncludeNewFolderButton:", true);
76       }
77
78       invoke(chooser, "setDelegate:", self);
79
80       final String toSelectPath = toSelect.intValue() == 0 ? null : Foundation.toStringViaUTF8(toSelect);
81       final VirtualFile toSelectFile = toSelectPath == null ? null : LocalFileSystem.getInstance().findFileByPath(toSelectPath);
82       final ID directory = toSelectFile == null ? null : toSelectFile.isDirectory() ? toSelect : null;
83       final ID file = toSelectFile == null ? null : !toSelectFile.isDirectory() ? toSelect : null;
84       final ID result = invoke(chooser, "runModalForDirectory:file:", directory, file);
85
86       SwingUtilities.invokeLater(new Runnable() {
87         public void run() {
88           if (myFakeDialog != null) {
89             myFakeDialog.dispose();
90             myFakeDialog = null;
91           }
92         }
93       });
94
95       final List<VirtualFile> resultFiles = new ArrayList<VirtualFile>();
96       if (result != null && OK == result.intValue()) {
97         ID fileNamesArray = invoke(chooser, "filenames");
98         ID enumerator = invoke(fileNamesArray, "objectEnumerator");
99
100         while (true) {
101           final ID filename = invoke(enumerator, "nextObject");
102           if (0 == filename.intValue()) break;
103
104           String s = Foundation.toStringViaUTF8(filename);
105           if (s != null) {
106             VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(s);
107             if (virtualFile != null && virtualFile.isValid()) resultFiles.add(virtualFile);
108           }
109         }
110
111         myResultFiles = resultFiles;
112       }
113     }
114   };
115
116   static {
117     final ID delegateClass = Foundation.registerObjcClass(Foundation.getClass("NSObject"), "NSOpenPanelDelegate_");
118     if (!Foundation.addMethod(delegateClass, Foundation.createSelector("panel:shouldShowFilename:"), SHOULD_SHOW_FILENAME_CALLBACK, "B@:*"))
119       throw new RuntimeException("Unable to add method to objective-c delegate class!");
120     if (!Foundation.addMethod(delegateClass, Foundation.createSelector("panel:isValidFilename:"), IS_VALID_FILENAME_CALLBACK, "B@:*"))
121       throw new RuntimeException("Unable to add method to objective-c delegate class!");
122     if (!Foundation.addMethod(delegateClass, Foundation.createSelector("showOpenPanel:"), MAIN_THREAD_RUNNABLE, "v@:*"))
123       throw new RuntimeException("Unable to add method to objective-c delegate class!");
124     Foundation.registerObjcClassPair(delegateClass);
125   }
126
127   public MacFileChooserDialog(final FileChooserDescriptor chooserDescriptor, final Project project) {
128     setDescriptor(chooserDescriptor);
129     myProject = project;
130   }
131
132   public MacFileChooserDialog(final FileChooserDescriptor chooserDescriptor, final Component component) {
133     setDescriptor(chooserDescriptor);
134   }
135
136   private static void setDescriptor(@Nullable final FileChooserDescriptor descriptor) {
137     myChooserDescriptor = descriptor;
138   }
139
140   private static FileChooserDescriptor getDescriptor() {
141     return myChooserDescriptor;
142   }
143
144   private static void showNativeChooser(@Nullable VirtualFile toSelect) {
145     final ID autoReleasePool = createAutoReleasePool();
146
147     try {
148       final ID delegate = invoke(Foundation.getClass("NSOpenPanelDelegate_"), "new");
149
150       final Pointer select = toSelect == null ? null : Foundation.cfString(toSelect.getPath());
151       Foundation.cfRetain(delegate);
152
153       invoke(delegate, "performSelectorOnMainThread:withObject:waitUntilDone:", Foundation.createSelector("showOpenPanel:"), select, false);
154     }
155     finally {
156       invoke(autoReleasePool, "release");
157     }
158   }
159
160   @NotNull
161   public VirtualFile[] choose(@Nullable final VirtualFile toSelect, @Nullable Project project) {
162     assert !myFileChooserActive: "Current native file chooser should finish before next usage!";
163
164     myFileChooserActive = true;
165
166     final VirtualFile[] selectFile = new VirtualFile[] {null};
167     if (toSelect == null) {
168       if (project != null && project.getBaseDir() != null) {
169         selectFile[0] = project.getBaseDir();
170       }
171     } else {
172       selectFile[0] = toSelect.isValid() ? toSelect : null;
173     }
174
175     SwingUtilities.invokeLater(new Runnable() {
176       public void run() {
177         showNativeChooser(selectFile[0]);
178       }
179     });
180
181     myFakeDialog = new JDialog(project != null ? WindowManager.getInstance().getFrame(project) : myProject != null ? WindowManager.getInstance().getFrame(myProject) : null);
182     myFakeDialog.setModal(true);
183     myFakeDialog.setUndecorated(true);
184     myFakeDialog.getRootPane().putClientProperty( "Window.shadow", Boolean.FALSE );
185
186     myFakeDialog.setBounds(0, 0, 0, 0);
187     myFakeDialog.setVisible(true);
188
189     try {
190       if (myResultFiles == null) {
191         return new VirtualFile[0];
192       } else {
193         return myResultFiles.toArray(new VirtualFile[myResultFiles.size()]);
194       }
195     }
196     finally {
197       myFileChooserActive = false;
198       myResultFiles = null;
199     }
200   }
201
202   private static ID createAutoReleasePool() {
203     return invoke("NSAutoreleasePool", "new");
204   }
205
206   private static ID invoke(@NotNull final String className, @NotNull final String selector, Object... args) {
207     return invoke(Foundation.getClass(className), selector, args);
208   }
209
210   private static ID invoke(@NotNull final ID id, @NotNull final String selector, Object... args) {
211     return Foundation.invoke(id, Foundation.createSelector(selector), args);
212   }
213 }