/* * Copyright (C) 2010, 2011 Igalia S.L. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include "KeyBindingTranslator.h" #include #include namespace WebKit { static void backspaceCallback(GtkWidget* widget, KeyBindingTranslator* translator) { g_signal_stop_emission_by_name(widget, "backspace"); translator->addPendingEditorCommand("DeleteBackward"); } static void selectAllCallback(GtkWidget* widget, gboolean select, KeyBindingTranslator* translator) { g_signal_stop_emission_by_name(widget, "select-all"); translator->addPendingEditorCommand(select ? "SelectAll" : "Unselect"); } static void cutClipboardCallback(GtkWidget* widget, KeyBindingTranslator* translator) { g_signal_stop_emission_by_name(widget, "cut-clipboard"); translator->addPendingEditorCommand("Cut"); } static void copyClipboardCallback(GtkWidget* widget, KeyBindingTranslator* translator) { g_signal_stop_emission_by_name(widget, "copy-clipboard"); translator->addPendingEditorCommand("Copy"); } static void pasteClipboardCallback(GtkWidget* widget, KeyBindingTranslator* translator) { g_signal_stop_emission_by_name(widget, "paste-clipboard"); translator->addPendingEditorCommand("Paste"); } static void toggleOverwriteCallback(GtkWidget* widget, KeyBindingTranslator* translator) { g_signal_stop_emission_by_name(widget, "toggle-overwrite"); translator->addPendingEditorCommand("OverWrite"); } // GTK+ will still send these signals to the web view. So we can safely stop signal // emission without breaking accessibility. static void popupMenuCallback(GtkWidget* widget, KeyBindingTranslator*) { g_signal_stop_emission_by_name(widget, "popup-menu"); } static void showHelpCallback(GtkWidget* widget, KeyBindingTranslator*) { g_signal_stop_emission_by_name(widget, "show-help"); } static const char* const gtkDeleteCommands[][2] = { { "DeleteBackward", "DeleteForward" }, // Characters { "DeleteWordBackward", "DeleteWordForward" }, // Word ends { "DeleteWordBackward", "DeleteWordForward" }, // Words { "DeleteToBeginningOfLine", "DeleteToEndOfLine" }, // Lines { "DeleteToBeginningOfLine", "DeleteToEndOfLine" }, // Line ends { "DeleteToBeginningOfParagraph", "DeleteToEndOfParagraph" }, // Paragraph ends { "DeleteToBeginningOfParagraph", "DeleteToEndOfParagraph" }, // Paragraphs { 0, 0 } // Whitespace (M-\ in Emacs) }; static void deleteFromCursorCallback(GtkWidget* widget, GtkDeleteType deleteType, gint count, KeyBindingTranslator* translator) { g_signal_stop_emission_by_name(widget, "delete-from-cursor"); int direction = count > 0 ? 1 : 0; // Ensuring that deleteType <= G_N_ELEMENTS here results in a compiler warning // that the condition is always true. if (deleteType == GTK_DELETE_WORDS) { if (!direction) { translator->addPendingEditorCommand("MoveWordForward"); translator->addPendingEditorCommand("MoveWordBackward"); } else { translator->addPendingEditorCommand("MoveWordBackward"); translator->addPendingEditorCommand("MoveWordForward"); } } else if (deleteType == GTK_DELETE_DISPLAY_LINES) { if (!direction) translator->addPendingEditorCommand("MoveToBeginningOfLine"); else translator->addPendingEditorCommand("MoveToEndOfLine"); } else if (deleteType == GTK_DELETE_PARAGRAPHS) { if (!direction) translator->addPendingEditorCommand("MoveToBeginningOfParagraph"); else translator->addPendingEditorCommand("MoveToEndOfParagraph"); } const char* rawCommand = gtkDeleteCommands[deleteType][direction]; if (!rawCommand) return; for (int i = 0; i < abs(count); i++) translator->addPendingEditorCommand(rawCommand); } static const char* const gtkMoveCommands[][4] = { { "MoveBackward", "MoveForward", "MoveBackwardAndModifySelection", "MoveForwardAndModifySelection" }, // Forward/backward grapheme { "MoveLeft", "MoveRight", "MoveBackwardAndModifySelection", "MoveForwardAndModifySelection" }, // Left/right grapheme { "MoveWordBackward", "MoveWordForward", "MoveWordBackwardAndModifySelection", "MoveWordForwardAndModifySelection" }, // Forward/backward word { "MoveUp", "MoveDown", "MoveUpAndModifySelection", "MoveDownAndModifySelection" }, // Up/down line { "MoveToBeginningOfLine", "MoveToEndOfLine", "MoveToBeginningOfLineAndModifySelection", "MoveToEndOfLineAndModifySelection" }, // Up/down line ends { 0, 0, "MoveParagraphBackwardAndModifySelection", "MoveParagraphForwardAndModifySelection" }, // Up/down paragraphs { "MoveToBeginningOfParagraph", "MoveToEndOfParagraph", "MoveToBeginningOfParagraphAndModifySelection", "MoveToEndOfParagraphAndModifySelection" }, // Up/down paragraph ends. { "MovePageUp", "MovePageDown", "MovePageUpAndModifySelection", "MovePageDownAndModifySelection" }, // Up/down page { "MoveToBeginningOfDocument", "MoveToEndOfDocument", "MoveToBeginningOfDocumentAndModifySelection", "MoveToEndOfDocumentAndModifySelection" }, // Begin/end of buffer { 0, 0, 0, 0 } // Horizontal page movement }; static void moveCursorCallback(GtkWidget* widget, GtkMovementStep step, gint count, gboolean extendSelection, KeyBindingTranslator* translator) { g_signal_stop_emission_by_name(widget, "move-cursor"); int direction = count > 0 ? 1 : 0; if (extendSelection) direction += 2; if (static_cast(step) >= G_N_ELEMENTS(gtkMoveCommands)) return; const char* rawCommand = gtkMoveCommands[step][direction]; if (!rawCommand) return; for (int i = 0; i < abs(count); i++) translator->addPendingEditorCommand(rawCommand); } KeyBindingTranslator::KeyBindingTranslator() : m_nativeWidget(gtk_text_view_new()) { g_signal_connect(m_nativeWidget.get(), "backspace", G_CALLBACK(backspaceCallback), this); g_signal_connect(m_nativeWidget.get(), "cut-clipboard", G_CALLBACK(cutClipboardCallback), this); g_signal_connect(m_nativeWidget.get(), "copy-clipboard", G_CALLBACK(copyClipboardCallback), this); g_signal_connect(m_nativeWidget.get(), "paste-clipboard", G_CALLBACK(pasteClipboardCallback), this); g_signal_connect(m_nativeWidget.get(), "select-all", G_CALLBACK(selectAllCallback), this); g_signal_connect(m_nativeWidget.get(), "move-cursor", G_CALLBACK(moveCursorCallback), this); g_signal_connect(m_nativeWidget.get(), "delete-from-cursor", G_CALLBACK(deleteFromCursorCallback), this); g_signal_connect(m_nativeWidget.get(), "toggle-overwrite", G_CALLBACK(toggleOverwriteCallback), this); g_signal_connect(m_nativeWidget.get(), "popup-menu", G_CALLBACK(popupMenuCallback), this); g_signal_connect(m_nativeWidget.get(), "show-help", G_CALLBACK(showHelpCallback), this); } struct KeyCombinationEntry { unsigned gdkKeyCode; unsigned state; const char* name; }; static const KeyCombinationEntry customKeyBindings[] = { { GDK_KEY_b, GDK_CONTROL_MASK, "ToggleBold" }, { GDK_KEY_i, GDK_CONTROL_MASK, "ToggleItalic" }, { GDK_KEY_Escape, 0, "Cancel" }, { GDK_KEY_greater, GDK_CONTROL_MASK, "Cancel" }, { GDK_KEY_Tab, 0, "InsertTab" }, { GDK_KEY_Tab, GDK_SHIFT_MASK, "InsertBacktab" }, }; Vector KeyBindingTranslator::commandsForKeyEvent(GdkEventKey* event) { ASSERT(m_pendingEditorCommands.isEmpty()); gtk_bindings_activate_event(G_OBJECT(m_nativeWidget.get()), event); if (!m_pendingEditorCommands.isEmpty()) return WTFMove(m_pendingEditorCommands); // Special-case enter keys for we want them to work regardless of modifier. if ((event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter || event->keyval == GDK_KEY_ISO_Enter)) return { "InsertNewLine" }; // For keypress events, we want charCode(), but keyCode() does that. unsigned mapKey = event->state << 16 | event->keyval; if (!mapKey) return { }; for (unsigned i = 0; i < G_N_ELEMENTS(customKeyBindings); ++i) { if (mapKey == (customKeyBindings[i].state << 16 | customKeyBindings[i].gdkKeyCode)) return { customKeyBindings[i].name }; } return { }; } } // namespace WebKit