/*******************************************************************************
* Copyright (c) 2007, 2014 compeople AG and others.
* 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:
* compeople AG - initial API and implementation
*******************************************************************************/
package org.eclipse.riena.internal.navigation.ui.swt;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.core.runtime.Assert;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.CoolBar;
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.riena.ui.swt.utils.SwtUtilities;
/**
* Tracks and restores focus.
* <p>
* Will track all focus events on the given Shell and will remember the last
* focused control (outside of the coolbars). If ESC is pressed on one of the
* tracked CoolBars it restores the focus to the last focused control.
* <p>
* Automatically stops focus tracking when the Shell is disposed. Automatically
* stops coolbar-key tracking a coolbar is disposed.
*
* @see #RestoreFocusOnEscListener(Display)
* @see #addControl(CoolBar)
*/
public final class RestoreFocusOnEscListener extends KeyAdapter implements Listener {
/**
* If ESC is pressed on one of these controls we restore the focus to
* {@link #savedFocusControl}.
*/
private final Set<CoolBar> controlSet;
/**
* The shell we are tracking.
*/
private final Shell shell;
/**
* Restore focus to this control if ESC is pressed; could be null or
* disposed
*/
private Control savedFocusControl;
/**
* Return the first CoolBar in this composite or null.
*/
public static CoolBar findCoolBar(final Composite composite) {
CoolBar result = null;
for (final Control child : composite.getChildren()) {
if (child instanceof CoolBar) {
result = (CoolBar) child;
break;
}
}
return result;
}
/**
* Create a new instance of this class.
*
* @param shell
* a non-null Shell; will be used to track focus events that
* occur inside this Shell and outside of the tracked coolbars.
* @see #addControl(CoolBar)]
*/
public RestoreFocusOnEscListener(final Shell shell) {
Assert.isNotNull(shell);
controlSet = new HashSet<CoolBar>();
this.shell = shell;
this.shell.getDisplay().addFilter(SWT.FocusIn, this);
this.shell.addListener(SWT.Dispose, new Listener() {
public void handleEvent(final Event event) {
event.display.removeFilter(SWT.FocusIn, this);
}
});
}
/**
* Start tracking key events on the given {@code coolBar} and restore the
* focus when ESC is pressed. Will stop tracking if the coolbar is disposed.
*
* @param coolbar
* a non-null {@link CoolBar}
*/
public void addControl(final CoolBar coolbar) {
Assert.isNotNull(coolbar);
Assert.isTrue(coolbar.getShell() == shell, "coolbar must be on same shell as this listener"); //$NON-NLS-1$
if (controlSet.add(coolbar)) {
for (final Control coolbarChild : coolbar.getChildren()) {
coolbarChild.addKeyListener(this);
coolbarChild.addDisposeListener(new DisposeListener() {
public void widgetDisposed(final DisposeEvent e) {
((Control) e.widget).removeKeyListener(RestoreFocusOnEscListener.this);
}
});
}
}
}
/**
* Stop tracking key events on the given coolbar.
*
* @param coolbar
* a non-null {@link CoolBar}
*/
public void removeControl(final CoolBar coolbar) {
Assert.isNotNull(coolbar);
if (controlSet.remove(coolbar)) {
for (final Control coolbarChild : coolbar.getChildren()) {
coolbarChild.removeKeyListener(this);
}
}
}
/**
* Should not be called directly.
* <p>
* When ESC is released, this listener restores the focus to the control
* that had it before the menu bar became focused.
*/
@Override
public void keyReleased(final KeyEvent e) {
if (e.keyCode == 27) { // 27 is ESC
// System.out.println("ESC on " + e.widget);
if (!SwtUtilities.isDisposed(savedFocusControl)) {
savedFocusControl.setFocus();
}
}
}
/**
* Should not be called directly.
*/
public void handleEvent(final Event event) {
if (event.widget instanceof Control) {
final Control control = (Control) event.widget;
if (contains(shell, control) && !contains(controlSet, control)) {
// System.out.println("savedFocusControl: " + control);
savedFocusControl = control;
}
}
}
// helping methods
//////////////////
private boolean contains(final Composite container, final Control control) {
boolean result = container == control;
Composite parent = control.getParent();
while (!result && parent != null) {
result = container == parent;
parent = parent.getParent();
}
return result;
}
private boolean contains(final Collection<CoolBar> containers, final Control control) {
boolean result = false;
for (final Composite container : containers) {
result = result || contains(container, control);
if (result) {
break;
}
}
return result;
}
}