1// Copyright (c) 2009 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/renderer_host/gtk_key_bindings_handler.h" 6 7#include <gdk/gdkkeysyms.h> 8 9#include <string> 10 11#include "base/logging.h" 12#include "base/string_util.h" 13#include "content/common/native_web_keyboard_event.h" 14 15GtkKeyBindingsHandler::GtkKeyBindingsHandler(GtkWidget* parent_widget) 16 : handler_(CreateNewHandler()) { 17 DCHECK(GTK_IS_FIXED(parent_widget)); 18 // We need add the |handler_| object into gtk widget hierarchy, so that 19 // gtk_bindings_activate_event() can find correct display and keymaps from 20 // the |handler_| object. 21 gtk_fixed_put(GTK_FIXED(parent_widget), handler_.get(), -1, -1); 22} 23 24GtkKeyBindingsHandler::~GtkKeyBindingsHandler() { 25 handler_.Destroy(); 26} 27 28bool GtkKeyBindingsHandler::Match(const NativeWebKeyboardEvent& wke, 29 EditCommands* edit_commands) { 30 if (wke.type == WebKit::WebInputEvent::Char || !wke.os_event) 31 return false; 32 33 edit_commands_.clear(); 34 // If this key event matches a predefined key binding, corresponding signal 35 // will be emitted. 36 gtk_bindings_activate_event(GTK_OBJECT(handler_.get()), wke.os_event); 37 38 bool matched = !edit_commands_.empty(); 39 if (edit_commands) 40 edit_commands->swap(edit_commands_); 41 return matched; 42} 43 44GtkWidget* GtkKeyBindingsHandler::CreateNewHandler() { 45 Handler* handler = 46 static_cast<Handler*>(g_object_new(HandlerGetType(), NULL)); 47 48 handler->owner = this; 49 50 // We don't need to show the |handler| object on screen, so set its size to 51 // zero. 52 gtk_widget_set_size_request(GTK_WIDGET(handler), 0, 0); 53 54 // Prevents it from handling any events by itself. 55 gtk_widget_set_sensitive(GTK_WIDGET(handler), FALSE); 56 gtk_widget_set_events(GTK_WIDGET(handler), 0); 57 GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(handler), GTK_CAN_FOCUS); 58 59#if !GTK_CHECK_VERSION(2, 14, 0) 60 // "move-focus", "move-viewport", "select-all" and "toggle-cursor-visible" 61 // have no corresponding virtual methods. Prior to glib 2.18 (gtk 2.14), 62 // there is no way to override the default class handler of a signal. 63 // So we need hook these signal explicitly. 64 g_signal_connect(handler, "move-focus", G_CALLBACK(MoveFocus), NULL); 65 g_signal_connect(handler, "move-viewport", G_CALLBACK(MoveViewport), NULL); 66 g_signal_connect(handler, "select-all", G_CALLBACK(SelectAll), NULL); 67 g_signal_connect(handler, "toggle-cursor-visible", 68 G_CALLBACK(ToggleCursorVisible), NULL); 69#endif 70 return GTK_WIDGET(handler); 71} 72 73void GtkKeyBindingsHandler::EditCommandMatched( 74 const std::string& name, const std::string& value) { 75 edit_commands_.push_back(EditCommand(name, value)); 76} 77 78void GtkKeyBindingsHandler::HandlerInit(Handler *self) { 79 self->owner = NULL; 80} 81 82void GtkKeyBindingsHandler::HandlerClassInit(HandlerClass *klass) { 83 GtkTextViewClass* text_view_class = GTK_TEXT_VIEW_CLASS(klass); 84 GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass); 85 86 // Overrides all virtual methods related to editor key bindings. 87 text_view_class->backspace = BackSpace; 88 text_view_class->copy_clipboard = CopyClipboard; 89 text_view_class->cut_clipboard = CutClipboard; 90 text_view_class->delete_from_cursor = DeleteFromCursor; 91 text_view_class->insert_at_cursor = InsertAtCursor; 92 text_view_class->move_cursor = MoveCursor; 93 text_view_class->paste_clipboard = PasteClipboard; 94 text_view_class->set_anchor = SetAnchor; 95 text_view_class->toggle_overwrite = ToggleOverwrite; 96 widget_class->show_help = ShowHelp; 97 98#if GTK_CHECK_VERSION(2, 14, 0) 99 // "move-focus", "move-viewport", "select-all" and "toggle-cursor-visible" 100 // have no corresponding virtual methods. Since glib 2.18 (gtk 2.14), 101 // g_signal_override_class_handler() is introduced to override a signal 102 // handler. 103 g_signal_override_class_handler("move-focus", 104 G_TYPE_FROM_CLASS(klass), 105 G_CALLBACK(MoveFocus)); 106 107 g_signal_override_class_handler("move-viewport", 108 G_TYPE_FROM_CLASS(klass), 109 G_CALLBACK(MoveViewport)); 110 111 g_signal_override_class_handler("select-all", 112 G_TYPE_FROM_CLASS(klass), 113 G_CALLBACK(SelectAll)); 114 115 g_signal_override_class_handler("toggle-cursor-visible", 116 G_TYPE_FROM_CLASS(klass), 117 G_CALLBACK(ToggleCursorVisible)); 118#endif 119} 120 121GType GtkKeyBindingsHandler::HandlerGetType() { 122 static volatile gsize type_id_volatile = 0; 123 if (g_once_init_enter(&type_id_volatile)) { 124 GType type_id = g_type_register_static_simple( 125 GTK_TYPE_TEXT_VIEW, 126 g_intern_static_string("GtkKeyBindingsHandler"), 127 sizeof(HandlerClass), 128 reinterpret_cast<GClassInitFunc>(HandlerClassInit), 129 sizeof(Handler), 130 reinterpret_cast<GInstanceInitFunc>(HandlerInit), 131 static_cast<GTypeFlags>(0)); 132 g_once_init_leave(&type_id_volatile, type_id); 133 } 134 return type_id_volatile; 135} 136 137GtkKeyBindingsHandler* GtkKeyBindingsHandler::GetHandlerOwner( 138 GtkTextView* text_view) { 139 Handler* handler = G_TYPE_CHECK_INSTANCE_CAST( 140 text_view, HandlerGetType(), Handler); 141 DCHECK(handler); 142 return handler->owner; 143} 144 145void GtkKeyBindingsHandler::BackSpace(GtkTextView* text_view) { 146 GetHandlerOwner(text_view)->EditCommandMatched("DeleteBackward", ""); 147} 148 149void GtkKeyBindingsHandler::CopyClipboard(GtkTextView* text_view) { 150 GetHandlerOwner(text_view)->EditCommandMatched("Copy", ""); 151} 152 153void GtkKeyBindingsHandler::CutClipboard(GtkTextView* text_view) { 154 GetHandlerOwner(text_view)->EditCommandMatched("Cut", ""); 155} 156 157void GtkKeyBindingsHandler::DeleteFromCursor( 158 GtkTextView* text_view, GtkDeleteType type, gint count) { 159 if (!count) 160 return; 161 162 const char *commands[3] = { NULL, NULL, NULL }; 163 switch (type) { 164 case GTK_DELETE_CHARS: 165 commands[0] = (count > 0 ? "DeleteForward" : "DeleteBackward"); 166 break; 167 case GTK_DELETE_WORD_ENDS: 168 commands[0] = (count > 0 ? "DeleteWordForward" : "DeleteWordBackward"); 169 break; 170 case GTK_DELETE_WORDS: 171 if (count > 0) { 172 commands[0] = "MoveWordForward"; 173 commands[1] = "DeleteWordBackward"; 174 } else { 175 commands[0] = "MoveWordBackward"; 176 commands[1] = "DeleteWordForward"; 177 } 178 break; 179 case GTK_DELETE_DISPLAY_LINES: 180 commands[0] = "MoveToBeginningOfLine"; 181 commands[1] = "DeleteToEndOfLine"; 182 break; 183 case GTK_DELETE_DISPLAY_LINE_ENDS: 184 commands[0] = (count > 0 ? "DeleteToEndOfLine" : 185 "DeleteToBeginningOfLine"); 186 break; 187 case GTK_DELETE_PARAGRAPH_ENDS: 188 commands[0] = (count > 0 ? "DeleteToEndOfParagraph" : 189 "DeleteToBeginningOfParagraph"); 190 break; 191 case GTK_DELETE_PARAGRAPHS: 192 commands[0] = "MoveToBeginningOfParagraph"; 193 commands[1] = "DeleteToEndOfParagraph"; 194 break; 195 default: 196 // GTK_DELETE_WHITESPACE has no corresponding editor command. 197 return; 198 } 199 200 GtkKeyBindingsHandler* owner = GetHandlerOwner(text_view); 201 if (count < 0) 202 count = -count; 203 for (; count > 0; --count) { 204 for (const char* const* p = commands; *p; ++p) 205 owner->EditCommandMatched(*p, ""); 206 } 207} 208 209void GtkKeyBindingsHandler::InsertAtCursor(GtkTextView* text_view, 210 const gchar* str) { 211 if (str && *str) 212 GetHandlerOwner(text_view)->EditCommandMatched("InsertText", str); 213} 214 215void GtkKeyBindingsHandler::MoveCursor( 216 GtkTextView* text_view, GtkMovementStep step, gint count, 217 gboolean extend_selection) { 218 if (!count) 219 return; 220 221 std::string command; 222 switch (step) { 223 case GTK_MOVEMENT_LOGICAL_POSITIONS: 224 command = (count > 0 ? "MoveForward" : "MoveBackward"); 225 break; 226 case GTK_MOVEMENT_VISUAL_POSITIONS: 227 command = (count > 0 ? "MoveRight" : "MoveLeft"); 228 break; 229 case GTK_MOVEMENT_WORDS: 230 command = (count > 0 ? "MoveWordForward" : "MoveWordBackward"); 231 break; 232 case GTK_MOVEMENT_DISPLAY_LINES: 233 command = (count > 0 ? "MoveDown" : "MoveUp"); 234 break; 235 case GTK_MOVEMENT_DISPLAY_LINE_ENDS: 236 command = (count > 0 ? "MoveToEndOfLine" : "MoveToBeginningOfLine"); 237 break; 238 case GTK_MOVEMENT_PARAGRAPH_ENDS: 239 command = (count > 0 ? "MoveToEndOfParagraph" : 240 "MoveToBeginningOfParagraph"); 241 break; 242 case GTK_MOVEMENT_PAGES: 243 command = (count > 0 ? "MovePageDown" : "MovePageUp"); 244 break; 245 case GTK_MOVEMENT_BUFFER_ENDS: 246 command = (count > 0 ? "MoveToEndOfDocument" : 247 "MoveToBeginningOfDocument"); 248 break; 249 default: 250 // GTK_MOVEMENT_PARAGRAPHS and GTK_MOVEMENT_HORIZONTAL_PAGES have 251 // no corresponding editor commands. 252 return; 253 } 254 255 GtkKeyBindingsHandler* owner = GetHandlerOwner(text_view); 256 if (extend_selection) 257 command.append("AndModifySelection"); 258 if (count < 0) 259 count = -count; 260 for (; count > 0; --count) 261 owner->EditCommandMatched(command, ""); 262} 263 264void GtkKeyBindingsHandler::MoveViewport( 265 GtkTextView* text_view, GtkScrollStep step, gint count) { 266 // Not supported by webkit. 267#if !GTK_CHECK_VERSION(2, 14, 0) 268 // Before gtk 2.14.0, there is no way to override a non-virtual default signal 269 // handler, so we need stop the signal emission explicitly to prevent the 270 // default handler from being executed. 271 g_signal_stop_emission_by_name(text_view, "move-viewport"); 272#endif 273} 274 275void GtkKeyBindingsHandler::PasteClipboard(GtkTextView* text_view) { 276 GetHandlerOwner(text_view)->EditCommandMatched("Paste", ""); 277} 278 279void GtkKeyBindingsHandler::SelectAll(GtkTextView* text_view, gboolean select) { 280 if (select) 281 GetHandlerOwner(text_view)->EditCommandMatched("SelectAll", ""); 282 else 283 GetHandlerOwner(text_view)->EditCommandMatched("Unselect", ""); 284#if !GTK_CHECK_VERSION(2, 14, 0) 285 // Before gtk 2.14.0, there is no way to override a non-virtual default signal 286 // handler, so we need stop the signal emission explicitly to prevent the 287 // default handler from being executed. 288 g_signal_stop_emission_by_name(text_view, "select-all"); 289#endif 290} 291 292void GtkKeyBindingsHandler::SetAnchor(GtkTextView* text_view) { 293 GetHandlerOwner(text_view)->EditCommandMatched("SetMark", ""); 294} 295 296void GtkKeyBindingsHandler::ToggleCursorVisible(GtkTextView* text_view) { 297 // Not supported by webkit. 298#if !GTK_CHECK_VERSION(2, 14, 0) 299 // Before gtk 2.14.0, there is no way to override a non-virtual default signal 300 // handler, so we need stop the signal emission explicitly to prevent the 301 // default handler from being executed. 302 g_signal_stop_emission_by_name(text_view, "toggle-cursor-visible"); 303#endif 304} 305 306void GtkKeyBindingsHandler::ToggleOverwrite(GtkTextView* text_view) { 307 // Not supported by webkit. 308} 309 310gboolean GtkKeyBindingsHandler::ShowHelp(GtkWidget* widget, 311 GtkWidgetHelpType arg1) { 312 // Just for disabling the default handler. 313 return FALSE; 314} 315 316void GtkKeyBindingsHandler::MoveFocus(GtkWidget* widget, 317 GtkDirectionType arg1) { 318 // Just for disabling the default handler. 319#if !GTK_CHECK_VERSION(2, 14, 0) 320 // Before gtk 2.14.0, there is no way to override a non-virtual default signal 321 // handler, so we need stop the signal emission explicitly to prevent the 322 // default handler from being executed. 323 g_signal_stop_emission_by_name(widget, "move-focus"); 324#endif 325} 326