/******************************************************************************* * Copyright (c) 2007-2008 SAS Institute Inc., ILOG S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * SAS Institute Inc. - initial API and implementation * ILOG S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.albireo.internal; import java.util.Stack; import org.eclipse.albireo.core.ThreadingHandler; import org.eclipse.swt.SWT; import org.eclipse.swt.events.FocusAdapter; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; /** * This class, together with {@link AwtDialogListener}, ensures the proper * modal behavior of Swing dialogs when running within a SWT environment. * It allows to block SWT input while the AWT/Swing modal dialog is visible. * * @see AwtDialogListener */ public class SwtInputBlocker { static private SwtInputBlocker instance = null; static private int blockCount = 0; private Shell shell; private final AwtDialogListener dialogListener; private final Shell parentShell; private Stack /* of Shell */shellsWithActivateListener; private final Listener activateListener = new Listener () { public void handleEvent ( final Event event ) { // Schedule the AWT focus request so that activation is completed first. Otherwise the focus // request can happen before the AWT window is deactivated. ThreadingHandler.getInstance ().asyncExec ( SwtInputBlocker.this.shell.getDisplay (), new Runnable () { public void run () { // On some platforms (e.g. Linux/GTK), the 0x0 shell still appears as a dot // on the screen, so make it invisible by moving it below other windows. This // is unnecessary under Windows and causes a flash, so only make the call when necessary. // note: would like to do this too: parentShell.moveBelow(null);, but see bug 170774 SwtInputBlocker.this.shell.moveBelow ( null ); SwtInputBlocker.this.dialogListener.requestFocus (); } } ); } }; private final FocusListener focusListener = new FocusAdapter () { @Override public void focusGained ( final FocusEvent e ) { SwtInputBlocker.this.dialogListener.requestFocus (); } }; private SwtInputBlocker ( final Shell parent, final AwtDialogListener dialogListener ) { this.parentShell = parent; this.dialogListener = dialogListener; } private void open () { assert Display.getCurrent () != null; // On SWT event thread // TODO: Will SWT.NO_FOCUS help in any way here? // TODO: Another shell is not necessary here if AwtEnvironment.createDialogParentFrame is used. // Construct with the current display, rather than parent. This reduces problems where // the AWT dialog gets covered or does not have focus when opened. // Use ON_TOP to prevent a Windows task bar button this.shell = new Shell ( Display.getCurrent (), SWT.APPLICATION_MODAL | SWT.NO_TRIM | SWT.ON_TOP ); this.shell.setSize ( 0, 0 ); // Add listener(s) to force focus back to the AWT dialog if SWT gets control if ( Platform.isGtk () ) { // Under GTK, focus events are not available to detect this condition, // so use the activate event. // TODO: is it necessary to do this for all parents? this.shellsWithActivateListener = new Stack (); Shell shell = this.parentShell; while ( shell != null ) { shell.addListener ( SWT.Activate, this.activateListener ); this.shellsWithActivateListener.push ( shell ); final Composite composite = shell.getParent (); shell = composite != null ? composite.getShell () : null; } } else { // Otherwise, restore focus to awt if the shell gets focus // TODO: test on MacOS and Motif this.shell.addFocusListener ( this.focusListener ); } this.shell.open (); final Display display = this.shell.getDisplay (); while ( !this.shell.isDisposed () ) { if ( !display.readAndDispatch () ) { display.sleep (); } } // If windows from other applications have been opened while SWT was being blocked, // the original parent shell can get lost under those windows after the blocking // is stopped. Force the parent shell back to the front here. if ( !this.parentShell.isDisposed () ) { this.parentShell.forceActive (); } } private void close () { assert this.shell != null; if ( Platform.isGtk () ) { while ( !this.shellsWithActivateListener.isEmpty () ) { final Shell shell = (Shell)this.shellsWithActivateListener.pop (); if ( !shell.isDisposed () ) { shell.removeListener ( SWT.Activate, this.activateListener ); } } } this.shell.dispose (); } public static void unblock () { assert blockCount >= 0; assert Display.getCurrent () != null; // On SWT event thread // System.out.println("Deleting SWT blocker"); if ( blockCount == 0 ) { return; } if ( blockCount == 1 && instance != null ) { instance.close (); instance = null; } blockCount--; } public static void block ( final AwtDialogListener dialogListener ) { assert blockCount >= 0; // System.out.println("Creating SWT blocker"); final Display display = Display.getCurrent (); assert display != null; // On SWT event thread blockCount++; if ( blockCount == 1 ) { assert instance == null; // should be no existing blocker // get a shell to parent the blocking dialog final Shell shell = getShell ( display ); // If there is a shell to block, block input now. If there are no shells, // then there is no input to block. In the case of no shells, we are not // protecting against a shell that might get created later. This is a rare // enough case to skip, at least for now. In the future, a listener could be // added to cover it. // TODO: if (shell==null) add listener to block shells created later? // // Block is implemented with a hidden modal dialog. Using setEnabled(false) is another option, but // on some platforms that will grey the disabled controls. if ( shell != null ) { instance = new SwtInputBlocker ( shell, dialogListener ); instance.open (); } } } // Find a shell to use, giving preference to the active shell. static private Shell getShell ( final Display display ) { Shell shell = display.getActiveShell (); if ( shell == null ) { final Shell[] allShells = display.getShells (); if ( allShells.length > 0 ) { shell = allShells[0]; } } return shell; } }