// Copyright (c) 2009 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/renderer_host/gtk_key_bindings_handler.h" #include <gdk/gdkkeysyms.h> #include <string> #include "base/logging.h" #include "base/string_util.h" #include "content/common/native_web_keyboard_event.h" GtkKeyBindingsHandler::GtkKeyBindingsHandler(GtkWidget* parent_widget) : handler_(CreateNewHandler()) { DCHECK(GTK_IS_FIXED(parent_widget)); // We need add the |handler_| object into gtk widget hierarchy, so that // gtk_bindings_activate_event() can find correct display and keymaps from // the |handler_| object. gtk_fixed_put(GTK_FIXED(parent_widget), handler_.get(), -1, -1); } GtkKeyBindingsHandler::~GtkKeyBindingsHandler() { handler_.Destroy(); } bool GtkKeyBindingsHandler::Match(const NativeWebKeyboardEvent& wke, EditCommands* edit_commands) { if (wke.type == WebKit::WebInputEvent::Char || !wke.os_event) return false; edit_commands_.clear(); // If this key event matches a predefined key binding, corresponding signal // will be emitted. gtk_bindings_activate_event(GTK_OBJECT(handler_.get()), wke.os_event); bool matched = !edit_commands_.empty(); if (edit_commands) edit_commands->swap(edit_commands_); return matched; } GtkWidget* GtkKeyBindingsHandler::CreateNewHandler() { Handler* handler = static_cast<Handler*>(g_object_new(HandlerGetType(), NULL)); handler->owner = this; // We don't need to show the |handler| object on screen, so set its size to // zero. gtk_widget_set_size_request(GTK_WIDGET(handler), 0, 0); // Prevents it from handling any events by itself. gtk_widget_set_sensitive(GTK_WIDGET(handler), FALSE); gtk_widget_set_events(GTK_WIDGET(handler), 0); GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(handler), GTK_CAN_FOCUS); #if !GTK_CHECK_VERSION(2, 14, 0) // "move-focus", "move-viewport", "select-all" and "toggle-cursor-visible" // have no corresponding virtual methods. Prior to glib 2.18 (gtk 2.14), // there is no way to override the default class handler of a signal. // So we need hook these signal explicitly. g_signal_connect(handler, "move-focus", G_CALLBACK(MoveFocus), NULL); g_signal_connect(handler, "move-viewport", G_CALLBACK(MoveViewport), NULL); g_signal_connect(handler, "select-all", G_CALLBACK(SelectAll), NULL); g_signal_connect(handler, "toggle-cursor-visible", G_CALLBACK(ToggleCursorVisible), NULL); #endif return GTK_WIDGET(handler); } void GtkKeyBindingsHandler::EditCommandMatched( const std::string& name, const std::string& value) { edit_commands_.push_back(EditCommand(name, value)); } void GtkKeyBindingsHandler::HandlerInit(Handler *self) { self->owner = NULL; } void GtkKeyBindingsHandler::HandlerClassInit(HandlerClass *klass) { GtkTextViewClass* text_view_class = GTK_TEXT_VIEW_CLASS(klass); GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass); // Overrides all virtual methods related to editor key bindings. text_view_class->backspace = BackSpace; text_view_class->copy_clipboard = CopyClipboard; text_view_class->cut_clipboard = CutClipboard; text_view_class->delete_from_cursor = DeleteFromCursor; text_view_class->insert_at_cursor = InsertAtCursor; text_view_class->move_cursor = MoveCursor; text_view_class->paste_clipboard = PasteClipboard; text_view_class->set_anchor = SetAnchor; text_view_class->toggle_overwrite = ToggleOverwrite; widget_class->show_help = ShowHelp; #if GTK_CHECK_VERSION(2, 14, 0) // "move-focus", "move-viewport", "select-all" and "toggle-cursor-visible" // have no corresponding virtual methods. Since glib 2.18 (gtk 2.14), // g_signal_override_class_handler() is introduced to override a signal // handler. g_signal_override_class_handler("move-focus", G_TYPE_FROM_CLASS(klass), G_CALLBACK(MoveFocus)); g_signal_override_class_handler("move-viewport", G_TYPE_FROM_CLASS(klass), G_CALLBACK(MoveViewport)); g_signal_override_class_handler("select-all", G_TYPE_FROM_CLASS(klass), G_CALLBACK(SelectAll)); g_signal_override_class_handler("toggle-cursor-visible", G_TYPE_FROM_CLASS(klass), G_CALLBACK(ToggleCursorVisible)); #endif } GType GtkKeyBindingsHandler::HandlerGetType() { static volatile gsize type_id_volatile = 0; if (g_once_init_enter(&type_id_volatile)) { GType type_id = g_type_register_static_simple( GTK_TYPE_TEXT_VIEW, g_intern_static_string("GtkKeyBindingsHandler"), sizeof(HandlerClass), reinterpret_cast<GClassInitFunc>(HandlerClassInit), sizeof(Handler), reinterpret_cast<GInstanceInitFunc>(HandlerInit), static_cast<GTypeFlags>(0)); g_once_init_leave(&type_id_volatile, type_id); } return type_id_volatile; } GtkKeyBindingsHandler* GtkKeyBindingsHandler::GetHandlerOwner( GtkTextView* text_view) { Handler* handler = G_TYPE_CHECK_INSTANCE_CAST( text_view, HandlerGetType(), Handler); DCHECK(handler); return handler->owner; } void GtkKeyBindingsHandler::BackSpace(GtkTextView* text_view) { GetHandlerOwner(text_view)->EditCommandMatched("DeleteBackward", ""); } void GtkKeyBindingsHandler::CopyClipboard(GtkTextView* text_view) { GetHandlerOwner(text_view)->EditCommandMatched("Copy", ""); } void GtkKeyBindingsHandler::CutClipboard(GtkTextView* text_view) { GetHandlerOwner(text_view)->EditCommandMatched("Cut", ""); } void GtkKeyBindingsHandler::DeleteFromCursor( GtkTextView* text_view, GtkDeleteType type, gint count) { if (!count) return; const char *commands[3] = { NULL, NULL, NULL }; switch (type) { case GTK_DELETE_CHARS: commands[0] = (count > 0 ? "DeleteForward" : "DeleteBackward"); break; case GTK_DELETE_WORD_ENDS: commands[0] = (count > 0 ? "DeleteWordForward" : "DeleteWordBackward"); break; case GTK_DELETE_WORDS: if (count > 0) { commands[0] = "MoveWordForward"; commands[1] = "DeleteWordBackward"; } else { commands[0] = "MoveWordBackward"; commands[1] = "DeleteWordForward"; } break; case GTK_DELETE_DISPLAY_LINES: commands[0] = "MoveToBeginningOfLine"; commands[1] = "DeleteToEndOfLine"; break; case GTK_DELETE_DISPLAY_LINE_ENDS: commands[0] = (count > 0 ? "DeleteToEndOfLine" : "DeleteToBeginningOfLine"); break; case GTK_DELETE_PARAGRAPH_ENDS: commands[0] = (count > 0 ? "DeleteToEndOfParagraph" : "DeleteToBeginningOfParagraph"); break; case GTK_DELETE_PARAGRAPHS: commands[0] = "MoveToBeginningOfParagraph"; commands[1] = "DeleteToEndOfParagraph"; break; default: // GTK_DELETE_WHITESPACE has no corresponding editor command. return; } GtkKeyBindingsHandler* owner = GetHandlerOwner(text_view); if (count < 0) count = -count; for (; count > 0; --count) { for (const char* const* p = commands; *p; ++p) owner->EditCommandMatched(*p, ""); } } void GtkKeyBindingsHandler::InsertAtCursor(GtkTextView* text_view, const gchar* str) { if (str && *str) GetHandlerOwner(text_view)->EditCommandMatched("InsertText", str); } void GtkKeyBindingsHandler::MoveCursor( GtkTextView* text_view, GtkMovementStep step, gint count, gboolean extend_selection) { if (!count) return; std::string command; switch (step) { case GTK_MOVEMENT_LOGICAL_POSITIONS: command = (count > 0 ? "MoveForward" : "MoveBackward"); break; case GTK_MOVEMENT_VISUAL_POSITIONS: command = (count > 0 ? "MoveRight" : "MoveLeft"); break; case GTK_MOVEMENT_WORDS: command = (count > 0 ? "MoveWordForward" : "MoveWordBackward"); break; case GTK_MOVEMENT_DISPLAY_LINES: command = (count > 0 ? "MoveDown" : "MoveUp"); break; case GTK_MOVEMENT_DISPLAY_LINE_ENDS: command = (count > 0 ? "MoveToEndOfLine" : "MoveToBeginningOfLine"); break; case GTK_MOVEMENT_PARAGRAPH_ENDS: command = (count > 0 ? "MoveToEndOfParagraph" : "MoveToBeginningOfParagraph"); break; case GTK_MOVEMENT_PAGES: command = (count > 0 ? "MovePageDown" : "MovePageUp"); break; case GTK_MOVEMENT_BUFFER_ENDS: command = (count > 0 ? "MoveToEndOfDocument" : "MoveToBeginningOfDocument"); break; default: // GTK_MOVEMENT_PARAGRAPHS and GTK_MOVEMENT_HORIZONTAL_PAGES have // no corresponding editor commands. return; } GtkKeyBindingsHandler* owner = GetHandlerOwner(text_view); if (extend_selection) command.append("AndModifySelection"); if (count < 0) count = -count; for (; count > 0; --count) owner->EditCommandMatched(command, ""); } void GtkKeyBindingsHandler::MoveViewport( GtkTextView* text_view, GtkScrollStep step, gint count) { // Not supported by webkit. #if !GTK_CHECK_VERSION(2, 14, 0) // Before gtk 2.14.0, there is no way to override a non-virtual default signal // handler, so we need stop the signal emission explicitly to prevent the // default handler from being executed. g_signal_stop_emission_by_name(text_view, "move-viewport"); #endif } void GtkKeyBindingsHandler::PasteClipboard(GtkTextView* text_view) { GetHandlerOwner(text_view)->EditCommandMatched("Paste", ""); } void GtkKeyBindingsHandler::SelectAll(GtkTextView* text_view, gboolean select) { if (select) GetHandlerOwner(text_view)->EditCommandMatched("SelectAll", ""); else GetHandlerOwner(text_view)->EditCommandMatched("Unselect", ""); #if !GTK_CHECK_VERSION(2, 14, 0) // Before gtk 2.14.0, there is no way to override a non-virtual default signal // handler, so we need stop the signal emission explicitly to prevent the // default handler from being executed. g_signal_stop_emission_by_name(text_view, "select-all"); #endif } void GtkKeyBindingsHandler::SetAnchor(GtkTextView* text_view) { GetHandlerOwner(text_view)->EditCommandMatched("SetMark", ""); } void GtkKeyBindingsHandler::ToggleCursorVisible(GtkTextView* text_view) { // Not supported by webkit. #if !GTK_CHECK_VERSION(2, 14, 0) // Before gtk 2.14.0, there is no way to override a non-virtual default signal // handler, so we need stop the signal emission explicitly to prevent the // default handler from being executed. g_signal_stop_emission_by_name(text_view, "toggle-cursor-visible"); #endif } void GtkKeyBindingsHandler::ToggleOverwrite(GtkTextView* text_view) { // Not supported by webkit. } gboolean GtkKeyBindingsHandler::ShowHelp(GtkWidget* widget, GtkWidgetHelpType arg1) { // Just for disabling the default handler. return FALSE; } void GtkKeyBindingsHandler::MoveFocus(GtkWidget* widget, GtkDirectionType arg1) { // Just for disabling the default handler. #if !GTK_CHECK_VERSION(2, 14, 0) // Before gtk 2.14.0, there is no way to override a non-virtual default signal // handler, so we need stop the signal emission explicitly to prevent the // default handler from being executed. g_signal_stop_emission_by_name(widget, "move-focus"); #endif }