/* * Copyright 2003-2005 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package sun.awt.motif; import java.awt.*; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetListener; import java.awt.event.*; import java.awt.image.ColorModel; import java.awt.image.ImageObserver; import java.awt.image.ImageProducer; import java.awt.image.VolatileImage; import java.awt.peer.*; import sun.awt.*; import sun.awt.motif.X11FontMetrics; import java.lang.reflect.*; import java.util.logging.*; import java.util.*; // FIXME: Add X errors handling // FIXME: Add chaining of parameters to XEmbed-client if we are both(accelerators; XDND; focus already automatically) public class MEmbedCanvasPeer extends MCanvasPeer implements WindowFocusListener, KeyEventPostProcessor, ModalityListener, WindowIDProvider { private static final Logger xembedLog = Logger.getLogger("sun.awt.motif.xembed.MEmbedCanvasPeer"); final static int XEMBED_VERSION = 0, XEMBED_MAPPED = (1 << 0); /* XEMBED messages */ final static int XEMBED_EMBEDDED_NOTIFY = 0; final static int XEMBED_WINDOW_ACTIVATE = 1; final static int XEMBED_WINDOW_DEACTIVATE = 2; final static int XEMBED_REQUEST_FOCUS =3; final static int XEMBED_FOCUS_IN = 4; final static int XEMBED_FOCUS_OUT = 5; final static int XEMBED_FOCUS_NEXT = 6; final static int XEMBED_FOCUS_PREV = 7; /* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */ final static int XEMBED_GRAB_KEY = 8; final static int XEMBED_UNGRAB_KEY = 9; final static int XEMBED_MODALITY_ON = 10; final static int XEMBED_MODALITY_OFF = 11; final static int XEMBED_REGISTER_ACCELERATOR = 12; final static int XEMBED_UNREGISTER_ACCELERATOR= 13; final static int XEMBED_ACTIVATE_ACCELERATOR = 14; final static int NON_STANDARD_XEMBED_GTK_GRAB_KEY = 108; final static int NON_STANDARD_XEMBED_GTK_UNGRAB_KEY = 109; // A detail code is required for XEMBED_FOCUS_IN. The following values are valid: /* Details for XEMBED_FOCUS_IN: */ final static int XEMBED_FOCUS_CURRENT = 0; final static int XEMBED_FOCUS_FIRST = 1; final static int XEMBED_FOCUS_LAST = 2; // Modifiers bits final static int XEMBED_MODIFIER_SHIFT = (1 << 0); final static int XEMBED_MODIFIER_CONTROL = (1 << 1); final static int XEMBED_MODIFIER_ALT = (1 << 2); final static int XEMBED_MODIFIER_SUPER = (1 << 3); final static int XEMBED_MODIFIER_HYPER = (1 << 4); boolean applicationActive; // Whether the application is active(has focus) Map<Long, AWTKeyStroke> accelerators = new HashMap<Long, AWTKeyStroke>(); // Maps accelerator ID into AWTKeyStroke Map<AWTKeyStroke, Long> accel_lookup = new HashMap<AWTKeyStroke, Long>(); // Maps AWTKeyStroke into accelerator ID Set<GrabbedKey> grabbed_keys = new HashSet<GrabbedKey>(); // A set of keys grabbed by client Object ACCEL_LOCK = accelerators; // Lock object for working with accelerators; Object GRAB_LOCK = grabbed_keys; // Lock object for working with keys grabbed by client MEmbedCanvasPeer() {} MEmbedCanvasPeer(Component target) { super(target); } void initialize() { super.initialize(); installActivateListener(); installAcceleratorListener(); installModalityListener(); // XEmbed canvas should be non-traversable. // FIXME: Probably should be removed and enforced setting of it by the users target.setFocusTraversalKeysEnabled(false); initXEmbedServer(); } void installModalityListener() { ((SunToolkit)Toolkit.getDefaultToolkit()).addModalityListener(this); } void deinstallModalityListener() { ((SunToolkit)Toolkit.getDefaultToolkit()).removeModalityListener(this); } void installAcceleratorListener() { KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventPostProcessor(this); } void deinstallAcceleratorListener() { KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventPostProcessor(this); } void installActivateListener() { // FIXME: should watch for hierarchy changes Window toplevel = getTopLevel(target); if (toplevel != null) { toplevel.addWindowFocusListener(this); applicationActive = toplevel.isFocused(); } } void deinstallActivateListener() { Window toplevel = getTopLevel(target); if (toplevel != null) { toplevel.removeWindowFocusListener(this); } } native boolean isXEmbedActive(); boolean isApplicationActive() { return applicationActive; } native void initDispatching(); native void endDispatching(); native void embedChild(long child); native void childDestroyed(); public void handleEvent(AWTEvent e) { super.handleEvent(e); if (isXEmbedActive()) { switch (e.getID()) { case FocusEvent.FOCUS_GAINED: canvasFocusGained((FocusEvent)e); break; case FocusEvent.FOCUS_LOST: canvasFocusLost((FocusEvent)e); break; case KeyEvent.KEY_PRESSED: case KeyEvent.KEY_RELEASED: if (!((InputEvent)e).isConsumed()) { forwardKeyEvent((KeyEvent)e); } break; } } } public Dimension getPreferredSize() { if (isXEmbedActive()) { Dimension dim = getEmbedPreferredSize(); if (dim == null) { return super.getPreferredSize(); } else { return dim; } } else { return super.getPreferredSize(); } } native Dimension getEmbedPreferredSize(); public Dimension getMinimumSize() { if (isXEmbedActive()) { Dimension dim = getEmbedMinimumSize(); if (dim == null) { return super.getMinimumSize(); } else { return dim; } } else { return super.getMinimumSize(); } } native Dimension getEmbedMinimumSize(); protected void disposeImpl() { if (isXEmbedActive()) { detachChild(); } deinstallActivateListener(); deinstallModalityListener(); deinstallAcceleratorListener(); destroyXEmbedServer(); super.disposeImpl(); } public boolean isFocusable() { return true; } Window getTopLevel(Component comp) { while (comp != null && !(comp instanceof Window)) { comp = comp.getParent(); } return (Window)comp; } native Rectangle getClientBounds(); void childResized() { if (xembedLog.isLoggable(Level.FINER)) { Rectangle bounds = getClientBounds(); xembedLog.finer("Child resized: " + bounds); // It is not required to update embedder's size when client size changes // However, since there is no any means to get client size it seems to be the // only way to provide it. However, it contradicts with Java layout concept - // so it is disabled for now. // Rectangle my_bounds = getBounds(); // setBounds(my_bounds.x, my_bounds.y, bounds.width, bounds.height, SET_BOUNDS); } postEvent(new ComponentEvent(target, ComponentEvent.COMPONENT_RESIZED)); } void focusNext() { if (isXEmbedActive()) { xembedLog.fine("Requesting focus for the next component after embedder"); postEvent(new InvocationEvent(target, new Runnable() { public void run() { KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent(target); } })); } else { xembedLog.fine("Application is not active - denying focus next"); } } void focusPrev() { if (isXEmbedActive()) { xembedLog.fine("Requesting focus for the next component after embedder"); postEvent(new InvocationEvent(target, new Runnable() { public void run() { KeyboardFocusManager.getCurrentKeyboardFocusManager().focusPreviousComponent(target); } })); } else { xembedLog.fine("Application is not active - denying focus prev"); } } void requestXEmbedFocus() { if (isXEmbedActive()) { xembedLog.fine("Requesting focus for client"); postEvent(new InvocationEvent(target, new Runnable() { public void run() { target.requestFocusInWindow(); } })); } else { xembedLog.fine("Application is not active - denying request focus"); } } native void notifyChildEmbedded(); native void detachChild(); public void windowGainedFocus(WindowEvent e) { applicationActive = true; if (isXEmbedActive()) { xembedLog.fine("Sending WINDOW_ACTIVATE"); sendMessage(XEMBED_WINDOW_ACTIVATE); } } public void windowLostFocus(WindowEvent e) { applicationActive = false; if (isXEmbedActive()) { xembedLog.fine("Sending WINDOW_DEACTIVATE"); sendMessage(XEMBED_WINDOW_DEACTIVATE); } } void canvasFocusGained(FocusEvent e) { if (isXEmbedActive()) { xembedLog.fine("Forwarding FOCUS_GAINED"); int flavor = XEMBED_FOCUS_CURRENT; if (e instanceof CausedFocusEvent) { CausedFocusEvent ce = (CausedFocusEvent)e; if (ce.getCause() == CausedFocusEvent.Cause.TRAVERSAL_FORWARD) { flavor = XEMBED_FOCUS_FIRST; } else if (ce.getCause() == CausedFocusEvent.Cause.TRAVERSAL_BACKWARD) { flavor = XEMBED_FOCUS_LAST; } } sendMessage(XEMBED_FOCUS_IN, flavor, 0, 0); } } void canvasFocusLost(FocusEvent e) { if (isXEmbedActive() && !e.isTemporary()) { xembedLog.fine("Forwarding FOCUS_LOST"); Component opp = e.getOppositeComponent(); int num = 0; try { num = Integer.parseInt(opp.getName()); } catch (NumberFormatException nfe) { } sendMessage(XEMBED_FOCUS_OUT, num, 0, 0); } } native void forwardKeyEvent(KeyEvent e); void grabKey(final long keysym, final long modifiers) { postEvent(new InvocationEvent(target, new Runnable() { public void run() { GrabbedKey grab = new GrabbedKey(keysym, modifiers); if (xembedLog.isLoggable(Level.FINE)) xembedLog.fine("Grabbing key: " + grab); synchronized(GRAB_LOCK) { grabbed_keys.add(grab); } } })); } void ungrabKey(final long keysym, final long modifiers) { postEvent(new InvocationEvent(target, new Runnable() { public void run() { GrabbedKey grab = new GrabbedKey(keysym, modifiers); if (xembedLog.isLoggable(Level.FINE)) xembedLog.fine("UnGrabbing key: " + grab); synchronized(GRAB_LOCK) { grabbed_keys.remove(grab); } } })); } void registerAccelerator(final long accel_id, final long keysym, final long modifiers) { postEvent(new InvocationEvent(target, new Runnable() { public void run() { AWTKeyStroke stroke = getKeyStrokeForKeySym(keysym, modifiers); if (stroke != null) { if (xembedLog.isLoggable(Level.FINE)) xembedLog.fine("Registering accelerator " + accel_id + " for " + stroke); synchronized(ACCEL_LOCK) { accelerators.put(accel_id, stroke); accel_lookup.put(stroke, accel_id); } } // Propogate accelerators to the another embedder propogateRegisterAccelerator(stroke); } })); } void unregisterAccelerator(final long accel_id) { postEvent(new InvocationEvent(target, new Runnable() { public void run() { AWTKeyStroke stroke = null; synchronized(ACCEL_LOCK) { stroke = accelerators.get(accel_id); if (stroke != null) { if (xembedLog.isLoggable(Level.FINE)) xembedLog.fine("Unregistering accelerator: " + accel_id); accelerators.remove(accel_id); accel_lookup.remove(stroke); // FIXME: How about several accelerators with the same stroke? } } // Propogate accelerators to the another embedder propogateUnRegisterAccelerator(stroke); } })); } void propogateRegisterAccelerator(AWTKeyStroke stroke) { // Find the top-level and see if it is XEmbed client. If so, ask him to // register the accelerator MWindowPeer parent = getParentWindow(); if (parent != null && parent instanceof MEmbeddedFramePeer) { MEmbeddedFramePeer embedded = (MEmbeddedFramePeer)parent; embedded.registerAccelerator(stroke); } } void propogateUnRegisterAccelerator(AWTKeyStroke stroke) { // Find the top-level and see if it is XEmbed client. If so, ask him to // register the accelerator MWindowPeer parent = getParentWindow(); if (parent != null && parent instanceof MEmbeddedFramePeer) { MEmbeddedFramePeer embedded = (MEmbeddedFramePeer)parent; embedded.unregisterAccelerator(stroke); } } public boolean postProcessKeyEvent(KeyEvent e) { // Processing events only if we are in the focused window. MWindowPeer parent = getParentWindow(); if (parent == null || !((Window)parent.target).isFocused() || target.isFocusOwner()) { return false; } boolean result = false; if (xembedLog.isLoggable(Level.FINER)) xembedLog.finer("Post-processing event " + e); // Process ACCELERATORS AWTKeyStroke stroke = AWTKeyStroke.getAWTKeyStrokeForEvent(e); long accel_id = 0; boolean exists = false; synchronized(ACCEL_LOCK) { exists = accel_lookup.containsKey(stroke); if (exists) { accel_id = accel_lookup.get(stroke).longValue(); } } if (exists) { if (xembedLog.isLoggable(Level.FINE)) xembedLog.fine("Activating accelerator " + accel_id); sendMessage(XEMBED_ACTIVATE_ACCELERATOR, accel_id, 0, 0); // FIXME: How about overloaded? result = true; } // Process Grabs, unofficial GTK feature exists = false; GrabbedKey key = new GrabbedKey(e); synchronized(GRAB_LOCK) { exists = grabbed_keys.contains(key); } if (exists) { if (xembedLog.isLoggable(Level.FINE)) xembedLog.fine("Forwarding grabbed key " + e); forwardKeyEvent(e); result = true; } return result; } public void modalityPushed(ModalityEvent ev) { sendMessage(XEMBED_MODALITY_ON); } public void modalityPopped(ModalityEvent ev) { sendMessage(XEMBED_MODALITY_OFF); } int getModifiers(int state) { int mods = 0; if ((state & XEMBED_MODIFIER_SHIFT) != 0) { mods |= InputEvent.SHIFT_DOWN_MASK; } if ((state & XEMBED_MODIFIER_CONTROL) != 0) { mods |= InputEvent.CTRL_DOWN_MASK; } if ((state & XEMBED_MODIFIER_ALT) != 0) { mods |= InputEvent.ALT_DOWN_MASK; } // FIXME: What is super/hyper? // FIXME: Experiments show that SUPER is ALT. So what is Alt then? if ((state & XEMBED_MODIFIER_SUPER) != 0) { mods |= InputEvent.ALT_DOWN_MASK; } // if ((state & XEMBED_MODIFIER_HYPER) != 0) { // mods |= InputEvent.DOWN_MASK; // } return mods; } // Shouldn't be called on Toolkit thread. AWTKeyStroke getKeyStrokeForKeySym(long keysym, long state) { int keycode = getAWTKeyCodeForKeySym((int)keysym); int modifiers = getModifiers((int)state); return AWTKeyStroke.getAWTKeyStroke(keycode, modifiers); } native int getAWTKeyCodeForKeySym(int keysym); native void sendMessage(int msg); native void sendMessage(int msg, long detail, long data1, long data2); MWindowPeer getParentWindow() { Component parent = target.getParent(); synchronized(target.getTreeLock()) { while (parent != null && !(parent.getPeer() instanceof MWindowPeer)) { parent = parent.getParent(); } return (parent != null)?(MWindowPeer)parent.getPeer():null; } } private static class XEmbedDropTarget extends DropTarget { public void addDropTargetListener(DropTargetListener dtl) throws TooManyListenersException { // Drop target listeners registered with this target will never be // notified, since all drag notifications are routed to the XEmbed // client. To avoid confusion we prohibit listeners registration // by throwing TooManyListenersException as if there is a listener // registered with this target already. throw new TooManyListenersException(); } } public void setXEmbedDropTarget() { // Register a drop site on the top level. Runnable r = new Runnable() { public void run() { target.setDropTarget(new XEmbedDropTarget()); } }; SunToolkit.executeOnEventHandlerThread(target, r); } public void removeXEmbedDropTarget() { // Unregister a drop site on the top level. Runnable r = new Runnable() { public void run() { if (target.getDropTarget() instanceof XEmbedDropTarget) { target.setDropTarget(null); } } }; SunToolkit.executeOnEventHandlerThread(target, r); } public boolean processXEmbedDnDEvent(long ctxt, int eventID) { if (target.getDropTarget() instanceof XEmbedDropTarget) { forwardEventToEmbedded(ctxt, eventID); return true; } else { return false; } } native void forwardEventToEmbedded(long ctxt, int eventID); native void initXEmbedServer(); native void destroyXEmbedServer(); public native long getWindow(); } class GrabbedKey { long keysym; long modifiers; GrabbedKey(long keysym, long modifiers) { this.keysym = keysym; this.modifiers = modifiers; } GrabbedKey(KeyEvent ev) { init(ev); } native void initKeySymAndModifiers(KeyEvent e); private void init(KeyEvent e) { initKeySymAndModifiers(e); } public int hashCode() { return (int)keysym & 0xFFFFFFFF; } public boolean equals(Object o) { if (!(o instanceof GrabbedKey)) { return false; } GrabbedKey key = (GrabbedKey)o; return (keysym == key.keysym && modifiers == key.modifiers); } public String toString() { return "Key combination[keysym=" + keysym + ", mods=" + modifiers + "]"; } }