mac native filechooser as sheet added
authorAlexey Pegov <alexey.pegov@jetbrains.com>
Fri, 23 Apr 2010 14:49:42 +0000 (18:49 +0400)
committerAlexey Pegov <alexey.pegov@jetbrains.com>
Fri, 23 Apr 2010 14:49:42 +0000 (18:49 +0400)
platform/platform-api/src/com/intellij/openapi/fileChooser/MacFileChooserDialog.java [new file with mode: 0644]
platform/platform-impl/src/com/intellij/openapi/fileChooser/impl/FileChooserFactoryImpl.java
platform/platform-impl/src/com/intellij/ui/mac/MacFileChooserDialogImpl.java [moved from platform/platform-impl/src/com/intellij/ui/mac/MacFileChooserDialog.java with 55% similarity]

diff --git a/platform/platform-api/src/com/intellij/openapi/fileChooser/MacFileChooserDialog.java b/platform/platform-api/src/com/intellij/openapi/fileChooser/MacFileChooserDialog.java
new file mode 100644 (file)
index 0000000..cef481c
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2000-2010 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.openapi.fileChooser;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * User: spLeaner
+ */
+public interface MacFileChooserDialog extends FileChooserDialog {
+
+  void chooseWithSheet(@Nullable VirtualFile toSelect, @Nullable Project project, @NotNull final MacFileChooserCallback callback);
+
+  interface MacFileChooserCallback {
+    void onChosen(@NotNull VirtualFile[] files);
+  }
+
+}
index b0cef140f4d4cee586337c610356580693beb3d5..3869a20b37bbe108f177ab9e4557678b998cdb33 100644 (file)
@@ -25,7 +25,7 @@ import com.intellij.openapi.fileChooser.ex.FileTextFieldImpl;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.SystemInfo;
 import com.intellij.openapi.util.registry.Registry;
-import com.intellij.ui.mac.MacFileChooserDialog;
+import com.intellij.ui.mac.MacFileChooserDialogImpl;
 
 import javax.swing.*;
 import java.awt.*;
