J/Link Tutorial

Creating Java palettes

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. One way 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 until they are dismissed.

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, for example, 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 palette buttons or Mathematica code you evaluate manually in a notebook.

Starting Out

In the example below, a simple palette-type button is developed 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.

The code is mostly straightforward. You should use the MathFrame class for the frame window because it closes and disposes of itself when its close box is clicked. You can create a MathActionListener that calls buttonFunc and assign it to the button. From the table in section 1.2.5 of the J/Link User Manual,  you see that buttonFunc will be called with two arguments, the first of which is the ActionEvent object. From this object you can obtain the button that was clicked and then its label, which you insert at the current cursor location using the standard NotebookApply function. You 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 last thing to do in PrintButton before you show the window is call ShareFrontEnd[], which 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 a click on the Java button, which would normally be sent to Java, appears in the front end. ShareFrontEnd is discussed in more detail in Section 1.2.5 of the J/Link User Manual.

Here is the code that J/Link will need in order to write a Java palette.

\(PrintButton[label_String]\  := 
	
        JavaBlock[
		Module[{frm, \ button, \ 
              listener}, 
			InstallJava[]; 
			frm\  = \ 
              JavaNew["com.wolfram.jlink.MathFrame"]; 
			button\  = \ JavaNew["java.awt.Button"]; 
			frm@add[button]; 
			frm@
              pack[]; 
			button@setLabel[label]; 
			listener\  = \ 
              JavaNew["com.wolfram.jlink.MathActionListener", 
					\ \ \ \ \ \ \ \ \ \ \ \ "printButtonFunc"]; 
			button@
              addActionListener[
                listener]; 
			ShareFrontEnd[]; 
			JavaShow[
              frm]
		]
	]
	

      printButtonFunc[event_, \ _]\  := 
	
        JavaBlock[
		NotebookApply[SelectedNotebook[], \ 
            event@\(getSource[]@getLabel[]\)]; 
		 (*\ 
            You\ need\ to\ explicitly\ release\ the\ event\ object, \ 
            since\ it\ was\ sent\ to
		\ \ \ Mathematica\ before\ the\ JavaBlock\ was\ \(\(entered\)\(.\)\)\ *) 
		ReleaseObject[event]
	]\)

Remember that you get the behavior that ShareKernel brings when you use a modal dialog box (i.e., when DoModal is running). When Java is being run modally, the kernel’s $ParentLink function 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 the palette example here because you need the palette to be an unobtrusive enhancement to the Mathematica environment. It cannot lock up the kernel while it is alive. ShareFrontEnd only allows modeless Java windows to interact with the front end.

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.

PrintButton["foo", "printButtonFunc"]

Now you can expand this example to include more buttons that perform different operations. First, separate the code that manages the frame that contains the buttons from the code that produces a button. This way you 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.

