1 /*
   2  * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.apple.laf;
  27 
  28 import java.awt.event.ActionEvent;
  29 import java.util.*;
  30 
  31 import javax.swing.*;
  32 import javax.swing.UIDefaults.LazyValue;
  33 import javax.swing.text.*;
  34 import javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction;
  35 
  36 import com.apple.laf.AquaUtils.RecyclableSingleton;
  37 import com.apple.laf.AquaUtils.RecyclableSingletonFromDefaultConstructor;
  38 
  39 public class AquaKeyBindings {
  40     private static final RecyclableSingleton<AquaKeyBindings> instance = new RecyclableSingletonFromDefaultConstructor<AquaKeyBindings>(AquaKeyBindings.class);
  41     static AquaKeyBindings instance() {
  42         return instance.get();
  43     }
  44 
  45     final DefaultKeyTypedAction defaultKeyTypedAction = new DefaultKeyTypedAction();
  46     void setDefaultAction(final String keymapName) {
  47         final javax.swing.text.Keymap map = JTextComponent.getKeymap(keymapName);
  48         map.setDefaultAction(defaultKeyTypedAction);
  49     }
  50 
  51     static final String upMultilineAction = "aqua-move-up";
  52     static final String downMultilineAction = "aqua-move-down";
  53     static final String pageUpMultiline = "aqua-page-up";
  54     static final String pageDownMultiline = "aqua-page-down";
  55 
  56     final String[] commonTextEditorBindings = {
  57         "ENTER", JTextField.notifyAction,
  58         "COPY", DefaultEditorKit.copyAction,
  59         "CUT", DefaultEditorKit.cutAction,
  60         "PASTE", DefaultEditorKit.pasteAction,
  61         "meta A", DefaultEditorKit.selectAllAction,
  62         "meta C", DefaultEditorKit.copyAction,
  63         "meta V", DefaultEditorKit.pasteAction,
  64         "meta X", DefaultEditorKit.cutAction,
  65         "meta BACK_SLASH", "unselect",
  66 
  67         "DELETE", DefaultEditorKit.deleteNextCharAction,
  68         "alt DELETE", "delete-next-word",
  69         "BACK_SPACE", DefaultEditorKit.deletePrevCharAction,
  70         "shift BACK_SPACE", DefaultEditorKit.deletePrevCharAction,
  71         "alt BACK_SPACE", "delete-previous-word",
  72 
  73         "LEFT", DefaultEditorKit.backwardAction,
  74         "KP_LEFT", DefaultEditorKit.backwardAction,
  75         "RIGHT", DefaultEditorKit.forwardAction,
  76         "KP_RIGHT", DefaultEditorKit.forwardAction,
  77         "shift LEFT", DefaultEditorKit.selectionBackwardAction,
  78         "shift KP_LEFT", DefaultEditorKit.selectionBackwardAction,
  79         "shift RIGHT", DefaultEditorKit.selectionForwardAction,
  80         "shift KP_RIGHT", DefaultEditorKit.selectionForwardAction,
  81         "meta LEFT", DefaultEditorKit.beginLineAction,
  82         "meta KP_LEFT", DefaultEditorKit.beginLineAction,
  83         "meta RIGHT", DefaultEditorKit.endLineAction,
  84         "meta KP_RIGHT", DefaultEditorKit.endLineAction,
  85         "shift meta LEFT", DefaultEditorKit.selectionBeginLineAction,
  86         "shift meta KP_LEFT", DefaultEditorKit.selectionBeginLineAction,
  87         "shift meta RIGHT", DefaultEditorKit.selectionEndLineAction,
  88         "shift meta KP_RIGHT", DefaultEditorKit.selectionEndLineAction,
  89         "alt LEFT", DefaultEditorKit.previousWordAction,
  90         "alt KP_LEFT", DefaultEditorKit.previousWordAction,
  91         "alt RIGHT", DefaultEditorKit.nextWordAction,
  92         "alt KP_RIGHT", DefaultEditorKit.nextWordAction,
  93         "shift alt LEFT", DefaultEditorKit.selectionPreviousWordAction,
  94         "shift alt KP_LEFT", DefaultEditorKit.selectionPreviousWordAction,
  95         "shift alt RIGHT", DefaultEditorKit.selectionNextWordAction,
  96         "shift alt KP_RIGHT", DefaultEditorKit.selectionNextWordAction,
  97 
  98         "control A", DefaultEditorKit.beginLineAction,
  99         "control B", DefaultEditorKit.backwardAction,
 100         "control D", DefaultEditorKit.deleteNextCharAction,
 101         "control E", DefaultEditorKit.endLineAction,
 102         "control F", DefaultEditorKit.forwardAction,
 103         "control H", DefaultEditorKit.deletePrevCharAction,
 104         "control W", "delete-previous-word",
 105         "control shift O", "toggle-componentOrientation",
 106 
 107         "END", DefaultEditorKit.endAction,
 108         "HOME", DefaultEditorKit.beginAction,
 109         "shift END", DefaultEditorKit.selectionEndAction,
 110         "shift HOME", DefaultEditorKit.selectionBeginAction,
 111 
 112         "PAGE_DOWN", pageDownMultiline,
 113         "PAGE_UP", pageUpMultiline,
 114         "shift PAGE_DOWN", "selection-page-down",
 115         "shift PAGE_UP", "selection-page-up",
 116         "meta shift PAGE_DOWN", "selection-page-right",
 117         "meta shift PAGE_UP", "selection-page-left",
 118 
 119         "meta DOWN", DefaultEditorKit.endAction,
 120         "meta KP_DOWN", DefaultEditorKit.endAction,
 121         "meta UP", DefaultEditorKit.beginAction,
 122         "meta KP_UP", DefaultEditorKit.beginAction,
 123         "shift meta DOWN", DefaultEditorKit.selectionEndAction,
 124         "shift meta KP_DOWN", DefaultEditorKit.selectionEndAction,
 125         "shift meta UP", DefaultEditorKit.selectionBeginAction,
 126         "shift meta KP_UP", DefaultEditorKit.selectionBeginAction,
 127     };
 128 
 129     LateBoundInputMap getTextFieldInputMap() {
 130         return new LateBoundInputMap(new SimpleBinding(commonTextEditorBindings), new SimpleBinding(new String[] {
 131             "DOWN", DefaultEditorKit.endLineAction,
 132             "KP_DOWN", DefaultEditorKit.endLineAction,
 133             "UP", DefaultEditorKit.beginLineAction,
 134             "KP_UP", DefaultEditorKit.beginLineAction,
 135             "shift DOWN", DefaultEditorKit.selectionEndLineAction,
 136             "shift KP_DOWN", DefaultEditorKit.selectionEndLineAction,
 137             "shift UP", DefaultEditorKit.selectionBeginLineAction,
 138             "shift KP_UP", DefaultEditorKit.selectionBeginLineAction,
 139 
 140             "control P", DefaultEditorKit.beginAction,
 141             "control N", DefaultEditorKit.endAction,
 142             "control V", DefaultEditorKit.endAction,
 143         }));
 144     }
 145 
 146     LateBoundInputMap getPasswordFieldInputMap() {
 147         return new LateBoundInputMap(new SimpleBinding(getTextFieldInputMap().getBindings()),
 148                 // nullify all the bindings that may discover space characters in the text
 149                 new SimpleBinding(new String[] {
 150                         "alt LEFT", null,
 151                         "alt KP_LEFT", null,
 152                         "alt RIGHT", null,
 153                         "alt KP_RIGHT", null,
 154                         "shift alt LEFT", null,
 155                         "shift alt KP_LEFT", null,
 156                         "shift alt RIGHT", null,
 157                         "shift alt KP_RIGHT", null,
 158                 }));
 159     }
 160 
 161     LateBoundInputMap getMultiLineTextInputMap() {
 162         return new LateBoundInputMap(new SimpleBinding(commonTextEditorBindings), new SimpleBinding(new String[] {
 163             "ENTER", DefaultEditorKit.insertBreakAction,
 164             "DOWN", downMultilineAction,
 165             "KP_DOWN", downMultilineAction,
 166             "UP", upMultilineAction,
 167             "KP_UP", upMultilineAction,
 168             "shift DOWN", DefaultEditorKit.selectionDownAction,
 169             "shift KP_DOWN", DefaultEditorKit.selectionDownAction,
 170             "shift UP", DefaultEditorKit.selectionUpAction,
 171             "shift KP_UP", DefaultEditorKit.selectionUpAction,
 172             "alt shift DOWN", DefaultEditorKit.selectionEndParagraphAction,
 173             "alt shift KP_DOWN", DefaultEditorKit.selectionEndParagraphAction,
 174             "alt shift UP", DefaultEditorKit.selectionBeginParagraphAction,
 175             "alt shift KP_UP", DefaultEditorKit.selectionBeginParagraphAction,
 176 
 177             "control P", DefaultEditorKit.upAction,
 178             "control N", DefaultEditorKit.downAction,
 179             "control V", pageDownMultiline,
 180 
 181             "TAB", DefaultEditorKit.insertTabAction,
 182             "meta SPACE", "activate-link-action",
 183             "meta T", "next-link-action",
 184             "meta shift T", "previous-link-action",
 185 
 186             "END", DefaultEditorKit.endAction,
 187             "HOME", DefaultEditorKit.beginAction,
 188             "shift END", DefaultEditorKit.selectionEndAction,
 189             "shift HOME", DefaultEditorKit.selectionBeginAction,
 190 
 191             "PAGE_DOWN", pageDownMultiline,
 192             "PAGE_UP", pageUpMultiline,
 193             "shift PAGE_DOWN", "selection-page-down",
 194             "shift PAGE_UP", "selection-page-up",
 195             "meta shift PAGE_DOWN", "selection-page-right",
 196             "meta shift PAGE_UP", "selection-page-left",
 197         }));
 198     }
 199 
 200     LateBoundInputMap getFormattedTextFieldInputMap() {
 201         return new LateBoundInputMap(getTextFieldInputMap(), new SimpleBinding(new String[] {
 202             "UP", "increment",
 203             "KP_UP", "increment",
 204             "DOWN", "decrement",
 205             "KP_DOWN", "decrement",
 206 
 207             "ESCAPE", "reset-field-edit",
 208         }));
 209     }
 210 
 211     LateBoundInputMap getComboBoxInputMap() {
 212         return new LateBoundInputMap(new SimpleBinding(new String[] {
 213             "ESCAPE", "aquaHidePopup",
 214             "PAGE_UP", "aquaSelectPageUp",
 215             "PAGE_DOWN", "aquaSelectPageDown",
 216             "HOME", "aquaSelectHome",
 217             "END", "aquaSelectEnd",
 218             "ENTER", "enterPressed",
 219             "UP", "aquaSelectPrevious",
 220             "KP_UP", "aquaSelectPrevious",
 221             "DOWN", "aquaSelectNext",
 222             "KP_DOWN", "aquaSelectNext",
 223             "SPACE", "aquaSpacePressed" // "spacePopup"
 224         }));
 225     }
 226 
 227     LateBoundInputMap getListInputMap() {
 228         return new LateBoundInputMap(new SimpleBinding(new String[] {
 229             "meta C", "copy",
 230             "meta V", "paste",
 231             "meta X", "cut",
 232             "COPY", "copy",
 233             "PASTE", "paste",
 234             "CUT", "cut",
 235             "UP", "selectPreviousRow",
 236             "KP_UP", "selectPreviousRow",
 237             "shift UP", "selectPreviousRowExtendSelection",
 238             "shift KP_UP", "selectPreviousRowExtendSelection",
 239             "DOWN", "selectNextRow",
 240             "KP_DOWN", "selectNextRow",
 241             "shift DOWN", "selectNextRowExtendSelection",
 242             "shift KP_DOWN", "selectNextRowExtendSelection",
 243             "LEFT", "selectPreviousColumn",
 244             "KP_LEFT", "selectPreviousColumn",
 245             "shift LEFT", "selectPreviousColumnExtendSelection",
 246             "shift KP_LEFT", "selectPreviousColumnExtendSelection",
 247             "RIGHT", "selectNextColumn",
 248             "KP_RIGHT", "selectNextColumn",
 249             "shift RIGHT", "selectNextColumnExtendSelection",
 250             "shift KP_RIGHT", "selectNextColumnExtendSelection",
 251             "meta A", "selectAll",
 252 
 253             // aquaHome and aquaEnd are new actions that just move the view so the first or last item is visible.
 254             "HOME", "aquaHome",
 255             "shift HOME", "selectFirstRowExtendSelection",
 256             "END", "aquaEnd",
 257             "shift END", "selectLastRowExtendSelection",
 258 
 259             // Unmodified PAGE_UP and PAGE_DOWN are handled by their scroll pane, if any.
 260             "shift PAGE_UP", "scrollUpExtendSelection",
 261             "shift PAGE_DOWN", "scrollDownExtendSelection"
 262         }));
 263     }
 264 
 265     LateBoundInputMap getScrollBarInputMap() {
 266         return new LateBoundInputMap(new SimpleBinding(new String[] {
 267             "RIGHT", "positiveUnitIncrement",
 268             "KP_RIGHT", "positiveUnitIncrement",
 269             "DOWN", "positiveUnitIncrement",
 270             "KP_DOWN", "positiveUnitIncrement",
 271             "PAGE_DOWN", "positiveBlockIncrement",
 272             "LEFT", "negativeUnitIncrement",
 273             "KP_LEFT", "negativeUnitIncrement",
 274             "UP", "negativeUnitIncrement",
 275             "KP_UP", "negativeUnitIncrement",
 276             "PAGE_UP", "negativeBlockIncrement",
 277             "HOME", "minScroll",
 278             "END", "maxScroll"
 279         }));
 280     }
 281 
 282     LateBoundInputMap getScrollBarRightToLeftInputMap() {
 283         return new LateBoundInputMap(new SimpleBinding(new String[] {
 284             "RIGHT", "negativeUnitIncrement",
 285             "KP_RIGHT", "negativeUnitIncrement",
 286             "LEFT", "positiveUnitIncrement",
 287             "KP_LEFT", "positiveUnitIncrement"
 288         }));
 289     }
 290 
 291     LateBoundInputMap getScrollPaneInputMap() {
 292         return new LateBoundInputMap(new SimpleBinding(new String[] {
 293             "RIGHT", "unitScrollRight",
 294             "KP_RIGHT", "unitScrollRight",
 295             "DOWN", "unitScrollDown",
 296             "KP_DOWN", "unitScrollDown",
 297             "LEFT", "unitScrollLeft",
 298             "KP_LEFT", "unitScrollLeft",
 299             "UP", "unitScrollUp",
 300             "KP_UP", "unitScrollUp",
 301             "PAGE_UP", "scrollUp",
 302             "PAGE_DOWN", "scrollDown",
 303             "HOME", "scrollHome",
 304             "END", "scrollEnd"
 305         }));
 306     }
 307 
 308     LateBoundInputMap getSliderInputMap() {
 309         return new LateBoundInputMap(new SimpleBinding(new String[] {
 310             "RIGHT", "positiveUnitIncrement",
 311             "KP_RIGHT", "positiveUnitIncrement",
 312             "DOWN", "negativeUnitIncrement",
 313             "KP_DOWN", "negativeUnitIncrement",
 314             "PAGE_DOWN", "negativeBlockIncrement",
 315             "LEFT", "negativeUnitIncrement",
 316             "KP_LEFT", "negativeUnitIncrement",
 317             "UP", "positiveUnitIncrement",
 318             "KP_UP", "positiveUnitIncrement",
 319             "PAGE_UP", "positiveBlockIncrement",
 320             "HOME", "minScroll",
 321             "END", "maxScroll"
 322         }));
 323     }
 324 
 325     LateBoundInputMap getSliderRightToLeftInputMap() {
 326         return new LateBoundInputMap(new SimpleBinding(new String[] {
 327             "RIGHT", "negativeUnitIncrement",
 328             "KP_RIGHT", "negativeUnitIncrement",
 329             "LEFT", "positiveUnitIncrement",
 330             "KP_LEFT", "positiveUnitIncrement"
 331         }));
 332     }
 333 
 334     LateBoundInputMap getSpinnerInputMap() {
 335         return new LateBoundInputMap(new SimpleBinding(new String[] {
 336             "UP", "increment",
 337             "KP_UP", "increment",
 338             "DOWN", "decrement",
 339             "KP_DOWN", "decrement"
 340         }));
 341     }
 342 
 343     LateBoundInputMap getTableInputMap() {
 344         return new LateBoundInputMap(new SimpleBinding(new String[] {
 345             "meta C", "copy",
 346             "meta V", "paste",
 347             "meta X", "cut",
 348             "COPY", "copy",
 349             "PASTE", "paste",
 350             "CUT", "cut",
 351             "RIGHT", "selectNextColumn",
 352             "KP_RIGHT", "selectNextColumn",
 353             "LEFT", "selectPreviousColumn",
 354             "KP_LEFT", "selectPreviousColumn",
 355             "DOWN", "selectNextRow",
 356             "KP_DOWN", "selectNextRow",
 357             "UP", "selectPreviousRow",
 358             "KP_UP", "selectPreviousRow",
 359             "shift RIGHT", "selectNextColumnExtendSelection",
 360             "shift KP_RIGHT", "selectNextColumnExtendSelection",
 361             "shift LEFT", "selectPreviousColumnExtendSelection",
 362             "shift KP_LEFT", "selectPreviousColumnExtendSelection",
 363             "shift DOWN", "selectNextRowExtendSelection",
 364             "shift KP_DOWN", "selectNextRowExtendSelection",
 365             "shift UP", "selectPreviousRowExtendSelection",
 366             "shift KP_UP", "selectPreviousRowExtendSelection",
 367             "PAGE_UP", "scrollUpChangeSelection",
 368             "PAGE_DOWN", "scrollDownChangeSelection",
 369             "HOME", "selectFirstColumn",
 370             "END", "selectLastColumn",
 371             "shift PAGE_UP", "scrollUpExtendSelection",
 372             "shift PAGE_DOWN", "scrollDownExtendSelection",
 373             "shift HOME", "selectFirstColumnExtendSelection",
 374             "shift END", "selectLastColumnExtendSelection",
 375             "TAB", "selectNextColumnCell",
 376             "shift TAB", "selectPreviousColumnCell",
 377             "meta A", "selectAll",
 378             "ESCAPE", "cancel",
 379             "ENTER", "selectNextRowCell",
 380             "shift ENTER", "selectPreviousRowCell",
 381             "alt TAB", "focusHeader",
 382             "alt shift TAB", "focusHeader"
 383         }));
 384     }
 385 
 386     LateBoundInputMap getTableRightToLeftInputMap() {
 387         return new LateBoundInputMap(new SimpleBinding(new String[] {
 388             "RIGHT", "selectPreviousColumn",
 389             "KP_RIGHT", "selectPreviousColumn",
 390             "LEFT", "selectNextColumn",
 391             "KP_LEFT", "selectNextColumn",
 392             "shift RIGHT", "selectPreviousColumnExtendSelection",
 393             "shift KP_RIGHT", "selectPreviousColumnExtendSelection",
 394             "shift LEFT", "selectNextColumnExtendSelection",
 395             "shift KP_LEFT", "selectNextColumnExtendSelection",
 396             "ctrl PAGE_UP", "scrollRightChangeSelection",
 397             "ctrl PAGE_DOWN", "scrollLeftChangeSelection",
 398             "ctrl shift PAGE_UP", "scrollRightExtendSelection",
 399             "ctrl shift PAGE_DOWN", "scrollLeftExtendSelection"
 400         }));
 401     }
 402 
 403     LateBoundInputMap getTreeInputMap() {
 404         return new LateBoundInputMap(new SimpleBinding(new String[] {
 405             "meta C", "copy",
 406             "meta V", "paste",
 407             "meta X", "cut",
 408             "COPY", "copy",
 409             "PASTE", "paste",
 410             "CUT", "cut",
 411             "UP", "selectPrevious",
 412             "KP_UP", "selectPrevious",
 413             "shift UP", "selectPreviousExtendSelection",
 414             "shift KP_UP", "selectPreviousExtendSelection",
 415             "DOWN", "selectNext",
 416             "KP_DOWN", "selectNext",
 417             "shift DOWN", "selectNextExtendSelection",
 418             "shift KP_DOWN", "selectNextExtendSelection",
 419             "RIGHT", "aquaExpandNode",
 420             "KP_RIGHT", "aquaExpandNode",
 421             "LEFT", "aquaCollapseNode",
 422             "KP_LEFT", "aquaCollapseNode",
 423             "shift RIGHT", "aquaExpandNode",
 424             "shift KP_RIGHT", "aquaExpandNode",
 425             "shift LEFT", "aquaCollapseNode",
 426             "shift KP_LEFT", "aquaCollapseNode",
 427             "ctrl LEFT", "aquaCollapseNode",
 428             "ctrl KP_LEFT", "aquaCollapseNode",
 429             "ctrl RIGHT", "aquaExpandNode",
 430             "ctrl KP_RIGHT", "aquaExpandNode",
 431             "alt RIGHT", "aquaFullyExpandNode",
 432             "alt KP_RIGHT", "aquaFullyExpandNode",
 433             "alt LEFT", "aquaFullyCollapseNode",
 434             "alt KP_LEFT", "aquaFullyCollapseNode",
 435             "meta A", "selectAll",
 436             "RETURN", "startEditing"
 437         }));
 438     }
 439 
 440     LateBoundInputMap getTreeRightToLeftInputMap() {
 441         return new LateBoundInputMap(new SimpleBinding(new String[] {
 442             "RIGHT", "aquaCollapseNode",
 443             "KP_RIGHT", "aquaCollapseNode",
 444             "LEFT", "aquaExpandNode",
 445             "KP_LEFT", "aquaExpandNode",
 446             "shift RIGHT", "aquaCollapseNode",
 447             "shift KP_RIGHT", "aquaCollapseNode",
 448             "shift LEFT", "aquaExpandNode",
 449             "shift KP_LEFT", "aquaExpandNode",
 450             "ctrl LEFT", "aquaExpandNode",
 451             "ctrl KP_LEFT", "aquaExpandNode",
 452             "ctrl RIGHT", "aquaCollapseNode",
 453             "ctrl KP_RIGHT", "aquaCollapseNode"
 454         }));
 455     }
 456 
 457     // common interface between a string array, and a dynamic provider of string arrays ;-)
 458     interface BindingsProvider {
 459         public String[] getBindings();
 460     }
 461 
 462     // wraps basic string arrays
 463     static class SimpleBinding implements BindingsProvider {
 464         final String[] bindings;
 465         public SimpleBinding(final String[] bindings) { this.bindings = bindings; }
 466         public String[] getBindings() { return bindings; }
 467     }
 468 
 469     // patches all providers together at the moment the UIManager needs the real InputMap
 470     static class LateBoundInputMap implements LazyValue, BindingsProvider {
 471         private final BindingsProvider[] providerList;
 472         private String[] mergedBindings;
 473 
 474         public LateBoundInputMap(final BindingsProvider ... providerList) {
 475             this.providerList = providerList;
 476         }
 477 
 478         public Object createValue(final UIDefaults table) {
 479             return LookAndFeel.makeInputMap(getBindings());
 480         }
 481 
 482         public String[] getBindings() {
 483             if (mergedBindings != null) return mergedBindings;
 484 
 485             final String[][] bindingsList = new String[providerList.length][];
 486             int size = 0;
 487             for (int i = 0; i < providerList.length; i++) {
 488                 bindingsList[i] = providerList[i].getBindings();
 489                 size += bindingsList[i].length;
 490             }
 491 
 492             if (bindingsList.length == 1) {
 493                 return mergedBindings = bindingsList[0];
 494             }
 495 
 496             final ArrayList<String> unifiedList = new ArrayList<String>(size);
 497             Collections.addAll(unifiedList, bindingsList[0]); // System.arrayCopy() the first set
 498 
 499             for (int i = 1; i < providerList.length; i++) {
 500                 mergeBindings(unifiedList, bindingsList[i]);
 501             }
 502 
 503             return mergedBindings = unifiedList.toArray(new String[unifiedList.size()]);
 504         }
 505 
 506         static void mergeBindings(final ArrayList<String> unifiedList, final String[] overrides) {
 507             for (int i = 0; i < overrides.length; i+=2) {
 508                 final String key = overrides[i];
 509                 final String value = overrides[i+1];
 510 
 511                 final int keyIndex = unifiedList.indexOf(key);
 512                 if (keyIndex == -1) {
 513                     unifiedList.add(key);
 514                     unifiedList.add(value);
 515                 } else {
 516                     unifiedList.set(keyIndex, key);
 517                     unifiedList.set(keyIndex + 1, value);
 518                 }
 519             }
 520         }
 521     }
 522 
 523     void installAquaUpDownActions(final JTextComponent component) {
 524         final ActionMap actionMap = component.getActionMap();
 525         actionMap.put(upMultilineAction, moveUpMultilineAction);
 526         actionMap.put(downMultilineAction, moveDownMultilineAction);
 527         actionMap.put(pageUpMultiline, pageUpMultilineAction);
 528         actionMap.put(pageDownMultiline, pageDownMultilineAction);
 529     }
 530 
 531     // extracted and adapted from DefaultEditorKit in 1.6
 532     @SuppressWarnings("serial") // Superclass is not serializable across versions
 533     abstract static class DeleteWordAction extends TextAction {
 534         public DeleteWordAction(final String name) { super(name); }
 535 
 536         public void actionPerformed(final ActionEvent e) {
 537             if (e == null) return;
 538 
 539             final JTextComponent target = getTextComponent(e);
 540             if (target == null) return;
 541 
 542             if (!target.isEditable() || !target.isEnabled()) {
 543                 UIManager.getLookAndFeel().provideErrorFeedback(target);
 544                 return;
 545             }
 546 
 547             try {
 548                 final int start = target.getSelectionStart();
 549                 final Element line = Utilities.getParagraphElement(target, start);
 550                 final int end = getEnd(target, line, start);
 551 
 552                 final int offs = Math.min(start, end);
 553                 final int len = Math.abs(end - start);
 554                 if (offs >= 0) {
 555                     target.getDocument().remove(offs, len);
 556                     return;
 557                 }
 558             } catch (final BadLocationException ignore) {}
 559             UIManager.getLookAndFeel().provideErrorFeedback(target);
 560         }
 561 
 562         abstract int getEnd(final JTextComponent target, final Element line, final int start) throws BadLocationException;
 563     }
 564 
 565     final TextAction moveUpMultilineAction = new AquaMultilineAction(upMultilineAction, DefaultEditorKit.upAction, DefaultEditorKit.beginAction);
 566     final TextAction moveDownMultilineAction = new AquaMultilineAction(downMultilineAction, DefaultEditorKit.downAction, DefaultEditorKit.endAction);
 567     final TextAction pageUpMultilineAction = new AquaMultilineAction(pageUpMultiline, DefaultEditorKit.pageUpAction, DefaultEditorKit.beginAction);
 568     final TextAction pageDownMultilineAction = new AquaMultilineAction(pageDownMultiline, DefaultEditorKit.pageDownAction, DefaultEditorKit.endAction);
 569 
 570     @SuppressWarnings("serial") // Superclass is not serializable across versions
 571     static class AquaMultilineAction extends TextAction {
 572         final String targetActionName;
 573         final String proxyActionName;
 574 
 575         public AquaMultilineAction(final String actionName, final String targetActionName, final String proxyActionName) {
 576             super(actionName);
 577             this.targetActionName = targetActionName;
 578             this.proxyActionName = proxyActionName;
 579         }
 580 
 581         public void actionPerformed(final ActionEvent e) {
 582             final JTextComponent c = getTextComponent(e);
 583             final ActionMap actionMap = c.getActionMap();
 584             final Action targetAction = actionMap.get(targetActionName);
 585 
 586             final int startPosition = c.getCaretPosition();
 587             targetAction.actionPerformed(e);
 588             if (startPosition != c.getCaretPosition()) return;
 589 
 590             final Action proxyAction = actionMap.get(proxyActionName);
 591             proxyAction.actionPerformed(e);
 592         }
 593     }
 594 }