1.3.8 Sharing the Front End: Palette-Type Buttons
As discussed in Section 1.2.7, one of the goals of J/Link is to allow Java user interface elements to be as close as possible to first-class members of the notebook front end environment in the way notebook and palette windows are. One of the ways this is accomplished is with the ShareKernel function, which allows Java windows to share the kernel's attention with notebook windows. We refer to such Java windows as "modeless," not in the traditional sense of allowing other Java windows to remain active, but modeless with respect to the kernel, meaning that the kernel is not kept busy while they are open.
Beyond the ability to have Java windows share the kernel with the front end, it would be nice to allow actions in Java to cause effects in notebook windows, such as printing something, displaying a graph, or any of the notebook-manipulation commands like NotebookApply, NotebookPrint, SelectionEvaluate, SelectionMove, and so on. A good example of this is palette buttons. A palette button can cause the current selection to be replaced by something else and the resulting expression to be evaluated in place.
The ShareFrontEnd function lets actions in Java modeless windows trigger events in a notebook window just like can be done from palette buttons or Mathematica code you evaluate manually in a notebook. Remember that you get automatically the ability to interact with the front end when you use a modal dialog (i.e., when DoModal is running). When Java is being run in a modal way, the kernel's $ParentLink always points at the front end, so all side effect outputs get sent to the front end automatically. A modal window would not be acceptable for our palette example here because we need the palette to be an unobtrusive enhancement to the Mathematica environment--it cannot lock up the kernel while it is alive. ShareKernel allows Java windows to call Mathematica without tying up the kernel, and ShareFrontEnd is an extension to ShareKernel (it calls ShareKernel internally) that allows such "modeless" Java windows to interact with the front end. ShareFrontEnd is discussed in more detail in Section 1.2.7.
In the PrintButton example below, we develop a simple palette-type button in Java that prints its label at the current cursor position in the active notebook. Because of current limitations with ShareFrontEnd, this example will not work with a remote kernel; the same machine must be running the kernel and the front end.
Now invoke the PrintButton function to create and display the palette. Click the button to see the button's label (foo in this example) inserted at the current cursor location. When you are done, click the window's close box.
The code is mostly straightforward. As usual, we use the MathFrame class for the frame window because it closes and disposes of itself when its close box is clicked. We create a MathActionListener that calls buttonFunc and we assign it to the button. From the table in Section 1.2.7, we know that buttonFunc will be called with two arguments, the first of which is the ActionEvent object. From this object we can obtain the button that was clicked and then its label, which we insert at the current cursor location using the standard NotebookApply function. One subtlety is that we need to specify SelectedNotebook as the target for notebook operations like NotebookApply, NotebookWrite, NotebookPrint, and so on, which take a notebook as an argument. Because of implementation details of ShareFrontEnd, the notebook given by EvaluationNotebook is not the correct target (after all, there isn't any evaluation currently in progress in the front end when the button is clicked).
The important thing to note in PrintButton is the use of ShareFrontEnd and UnshareFrontEnd. As discussed earlier, ShareFrontEnd puts Java into a state where it forwards everything other than the result of a computation to the front end, and puts the front end into a state where it is able to receive it. This is why the Print output triggered by clicking the Java button, which would normally be sent to Java (and just discarded there), appears in the front end. Front end sharing (and also kernel sharing) should be turned off when they are no longer needed, but if you are writing code for others to use you cannot just blindly shut sharing down--the user could have other Java windows open that need sharing. To handle this issue, ShareFrontEnd (and ShareKernel) works on a register/unregister principle. Every time you call ShareFrontEnd, it returns a token that represents a request for front end sharing. If front end sharing is not on, it will be turned on. When a program no longer needs front end sharing, it should call UnshareFrontEnd, passing the token from ShareFrontEnd as the argument. Only when all requests for sharing have been unregistered in this way will sharing actually be turned off.
The onClose() method of the MathFrame class lets you specify Mathematica code to be executed when the frame is closed. This code is executed after all event listeners have been notified, so it is a safe place to turn off sharing. In the onClose() code, we call UnshareFrontEnd with the token returned by ShareFrontEnd. Using the onClose() method in this way allows us to avoid writing a cleanup function that users would have to call manually after they were finished with the palette. It is not a problem to leave front end sharing turned on, but it is desirable to have your program alter the user's session as little as possible.
Now let's expand this example to include more buttons that perform different operations. The complete code for this example is provided in the file Palette.nb in the JLink/Examples/Part1 directory.
The first thing we do is separate the code that manages the frame containing the buttons from the code that produces a button. In this way we will have a reusable palette frame that can hold any number of different buttons. The ShowPalette function below takes a list of buttons, arranges them vertically in a frame window, calls ShareFrontEnd, and displays the frame in front of the user's notebook window.
Note that we don't return anything from the ShowPalette function--specifically, we don't return the frame object itself. This is because we don't need to refer to the frame ever again. It is destroyed automatically when its close box is clicked (remember, this is a feature of the MathFrame class). Because we don't need to keep references to any of the Java objects we create, the entire body of ShowPalette can be wrapped in JavaBlock.
Now let's create a reusable PaletteButton function that creates a button. We have to pass in only two things: the label text we want on the button and the function (as a string) we want to have invoked when the button is clicked. This is sufficient to allow completely arbitrary button behavior, as the entire functionality of the button is tied up in the button function we pass in as the second argument.
We will use the PaletteButton function to create four buttons. The first is just the print button defined above, the behavior of which is specified by printButtonFunc.
The second will duplicate the functionality of the buttons in the standard AlgebraicManipulation front end palette. These buttons wrap a function (e.g., Expand) around the current selection and evaluate the resulting expression in place. Here is how we create the button and define the button function for that operation:
The third button will create a plot. All we have to do is call a plotting function--the work of directing the graphics output to a new cell in the frontmost notebook is handled internally by J/Link as a result of having front end sharing turned on via ShareFrontEnd.
The final button demonstrates another method for causing text to be inserted at the current cursor location. The first example of this, printButtonFunc, uses NotebookApply. We can also just call Print--as with graphics, Print output is automatically routed to the frontmost notebook window by J/Link when front end sharing is on. This quick-and-easy Print method works fine for many situations when you want something to appear in a notebook window, but using NotebookApply is a more rigorous technique. You will see some differences in the effects of these two buttons if you put the insertion point into a StandardForm cell and try them.
Now we are finally ready to create the palette and show it:
In closing, it must be noted that although this example has demonstrated some useful techniques, it is not a particularly valuable way to use ShareFrontEnd. In creating a simple palette of buttons, we have done nothing that the front end cannot do all by itself. The real uses you will find for ShareFrontEnd will presumably involve aspects that cannot be duplicated within the front end, such as more sophisticated dialog boxes or other user interface elements.