I can get my JNI class to only partially execute due to my unfamiliarity with Objective-C and JNF. What I've tried so far is as follows:
Here's Open.java, which an MCV for a more significant Java Swing app that deserves a better-looking better-featured open file dialog than unreasonable java.awt.FileDialog
or the ugly and less-featured javax.swing.JFileChooser
.
import java.awt.event.*;
import javax.swing.*;
public class Open extends JFrame {
public Open () {
getContentPane().add(new JButton(new AbstractAction() {
public void actionPerformed (ActionEvent e) {
try {
NativeOpenFileDialog.run();
} catch (Throwable ex) {
System.err.println(ex.getMessage());
}
}
}));
pack();
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() { new Open(); }
});
}
}
Here's NativeOpenFileDialog.java
import javax.swing.*;
public class NativeOpenFileDialog {
static {
System.loadLibrary("natopndlg");
};
public static native void run ();
public static void main (String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() { NativeOpenFileDialog.run(); }
});
}
}
Here's NativeOpenFileDialog.m
#import <Cocoa/Cocoa.h>
#include <jni.h>
#include "NativeOpenFileDialog.h"
JNIEXPORT void JNICALL Java_NativeOpenFileDialog_run (JNIEnv *thisEnv, jclass jcls) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSOpenPanel *panel = [NSOpenPanel openPanel];
[panel setCanChooseFiles:YES];
[panel setCanChooseDirectories:YES];
[panel setAllowsMultipleSelection:YES];
if ([panel runModal] != NSModalResponseOK)
NSLog(@"User did not press OK");
[pool release];
}
And here's the makefile (I'm using Xcode 15.2):
SDK = /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
Open.class: Open.java NativeOpenFileDialog.java NativeOpenFileDialog.m
javac -h . NativeOpenFileDialog.java
javac Open.java
gcc -dynamiclib -o libnatopndlg.dylib -framework Cocoa -isysroot $(SDK) -I $(JAVA_HOME)/include -I $(JAVA_HOME)/include/darwin -fobjc-exceptions -std=c99 NativeOpenFileDialog.m
And here's how I execute it after executing make
.
java Open
Clicking the button raises the exception:
NSInternalInconsistencyException', reason: 'NSWindow should only be instantiated on the main thread!
Which is understandable knowing that the panel is supposed to be opened on the UI thread. I believe all I have to do is make sure the window created by NSOpenPanel is a child of the JFrame and opened on the UI thread. And I believe that at leasat means I would have to execute a Java function to get the NSWindow of the JFrame.
You can execute things on the UI thread using dispatch_async() and dispatch_sync(). NSObject also includes a method performSelectorOnMainThread:withObject:waitUntilDone:
that will perform a selector on the main thread.
This is a gist using the Java objective-c bridge that does something similar to this:
https://gist.github.com/anonymous/3966989
For your specific problem you could wrap the method body in dispatch_async
JNIEXPORT void JNICALL Java_NativeOpenFileDialog_run (JNIEnv *thisEnv, jclass jcls) {
dispatch_async(dispatch_get_main_queue(), ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSOpenPanel *panel = [NSOpenPanel openPanel];
[panel setCanChooseFiles:YES];
[panel setCanChooseDirectories:YES];
[panel setAllowsMultipleSelection:YES];
if ([panel runModal] != NSModalResponseOK)
NSLog(@"User did not press OK");
[pool release];
});
}
Of course, this just opens the dialog and doesn't do anything with the file that the user selects, so you'll need to build on this.