Creating an Alert Box with Swing/mxj

Cycling '74's icon

Hi All,

I've put together a simple mxj class to create an alert box using Swing. It compiles and runs fine on our Athlon 64 4000+, running XP with MaxMSP 4.5.6, with JVM Version 1.5.0_04, and displays the dialog upon receiving a bang with little or no noticeable lag.

However, when I run the app on my OS X 10.4.4 machine, a G4 1 gHz, running 4.5.6 with JVM 1.5.0_05, I get the "spinning wheel of death" and Max hangs indefinitely and requires me to force quit. I should clarify that the code compiles fine and gives me no errors on either machine.

Both the patch and the java code are included in the text below. I've also attached the .class file as well.

Does anyone know why this might be?

Thanks in Advance,
-Henry

Max Patch
Copy patch and select New From Clipboard in Max.

//
//    SaveChanges.java
//
//    01.21.06, Henry Till (henrytill@gmail.com)
//

import com.cycling74.max.*;
import javax.swing.JOptionPane;

public class SaveChanges extends MaxObject
{
    String name = null;

public SaveChanges(Atom[] atoms) {
        declareInlets(new int[] { 1 });
        declareOutlets(new int[] { 1 });
}

    public void bang() {

        Object[] options = {"Don't Save",
                            "Cancel",
                            "Save"};

        int n = JOptionPane.showOptionDialog(null,    // null to hatch from parent frame
            "Do you want to save changes to "
            + name,
            "Save Changes?",
            JOptionPane.YES_NO_CANCEL_OPTION,
            JOptionPane.QUESTION_MESSAGE,
            null,
            options,
            options[2]);

        if (n == JOptionPane.YES_OPTION) {
            outlet(0, "2");

        } else if (n == JOptionPane.NO_OPTION) {
            outlet(0, "0");
        } else if (n == JOptionPane.CANCEL_OPTION) {
            outlet(0, "1");
        } else {
            post("no answer given");
        }
    }

    public void inlet(int i) { }

    public void inlet(float f) { }

    public void list(Atom[] list) { }

    public void anything(String s, Atom[] args) {        
        if(s.equals("name"))
            name = Atom.toOneString(args);
    }

}

Cycling '74's icon

You have to call nextWindowIsModal() before displaying the Swing window, that should fix it.
Look at the javadocs for MaxSystem, it's somewhere in there.

Roby

Cycling '74's icon

Hi Roby, Thanks for the reply. The api doc makes it sound as though
this should do the trick.

Just to clarify, would it be correct to put this line:

        MaxSystem.nextWindowIsModal();            

right before the following block?

        int n = JOptionPane.showOptionDialog(null,    // null to hatch from
parent frame
            "Do you want to save changes to "
            + name,
            "Save Changes?",
            JOptionPane.YES_NO_CANCEL_OPTION,
            JOptionPane.QUESTION_MESSAGE,
            null,
            options,
            options[2]);

This compiled okay, but when I went to run it on OS X, it still hung,
which made me think maybe I am doing this wrong.

-H

Cycling '74's icon

> Just to clarify, would it be correct to put this line:
>
>         MaxSystem.nextWindowIsModal();            
>
> right before the following block?
>

> This compiled okay, but when I went to run it on OS X, it still hung,
> which made me think maybe I am doing this wrong.
>

What I do is the following:
I declare in my class:
    MaxSystem maxsys = new MaxSystem();    

and later before displaying the window I put:

    maxsys.nextWindowIsModal();

Roby

Cycling '74's icon

Roby et al,

That's clearly the right way to do it - so I changed my code, and
it's compiling fine, but still hanging when I run it.

Have you or anybody else successfully displayed an Alert Box using
JOptionPane in mxj on OS X before?

-Henry

Cycling '74's icon

Henry Till wrote:
> Have you or anybody else successfully displayed an Alert Box using
> JOptionPane in mxj on OS X before?

Have you tried using SwingUtilities.invokeLater() to handle Swing
display stuff asynchronously in Swing's own event thread? Thus:

