Becoming a Jedi, err...JNI Master!

I myself am far from being a JNI master, since I only started doing my first JNI a few days ago. The problem we were having is that there is no way in Java to get some native behavior on OS X. For example, the closest to native you can get with Open/Save file dialogs is what you get from java.awt.FileDialog, which isn't very much.

Since we're developing in Swing it is key that if we do anything, it should be wrapped up in a neat little package that mimics existing Swing components. Not only that, but we want the behavior of any wrapper classes to be the exact same, or very similar to, the corresponding JComponent. Although it's far from being ready for public release, I currently have a pretty solid JNI library for the Open/Save dialogs in OS X. It's all wrapped up neatly in CarbonFileChooser, an extension to JFileChooser.

One issue that arose, at least for OS X, is that there are various threads that have to run independently: the AWT/Swing thread, and the AppKit thread. Hence, when one makes a native call that is going to be doing some GUI stuff or event-oriented callbacks to the AWT/Swing thread, one has to forward things along to the AppKit thread. This can be done via the performSelectorOnMainThread method. To achieve modality, I use the following code:

public int showSaveDialog(Component parent) {
dialogOpen = true;
result = CANCEL_OPTION;

cc_showSaveDialog(parent);
while(dialogOpen) {
try {
Thread.sleep(100);
} catch(InterruptedException e) { }
}

return result;
}

What happens is that when the dialog is disposed, JNI calls are executed to set the dialogOpen variable to false, breaking the loop. I've been debating trying out wait() and using JNI to wake up the object instead, but for now the above code gets the job done.

Eventually I'm going to release this code library to the public so that anyone can use it, and eventually I'll open-source it (when I'm too lazy to keep maintaining it). The beauty of this, if one is using Swing, is that you really don't change your code at all. Just create the CarbonFileChooser class and you're good to go. Right now things are simple, so just some basic things can be done. You can do the following:
  • Set the initial directory
  • Add choosable filters, set the initial file filter, and get the chosen file filter once the dialog is disposed
  • Get the selected file to open/save
And some things left to do:
  • Multiple file selection and retrieval
  • Show no filters if the "All Files" filter is the only one
  • Setting the initial file name for save dialogs
  • Update dialog to unselect files, as necessary, when filter changes

Java: not always so cross-platform

There's no doubting the fact that Java really makes one's life far easier in general when creating a piece of software that works on various platforms. But even with such power, there's always little things that really can make one's life a pain when creating a piece of software that is intended to be high quality.

One of the major sources of pain in Java is cross-platform GUIs. It's nice that Swing works "out-of-the-box" on most systems, but it often suffers from lacking in a "native" feel, even when using the system LaF. Also, it's sometimes hard to take advantage of platform-specific features that could really open up a world of possibilities for your app.

For our own project we decided to create plugins for various platforms that need more than the standard library offers. I'll talk about our plugin system in a future post. When we distribute platform-specific packages (e.g., a Mac OS X application bundle) we will include the appropriate plugin to extend the functionality of the app for that platform. We prefer this method over checking system properties so that a release never "breaks" when one of these properties magically changes. Anyways, some functionality we require includes:
  • Querying what platform we're on. The application does not suffer when the platform is unknown, but we can provide specific features and an enhanced UI based on a known platform.
  • Directory services. We want to know where the user stores their documents, where is their desktop, etc.
  • Platform-specific GUI components.
So nothing too interesting to talk about in the first 2, but for the last one I can provide a little snippet of our default behaviour:

public T getSwingComponent(Class componentClass, Object... args) {
//
Class[] classes = new Class[args.length];
for(int i = 0; i < args.length; ++i)
classes[i] = args[i].getClass();

//
try {
return componentClass.getConstructor(classes).newInstance(args);
} catch (SecurityException e) {
} catch (NoSuchMethodException e) {
} catch (IllegalArgumentException e) {
} catch (InstantiationException e) {
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
}
return null;
}
So if we want a JToolBar we simply go getSwingComponent(JToolBar.class) and that's it. The implementation provides variable arguments behaviour to elegantly allow usage of all valid constructors. The only thing we lose here is compile-time error checking for incorrect constructor arguments, but this is not too big of an issue for us at the moment. Our platform-specific plugins can override this method to check for various Swing classes, and return an alternate instance of that class if necessary. For example, our OS X plugin returns a simple extension to JMenuBar that removes all icons from any menus added to it (to adhere to the Apple Human Interface guidelines). Another component I plan to work on for OS X a native file dialog (I don't like java.awt.FileDialog). This will probably have me diving into some JNI, which is good because I would like to play around with it. If I can get something decent working there, I'll definitely look into releasing the JNI code publicly.

A really simple technique but it really helps enhance our application and give an improved native feel over what Swing offers.

Beginnings

So I'm going to start my blogging off with an introduction to my project: a free cross platform musical score editor (which currently has no name). Our team consists of just myself and a classmate from my undergrad. We previously worked on a team project during our undergrad (a required course), so we were already familiar with team development.

For me, projects come in two flavors:
  1. those I do simply for my own personal enjoyment, such as a game and,
  2. those that fill a need for me, such as small scripts to get repetitive tasks done.
The musical score editor falls into category 2, but it is definitely an enjoyable project too. I personally found myself unhappy with existing free software for editing scores. Since I focus on guitar, I was looking at something with a simple interface to whip up a guitar tab and be on my way. Probably the best I could find is TuxGuitar, but it was far from a pleasurable experience. This established a need for me, one whose solution we will eventually share with others. So with a project idea, the next thing was to lay out some basic requirements:
  • Cross-platform. I am an OS X user, and my friend is a Linux user.
  • An interface that is both simple for the first-time user, but powerful for the more advanced users.
  • Quick keyboard access to the most common commands to greatly improve throughput.
  • Fully-featured. We want users to be able to do just about anything and everything they'd want to do with their musical scores. Clearly this will take time, but it is our goal.
With these requirements in mind, we decided that Java would make our lives far simpler. We chose Swing over SWT for our GUI library, since we both know and enjoy Swing. Our goal is to eventually bring this project to a level comparable to that of commercial software. It's a big goal, but we're extremely motivated and really enjoy this project. Anyways, some things I plan to blog about in the near future:
  • Java: not always that cross-platform. Various topics on producing code and user-interfaces that feel more native.
  • Working with JNI.
  • Developing a flexible and easy-to-use plugin system.
  • Object-based rendering systems: the pros and the cons.
  • Other cool stuff!
We already have a highly functional and [mostly] stable version of our score editor internally, but we want our first public release to really be something amazing. We have many incredibly powerful features planned, some of which we have never seen before in the area of score editing. Hence, if you're reading this entry you should stay tuned for some good stuff! I'd post a teaser screenshot, but everyone likes a bit of suspense :)