\(ShowPalette[buttons : {__?JavaObjectQ}] := 
      JavaBlock[
        Module[{frm}, 
          frm = JavaNew["com.wolfram.jlink.MathFrame"]; 
frm@
            setLayout[
              JavaNew["java.awt.GridLayout", 0, 
                1]]; 
\(frm@add[#] &\) /@ 
            buttons; 
ReleaseObject[
            buttons]; 
frm@
            pack[]; 
ShareFrontEnd[]; 
JavaShow[frm];]]\)

Note that you don’t return anything from the ShowPalette function—specifically, you don’t return the frame object itself because you don’t need to refer to the frame ever again. It is destroyed automatically when its close box is clicked. This is a feature of the MathFrame class. Because you don’t need to keep references to any of the Java objects you create, the entire body of ShowPalette can be wrapped in JavaBlock.

Now you can create a reusable PaletteButton function that creates a button. You have to pass in only two things: the label text you want on the button and the function (as a string) you 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 you pass in as the second argument. You need to return the button object, so use the usual technique of creating it before the JavaBlock is entered.

PaletteButton[label_String, buttonFunc_String] := 
      Module[{button, listener}, 
        button = JavaNew["java.awt.Button"]; 
JavaBlock[
          button@setLabel[label]; 
listener = 
            JavaNew["com.wolfram.jlink.MathActionListener", 
              buttonFunc]; 
button@
            addActionListener[
              listener];]; 
button]

Now use the PaletteButton function to create four buttons. The first is just the print button defined above, whose behavior is specified by printButtonFunc.

btn1 = PaletteButton["foo", "printButtonFunc"];

The second button 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 you create the button and define the button function for that operation.

\(\(btn2 = 
        PaletteButton["Expand[\[SelectionPlaceholder]]", "applyButtonFunc"];\)\)

applyButtonFunc[event_, _] := 
      JavaBlock[With[{nb = SelectedNotebook[]}, 
            NotebookApply[nb, event@getSource[]@getLabel[], 
              All]; 
ReleaseObject[
              event]; 
SelectionEvaluate[
              nb]];]

The third button will create a plot. All you 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 using ShareFrontEnd.

btn3 = 
        PaletteButton["Create Plot", "plotButtonFunc"];

plotButtonFunc[
        event_, _] := (Plot[x, {x, 0, 1}]; 
ReleaseObject[
          event];)

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. You 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, for example, if you put the insertion point into a StandardForm cell and try them.

btn4 = PaletteButton["foo", "printButtonFunc2"];

printButtonFunc2[event_, _] := 
      JavaBlock[
        Print[event@getSource[]@
              getLabel[]]; 
ReleaseObject[
          event];]

Now you are ready to create the palette and show it.

ShowPalette[{btn1, btn2, btn3, btn4}]

Clicking the close box will destroy the window, but it does not turn off front end sharing. To do that, you must manually execute UnshareFrontEnd[]. You could set things up to have this called automatically when the window is closed, but turning off front end sharing, like turning off kernel sharing or calling UninstallJava, is best left to the user’s discretion. Your code cannot know whether there is some other component in the user’s environment that is also using front end or kernel sharing.

Front end sharing relies on kernel sharing, so when you call ShareFrontEnd, ShareKernel is called internally. Calling UnshareFrontEnd does not call UnshareKernel for the reason given above. To turn off all sharing initiated by ShareFrontEnd, we must call UnshareFrontEnd and UnshareKernel. Alternatively, you can just call UnshareKernel, which calls UnshareFrontEnd because front end sharing cannot function if kernel sharing is not enabled.

UnshareKernel[];

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, you have done nothing that the front end cannot do all by itself. The real uses you 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.

Final Code

The following example combines all the above elements for J/Link to write a Java palette.

Needs["JLink`"]
PaletteButton[label_String, buttonFunc_String] := 
      Module[{button, listener}, 
        button = JavaNew["java.awt.Button"]; 
JavaBlock[
          button@setLabel[label]; 
listener = 
            JavaNew["com.wolfram.jlink.MathActionListener", 
              buttonFunc]; 
button@
            addActionListener[
              listener];]; 
button]
\(ShowPalette[buttons : {__?JavaObjectQ}] := 
      JavaBlock[
        Module[{frm}, 
          frm = JavaNew["com.wolfram.jlink.MathFrame"]; 
frm@
            setLayout[
              JavaNew["java.awt.GridLayout", 0, 
                1]]; 
\(frm@add[#] &\) /@ 
            buttons; 
ReleaseObject[
            buttons]; 
frm@
            pack[]; 
ShareFrontEnd[]; 
JavaShow[frm];]]\)
printButtonFunc[event_, _] := 
      JavaBlock[
        NotebookApply[SelectedNotebook[], 
          event@getSource[]@getLabel[]]; 
  ReleaseObject[event]]
applyButtonFunc[event_, _] := 
      JavaBlock[With[{nb = SelectedNotebook[]}, 
            fixedNotebokApply[nb, event@getSource[]@getLabel[], 
              All]; 
ReleaseObject[
              event]; 
SelectionEvaluate[
              nb]];]
plotButtonFunc[
        event_, _] := (Plot[x, {x, 0, 1}]; 
ReleaseObject[
          event];)
printButtonFunc2[event_, _] := 
      JavaBlock[
        Print[event@getSource[]@
              getLabel[]]; 
ReleaseObject[
          event];]
  
fixedNotebokApply[nb_, buttonText_String, selType_] := 
      Module[{sel, e}, 
        sel = NotebookRead[nb]; 
e = 
          MakeExpression[
            sel]; 
With[{result = 
              Function[Null, 
                  ReleaseHold[
                    ToExpression[buttonText, StandardForm, 
                        Hold] /. \[SelectionPlaceholder] :> #], 
                  HoldFirst] @@ e}, 
          NotebookWrite[nb, 
            If[Head[sel] === BoxData, MakeBoxes[result], 
              ToString[result, InputForm]], 
            selType]]]
\(PaletteDemo[] := 
      JavaBlock[
        Module[{btn1, btn2, btn3, btn4}, 
          btn1 = PaletteButton["insertMe", "printButtonFunc"]; 
btn2 = 
            PaletteButton["Expand[\[SelectionPlaceholder]]", "applyButtonFunc"]; 
btn3 = 
            PaletteButton["Create Plot", "plotButtonFunc"]; 
btn4 = 
            PaletteButton["printMe", "printButtonFunc2"]; 
ShowPalette[{btn1, btn2, btn3, btn4}]]]\)

Examples

You must call InstallJava prior to running this example.

InstallJava[];

PaletteDemo[]

You can continue to use the front end normally while the palette window is visible and active. When you are finished with the palette window, close it and then turn off front end and kernel sharing with the following command.

UnshareKernel[]