Use unified build for UIProcess
[WebKit-https.git] / Source / WebKit / UIProcess / gtk / KeyBindingTranslator.cpp
1 /*
2  * Copyright (C) 2010, 2011 Igalia S.L.
3  *
4  *  This library is free software; you can redistribute it and/or
5  *  modify it under the terms of the GNU Lesser General Public
6  *  License as published by the Free Software Foundation; either
7  *  version 2 of the License, or (at your option) any later version.
8  *
9  *  This library is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  *  Lesser General Public License for more details.
13  *
14  *  You should have received a copy of the GNU Lesser General Public
15  *  License along with this library; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18
19 #include "config.h"
20 #include "KeyBindingTranslator.h"
21
22 #include <gdk/gdkkeysyms.h>
23 #include <gtk/gtk.h>
24
25 namespace WebKit {
26
27 static void backspaceCallback(GtkWidget* widget, KeyBindingTranslator* translator)
28 {
29     g_signal_stop_emission_by_name(widget, "backspace");
30     translator->addPendingEditorCommand("DeleteBackward");
31 }
32
33 static void selectAllCallback(GtkWidget* widget, gboolean select, KeyBindingTranslator* translator)
34 {
35     g_signal_stop_emission_by_name(widget, "select-all");
36     translator->addPendingEditorCommand(select ? "SelectAll" : "Unselect");
37 }
38
39 static void cutClipboardCallback(GtkWidget* widget, KeyBindingTranslator* translator)
40 {
41     g_signal_stop_emission_by_name(widget, "cut-clipboard");
42     translator->addPendingEditorCommand("Cut");
43 }
44
45 static void copyClipboardCallback(GtkWidget* widget, KeyBindingTranslator* translator)
46 {
47     g_signal_stop_emission_by_name(widget, "copy-clipboard");
48     translator->addPendingEditorCommand("Copy");
49 }
50
51 static void pasteClipboardCallback(GtkWidget* widget, KeyBindingTranslator* translator)
52 {
53     g_signal_stop_emission_by_name(widget, "paste-clipboard");
54     translator->addPendingEditorCommand("Paste");
55 }
56
57 static void toggleOverwriteCallback(GtkWidget* widget, KeyBindingTranslator* translator)
58 {
59     g_signal_stop_emission_by_name(widget, "toggle-overwrite");
60     translator->addPendingEditorCommand("OverWrite");
61 }
62
63 // GTK+ will still send these signals to the web view. So we can safely stop signal
64 // emission without breaking accessibility.
65 static void popupMenuCallback(GtkWidget* widget, KeyBindingTranslator*)
66 {
67     g_signal_stop_emission_by_name(widget, "popup-menu");
68 }
69
70 static void showHelpCallback(GtkWidget* widget, KeyBindingTranslator*)
71 {
72     g_signal_stop_emission_by_name(widget, "show-help");
73 }
74
75 static const char* const gtkDeleteCommands[][2] = {
76     { "DeleteBackward",               "DeleteForward"                        }, // Characters
77     { "DeleteWordBackward",           "DeleteWordForward"                    }, // Word ends
78     { "DeleteWordBackward",           "DeleteWordForward"                    }, // Words
79     { "DeleteToBeginningOfLine",      "DeleteToEndOfLine"                    }, // Lines
80     { "DeleteToBeginningOfLine",      "DeleteToEndOfLine"                    }, // Line ends
81     { "DeleteToBeginningOfParagraph", "DeleteToEndOfParagraph"               }, // Paragraph ends
82     { "DeleteToBeginningOfParagraph", "DeleteToEndOfParagraph"               }, // Paragraphs
83     { 0,                              0                                      } // Whitespace (M-\ in Emacs)
84 };
85
86 static void deleteFromCursorCallback(GtkWidget* widget, GtkDeleteType deleteType, gint count, KeyBindingTranslator* translator)
87 {
88     g_signal_stop_emission_by_name(widget, "delete-from-cursor");
89     int direction = count > 0 ? 1 : 0;
90
91     // Ensuring that deleteType <= G_N_ELEMENTS here results in a compiler warning
92     // that the condition is always true.
93
94     if (deleteType == GTK_DELETE_WORDS) {
95         if (!direction) {
96             translator->addPendingEditorCommand("MoveWordForward");
97             translator->addPendingEditorCommand("MoveWordBackward");
98         } else {
99             translator->addPendingEditorCommand("MoveWordBackward");
100             translator->addPendingEditorCommand("MoveWordForward");
101         }
102     } else if (deleteType == GTK_DELETE_DISPLAY_LINES) {
103         if (!direction)
104             translator->addPendingEditorCommand("MoveToBeginningOfLine");
105         else
106             translator->addPendingEditorCommand("MoveToEndOfLine");
107     } else if (deleteType == GTK_DELETE_PARAGRAPHS) {
108         if (!direction)
109             translator->addPendingEditorCommand("MoveToBeginningOfParagraph");
110         else
111             translator->addPendingEditorCommand("MoveToEndOfParagraph");
112     }
113
114     const char* rawCommand = gtkDeleteCommands[deleteType][direction];
115     if (!rawCommand)
116         return;
117
118     for (int i = 0; i < abs(count); i++)
119         translator->addPendingEditorCommand(rawCommand);
120 }
121
122 static const char* const gtkMoveCommands[][4] = {
123     { "MoveBackward",                                   "MoveForward",
124       "MoveBackwardAndModifySelection",                 "MoveForwardAndModifySelection"             }, // Forward/backward grapheme
125     { "MoveLeft",                                       "MoveRight",
126       "MoveBackwardAndModifySelection",                 "MoveForwardAndModifySelection"             }, // Left/right grapheme
127     { "MoveWordBackward",                               "MoveWordForward",
128       "MoveWordBackwardAndModifySelection",             "MoveWordForwardAndModifySelection"         }, // Forward/backward word
129     { "MoveUp",                                         "MoveDown",
130       "MoveUpAndModifySelection",                       "MoveDownAndModifySelection"                }, // Up/down line
131     { "MoveToBeginningOfLine",                          "MoveToEndOfLine",
132       "MoveToBeginningOfLineAndModifySelection",        "MoveToEndOfLineAndModifySelection"         }, // Up/down line ends
133     { 0,                                                0,
134       "MoveParagraphBackwardAndModifySelection",        "MoveParagraphForwardAndModifySelection"    }, // Up/down paragraphs
135     { "MoveToBeginningOfParagraph",                     "MoveToEndOfParagraph",
136       "MoveToBeginningOfParagraphAndModifySelection",   "MoveToEndOfParagraphAndModifySelection"    }, // Up/down paragraph ends.
137     { "MovePageUp",                                     "MovePageDown",
138       "MovePageUpAndModifySelection",                   "MovePageDownAndModifySelection"            }, // Up/down page
139     { "MoveToBeginningOfDocument",                      "MoveToEndOfDocument",
140       "MoveToBeginningOfDocumentAndModifySelection",    "MoveToEndOfDocumentAndModifySelection"     }, // Begin/end of buffer
141     { 0,                                                0,
142       0,                                                0                                           } // Horizontal page movement
143 };
144
145 static void moveCursorCallback(GtkWidget* widget, GtkMovementStep step, gint count, gboolean extendSelection, KeyBindingTranslator* translator)
146 {
147     g_signal_stop_emission_by_name(widget, "move-cursor");
148     int direction = count > 0 ? 1 : 0;
149     if (extendSelection)
150         direction += 2;
151
152     if (static_cast<unsigned>(step) >= G_N_ELEMENTS(gtkMoveCommands))
153         return;
154
155     const char* rawCommand = gtkMoveCommands[step][direction];
156     if (!rawCommand)
157         return;
158
159     for (int i = 0; i < abs(count); i++)
160         translator->addPendingEditorCommand(rawCommand);
161 }
162
163 KeyBindingTranslator::KeyBindingTranslator()
164     : m_nativeWidget(gtk_text_view_new())
165 {
166     g_signal_connect(m_nativeWidget.get(), "backspace", G_CALLBACK(backspaceCallback), this);
167     g_signal_connect(m_nativeWidget.get(), "cut-clipboard", G_CALLBACK(cutClipboardCallback), this);
168     g_signal_connect(m_nativeWidget.get(), "copy-clipboard", G_CALLBACK(copyClipboardCallback), this);
169     g_signal_connect(m_nativeWidget.get(), "paste-clipboard", G_CALLBACK(pasteClipboardCallback), this);
170     g_signal_connect(m_nativeWidget.get(), "select-all", G_CALLBACK(selectAllCallback), this);
171     g_signal_connect(m_nativeWidget.get(), "move-cursor", G_CALLBACK(moveCursorCallback), this);
172     g_signal_connect(m_nativeWidget.get(), "delete-from-cursor", G_CALLBACK(deleteFromCursorCallback), this);
173     g_signal_connect(m_nativeWidget.get(), "toggle-overwrite", G_CALLBACK(toggleOverwriteCallback), this);
174     g_signal_connect(m_nativeWidget.get(), "popup-menu", G_CALLBACK(popupMenuCallback), this);
175     g_signal_connect(m_nativeWidget.get(), "show-help", G_CALLBACK(showHelpCallback), this);
176 }
177
178 struct KeyCombinationEntry {
179     unsigned gdkKeyCode;
180     unsigned state;
181     const char* name;
182 };
183
184 static const KeyCombinationEntry customKeyBindings[] = {
185     { GDK_KEY_b,         GDK_CONTROL_MASK,               "ToggleBold"    },
186     { GDK_KEY_i,         GDK_CONTROL_MASK,               "ToggleItalic"  },
187     { GDK_KEY_Escape,    0,                              "Cancel"        },
188     { GDK_KEY_greater,   GDK_CONTROL_MASK,               "Cancel"        },
189     { GDK_KEY_Tab,       0,                              "InsertTab"     },
190     { GDK_KEY_Tab,       GDK_SHIFT_MASK,                 "InsertBacktab" },
191 };
192
193 Vector<String> KeyBindingTranslator::commandsForKeyEvent(GdkEventKey* event)
194 {
195     ASSERT(m_pendingEditorCommands.isEmpty());
196
197     gtk_bindings_activate_event(G_OBJECT(m_nativeWidget.get()), event);
198     if (!m_pendingEditorCommands.isEmpty())
199         return WTFMove(m_pendingEditorCommands);
200
201     // Special-case enter keys for we want them to work regardless of modifier.
202     if ((event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter || event->keyval == GDK_KEY_ISO_Enter))
203         return { "InsertNewLine" };
204
205     // For keypress events, we want charCode(), but keyCode() does that.
206     unsigned mapKey = event->state << 16 | event->keyval;
207     if (!mapKey)
208         return { };
209
210     for (unsigned i = 0; i < G_N_ELEMENTS(customKeyBindings); ++i) {
211         if (mapKey == (customKeyBindings[i].state << 16 | customKeyBindings[i].gdkKeyCode))
212             return { customKeyBindings[i].name };
213     }
214
215     return { };
216 }
217
218 } // namespace WebKit