X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.utils.ui%2Fsrc%2Forg%2Fsimantics%2Futils%2Fui%2Finternal%2Fawt%2FAwtFocusHandler.java;fp=bundles%2Forg.simantics.utils.ui%2Fsrc%2Forg%2Fsimantics%2Futils%2Fui%2Finternal%2Fawt%2FAwtFocusHandler.java;h=15b9d967df2d673cbcf44cc36e635945fdbe2c33;hb=ac5f1da15cc639da880fea86a7b828c8fa2e1b7e;hp=0000000000000000000000000000000000000000;hpb=b8392422ec5b5961202d941ec447018526d26dd2;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/internal/awt/AwtFocusHandler.java b/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/internal/awt/AwtFocusHandler.java new file mode 100644 index 000000000..15b9d967d --- /dev/null +++ b/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/internal/awt/AwtFocusHandler.java @@ -0,0 +1,276 @@ +/******************************************************************************* + * Copyright (c) 2007 SAS Institute. + * 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 - initial API and implementation + *******************************************************************************/ +package org.simantics.utils.ui.internal.awt; + +import java.awt.Component; +import java.awt.Container; +import java.awt.EventQueue; +import java.awt.FocusTraversalPolicy; +import java.awt.Frame; +import java.awt.Window; +import java.awt.event.ContainerEvent; +import java.awt.event.ContainerListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowFocusListener; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.swing.JPopupMenu; +import javax.swing.text.Caret; +import javax.swing.text.JTextComponent; + + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class AwtFocusHandler implements FocusListener, ContainerListener, + WindowFocusListener { + + private Frame frame; + private SwtFocusHandler swtHandler; + private RecursiveContainerListener containerListener; + private boolean awtHasFocus = false; + private Component currentComponent = null; + + public AwtFocusHandler(Frame frame) { + assert frame != null; + + this.frame = frame; + this.containerListener = new RecursiveContainerListener(this); + frame.addContainerListener(this.containerListener); + frame.addWindowFocusListener(this); + } + + public void setSwtHandler(SwtFocusHandler handler) { + assert handler != null; + assert swtHandler == null; // this method is meant to be called once + + swtHandler = handler; + } + + /** + * Invoked from {@link SwtFocusHandler} DisposeListener. + * Only intended to be invoked once. + */ + public void dispose() { + assert frame != null; + frame.removeWindowFocusListener(this); + frame.removeContainerListener(containerListener); + frame = null; + currentComponent = null; + } + + void gainFocus() { + // assert frame != null; + // assert !awtHasFocus; + assert EventQueue.isDispatchThread(); // On AWT event thread + if (frame == null) + return; + + FocusTraversalPolicy policy = frame.getFocusTraversalPolicy(); + Component component; + if (policy instanceof EmbeddedChildFocusTraversalPolicy) { + EmbeddedChildFocusTraversalPolicy embeddedPolicy = (EmbeddedChildFocusTraversalPolicy) policy; + component = embeddedPolicy.getCurrentComponent(frame); + } else { + // TODO: direction based? + component = policy.getDefaultComponent(frame); + } + if (component != null) { + // System.out.println("Requesting focus for component: " + component); + component.requestFocus(); + // TODO: else case error? If not, consider moving flag setting below into this if + } + awtHasFocus = true; + } + + /** + * Moves focus back to the next SWT component + */ + void transferFocusNext() { + assert swtHandler != null; + assert awtHasFocus; + + awtHasFocus = false; + swtHandler.gainFocusNext(); + } + + /** + * Moves focus back to the previous SWT component + */ + void transferFocusPrevious() { + assert swtHandler != null; + assert awtHasFocus; + + awtHasFocus = false; + swtHandler.gainFocusPrevious(); + } + + boolean awtHasFocus() { + return awtHasFocus; + } + + Component getCurrentComponent() { + return currentComponent; + } + + // ..................... Listener implementations + + public void focusGained(FocusEvent e) { + assert e != null; + assert EventQueue.isDispatchThread(); // On AWT event thread + + // System.out.println("gained (awt). component = " + e.getComponent() + ", opposite = " + e.getOppositeComponent()); + currentComponent = e.getComponent(); + } + + public void focusLost(FocusEvent e) { + // System.out.println("component focus lost (awt). opposite = " + e.getOppositeComponent()); + + // Intentionally leaving currentComponent set. When window focus is lost, + // it will be needed. + } + + public void componentAdded(ContainerEvent e) { + assert e != null; + assert EventQueue.isDispatchThread(); // On AWT event thread + + e.getChild().addFocusListener(this); + } + + public void componentRemoved(ContainerEvent e) { + assert e != null; + assert EventQueue.isDispatchThread(); // On AWT event thread + + e.getChild().removeFocusListener(this); + } + + public void windowGainedFocus(WindowEvent e) { + assert EventQueue.isDispatchThread(); // On AWT event thread + // System.out.println("WindowFocusListener.windowGainedFocus"); + awtHasFocus = true; + } + + public void windowLostFocus(WindowEvent e) { + assert e != null; + assert swtHandler != null; + assert EventQueue.isDispatchThread(); // On AWT event thread + + // System.out.println("WindowFocusListener.windowLostFocus"); + + // Dismiss any popup menus that are + // open when losing focus. This prevents situations where + // multiple popup menus are visible at the same time. In JDK 1.4 and earlier, + // the dismissal is not done automatically. In JDK 1.5, this code is + // unnecessary, but it doesn't seem to hurt anything. + // TODO: verify this is OK on other windowing systems + // TODO: disable in post-1.4 environments + /* boolean popupShown = */hidePopups(); + + // If focus is being lost to the parent SWT composite, then + // grab it back for AWT and return. Normally the parent SWT composite will + // do this for us, but it will not see a focus gained event when focus + // is transferred to it from its AWT frame child. + // This happens, for example, if an AWT control has focus and the + // tab of a containing (already active) view is clicked. + // + // However, don't grab back focus if a popup was hidden above. The popup + // area will not be properly redrawn (the popup, or part of it, will + // appear to be still there. + //if (!popupShown && swtHandler.hasFocus()) { + // System.out.println("**** Taking back focus: " + e); + // This seems to have side effects, so it's commented out for now. + // (Sometimes, it forces the workbench window to the foreground when another + // program's window is selected.) + // TODO: find an alternate approach to reassert focus + // gainFocus(); + // return; + //} + + // On a normal change of focus, Swing will turn off any selection + // in a text field to help indicate focus is lost. This won't happen + // automatically when transferring to SWT, so turn off the selection + // manually. + if (currentComponent instanceof JTextComponent) { + Caret caret = ((JTextComponent)currentComponent).getCaret(); + if (caret != null) { + caret.setSelectionVisible(false); + } + } + awtHasFocus = false; + } + + // Returns true if any popup has been hidden + private boolean hidePopups() { + boolean result = false; + List popups = new ArrayList(); + assert EventQueue.isDispatchThread(); // On AWT event thread + Window frame = this.frame; + if (frame == null) + return result; + + // Look for popups inside the frame's component hierarchy. + // Lightweight popups will be found here. + findContainedPopups(frame, popups); + + // Also look for popups in the frame's window hierachy. + // Heavyweight popups will be found here. + findOwnedPopups(frame, popups); + + // System.out.println("Hiding popups, count=" + popups.size()); + for (Iterator iter = popups.iterator(); iter.hasNext();) { + Component popup = (Component)iter.next(); + if (popup.isVisible()) { + result = true; + popup.setVisible(false); + } + } + return result; + } + + private void findOwnedPopups(Window window, List popups) { + assert window != null; + assert EventQueue.isDispatchThread(); // On AWT event thread + + Window[] ownedWindows = window.getOwnedWindows(); + for (int i = 0; i < ownedWindows.length; i++) { + findContainedPopups(ownedWindows[i], popups); + findOwnedPopups(ownedWindows[i], popups); + } + } + + private void findContainedPopups(Container container, List popups) { + assert container != null; + assert popups != null; + assert EventQueue.isDispatchThread(); // On AWT event thread + + Component[] components = container.getComponents(); + for (int i = 0; i < components.length; i++) { + Component c = components[i]; + // JPopupMenu is a container, so check for it first + if (c instanceof JPopupMenu) { + popups.add(c); + } else if (c instanceof Container) { + findContainedPopups((Container)c, popups); + } + } + } + + public void postHidePopups() { + EventQueue.invokeLater(new Runnable() { + public void run() { + hidePopups(); + } + }); + } + +}