Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / libgtk2ui / gtk2_key_bindings_handler.cc
blobaad16ccddc24e22da85396a93a6cebf846ad26cb
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.
5 #include "chrome/browser/ui/libgtk2ui/gtk2_key_bindings_handler.h"
7 #include <gdk/gdkkeysyms.h>
8 #include <X11/Xlib.h>
9 #include <X11/XKBlib.h>
11 #include <string>
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"
20 using ui::TextEditCommandAuraLinux;
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.
29 namespace libgtk2ui {
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());
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);
44 Gtk2KeyBindingsHandler::~Gtk2KeyBindingsHandler() {
45 handler_.Destroy();
46 gtk_widget_destroy(fake_window_);
49 bool Gtk2KeyBindingsHandler::MatchEvent(
50 const ui::Event& event,
51 std::vector<TextEditCommandAuraLinux>* edit_commands) {
52 CHECK(event.IsKeyEvent());
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;
58 GdkEventKey gdk_event;
59 BuildGdkEventKeyFromXEvent(key_event.native_event(), &gdk_event);
61 edit_commands_.clear();
62 // If this key event matches a predefined key binding, corresponding signal
63 // will be emitted.
65 gtk_bindings_activate_event(
66 #if GDK_MAJOR_VERSION >= 3
67 G_OBJECT(handler_.get()),
68 #else
69 GTK_OBJECT(handler_.get()),
70 #endif
71 &gdk_event);
73 bool matched = !edit_commands_.empty();
74 if (edit_commands)
75 edit_commands->swap(edit_commands_);
76 return matched;
79 GtkWidget* Gtk2KeyBindingsHandler::CreateNewHandler() {
80 Handler* handler =
81 static_cast<Handler*>(g_object_new(HandlerGetType(), NULL));
83 handler->owner = this;
85 // We don't need to show the |handler| object on screen, so set its size to
86 // zero.
87 gtk_widget_set_size_request(GTK_WIDGET(handler), 0, 0);
89 // Prevents it from handling any events by itself.
90 gtk_widget_set_sensitive(GTK_WIDGET(handler), FALSE);
91 gtk_widget_set_events(GTK_WIDGET(handler), 0);
92 gtk_widget_set_can_focus(GTK_WIDGET(handler), TRUE);
94 return GTK_WIDGET(handler);
97 void Gtk2KeyBindingsHandler::EditCommandMatched(
98 TextEditCommandAuraLinux::CommandId id,
99 const std::string& value,
100 bool extend_selection) {
101 edit_commands_.push_back(TextEditCommandAuraLinux(id,
102 value,
103 extend_selection));
106 void Gtk2KeyBindingsHandler::BuildGdkEventKeyFromXEvent(
107 const base::NativeEvent& xevent,
108 GdkEventKey* gdk_event) {
109 GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
110 GdkModifierType consumed, state;
112 gdk_event->type = xevent->xany.type == KeyPress ?
113 GDK_KEY_PRESS : GDK_KEY_RELEASE;
114 gdk_event->time = xevent->xkey.time;
115 gdk_event->state = static_cast<GdkModifierType>(xevent->xkey.state);
116 gdk_event->hardware_keycode = xevent->xkey.keycode;
118 if (has_xkb_) {
119 gdk_event->group = XkbGroupForCoreState(xevent->xkey.state);
120 } else {
121 // The overwhelming majority of people will be using X servers that support
122 // XKB. GDK has a fallback here that does some complicated stuff to detect
123 // whether a modifier key affects the keybinding, but that should be
124 // extremely rare.
125 static bool logged = false;
126 if (!logged) {
127 NOTIMPLEMENTED();
128 logged = true;
130 gdk_event->group = 0;
133 gdk_event->keyval = GDK_KEY_VoidSymbol;
134 gdk_keymap_translate_keyboard_state(
135 keymap,
136 gdk_event->hardware_keycode,
137 static_cast<GdkModifierType>(gdk_event->state),
138 gdk_event->group,
139 &gdk_event->keyval,
140 NULL, NULL, &consumed);
142 state = static_cast<GdkModifierType>(gdk_event->state & ~consumed);
143 gdk_keymap_add_virtual_modifiers(keymap, &state);
144 gdk_event->state |= state;
147 void Gtk2KeyBindingsHandler::HandlerInit(Handler *self) {
148 self->owner = NULL;
151 void Gtk2KeyBindingsHandler::HandlerClassInit(HandlerClass *klass) {
152 GtkTextViewClass* text_view_class = GTK_TEXT_VIEW_CLASS(klass);
153 GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
155 // Overrides all virtual methods related to editor key bindings.
156 text_view_class->backspace = BackSpace;
157 text_view_class->copy_clipboard = CopyClipboard;
158 text_view_class->cut_clipboard = CutClipboard;
159 text_view_class->delete_from_cursor = DeleteFromCursor;
160 text_view_class->insert_at_cursor = InsertAtCursor;
161 text_view_class->move_cursor = MoveCursor;
162 text_view_class->paste_clipboard = PasteClipboard;
163 text_view_class->set_anchor = SetAnchor;
164 text_view_class->toggle_overwrite = ToggleOverwrite;
165 widget_class->show_help = ShowHelp;
167 // "move-focus", "move-viewport", "select-all" and "toggle-cursor-visible"
168 // have no corresponding virtual methods. Since glib 2.18 (gtk 2.14),
169 // g_signal_override_class_handler() is introduced to override a signal
170 // handler.
171 g_signal_override_class_handler("move-focus",
172 G_TYPE_FROM_CLASS(klass),
173 G_CALLBACK(MoveFocus));
175 g_signal_override_class_handler("move-viewport",
176 G_TYPE_FROM_CLASS(klass),
177 G_CALLBACK(MoveViewport));
179 g_signal_override_class_handler("select-all",
180 G_TYPE_FROM_CLASS(klass),
181 G_CALLBACK(SelectAll));
183 g_signal_override_class_handler("toggle-cursor-visible",
184 G_TYPE_FROM_CLASS(klass),
185 G_CALLBACK(ToggleCursorVisible));
188 GType Gtk2KeyBindingsHandler::HandlerGetType() {
189 static volatile gsize type_id_volatile = 0;
190 if (g_once_init_enter(&type_id_volatile)) {
191 GType type_id = g_type_register_static_simple(
192 GTK_TYPE_TEXT_VIEW,
193 g_intern_static_string("Gtk2KeyBindingsHandler"),
194 sizeof(HandlerClass),
195 reinterpret_cast<GClassInitFunc>(HandlerClassInit),
196 sizeof(Handler),
197 reinterpret_cast<GInstanceInitFunc>(HandlerInit),
198 static_cast<GTypeFlags>(0));
199 g_once_init_leave(&type_id_volatile, type_id);
201 return type_id_volatile;
204 Gtk2KeyBindingsHandler* Gtk2KeyBindingsHandler::GetHandlerOwner(
205 GtkTextView* text_view) {
206 Handler* handler = G_TYPE_CHECK_INSTANCE_CAST(
207 text_view, HandlerGetType(), Handler);
208 DCHECK(handler);
209 return handler->owner;
212 void Gtk2KeyBindingsHandler::BackSpace(GtkTextView* text_view) {
213 GetHandlerOwner(text_view)
214 ->EditCommandMatched(
215 TextEditCommandAuraLinux::DELETE_BACKWARD, std::string(), false);
218 void Gtk2KeyBindingsHandler::CopyClipboard(GtkTextView* text_view) {
219 GetHandlerOwner(text_view)->EditCommandMatched(
220 TextEditCommandAuraLinux::COPY, std::string(), false);
223 void Gtk2KeyBindingsHandler::CutClipboard(GtkTextView* text_view) {
224 GetHandlerOwner(text_view)->EditCommandMatched(
225 TextEditCommandAuraLinux::CUT, std::string(), false);
228 void Gtk2KeyBindingsHandler::DeleteFromCursor(
229 GtkTextView* text_view, GtkDeleteType type, gint count) {
230 if (!count)
231 return;
233 TextEditCommandAuraLinux::CommandId commands[2] = {
234 TextEditCommandAuraLinux::INVALID_COMMAND,
235 TextEditCommandAuraLinux::INVALID_COMMAND,
237 switch (type) {
238 case GTK_DELETE_CHARS:
239 commands[0] = (count > 0 ?
240 TextEditCommandAuraLinux::DELETE_FORWARD :
241 TextEditCommandAuraLinux::DELETE_BACKWARD);
242 break;
243 case GTK_DELETE_WORD_ENDS:
244 commands[0] = (count > 0 ?
245 TextEditCommandAuraLinux::DELETE_WORD_FORWARD :
246 TextEditCommandAuraLinux::DELETE_WORD_BACKWARD);
247 break;
248 case GTK_DELETE_WORDS:
249 if (count > 0) {
250 commands[0] = TextEditCommandAuraLinux::MOVE_WORD_FORWARD;
251 commands[1] = TextEditCommandAuraLinux::DELETE_WORD_BACKWARD;
252 } else {
253 commands[0] = TextEditCommandAuraLinux::MOVE_WORD_BACKWARD;
254 commands[1] = TextEditCommandAuraLinux::DELETE_WORD_FORWARD;
256 break;
257 case GTK_DELETE_DISPLAY_LINES:
258 commands[0] = TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_LINE;
259 commands[1] = TextEditCommandAuraLinux::DELETE_TO_END_OF_LINE;
260 break;
261 case GTK_DELETE_DISPLAY_LINE_ENDS:
262 commands[0] = (count > 0 ?
263 TextEditCommandAuraLinux::DELETE_TO_END_OF_LINE :
264 TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_LINE);
265 break;
266 case GTK_DELETE_PARAGRAPH_ENDS:
267 commands[0] = (count > 0 ?
268 TextEditCommandAuraLinux::DELETE_TO_END_OF_PARAGRAPH :
269 TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_PARAGRAPH);
270 break;
271 case GTK_DELETE_PARAGRAPHS:
272 commands[0] =
273 TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_PARAGRAPH;
274 commands[1] =
275 TextEditCommandAuraLinux::DELETE_TO_END_OF_PARAGRAPH;
276 break;
277 default:
278 // GTK_DELETE_WHITESPACE has no corresponding editor command.
279 return;
282 Gtk2KeyBindingsHandler* owner = GetHandlerOwner(text_view);
283 if (count < 0)
284 count = -count;
285 for (; count > 0; --count) {
286 for (size_t i = 0; i < arraysize(commands); ++i)
287 if (commands[i] != TextEditCommandAuraLinux::INVALID_COMMAND)
288 owner->EditCommandMatched(commands[i], std::string(), false);
292 void Gtk2KeyBindingsHandler::InsertAtCursor(GtkTextView* text_view,
293 const gchar* str) {
294 if (str && *str)
295 GetHandlerOwner(text_view)->EditCommandMatched(
296 TextEditCommandAuraLinux::INSERT_TEXT, str, false);
299 void Gtk2KeyBindingsHandler::MoveCursor(
300 GtkTextView* text_view, GtkMovementStep step, gint count,
301 gboolean extend_selection) {
302 if (!count)
303 return;
305 TextEditCommandAuraLinux::CommandId command;
306 switch (step) {
307 case GTK_MOVEMENT_LOGICAL_POSITIONS:
308 command = (count > 0 ?
309 TextEditCommandAuraLinux::MOVE_FORWARD :
310 TextEditCommandAuraLinux::MOVE_BACKWARD);
311 break;
312 case GTK_MOVEMENT_VISUAL_POSITIONS:
313 command = (count > 0 ?
314 TextEditCommandAuraLinux::MOVE_RIGHT :
315 TextEditCommandAuraLinux::MOVE_LEFT);
316 break;
317 case GTK_MOVEMENT_WORDS:
318 command = (count > 0 ?
319 TextEditCommandAuraLinux::MOVE_WORD_RIGHT :
320 TextEditCommandAuraLinux::MOVE_WORD_LEFT);
321 break;
322 case GTK_MOVEMENT_DISPLAY_LINES:
323 command = (count > 0 ?
324 TextEditCommandAuraLinux::MOVE_DOWN :
325 TextEditCommandAuraLinux::MOVE_UP);
326 break;
327 case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
328 command = (count > 0 ?
329 TextEditCommandAuraLinux::MOVE_TO_END_OF_LINE :
330 TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_LINE);
331 break;
332 case GTK_MOVEMENT_PARAGRAPH_ENDS:
333 command = (count > 0 ?
334 TextEditCommandAuraLinux::MOVE_TO_END_OF_PARAGRAPH :
335 TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_PARAGRAPH);
336 break;
337 case GTK_MOVEMENT_PAGES:
338 command = (count > 0 ? TextEditCommandAuraLinux::MOVE_PAGE_DOWN :
339 TextEditCommandAuraLinux::MOVE_PAGE_UP);
340 break;
341 case GTK_MOVEMENT_BUFFER_ENDS:
342 command = (count > 0 ? TextEditCommandAuraLinux::MOVE_TO_END_OF_DOCUMENT :
343 TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_DOCUMENT);
344 break;
345 default:
346 // GTK_MOVEMENT_PARAGRAPHS and GTK_MOVEMENT_HORIZONTAL_PAGES have
347 // no corresponding editor commands.
348 return;
351 Gtk2KeyBindingsHandler* owner = GetHandlerOwner(text_view);
352 if (count < 0)
353 count = -count;
354 for (; count > 0; --count)
355 owner->EditCommandMatched(command, std::string(), extend_selection);
358 void Gtk2KeyBindingsHandler::MoveViewport(
359 GtkTextView* text_view, GtkScrollStep step, gint count) {
360 // Not supported by webkit.
363 void Gtk2KeyBindingsHandler::PasteClipboard(GtkTextView* text_view) {
364 GetHandlerOwner(text_view)->EditCommandMatched(
365 TextEditCommandAuraLinux::PASTE, std::string(), false);
368 void Gtk2KeyBindingsHandler::SelectAll(GtkTextView* text_view,
369 gboolean select) {
370 if (select) {
371 GetHandlerOwner(text_view)->EditCommandMatched(
372 TextEditCommandAuraLinux::SELECT_ALL, std::string(), false);
373 } else {
374 GetHandlerOwner(text_view)->EditCommandMatched(
375 TextEditCommandAuraLinux::UNSELECT, std::string(), false);
379 void Gtk2KeyBindingsHandler::SetAnchor(GtkTextView* text_view) {
380 GetHandlerOwner(text_view)->EditCommandMatched(
381 TextEditCommandAuraLinux::SET_MARK, std::string(), false);
384 void Gtk2KeyBindingsHandler::ToggleCursorVisible(GtkTextView* text_view) {
385 // Not supported by webkit.
388 void Gtk2KeyBindingsHandler::ToggleOverwrite(GtkTextView* text_view) {
389 // Not supported by webkit.
392 gboolean Gtk2KeyBindingsHandler::ShowHelp(GtkWidget* widget,
393 GtkWidgetHelpType arg1) {
394 // Just for disabling the default handler.
395 return FALSE;
398 void Gtk2KeyBindingsHandler::MoveFocus(GtkWidget* widget,
399 GtkDirectionType arg1) {
400 // Just for disabling the default handler.
403 } // namespace libgtk2ui