Here is what I came up with. It is not perfect but does the job. Recall I had a JFrame on top of which I needed a modal SWT Shell behaving like a modal JDialog.
The Shell that is started can not live in the AWT event dispatch thread. We need access to the thread to intercept mouse and window events later. So start the Shell in a new Thread
I placed a GlassPane on my JFrame for the time the Shell was open. The purpose was to block other interactive Swing components in the frame and it's containers and to intercept all mouse events. I needed the mouse events, so I could not simply do Frame.setEnabled(false).
I used on WindowListener on the frame and a MouseListener on the GlassPane
I used a DisposeListener on the Shell to detect the moment when the JFrame has to lose the GlassPane and other modifications done for the time the Shell is live.
To control the visibility of the Shell with my Swing listeners I needed access to the SWT event thread. This is done by executing Runnable with shell.getDisplay().syncExec(...)
This is how I launch the Shell, activate the GlassPane, attach "modality listeners" and also remove them when the Shell is closed. The Shell is started in a new thread.
new Thread(new Runnable() {
@Override
public void run() {
final Shell shell = myWidget.getShell();
final EditorWindowListener ewl = new EditorWindowListener(shell);
myFrame.addWindowListener(ewl);
final EditorClickListener ecl = new EditorClickListener(shell);
myFrame.getGlassPane().addMouseListener(ecl);
myFrame.getGlassPane().setVisible(true);
shell.addDisposeListener(new DisposeListener() {
//Remove the disabled status
@Override
public void widgetDisposed(DisposeEvent arg0) {
myFrame.removeWindowListener(ewl);
myFrame.getGlassPane().removeMouseListener(ecl);
myFrame.getGlassPane().setVisible(false);
}
});
//The method that starts the shell
myWidget.show();
}
}).start();
This is what happens in myWidget.show() (standard SWT stuff, no modifications)
shell.open();
shell.forceActive();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
Here are the two listeners I add to the frame and glassPane. First the MouseListener that detects clicks on the glass pane and if any event is caught, brings the Shell to the top. I know this would not be needed in a perfectly modal dialog, and mostly it is not needed in this case either, but some dual monitor setups caused problems that were solved with this listener.
class EditorClickListener extends MouseAdapter
{
private Shell shell;
public EditorClickListener(Shell s)
{
this.shell = s;
}
@Override
public void mouseClicked(MouseEvent e) {
shellToFront(shell);
}
}
Then the WindowListener attached to the frame. It makes sure anytime the frame is active, the shell jumps on top.
class EditorWindowListener extends WindowAdapter
{
Shell shell;
public EditorWindowListener(Shell s)
{
this.shell = s;
}
@Override
public void windowOpened(WindowEvent e) {
shellToFront(shell);
}
@Override
public void windowDeiconified(WindowEvent e) {
shellToFront(shell);
}
@Override
public void windowActivated(WindowEvent e) {
//Set the shell on top of the frame
//Fixes some problems with dual monitor setups.
final java.awt.Point framePoint = myFrame.getLocation();
shellToFront(shell);
shell.getDisplay().syncExec(new Runnable() {
@Override
public void run() {
shell.setMinimized(false);
shell.setActive();
org.eclipse.swt.graphics.Point shellPoint = aikataulu.getLocation();
shellPoint.x = (int) framePoint.getX();
shellPoint.y = (int) framePoint.getY();
shell.setLocation(shellPoint);
}
});
}
}
And finally the method to pop the shell to your face to create a feeling of modality. Note that all events need to assigned to happen in the SWT event thread. I had some trouble with setting the minimized state to false. So I had to add an artificial minimization in case the Shell was not minimized and was indeed behind other windows. This results in a stupid unnecessary animation in some use cases, but for now it will make do
private void shellToFront(final Shell shell)
{
shell.getDisplay().syncExec(new Runnable() {
@Override
public void run() {
if (!shell.getMinimized())
{
shell.setMinimized(true);
}
shell.setMinimized(false);
shell.setActive();
}
});
}