1/* 2 * Copyright (C) 2010, 2011 Igalia S.L. 3 * 4 * This library is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU Lesser General Public 6 * License as published by the Free Software Foundation; either 7 * version 2 of the License, or (at your option) any later version. 8 * 9 * This library is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 * Lesser General Public License for more details. 13 * 14 * You should have received a copy of the GNU Lesser General Public 15 * License along with this library; if not, write to the Free Software 16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 */ 18 19#include "config.h" 20#include "KeyBindingTranslator.h" 21 22#include "GtkVersioning.h" 23#include <gdk/gdkkeysyms.h> 24#include <wtf/HashMap.h> 25 26namespace WebCore { 27 28typedef HashMap<int, const char*> IntConstCharHashMap; 29 30static void backspaceCallback(GtkWidget* widget, KeyBindingTranslator* translator) 31{ 32 g_signal_stop_emission_by_name(widget, "backspace"); 33 translator->addPendingEditorCommand("DeleteBackward"); 34} 35 36static void selectAllCallback(GtkWidget* widget, gboolean select, KeyBindingTranslator* translator) 37{ 38 g_signal_stop_emission_by_name(widget, "select-all"); 39 translator->addPendingEditorCommand(select ? "SelectAll" : "Unselect"); 40} 41 42static void cutClipboardCallback(GtkWidget* widget, KeyBindingTranslator* translator) 43{ 44 g_signal_stop_emission_by_name(widget, "cut-clipboard"); 45 translator->addPendingEditorCommand("Cut"); 46} 47 48static void copyClipboardCallback(GtkWidget* widget, KeyBindingTranslator* translator) 49{ 50 g_signal_stop_emission_by_name(widget, "copy-clipboard"); 51 translator->addPendingEditorCommand("Copy"); 52} 53 54static void pasteClipboardCallback(GtkWidget* widget, KeyBindingTranslator* translator) 55{ 56 g_signal_stop_emission_by_name(widget, "paste-clipboard"); 57 translator->addPendingEditorCommand("Paste"); 58} 59 60static void toggleOverwriteCallback(GtkWidget* widget, KeyBindingTranslator* translator) 61{ 62 g_signal_stop_emission_by_name(widget, "toggle-overwrite"); 63 translator->addPendingEditorCommand("OverWrite"); 64} 65 66// GTK+ will still send these signals to the web view. So we can safely stop signal 67// emission without breaking accessibility. 68static void popupMenuCallback(GtkWidget* widget, KeyBindingTranslator*) 69{ 70 g_signal_stop_emission_by_name(widget, "popup-menu"); 71} 72 73static void showHelpCallback(GtkWidget* widget, KeyBindingTranslator*) 74{ 75 g_signal_stop_emission_by_name(widget, "show-help"); 76} 77 78static const char* const gtkDeleteCommands[][2] = { 79 { "DeleteBackward", "DeleteForward" }, // Characters 80 { "DeleteWordBackward", "DeleteWordForward" }, // Word ends 81 { "DeleteWordBackward", "DeleteWordForward" }, // Words 82 { "DeleteToBeginningOfLine", "DeleteToEndOfLine" }, // Lines 83 { "DeleteToBeginningOfLine", "DeleteToEndOfLine" }, // Line ends 84 { "DeleteToBeginningOfParagraph", "DeleteToEndOfParagraph" }, // Paragraph ends 85 { "DeleteToBeginningOfParagraph", "DeleteToEndOfParagraph" }, // Paragraphs 86 { 0, 0 } // Whitespace (M-\ in Emacs) 87}; 88 89static void deleteFromCursorCallback(GtkWidget* widget, GtkDeleteType deleteType, gint count, KeyBindingTranslator* translator) 90{ 91 g_signal_stop_emission_by_name(widget, "delete-from-cursor"); 92 int direction = count > 0 ? 1 : 0; 93 94 // Ensuring that deleteType <= G_N_ELEMENTS here results in a compiler warning 95 // that the condition is always true. 96 97 if (deleteType == GTK_DELETE_WORDS) { 98 if (!direction) { 99 translator->addPendingEditorCommand("MoveWordForward"); 100 translator->addPendingEditorCommand("MoveWordBackward"); 101 } else { 102 translator->addPendingEditorCommand("MoveWordBackward"); 103 translator->addPendingEditorCommand("MoveWordForward"); 104 } 105 } else if (deleteType == GTK_DELETE_DISPLAY_LINES) { 106 if (!direction) 107 translator->addPendingEditorCommand("MoveToBeginningOfLine"); 108 else 109 translator->addPendingEditorCommand("MoveToEndOfLine"); 110 } else if (deleteType == GTK_DELETE_PARAGRAPHS) { 111 if (!direction) 112 translator->addPendingEditorCommand("MoveToBeginningOfParagraph"); 113 else 114 translator->addPendingEditorCommand("MoveToEndOfParagraph"); 115 } 116 117 const char* rawCommand = gtkDeleteCommands[deleteType][direction]; 118 if (!rawCommand) 119 return; 120 121 for (int i = 0; i < abs(count); i++) 122 translator->addPendingEditorCommand(rawCommand); 123} 124 125static const char* const gtkMoveCommands[][4] = { 126 { "MoveBackward", "MoveForward", 127 "MoveBackwardAndModifySelection", "MoveForwardAndModifySelection" }, // Forward/backward grapheme 128 { "MoveLeft", "MoveRight", 129 "MoveBackwardAndModifySelection", "MoveForwardAndModifySelection" }, // Left/right grapheme 130 { "MoveWordBackward", "MoveWordForward", 131 "MoveWordBackwardAndModifySelection", "MoveWordForwardAndModifySelection" }, // Forward/backward word 132 { "MoveUp", "MoveDown", 133 "MoveUpAndModifySelection", "MoveDownAndModifySelection" }, // Up/down line 134 { "MoveToBeginningOfLine", "MoveToEndOfLine", 135 "MoveToBeginningOfLineAndModifySelection", "MoveToEndOfLineAndModifySelection" }, // Up/down line ends 136 { "MoveParagraphBackward", "MoveParagraphForward", 137 "MoveParagraphBackwardAndModifySelection", "MoveParagraphForwardAndModifySelection" }, // Up/down paragraphs 138 { "MoveToBeginningOfParagraph", "MoveToEndOfParagraph", 139 "MoveToBeginningOfParagraphAndModifySelection", "MoveToEndOfParagraphAndModifySelection" }, // Up/down paragraph ends. 140 { "MovePageUp", "MovePageDown", 141 "MovePageUpAndModifySelection", "MovePageDownAndModifySelection" }, // Up/down page 142 { "MoveToBeginningOfDocument", "MoveToEndOfDocument", 143 "MoveToBeginningOfDocumentAndModifySelection", "MoveToEndOfDocumentAndModifySelection" }, // Begin/end of buffer 144 { 0, 0, 145 0, 0 } // Horizontal page movement 146}; 147 148static void moveCursorCallback(GtkWidget* widget, GtkMovementStep step, gint count, gboolean extendSelection, KeyBindingTranslator* translator) 149{ 150 g_signal_stop_emission_by_name(widget, "move-cursor"); 151 int direction = count > 0 ? 1 : 0; 152 if (extendSelection) 153 direction += 2; 154 155 if (static_cast<unsigned>(step) >= G_N_ELEMENTS(gtkMoveCommands)) 156 return; 157 158 const char* rawCommand = gtkMoveCommands[step][direction]; 159 if (!rawCommand) 160 return; 161 162 for (int i = 0; i < abs(count); i++) 163 translator->addPendingEditorCommand(rawCommand); 164} 165 166KeyBindingTranslator::KeyBindingTranslator() 167 : m_nativeWidget(gtk_text_view_new()) 168{ 169 g_signal_connect(m_nativeWidget.get(), "backspace", G_CALLBACK(backspaceCallback), this); 170 g_signal_connect(m_nativeWidget.get(), "cut-clipboard", G_CALLBACK(cutClipboardCallback), this); 171 g_signal_connect(m_nativeWidget.get(), "copy-clipboard", G_CALLBACK(copyClipboardCallback), this); 172 g_signal_connect(m_nativeWidget.get(), "paste-clipboard", G_CALLBACK(pasteClipboardCallback), this); 173 g_signal_connect(m_nativeWidget.get(), "select-all", G_CALLBACK(selectAllCallback), this); 174 g_signal_connect(m_nativeWidget.get(), "move-cursor", G_CALLBACK(moveCursorCallback), this); 175 g_signal_connect(m_nativeWidget.get(), "delete-from-cursor", G_CALLBACK(deleteFromCursorCallback), this); 176 g_signal_connect(m_nativeWidget.get(), "toggle-overwrite", G_CALLBACK(toggleOverwriteCallback), this); 177 g_signal_connect(m_nativeWidget.get(), "popup-menu", G_CALLBACK(popupMenuCallback), this); 178 g_signal_connect(m_nativeWidget.get(), "show-help", G_CALLBACK(showHelpCallback), this); 179} 180 181struct KeyCombinationEntry { 182 unsigned gdkKeyCode; 183 unsigned state; 184 const char* name; 185}; 186 187static const KeyCombinationEntry keyDownEntries[] = { 188 { GDK_b, GDK_CONTROL_MASK, "ToggleBold" }, 189 { GDK_i, GDK_CONTROL_MASK, "ToggleItalic" }, 190 { GDK_Escape, 0, "Cancel" }, 191 { GDK_greater, GDK_CONTROL_MASK, "Cancel" }, 192}; 193 194// These commands are text insertion commands, so should take place 195// while handling the KeyPress event. 196static const KeyCombinationEntry keyPressEntries[] = { 197 { GDK_Tab, 0, "InsertTab" }, 198 { GDK_Tab, GDK_SHIFT_MASK, "InsertBacktab" }, 199}; 200 201void KeyBindingTranslator::getEditorCommandsForKeyEvent(GdkEventKey* event, EventType type, Vector<WTF::String>& commandList) 202{ 203 m_pendingEditorCommands.clear(); 204 205#ifdef GTK_API_VERSION_2 206 gtk_bindings_activate_event(GTK_OBJECT(m_nativeWidget.get()), event); 207#else 208 gtk_bindings_activate_event(G_OBJECT(m_nativeWidget.get()), event); 209#endif 210 211 if (!m_pendingEditorCommands.isEmpty()) { 212 commandList.appendVector(m_pendingEditorCommands); 213 return; 214 } 215 216 DEPRECATED_DEFINE_STATIC_LOCAL(IntConstCharHashMap, keyDownCommandsMap, ()); 217 DEPRECATED_DEFINE_STATIC_LOCAL(IntConstCharHashMap, keyPressCommandsMap, ()); 218 219 if (keyDownCommandsMap.isEmpty()) { 220 for (unsigned i = 0; i < G_N_ELEMENTS(keyDownEntries); i++) 221 keyDownCommandsMap.set(keyDownEntries[i].state << 16 | keyDownEntries[i].gdkKeyCode, keyDownEntries[i].name); 222 223 for (unsigned i = 0; i < G_N_ELEMENTS(keyPressEntries); i++) 224 keyPressCommandsMap.set(keyPressEntries[i].state << 16 | keyPressEntries[i].gdkKeyCode, keyPressEntries[i].name); 225 } 226 227 // Special-case enter keys for we want them to work regardless of modifier. 228 if ((event->keyval == GDK_Return || event->keyval == GDK_KP_Enter || event->keyval == GDK_ISO_Enter) && type == KeyPress) { 229 commandList.append("InsertNewLine"); 230 return; 231 } 232 233 // For keypress events, we want charCode(), but keyCode() does that. 234 int mapKey = event->state << 16 | event->keyval; 235 if (mapKey) { 236 HashMap<int, const char*>* commandMap = type == KeyDown ? &keyDownCommandsMap : &keyPressCommandsMap; 237 if (const char* commandString = commandMap->get(mapKey)) { 238 commandList.append(commandString); 239 return; 240 } 241 } 242} 243 244} // namespace WebCore 245