@@ -37,7 +37,7 @@ public class FileChooserFactoryImpl extends FileChooserFactory {
   public FileChooserDialog createFileChooser(FileChooserDescriptor descriptor, Project project) {
     if (SystemInfo.isMac && (System.getProperty("idea.use.native.mac.filechooser", Boolean.FALSE.toString()).equals(Boolean.TRUE.toString())
       || Registry.is("ide.use.native.mac.filechooser"))) {
-      return new MacFileChooserDialog(descriptor, project);
+      return new MacFileChooserDialogImpl(descriptor, project);
     }
 
     return new FileChooserDialogImpl(descriptor, project);
@@ -46,7 +46,7 @@ public class FileChooserFactoryImpl extends FileChooserFactory {
   public FileChooserDialog createFileChooser(FileChooserDescriptor descriptor, Component parent) {
     if (SystemInfo.isMac && (System.getProperty("idea.use.native.mac.filechooser", Boolean.FALSE.toString()).equals(Boolean.TRUE.toString())
     || Registry.is("ide.use.native.mac.filechooser"))) {
-      return new MacFileChooserDialog(descriptor, parent);
+      return new MacFileChooserDialogImpl(descriptor, parent);
     }
 
     return new FileChooserDialogImpl(descriptor, parent);
similarity index 55%
rename from platform/platform-impl/src/com/intellij/ui/mac/MacFileChooserDialog.java
rename to platform/platform-impl/src/com/intellij/ui/mac/MacFileChooserDialogImpl.java
index 5e06e89d9bd962eb49ac4f1753ccc5e4b1fa147a..9b952251d6fcc472062d24d59ed85dbb74b35c3d 100644 (file)
 package com.intellij.ui.mac;
 
 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
-import com.intellij.openapi.fileChooser.FileChooserDialog;
+import com.intellij.openapi.fileChooser.MacFileChooserDialog;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.openapi.wm.WindowManager;
 import com.intellij.ui.mac.foundation.Foundation;
 import com.intellij.ui.mac.foundation.ID;
 import com.sun.jna.Callback;
@@ -36,7 +35,7 @@ import java.util.List;
 /**
  * @author spleaner
  */
-public class MacFileChooserDialog implements FileChooserDialog {
+public class MacFileChooserDialogImpl implements MacFileChooserDialog {
   private static final int OK = 1;
 
   private static JDialog myFakeDialog;
@@ -45,6 +44,8 @@ public class MacFileChooserDialog implements FileChooserDialog {
   private static FileChooserDescriptor myChooserDescriptor;
   private static boolean myFileChooserActive = false;
 
+  private static MacFileChooserCallback mySheetCallback = null;
+
   private Project myProject;
 
   private static final Callback SHOULD_SHOW_FILENAME_CALLBACK = new Callback() {
@@ -63,6 +64,30 @@ public class MacFileChooserDialog implements FileChooserDialog {
       }
     };
 
+  private static final Callback OPEN_PANEL_DID_END = new Callback() {
+      public void callback(ID self, String selector, ID openPanelDidEnd, ID returnCode, ID contextInfo) {
+        processResult(returnCode, openPanelDidEnd);
+
+        try {
+          if (myResultFiles != null) {
+            final VirtualFile[] chosenFiles = myResultFiles.toArray(new VirtualFile[myResultFiles.size()]);
+            final MacFileChooserCallback callback = mySheetCallback;
+            SwingUtilities.invokeLater(new Runnable() {
+              public void run() {
+                callback.onChosen(chosenFiles);
+              }
+            });
+          }
+        }
+        finally {
+          myFileChooserActive = false;
+          myResultFiles = null;
+          mySheetCallback = null;
+        }
+      }
+    };
+
+
   private static final Callback MAIN_THREAD_RUNNABLE = new Callback() {
     public void callback(ID self, String selector, ID toSelect) {
       final ID chooser = invoke("NSOpenPanel", "openPanel");
@@ -81,55 +106,88 @@ public class MacFileChooserDialog implements FileChooserDialog {
       final VirtualFile toSelectFile = toSelectPath == null ? null : LocalFileSystem.getInstance().findFileByPath(toSelectPath);
       final ID directory = toSelectFile == null ? null : toSelectFile.isDirectory() ? toSelect : null;
       final ID file = toSelectFile == null ? null : !toSelectFile.isDirectory() ? toSelect : null;
-      final ID result = invoke(chooser, "runModalForDirectory:file:", directory, file);
 
-      SwingUtilities.invokeLater(new Runnable() {
-        public void run() {
-          if (myFakeDialog != null) {
-            myFakeDialog.dispose();
-            myFakeDialog = null;
-          }
-        }
-      });
+      if (mySheetCallback != null) {
+        final Window activeWindow = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
+        if (activeWindow != null && activeWindow instanceof Frame) {
+          final String frameTitle = ((Frame)activeWindow).getTitle();
 
-      final List<VirtualFile> resultFiles = new ArrayList<VirtualFile>();
-      if (result != null && OK == result.intValue()) {
-        ID fileNamesArray = invoke(chooser, "filenames");
-        ID enumerator = invoke(fileNamesArray, "objectEnumerator");
+          final ID sharedApplication = invoke("NSApplication", "sharedApplication");
+          final ID windows = invoke(sharedApplication, "windows");
+          final ID windowEnumerator = invoke(windows, "objectEnumerator");
 
-        while (true) {
-          final ID filename = invoke(enumerator, "nextObject");
-          if (0 == filename.intValue()) break;
+          ID focusedWindow = null;
+          while (true) {
+            // dirty hack: walks through all the windows to find a cocoa window to show sheet for
+            final ID window = invoke(windowEnumerator, "nextObject");
+            if (0 == window.intValue()) break;
 
-          String s = Foundation.toStringViaUTF8(filename);
-          if (s != null) {
-            VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(s);
-            if (virtualFile != null && virtualFile.isValid()) resultFiles.add(virtualFile);
+            final ID windowTitle = invoke(window, "title");
+            final String titleString = Foundation.toStringViaUTF8(windowTitle);
+            if (titleString.equals(frameTitle)) focusedWindow = window;
           }
-        }
 
-        myResultFiles = resultFiles;
+          if (focusedWindow != null) {
+            invoke(chooser, "beginSheetForDirectory:file:types:modalForWindow:modalDelegate:didEndSelector:contextInfo:",
+                   directory, file, null, focusedWindow, self, Foundation.createSelector("openPanelDidEnd:returnCode:contextInfo:"), null);
+          }
+        }
+      } else {
+        final ID result = invoke(chooser, "runModalForDirectory:file:", directory, file);
+        processResult(result, chooser);
       }
     }
   };
 
+  private static void processResult(final ID result, final ID panel) {
+    SwingUtilities.invokeLater(new Runnable() {
+      public void run() {
+        if (myFakeDialog != null) {
+          myFakeDialog.dispose();
+          myFakeDialog = null;
+        }
+      }
+    });
+
+    final List<VirtualFile> resultFiles = new ArrayList<VirtualFile>();
+    if (result != null && OK == result.intValue()) {
+      ID fileNamesArray = invoke(panel, "filenames");
+      ID enumerator = invoke(fileNamesArray, "objectEnumerator");
+
+      while (true) {
+        final ID filename = invoke(enumerator, "nextObject");
+        if (0 == filename.intValue()) break;
+
+        String s = Foundation.toStringViaUTF8(filename);
+        if (s != null) {
+          VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(s);
+          if (virtualFile != null && virtualFile.isValid()) resultFiles.add(virtualFile);
+        }
+      }
+
+      myResultFiles = resultFiles;
+    }
+  }
+
   static {
     final ID delegateClass = Foundation.registerObjcClass(Foundation.getClass("NSObject"), "NSOpenPanelDelegate_");
-    if (!Foundation.addMethod(delegateClass, Foundation.createSelector("panel:shouldShowFilename:"), SHOULD_SHOW_FILENAME_CALLBACK, "B@:*"))
+    if (!Foundation.addMethod(delegateClass, Foundation.createSelector("panel:shouldShowFilename:"), SHOULD_SHOW_FILENAME_CALLBACK, "B*"))
       throw new RuntimeException("Unable to add method to objective-c delegate class!");
-    if (!Foundation.addMethod(delegateClass, Foundation.createSelector("panel:isValidFilename:"), IS_VALID_FILENAME_CALLBACK, "B@:*"))
+    if (!Foundation.addMethod(delegateClass, Foundation.createSelector("panel:isValidFilename:"), IS_VALID_FILENAME_CALLBACK, "B*"))
       throw new RuntimeException("Unable to add method to objective-c delegate class!");
-    if (!Foundation.addMethod(delegateClass, Foundation.createSelector("showOpenPanel:"), MAIN_THREAD_RUNNABLE, "v@:*"))
+    if (!Foundation.addMethod(delegateClass, Foundation.createSelector("showOpenPanel:"), MAIN_THREAD_RUNNABLE, "v*"))
+      throw new RuntimeException("Unable to add method to objective-c delegate class!");
+    if (!Foundation.addMethod(delegateClass, Foundation.createSelector("openPanelDidEnd:returnCode:contextInfo:"), OPEN_PANEL_DID_END, "v*i^void"))
       throw new RuntimeException("Unable to add method to objective-c delegate class!");
     Foundation.registerObjcClassPair(delegateClass);
   }
 
-  public MacFileChooserDialog(final FileChooserDescriptor chooserDescriptor, final Project project) {
+  public MacFileChooserDialogImpl(final FileChooserDescriptor chooserDescriptor, final Project project) {
     setDescriptor(chooserDescriptor);
     myProject = project;
   }
 
-  public MacFileChooserDialog(final FileChooserDescriptor chooserDescriptor, final Component component) {
+  public MacFileChooserDialogImpl(final FileChooserDescriptor chooserDescriptor, final Component component) {
     setDescriptor(chooserDescriptor);
   }
 
@@ -157,28 +215,53 @@ public class MacFileChooserDialog implements FileChooserDialog {
     }
   }
 
+  private static void showNativeChooserAsSheet(@Nullable VirtualFile toSelect, @NotNull final MacFileChooserCallback callback) {
+    final ID autoReleasePool = createAutoReleasePool();
+
+    try {
+      final ID delegate = invoke(Foundation.getClass("NSOpenPanelDelegate_"), "new");
+
+      final Pointer select = toSelect == null ? null : Foundation.cfString(toSelect.getPath());
+      Foundation.cfRetain(delegate);
+
+      invoke(delegate, "performSelectorOnMainThread:withObject:waitUntilDone:", Foundation.createSelector("showOpenPanel:"), select, false);
+    }
+    finally {
+      invoke(autoReleasePool, "release");
+    }
+  }
+
+  public void chooseWithSheet(@Nullable final VirtualFile toSelect, @Nullable final Project project,
+                                       @NotNull final MacFileChooserCallback callback) {
+    assert !myFileChooserActive: "Current native file chooser should finish before next usage!";
+    mySheetCallback = callback;
+
+    SwingUtilities.invokeLater(new Runnable() {
+      public void run() {
+        showNativeChooserAsSheet(getToSelect(toSelect, project), callback);
+      }
+    });
+  }
+
   @NotNull
-  public VirtualFile[] choose(@Nullable final VirtualFile toSelect, @Nullable Project project) {
+  public VirtualFile[] choose(@Nullable final VirtualFile toSelect, @Nullable final Project project) {
     assert !myFileChooserActive: "Current native file chooser should finish before next usage!";
 
     myFileChooserActive = true;
 
-    final VirtualFile[] selectFile = new VirtualFile[] {null};
-    if (toSelect == null) {
-      if (project != null && project.getBaseDir() != null) {
-        selectFile[0] = project.getBaseDir();
-      }
-    } else {
-      selectFile[0] = toSelect.isValid() ? toSelect : null;
-    }
-
     SwingUtilities.invokeLater(new Runnable() {
       public void run() {
-        showNativeChooser(selectFile[0]);
+        showNativeChooser(getToSelect(toSelect, project));
       }
     });
 
-    myFakeDialog = new JDialog(project != null ? WindowManager.getInstance().getFrame(project) : myProject != null ? WindowManager.getInstance().getFrame(myProject) : null);
+    Frame parentFrame = null;
+    final Window parent = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
+    if (parent instanceof Frame) {
+      parentFrame = (Frame) parent;
+    }
+
+    myFakeDialog = new JDialog(parentFrame);
     myFakeDialog.setModal(true);
     myFakeDialog.setUndecorated(true);
     myFakeDialog.getRootPane().putClientProperty( "Window.shadow", Boolean.FALSE );
@@ -199,6 +282,18 @@ public class MacFileChooserDialog implements FileChooserDialog {
     }
   }
 
+  private static VirtualFile getToSelect(VirtualFile toSelect, Project project) {
+    final VirtualFile[] selectFile = new VirtualFile[] {null};
+    if (toSelect == null) {
+      if (project != null && project.getBaseDir() != null) {
+        selectFile[0] = project.getBaseDir();
+      }
+    } else {
+      selectFile[0] = toSelect.isValid() ? toSelect : null;
+    }
+    return selectFile[0];
+  }
+
   private static ID createAutoReleasePool() {
     return invoke("NSAutoreleasePool", "new");
   }