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