SwingUtilities.invokeLater(new Runnable(){
    public void run(){
        JOptionPane.showConfirmDialog(null,"Hello"); //eg
    }
});

--
Owen

Cycling '74's icon

Owen,

Thanks a lot for the tip. Using this technique did the trick.

-Henry

Cycling '74's icon

Henry,
Would you be so kind to share your code of the original example
as enhanced with this trick? I am particularly interested in how
you communicate the result of the asynchronously executed OptionPane
call
back to the mxj thread.
thanks
-jennek

Cycling '74's icon

Hi Jennek,

In general you don't need to - the rest of the action can be done in the
Swing thread, or some other event handling thread. In this specific
case there doesn't seem to be much choice as it was the very condition
of having the mxj thread wait on the swing thread that seemed to be
causing deadlock.

--
Owen

Jennek Geels's icon

OK, I was worried that calls to outlet would cause more threading
problems,
as they run in in a Max thread. But from the 'Writing Max externals
in Java'
document from Topher I understand that this is safe to do (v0.3 page 42)
"It is worth noting that when using Java threads inside of your Max
external, any outlet
calls you make back into the Max application will automatically be
deferred for handling
by the low-priority main thread for normal outlet calls and the high-
priority scheduler
thread for outletHigh calls."

One thing puzzles me: why is there no hang up on WinXP?
Anyway, I am glad to have learned about invokeLater().

For the sake of archiving, I'll post my edition of your code below.
-jennek

//
//    SaveChanges.java
//
//    01.21.06, Henry Till (henrytill@gmail.com)
//

import com.cycling74.max.*;
import javax.swing.*;

public class SaveChanges extends MaxObject {
    String name = null;

    public SaveChanges(Atom[] atoms) {
        declareInlets(new int[] { 1 });
        declareOutlets(new int[] { 1 });
    }

    public void bang() {

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {

                Object[] options = { "Don't Save", "Cancel", "Save" };

                int n = JOptionPane
                        .showOptionDialog(null, // null to hatch from parent
                                                // frame
                                "Do you want to save changes to " + name,
                                "Save Changes?",
                                JOptionPane.YES_NO_CANCEL_OPTION,
                                JOptionPane.QUESTION_MESSAGE, null, options,
                                options[2]);

                if (n == JOptionPane.YES_OPTION) {
                    outlet(0, "2");

                } else if (n == JOptionPane.NO_OPTION) {
                    outlet(0, "0");
                } else if (n == JOptionPane.CANCEL_OPTION) {
                    outlet(0, "1");
                } else {
                    post("no answer given");
                }

            }
        });

    }

    public void inlet(int i) {
    }

    public void inlet(float f) {
    }

    public void list(Atom[] list) {
    }

    public void anything(String s, Atom[] args) {
        if (s.equals("name"))
            name = Atom.toOneString(args);
    }

}

Owen Green's icon

jennek geels wrote:

> One thing puzzles me: why is there no hang up on WinXP?

It seems to have something to do with Carbon-Cocoa interaction. As far
as I can tell, one has to make sure your main thread and the AWT/Swing
thread are never in contention or a deadlock will occur.

--
Owen

Léopold Frey's icon
topher lafata's icon

Yes. There is indeed much potential for deadlock when using swing on OS X due to the fact that swing does its event handling via cocoa and max/msp has its own event handlin loop which uses carbon. It definitely took a significant amount of vudu to get swing to work at all on os x.

Regardless. Anytime you display a frame or other UI element you should do it from the swing/awt thread. From suns own docs.

"To avoid the possibility of thread problems, we recommend that you use invokeLater to create the GUI on the event-dispatching thread for all new applications. If you have old programs that are working fine they are probably OK; however you might want to convert them when it's convenient to do so. "

This is probably particularly important on OS X given the fact that we are dealing with cocoa java embedded in a carbon application. I wish it wasn't so but i think we need to live with it for the time being!