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