1// Copyright (c) 2009 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/renderer_host/gtk_key_bindings_handler.h"
6
7#include <gdk/gdkkeysyms.h>
8
9#include <string>
10
11#include "base/logging.h"
12#include "base/string_util.h"
13#include "content/common/native_web_keyboard_event.h"
14
15GtkKeyBindingsHandler::GtkKeyBindingsHandler(GtkWidget* parent_widget)
16    : handler_(CreateNewHandler()) {
17  DCHECK(GTK_IS_FIXED(parent_widget));
18  // We need add the |handler_| object into gtk widget hierarchy, so that
19  // gtk_bindings_activate_event() can find correct display and keymaps from
20  // the |handler_| object.
21  gtk_fixed_put(GTK_FIXED(parent_widget), handler_.get(), -1, -1);
22}
23
24GtkKeyBindingsHandler::~GtkKeyBindingsHandler() {
25  handler_.Destroy();
26}
27
28bool GtkKeyBindingsHandler::Match(const NativeWebKeyboardEvent& wke,
29                                  EditCommands* edit_commands) {
30  if (wke.type == WebKit::WebInputEvent::Char || !wke.os_event)
31    return false;
32
33  edit_commands_.clear();
34  // If this key event matches a predefined key binding, corresponding signal
35  // will be emitted.
36  gtk_bindings_activate_event(GTK_OBJECT(handler_.get()), wke.os_event);
37
38  bool matched = !edit_commands_.empty();
39  if (edit_commands)
40    edit_commands->swap(edit_commands_);
41  return matched;
42}
43
44GtkWidget* GtkKeyBindingsHandler::CreateNewHandler() {
45  Handler* handler =
46      static_cast<Handler*>(g_object_new(HandlerGetType(), NULL));
47
48  handler->owner = this;
49
50  // We don't need to show the |handler| object on screen, so set its size to
51  // zero.
52  gtk_widget_set_size_request(GTK_WIDGET(handler), 0, 0);
53
54  // Prevents it from handling any events by itself.
55  gtk_widget_set_sensitive(GTK_WIDGET(handler), FALSE);
56  gtk_widget_set_events(GTK_WIDGET(handler), 0);
57  GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(handler), GTK_CAN_FOCUS);
58
59#if !GTK_CHECK_VERSION(2, 14, 0)
60  // "move-focus", "move-viewport", "select-all" and "toggle-cursor-visible"
61  // have no corresponding virtual methods. Prior to glib 2.18 (gtk 2.14),
62  // there is no way to override the default class handler of a signal.
63  // So we need hook these signal explicitly.
64  g_signal_connect(handler, "move-focus", G_CALLBACK(MoveFocus), NULL);
65  g_signal_connect(handler, "move-viewport", G_CALLBACK(MoveViewport), NULL);
66  g_signal_connect(handler, "select-all", G_CALLBACK(SelectAll), NULL);
67  g_signal_connect(handler, "toggle-cursor-visible",
68                   G_CALLBACK(ToggleCursorVisible), NULL);
69#endif
70  return GTK_WIDGET(handler);
71}
72
73void GtkKeyBindingsHandler::EditCommandMatched(
74    const std::string& name, const std::string& value) {
75  edit_commands_.push_back(EditCommand(name, value));
76}
77
78void GtkKeyBindingsHandler::HandlerInit(Handler *self) {
79  self->owner = NULL;
80}
81
82void GtkKeyBindingsHandler::HandlerClassInit(HandlerClass *klass) {
83  GtkTextViewClass* text_view_class = GTK_TEXT_VIEW_CLASS(klass);
84  GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
85
86  // Overrides all virtual methods related to editor key bindings.
87  text_view_class->backspace = BackSpace;
88  text_view_class->copy_clipboard = CopyClipboard;
89  text_view_class->cut_clipboard = CutClipboard;
90  text_view_class->delete_from_cursor = DeleteFromCursor;
91  text_view_class->insert_at_cursor = InsertAtCursor;
92  text_view_class->move_cursor = MoveCursor;
93  text_view_class->paste_clipboard = PasteClipboard;
94  text_view_class->set_anchor = SetAnchor;
95  text_view_class->toggle_overwrite = ToggleOverwrite;
96  widget_class->show_help = ShowHelp;
97
98#if GTK_CHECK_VERSION(2, 14, 0)
99  // "move-focus", "move-viewport", "select-all" and "toggle-cursor-visible"
100  // have no corresponding virtual methods. Since glib 2.18 (gtk 2.14),
101  // g_signal_override_class_handler() is introduced to override a signal
102  // handler.
103  g_signal_override_class_handler("move-focus",
104                                  G_TYPE_FROM_CLASS(klass),
105                                  G_CALLBACK(MoveFocus));
106
107  g_signal_override_class_handler("move-viewport",
108                                  G_TYPE_FROM_CLASS(klass),
109                                  G_CALLBACK(MoveViewport));
110
111  g_signal_override_class_handler("select-all",
112                                  G_TYPE_FROM_CLASS(klass),
113                                  G_CALLBACK(SelectAll));
114
115  g_signal_override_class_handler("toggle-cursor-visible",
116                                  G_TYPE_FROM_CLASS(klass),
117                                  G_CALLBACK(ToggleCursorVisible));
118#endif
119}
120
121GType GtkKeyBindingsHandler::HandlerGetType() {
122  static volatile gsize type_id_volatile = 0;
123  if (g_once_init_enter(&type_id_volatile)) {
124    GType type_id = g_type_register_static_simple(
125        GTK_TYPE_TEXT_VIEW,
126        g_intern_static_string("GtkKeyBindingsHandler"),
127        sizeof(HandlerClass),
128        reinterpret_cast<GClassInitFunc>(HandlerClassInit),
129        sizeof(Handler),
130        reinterpret_cast<GInstanceInitFunc>(HandlerInit),
131        static_cast<GTypeFlags>(0));
132    g_once_init_leave(&type_id_volatile, type_id);
133  }
134  return type_id_volatile;
135}
136
137GtkKeyBindingsHandler* GtkKeyBindingsHandler::GetHandlerOwner(
138    GtkTextView* text_view) {
139  Handler* handler = G_TYPE_CHECK_INSTANCE_CAST(
140      text_view, HandlerGetType(), Handler);
141  DCHECK(handler);
142  return handler->owner;
143}
144
145void GtkKeyBindingsHandler::BackSpace(GtkTextView* text_view) {
146  GetHandlerOwner(text_view)->EditCommandMatched("DeleteBackward", "");
147}
148
149void GtkKeyBindingsHandler::CopyClipboard(GtkTextView* text_view) {
150  GetHandlerOwner(text_view)->EditCommandMatched("Copy", "");
151}
152
153void GtkKeyBindingsHandler::CutClipboard(GtkTextView* text_view) {
154  GetHandlerOwner(text_view)->EditCommandMatched("Cut", "");
155}
156
157void GtkKeyBindingsHandler::DeleteFromCursor(
158    GtkTextView* text_view, GtkDeleteType type, gint count) {
159  if (!count)
160    return;
161
162  const char *commands[3] = { NULL, NULL, NULL };
163  switch (type) {
164    case GTK_DELETE_CHARS:
165      commands[0] = (count > 0 ? "DeleteForward" : "DeleteBackward");
166      break;
167    case GTK_DELETE_WORD_ENDS:
168      commands[0] = (count > 0 ? "DeleteWordForward" : "DeleteWordBackward");
169      break;
170    case GTK_DELETE_WORDS:
171      if (count > 0) {
172        commands[0] = "MoveWordForward";
173        commands[1] = "DeleteWordBackward";
174      } else {
175        commands[0] = "MoveWordBackward";
176        commands[1] = "DeleteWordForward";
177      }
178      break;
179    case GTK_DELETE_DISPLAY_LINES:
180      commands[0] = "MoveToBeginningOfLine";
181      commands[1] = "DeleteToEndOfLine";
182      break;
183    case GTK_DELETE_DISPLAY_LINE_ENDS:
184      commands[0] = (count > 0 ? "DeleteToEndOfLine" :
185                     "DeleteToBeginningOfLine");
186      break;
187    case GTK_DELETE_PARAGRAPH_ENDS:
188      commands[0] = (count > 0 ? "DeleteToEndOfParagraph" :
189                     "DeleteToBeginningOfParagraph");
190      break;
191    case GTK_DELETE_PARAGRAPHS:
192      commands[0] = "MoveToBeginningOfParagraph";
193      commands[1] = "DeleteToEndOfParagraph";
194      break;
195    default:
196      // GTK_DELETE_WHITESPACE has no corresponding editor command.
197      return;
198  }
199
200  GtkKeyBindingsHandler* owner = GetHandlerOwner(text_view);
201  if (count < 0)
202    count = -count;
203  for (; count > 0; --count) {
204    for (const char* const* p = commands; *p; ++p)
205      owner->EditCommandMatched(*p, "");
206  }
207}
208
209void GtkKeyBindingsHandler::InsertAtCursor(GtkTextView* text_view,
210                                           const gchar* str) {
211  if (str && *str)
212    GetHandlerOwner(text_view)->EditCommandMatched("InsertText", str);
213}
214
215void GtkKeyBindingsHandler::MoveCursor(
216    GtkTextView* text_view, GtkMovementStep step, gint count,
217    gboolean extend_selection) {
218  if (!count)
219    return;
220
221  std::string command;
222  switch (step) {
223    case GTK_MOVEMENT_LOGICAL_POSITIONS:
224      command = (count > 0 ? "MoveForward" : "MoveBackward");
225      break;
226    case GTK_MOVEMENT_VISUAL_POSITIONS:
227      command = (count > 0 ? "MoveRight" : "MoveLeft");
228      break;
229    case GTK_MOVEMENT_WORDS:
230      command = (count > 0 ? "MoveWordForward" : "MoveWordBackward");
231      break;
232    case GTK_MOVEMENT_DISPLAY_LINES:
233      command = (count > 0 ? "MoveDown" : "MoveUp");
234      break;
235    case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
236      command = (count > 0 ? "MoveToEndOfLine" : "MoveToBeginningOfLine");
237      break;
238    case GTK_MOVEMENT_PARAGRAPH_ENDS:
239      command = (count > 0 ? "MoveToEndOfParagraph" :
240                 "MoveToBeginningOfParagraph");
241      break;
242    case GTK_MOVEMENT_PAGES:
243      command = (count > 0 ? "MovePageDown" : "MovePageUp");
244      break;
245    case GTK_MOVEMENT_BUFFER_ENDS:
246      command = (count > 0 ? "MoveToEndOfDocument" :
247                 "MoveToBeginningOfDocument");
248      break;
249    default:
250      // GTK_MOVEMENT_PARAGRAPHS and GTK_MOVEMENT_HORIZONTAL_PAGES have
251      // no corresponding editor commands.
252      return;
253  }
254
255  GtkKeyBindingsHandler* owner = GetHandlerOwner(text_view);
256  if (extend_selection)
257    command.append("AndModifySelection");
258  if (count < 0)
259    count = -count;
260  for (; count > 0; --count)
261    owner->EditCommandMatched(command, "");
262}
263
264void GtkKeyBindingsHandler::MoveViewport(
265    GtkTextView* text_view, GtkScrollStep step, gint count) {
266  // Not supported by webkit.
267#if !GTK_CHECK_VERSION(2, 14, 0)
268  // Before gtk 2.14.0, there is no way to override a non-virtual default signal
269  // handler, so we need stop the signal emission explicitly to prevent the
270  // default handler from being executed.
271  g_signal_stop_emission_by_name(text_view, "move-viewport");
272#endif
273}
274
275void GtkKeyBindingsHandler::PasteClipboard(GtkTextView* text_view) {
276  GetHandlerOwner(text_view)->EditCommandMatched("Paste", "");
277}
278
279void GtkKeyBindingsHandler::SelectAll(GtkTextView* text_view, gboolean select) {
280  if (select)
281    GetHandlerOwner(text_view)->EditCommandMatched("SelectAll", "");
282  else
283    GetHandlerOwner(text_view)->EditCommandMatched("Unselect", "");
284#if !GTK_CHECK_VERSION(2, 14, 0)
285  // Before gtk 2.14.0, there is no way to override a non-virtual default signal
286  // handler, so we need stop the signal emission explicitly to prevent the
287  // default handler from being executed.
288  g_signal_stop_emission_by_name(text_view, "select-all");
289#endif
290}
291
292void GtkKeyBindingsHandler::SetAnchor(GtkTextView* text_view) {
293  GetHandlerOwner(text_view)->EditCommandMatched("SetMark", "");
294}
295
296void GtkKeyBindingsHandler::ToggleCursorVisible(GtkTextView* text_view) {
297  // Not supported by webkit.
298#if !GTK_CHECK_VERSION(2, 14, 0)
299  // Before gtk 2.14.0, there is no way to override a non-virtual default signal
300  // handler, so we need stop the signal emission explicitly to prevent the
301  // default handler from being executed.
302  g_signal_stop_emission_by_name(text_view, "toggle-cursor-visible");
303#endif
304}
305
306void GtkKeyBindingsHandler::ToggleOverwrite(GtkTextView* text_view) {
307  // Not supported by webkit.
308}
309
310gboolean GtkKeyBindingsHandler::ShowHelp(GtkWidget* widget,
311                                         GtkWidgetHelpType arg1) {
312  // Just for disabling the default handler.
313  return FALSE;
314}
315
316void GtkKeyBindingsHandler::MoveFocus(GtkWidget* widget,
317                                      GtkDirectionType arg1) {
318  // Just for disabling the default handler.
319#if !GTK_CHECK_VERSION(2, 14, 0)
320  // Before gtk 2.14.0, there is no way to override a non-virtual default signal
321  // handler, so we need stop the signal emission explicitly to prevent the
322  // default handler from being executed.
323  g_signal_stop_emission_by_name(widget, "move-focus");
324#endif
325}
326