Index: ide/golden/deps.txt =================================================================== RCS file: /cvs/ide/golden/deps.txt,v retrieving revision 1.211 retrieving revision 1.209.2.2 diff -u -r1.211 -r1.209.2.2 --- ide/golden/deps.txt 23 Sep 2005 15:25:54 -0000 1.211 +++ ide/golden/deps.txt 30 Sep 2005 08:41:35 -0000 1.209.2.2 @@ -572,6 +572,7 @@ MODULE org.netbeans.modules.editor.lib/1 (ide) REQUIRES org.netbeans.modules.editor.fold/1 (ide) REQUIRES org.netbeans.modules.editor.util/1 (ide) + REQUIRES org.openide.awt (platform) REQUIRES org.openide.dialogs (platform) REQUIRES org.openide.modules.ModuleFormat1 REQUIRES org.openide.util (platform) Index: editor/lib/nbproject/project.xml =================================================================== RCS file: /cvs/editor/lib/nbproject/project.xml,v retrieving revision 1.5 retrieving revision 1.5.16.1 diff -u -r1.5 -r1.5.16.1 --- editor/lib/nbproject/project.xml 4 Jun 2005 05:16:07 -0000 1.5 +++ editor/lib/nbproject/project.xml 26 Sep 2005 14:20:29 -0000 1.5.16.1 @@ -41,12 +41,20 @@ 6.2 - + org.openide.dialogs 6.2 + + + + org.openide.awt + + + + 6.5 Index: editor/libsrc/org/netbeans/editor/BaseKit.java =================================================================== RCS file: /cvs/editor/libsrc/org/netbeans/editor/BaseKit.java,v retrieving revision 1.136 retrieving revision 1.135.4.2 diff -u -r1.136 -r1.135.4.2 --- editor/libsrc/org/netbeans/editor/BaseKit.java 27 Sep 2005 15:51:45 -0000 1.136 +++ editor/libsrc/org/netbeans/editor/BaseKit.java 29 Sep 2005 14:51:05 -0000 1.135.4.2 @@ -510,6 +510,9 @@ c.putClientProperty("hyperlink-operation", // NOI18N org.netbeans.lib.editor.hyperlink.HyperlinkOperation.create(c, getContentType())); + + // Mark that the editor's multi keymap adheres to context API in status displayer + c.putClientProperty("context-api-aware", Boolean.TRUE); } protected void executeInstallActions(JEditorPane c) { Index: editor/libsrc/org/netbeans/editor/MultiKeymap.java =================================================================== RCS file: /cvs/editor/libsrc/org/netbeans/editor/MultiKeymap.java,v retrieving revision 1.27 retrieving revision 1.27.76.2 diff -u -r1.27 -r1.27.76.2 --- editor/libsrc/org/netbeans/editor/MultiKeymap.java 10 Aug 2004 15:53:53 -0000 1.27 +++ editor/libsrc/org/netbeans/editor/MultiKeymap.java 27 Sep 2005 08:44:07 -0000 1.27.76.2 @@ -16,6 +16,8 @@ import java.awt.event.KeyEvent; import java.awt.event.ActionEvent; import java.awt.event.InputEvent; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.HashMap; import javax.swing.text.Keymap; @@ -24,6 +26,7 @@ import javax.swing.KeyStroke; import javax.swing.Action; import javax.swing.AbstractAction; +import org.openide.awt.StatusDisplayer; /** * Keymap that is capable to work with MultiKeyBindings @@ -60,22 +63,52 @@ * is found in base context. */ private Action contextKeyNotFoundAction = BEEP_ACTION; + + /** + * List of key strokes that form the present context. + * If this list differs from the global context maintained in the status displayer + * then the keymap must be reset and attempted to be put + * into the global context before attempting to process the given keystroke. + * If the keymap cannot be put into such a context then + * it returns null action for the given keystroke. + */ + private List contextKeys; /** Construct new keymap. * @param name name of new keymap */ public MultiKeymap(String name) { delegate = JTextComponent.addKeymap(name, null); + contextKeys = new ArrayList(); } /** Set the context keymap */ void setContext(Keymap contextKeymap) { context = contextKeymap; } - + /** Reset keymap to base context */ public void resetContext() { context = null; + contextKeys.clear(); + } + + /** + * Add a context key to the global context maintained by the StatusDisplayer. + * + * @param key a key to be added to the global context. + */ + private void shiftGlobalContext(KeyStroke key) { + StatusDisplayer.getDefault().shiftContext(key); + // Shift the locally maintained mirror context as well + contextKeys.add(key); + } + + /** + * Reset the global context in case there is a reason for it. + */ + private void resetGlobalContext() { + StatusDisplayer.getDefault().resetContext(); } /** What to do when key is not resolved for context */ @@ -160,11 +193,14 @@ } } - Action getActionImpl(KeyStroke key) { + private Action getActionImpl(KeyStroke key) { Action a = null; if (context != null) { a = context.getAction(key); - if (a == null) { // possibly ignore modifier keystrokes + // Commented out the next part to allow the other + // keystroke processors to work when the editor does not have an action + // for the particular keystroke. +/* if (a == null) { // possibly ignore modifier keystrokes switch (key.getKeyCode()) { case KeyEvent.VK_SHIFT: case KeyEvent.VK_CONTROL: @@ -178,16 +214,51 @@ return EMPTY_ACTION; // ignore releasing and typed events } } + */ } else { a = delegate.getAction(key); } return a; } + + private boolean contextKeysEqual(KeyStroke[] keys) { + if (keys.length != contextKeys.size()) { + return false; + } + for (int i = keys.length - 1; i >= 0; i--) { + if (!contextKeys.get(i).equals(keys[i])) { + return false; + } + } + return true; + } public Action getAction(KeyStroke key) { Action ret = null; + // Check whether the context in status displayer corresponds to the keymap's context + // If there would be a non-empty SD context that differs from the editor's one + // then do not return any action for this keystroke. + KeyStroke[] globalContext = StatusDisplayer.getDefault().getContext(); + if (globalContext.length > 0 && !contextKeysEqual(globalContext)) { + resetContext(); + int i; + for (i = 0; i < globalContext.length; i++) { + Action a = getActionImpl(globalContext[i]); + if (a instanceof KeymapSetContextAction) { + a.actionPerformed(null); + } else { + // no multi-keystrokes for such context in editor + resetContext(); + break; + } + } + if (i != globalContext.length) { // unsuccessful context switch + return null; + } + } + // Explicit patches of the keyboard problems if (ignoreNextTyped) { if (key.isOnKeyRelease()) { // ignore releasing here @@ -202,60 +273,73 @@ if (ret == null) { ret = getActionImpl(key); - if (ret != EMPTY_ACTION) { // key that should be ignored - if (!(ret instanceof KeymapSetContextAction)) { - if (context != null) { - ignoreNextTyped = true; - } else if (compatibleIgnoreNextTyped) { - // #44307 = disabled extra ignoreNextTyped patches for past JDKs - if ( // Explicit patch for the keyTyped sent after Alt+key - (key.getModifiers() & InputEvent.ALT_MASK) != 0 // Alt pressed - && (key.getModifiers() & InputEvent.CTRL_MASK) == 0 // Ctrl not pressed - ) { - boolean patch = true; - if (key.getKeyChar() == 0 || key.getKeyChar() == KeyEvent.CHAR_UNDEFINED) { - switch (key.getKeyCode()) { - case KeyEvent.VK_ALT: // don't patch single Alt - case KeyEvent.VK_KANJI: - case KeyEvent.VK_KATAKANA: - case KeyEvent.VK_HIRAGANA: - case KeyEvent.VK_JAPANESE_KATAKANA: - case KeyEvent.VK_JAPANESE_HIRAGANA: - case 0x0107: // KeyEvent.VK_INPUT_METHOD_ON_OFF: - in 1.3 only - case KeyEvent.VK_NUMPAD0: // Alt+NumPad keys - case KeyEvent.VK_NUMPAD1: - case KeyEvent.VK_NUMPAD2: - case KeyEvent.VK_NUMPAD3: - case KeyEvent.VK_NUMPAD4: - case KeyEvent.VK_NUMPAD5: - case KeyEvent.VK_NUMPAD6: - case KeyEvent.VK_NUMPAD7: - case KeyEvent.VK_NUMPAD8: - case KeyEvent.VK_NUMPAD9: - patch = false; - break; - } + if (ret instanceof KeymapSetContextAction) { // + // Mark the context shifting + shiftGlobalContext(key); + + } else { // not a context shift action + if (context != null) { // Already in a non-empty context + ignoreNextTyped = true; + + } else if (compatibleIgnoreNextTyped) { + // #44307 = disabled extra ignoreNextTyped patches for past JDKs + if ( // Explicit patch for the keyTyped sent after Alt+key + (key.getModifiers() & InputEvent.ALT_MASK) != 0 // Alt pressed + && (key.getModifiers() & InputEvent.CTRL_MASK) == 0 // Ctrl not pressed + ) { + boolean patch = true; + if (key.getKeyChar() == 0 || key.getKeyChar() == KeyEvent.CHAR_UNDEFINED) { + switch (key.getKeyCode()) { + case KeyEvent.VK_ALT: // don't patch single Alt + case KeyEvent.VK_KANJI: + case KeyEvent.VK_KATAKANA: + case KeyEvent.VK_HIRAGANA: + case KeyEvent.VK_JAPANESE_KATAKANA: + case KeyEvent.VK_JAPANESE_HIRAGANA: + case 0x0107: // KeyEvent.VK_INPUT_METHOD_ON_OFF: - in 1.3 only + case KeyEvent.VK_NUMPAD0: // Alt+NumPad keys + case KeyEvent.VK_NUMPAD1: + case KeyEvent.VK_NUMPAD2: + case KeyEvent.VK_NUMPAD3: + case KeyEvent.VK_NUMPAD4: + case KeyEvent.VK_NUMPAD5: + case KeyEvent.VK_NUMPAD6: + case KeyEvent.VK_NUMPAD7: + case KeyEvent.VK_NUMPAD8: + case KeyEvent.VK_NUMPAD9: + patch = false; + break; } + } - if (patch) { - ignoreNextTyped = true; - } - } else if ((key.getModifiers() & InputEvent.META_MASK) != 0) { // Explicit patch for the keyTyped sent after Meta+key for Mac OS X - ignoreNextTyped = true; - } else if ((key.getModifiers() & InputEvent.CTRL_MASK) != 0 && - (key.getModifiers() & InputEvent.SHIFT_MASK) != 0 && - (key.getKeyCode() == KeyEvent.VK_ADD || key.getKeyCode() == KeyEvent.VK_SUBTRACT)) { - // Explicit patch for keyTyped sent without the Ctrl+Shift modifiers on Mac OS X - see issue #39835 - ignoreNextTyped = true; + if (patch) { + ignoreNextTyped = true; } + } else if ((key.getModifiers() & InputEvent.META_MASK) != 0) { // Explicit patch for the keyTyped sent after Meta+key for Mac OS X + ignoreNextTyped = true; + } else if ((key.getModifiers() & InputEvent.CTRL_MASK) != 0 && + (key.getModifiers() & InputEvent.SHIFT_MASK) != 0 && + (key.getKeyCode() == KeyEvent.VK_ADD || key.getKeyCode() == KeyEvent.VK_SUBTRACT)) { + // Explicit patch for keyTyped sent without the Ctrl+Shift modifiers on Mac OS X - see issue #39835 + ignoreNextTyped = true; } - resetContext(); // reset context when resolved } - if (context != null && ret == null) { // no action found when in context - ret = contextKeyNotFoundAction; - } + resetContext(); // reset context when resolved + // The global context cannot be reset because + // this is just a situation when the editor keymap + // does not know but the system or other } + + if (context != null && ret == null) { // no action found when in context + // Letting to return null in order to give chance to other keymaps + // ret = contextKeyNotFoundAction; + } + } + + // Reset global context if a valid action is found + if (ret != null && !(ret instanceof KeymapSetContextAction) && (ret != EMPTY_ACTION)) { + resetGlobalContext(); } if (compatibleIgnoreNextTyped) { Index: openide/awt/src/org/openide/awt/StatusDisplayer.java =================================================================== RCS file: /cvs/openide/awt/src/org/openide/awt/StatusDisplayer.java,v retrieving revision 1.1 retrieving revision 1.1.20.2 diff -u -r1.1 -r1.1.20.2 --- openide/awt/src/org/openide/awt/StatusDisplayer.java 21 Apr 2005 20:12:31 -0000 1.1 +++ openide/awt/src/org/openide/awt/StatusDisplayer.java 30 Sep 2005 16:11:45 -0000 1.1.20.2 @@ -14,14 +14,18 @@ import org.openide.util.Lookup; +import java.awt.event.KeyEvent; import java.util.*; +import javax.swing.KeyStroke; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; -/** Permits control of a status line. +/** Permits control of a status line and keeps track of current context in case + * of multikey action bindings. * The default instance may correspond to the NetBeans status line in the main window. + * * @author Jesse Glick * @since 3.14 */ @@ -75,6 +79,57 @@ */ public abstract void removeChangeListener(ChangeListener l); + private List/**/ context = new ArrayList(); + private String lastString = ""; + + /** + * Get current multikey context, that is, a sequence of keystrokes that + * were registered using {@link shiftContext(KeyStroke)}. The context should + * match a prefix of some registered multikey binding. + * @return current context + */ + public KeyStroke[] getContext() { + return (KeyStroke[]) context.toArray(new KeyStroke[context.size()]); + } + + /** + * Appends new keystroke to the current context. Should be called by the + * active multi keymap implementation when there exists an action binding + * that has a keystroke prefix consisting of current context followed by the + * keystroke stroke. + * The method also displays the newly created context in the status line. + * + * @param stroke the new keystroke that shifts the context. + */ + public void shiftContext(KeyStroke stroke) { + context.add(stroke); + lastString = lastString + getKeyText(stroke) + " "; + setStatusText(lastString); + } + + private static String getKeyText (KeyStroke keyStroke) { + if (keyStroke == null) return ""; // NOI18N + String modifText = KeyEvent.getKeyModifiersText + (keyStroke.getModifiers ()); + if ("".equals (modifText)) // NOI18N + return KeyEvent.getKeyText (keyStroke.getKeyCode ()); + return modifText + "+" + // NOI18N + KeyEvent.getKeyText (keyStroke.getKeyCode ()); + } + + /** + * Resets the multikey context and clears the status line - displays the + * current (empty) context in the status line. Should be called by the + * active multi keymap in case it reached complete action binding or by the + * last keymap in the keymap stack in case there is no binding for current + * keystroke sequence anymore. + */ + public void resetContext() { + context.clear(); + lastString=""; + setStatusText(lastString); + } + /** * Trivial default impl for standalone usage. * @see "#32154" Index: core/src/org/netbeans/core/NbKeymap.java =================================================================== RCS file: /cvs/core/src/org/netbeans/core/NbKeymap.java,v retrieving revision 1.26 retrieving revision 1.26.8.2 diff -u -r1.26 -r1.26.8.2 --- core/src/org/netbeans/core/NbKeymap.java 28 Aug 2005 11:34:05 -0000 1.26 +++ core/src/org/netbeans/core/NbKeymap.java 21 Sep 2005 09:59:04 -0000 1.26.8.2 @@ -37,14 +37,11 @@ Action defaultAction; /** hash table to map (Action -> ArrayList of KeyStrokes) */ Map actions; + + private final Action NO_ACTION = new KeymapAction(null, null, null); - /* The keymap that is currently active */ - private Keymap active; - - private final Action NO_ACTION = new KeymapAction(null, null); - - public Action createMapAction(Keymap k, String contextName) { - return new KeymapAction(k, contextName); + public Action createMapAction(Keymap k, KeyStroke stroke, String contextName) { + return new KeymapAction(k, stroke, contextName); } /** Default constructor @@ -78,46 +75,60 @@ public Action getAction(KeyStroke key) { Action a; - - if (active != null) { - a = active.getAction(key); - if (a != null) { - // always reset immediatelly, the action may set the new ctx then - active = null; - StatusDisplayer.getDefault ().setStatusText (""); - return a; + KeyStroke[] ctx = StatusDisplayer.getDefault().getContext(); + Keymap activ = this; + for (int i=0; i reset + StatusDisplayer.getDefault().resetContext(); + } else { + StatusDisplayer.getDefault().shiftContext(stroke); + } } } } Index: core/src/org/netbeans/core/ShortcutsFolder.java =================================================================== RCS file: /cvs/core/src/org/netbeans/core/ShortcutsFolder.java,v retrieving revision 1.36 retrieving revision 1.36.2.1 diff -u -r1.36 -r1.36.2.1 --- core/src/org/netbeans/core/ShortcutsFolder.java 19 Sep 2005 10:04:39 -0000 1.36 +++ core/src/org/netbeans/core/ShortcutsFolder.java 19 Sep 2005 15:54:23 -0000 1.36.2.1 @@ -141,7 +141,7 @@ shoutcutText += " " + getKeyText (keyStrokes [i]); // NOI18N if (a == null) { a = keymap.createMapAction - (new NbKeymap.SubKeymap (null), shoutcutText); + (new NbKeymap.SubKeymap (null), keyStrokes [i], shoutcutText); currentKeymap.addActionForKeyStroke (keyStrokes [i], a); } if (!(a instanceof KeymapAction)) return; Index: core/windows/src/org/netbeans/core/windows/ShortcutAndMenuKeyEventProcessor.java =================================================================== RCS file: /cvs/core/windows/src/org/netbeans/core/windows/ShortcutAndMenuKeyEventProcessor.java,v retrieving revision 1.14 retrieving revision 1.14.4.3 diff -u -r1.14 -r1.14.4.3 --- core/windows/src/org/netbeans/core/windows/ShortcutAndMenuKeyEventProcessor.java 9 Sep 2005 11:56:53 -0000 1.14 +++ core/windows/src/org/netbeans/core/windows/ShortcutAndMenuKeyEventProcessor.java 29 Sep 2005 15:38:26 -0000 1.14.4.3 @@ -17,6 +17,7 @@ import java.util.Set; import org.netbeans.core.windows.view.ui.KeyboardPopupSwitcher; import org.openide.actions.ActionManager; +import org.openide.awt.StatusDisplayer; import org.openide.util.Lookup; import org.openide.util.Utilities; @@ -114,6 +115,7 @@ private int lastModifiers; private char lastKeyChar; private boolean lastSampled = false; + private boolean skipNextTyped = false; public boolean postProcessKeyEvent(KeyEvent ev) { if (ev.isConsumed()) @@ -152,7 +154,20 @@ ev.setModifiers(mods); } } - + + // in some ctx, may need event filtering + if (StatusDisplayer.getDefault().getContext().length != 0) { + 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 + skipNextTyped = true; + return true; + } + } + if (ev.getID() == KeyEvent.KEY_PRESSED && ev.getModifiers() == (InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK) && (ev.getKeyCode() == KeyEvent.VK_PAUSE @@ -167,9 +182,23 @@ ev.consume(); return true; } + + + // multi-shortcut in middle + if (ev.getID() == KeyEvent.KEY_TYPED && skipNextTyped) { + ev.consume(); + skipNextTyped = false; + return true; + } + + skipNextTyped = false; + +// if (ev.getID() == KeyEvent.KEY_PRESSED && StatusDisplayer.getDefault().getContext().length != 0) { +// skipNextTyped = true; +// } if (ev.getID() == KeyEvent.KEY_PRESSED) { - // decompose to primitive fields to avoid memory profiler confusion (keyEevnt keeps source reference) + // decompose to primitive fields to avoid memory profiler confusion (keyEvent keeps source reference) lastKeyChar = ev.getKeyChar(); lastModifiers = ev.getModifiers(); lastSampled = true; @@ -242,7 +271,7 @@ // don't let action keystrokes to propagate from both // modal and nonmodal dialogs - if (!isTransmodalAction(ks) && (w instanceof Dialog)) { + if ((w instanceof Dialog) && !isTransmodalAction(ks)) { return false; }