The Cross-Platform Java GUI: Designing Code for More Than One Operating System
One of the most accurate complaints users have against Java GUIs is that they do not feel native. While this is partially due to the way that Swing is designed, it is also partially the fault of the developer. It is quite common for a developer to design the GUI to work on his or her platform of choice and then rely solely on Java’s cross-platform nature to make the application available for other operating systems. This behavior naturally leads to users on the other platforms complaining about the lack of a native feel for the application.
Installation
First impressions are vital, and the installation procedure for your application is your only first impression. If the user has to go through an arcane ritual to get your software installed, it already has that "non-native" feel to it. But what is considered an acceptable installation procedure? Unfortunately, that procedure varies completely on each system. Here are a few examples.
Windows
Windows users expect an installer. Simple as that. They expect an application that puts all the little bits in the right places when they run it and places an icon where they want it. They expect the installer to ask them where they want the icons and to only put them there. They also expect this installer to put the right information into the registry so that if they want to uninstall the application it will properly remove all its bits and pieces.
Fortunately, there are several very good Windows installers available on the market today. Some of them are even free or available at a small cost. If Windows is a primary target for your application, it is worth it to not only invest in a good installer but also to make sure you understand it fully so that there are no mishaps. A failed uninstall on Windows is almost as bad as a failed install. An improper uninstall tends to leave a bad aftertaste and might keep the user from selecting any other software from the same developer.
OS X
Arguably OS X has the easiest install available. Users expect to just drag the application to wherever they want it on their computer and just double-click the icon. Anything more than that feels wrong and overly complicated to the user. OS X expects an application to be completely self-contained inside an application directory structure. From the user’s point of view, the application will be just a single icon because OS X automatically treats the folder structure as if it were a single file when it has an .app extension. Unfortunately, there is more to creating a properly built OS X application than just putting everything in the right directory. Assuming that you have access to an OS X machine on which to build your application, there is a simple ant build script that will produce the proper structure for the application bundle:
<target name="dist.mac" depends="all"> <mkdir dir="${dist}/${app}"/> <property name="appdir" value="${dist}/${app}/${app}.app"/> <mkdir dir="${appdir}/Contents/Resources/Java"/> <mkdir dir="${appdir}/Contents/Resources/logs"/> <copy file="${packaging}/JavaApplicationStub" todir="${appdir}/Contents/MacOS"/> <exec command="chmod 755 ${appdir}/Contents/MacOS/JavaApplicationStub"/> <copy file="config/log4j.properties" todir="${appdir}/Contents/Resources"/> <copy file="config/Info.plist" todir="${appdir}/Contents"/> <copy file="images/${app}.icns" todir="${appdir}/Contents/Resources"/> <copy todir="${appdir}/Contents/Resources/images"> <fileset dir="images" includes="*png"/> </copy> <copy file="dist/${app}.jar" todir="${appdir}/Contents/Resources/Java"/> <copy todir="${appdir}/Contents/Resources/Java"> <fileset dir="lib"/> </copy> <exec dir="${dist}" executable="hdiutil"> <arg line="create -ov -srcfolder ${app} ${app}.dmg"/> </exec> <delete dir="${dist}/${app}"/> </target>
This fairly straightforward ant task not only builds the entire file structure needed for the application to be properly executed on OS X but also stores the created application in a disk archive. While this task makes fairly heavy use of variable substitution, the names of the variables should make it clear exactly what it is copying and to where.
This must be executed on an OS X machine because of the launcher that is copied into the file structure. Without that, it would be possible to create this entire thing on any machine. In fact, if you had a copy of the JavaApplicationStub on another machine, it would be possible to build it without access to an OS X machine. You would simply need to remove the hdiutil step and zip or tar up the finished app instead.
Included in this ant build file is the Info.plist file, which is the file that OS X turns to when it needs information about the application. A sample of this file is as follows:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleName</key> <string>MyApp</string> <key>CFBundleVersion</key> <string>10.2</string> <key>CFBundleAllowMixedLocalizations</key> <string>true</string> <key>CFBundleExecutable</key> <string>JavaApplicationStub</string> <key>CFBundleDevelopmentRegion</key> <string>English</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleIconFile</key> <string>MyApp.icns</string> <key>Java</key> <dict> <key>WorkingDirectory</key> <string>$APP_PACKAGE/Contents/Resources</string> <key>MainClass</key> <string>com.zarrastudios.Main</string> <key>JVMVersion</key> <string>1.3+</string> <key>ClassPath</key> <array> <string>$JAVAROOT/myapp.jar</string> <string>$JAVAROOT/commons-logging.jar</string> </array> <key>Properties</key> <dict> <key>apple.laf.useScreenMenuBar</key> <string>true</string> <key>com.apple.hwaccel</key> <string>true</string> </dict> </dict> </dict> </plist>
This simple xml file gives OS X all the information it needs about the application, its icon, its properties, and the location of all files to properly build the classpath prior to execution.
Solaris
Sun has its own packaging system and recommends that deployments for Solaris use their packages instead of just a tar file. Fortunately, its system, although arcane, is simple to build against once you are familiar with it. I have yet to produce an ant build script for Sun and I generally run the steps from the command line. First, it requires two files that will describe the package.
The first file is called pkginfo and it looks like this:
PKG="MyAppPackage" NAME="MyApp" ARCH="sparc" VERSION="1.00" CATEGORY="application" VENDOR="zarrastudios.com" EMAIL="nobody@nowhere.com" PSTAMP="Marcus S. Zarra" BASEDIR="/usr/local" CLASSES="none"
The second file is called prototype:
i pkginfo=./pkginfo-client d none /usr 0755 root other d none /usr/local 0755 root other d none /usr/local/MyApp 0755 root other d none /usr/local/MyApp/conf 0755 root other d none /usr/local/MyApp/lib 0755 root other f none /usr/local/MyApp/lib/example.jar 0644 root other f none /usr/local/MyApp/conf/log4j.xml 0644 root other
With these two files in hand, it is now just a matter of executing two command-line programs to build the package:
pkgmk -r ´pwd´ -o MyApp
This produces the actual package. Once it is created, the next step is to make it transportable:
cd /var/spool/pkg pkgtrans -s ´pwd´ /tmp/MyApp <<Select MyApp from the menu>> cd /tmp gzip MyApp
Now the completed application is sitting inside a gzipped Sun package and is ready for deployment.