blob: d6395accd001d6cc4a0000efff38ed60abedfb6c [file] [log] [blame]
/*
* Copyright (c) 2011, 2014, Oracle and/or its affiliates. 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.event.ActionEvent;
import java.util.*;
import javax.swing.*;
import javax.swing.UIDefaults.LazyValue;
import javax.swing.text.*;
import javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction;
import com.apple.laf.AquaUtils.RecyclableSingleton;
import com.apple.laf.AquaUtils.RecyclableSingletonFromDefaultConstructor;
public class AquaKeyBindings {
static final RecyclableSingleton<AquaKeyBindings> instance = new RecyclableSingletonFromDefaultConstructor<AquaKeyBindings>(AquaKeyBindings.class);
static AquaKeyBindings instance() {
return instance.get();
}
final DefaultKeyTypedAction defaultKeyTypedAction = new DefaultKeyTypedAction();
void setDefaultAction(final String keymapName) {
final javax.swing.text.Keymap map = JTextComponent.getKeymap(keymapName);
map.setDefaultAction(defaultKeyTypedAction);
}
static final String upMultilineAction = "aqua-move-up";
static final String downMultilineAction = "aqua-move-down";
static final String pageUpMultiline = "aqua-page-up";
static final String pageDownMultiline = "aqua-page-down";
final String[] commonTextEditorBindings = {
"ENTER", JTextField.notifyAction,
"COPY", DefaultEditorKit.copyAction,
"CUT", DefaultEditorKit.cutAction,
"PASTE", DefaultEditorKit.pasteAction,
"meta A", DefaultEditorKit.selectAllAction,
"meta C", DefaultEditorKit.copyAction,
"meta V", DefaultEditorKit.pasteAction,
"meta X", DefaultEditorKit.cutAction,
"meta BACK_SLASH", "unselect",
"DELETE", DefaultEditorKit.deleteNextCharAction,
"alt DELETE", "delete-next-word",
"BACK_SPACE", DefaultEditorKit.deletePrevCharAction,
"alt BACK_SPACE", "delete-previous-word",
"LEFT", DefaultEditorKit.backwardAction,
"KP_LEFT", DefaultEditorKit.backwardAction,
"RIGHT", DefaultEditorKit.forwardAction,
"KP_RIGHT", DefaultEditorKit.forwardAction,
"shift LEFT", DefaultEditorKit.selectionBackwardAction,
"shift KP_LEFT", DefaultEditorKit.selectionBackwardAction,
"shift RIGHT", DefaultEditorKit.selectionForwardAction,
"shift KP_RIGHT", DefaultEditorKit.selectionForwardAction,
"meta LEFT", DefaultEditorKit.beginLineAction,
"meta KP_LEFT", DefaultEditorKit.beginLineAction,
"meta RIGHT", DefaultEditorKit.endLineAction,
"meta KP_RIGHT", DefaultEditorKit.endLineAction,
"shift meta LEFT", DefaultEditorKit.selectionBeginLineAction,
"shift meta KP_LEFT", DefaultEditorKit.selectionBeginLineAction,
"shift meta RIGHT", DefaultEditorKit.selectionEndLineAction,
"shift meta KP_RIGHT", DefaultEditorKit.selectionEndLineAction,
"alt LEFT", DefaultEditorKit.previousWordAction,
"alt KP_LEFT", DefaultEditorKit.previousWordAction,
"alt RIGHT", DefaultEditorKit.nextWordAction,
"alt KP_RIGHT", DefaultEditorKit.nextWordAction,
"shift alt LEFT", DefaultEditorKit.selectionPreviousWordAction,
"shift alt KP_LEFT", DefaultEditorKit.selectionPreviousWordAction,
"shift alt RIGHT", DefaultEditorKit.selectionNextWordAction,
"shift alt KP_RIGHT", DefaultEditorKit.selectionNextWordAction,
"control A", DefaultEditorKit.beginLineAction,
"control B", DefaultEditorKit.backwardAction,
"control D", DefaultEditorKit.deleteNextCharAction,
"control E", DefaultEditorKit.endLineAction,
"control F", DefaultEditorKit.forwardAction,
"control H", DefaultEditorKit.deletePrevCharAction,
"control W", "delete-previous-word",
"control shift O", "toggle-componentOrientation",
"END", DefaultEditorKit.endAction,
"HOME", DefaultEditorKit.beginAction,
"shift END", DefaultEditorKit.selectionEndAction,
"shift HOME", DefaultEditorKit.selectionBeginAction,
"PAGE_DOWN", pageDownMultiline,
"PAGE_UP", pageUpMultiline,
"shift PAGE_DOWN", "selection-page-down",
"shift PAGE_UP", "selection-page-up",
"meta shift PAGE_DOWN", "selection-page-right",
"meta shift PAGE_UP", "selection-page-left",
"meta DOWN", DefaultEditorKit.endAction,
"meta KP_DOWN", DefaultEditorKit.endAction,
"meta UP", DefaultEditorKit.beginAction,
"meta KP_UP", DefaultEditorKit.beginAction,
"shift meta DOWN", DefaultEditorKit.selectionEndAction,
"shift meta KP_DOWN", DefaultEditorKit.selectionEndAction,
"shift meta UP", DefaultEditorKit.selectionBeginAction,
"shift meta KP_UP", DefaultEditorKit.selectionBeginAction,
};
LateBoundInputMap getTextFieldInputMap() {
return new LateBoundInputMap(new SimpleBinding(commonTextEditorBindings), new SimpleBinding(new String[] {
"DOWN", DefaultEditorKit.endLineAction,
"KP_DOWN", DefaultEditorKit.endLineAction,
"UP", DefaultEditorKit.beginLineAction,
"KP_UP", DefaultEditorKit.beginLineAction,
"shift DOWN", DefaultEditorKit.selectionEndLineAction,
"shift KP_DOWN", DefaultEditorKit.selectionEndLineAction,
"shift UP", DefaultEditorKit.selectionBeginLineAction,
"shift KP_UP", DefaultEditorKit.selectionBeginLineAction,
"control P", DefaultEditorKit.beginAction,
"control N", DefaultEditorKit.endAction,
"control V", DefaultEditorKit.endAction,
}));
}
LateBoundInputMap getPasswordFieldInputMap() {
return new LateBoundInputMap(new SimpleBinding(getTextFieldInputMap().getBindings()),
// nullify all the bindings that may discover space characters in the text
new SimpleBinding(new String[] {
"alt LEFT", null,
"alt KP_LEFT", null,
"alt RIGHT", null,
"alt KP_RIGHT", null,
"shift alt LEFT", null,
"shift alt KP_LEFT", null,
"shift alt RIGHT", null,
"shift alt KP_RIGHT", null,
}));
}
LateBoundInputMap getMultiLineTextInputMap() {
return new LateBoundInputMap(new SimpleBinding(commonTextEditorBindings), new SimpleBinding(new String[] {
"ENTER", DefaultEditorKit.insertBreakAction,
"DOWN", downMultilineAction,
"KP_DOWN", downMultilineAction,
"UP", upMultilineAction,
"KP_UP", upMultilineAction,
"shift DOWN", DefaultEditorKit.selectionDownAction,
"shift KP_DOWN", DefaultEditorKit.selectionDownAction,
"shift UP", DefaultEditorKit.selectionUpAction,
"shift KP_UP", DefaultEditorKit.selectionUpAction,
"alt shift DOWN", DefaultEditorKit.selectionEndParagraphAction,
"alt shift KP_DOWN", DefaultEditorKit.selectionEndParagraphAction,
"alt shift UP", DefaultEditorKit.selectionBeginParagraphAction,
"alt shift KP_UP", DefaultEditorKit.selectionBeginParagraphAction,
"control P", DefaultEditorKit.upAction,
"control N", DefaultEditorKit.downAction,
"control V", pageDownMultiline,
"TAB", DefaultEditorKit.insertTabAction,
"meta SPACE", "activate-link-action",
"meta T", "next-link-action",
"meta shift T", "previous-link-action",
"END", DefaultEditorKit.endAction,
"HOME", DefaultEditorKit.beginAction,
"shift END", DefaultEditorKit.selectionEndAction,
"shift HOME", DefaultEditorKit.selectionBeginAction,
"PAGE_DOWN", pageDownMultiline,
"PAGE_UP", pageUpMultiline,
"shift PAGE_DOWN", "selection-page-down",
"shift PAGE_UP", "selection-page-up",
"meta shift PAGE_DOWN", "selection-page-right",
"meta shift PAGE_UP", "selection-page-left",
}));
}
LateBoundInputMap getFormattedTextFieldInputMap() {
return new LateBoundInputMap(getTextFieldInputMap(), new SimpleBinding(new String[] {
"UP", "increment",
"KP_UP", "increment",
"DOWN", "decrement",
"KP_DOWN", "decrement",
"ESCAPE", "reset-field-edit",
}));
}
LateBoundInputMap getComboBoxInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"ESCAPE", "aquaHidePopup",
"PAGE_UP", "aquaSelectPageUp",
"PAGE_DOWN", "aquaSelectPageDown",
"HOME", "aquaSelectHome",
"END", "aquaSelectEnd",
"ENTER", "enterPressed",
"UP", "aquaSelectPrevious",
"KP_UP", "aquaSelectPrevious",
"DOWN", "aquaSelectNext",
"KP_DOWN", "aquaSelectNext",
"SPACE", "aquaSpacePressed" // "spacePopup"
}));
}
LateBoundInputMap getListInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"meta C", "copy",
"meta V", "paste",
"meta X", "cut",
"COPY", "copy",
"PASTE", "paste",
"CUT", "cut",
"UP", "selectPreviousRow",
"KP_UP", "selectPreviousRow",
"shift UP", "selectPreviousRowExtendSelection",
"shift KP_UP", "selectPreviousRowExtendSelection",
"DOWN", "selectNextRow",
"KP_DOWN", "selectNextRow",
"shift DOWN", "selectNextRowExtendSelection",
"shift KP_DOWN", "selectNextRowExtendSelection",
"LEFT", "selectPreviousColumn",
"KP_LEFT", "selectPreviousColumn",
"shift LEFT", "selectPreviousColumnExtendSelection",
"shift KP_LEFT", "selectPreviousColumnExtendSelection",
"RIGHT", "selectNextColumn",
"KP_RIGHT", "selectNextColumn",
"shift RIGHT", "selectNextColumnExtendSelection",
"shift KP_RIGHT", "selectNextColumnExtendSelection",
"meta A", "selectAll",
// aquaHome and aquaEnd are new actions that just move the view so the first or last item is visible.
"HOME", "aquaHome",
"shift HOME", "selectFirstRowExtendSelection",
"END", "aquaEnd",
"shift END", "selectLastRowExtendSelection",
// Unmodified PAGE_UP and PAGE_DOWN are handled by their scroll pane, if any.
"shift PAGE_UP", "scrollUpExtendSelection",
"shift PAGE_DOWN", "scrollDownExtendSelection"
}));
}
LateBoundInputMap getScrollBarInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"RIGHT", "positiveUnitIncrement",
"KP_RIGHT", "positiveUnitIncrement",
"DOWN", "positiveUnitIncrement",
"KP_DOWN", "positiveUnitIncrement",
"PAGE_DOWN", "positiveBlockIncrement",
"LEFT", "negativeUnitIncrement",
"KP_LEFT", "negativeUnitIncrement",
"UP", "negativeUnitIncrement",
"KP_UP", "negativeUnitIncrement",
"PAGE_UP", "negativeBlockIncrement",
"HOME", "minScroll",
"END", "maxScroll"
}));
}
LateBoundInputMap getScrollBarRightToLeftInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"RIGHT", "negativeUnitIncrement",
"KP_RIGHT", "negativeUnitIncrement",
"LEFT", "positiveUnitIncrement",
"KP_LEFT", "positiveUnitIncrement"
}));
}
LateBoundInputMap getScrollPaneInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"RIGHT", "unitScrollRight",
"KP_RIGHT", "unitScrollRight",
"DOWN", "unitScrollDown",
"KP_DOWN", "unitScrollDown",
"LEFT", "unitScrollLeft",
"KP_LEFT", "unitScrollLeft",
"UP", "unitScrollUp",
"KP_UP", "unitScrollUp",
"PAGE_UP", "scrollUp",
"PAGE_DOWN", "scrollDown",
"HOME", "scrollHome",
"END", "scrollEnd"
}));
}
LateBoundInputMap getSliderInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"RIGHT", "positiveUnitIncrement",
"KP_RIGHT", "positiveUnitIncrement",
"DOWN", "negativeUnitIncrement",
"KP_DOWN", "negativeUnitIncrement",
"PAGE_DOWN", "negativeBlockIncrement",
"LEFT", "negativeUnitIncrement",
"KP_LEFT", "negativeUnitIncrement",
"UP", "positiveUnitIncrement",
"KP_UP", "positiveUnitIncrement",
"PAGE_UP", "positiveBlockIncrement",
"HOME", "minScroll",
"END", "maxScroll"
}));
}
LateBoundInputMap getSliderRightToLeftInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"RIGHT", "negativeUnitIncrement",
"KP_RIGHT", "negativeUnitIncrement",
"LEFT", "positiveUnitIncrement",
"KP_LEFT", "positiveUnitIncrement"
}));
}
LateBoundInputMap getSpinnerInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"UP", "increment",
"KP_UP", "increment",
"DOWN", "decrement",
"KP_DOWN", "decrement"
}));
}
LateBoundInputMap getTableInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"meta C", "copy",
"meta V", "paste",
"meta X", "cut",
"COPY", "copy",
"PASTE", "paste",
"CUT", "cut",
"RIGHT", "selectNextColumn",
"KP_RIGHT", "selectNextColumn",
"LEFT", "selectPreviousColumn",
"KP_LEFT", "selectPreviousColumn",
"DOWN", "selectNextRow",
"KP_DOWN", "selectNextRow",
"UP", "selectPreviousRow",
"KP_UP", "selectPreviousRow",
"shift RIGHT", "selectNextColumnExtendSelection",
"shift KP_RIGHT", "selectNextColumnExtendSelection",
"shift LEFT", "selectPreviousColumnExtendSelection",
"shift KP_LEFT", "selectPreviousColumnExtendSelection",
"shift DOWN", "selectNextRowExtendSelection",
"shift KP_DOWN", "selectNextRowExtendSelection",
"shift UP", "selectPreviousRowExtendSelection",
"shift KP_UP", "selectPreviousRowExtendSelection",
"PAGE_UP", "scrollUpChangeSelection",
"PAGE_DOWN", "scrollDownChangeSelection",
"HOME", "selectFirstColumn",
"END", "selectLastColumn",
"shift PAGE_UP", "scrollUpExtendSelection",
"shift PAGE_DOWN", "scrollDownExtendSelection",
"shift HOME", "selectFirstColumnExtendSelection",
"shift END", "selectLastColumnExtendSelection",
"TAB", "selectNextColumnCell",
"shift TAB", "selectPreviousColumnCell",
"meta A", "selectAll",
"ESCAPE", "cancel",
"ENTER", "selectNextRowCell",
"shift ENTER", "selectPreviousRowCell",
"alt TAB", "focusHeader",
"alt shift TAB", "focusHeader"
}));
}
LateBoundInputMap getTableRightToLeftInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"RIGHT", "selectPreviousColumn",
"KP_RIGHT", "selectPreviousColumn",
"LEFT", "selectNextColumn",
"KP_LEFT", "selectNextColumn",
"shift RIGHT", "selectPreviousColumnExtendSelection",
"shift KP_RIGHT", "selectPreviousColumnExtendSelection",
"shift LEFT", "selectNextColumnExtendSelection",
"shift KP_LEFT", "selectNextColumnExtendSelection",
"ctrl PAGE_UP", "scrollRightChangeSelection",
"ctrl PAGE_DOWN", "scrollLeftChangeSelection",
"ctrl shift PAGE_UP", "scrollRightExtendSelection",
"ctrl shift PAGE_DOWN", "scrollLeftExtendSelection"
}));
}
LateBoundInputMap getTreeInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"meta C", "copy",
"meta V", "paste",
"meta X", "cut",
"COPY", "copy",
"PASTE", "paste",
"CUT", "cut",
"UP", "selectPrevious",
"KP_UP", "selectPrevious",
"shift UP", "selectPreviousExtendSelection",
"shift KP_UP", "selectPreviousExtendSelection",
"DOWN", "selectNext",
"KP_DOWN", "selectNext",
"shift DOWN", "selectNextExtendSelection",
"shift KP_DOWN", "selectNextExtendSelection",
"RIGHT", "aquaExpandNode",
"KP_RIGHT", "aquaExpandNode",
"LEFT", "aquaCollapseNode",
"KP_LEFT", "aquaCollapseNode",
"shift RIGHT", "aquaExpandNode",
"shift KP_RIGHT", "aquaExpandNode",
"shift LEFT", "aquaCollapseNode",
"shift KP_LEFT", "aquaCollapseNode",
"ctrl LEFT", "aquaCollapseNode",
"ctrl KP_LEFT", "aquaCollapseNode",
"ctrl RIGHT", "aquaExpandNode",
"ctrl KP_RIGHT", "aquaExpandNode",
"alt RIGHT", "aquaFullyExpandNode",
"alt KP_RIGHT", "aquaFullyExpandNode",
"alt LEFT", "aquaFullyCollapseNode",
"alt KP_LEFT", "aquaFullyCollapseNode",
"meta A", "selectAll",
"RETURN", "startEditing"
}));
}
LateBoundInputMap getTreeRightToLeftInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"RIGHT", "aquaCollapseNode",
"KP_RIGHT", "aquaCollapseNode",
"LEFT", "aquaExpandNode",
"KP_LEFT", "aquaExpandNode",
"shift RIGHT", "aquaCollapseNode",
"shift KP_RIGHT", "aquaCollapseNode",
"shift LEFT", "aquaExpandNode",
"shift KP_LEFT", "aquaExpandNode",
"ctrl LEFT", "aquaExpandNode",
"ctrl KP_LEFT", "aquaExpandNode",
"ctrl RIGHT", "aquaCollapseNode",
"ctrl KP_RIGHT", "aquaCollapseNode"
}));
}
// common interface between a string array, and a dynamic provider of string arrays ;-)
interface BindingsProvider {
public String[] getBindings();
}
// wraps basic string arrays
static class SimpleBinding implements BindingsProvider {
final String[] bindings;
public SimpleBinding(final String[] bindings) { this.bindings = bindings; }
public String[] getBindings() { return bindings; }
}
// patches all providers together at the moment the UIManager needs the real InputMap
static class LateBoundInputMap implements LazyValue, BindingsProvider {
private final BindingsProvider[] providerList;
private String[] mergedBindings;
public LateBoundInputMap(final BindingsProvider ... providerList) {
this.providerList = providerList;
}
public Object createValue(final UIDefaults table) {
return LookAndFeel.makeInputMap(getBindings());
}
public String[] getBindings() {
if (mergedBindings != null) return mergedBindings;
final String[][] bindingsList = new String[providerList.length][];
int size = 0;
for (int i = 0; i < providerList.length; i++) {
bindingsList[i] = providerList[i].getBindings();
size += bindingsList[i].length;
}
if (bindingsList.length == 1) {
return mergedBindings = bindingsList[0];
}
final ArrayList<String> unifiedList = new ArrayList<String>(size);
Collections.addAll(unifiedList, bindingsList[0]); // System.arrayCopy() the first set
for (int i = 1; i < providerList.length; i++) {
mergeBindings(unifiedList, bindingsList[i]);
}
return mergedBindings = unifiedList.toArray(new String[unifiedList.size()]);
}
static void mergeBindings(final ArrayList<String> unifiedList, final String[] overrides) {
for (int i = 0; i < overrides.length; i+=2) {
final String key = overrides[i];
final String value = overrides[i+1];
final int keyIndex = unifiedList.indexOf(key);
if (keyIndex == -1) {
unifiedList.add(key);
unifiedList.add(value);
} else {
unifiedList.set(keyIndex, key);
unifiedList.set(keyIndex + 1, value);
}
}
}
}
void installAquaUpDownActions(final JTextComponent component) {
final ActionMap actionMap = component.getActionMap();
actionMap.put(upMultilineAction, moveUpMultilineAction);
actionMap.put(downMultilineAction, moveDownMultilineAction);
actionMap.put(pageUpMultiline, pageUpMultilineAction);
actionMap.put(pageDownMultiline, pageDownMultilineAction);
}
// extracted and adapted from DefaultEditorKit in 1.6
@SuppressWarnings("serial") // Superclass is not serializable across versions
abstract static class DeleteWordAction extends TextAction {
public DeleteWordAction(final String name) { super(name); }
public void actionPerformed(final ActionEvent e) {
if (e == null) return;
final JTextComponent target = getTextComponent(e);
if (target == null) return;
if (!target.isEditable() || !target.isEnabled()) {
UIManager.getLookAndFeel().provideErrorFeedback(target);
return;
}
try {
final int start = target.getSelectionStart();
final Element line = Utilities.getParagraphElement(target, start);
final int end = getEnd(target, line, start);
final int offs = Math.min(start, end);
final int len = Math.abs(end - start);
if (offs >= 0) {
target.getDocument().remove(offs, len);
return;
}
} catch (final BadLocationException ignore) {}
UIManager.getLookAndFeel().provideErrorFeedback(target);
}
abstract int getEnd(final JTextComponent target, final Element line, final int start) throws BadLocationException;
}
final TextAction moveUpMultilineAction = new AquaMultilineAction(upMultilineAction, DefaultEditorKit.upAction, DefaultEditorKit.beginAction);
final TextAction moveDownMultilineAction = new AquaMultilineAction(downMultilineAction, DefaultEditorKit.downAction, DefaultEditorKit.endAction);
final TextAction pageUpMultilineAction = new AquaMultilineAction(pageUpMultiline, DefaultEditorKit.pageUpAction, DefaultEditorKit.beginAction);
final TextAction pageDownMultilineAction = new AquaMultilineAction(pageDownMultiline, DefaultEditorKit.pageDownAction, DefaultEditorKit.endAction);
@SuppressWarnings("serial") // Superclass is not serializable across versions
static class AquaMultilineAction extends TextAction {
final String targetActionName;
final String proxyActionName;
public AquaMultilineAction(final String actionName, final String targetActionName, final String proxyActionName) {
super(actionName);
this.targetActionName = targetActionName;
this.proxyActionName = proxyActionName;
}
public void actionPerformed(final ActionEvent e) {
final JTextComponent c = getTextComponent(e);
final ActionMap actionMap = c.getActionMap();
final Action targetAction = actionMap.get(targetActionName);
final int startPosition = c.getCaretPosition();
targetAction.actionPerformed(e);
if (startPosition != c.getCaretPosition()) return;
final Action proxyAction = actionMap.get(proxyActionName);
proxyAction.actionPerformed(e);
}
}
}