Upstream version 7.35.139.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / libgtk2ui / gtk2_key_bindings_handler.cc
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::TextEditCommandAuraLinux;
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<TextEditCommandAuraLinux>* 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     TextEditCommandAuraLinux::CommandId id,
92     const std::string& value,
93     bool extend_selection) {
94   edit_commands_.push_back(TextEditCommandAuraLinux(id,
95                                                     value,
96                                                     extend_selection));
97 }
98
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;
104
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;
110
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     NOTIMPLEMENTED();
119     gdk_event->group = 0;
120   }
121
122   gdk_event->keyval = GDK_VoidSymbol;
123   gdk_keymap_translate_keyboard_state(
124       keymap,
125       gdk_event->hardware_keycode,
126       static_cast<GdkModifierType>(gdk_event->state),
127       gdk_event->group,
128       &gdk_event->keyval,
129       NULL, NULL, &consumed);
130
131   state = static_cast<GdkModifierType>(gdk_event->state & ~consumed);
132   gdk_keymap_add_virtual_modifiers(keymap, &state);
133   gdk_event->state |= state;
134 }
135
136 void Gtk2KeyBindingsHandler::HandlerInit(Handler *self) {
137   self->owner = NULL;
138 }
139
140 void Gtk2KeyBindingsHandler::HandlerClassInit(HandlerClass *klass) {
141   GtkTextViewClass* text_view_class = GTK_TEXT_VIEW_CLASS(klass);
142   GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
143
144   // Overrides all virtual methods related to editor key bindings.
145   text_view_class->backspace = BackSpace;
146   text_view_class->copy_clipboard = CopyClipboard;
147   text_view_class->cut_clipboard = CutClipboard;
148   text_view_class->delete_from_cursor = DeleteFromCursor;
149   text_view_class->insert_at_cursor = InsertAtCursor;
150   text_view_class->move_cursor = MoveCursor;
151   text_view_class->paste_clipboard = PasteClipboard;
152   text_view_class->set_anchor = SetAnchor;
153   text_view_class->toggle_overwrite = ToggleOverwrite;
154   widget_class->show_help = ShowHelp;
155
156   // "move-focus", "move-viewport", "select-all" and "toggle-cursor-visible"
157   // have no corresponding virtual methods. Since glib 2.18 (gtk 2.14),
158   // g_signal_override_class_handler() is introduced to override a signal
159   // handler.
160   g_signal_override_class_handler("move-focus",
161                                   G_TYPE_FROM_CLASS(klass),
162                                   G_CALLBACK(MoveFocus));
163
164   g_signal_override_class_handler("move-viewport",
165                                   G_TYPE_FROM_CLASS(klass),
166                                   G_CALLBACK(MoveViewport));
167
168   g_signal_override_class_handler("select-all",
169                                   G_TYPE_FROM_CLASS(klass),
170                                   G_CALLBACK(SelectAll));
171
172   g_signal_override_class_handler("toggle-cursor-visible",
173                                   G_TYPE_FROM_CLASS(klass),
174                                   G_CALLBACK(ToggleCursorVisible));
175 }
176
177 GType Gtk2KeyBindingsHandler::HandlerGetType() {
178   static volatile gsize type_id_volatile = 0;
179   if (g_once_init_enter(&type_id_volatile)) {
180     GType type_id = g_type_register_static_simple(
181         GTK_TYPE_TEXT_VIEW,
182         g_intern_static_string("Gtk2KeyBindingsHandler"),
183         sizeof(HandlerClass),
184         reinterpret_cast<GClassInitFunc>(HandlerClassInit),
185         sizeof(Handler),
186         reinterpret_cast<GInstanceInitFunc>(HandlerInit),
187         static_cast<GTypeFlags>(0));
188     g_once_init_leave(&type_id_volatile, type_id);
189   }
190   return type_id_volatile;
191 }
192
193 Gtk2KeyBindingsHandler* Gtk2KeyBindingsHandler::GetHandlerOwner(
194     GtkTextView* text_view) {
195   Handler* handler = G_TYPE_CHECK_INSTANCE_CAST(
196       text_view, HandlerGetType(), Handler);
197   DCHECK(handler);
198   return handler->owner;
199 }
200
201 void Gtk2KeyBindingsHandler::BackSpace(GtkTextView* text_view) {
202   GetHandlerOwner(text_view)
203       ->EditCommandMatched(
204           TextEditCommandAuraLinux::DELETE_BACKWARD, std::string(), false);
205 }
206
207 void Gtk2KeyBindingsHandler::CopyClipboard(GtkTextView* text_view) {
208   GetHandlerOwner(text_view)->EditCommandMatched(
209       TextEditCommandAuraLinux::COPY, std::string(), false);
210 }
211
212 void Gtk2KeyBindingsHandler::CutClipboard(GtkTextView* text_view) {
213   GetHandlerOwner(text_view)->EditCommandMatched(
214       TextEditCommandAuraLinux::CUT, std::string(), false);
215 }
216
217 void Gtk2KeyBindingsHandler::DeleteFromCursor(
218     GtkTextView* text_view, GtkDeleteType type, gint count) {
219   if (!count)
220     return;
221
222   TextEditCommandAuraLinux::CommandId commands[2] = {
223     TextEditCommandAuraLinux::INVALID_COMMAND,
224     TextEditCommandAuraLinux::INVALID_COMMAND,
225   };
226   switch (type) {
227     case GTK_DELETE_CHARS:
228       commands[0] = (count > 0 ?
229           TextEditCommandAuraLinux::DELETE_FORWARD :
230           TextEditCommandAuraLinux::DELETE_BACKWARD);
231       break;
232     case GTK_DELETE_WORD_ENDS:
233       commands[0] = (count > 0 ?
234           TextEditCommandAuraLinux::DELETE_WORD_FORWARD :
235           TextEditCommandAuraLinux::DELETE_WORD_BACKWARD);
236       break;
237     case GTK_DELETE_WORDS:
238       if (count > 0) {
239         commands[0] = TextEditCommandAuraLinux::MOVE_WORD_FORWARD;
240         commands[1] = TextEditCommandAuraLinux::DELETE_WORD_BACKWARD;
241       } else {
242         commands[0] = TextEditCommandAuraLinux::MOVE_WORD_BACKWARD;
243         commands[1] = TextEditCommandAuraLinux::DELETE_WORD_FORWARD;
244       }
245       break;
246     case GTK_DELETE_DISPLAY_LINES:
247       commands[0] = TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_LINE;
248       commands[1] = TextEditCommandAuraLinux::DELETE_TO_END_OF_LINE;
249       break;
250     case GTK_DELETE_DISPLAY_LINE_ENDS:
251       commands[0] = (count > 0 ?
252           TextEditCommandAuraLinux::DELETE_TO_END_OF_LINE :
253           TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_LINE);
254       break;
255     case GTK_DELETE_PARAGRAPH_ENDS:
256       commands[0] = (count > 0 ?
257           TextEditCommandAuraLinux::DELETE_TO_END_OF_PARAGRAPH :
258           TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_PARAGRAPH);
259       break;
260     case GTK_DELETE_PARAGRAPHS:
261       commands[0] =
262           TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_PARAGRAPH;
263       commands[1] =
264           TextEditCommandAuraLinux::DELETE_TO_END_OF_PARAGRAPH;
265       break;
266     default:
267       // GTK_DELETE_WHITESPACE has no corresponding editor command.
268       return;
269   }
270
271   Gtk2KeyBindingsHandler* owner = GetHandlerOwner(text_view);
272   if (count < 0)
273     count = -count;
274   for (; count > 0; --count) {
275     for (size_t i = 0; i < arraysize(commands); ++i)
276       if (commands[i] != TextEditCommandAuraLinux::INVALID_COMMAND)
277         owner->EditCommandMatched(commands[i], std::string(), false);
278   }
279 }
280
281 void Gtk2KeyBindingsHandler::InsertAtCursor(GtkTextView* text_view,
282                                             const gchar* str) {
283   if (str && *str)
284     GetHandlerOwner(text_view)->EditCommandMatched(
285         TextEditCommandAuraLinux::INSERT_TEXT, str, false);
286 }
287
288 void Gtk2KeyBindingsHandler::MoveCursor(
289     GtkTextView* text_view, GtkMovementStep step, gint count,
290     gboolean extend_selection) {
291   if (!count)
292     return;
293
294   TextEditCommandAuraLinux::CommandId command;
295   switch (step) {
296     case GTK_MOVEMENT_LOGICAL_POSITIONS:
297       command = (count > 0 ?
298                  TextEditCommandAuraLinux::MOVE_FORWARD :
299                  TextEditCommandAuraLinux::MOVE_BACKWARD);
300       break;
301     case GTK_MOVEMENT_VISUAL_POSITIONS:
302       command = (count > 0 ?
303                  TextEditCommandAuraLinux::MOVE_RIGHT :
304                  TextEditCommandAuraLinux::MOVE_LEFT);
305       break;
306     case GTK_MOVEMENT_WORDS:
307       command = (count > 0 ?
308                  TextEditCommandAuraLinux::MOVE_WORD_RIGHT :
309                  TextEditCommandAuraLinux::MOVE_WORD_LEFT);
310       break;
311     case GTK_MOVEMENT_DISPLAY_LINES:
312       command = (count > 0 ?
313                  TextEditCommandAuraLinux::MOVE_DOWN :
314                  TextEditCommandAuraLinux::MOVE_UP);
315       break;
316     case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
317       command = (count > 0 ?
318                  TextEditCommandAuraLinux::MOVE_TO_END_OF_LINE :
319                  TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_LINE);
320       break;
321     case GTK_MOVEMENT_PARAGRAPH_ENDS:
322       command = (count > 0 ?
323                  TextEditCommandAuraLinux::MOVE_TO_END_OF_PARAGRAPH :
324                  TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_PARAGRAPH);
325       break;
326     case GTK_MOVEMENT_PAGES:
327       command = (count > 0 ? TextEditCommandAuraLinux::MOVE_PAGE_DOWN :
328                  TextEditCommandAuraLinux::MOVE_PAGE_UP);
329       break;
330     case GTK_MOVEMENT_BUFFER_ENDS:
331       command = (count > 0 ? TextEditCommandAuraLinux::MOVE_TO_END_OF_DOCUMENT :
332                  TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_DOCUMENT);
333       break;
334     default:
335       // GTK_MOVEMENT_PARAGRAPHS and GTK_MOVEMENT_HORIZONTAL_PAGES have
336       // no corresponding editor commands.
337       return;
338   }
339
340   Gtk2KeyBindingsHandler* owner = GetHandlerOwner(text_view);
341   if (count < 0)
342     count = -count;
343   for (; count > 0; --count)
344     owner->EditCommandMatched(command, std::string(), extend_selection);
345 }
346
347 void Gtk2KeyBindingsHandler::MoveViewport(
348     GtkTextView* text_view, GtkScrollStep step, gint count) {
349   // Not supported by webkit.
350 }
351
352 void Gtk2KeyBindingsHandler::PasteClipboard(GtkTextView* text_view) {
353   GetHandlerOwner(text_view)->EditCommandMatched(
354       TextEditCommandAuraLinux::PASTE, std::string(), false);
355 }
356
357 void Gtk2KeyBindingsHandler::SelectAll(GtkTextView* text_view,
358                                        gboolean select) {
359   if (select) {
360     GetHandlerOwner(text_view)->EditCommandMatched(
361         TextEditCommandAuraLinux::SELECT_ALL, std::string(), false);
362   } else {
363     GetHandlerOwner(text_view)->EditCommandMatched(
364         TextEditCommandAuraLinux::UNSELECT, std::string(), false);
365   }
366 }
367
368 void Gtk2KeyBindingsHandler::SetAnchor(GtkTextView* text_view) {
369   GetHandlerOwner(text_view)->EditCommandMatched(
370       TextEditCommandAuraLinux::SET_MARK, std::string(), false);
371 }
372
373 void Gtk2KeyBindingsHandler::ToggleCursorVisible(GtkTextView* text_view) {
374   // Not supported by webkit.
375 }
376
377 void Gtk2KeyBindingsHandler::ToggleOverwrite(GtkTextView* text_view) {
378   // Not supported by webkit.
379 }
380
381 gboolean Gtk2KeyBindingsHandler::ShowHelp(GtkWidget* widget,
382                                           GtkWidgetHelpType arg1) {
383   // Just for disabling the default handler.
384   return FALSE;
385 }
386
387 void Gtk2KeyBindingsHandler::MoveFocus(GtkWidget* widget,
388                                        GtkDirectionType arg1) {
389   // Just for disabling the default handler.
390 }
391
392 }  // namespace libgtk2ui