| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.netbeans.core.windows; |
| |
| import java.awt.AWTKeyStroke; |
| import java.awt.Component; |
| import java.awt.Container; |
| import java.awt.Dialog; |
| import java.awt.KeyEventDispatcher; |
| import java.awt.KeyEventPostProcessor; |
| import java.awt.KeyboardFocusManager; |
| import java.awt.Window; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.InputEvent; |
| import java.awt.event.KeyEvent; |
| import java.lang.reflect.Method; |
| import java.util.Collections; |
| import java.util.Set; |
| import java.util.logging.Logger; |
| import javax.swing.Action; |
| import javax.swing.JComponent; |
| import javax.swing.JFrame; |
| import javax.swing.JMenuBar; |
| import javax.swing.KeyStroke; |
| import javax.swing.MenuElement; |
| import javax.swing.MenuSelectionManager; |
| import javax.swing.SwingUtilities; |
| import javax.swing.text.Keymap; |
| import org.netbeans.core.NbKeymap; |
| import org.netbeans.core.NbLifecycleManager; |
| import org.netbeans.core.windows.view.ui.popupswitcher.KeyboardPopupSwitcher; |
| import org.openide.actions.ActionManager; |
| import org.openide.util.Lookup; |
| import org.openide.util.Utilities; |
| |
| /** |
| * this class registers itself to the KeyboardFocusManager as a key event |
| * post-processor as well as a key event dispatcher. It invokes the action |
| * bound to the key stroke, or routes unconsumed key events to the menu bar. |
| * If a menu is already shown, all key events are routed to the main menu bar. |
| * |
| * @author Tran Duc Trung |
| */ |
| final class ShortcutAndMenuKeyEventProcessor implements KeyEventDispatcher, KeyEventPostProcessor { |
| |
| private static ShortcutAndMenuKeyEventProcessor defaultInstance; |
| |
| private static boolean installed = false; |
| |
| /* holds original set of focus forward traversal keys */ |
| private static Set<AWTKeyStroke> defaultForward; |
| /* holds original set of focus backward traversal keys */ |
| private static Set<AWTKeyStroke> defaultBackward; |
| |
| private static final Logger log = Logger.getLogger("org.netbeans.core.windows.ShortcutAndMenuKeyEventProcessor"); // NOI18N |
| |
| private ShortcutAndMenuKeyEventProcessor() { |
| } |
| |
| |
| private static synchronized ShortcutAndMenuKeyEventProcessor getDefault() { |
| if(defaultInstance == null) { |
| defaultInstance = new ShortcutAndMenuKeyEventProcessor(); |
| } |
| |
| return defaultInstance; |
| } |
| |
| |
| public static synchronized void install() { |
| if(installed) { |
| return; |
| } |
| |
| ShortcutAndMenuKeyEventProcessor instance = getDefault(); |
| |
| KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); |
| keyboardFocusManager.addKeyEventDispatcher(instance); |
| keyboardFocusManager.addKeyEventPostProcessor(instance); |
| // #63252: Disable focus traversal functionality of Ctrl+Tab and Ctrl+Shift+Tab, |
| // to allow our own document switching (RecentViewListAction) |
| defaultForward = keyboardFocusManager.getDefaultFocusTraversalKeys( |
| KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS); |
| defaultBackward = keyboardFocusManager.getDefaultFocusTraversalKeys( |
| KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS); |
| keyboardFocusManager.setDefaultFocusTraversalKeys( |
| KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, |
| Collections.singleton(AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_TAB, 0)) |
| ); |
| keyboardFocusManager.setDefaultFocusTraversalKeys( |
| KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, |
| Collections.singleton(AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_TAB, KeyEvent.SHIFT_DOWN_MASK)) |
| ); |
| } |
| |
| public static synchronized void uninstall() { |
| if(!installed) { |
| return; |
| } |
| |
| ShortcutAndMenuKeyEventProcessor instance = getDefault(); |
| |
| KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); |
| keyboardFocusManager.removeKeyEventDispatcher(instance); |
| keyboardFocusManager.removeKeyEventPostProcessor(instance); |
| // reset default focus traversal keys |
| keyboardFocusManager.setDefaultFocusTraversalKeys( |
| KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, defaultForward |
| ); |
| keyboardFocusManager.setDefaultFocusTraversalKeys( |
| KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, defaultBackward |
| ); |
| defaultBackward = null; |
| defaultForward = null; |
| } |
| |
| private boolean wasPopupDisplayed; |
| private int lastModifiers; |
| private char lastKeyChar; |
| private boolean lastSampled = false; |
| private boolean skipNextTyped = false; |
| |
| public boolean postProcessKeyEvent(KeyEvent ev) { |
| if (ev.isConsumed()) |
| return false; |
| |
| if (processShortcut(ev)) |
| return true; |
| |
| Window w = SwingUtilities.windowForComponent(ev.getComponent()); |
| if (w instanceof Dialog && !WindowManagerImpl.isSeparateWindow(w)) |
| return false; |
| |
| JFrame mw = (JFrame)WindowManagerImpl.getInstance().getMainWindow(); |
| if (w == mw) { |
| return false; |
| } |
| |
| JMenuBar mb = mw.getJMenuBar(); |
| if (mb == null) |
| return false; |
| boolean pressed = (ev.getID() == KeyEvent.KEY_PRESSED); |
| boolean res = invokeProcessKeyBindingsForAllComponents(ev, mw, pressed); |
| |
| if (res) |
| ev.consume(); |
| return res; |
| } |
| |
| public boolean dispatchKeyEvent(KeyEvent ev) { |
| log.fine("dispatchKeyEvent ev: " + ev.paramString() |
| + " source:" + ev.getSource().getClass().getName()); |
| // in some ctx, may need event filtering |
| if (NbKeymap.getContext().length != 0) { |
| // Ignore anything but KeyPressed inside ctx, #67187 |
| if (ev.getID() != KeyEvent.KEY_PRESSED) { |
| ev.consume(); |
| return true; |
| } |
| |
| skipNextTyped = true; |
| |
| Component comp = ev.getComponent(); |
| if (!(comp instanceof JComponent) || |
| ((JComponent)comp).getClientProperty("context-api-aware") == null) { |
| // not context api aware, don't pass subsequent events |
| processShortcut(ev); |
| // ignore processShortcut result, consume everything while in ctx |
| return true; |
| } |
| } |
| |
| if (ev.getID() == KeyEvent.KEY_PRESSED |
| && ev.getModifiers() == (InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK) |
| && (ev.getKeyCode() == KeyEvent.VK_PAUSE |
| || ev.getKeyCode() == KeyEvent.VK_CANCEL) |
| ) { |
| Object source = ev.getSource(); |
| if (source instanceof Component) { |
| Component focused = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); |
| System.err.println("*** ShortcutAndMenuKeyEventProcessor: current focus owner = " + focused); // NOI18N |
| } |
| ev.consume(); |
| return true; |
| } |
| |
| |
| // multi-shortcut in middle |
| if (ev.getID() == KeyEvent.KEY_TYPED && skipNextTyped) { |
| ev.consume(); |
| skipNextTyped = false; |
| return true; |
| } |
| |
| if (ev.getID() == KeyEvent.KEY_RELEASED) { |
| skipNextTyped = false; |
| } |
| |
| if (ev.getID() == KeyEvent.KEY_PRESSED) { |
| // decompose to primitive fields to avoid memory profiler confusion (keyEvent keeps source reference) |
| lastKeyChar = ev.getKeyChar(); |
| lastModifiers = ev.getModifiers(); |
| lastSampled = true; |
| } |
| |
| MenuElement[] arr = MenuSelectionManager.defaultManager().getSelectedPath(); |
| if (arr == null || arr.length == 0) { |
| wasPopupDisplayed = false; |
| |
| // Only here for fix #41477: |
| // To be able to catch and dispatch Ctrl+TAB and Ctrl+Shift+Tab |
| // in our own way, it's needed to do as soon as here, because |
| // otherwise Swing will use these keys as focus traversals, which |
| // means that TopComponent which contains focusCycleRoot inside itself |
| // will grab these shortcuts, which is not desirable |
| return KeyboardPopupSwitcher.processShortcut(ev); |
| } |
| |
| if (!wasPopupDisplayed |
| && lastSampled == true |
| && ev.getID() == KeyEvent.KEY_TYPED |
| && lastModifiers == InputEvent.ALT_MASK |
| && ev.getModifiers() == InputEvent.ALT_MASK |
| && lastKeyChar == ev.getKeyChar() |
| ) { |
| wasPopupDisplayed = true; |
| ev.consume(); |
| return true; |
| } |
| |
| wasPopupDisplayed = true; |
| |
| MenuSelectionManager.defaultManager().processKeyEvent(ev); |
| |
| // commented out as #130919 fix - I don't know why this was here, but |
| // it did prevent keyboard functioning in menus in dialogs |
| /*if (!ev.isConsumed() && arr != null && arr.length > 0 && arr[0] instanceof JMenuBar) { |
| ev.setSource(WindowManagerImpl.getInstance().getMainWindow()); |
| }*/ |
| |
| return ev.isConsumed(); |
| } |
| |
| private boolean processShortcut(KeyEvent ev) { |
| //ignore shortcut keys when the IDE is shutting down |
| if (NbLifecycleManager.isExiting()) { |
| ev.consume(); |
| return true; |
| } |
| |
| KeyStroke ks = KeyStroke.getKeyStrokeForEvent(ev); |
| Window w = SwingUtilities.windowForComponent(ev.getComponent()); |
| |
| // don't process shortcuts if this is a help frame |
| if ((w instanceof JFrame) && ((JFrame)w).getRootPane().getClientProperty("netbeans.helpframe") != null) // NOI18N |
| return true; |
| |
| // don't let action keystrokes to propagate from both |
| // modal and nonmodal dialogs, but propagate from separate floating windows, |
| // even if they are backed by JDialog |
| if ((w instanceof Dialog) && |
| !WindowManagerImpl.isSeparateWindow(w) && |
| !isTransmodalAction(ks)) { |
| return false; |
| } |
| |
| // Provide a reasonably useful action event that identifies what was focused |
| // when the key was pressed, as well as what keystroke ran the action. |
| ActionEvent aev = new ActionEvent( |
| ev.getSource(), ActionEvent.ACTION_PERFORMED, Utilities.keyToString(ks)); |
| |
| Keymap root = Lookup.getDefault().lookup(Keymap.class); |
| Action a = root.getAction (ks); |
| if (a != null && a.isEnabled()) { |
| ActionManager am = Lookup.getDefault().lookup(ActionManager.class); |
| am.invokeAction(a, aev); |
| ev.consume(); |
| return true; |
| } |
| return false; |
| } |
| |
| private static boolean invokeProcessKeyBindingsForAllComponents( |
| KeyEvent e, Container container, boolean pressed) |
| { |
| try { |
| Method m = JComponent.class.getDeclaredMethod( |
| "processKeyBindingsForAllComponents", // NOI18N |
| new Class[] { KeyEvent.class, Container.class, Boolean.TYPE }); |
| if (m == null) |
| return false; |
| |
| m.setAccessible(true); |
| Boolean b = (Boolean) m.invoke(null, new Object[] { e, container, pressed ? Boolean.TRUE : Boolean.FALSE }); |
| return b.booleanValue(); |
| } catch (Exception ex) { |
| //ex.printStackTrace(); |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Checks to see if a given keystroke is bound to an action which should |
| * function on all focused components. This includes the Main Window, |
| * dialogs, popup menus, etc. Otherwise only the Main Window and |
| * TopComponents will receive the keystroke. By default, off, unless the |
| * action has a property named <code>OpenIDE-Transmodal-Action</code> which |
| * is set to {@link Boolean#TRUE}. |
| * @param key the keystroke to check |
| * @return <code>true</code> if transmodal; <code>false</code> if a normal |
| * action, or the key is not bound to anything in the global keymap |
| */ |
| private static boolean isTransmodalAction (KeyStroke key) { |
| Keymap root = Lookup.getDefault().lookup(Keymap.class); |
| Action a = root.getAction (key); |
| if (a == null) return false; |
| Object val = a.getValue ("OpenIDE-Transmodal-Action"); // NOI18N |
| return val != null && val.equals (Boolean.TRUE); |
| } |
| |
| } |