Tuesday, March 3, 2020

JavaFX desktop application on macOS - Part I

Copyright © 2020, Steven E. Houchin. All rights reserved.

This posting is first in a series, whose purpose is to show what is necessary to get a JavaFX desktop application to work reasonably on macOS.

Environment


In my case, I am running macOS High Sierra (10.13.6) with Java 8 (version 1.8.0_231). My development environment is Windows 10, Java 8 (version 1.8.0_241), NetBeans 8.2 and JavaFX Scene Builder 2.0.

The Application


Mine is a desktop application that loads map images and allows for smart panning, zooming, and custom annotations. It is intended to run on Windows and macOS, and thus I chose Java as the language, and JavaFX for the User Interface components.

Code Unique for macOS


In several places in the code, it was necessary to recognize macOS differences. In many cases, to do this, I had to know it was running on macOS, and not Windows. Here's the method I used to do this, which takes advantage of the built-in system properties Java provides:

/**
 * Return true if the running OS is Mac OS,
 * otherwise Windows is assumed.
 *
 * @return - True if Mac OS, else assume Windows.
 */
public static boolean isMacOS()
{
    String os = System.getProperty("os.name");
    return os.startsWith("Mac") || os.startsWith("mac");
}

Also, I had to make sure file paths use the proper path separator character for the OS:
// get fully qualified path to the map file
String fullFilePath = myRootFolderPath +
                 File.separator + "mymap.jpg";
Then there is the application's menu bar. By default, when it runs on macOS, the menus appear atop the application's main window, just like on Windows. But, of course, that isn't what we want on the Mac; it should appear on the standard Mac menu bar at the top of the screen. Here's the trick to make that happen:
@FXML
private MenuBar menuBar;
if (isMacOS())
{
    // place the program menus on the standard
    // macOS menu bar
    menuBar.setUseSystemMenuBar(true);
}
Two more useful snippets of code that help get things right on the Mac.

First, how to find the user's home directory.

/**
 * Get the user's home directory path, appending the
 * system path separator to it.
 *
 * @return - The full path to the user home directory
 *    (ending in a path separator), or null if error
 */
public static String getUserHomeFolderPath()
{
    // get the user home directory from a standard
    // environment variable
    String folder = isMacOS() ? System.getenv("HOME") :
                                System.getenv("HOMEPATH");
    if (null != folder)
    {
        folder += File.separator;
    }
    return folder;
}

Second, how to get the name of the user's Documents folder.


/**
 * Get the user's Documents folder name.
 *
 * @return The folder name for Documents on this OS.
 */
public static String getDocumentsFolderName()
{
    if (!isMacOS())
    {
        String os = System.getProperty("os.version");
        double version = Double.parseDouble(os);
        if (version < 7.0)
        {
            // name of Documents folder for Windows 2000
            // and XP
            return "My Documents";
        }
    }
    // name of Documents folder for macOS and
    // Windows 7.0+
    return "Documents";
}

Installing the Application to Test


My application links with a library, which I also developed, which does the heavy lifting for drawing and manipulating the map. When I copy the application JAR (MapApp.jar) to my test folder on the Mac, my maps library JAR (MapLib.jar) must be placed in a "lib" subfolder below the test folder. At this point, I can execute the app on the Mac by double-clicking MapApp.jar, and it starts up. But, two things show up that aren't yet right:
  1. The application's title in the system menu bar is "java," instead of "MapApp."
  2. The application's dock icon is the standard Java coffee cup image.
I will discuss solving these problems and others in a future post.

No comments: