Add unit test for the Settings API Bubble.
[chromium-blink-merge.git] / chrome / browser / ui / libgtk2ui / gtk2_key_bindings_handler.cc
blobfdf5799ee42218f61b8e0d21c889a4a489987a3d
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::TextEditCommandX11;
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<TextEditCommandX11>* 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 TextEditCommandX11::CommandId id,
92 const std::string& value,
93 bool extend_selection) {
94 edit_commands_.push_back(TextEditCommandX11(id, value, extend_selection));
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;
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;
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;
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);
129 state = static_cast<GdkModifierType>(gdk_event->state & ~consumed);
130 gdk_keymap_add_virtual_modifiers(keymap, &state);
131 gdk_event->state |= state;
134 void Gtk2KeyBindingsHandler::HandlerInit(Handler *self) {
135 self->owner = NULL;
138 void Gtk2KeyBindingsHandler::HandlerClassInit(HandlerClass *klass) {
139 GtkTextViewClass* text_view_class = GTK_TEXT_VIEW_CLASS(klass);
140 GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
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;
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));
162 g_signal_override_class_handler("move-viewport",
163 G_TYPE_FROM_CLASS(klass),
164 G_CALLBACK(MoveViewport));
166 g_signal_override_class_handler("select-all",
167 G_TYPE_FROM_CLASS(klass),
168 G_CALLBACK(SelectAll));
170 g_signal_override_class_handler("toggle-cursor-visible",
171 G_TYPE_FROM_CLASS(klass),
172 G_CALLBACK(ToggleCursorVisible));
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);
188 return type_id_volatile;
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;
199 void Gtk2KeyBindingsHandler::BackSpace(GtkTextView* text_view) {
200 GetHandlerOwner(text_view)
201 ->EditCommandMatched(
202 TextEditCommandX11::DELETE_BACKWARD, std::string(), false);
205 void Gtk2KeyBindingsHandler::CopyClipboard(GtkTextView* text_view) {
206 GetHandlerOwner(text_view)->EditCommandMatched(
207 TextEditCommandX11::COPY, std::string(), false);
210 void Gtk2KeyBindingsHandler::CutClipboard(GtkTextView* text_view) {
211 GetHandlerOwner(text_view)->EditCommandMatched(
212 TextEditCommandX11::CUT, std::string(), false);
215 void Gtk2KeyBindingsHandler::DeleteFromCursor(
216 GtkTextView* text_view, GtkDeleteType type, gint count) {
217 if (!count)
218 return;
220 TextEditCommandX11::CommandId commands[2] = {
221 TextEditCommandX11::INVALID_COMMAND,
222 TextEditCommandX11::INVALID_COMMAND,
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;
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;
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);
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);
286 void Gtk2KeyBindingsHandler::MoveCursor(
287 GtkTextView* text_view, GtkMovementStep step, gint count,
288 gboolean extend_selection) {
289 if (!count)
290 return;
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;
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);
344 void Gtk2KeyBindingsHandler::MoveViewport(
345 GtkTextView* text_view, GtkScrollStep step, gint count) {
346 // Not supported by webkit.
349 void Gtk2KeyBindingsHandler::PasteClipboard(GtkTextView* text_view) {
350 GetHandlerOwner(text_view)->EditCommandMatched(
351 TextEditCommandX11::PASTE, std::string(), false);
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);
365 void Gtk2KeyBindingsHandler::SetAnchor(GtkTextView* text_view) {
366 GetHandlerOwner(text_view)->EditCommandMatched(
367 TextEditCommandX11::SET_MARK, std::string(), false);
370 void Gtk2KeyBindingsHandler::ToggleCursorVisible(GtkTextView* text_view) {
371 // Not supported by webkit.
374 void Gtk2KeyBindingsHandler::ToggleOverwrite(GtkTextView* text_view) {
375 // Not supported by webkit.
378 gboolean Gtk2KeyBindingsHandler::ShowHelp(GtkWidget* widget,
379 GtkWidgetHelpType arg1) {
380 // Just for disabling the default handler.
381 return FALSE;
384 void Gtk2KeyBindingsHandler::MoveFocus(GtkWidget* widget,
385 GtkDirectionType arg1) {
386 // Just for disabling the default handler.
389 } // namespace libgtk2ui