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