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 "ui/gtk/gtk_key_bindings_handler.h"
6 
7 #include <gdk/gdkkeysyms.h>
8 #include <stddef.h>
9 
10 #include <string>
11 
12 #include "base/logging.h"
13 #include "base/stl_util.h"
14 #include "base/strings/string_util.h"
15 #include "ui/base/ime/text_edit_commands.h"
16 #include "ui/events/event.h"
17 #include "ui/gtk/gtk_util.h"
18 
19 using ui::TextEditCommand;
20 
21 // TODO(erg): Rewrite the old gtk_key_bindings_handler_unittest.cc and get them
22 // in a state that links. This code was adapted from the content layer GTK
23 // code, which had some simple unit tests. However, the changes in the public
24 // interface basically meant the tests need to be rewritten; this imposes weird
25 // linking requirements regarding GTK+ as we don't have a gtk_unittests
26 // yet. http://crbug.com/358297.
27 
28 namespace {
29 
CreateInvisibleWindow()30 GtkWidget* CreateInvisibleWindow() {
31 #if GTK_CHECK_VERSION(3, 90, 0)
32   return gtk_invisible_new();
33 #else
34   return gtk_offscreen_window_new();
35 #endif
36 }
37 
38 }  // namespace
39 
40 namespace gtk {
41 
GtkKeyBindingsHandler()42 GtkKeyBindingsHandler::GtkKeyBindingsHandler()
43     : fake_window_(CreateInvisibleWindow()), handler_(CreateNewHandler()) {
44   gtk_container_add(GTK_CONTAINER(fake_window_), handler_);
45 }
46 
~GtkKeyBindingsHandler()47 GtkKeyBindingsHandler::~GtkKeyBindingsHandler() {
48   gtk_widget_destroy(handler_);
49   gtk_widget_destroy(fake_window_);
50 }
51 
MatchEvent(const ui::Event & event,std::vector<ui::TextEditCommandAuraLinux> * edit_commands)52 bool GtkKeyBindingsHandler::MatchEvent(
53     const ui::Event& event,
54     std::vector<ui::TextEditCommandAuraLinux>* edit_commands) {
55   CHECK(event.IsKeyEvent());
56 
57   const ui::KeyEvent& key_event = static_cast<const ui::KeyEvent&>(event);
58   if (key_event.is_char())
59     return false;
60 
61   GdkEvent* gdk_event = GdkEventFromKeyEvent(key_event);
62   if (!gdk_event)
63     return false;
64 
65   edit_commands_.clear();
66   // If this key event matches a predefined key binding, corresponding signal
67   // will be emitted.
68 
69   gtk_bindings_activate_event(G_OBJECT(handler_), &gdk_event->key);
70   gdk_event_free(gdk_event);
71 
72   bool matched = !edit_commands_.empty();
73   if (edit_commands)
74     edit_commands->swap(edit_commands_);
75   return matched;
76 }
77 
CreateNewHandler()78 GtkWidget* GtkKeyBindingsHandler::CreateNewHandler() {
79   Handler* handler =
80       static_cast<Handler*>(g_object_new(HandlerGetType(), nullptr));
81 
82   handler->owner = this;
83 
84   // We don't need to show the |handler| object on screen, so set its size to
85   // zero.
86   gtk_widget_set_size_request(GTK_WIDGET(handler), 0, 0);
87 
88   // Prevents it from handling any events by itself.
89   gtk_widget_set_sensitive(GTK_WIDGET(handler), FALSE);
90 #if !GTK_CHECK_VERSION(3, 90, 0)
91   gtk_widget_set_events(GTK_WIDGET(handler), 0);
92 #endif
93   gtk_widget_set_can_focus(GTK_WIDGET(handler), TRUE);
94 
95   return GTK_WIDGET(handler);
96 }
97 
EditCommandMatched(TextEditCommand command,const std::string & value)98 void GtkKeyBindingsHandler::EditCommandMatched(TextEditCommand command,
99                                                const std::string& value) {
100   edit_commands_.push_back(ui::TextEditCommandAuraLinux(command, value));
101 }
102 
HandlerInit(Handler * self)103 void GtkKeyBindingsHandler::HandlerInit(Handler* self) {
104   self->owner = nullptr;
105 }
106 
HandlerClassInit(HandlerClass * klass)107 void GtkKeyBindingsHandler::HandlerClassInit(HandlerClass* klass) {
108   GtkTextViewClass* text_view_class = GTK_TEXT_VIEW_CLASS(klass);
109 
110   // Overrides all virtual methods related to editor key bindings.
111   text_view_class->backspace = BackSpace;
112   text_view_class->copy_clipboard = CopyClipboard;
113   text_view_class->cut_clipboard = CutClipboard;
114   text_view_class->delete_from_cursor = DeleteFromCursor;
115   text_view_class->insert_at_cursor = InsertAtCursor;
116   text_view_class->move_cursor = MoveCursor;
117   text_view_class->paste_clipboard = PasteClipboard;
118   text_view_class->set_anchor = SetAnchor;
119   text_view_class->toggle_overwrite = ToggleOverwrite;
120 #if !GTK_CHECK_VERSION(3, 90, 0)
121   GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
122   widget_class->show_help = ShowHelp;
123 #endif
124 
125   // "move-focus", "move-viewport", "select-all" and "toggle-cursor-visible"
126   // have no corresponding virtual methods. Since glib 2.18 (gtk 2.14),
127   // g_signal_override_class_handler() is introduced to override a signal
128   // handler.
129   g_signal_override_class_handler("move-focus", G_TYPE_FROM_CLASS(klass),
130                                   G_CALLBACK(MoveFocus));
131 
132   g_signal_override_class_handler("move-viewport", G_TYPE_FROM_CLASS(klass),
133                                   G_CALLBACK(MoveViewport));
134 
135   g_signal_override_class_handler("select-all", G_TYPE_FROM_CLASS(klass),
136                                   G_CALLBACK(SelectAll));
137 
138   g_signal_override_class_handler("toggle-cursor-visible",
139                                   G_TYPE_FROM_CLASS(klass),
140                                   G_CALLBACK(ToggleCursorVisible));
141 }
142 
HandlerGetType()143 GType GtkKeyBindingsHandler::HandlerGetType() {
144   static volatile gsize type_id_volatile = 0;
145   if (g_once_init_enter(&type_id_volatile)) {
146     GType type_id = g_type_register_static_simple(
147         GTK_TYPE_TEXT_VIEW, g_intern_static_string("GtkKeyBindingsHandler"),
148         sizeof(HandlerClass),
149         reinterpret_cast<GClassInitFunc>(HandlerClassInit), sizeof(Handler),
150         reinterpret_cast<GInstanceInitFunc>(HandlerInit),
151         static_cast<GTypeFlags>(0));
152     g_once_init_leave(&type_id_volatile, type_id);
153   }
154   return type_id_volatile;
155 }
156 
GetHandlerOwner(GtkTextView * text_view)157 GtkKeyBindingsHandler* GtkKeyBindingsHandler::GetHandlerOwner(
158     GtkTextView* text_view) {
159   Handler* handler =
160       G_TYPE_CHECK_INSTANCE_CAST(text_view, HandlerGetType(), Handler);
161   DCHECK(handler);
162   return handler->owner;
163 }
164 
BackSpace(GtkTextView * text_view)165 void GtkKeyBindingsHandler::BackSpace(GtkTextView* text_view) {
166   GetHandlerOwner(text_view)->EditCommandMatched(
167       TextEditCommand::DELETE_BACKWARD, std::string());
168 }
169 
CopyClipboard(GtkTextView * text_view)170 void GtkKeyBindingsHandler::CopyClipboard(GtkTextView* text_view) {
171   GetHandlerOwner(text_view)->EditCommandMatched(TextEditCommand::COPY,
172                                                  std::string());
173 }
174 
CutClipboard(GtkTextView * text_view)175 void GtkKeyBindingsHandler::CutClipboard(GtkTextView* text_view) {
176   GetHandlerOwner(text_view)->EditCommandMatched(TextEditCommand::CUT,
177                                                  std::string());
178 }
179 
DeleteFromCursor(GtkTextView * text_view,GtkDeleteType type,gint count)180 void GtkKeyBindingsHandler::DeleteFromCursor(GtkTextView* text_view,
181                                              GtkDeleteType type,
182                                              gint count) {
183   if (!count)
184     return;
185 
186   TextEditCommand commands[2] = {
187       TextEditCommand::INVALID_COMMAND,
188       TextEditCommand::INVALID_COMMAND,
189   };
190   switch (type) {
191     case GTK_DELETE_CHARS:
192       commands[0] = (count > 0 ? TextEditCommand::DELETE_FORWARD
193                                : TextEditCommand::DELETE_BACKWARD);
194       break;
195     case GTK_DELETE_WORD_ENDS:
196       commands[0] = (count > 0 ? TextEditCommand::DELETE_WORD_FORWARD
197                                : TextEditCommand::DELETE_WORD_BACKWARD);
198       break;
199     case GTK_DELETE_WORDS:
200       if (count > 0) {
201         commands[0] = TextEditCommand::MOVE_WORD_FORWARD;
202         commands[1] = TextEditCommand::DELETE_WORD_BACKWARD;
203       } else {
204         commands[0] = TextEditCommand::MOVE_WORD_BACKWARD;
205         commands[1] = TextEditCommand::DELETE_WORD_FORWARD;
206       }
207       break;
208     case GTK_DELETE_DISPLAY_LINES:
209       commands[0] = TextEditCommand::MOVE_TO_BEGINNING_OF_LINE;
210       commands[1] = TextEditCommand::DELETE_TO_END_OF_LINE;
211       break;
212     case GTK_DELETE_DISPLAY_LINE_ENDS:
213       commands[0] = (count > 0 ? TextEditCommand::DELETE_TO_END_OF_LINE
214                                : TextEditCommand::DELETE_TO_BEGINNING_OF_LINE);
215       break;
216     case GTK_DELETE_PARAGRAPH_ENDS:
217       commands[0] =
218           (count > 0 ? TextEditCommand::DELETE_TO_END_OF_PARAGRAPH
219                      : TextEditCommand::DELETE_TO_BEGINNING_OF_PARAGRAPH);
220       break;
221     case GTK_DELETE_PARAGRAPHS:
222       commands[0] = TextEditCommand::MOVE_TO_BEGINNING_OF_PARAGRAPH;
223       commands[1] = TextEditCommand::DELETE_TO_END_OF_PARAGRAPH;
224       break;
225     default:
226       // GTK_DELETE_WHITESPACE has no corresponding editor command.
227       return;
228   }
229 
230   GtkKeyBindingsHandler* owner = GetHandlerOwner(text_view);
231   if (count < 0)
232     count = -count;
233   for (; count > 0; --count) {
234     for (size_t i = 0; i < base::size(commands); ++i)
235       if (commands[i] != TextEditCommand::INVALID_COMMAND)
236         owner->EditCommandMatched(commands[i], std::string());
237   }
238 }
239 
InsertAtCursor(GtkTextView * text_view,const gchar * str)240 void GtkKeyBindingsHandler::InsertAtCursor(GtkTextView* text_view,
241                                            const gchar* str) {
242   if (str && *str) {
243     GetHandlerOwner(text_view)->EditCommandMatched(TextEditCommand::INSERT_TEXT,
244                                                    str);
245   }
246 }
247 
MoveCursor(GtkTextView * text_view,GtkMovementStep step,gint count,gboolean extend_selection)248 void GtkKeyBindingsHandler::MoveCursor(GtkTextView* text_view,
249                                        GtkMovementStep step,
250                                        gint count,
251                                        gboolean extend_selection) {
252   if (!count)
253     return;
254 
255   TextEditCommand command;
256   switch (step) {
257     case GTK_MOVEMENT_LOGICAL_POSITIONS:
258       if (extend_selection) {
259         command =
260             (count > 0 ? TextEditCommand::MOVE_FORWARD_AND_MODIFY_SELECTION
261                        : TextEditCommand::MOVE_BACKWARD_AND_MODIFY_SELECTION);
262       } else {
263         command = (count > 0 ? TextEditCommand::MOVE_FORWARD
264                              : TextEditCommand::MOVE_BACKWARD);
265       }
266       break;
267     case GTK_MOVEMENT_VISUAL_POSITIONS:
268       if (extend_selection) {
269         command = (count > 0 ? TextEditCommand::MOVE_RIGHT_AND_MODIFY_SELECTION
270                              : TextEditCommand::MOVE_LEFT_AND_MODIFY_SELECTION);
271       } else {
272         command = (count > 0 ? TextEditCommand::MOVE_RIGHT
273                              : TextEditCommand::MOVE_LEFT);
274       }
275       break;
276     case GTK_MOVEMENT_WORDS:
277       if (extend_selection) {
278         command =
279             (count > 0 ? TextEditCommand::MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
280                        : TextEditCommand::MOVE_WORD_LEFT_AND_MODIFY_SELECTION);
281       } else {
282         command = (count > 0 ? TextEditCommand::MOVE_WORD_RIGHT
283                              : TextEditCommand::MOVE_WORD_LEFT);
284       }
285       break;
286     case GTK_MOVEMENT_DISPLAY_LINES:
287       if (extend_selection) {
288         command = (count > 0 ? TextEditCommand::MOVE_DOWN_AND_MODIFY_SELECTION
289                              : TextEditCommand::MOVE_UP_AND_MODIFY_SELECTION);
290       } else {
291         command =
292             (count > 0 ? TextEditCommand::MOVE_DOWN : TextEditCommand::MOVE_UP);
293       }
294       break;
295     case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
296       if (extend_selection) {
297         command =
298             (count > 0
299                  ? TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
300                  : TextEditCommand::
301                        MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION);
302       } else {
303         command = (count > 0 ? TextEditCommand::MOVE_TO_END_OF_LINE
304                              : TextEditCommand::MOVE_TO_BEGINNING_OF_LINE);
305       }
306       break;
307     case GTK_MOVEMENT_PARAGRAPH_ENDS:
308       if (extend_selection) {
309         command =
310             (count > 0
311                  ? TextEditCommand::
312                        MOVE_TO_END_OF_PARAGRAPH_AND_MODIFY_SELECTION
313                  : TextEditCommand::
314                        MOVE_TO_BEGINNING_OF_PARAGRAPH_AND_MODIFY_SELECTION);
315       } else {
316         command = (count > 0 ? TextEditCommand::MOVE_TO_END_OF_PARAGRAPH
317                              : TextEditCommand::MOVE_TO_BEGINNING_OF_PARAGRAPH);
318       }
319       break;
320     case GTK_MOVEMENT_PAGES:
321       if (extend_selection) {
322         command =
323             (count > 0 ? TextEditCommand::MOVE_PAGE_DOWN_AND_MODIFY_SELECTION
324                        : TextEditCommand::MOVE_PAGE_UP_AND_MODIFY_SELECTION);
325       } else {
326         command = (count > 0 ? TextEditCommand::MOVE_PAGE_DOWN
327                              : TextEditCommand::MOVE_PAGE_UP);
328       }
329       break;
330     case GTK_MOVEMENT_BUFFER_ENDS:
331       if (extend_selection) {
332         command =
333             (count > 0
334                  ? TextEditCommand::MOVE_TO_END_OF_DOCUMENT_AND_MODIFY_SELECTION
335                  : TextEditCommand::
336                        MOVE_TO_BEGINNING_OF_DOCUMENT_AND_MODIFY_SELECTION);
337       } else {
338         command = (count > 0 ? TextEditCommand::MOVE_TO_END_OF_DOCUMENT
339                              : TextEditCommand::MOVE_TO_BEGINNING_OF_DOCUMENT);
340       }
341       break;
342     default:
343       // GTK_MOVEMENT_PARAGRAPHS and GTK_MOVEMENT_HORIZONTAL_PAGES have
344       // no corresponding editor commands.
345       return;
346   }
347 
348   GtkKeyBindingsHandler* owner = GetHandlerOwner(text_view);
349   if (count < 0)
350     count = -count;
351   for (; count > 0; --count)
352     owner->EditCommandMatched(command, std::string());
353 }
354 
MoveViewport(GtkTextView * text_view,GtkScrollStep step,gint count)355 void GtkKeyBindingsHandler::MoveViewport(GtkTextView* text_view,
356                                          GtkScrollStep step,
357                                          gint count) {
358   // Not supported by Blink.
359 }
360 
PasteClipboard(GtkTextView * text_view)361 void GtkKeyBindingsHandler::PasteClipboard(GtkTextView* text_view) {
362   GetHandlerOwner(text_view)->EditCommandMatched(TextEditCommand::PASTE,
363                                                  std::string());
364 }
365 
SelectAll(GtkTextView * text_view,gboolean select)366 void GtkKeyBindingsHandler::SelectAll(GtkTextView* text_view, gboolean select) {
367   GetHandlerOwner(text_view)->EditCommandMatched(
368       select ? TextEditCommand::SELECT_ALL : TextEditCommand::UNSELECT,
369       std::string());
370 }
371 
SetAnchor(GtkTextView * text_view)372 void GtkKeyBindingsHandler::SetAnchor(GtkTextView* text_view) {
373   GetHandlerOwner(text_view)->EditCommandMatched(TextEditCommand::SET_MARK,
374                                                  std::string());
375 }
376 
ToggleCursorVisible(GtkTextView * text_view)377 void GtkKeyBindingsHandler::ToggleCursorVisible(GtkTextView* text_view) {
378   // Not supported by Blink.
379 }
380 
ToggleOverwrite(GtkTextView * text_view)381 void GtkKeyBindingsHandler::ToggleOverwrite(GtkTextView* text_view) {
382   // Not supported by Blink.
383 }
384 
385 #if !GTK_CHECK_VERSION(3, 90, 0)
ShowHelp(GtkWidget * widget,GtkWidgetHelpType arg1)386 gboolean GtkKeyBindingsHandler::ShowHelp(GtkWidget* widget,
387                                          GtkWidgetHelpType arg1) {
388   // Just for disabling the default handler.
389   return FALSE;
390 }
391 #endif
392 
MoveFocus(GtkWidget * widget,GtkDirectionType arg1)393 void GtkKeyBindingsHandler::MoveFocus(GtkWidget* widget,
394                                       GtkDirectionType arg1) {
395   // Just for disabling the default handler.
396 }
397 
398 }  // namespace gtk
399