package org.eclipse.albireo.internal;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.KeyboardFocusManager;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.albireo.core.SwingControl;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Widget;
// Handler for global focus events. Maintains global state information like current and
// previous active widgets. When actions based on global state need to be done, they are
// implemented in this class. See also the FocusHandler class for additional handling based
// on a single SwingControl.
public class GlobalFocusHandler
{
private static final String SAVED_FOCUS_OWNER_KEY = "org.eclipse.albireo.savedFocusOwner";
private final Display display;
private final SwtEventFilter swtEventFilter;
private final List listeners = new ArrayList ();
private static final boolean verboseFocusEvents = FocusHandler.verboseFocusEvents;
public GlobalFocusHandler ( final Display display )
{
this.display = display;
this.swtEventFilter = new SwtEventFilter ();
display.addFilter ( SWT.Activate, this.swtEventFilter );
display.addFilter ( SWT.Deactivate, this.swtEventFilter );
display.addFilter ( SWT.Traverse, this.swtEventFilter );
}
public int getCurrentSwtTraversal ()
{
assert Display.getCurrent () != null; // On SWT event thread
return this.swtEventFilter.currentSwtTraversal;
}
public Widget getActiveWidget ()
{
assert Display.getCurrent () != null; // On SWT event thread
return this.swtEventFilter.activeWidget;
}
public Shell getActiveShell ()
{
assert Display.getCurrent () != null; // On SWT event thread
return this.swtEventFilter.activeShell;
}
public SwingControl getActiveEmbedded ()
{
assert Display.getCurrent () != null; // On SWT event thread
return this.swtEventFilter.activeEmbedded;
}
public Widget getLastActiveWidget ()
{
assert Display.getCurrent () != null; // On SWT event thread
return this.swtEventFilter.lastActiveWidget;
}
public SwingControl getLastActiveEmbedded ()
{
assert Display.getCurrent () != null; // On SWT event thread
return this.swtEventFilter.lastActiveEmbedded;
}
public boolean getLastActiveFocusCleared ()
{
assert Display.getCurrent () != null; // On SWT event thread
return this.swtEventFilter.lastActiveFocusCleared;
}
public void setLastActiveFocusCleared ( final boolean lastActiveFocusCleared )
{
assert Display.getCurrent () != null; // On SWT event thread
this.swtEventFilter.lastActiveFocusCleared = lastActiveFocusCleared;
}
public void addEventFilter ( final Listener filter )
{
this.listeners.add ( filter );
}
public void removeEventFilter ( final Listener filter )
{
this.listeners.remove ( filter );
}
protected void fireEvent ( final Event event )
{
for ( final Iterator iterator = this.listeners.iterator (); iterator.hasNext (); )
{
final Listener listener = (Listener)iterator.next ();
listener.handleEvent ( event );
}
}
public void dispose ()
{
this.display.removeFilter ( SWT.Activate, this.swtEventFilter );
this.display.removeFilter ( SWT.Deactivate, this.swtEventFilter );
this.display.removeFilter ( SWT.Traverse, this.swtEventFilter );
}
protected boolean isBorderlessSwingControl ( final Widget widget )
{
return widget instanceof SwingControl && ( widget.getStyle () & SWT.EMBEDDED ) != 0;
}
protected void clearFocusOwner ( final SwingControl swingControl )
{
assert Display.getCurrent () != null; // On SWT event thread
if ( !swingControl.isAWTPermanentFocusLossForced () )
{
return;
}
// It appears safe to call getFocusOwner on SWT thread
final Component owner = ( (Frame)swingControl.getAWTHierarchyRoot () ).getFocusOwner ();
if ( owner != null )
{
EventQueue.invokeLater ( new Runnable () {
public void run ()
{
// Clear the AWT focus owner so that a permanent focus lost event is
// generated. Where possible, we use the KeyboardFocusManager, but
// it has no method to clear the focus owner within a particular frame,
// if that frame is no longer active. In that case, we use a hack of
// disabling and re-enabling the window's focus owner. The hack has
// the drawback of a brief visual movement of the cursor (or other
// focus indicator), so it is good to avoid it whenever possible, as
// we do here.
final KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager ();
if ( owner == kfm.getFocusOwner () )
{
if ( verboseFocusEvents )
{
trace ( "clearing focus thru kfm: " + owner );
}
kfm.clearGlobalFocusOwner ();
}
else
{
if ( verboseFocusEvents )
{
trace ( "clearing focus thru hack: " + owner );
}
owner.setEnabled ( false );
owner.setEnabled ( true );
}
}
} );
swingControl.setData ( SAVED_FOCUS_OWNER_KEY, owner );
}
}
protected void restoreFocusOwner ( final SwingControl swingControl )
{
assert Display.getCurrent () != null; // On SWT event thread
final Component savedOwner = (Component)swingControl.getData ( SAVED_FOCUS_OWNER_KEY );
if ( savedOwner != null )
{
swingControl.setData ( SAVED_FOCUS_OWNER_KEY, null );
EventQueue.invokeLater ( new Runnable () {
public void run ()
{
// Restore focus to any AWT component that lost focus due to
// clearFocusOwner().
if ( verboseFocusEvents )
{
trace ( "restoring focus: " + savedOwner );
}
savedOwner.requestFocus ();
}
} );
}
}
private void trace ( final String msg )
{
System.err.println ( header () + ' ' + msg );
}
private String header ()
{
return "@" + System.currentTimeMillis () + " " + System.identityHashCode ( this );
}
protected class SwtEventFilter implements Listener
{
int currentSwtTraversal = SWT.TRAVERSE_NONE;
Widget activeWidget;
Shell activeShell;
SwingControl activeEmbedded;
Widget lastActiveWidget = null;
SwingControl lastActiveEmbedded = null;
boolean lastActiveFocusCleared = false;
public SwtEventFilter ()
{
this.activeWidget = GlobalFocusHandler.this.display.getFocusControl ();
this.activeShell = GlobalFocusHandler.this.display.getActiveShell ();
if ( isBorderlessSwingControl ( this.activeWidget ) )
{
this.activeEmbedded = (SwingControl)this.activeWidget;
}
}
public void handleEvent ( final Event event )
{
final Widget widget = event.widget;
switch ( event.type )
{
case SWT.Activate:
this.activeWidget = widget;
// Track the currently active shell. This is more reliable than
// depending on Display.getActiveShell() which sometimes returns an
// inactive shell.
if ( widget instanceof Shell )
{
this.activeShell = (Shell)widget;
}
// If we have moved from a SwingControl to another control in the same
// shell, clear its current focus owner so that a permanent focus
// lost event is generated.
if ( this.lastActiveEmbedded != null && !this.lastActiveEmbedded.isDisposed () && this.lastActiveEmbedded != widget && !this.lastActiveFocusCleared && widget instanceof Control && // (need a getShell() method)
this.lastActiveEmbedded.getShell () == ( (Control)widget ).getShell () )
{
clearFocusOwner ( this.lastActiveEmbedded );
this.lastActiveFocusCleared = true;
}
// If we have moved to a SwingControl, restore the current focus owner
// that was cleared above during a previous Activate event.
if ( isBorderlessSwingControl ( widget ) )
{
this.activeEmbedded = (SwingControl)widget;
restoreFocusOwner ( this.activeEmbedded );
}
break;
case SWT.Deactivate:
if ( this.activeWidget != null )
{
this.lastActiveWidget = this.activeWidget;
this.activeWidget = null;
}
if ( event.widget instanceof Shell )
{
this.activeShell = null;
}
if ( isBorderlessSwingControl ( widget ) )
{
if ( this.activeEmbedded != null )
{
this.lastActiveEmbedded = this.activeEmbedded;
this.lastActiveFocusCleared = false;
this.activeEmbedded = null;
}
}
break;
case SWT.Traverse:
this.currentSwtTraversal = event.detail;
break;
}
// Propagate to any listeners
fireEvent ( event );
// If there is a current traversal, it is now complete
// with the activation of a control. Reset the value
// to indicate no current traversal.
if ( event.type == SWT.Activate )
{
this.currentSwtTraversal = SWT.TRAVERSE_NONE;
}
}
}
}