Add ability to gather metrics to BubbleManager.
[chromium-blink-merge.git] / chrome / browser / ui / libgtk2ui / gtk2_key_bindings_handler.cc
blobeec9dde2d6dbdf5787e391e62508b9bfcb3d2c51
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.
64 gtk_bindings_activate_event(GTK_OBJECT(handler_.get()), &gdk_event);
66 bool matched = !edit_commands_.empty();
67 if (edit_commands)
68 edit_commands->swap(edit_commands_);
69 return matched;
72 GtkWidget* Gtk2KeyBindingsHandler::CreateNewHandler() {
73 Handler* handler =
74 static_cast<Handler*>(g_object_new(HandlerGetType(), NULL));
76 handler->owner = this;
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);
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);
87 return GTK_WIDGET(handler);
90 void Gtk2KeyBindingsHandler::EditCommandMatched(
91 TextEditCommandAuraLinux::CommandId id,
92 const std::string& value,
93 bool extend_selection) {
94 edit_commands_.push_back(TextEditCommandAuraLinux(id,
95 value,
96 extend_selection));
99 void Gtk2KeyBindingsHandler::BuildGdkEventKeyFromXEvent(
100 const base::NativeEvent& xevent,
101 GdkEventKey* gdk_event) {
102 GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
103 GdkModifierType consumed, state;
105 gdk_event->type = xevent->xany.type == KeyPress ?
106 GDK_KEY_PRESS : GDK_KEY_RELEASE;
107 gdk_event->time = xevent->xkey.time;
108 gdk_event->state = static_cast<GdkModifierType>(xevent->xkey.state);
109 gdk_event->hardware_keycode = xevent->xkey.keycode;
111 if (has_xkb_) {
112 gdk_event->group = XkbGroupForCoreState(xevent->xkey.state);
113 } else {
114 // The overwhelming majority of people will be using X servers that support
115 // XKB. GDK has a fallback here that does some complicated stuff to detect
116 // whether a modifier key affects the keybinding, but that should be
117 // extremely rare.
118 static bool logged = false;
119 if (!logged) {
120 NOTIMPLEMENTED();
121 logged = true;
123 gdk_event->group = 0;
126 gdk_event->keyval = GDK_VoidSymbol;
127 gdk_keymap_translate_keyboard_state(
128 keymap,
129 gdk_event->hardware_keycode,
130 static_cast<GdkModifierType>(gdk_event->state),
131 gdk_event->group,
132 &gdk_event->keyval,
133 NULL, NULL, &consumed);
135 state = static_cast<GdkModifierType>(gdk_event->state & ~consumed);
136 gdk_keymap_add_virtual_modifiers(keymap, &state);
137 gdk_event->state |= state;
140 void Gtk2KeyBindingsHandler::HandlerInit(Handler *self) {
141 self->owner = NULL;
144 void Gtk2KeyBindingsHandler::HandlerClassInit(HandlerClass *klass) {
145 GtkTextViewClass* text_view_class = GTK_TEXT_VIEW_CLASS(klass);
146 GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
148 // Overrides all virtual methods related to editor key bindings.
149 text_view_class->backspace = BackSpace;
150 text_view_class->copy_clipboard = CopyClipboard;
151 text_view_class->cut_clipboard = CutClipboard;
152 text_view_class->delete_from_cursor = DeleteFromCursor;
153 text_view_class->insert_at_cursor = InsertAtCursor;
154 text_view_class->move_cursor = MoveCursor;
155 text_view_class->paste_clipboard = PasteClipboard;
156 text_view_class->set_anchor = SetAnchor;
157 text_view_class->toggle_overwrite = ToggleOverwrite;
158 widget_class->show_help = ShowHelp;
160 // "move-focus", "move-viewport", "select-all" and "toggle-cursor-visible"
161 // have no corresponding virtual methods. Since glib 2.18 (gtk 2.14),
162 // g_signal_override_class_handler() is introduced to override a signal
163 // handler.
164 g_signal_override_class_handler("move-focus",
165 G_TYPE_FROM_CLASS(klass),
166 G_CALLBACK(MoveFocus));
168 g_signal_override_class_handler("move-viewport",
169 G_TYPE_FROM_CLASS(klass),
170 G_CALLBACK(MoveViewport));
172 g_signal_override_class_handler("select-all",
173 G_TYPE_FROM_CLASS(klass),
174 G_CALLBACK(SelectAll));
176 g_signal_override_class_handler("toggle-cursor-visible",
177 G_TYPE_FROM_CLASS(klass),
178 G_CALLBACK(ToggleCursorVisible));
181 GType Gtk2KeyBindingsHandler::HandlerGetType() {
182 static volatile gsize type_id_volatile = 0;
183 if (g_once_init_enter(&type_id_volatile)) {
184 GType type_id = g_type_register_static_simple(
185 GTK_TYPE_TEXT_VIEW,
186 g_intern_static_string("Gtk2KeyBindingsHandler"),
187 sizeof(HandlerClass),
188 reinterpret_cast<GClassInitFunc>(HandlerClassInit),
189 sizeof(Handler),
190 reinterpret_cast<GInstanceInitFunc>(HandlerInit),
191 static_cast<GTypeFlags>(0));
192 g_once_init_leave(&type_id_volatile, type_id);
194 return type_id_volatile;
197 Gtk2KeyBindingsHandler* Gtk2KeyBindingsHandler::GetHandlerOwner(
198 GtkTextView* text_view) {
199 Handler* handler = G_TYPE_CHECK_INSTANCE_CAST(
200 text_view, HandlerGetType(), Handler);
201 DCHECK(handler);
202 return handler->owner;
205 void Gtk2KeyBindingsHandler::BackSpace(GtkTextView* text_view) {
206 GetHandlerOwner(text_view)
207 ->EditCommandMatched(
208 TextEditCommandAuraLinux::DELETE_BACKWARD, std::string(), false);
211 void Gtk2KeyBindingsHandler::CopyClipboard(GtkTextView* text_view) {
212 GetHandlerOwner(text_view)->EditCommandMatched(
213 TextEditCommandAuraLinux::COPY, std::string(), false);
216 void Gtk2KeyBindingsHandler::CutClipboard(GtkTextView* text_view) {
217 GetHandlerOwner(text_view)->EditCommandMatched(
218 TextEditCommandAuraLinux::CUT, std::string(), false);
221 void Gtk2KeyBindingsHandler::DeleteFromCursor(
222 GtkTextView* text_view, GtkDeleteType type, gint count) {
223 if (!count)
224 return;
226 TextEditCommandAuraLinux::CommandId commands[2] = {
227 TextEditCommandAuraLinux::INVALID_COMMAND,
228 TextEditCommandAuraLinux::INVALID_COMMAND,
230 switch (type) {
231 case GTK_DELETE_CHARS:
232 commands[0] = (count > 0 ?
233 TextEditCommandAuraLinux::DELETE_FORWARD :
234 TextEditCommandAuraLinux::DELETE_BACKWARD);
235 break;
236 case GTK_DELETE_WORD_ENDS:
237 commands[0] = (count > 0 ?
238 TextEditCommandAuraLinux::DELETE_WORD_FORWARD :
239 TextEditCommandAuraLinux::DELETE_WORD_BACKWARD);
240 break;
241 case GTK_DELETE_WORDS:
242 if (count > 0) {
243 commands[0] = TextEditCommandAuraLinux::MOVE_WORD_FORWARD;
244 commands[1] = TextEditCommandAuraLinux::DELETE_WORD_BACKWARD;
245 } else {
246 commands[0] = TextEditCommandAuraLinux::MOVE_WORD_BACKWARD;
247 commands[1] = TextEditCommandAuraLinux::DELETE_WORD_FORWARD;
249 break;
250 case GTK_DELETE_DISPLAY_LINES:
251 commands[0] = TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_LINE;
252 commands[1] = TextEditCommandAuraLinux::DELETE_TO_END_OF_LINE;
253 break;
254 case GTK_DELETE_DISPLAY_LINE_ENDS:
255 commands[0] = (count > 0 ?
256 TextEditCommandAuraLinux::DELETE_TO_END_OF_LINE :
257 TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_LINE);
258 break;
259 case GTK_DELETE_PARAGRAPH_ENDS:
260 commands[0] = (count > 0 ?
261 TextEditCommandAuraLinux::DELETE_TO_END_OF_PARAGRAPH :
262 TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_PARAGRAPH);
263 break;
264 case GTK_DELETE_PARAGRAPHS:
265 commands[0] =
266 TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_PARAGRAPH;
267 commands[1] =
268 TextEditCommandAuraLinux::DELETE_TO_END_OF_PARAGRAPH;
269 break;
270 default:
271 // GTK_DELETE_WHITESPACE has no corresponding editor command.
272 return;
275 Gtk2KeyBindingsHandler* owner = GetHandlerOwner(text_view);
276 if (count < 0)
277 count = -count;
278 for (; count > 0; --count) {
279 for (size_t i = 0; i < arraysize(commands); ++i)
280 if (commands[i] != TextEditCommandAuraLinux::INVALID_COMMAND)
281 owner->EditCommandMatched(commands[i], std::string(), false);
285 void Gtk2KeyBindingsHandler::InsertAtCursor(GtkTextView* text_view,
286 const gchar* str) {
287 if (str && *str)
288 GetHandlerOwner(text_view)->EditCommandMatched(
289 TextEditCommandAuraLinux::INSERT_TEXT, str, false);
292 void Gtk2KeyBindingsHandler::MoveCursor(
293 GtkTextView* text_view, GtkMovementStep step, gint count,
294 gboolean extend_selection) {
295 if (!count)
296 return;
298 TextEditCommandAuraLinux::CommandId command;
299 switch (step) {
300 case GTK_MOVEMENT_LOGICAL_POSITIONS:
301 command = (count > 0 ?
302 TextEditCommandAuraLinux::MOVE_FORWARD :
303 TextEditCommandAuraLinux::MOVE_BACKWARD);
304 break;
305 case GTK_MOVEMENT_VISUAL_POSITIONS:
306 command = (count > 0 ?
307 TextEditCommandAuraLinux::MOVE_RIGHT :
308 TextEditCommandAuraLinux::MOVE_LEFT);
309 break;
310 case GTK_MOVEMENT_WORDS:
311 command = (count > 0 ?
312 TextEditCommandAuraLinux::MOVE_WORD_RIGHT :
313 TextEditCommandAuraLinux::MOVE_WORD_LEFT);
314 break;
315 case GTK_MOVEMENT_DISPLAY_LINES:
316 command = (count > 0 ?
317 TextEditCommandAuraLinux::MOVE_DOWN :
318 TextEditCommandAuraLinux::MOVE_UP);
319 break;
320 case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
321 command = (count > 0 ?
322 TextEditCommandAuraLinux::MOVE_TO_END_OF_LINE :
323 TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_LINE);
324 break;
325 case GTK_MOVEMENT_PARAGRAPH_ENDS:
326 command = (count > 0 ?
327 TextEditCommandAuraLinux::MOVE_TO_END_OF_PARAGRAPH :
328 TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_PARAGRAPH);
329 break;
330 case GTK_MOVEMENT_PAGES:
331 command = (count > 0 ? TextEditCommandAuraLinux::MOVE_PAGE_DOWN :
332 TextEditCommandAuraLinux::MOVE_PAGE_UP);
333 break;
334 case GTK_MOVEMENT_BUFFER_ENDS:
335 command = (count > 0 ? TextEditCommandAuraLinux::MOVE_TO_END_OF_DOCUMENT :
336 TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_DOCUMENT);
337 break;
338 default:
339 // GTK_MOVEMENT_PARAGRAPHS and GTK_MOVEMENT_HORIZONTAL_PAGES have
340 // no corresponding editor commands.
341 return;
344 Gtk2KeyBindingsHandler* owner = GetHandlerOwner(text_view);
345 if (count < 0)
346 count = -count;
347 for (; count > 0; --count)
348 owner->EditCommandMatched(command, std::string(), extend_selection);
351 void Gtk2KeyBindingsHandler::MoveViewport(
352 GtkTextView* text_view, GtkScrollStep step, gint count) {
353 // Not supported by webkit.
356 void Gtk2KeyBindingsHandler::PasteClipboard(GtkTextView* text_view) {
357 GetHandlerOwner(text_view)->EditCommandMatched(
358 TextEditCommandAuraLinux::PASTE, std::string(), false);
361 void Gtk2KeyBindingsHandler::SelectAll(GtkTextView* text_view,
362 gboolean select) {
363 if (select) {
364 GetHandlerOwner(text_view)->EditCommandMatched(
365 TextEditCommandAuraLinux::SELECT_ALL, std::string(), false);
366 } else {
367 GetHandlerOwner(text_view)->EditCommandMatched(
368 TextEditCommandAuraLinux::UNSELECT, std::string(), false);
372 void Gtk2KeyBindingsHandler::SetAnchor(GtkTextView* text_view) {
373 GetHandlerOwner(text_view)->EditCommandMatched(
374 TextEditCommandAuraLinux::SET_MARK, std::string(), false);
377 void Gtk2KeyBindingsHandler::ToggleCursorVisible(GtkTextView* text_view) {
378 // Not supported by webkit.
381 void Gtk2KeyBindingsHandler::ToggleOverwrite(GtkTextView* text_view) {
382 // Not supported by webkit.
385 gboolean Gtk2KeyBindingsHandler::ShowHelp(GtkWidget* widget,
386 GtkWidgetHelpType arg1) {
387 // Just for disabling the default handler.
388 return FALSE;
391 void Gtk2KeyBindingsHandler::MoveFocus(GtkWidget* widget,
392 GtkDirectionType arg1) {
393 // Just for disabling the default handler.
396 } // namespace libgtk2ui