1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/widget/gtk/NativeKeyBindings.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,358 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "mozilla/ArrayUtils.h" 1.10 +#include "mozilla/MathAlgorithms.h" 1.11 +#include "mozilla/TextEvents.h" 1.12 + 1.13 +#include "NativeKeyBindings.h" 1.14 +#include "nsString.h" 1.15 +#include "nsMemory.h" 1.16 +#include "nsGtkKeyUtils.h" 1.17 + 1.18 +#include <gtk/gtk.h> 1.19 +#include <gdk/gdkkeysyms.h> 1.20 +#include <gdk/gdk.h> 1.21 + 1.22 +namespace mozilla { 1.23 +namespace widget { 1.24 + 1.25 +static nsIWidget::DoCommandCallback gCurrentCallback; 1.26 +static void *gCurrentCallbackData; 1.27 +static bool gHandled; 1.28 + 1.29 +// Common GtkEntry and GtkTextView signals 1.30 +static void 1.31 +copy_clipboard_cb(GtkWidget *w, gpointer user_data) 1.32 +{ 1.33 + gCurrentCallback(CommandCopy, gCurrentCallbackData); 1.34 + g_signal_stop_emission_by_name(w, "copy_clipboard"); 1.35 + gHandled = true; 1.36 +} 1.37 + 1.38 +static void 1.39 +cut_clipboard_cb(GtkWidget *w, gpointer user_data) 1.40 +{ 1.41 + gCurrentCallback(CommandCut, gCurrentCallbackData); 1.42 + g_signal_stop_emission_by_name(w, "cut_clipboard"); 1.43 + gHandled = true; 1.44 +} 1.45 + 1.46 +// GTK distinguishes between display lines (wrapped, as they appear on the 1.47 +// screen) and paragraphs, which are runs of text terminated by a newline. 1.48 +// We don't have this distinction, so we always use editor's notion of 1.49 +// lines, which are newline-terminated. 1.50 + 1.51 +static const Command sDeleteCommands[][2] = { 1.52 + // backward, forward 1.53 + { CommandDeleteCharBackward, CommandDeleteCharForward }, // CHARS 1.54 + { CommandDeleteWordBackward, CommandDeleteWordForward }, // WORD_ENDS 1.55 + { CommandDeleteWordBackward, CommandDeleteWordForward }, // WORDS 1.56 + { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // LINES 1.57 + { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // LINE_ENDS 1.58 + { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // PARAGRAPH_ENDS 1.59 + { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // PARAGRAPHS 1.60 + // This deletes from the end of the previous word to the beginning of the 1.61 + // next word, but only if the caret is not in a word. 1.62 + // XXX need to implement in editor 1.63 + { CommandDoNothing, CommandDoNothing } // WHITESPACE 1.64 +}; 1.65 + 1.66 +static void 1.67 +delete_from_cursor_cb(GtkWidget *w, GtkDeleteType del_type, 1.68 + gint count, gpointer user_data) 1.69 +{ 1.70 + g_signal_stop_emission_by_name(w, "delete_from_cursor"); 1.71 + gHandled = true; 1.72 + 1.73 + bool forward = count > 0; 1.74 + if (uint32_t(del_type) >= ArrayLength(sDeleteCommands)) { 1.75 + // unsupported deletion type 1.76 + return; 1.77 + } 1.78 + 1.79 + if (del_type == GTK_DELETE_WORDS) { 1.80 + // This works like word_ends, except we first move the caret to the 1.81 + // beginning/end of the current word. 1.82 + if (forward) { 1.83 + gCurrentCallback(CommandWordNext, gCurrentCallbackData); 1.84 + gCurrentCallback(CommandWordPrevious, gCurrentCallbackData); 1.85 + } else { 1.86 + gCurrentCallback(CommandWordPrevious, gCurrentCallbackData); 1.87 + gCurrentCallback(CommandWordNext, gCurrentCallbackData); 1.88 + } 1.89 + } else if (del_type == GTK_DELETE_DISPLAY_LINES || 1.90 + del_type == GTK_DELETE_PARAGRAPHS) { 1.91 + 1.92 + // This works like display_line_ends, except we first move the caret to the 1.93 + // beginning/end of the current line. 1.94 + if (forward) { 1.95 + gCurrentCallback(CommandBeginLine, gCurrentCallbackData); 1.96 + } else { 1.97 + gCurrentCallback(CommandEndLine, gCurrentCallbackData); 1.98 + } 1.99 + } 1.100 + 1.101 + Command command = sDeleteCommands[del_type][forward]; 1.102 + if (!command) { 1.103 + return; // unsupported command 1.104 + } 1.105 + 1.106 + unsigned int absCount = Abs(count); 1.107 + for (unsigned int i = 0; i < absCount; ++i) { 1.108 + gCurrentCallback(command, gCurrentCallbackData); 1.109 + } 1.110 +} 1.111 + 1.112 +static const Command sMoveCommands[][2][2] = { 1.113 + // non-extend { backward, forward }, extend { backward, forward } 1.114 + // GTK differentiates between logical position, which is prev/next, 1.115 + // and visual position, which is always left/right. 1.116 + // We should fix this to work the same way for RTL text input. 1.117 + { // LOGICAL_POSITIONS 1.118 + { CommandCharPrevious, CommandCharNext }, 1.119 + { CommandSelectCharPrevious, CommandSelectCharNext } 1.120 + }, 1.121 + { // VISUAL_POSITIONS 1.122 + { CommandCharPrevious, CommandCharNext }, 1.123 + { CommandSelectCharPrevious, CommandSelectCharNext } 1.124 + }, 1.125 + { // WORDS 1.126 + { CommandWordPrevious, CommandWordNext }, 1.127 + { CommandSelectWordPrevious, CommandSelectWordNext } 1.128 + }, 1.129 + { // DISPLAY_LINES 1.130 + { CommandLinePrevious, CommandLineNext }, 1.131 + { CommandSelectLinePrevious, CommandSelectLineNext } 1.132 + }, 1.133 + { // DISPLAY_LINE_ENDS 1.134 + { CommandBeginLine, CommandEndLine }, 1.135 + { CommandSelectBeginLine, CommandSelectEndLine } 1.136 + }, 1.137 + { // PARAGRAPHS 1.138 + { CommandLinePrevious, CommandLineNext }, 1.139 + { CommandSelectLinePrevious, CommandSelectLineNext } 1.140 + }, 1.141 + { // PARAGRAPH_ENDS 1.142 + { CommandBeginLine, CommandEndLine }, 1.143 + { CommandSelectBeginLine, CommandSelectEndLine } 1.144 + }, 1.145 + { // PAGES 1.146 + { CommandMovePageUp, CommandMovePageDown }, 1.147 + { CommandSelectPageUp, CommandSelectPageDown } 1.148 + }, 1.149 + { // BUFFER_ENDS 1.150 + { CommandMoveTop, CommandMoveBottom }, 1.151 + { CommandSelectTop, CommandSelectBottom } 1.152 + }, 1.153 + { // HORIZONTAL_PAGES (unsupported) 1.154 + { CommandDoNothing, CommandDoNothing }, 1.155 + { CommandDoNothing, CommandDoNothing } 1.156 + } 1.157 +}; 1.158 + 1.159 +static void 1.160 +move_cursor_cb(GtkWidget *w, GtkMovementStep step, gint count, 1.161 + gboolean extend_selection, gpointer user_data) 1.162 +{ 1.163 + g_signal_stop_emission_by_name(w, "move_cursor"); 1.164 + gHandled = true; 1.165 + bool forward = count > 0; 1.166 + if (uint32_t(step) >= ArrayLength(sMoveCommands)) { 1.167 + // unsupported movement type 1.168 + return; 1.169 + } 1.170 + 1.171 + Command command = sMoveCommands[step][extend_selection][forward]; 1.172 + if (!command) { 1.173 + return; // unsupported command 1.174 + } 1.175 + 1.176 + unsigned int absCount = Abs(count); 1.177 + for (unsigned int i = 0; i < absCount; ++i) { 1.178 + gCurrentCallback(command, gCurrentCallbackData); 1.179 + } 1.180 +} 1.181 + 1.182 +static void 1.183 +paste_clipboard_cb(GtkWidget *w, gpointer user_data) 1.184 +{ 1.185 + gCurrentCallback(CommandPaste, gCurrentCallbackData); 1.186 + g_signal_stop_emission_by_name(w, "paste_clipboard"); 1.187 + gHandled = true; 1.188 +} 1.189 + 1.190 +// GtkTextView-only signals 1.191 +static void 1.192 +select_all_cb(GtkWidget *w, gboolean select, gpointer user_data) 1.193 +{ 1.194 + gCurrentCallback(CommandSelectAll, gCurrentCallbackData); 1.195 + g_signal_stop_emission_by_name(w, "select_all"); 1.196 + gHandled = true; 1.197 +} 1.198 + 1.199 +NativeKeyBindings* NativeKeyBindings::sInstanceForSingleLineEditor = nullptr; 1.200 +NativeKeyBindings* NativeKeyBindings::sInstanceForMultiLineEditor = nullptr; 1.201 + 1.202 +// static 1.203 +NativeKeyBindings* 1.204 +NativeKeyBindings::GetInstance(NativeKeyBindingsType aType) 1.205 +{ 1.206 + switch (aType) { 1.207 + case nsIWidget::NativeKeyBindingsForSingleLineEditor: 1.208 + if (!sInstanceForSingleLineEditor) { 1.209 + sInstanceForSingleLineEditor = new NativeKeyBindings(); 1.210 + sInstanceForSingleLineEditor->Init(aType); 1.211 + } 1.212 + return sInstanceForSingleLineEditor; 1.213 + 1.214 + default: 1.215 + // fallback to multiline editor case in release build 1.216 + MOZ_ASSERT(false, "aType is invalid or not yet implemented"); 1.217 + case nsIWidget::NativeKeyBindingsForMultiLineEditor: 1.218 + case nsIWidget::NativeKeyBindingsForRichTextEditor: 1.219 + if (!sInstanceForMultiLineEditor) { 1.220 + sInstanceForMultiLineEditor = new NativeKeyBindings(); 1.221 + sInstanceForMultiLineEditor->Init(aType); 1.222 + } 1.223 + return sInstanceForMultiLineEditor; 1.224 + } 1.225 +} 1.226 + 1.227 +// static 1.228 +void 1.229 +NativeKeyBindings::Shutdown() 1.230 +{ 1.231 + delete sInstanceForSingleLineEditor; 1.232 + sInstanceForSingleLineEditor = nullptr; 1.233 + delete sInstanceForMultiLineEditor; 1.234 + sInstanceForMultiLineEditor = nullptr; 1.235 +} 1.236 + 1.237 +void 1.238 +NativeKeyBindings::Init(NativeKeyBindingsType aType) 1.239 +{ 1.240 + switch (aType) { 1.241 + case nsIWidget::NativeKeyBindingsForSingleLineEditor: 1.242 + mNativeTarget = gtk_entry_new(); 1.243 + break; 1.244 + default: 1.245 + mNativeTarget = gtk_text_view_new(); 1.246 + if (gtk_major_version > 2 || 1.247 + (gtk_major_version == 2 && (gtk_minor_version > 2 || 1.248 + (gtk_minor_version == 2 && 1.249 + gtk_micro_version >= 2)))) { 1.250 + // select_all only exists in gtk >= 2.2.2. Prior to that, 1.251 + // ctrl+a is bound to (move to beginning, select to end). 1.252 + g_signal_connect(mNativeTarget, "select_all", 1.253 + G_CALLBACK(select_all_cb), this); 1.254 + } 1.255 + break; 1.256 + } 1.257 + 1.258 + g_object_ref_sink(mNativeTarget); 1.259 + 1.260 + g_signal_connect(mNativeTarget, "copy_clipboard", 1.261 + G_CALLBACK(copy_clipboard_cb), this); 1.262 + g_signal_connect(mNativeTarget, "cut_clipboard", 1.263 + G_CALLBACK(cut_clipboard_cb), this); 1.264 + g_signal_connect(mNativeTarget, "delete_from_cursor", 1.265 + G_CALLBACK(delete_from_cursor_cb), this); 1.266 + g_signal_connect(mNativeTarget, "move_cursor", 1.267 + G_CALLBACK(move_cursor_cb), this); 1.268 + g_signal_connect(mNativeTarget, "paste_clipboard", 1.269 + G_CALLBACK(paste_clipboard_cb), this); 1.270 +} 1.271 + 1.272 +NativeKeyBindings::~NativeKeyBindings() 1.273 +{ 1.274 + gtk_widget_destroy(mNativeTarget); 1.275 + g_object_unref(mNativeTarget); 1.276 +} 1.277 + 1.278 +bool 1.279 +NativeKeyBindings::Execute(const WidgetKeyboardEvent& aEvent, 1.280 + DoCommandCallback aCallback, 1.281 + void* aCallbackData) 1.282 +{ 1.283 + // If the native key event is set, it must be synthesized for tests. 1.284 + // We just ignore such events because this behavior depends on system 1.285 + // settings. 1.286 + if (!aEvent.mNativeKeyEvent) { 1.287 + // It must be synthesized event or dispatched DOM event from chrome. 1.288 + return false; 1.289 + } 1.290 + 1.291 + guint keyval; 1.292 + 1.293 + if (aEvent.charCode) { 1.294 + keyval = gdk_unicode_to_keyval(aEvent.charCode); 1.295 + } else { 1.296 + keyval = 1.297 + static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent)->keyval; 1.298 + } 1.299 + 1.300 + if (ExecuteInternal(aEvent, aCallback, aCallbackData, keyval)) { 1.301 + return true; 1.302 + } 1.303 + 1.304 + for (uint32_t i = 0; i < aEvent.alternativeCharCodes.Length(); ++i) { 1.305 + uint32_t ch = aEvent.IsShift() ? 1.306 + aEvent.alternativeCharCodes[i].mShiftedCharCode : 1.307 + aEvent.alternativeCharCodes[i].mUnshiftedCharCode; 1.308 + if (ch && ch != aEvent.charCode) { 1.309 + keyval = gdk_unicode_to_keyval(ch); 1.310 + if (ExecuteInternal(aEvent, aCallback, aCallbackData, keyval)) { 1.311 + return true; 1.312 + } 1.313 + } 1.314 + } 1.315 + 1.316 +/* 1.317 +gtk_bindings_activate_event is preferable, but it has unresolved bug: 1.318 +http://bugzilla.gnome.org/show_bug.cgi?id=162726 1.319 +The bug was already marked as FIXED. However, somebody reports that the 1.320 +bug still exists. 1.321 +Also gtk_bindings_activate may work with some non-shortcuts operations 1.322 +(todo: check it). See bug 411005 and bug 406407. 1.323 + 1.324 +Code, which should be used after fixing GNOME bug 162726: 1.325 + 1.326 + gtk_bindings_activate_event(GTK_OBJECT(mNativeTarget), 1.327 + static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent)); 1.328 +*/ 1.329 + 1.330 + return false; 1.331 +} 1.332 + 1.333 +bool 1.334 +NativeKeyBindings::ExecuteInternal(const WidgetKeyboardEvent& aEvent, 1.335 + DoCommandCallback aCallback, 1.336 + void* aCallbackData, 1.337 + guint aKeyval) 1.338 +{ 1.339 + guint modifiers = 1.340 + static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent)->state; 1.341 + 1.342 + gCurrentCallback = aCallback; 1.343 + gCurrentCallbackData = aCallbackData; 1.344 + 1.345 + gHandled = false; 1.346 +#if (MOZ_WIDGET_GTK == 2) 1.347 + gtk_bindings_activate(GTK_OBJECT(mNativeTarget), 1.348 + aKeyval, GdkModifierType(modifiers)); 1.349 +#else 1.350 + gtk_bindings_activate(G_OBJECT(mNativeTarget), 1.351 + aKeyval, GdkModifierType(modifiers)); 1.352 +#endif 1.353 + 1.354 + gCurrentCallback = nullptr; 1.355 + gCurrentCallbackData = nullptr; 1.356 + 1.357 + return gHandled; 1.358 +} 1.359 + 1.360 +} // namespace widget 1.361 +} // namespace mozilla