Missing Mac APIs in JavaFX

Markus KARG markus at headcrashing.eu
Sun Nov 8 08:17:42 UTC 2015


Side note: First launching a JavaFX application and then sending a file-open-event afterwards is also something Windows does since decades. Whether or not the old (file name paramater) or new (OLE event) method is used can be customized by the administrator for every single file type. So adding support for this actually is not Mac OS specific.

-----Original Message-----
From: openjfx-dev [mailto:openjfx-dev-bounces at openjdk.java.net] On Behalf Of Dr. Michael Paus
Sent: Sonntag, 8. November 2015 09:10
To: openjfx-dev at openjdk.java.net
Subject: Re: Missing Mac APIs in JavaFX

Hi,

I agree with you that these things should work as expected with JavaFX. 
Have you filed a bug report for them?
In addition to the platform specific APIs I would prefer a common 
solution for common things though, like opening
a file which is associated with your application.

Michael

Am 04.11.15 um 04:41 schrieb Neil Brown:
> Hi,
>
> Short version: I believe JavaFX is lacking some Mac-related APIs which 
> cannot be worked around without an internal com.sun.* call, and would 
> like to request that the APIs be added/made public for JDK 9, perhaps 
> as part of Jonathan Giles' work around JEP 253.
>
> I stand to be corrected on any of this...
>
> Long version: On Windows and Linux, files are generally opened via 
> command-line arguments.  On Mac, there is a file-open event issued to 
> an application, e.g. you double-click a .txt file, TextEdit gets a 
> file-open event.  If TextEdit is not open, it will be launched, then a 
> file-open event will be sent to it.
>
> Pre-JavaFX, the Java solution to receive these events was to use the 
> com.apple.eawt.* packages, which Oracle added in JDK 7. However, it 
> seems that these APIs don't play nice with FX.
>
> I've put some source code at the end of the email for a simple 
> application which I bundled with infinitekind's appbundler fork for 
> testing (https://bitbucket.org/infinitekind/appbundler/).  No matter 
> where you uncomment the setEAWTFileHandler call (search AAA, or BBB in 
> the source), the .app always misses the initial load event which 
> caused the application to open.  The net result is if the user 
> double-clicks an associated file, the JavaFX application will launch, 
> but not load the file in question unless the user double-clicks it 
> again.  Needless to say, this is problematic.
>
> If you look at the FX source code, you can see that FX does set a 
> file-open event handler with an empty implementation 
> (com.sun.glass.ui.Application, line 83 in 8u60).  Grepping the source 
> code shows this method is not implemented anywhere in the source code, 
> so FX is simply discarding the event.  The only Java way I've found to 
> let an FX application listen to the open files event is using a 
> com.sun call in the Application constructor (see CCC in the source, 
> and setFXFileHandler).  Suggestions for a better work-around very 
> welcome!
>
>
> The second API is a similar problem.  JavaFX installs an application 
> menu with standard options, including "Quit" (bound to Cmd-Q).  If you 
> leave Platform.setImplicitExit to its default value of true, all is 
> well.  Cmd-Q quits the application. However, if you 
> setImplicitExit(false) (see YYY in the source), as our actual 
> application must, problems arise.  The quit handler now is a no-op, 
> with no way of overriding it.  Pressing Cmd-Q or right-clicking the 
> dock and selecting quit is now totally ignored by the application, and 
> from the user's perspective the app is unquittable by these normal 
> means.  The only work-around I've found is to set a quit handler in 
> the same spot we set the file open handler (see ZZZ in the source: 
> I've used Platform.exit for this example, in reality we have a nicer 
> custom shutdown routine).
>
>
> My proposal is fairly straight-forward: the 
> com.sun.glass.ui.Application.EventHandler class (and associated 
> setEventHandler call) needs to become a public API in JDK 9, or else 
> these issues will be impossible to work around post-modularisation. 
> Initial file open and quit handler are the only ones biting us at the 
> moment, but I suspect that whole handler class should be public, 
> either as-is or by repurposing it into an EventHandler with some sort 
> of AppEvent extends Event class.
>
> Thanks,
>
> Neil.
>
> ======
> src/example/OpenApp.java
> ======
> package example;
>
> import java.io.File;
> import java.util.ArrayList;
> import java.util.Arrays;
> import java.util.List;
> import java.util.stream.Collectors;
> import javafx.application.Application;
> import javafx.application.Platform;
> import javafx.collections.FXCollections;
> import javafx.collections.ListChangeListener;
> import javafx.collections.ObservableList;
> import javafx.scene.Scene;
> import javafx.scene.control.Label;
> import javafx.scene.control.Menu;
> import javafx.scene.control.MenuBar;
> import javafx.scene.layout.BorderPane;
> import javafx.stage.Stage;
>
> public class OpenApp extends Application
> {
>     private static ObservableList<File> requestedFiles = 
> FXCollections.observableArrayList();
>
>     public static void main(String[] args)
>     {
>         // AAA: Uncommenting here, we miss initial load events:
>         //setEAWTFileHandler();
>         launch(args);
>     }
>
>     @Override
>     public void start(Stage stage) throws Exception
>     {
>         // YYY: this causes problems.
>         //Platform.setImplicitExit(false);
>         // BBB: Here also misses initial load events:
>         //setEAWTFileHandler();
>
>         BorderPane bp = new BorderPane();
>         Label filename = new Label("");
>         bp.setCenter(filename);
>         Runnable updateFiles = () -> 
> filename.setText(requestedFiles.stream().map(File::getAbsolutePath).collect(Collectors.joining("\n")));
>         requestedFiles.addListener((ListChangeListener<? super File>)c 
> -> updateFiles.run());
>         updateFiles.run();
>         bp.setBottom(new Label("Example Application"));
>         MenuBar menuBar = new MenuBar(new Menu("My Custom Menu"));
>         menuBar.setUseSystemMenuBar(true);
>         bp.setTop(menuBar);
>         stage.setScene(new Scene(bp));
>         stage.show();
>     }
>
>     public OpenApp()
>     {
>         // CCC: Constructor is the only time we are at the right point in
>         // FX control flow.
>         //setFXFileHandler();
>     }
>
>     private static void setEAWTFileHandler()
>     {
>         com.apple.eawt.Application appleApp = 
> com.apple.eawt.Application.getApplication();
>         appleApp.setOpenFileHandler(e -> {
>             List<File> files = new ArrayList(e.getFiles());
>             Platform.runLater(() -> requestedFiles.addAll(e.getFiles()));
>         });
>     }
>
>     private static void setFXFileHandler()
>     {
>         com.sun.glass.ui.Application glassApp = 
> com.sun.glass.ui.Application.GetApplication();
>         glassApp.setEventHandler(new 
> com.sun.glass.ui.Application.EventHandler() {
>             @Override
>             public void 
> handleOpenFilesAction(com.sun.glass.ui.Application app, long time, 
> String[] filenames)
>             {
>                 List<File> files = 
> Arrays.stream(filenames).map(File::new).collect(Collectors.toList());
>                 Platform.runLater(() -> requestedFiles.addAll(files));
>                 super.handleOpenFilesAction(app, time, filenames);
>             }
>
>             // ZZZ: add our own quit handler, too
>             /*
>             @Override
>             public void handleQuitAction(com.sun.glass.ui.Application 
> app, long time)
>             {
>                 Platform.exit();
>                 super.handleQuitAction(app, time);
>             }
>             */
>         });
>     }
>
> }
>
> ======
> build.xml (needs appbundler in ant lib, built from 
> https://bitbucket.org/infinitekind/appbundler/)
> ======
> <?xml version="1.0" encoding="UTF-8"?>
> <project name="machandler" default="all">
>     <taskdef name="bundleapp"
>              classpath="appbundler-1.0ea.jar"
>              classname="com.oracle.appbundler.AppBundlerTask"/>
>
>     <target name="all">
>         <mkdir dir="classes"/>
>         <javac srcdir="src" destdir="classes">
>             <compilerarg value="-XDignore.symbol.file=true"/>
>         </javac>
>         <jar destfile="openapp.jar" basedir="classes"></jar>
>         <bundleapp
>                 jvmrequired="1.8"
>                 outputdirectory="."
>                 name="OpenApp"
>                 displayname="OpenApp"
>                 executableName="OpenApp"
>                 identifier="example.OpenApp"
>                 shortversion="1.0"
>                 version="1.0"
>                 mainclassname="example.OpenApp">
>             <classpath file="openapp.jar"/>
>             <bundledocument extensions="dummyfx"
>                             name="Dummy document"
>                             role="editor">
>             </bundledocument>
>         </bundleapp>
>     </target>
> </project>




More information about the openjfx-dev mailing list