OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2012 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/ui/libgtk2ui/gtk2_key_bindings_handler.h" |
| 6 |
| 7 #include <gdk/gdkkeysyms.h> |
| 8 #include <X11/Xlib.h> |
| 9 #include <X11/XKBlib.h> |
| 10 |
| 11 #include <string> |
| 12 |
| 13 #include "base/logging.h" |
| 14 #include "base/strings/string_util.h" |
| 15 #include "chrome/browser/ui/libgtk2ui/gtk2_util.h" |
| 16 #include "content/public/browser/native_web_keyboard_event.h" |
| 17 #include "ui/base/x/x11_util.h" |
| 18 #include "ui/events/event.h" |
| 19 |
| 20 using ui::TextEditCommandX11; |
| 21 |
| 22 // TODO(erg): Rewrite the old gtk_key_bindings_handler_unittest.cc and get them |
| 23 // in a state that links. This code was adapted from the content layer GTK |
| 24 // code, which had some simple unit tests. However, the changes in the public |
| 25 // interface basically meant the tests need to be rewritten; this imposes weird |
| 26 // linking requirements regarding GTK+ as we don't have a libgtk2ui_unittests |
| 27 // yet. http://crbug.com/358297. |
| 28 |
| 29 namespace libgtk2ui { |
| 30 |
| 31 Gtk2KeyBindingsHandler::Gtk2KeyBindingsHandler() |
| 32 : fake_window_(gtk_offscreen_window_new()), |
| 33 handler_(CreateNewHandler()), |
| 34 has_xkb_(false) { |
| 35 gtk_container_add(GTK_CONTAINER(fake_window_), handler_.get()); |
| 36 |
| 37 int opcode, event, error; |
| 38 int major = XkbMajorVersion; |
| 39 int minor = XkbMinorVersion; |
| 40 has_xkb_ = XkbQueryExtension(gfx::GetXDisplay(), &opcode, &event, &error, |
| 41 &major, &minor); |
| 42 } |
| 43 |
| 44 Gtk2KeyBindingsHandler::~Gtk2KeyBindingsHandler() { |
| 45 handler_.Destroy(); |
| 46 gtk_widget_destroy(fake_window_); |
| 47 } |
| 48 |
| 49 bool Gtk2KeyBindingsHandler::MatchEvent( |
| 50 const ui::Event& event, |
| 51 std::vector<TextEditCommandX11>* edit_commands) { |
| 52 CHECK(event.IsKeyEvent()); |
| 53 |
| 54 const ui::KeyEvent& key_event = static_cast<const ui::KeyEvent&>(event); |
| 55 if (key_event.is_char() || !key_event.native_event()) |
| 56 return false; |
| 57 |
| 58 GdkEventKey gdk_event; |
| 59 BuildGdkEventKeyFromXEvent(key_event.native_event(), &gdk_event); |
| 60 |
| 61 edit_commands_.clear(); |
| 62 // If this key event matches a predefined key binding, corresponding signal |
| 63 // will be emitted. |
| 64 gtk_bindings_activate_event(GTK_OBJECT(handler_.get()), &gdk_event); |
| 65 |
| 66 bool matched = !edit_commands_.empty(); |
| 67 if (edit_commands) |
| 68 edit_commands->swap(edit_commands_); |
| 69 return matched; |
| 70 } |
| 71 |
| 72 GtkWidget* Gtk2KeyBindingsHandler::CreateNewHandler() { |
| 73 Handler* handler = |
| 74 static_cast<Handler*>(g_object_new(HandlerGetType(), NULL)); |
| 75 |
| 76 handler->owner = this; |
| 77 |
| 78 // We don't need to show the |handler| object on screen, so set its size to |
| 79 // zero. |
| 80 gtk_widget_set_size_request(GTK_WIDGET(handler), 0, 0); |
| 81 |
| 82 // Prevents it from handling any events by itself. |
| 83 gtk_widget_set_sensitive(GTK_WIDGET(handler), FALSE); |
| 84 gtk_widget_set_events(GTK_WIDGET(handler), 0); |
| 85 gtk_widget_set_can_focus(GTK_WIDGET(handler), TRUE); |
| 86 |
| 87 return GTK_WIDGET(handler); |
| 88 } |
| 89 |
| 90 void Gtk2KeyBindingsHandler::EditCommandMatched( |
| 91 TextEditCommandX11::CommandId id, |
| 92 const std::string& value, |
| 93 bool extend_selection) { |
| 94 edit_commands_.push_back(TextEditCommandX11(id, value, extend_selection)); |
| 95 } |
| 96 |
| 97 void Gtk2KeyBindingsHandler::BuildGdkEventKeyFromXEvent( |
| 98 const base::NativeEvent& xevent, |
| 99 GdkEventKey* gdk_event) { |
| 100 GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default()); |
| 101 GdkModifierType consumed, state; |
| 102 |
| 103 gdk_event->type = xevent->xany.type == KeyPress ? |
| 104 GDK_KEY_PRESS : GDK_KEY_RELEASE; |
| 105 gdk_event->time = xevent->xkey.time; |
| 106 gdk_event->state = static_cast<GdkModifierType>(xevent->xkey.state); |
| 107 gdk_event->hardware_keycode = xevent->xkey.keycode; |
| 108 |
| 109 if (has_xkb_) { |
| 110 gdk_event->group = XkbGroupForCoreState(xevent->xkey.state); |
| 111 } else { |
| 112 // The overwhelming majority of people will be using X servers that support |
| 113 // XKB. GDK has a fallback here that does some complicated stuff to detect |
| 114 // whether a modifier key affects the keybinding, but that should be |
| 115 // extremely rare. |
| 116 NOTIMPLEMENTED(); |
| 117 gdk_event->group = 0; |
| 118 } |
| 119 |
| 120 gdk_event->keyval = GDK_VoidSymbol; |
| 121 gdk_keymap_translate_keyboard_state( |
| 122 keymap, |
| 123 gdk_event->hardware_keycode, |
| 124 static_cast<GdkModifierType>(gdk_event->state), |
| 125 gdk_event->group, |
| 126 &gdk_event->keyval, |
| 127 NULL, NULL, &consumed); |
| 128 |
| 129 state = static_cast<GdkModifierType>(gdk_event->state & ~consumed); |
| 130 gdk_keymap_add_virtual_modifiers(keymap, &state); |
| 131 gdk_event->state |= state; |
| 132 } |
| 133 |
| 134 void Gtk2KeyBindingsHandler::HandlerInit(Handler *self) { |
| 135 self->owner = NULL; |
| 136 } |
| 137 |
| 138 void Gtk2KeyBindingsHandler::HandlerClassInit(HandlerClass *klass) { |
| 139 GtkTextViewClass* text_view_class = GTK_TEXT_VIEW_CLASS(klass); |
| 140 GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass); |
| 141 |
| 142 // Overrides all virtual methods related to editor key bindings. |
| 143 text_view_class->backspace = BackSpace; |
| 144 text_view_class->copy_clipboard = CopyClipboard; |
| 145 text_view_class->cut_clipboard = CutClipboard; |
| 146 text_view_class->delete_from_cursor = DeleteFromCursor; |
| 147 text_view_class->insert_at_cursor = InsertAtCursor; |
| 148 text_view_class->move_cursor = MoveCursor; |
| 149 text_view_class->paste_clipboard = PasteClipboard; |
| 150 text_view_class->set_anchor = SetAnchor; |
| 151 text_view_class->toggle_overwrite = ToggleOverwrite; |
| 152 widget_class->show_help = ShowHelp; |
| 153 |
| 154 // "move-focus", "move-viewport", "select-all" and "toggle-cursor-visible" |
| 155 // have no corresponding virtual methods. Since glib 2.18 (gtk 2.14), |
| 156 // g_signal_override_class_handler() is introduced to override a signal |
| 157 // handler. |
| 158 g_signal_override_class_handler("move-focus", |
| 159 G_TYPE_FROM_CLASS(klass), |
| 160 G_CALLBACK(MoveFocus)); |
| 161 |
| 162 g_signal_override_class_handler("move-viewport", |
| 163 G_TYPE_FROM_CLASS(klass), |
| 164 G_CALLBACK(MoveViewport)); |
| 165 |
| 166 g_signal_override_class_handler("select-all", |
| 167 G_TYPE_FROM_CLASS(klass), |
| 168 G_CALLBACK(SelectAll)); |
| 169 |
| 170 g_signal_override_class_handler("toggle-cursor-visible", |
| 171 G_TYPE_FROM_CLASS(klass), |
| 172 G_CALLBACK(ToggleCursorVisible)); |
| 173 } |
| 174 |
| 175 GType Gtk2KeyBindingsHandler::HandlerGetType() { |
| 176 static volatile gsize type_id_volatile = 0; |
| 177 if (g_once_init_enter(&type_id_volatile)) { |
| 178 GType type_id = g_type_register_static_simple( |
| 179 GTK_TYPE_TEXT_VIEW, |
| 180 g_intern_static_string("Gtk2KeyBindingsHandler"), |
| 181 sizeof(HandlerClass), |
| 182 reinterpret_cast<GClassInitFunc>(HandlerClassInit), |
| 183 sizeof(Handler), |
| 184 reinterpret_cast<GInstanceInitFunc>(HandlerInit), |
| 185 static_cast<GTypeFlags>(0)); |
| 186 g_once_init_leave(&type_id_volatile, type_id); |
| 187 } |
| 188 return type_id_volatile; |
| 189 } |
| 190 |
| 191 Gtk2KeyBindingsHandler* Gtk2KeyBindingsHandler::GetHandlerOwner( |
| 192 GtkTextView* text_view) { |
| 193 Handler* handler = G_TYPE_CHECK_INSTANCE_CAST( |
| 194 text_view, HandlerGetType(), Handler); |
| 195 DCHECK(handler); |
| 196 return handler->owner; |
| 197 } |
| 198 |
| 199 void Gtk2KeyBindingsHandler::BackSpace(GtkTextView* text_view) { |
| 200 GetHandlerOwner(text_view) |
| 201 ->EditCommandMatched( |
| 202 TextEditCommandX11::DELETE_BACKWARD, std::string(), false); |
| 203 } |
| 204 |
| 205 void Gtk2KeyBindingsHandler::CopyClipboard(GtkTextView* text_view) { |
| 206 GetHandlerOwner(text_view)->EditCommandMatched( |
| 207 TextEditCommandX11::COPY, std::string(), false); |
| 208 } |
| 209 |
| 210 void Gtk2KeyBindingsHandler::CutClipboard(GtkTextView* text_view) { |
| 211 GetHandlerOwner(text_view)->EditCommandMatched( |
| 212 TextEditCommandX11::CUT, std::string(), false); |
| 213 } |
| 214 |
| 215 void Gtk2KeyBindingsHandler::DeleteFromCursor( |
| 216 GtkTextView* text_view, GtkDeleteType type, gint count) { |
| 217 if (!count) |
| 218 return; |
| 219 |
| 220 TextEditCommandX11::CommandId commands[2] = { |
| 221 TextEditCommandX11::INVALID_COMMAND, |
| 222 TextEditCommandX11::INVALID_COMMAND, |
| 223 }; |
| 224 switch (type) { |
| 225 case GTK_DELETE_CHARS: |
| 226 commands[0] = (count > 0 ? |
| 227 TextEditCommandX11::DELETE_FORWARD : |
| 228 TextEditCommandX11::DELETE_BACKWARD); |
| 229 break; |
| 230 case GTK_DELETE_WORD_ENDS: |
| 231 commands[0] = (count > 0 ? |
| 232 TextEditCommandX11::DELETE_WORD_FORWARD : |
| 233 TextEditCommandX11::DELETE_WORD_BACKWARD); |
| 234 break; |
| 235 case GTK_DELETE_WORDS: |
| 236 if (count > 0) { |
| 237 commands[0] = TextEditCommandX11::MOVE_WORD_FORWARD; |
| 238 commands[1] = TextEditCommandX11::DELETE_WORD_BACKWARD; |
| 239 } else { |
| 240 commands[0] = TextEditCommandX11::MOVE_WORD_BACKWARD; |
| 241 commands[1] = TextEditCommandX11::DELETE_WORD_FORWARD; |
| 242 } |
| 243 break; |
| 244 case GTK_DELETE_DISPLAY_LINES: |
| 245 commands[0] = TextEditCommandX11::MOVE_TO_BEGINING_OF_LINE; |
| 246 commands[1] = TextEditCommandX11::DELETE_TO_END_OF_LINE; |
| 247 break; |
| 248 case GTK_DELETE_DISPLAY_LINE_ENDS: |
| 249 commands[0] = (count > 0 ? |
| 250 TextEditCommandX11::DELETE_TO_END_OF_LINE : |
| 251 TextEditCommandX11::DELETE_TO_BEGINING_OF_LINE); |
| 252 break; |
| 253 case GTK_DELETE_PARAGRAPH_ENDS: |
| 254 commands[0] = (count > 0 ? |
| 255 TextEditCommandX11::DELETE_TO_END_OF_PARAGRAPH : |
| 256 TextEditCommandX11::DELETE_TO_BEGINING_OF_PARAGRAPH); |
| 257 break; |
| 258 case GTK_DELETE_PARAGRAPHS: |
| 259 commands[0] = |
| 260 TextEditCommandX11::MOVE_TO_BEGINING_OF_PARAGRAPH; |
| 261 commands[1] = |
| 262 TextEditCommandX11::DELETE_TO_END_OF_PARAGRAPH; |
| 263 break; |
| 264 default: |
| 265 // GTK_DELETE_WHITESPACE has no corresponding editor command. |
| 266 return; |
| 267 } |
| 268 |
| 269 Gtk2KeyBindingsHandler* owner = GetHandlerOwner(text_view); |
| 270 if (count < 0) |
| 271 count = -count; |
| 272 for (; count > 0; --count) { |
| 273 for (size_t i = 0; i < arraysize(commands); ++i) |
| 274 if (commands[i] != TextEditCommandX11::INVALID_COMMAND) |
| 275 owner->EditCommandMatched(commands[i], std::string(), false); |
| 276 } |
| 277 } |
| 278 |
| 279 void Gtk2KeyBindingsHandler::InsertAtCursor(GtkTextView* text_view, |
| 280 const gchar* str) { |
| 281 if (str && *str) |
| 282 GetHandlerOwner(text_view)->EditCommandMatched( |
| 283 TextEditCommandX11::INSERT_TEXT, str, false); |
| 284 } |
| 285 |
| 286 void Gtk2KeyBindingsHandler::MoveCursor( |
| 287 GtkTextView* text_view, GtkMovementStep step, gint count, |
| 288 gboolean extend_selection) { |
| 289 if (!count) |
| 290 return; |
| 291 |
| 292 TextEditCommandX11::CommandId command; |
| 293 switch (step) { |
| 294 case GTK_MOVEMENT_LOGICAL_POSITIONS: |
| 295 command = (count > 0 ? |
| 296 TextEditCommandX11::MOVE_FORWARD : |
| 297 TextEditCommandX11::MOVE_BACKWARD); |
| 298 break; |
| 299 case GTK_MOVEMENT_VISUAL_POSITIONS: |
| 300 command = (count > 0 ? |
| 301 TextEditCommandX11::MOVE_RIGHT : |
| 302 TextEditCommandX11::MOVE_LEFT); |
| 303 break; |
| 304 case GTK_MOVEMENT_WORDS: |
| 305 command = (count > 0 ? |
| 306 TextEditCommandX11::MOVE_WORD_RIGHT : |
| 307 TextEditCommandX11::MOVE_WORD_LEFT); |
| 308 break; |
| 309 case GTK_MOVEMENT_DISPLAY_LINES: |
| 310 command = (count > 0 ? |
| 311 TextEditCommandX11::MOVE_DOWN : TextEditCommandX11::MOVE_UP); |
| 312 break; |
| 313 case GTK_MOVEMENT_DISPLAY_LINE_ENDS: |
| 314 command = (count > 0 ? |
| 315 TextEditCommandX11::MOVE_TO_END_OF_LINE : |
| 316 TextEditCommandX11::MOVE_TO_BEGINING_OF_LINE); |
| 317 break; |
| 318 case GTK_MOVEMENT_PARAGRAPH_ENDS: |
| 319 command = (count > 0 ? |
| 320 TextEditCommandX11::MOVE_TO_END_OF_PARAGRAPH : |
| 321 TextEditCommandX11::MOVE_TO_BEGINING_OF_PARAGRAPH); |
| 322 break; |
| 323 case GTK_MOVEMENT_PAGES: |
| 324 command = (count > 0 ? TextEditCommandX11::MOVE_PAGE_DOWN : |
| 325 TextEditCommandX11::MOVE_PAGE_UP); |
| 326 break; |
| 327 case GTK_MOVEMENT_BUFFER_ENDS: |
| 328 command = (count > 0 ? TextEditCommandX11::MOVE_TO_END_OF_DOCUMENT : |
| 329 TextEditCommandX11::MOVE_TO_BEGINING_OF_PARAGRAPH); |
| 330 break; |
| 331 default: |
| 332 // GTK_MOVEMENT_PARAGRAPHS and GTK_MOVEMENT_HORIZONTAL_PAGES have |
| 333 // no corresponding editor commands. |
| 334 return; |
| 335 } |
| 336 |
| 337 Gtk2KeyBindingsHandler* owner = GetHandlerOwner(text_view); |
| 338 if (count < 0) |
| 339 count = -count; |
| 340 for (; count > 0; --count) |
| 341 owner->EditCommandMatched(command, std::string(), extend_selection); |
| 342 } |
| 343 |
| 344 void Gtk2KeyBindingsHandler::MoveViewport( |
| 345 GtkTextView* text_view, GtkScrollStep step, gint count) { |
| 346 // Not supported by webkit. |
| 347 } |
| 348 |
| 349 void Gtk2KeyBindingsHandler::PasteClipboard(GtkTextView* text_view) { |
| 350 GetHandlerOwner(text_view)->EditCommandMatched( |
| 351 TextEditCommandX11::PASTE, std::string(), false); |
| 352 } |
| 353 |
| 354 void Gtk2KeyBindingsHandler::SelectAll(GtkTextView* text_view, |
| 355 gboolean select) { |
| 356 if (select) { |
| 357 GetHandlerOwner(text_view)->EditCommandMatched( |
| 358 TextEditCommandX11::SELECT_ALL, std::string(), false); |
| 359 } else { |
| 360 GetHandlerOwner(text_view)->EditCommandMatched( |
| 361 TextEditCommandX11::UNSELECT, std::string(), false); |
| 362 } |
| 363 } |
| 364 |
| 365 void Gtk2KeyBindingsHandler::SetAnchor(GtkTextView* text_view) { |
| 366 GetHandlerOwner(text_view)->EditCommandMatched( |
| 367 TextEditCommandX11::SET_MARK, std::string(), false); |
| 368 } |
| 369 |
| 370 void Gtk2KeyBindingsHandler::ToggleCursorVisible(GtkTextView* text_view) { |
| 371 // Not supported by webkit. |
| 372 } |
| 373 |
| 374 void Gtk2KeyBindingsHandler::ToggleOverwrite(GtkTextView* text_view) { |
| 375 // Not supported by webkit. |
| 376 } |
| 377 |
| 378 gboolean Gtk2KeyBindingsHandler::ShowHelp(GtkWidget* widget, |
| 379 GtkWidgetHelpType arg1) { |
| 380 // Just for disabling the default handler. |
| 381 return FALSE; |
| 382 } |
| 383 |
| 384 void Gtk2KeyBindingsHandler::MoveFocus(GtkWidget* widget, |
| 385 GtkDirectionType arg1) { |
| 386 // Just for disabling the default handler. |
| 387 } |
| 388 |
| 389 } // namespace libgtk2ui |
OLD | NEW |