Home | History | Annotate | Download | only in editing
      1 /*
      2  * Copyright (C) 2006, 2007 Apple, Inc.  All rights reserved.
      3  * Copyright (C) 2012 Google, Inc.  All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  * 1. Redistributions of source code must retain the above copyright
      9  *    notice, this list of conditions and the following disclaimer.
     10  * 2. Redistributions in binary form must reproduce the above copyright
     11  *    notice, this list of conditions and the following disclaimer in the
     12  *    documentation and/or other materials provided with the distribution.
     13  *
     14  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     25  */
     26 
     27 #include "config.h"
     28 #include "core/editing/Editor.h"
     29 
     30 #include "core/events/KeyboardEvent.h"
     31 #include "core/frame/Frame.h"
     32 #include "core/page/EditorClient.h"
     33 #include "platform/KeyboardCodes.h"
     34 #include "platform/PlatformKeyboardEvent.h"
     35 
     36 namespace WebCore {
     37 
     38 //
     39 // The below code was adapted from the WebKit file webview.cpp
     40 //
     41 
     42 static const unsigned CtrlKey = 1 << 0;
     43 static const unsigned AltKey = 1 << 1;
     44 static const unsigned ShiftKey = 1 << 2;
     45 static const unsigned MetaKey = 1 << 3;
     46 #if OS(MACOSX)
     47 // Aliases for the generic key defintions to make kbd shortcuts definitions more
     48 // readable on OS X.
     49 static const unsigned OptionKey  = AltKey;
     50 
     51 // Do not use this constant for anything but cursor movement commands. Keys
     52 // with cmd set have their |isSystemKey| bit set, so chances are the shortcut
     53 // will not be executed. Another, less important, reason is that shortcuts
     54 // defined in the renderer do not blink the menu item that they triggered. See
     55 // http://crbug.com/25856 and the bugs linked from there for details.
     56 static const unsigned CommandKey = MetaKey;
     57 #endif
     58 
     59 // Keys with special meaning. These will be delegated to the editor using
     60 // the execCommand() method
     61 struct KeyDownEntry {
     62     unsigned virtualKey;
     63     unsigned modifiers;
     64     const char* name;
     65 };
     66 
     67 struct KeyPressEntry {
     68     unsigned charCode;
     69     unsigned modifiers;
     70     const char* name;
     71 };
     72 
     73 // Key bindings with command key on Mac and alt key on other platforms are
     74 // marked as system key events and will be ignored (with the exception
     75 // of Command-B and Command-I) so they shouldn't be added here.
     76 static const KeyDownEntry keyDownEntries[] = {
     77     { VKEY_LEFT,   0,                  "MoveLeft"                             },
     78     { VKEY_LEFT,   ShiftKey,           "MoveLeftAndModifySelection"           },
     79 #if OS(MACOSX)
     80     { VKEY_LEFT,   OptionKey,          "MoveWordLeft"                         },
     81     { VKEY_LEFT,   OptionKey | ShiftKey,
     82         "MoveWordLeftAndModifySelection"                                      },
     83 #else
     84     { VKEY_LEFT,   CtrlKey,            "MoveWordLeft"                         },
     85     { VKEY_LEFT,   CtrlKey | ShiftKey,
     86         "MoveWordLeftAndModifySelection"                                      },
     87 #endif
     88     { VKEY_RIGHT,  0,                  "MoveRight"                            },
     89     { VKEY_RIGHT,  ShiftKey,           "MoveRightAndModifySelection"          },
     90 #if OS(MACOSX)
     91     { VKEY_RIGHT,  OptionKey,          "MoveWordRight"                        },
     92     { VKEY_RIGHT,  OptionKey | ShiftKey, "MoveWordRightAndModifySelection"    },
     93 #else
     94     { VKEY_RIGHT,  CtrlKey,            "MoveWordRight"                        },
     95     { VKEY_RIGHT,  CtrlKey | ShiftKey, "MoveWordRightAndModifySelection"      },
     96 #endif
     97     { VKEY_UP,     0,                  "MoveUp"                               },
     98     { VKEY_UP,     ShiftKey,           "MoveUpAndModifySelection"             },
     99     { VKEY_PRIOR,  ShiftKey,           "MovePageUpAndModifySelection"         },
    100     { VKEY_DOWN,   0,                  "MoveDown"                             },
    101     { VKEY_DOWN,   ShiftKey,           "MoveDownAndModifySelection"           },
    102     { VKEY_NEXT,   ShiftKey,           "MovePageDownAndModifySelection"       },
    103 #if !OS(MACOSX)
    104     { VKEY_UP,     CtrlKey,            "MoveParagraphBackward"                },
    105     { VKEY_UP,     CtrlKey | ShiftKey, "MoveParagraphBackwardAndModifySelection" },
    106     { VKEY_DOWN,   CtrlKey,            "MoveParagraphForward"                },
    107     { VKEY_DOWN,   CtrlKey | ShiftKey, "MoveParagraphForwardAndModifySelection" },
    108     { VKEY_PRIOR,  0,                  "MovePageUp"                           },
    109     { VKEY_NEXT,   0,                  "MovePageDown"                         },
    110 #endif
    111     { VKEY_HOME,   0,                  "MoveToBeginningOfLine"                },
    112     { VKEY_HOME,   ShiftKey,
    113         "MoveToBeginningOfLineAndModifySelection"                             },
    114 #if OS(MACOSX)
    115     { VKEY_PRIOR,  OptionKey,          "MovePageUp"                           },
    116     { VKEY_NEXT,   OptionKey,          "MovePageDown"                         },
    117 #endif
    118 #if !OS(MACOSX)
    119     { VKEY_HOME,   CtrlKey,            "MoveToBeginningOfDocument"            },
    120     { VKEY_HOME,   CtrlKey | ShiftKey,
    121         "MoveToBeginningOfDocumentAndModifySelection"                         },
    122 #endif
    123     { VKEY_END,    0,                  "MoveToEndOfLine"                      },
    124     { VKEY_END,    ShiftKey,           "MoveToEndOfLineAndModifySelection"    },
    125 #if !OS(MACOSX)
    126     { VKEY_END,    CtrlKey,            "MoveToEndOfDocument"                  },
    127     { VKEY_END,    CtrlKey | ShiftKey,
    128         "MoveToEndOfDocumentAndModifySelection"                               },
    129 #endif
    130     { VKEY_BACK,   0,                  "DeleteBackward"                       },
    131     { VKEY_BACK,   ShiftKey,           "DeleteBackward"                       },
    132     { VKEY_DELETE, 0,                  "DeleteForward"                        },
    133 #if OS(MACOSX)
    134     { VKEY_BACK,   OptionKey,          "DeleteWordBackward"                   },
    135     { VKEY_DELETE, OptionKey,          "DeleteWordForward"                    },
    136 #else
    137     { VKEY_BACK,   CtrlKey,            "DeleteWordBackward"                   },
    138     { VKEY_DELETE, CtrlKey,            "DeleteWordForward"                    },
    139 #endif
    140 #if OS(MACOSX)
    141     { 'B',         CommandKey,         "ToggleBold"                           },
    142     { 'I',         CommandKey,         "ToggleItalic"                         },
    143 #else
    144     { 'B',         CtrlKey,            "ToggleBold"                           },
    145     { 'I',         CtrlKey,            "ToggleItalic"                         },
    146 #endif
    147     { 'U',         CtrlKey,            "ToggleUnderline"                      },
    148     { VKEY_ESCAPE, 0,                  "Cancel"                               },
    149     { VKEY_OEM_PERIOD, CtrlKey,        "Cancel"                               },
    150     { VKEY_TAB,    0,                  "InsertTab"                            },
    151     { VKEY_TAB,    ShiftKey,           "InsertBacktab"                        },
    152     { VKEY_RETURN, 0,                  "InsertNewline"                        },
    153     { VKEY_RETURN, CtrlKey,            "InsertNewline"                        },
    154     { VKEY_RETURN, AltKey,             "InsertNewline"                        },
    155     { VKEY_RETURN, AltKey | ShiftKey,  "InsertNewline"                        },
    156     { VKEY_RETURN, ShiftKey,           "InsertLineBreak"                      },
    157     { VKEY_INSERT, CtrlKey,            "Copy"                                 },
    158     { VKEY_INSERT, ShiftKey,           "Paste"                                },
    159     { VKEY_DELETE, ShiftKey,           "Cut"                                  },
    160 #if !OS(MACOSX)
    161     // On OS X, we pipe these back to the browser, so that it can do menu item
    162     // blinking.
    163     { 'C',         CtrlKey,            "Copy"                                 },
    164     { 'V',         CtrlKey,            "Paste"                                },
    165     { 'V',         CtrlKey | ShiftKey, "PasteAndMatchStyle"                   },
    166     { 'X',         CtrlKey,            "Cut"                                  },
    167     { 'A',         CtrlKey,            "SelectAll"                            },
    168     { 'Z',         CtrlKey,            "Undo"                                 },
    169     { 'Z',         CtrlKey | ShiftKey, "Redo"                                 },
    170     { 'Y',         CtrlKey,            "Redo"                                 },
    171 #endif
    172     { VKEY_INSERT, 0,                  "OverWrite"                            },
    173 };
    174 
    175 static const KeyPressEntry keyPressEntries[] = {
    176     { '\t',   0,                  "InsertTab"                                 },
    177     { '\t',   ShiftKey,           "InsertBacktab"                             },
    178     { '\r',   0,                  "InsertNewline"                             },
    179     { '\r',   CtrlKey,            "InsertNewline"                             },
    180     { '\r',   ShiftKey,           "InsertLineBreak"                           },
    181     { '\r',   AltKey,             "InsertNewline"                             },
    182     { '\r',   AltKey | ShiftKey,  "InsertNewline"                             },
    183 };
    184 
    185 const char* Editor::interpretKeyEvent(const KeyboardEvent* evt)
    186 {
    187     const PlatformKeyboardEvent* keyEvent = evt->keyEvent();
    188     if (!keyEvent)
    189         return "";
    190 
    191     static HashMap<int, const char*>* keyDownCommandsMap = 0;
    192     static HashMap<int, const char*>* keyPressCommandsMap = 0;
    193 
    194     if (!keyDownCommandsMap) {
    195         keyDownCommandsMap = new HashMap<int, const char*>;
    196         keyPressCommandsMap = new HashMap<int, const char*>;
    197 
    198         for (unsigned i = 0; i < arraysize(keyDownEntries); i++) {
    199             keyDownCommandsMap->set(keyDownEntries[i].modifiers << 16 | keyDownEntries[i].virtualKey, keyDownEntries[i].name);
    200         }
    201 
    202         for (unsigned i = 0; i < arraysize(keyPressEntries); i++) {
    203             keyPressCommandsMap->set(keyPressEntries[i].modifiers << 16 | keyPressEntries[i].charCode, keyPressEntries[i].name);
    204         }
    205     }
    206 
    207     unsigned modifiers = 0;
    208     if (keyEvent->shiftKey())
    209         modifiers |= ShiftKey;
    210     if (keyEvent->altKey())
    211         modifiers |= AltKey;
    212     if (keyEvent->ctrlKey())
    213         modifiers |= CtrlKey;
    214     if (keyEvent->metaKey())
    215         modifiers |= MetaKey;
    216 
    217     if (keyEvent->type() == PlatformEvent::RawKeyDown) {
    218         int mapKey = modifiers << 16 | evt->keyCode();
    219         return mapKey ? keyDownCommandsMap->get(mapKey) : 0;
    220     }
    221 
    222     int mapKey = modifiers << 16 | evt->charCode();
    223     return mapKey ? keyPressCommandsMap->get(mapKey) : 0;
    224 }
    225 
    226 bool Editor::handleEditingKeyboardEvent(KeyboardEvent* evt)
    227 {
    228     const PlatformKeyboardEvent* keyEvent = evt->keyEvent();
    229     // do not treat this as text input if it's a system key event
    230     if (!keyEvent || keyEvent->isSystemKey())
    231         return false;
    232 
    233     String commandName = interpretKeyEvent(evt);
    234     Command command = this->command(commandName);
    235 
    236     if (keyEvent->type() == PlatformEvent::RawKeyDown) {
    237         // WebKit doesn't have enough information about mode to decide how
    238         // commands that just insert text if executed via Editor should be treated,
    239         // so we leave it upon WebCore to either handle them immediately
    240         // (e.g. Tab that changes focus) or let a keypress event be generated
    241         // (e.g. Tab that inserts a Tab character, or Enter).
    242         if (command.isTextInsertion() || commandName.isEmpty())
    243             return false;
    244         if (command.execute(evt)) {
    245             client().didExecuteCommand(commandName);
    246             return true;
    247         }
    248         return false;
    249     }
    250 
    251     if (command.execute(evt)) {
    252         client().didExecuteCommand(commandName);
    253         return true;
    254     }
    255 
    256     // Here we need to filter key events.
    257     // On Gtk/Linux, it emits key events with ASCII text and ctrl on for ctrl-<x>.
    258     // In Webkit, EditorClient::handleKeyboardEvent in
    259     // WebKit/gtk/WebCoreSupport/EditorClientGtk.cpp drop such events.
    260     // On Mac, it emits key events with ASCII text and meta on for Command-<x>.
    261     // These key events should not emit text insert event.
    262     // Alt key would be used to insert alternative character, so we should let
    263     // through. Also note that Ctrl-Alt combination equals to AltGr key which is
    264     // also used to insert alternative character.
    265     // http://code.google.com/p/chromium/issues/detail?id=10846
    266     // Windows sets both alt and meta are on when "Alt" key pressed.
    267     // http://code.google.com/p/chromium/issues/detail?id=2215
    268     // Also, we should not rely on an assumption that keyboards don't
    269     // send ASCII characters when pressing a control key on Windows,
    270     // which may be configured to do it so by user.
    271     // See also http://en.wikipedia.org/wiki/Keyboard_Layout
    272     // FIXME(ukai): investigate more detail for various keyboard layout.
    273     if (evt->keyEvent()->text().length() == 1) {
    274         UChar ch = evt->keyEvent()->text()[0U];
    275 
    276         // Don't insert null or control characters as they can result in
    277         // unexpected behaviour
    278         if (ch < ' ')
    279             return false;
    280 #if !OS(WIN)
    281         // Don't insert ASCII character if ctrl w/o alt or meta is on.
    282         // On Mac, we should ignore events when meta is on (Command-<x>).
    283         if (ch < 0x80) {
    284             if (evt->keyEvent()->ctrlKey() && !evt->keyEvent()->altKey())
    285                 return false;
    286 #if OS(MACOSX)
    287             if (evt->keyEvent()->metaKey())
    288             return false;
    289 #endif
    290         }
    291 #endif
    292     }
    293 
    294     if (!canEdit())
    295         return false;
    296 
    297     return insertText(evt->keyEvent()->text(), evt);
    298 }
    299 
    300 void Editor::handleKeyboardEvent(KeyboardEvent* evt)
    301 {
    302     // Give the embedder a chance to handle the keyboard event.
    303     if (client().handleKeyboardEvent() || handleEditingKeyboardEvent(evt))
    304         evt->setDefaultHandled();
    305 }
    306 
    307 } // namesace WebCore
    308