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