• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GtkKeyBindingsHandler(GtkWidget * parent_widget)15 GtkKeyBindingsHandler::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 
~GtkKeyBindingsHandler()24 GtkKeyBindingsHandler::~GtkKeyBindingsHandler() {
25   handler_.Destroy();
26 }
27 
Match(const NativeWebKeyboardEvent & wke,EditCommands * edit_commands)28 bool 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 
CreateNewHandler()44 GtkWidget* 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 
EditCommandMatched(const std::string & name,const std::string & value)73 void GtkKeyBindingsHandler::EditCommandMatched(
74     const std::string& name, const std::string& value) {
75   edit_commands_.push_back(EditCommand(name, value));
76 }
77 
HandlerInit(Handler * self)78 void GtkKeyBindingsHandler::HandlerInit(Handler *self) {
79   self->owner = NULL;
80 }
81 
HandlerClassInit(HandlerClass * klass)82 void 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 
HandlerGetType()121 GType 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 
GetHandlerOwner(GtkTextView * text_view)137 GtkKeyBindingsHandler* 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 
BackSpace(GtkTextView * text_view)145 void GtkKeyBindingsHandler::BackSpace(GtkTextView* text_view) {
146   GetHandlerOwner(text_view)->EditCommandMatched("DeleteBackward", "");
147 }
148 
CopyClipboard(GtkTextView * text_view)149 void GtkKeyBindingsHandler::CopyClipboard(GtkTextView* text_view) {
150   GetHandlerOwner(text_view)->EditCommandMatched("Copy", "");
151 }
152 
CutClipboard(GtkTextView * text_view)153 void GtkKeyBindingsHandler::CutClipboard(GtkTextView* text_view) {
154   GetHandlerOwner(text_view)->EditCommandMatched("Cut", "");
155 }
156 
DeleteFromCursor(GtkTextView * text_view,GtkDeleteType type,gint count)157 void 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 
InsertAtCursor(GtkTextView * text_view,const gchar * str)209 void GtkKeyBindingsHandler::InsertAtCursor(GtkTextView* text_view,
210                                            const gchar* str) {
211   if (str && *str)
212     GetHandlerOwner(text_view)->EditCommandMatched("InsertText", str);
213 }
214 
MoveCursor(GtkTextView * text_view,GtkMovementStep step,gint count,gboolean extend_selection)215 void 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 
MoveViewport(GtkTextView * text_view,GtkScrollStep step,gint count)264 void 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 
PasteClipboard(GtkTextView * text_view)275 void GtkKeyBindingsHandler::PasteClipboard(GtkTextView* text_view) {
276   GetHandlerOwner(text_view)->EditCommandMatched("Paste", "");
277 }
278 
SelectAll(GtkTextView * text_view,gboolean select)279 void 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 
SetAnchor(GtkTextView * text_view)292 void GtkKeyBindingsHandler::SetAnchor(GtkTextView* text_view) {
293   GetHandlerOwner(text_view)->EditCommandMatched("SetMark", "");
294 }
295 
ToggleCursorVisible(GtkTextView * text_view)296 void 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 
ToggleOverwrite(GtkTextView * text_view)306 void GtkKeyBindingsHandler::ToggleOverwrite(GtkTextView* text_view) {
307   // Not supported by webkit.
308 }
309 
ShowHelp(GtkWidget * widget,GtkWidgetHelpType arg1)310 gboolean GtkKeyBindingsHandler::ShowHelp(GtkWidget* widget,
311                                          GtkWidgetHelpType arg1) {
312   // Just for disabling the default handler.
313   return FALSE;
314 }
315 
MoveFocus(GtkWidget * widget,GtkDirectionType arg1)316 void 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