widget/gtk/NativeKeyBindings.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "mozilla/ArrayUtils.h"
     7 #include "mozilla/MathAlgorithms.h"
     8 #include "mozilla/TextEvents.h"
    10 #include "NativeKeyBindings.h"
    11 #include "nsString.h"
    12 #include "nsMemory.h"
    13 #include "nsGtkKeyUtils.h"
    15 #include <gtk/gtk.h>
    16 #include <gdk/gdkkeysyms.h>
    17 #include <gdk/gdk.h>
    19 namespace mozilla {
    20 namespace widget {
    22 static nsIWidget::DoCommandCallback gCurrentCallback;
    23 static void *gCurrentCallbackData;
    24 static bool gHandled;
    26 // Common GtkEntry and GtkTextView signals
    27 static void
    28 copy_clipboard_cb(GtkWidget *w, gpointer user_data)
    29 {
    30   gCurrentCallback(CommandCopy, gCurrentCallbackData);
    31   g_signal_stop_emission_by_name(w, "copy_clipboard");
    32   gHandled = true;
    33 }
    35 static void
    36 cut_clipboard_cb(GtkWidget *w, gpointer user_data)
    37 {
    38   gCurrentCallback(CommandCut, gCurrentCallbackData);
    39   g_signal_stop_emission_by_name(w, "cut_clipboard");
    40   gHandled = true;
    41 }
    43 // GTK distinguishes between display lines (wrapped, as they appear on the
    44 // screen) and paragraphs, which are runs of text terminated by a newline.
    45 // We don't have this distinction, so we always use editor's notion of
    46 // lines, which are newline-terminated.
    48 static const Command sDeleteCommands[][2] = {
    49   // backward, forward
    50   { CommandDeleteCharBackward, CommandDeleteCharForward },    // CHARS
    51   { CommandDeleteWordBackward, CommandDeleteWordForward },    // WORD_ENDS
    52   { CommandDeleteWordBackward, CommandDeleteWordForward },    // WORDS
    53   { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // LINES
    54   { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // LINE_ENDS
    55   { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // PARAGRAPH_ENDS
    56   { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // PARAGRAPHS
    57   // This deletes from the end of the previous word to the beginning of the
    58   // next word, but only if the caret is not in a word.
    59   // XXX need to implement in editor
    60   { CommandDoNothing, CommandDoNothing } // WHITESPACE
    61 };
    63 static void
    64 delete_from_cursor_cb(GtkWidget *w, GtkDeleteType del_type,
    65                       gint count, gpointer user_data)
    66 {
    67   g_signal_stop_emission_by_name(w, "delete_from_cursor");
    68   gHandled = true;
    70   bool forward = count > 0;
    71   if (uint32_t(del_type) >= ArrayLength(sDeleteCommands)) {
    72     // unsupported deletion type
    73     return;
    74   }
    76   if (del_type == GTK_DELETE_WORDS) {
    77     // This works like word_ends, except we first move the caret to the
    78     // beginning/end of the current word.
    79     if (forward) {
    80       gCurrentCallback(CommandWordNext, gCurrentCallbackData);
    81       gCurrentCallback(CommandWordPrevious, gCurrentCallbackData);
    82     } else {
    83       gCurrentCallback(CommandWordPrevious, gCurrentCallbackData);
    84       gCurrentCallback(CommandWordNext, gCurrentCallbackData);
    85     }
    86   } else if (del_type == GTK_DELETE_DISPLAY_LINES ||
    87              del_type == GTK_DELETE_PARAGRAPHS) {
    89     // This works like display_line_ends, except we first move the caret to the
    90     // beginning/end of the current line.
    91     if (forward) {
    92       gCurrentCallback(CommandBeginLine, gCurrentCallbackData);
    93     } else {
    94       gCurrentCallback(CommandEndLine, gCurrentCallbackData);
    95     }
    96   }
    98   Command command = sDeleteCommands[del_type][forward];
    99   if (!command) {
   100     return; // unsupported command
   101   }
   103   unsigned int absCount = Abs(count);
   104   for (unsigned int i = 0; i < absCount; ++i) {
   105     gCurrentCallback(command, gCurrentCallbackData);
   106   }
   107 }
   109 static const Command sMoveCommands[][2][2] = {
   110   // non-extend { backward, forward }, extend { backward, forward }
   111   // GTK differentiates between logical position, which is prev/next,
   112   // and visual position, which is always left/right.
   113   // We should fix this to work the same way for RTL text input.
   114   { // LOGICAL_POSITIONS
   115     { CommandCharPrevious, CommandCharNext },
   116     { CommandSelectCharPrevious, CommandSelectCharNext }
   117   },
   118   { // VISUAL_POSITIONS
   119     { CommandCharPrevious, CommandCharNext },
   120     { CommandSelectCharPrevious, CommandSelectCharNext }
   121   },
   122   { // WORDS
   123     { CommandWordPrevious, CommandWordNext },
   124     { CommandSelectWordPrevious, CommandSelectWordNext }
   125   },
   126   { // DISPLAY_LINES
   127     { CommandLinePrevious, CommandLineNext },
   128     { CommandSelectLinePrevious, CommandSelectLineNext }
   129   },
   130   { // DISPLAY_LINE_ENDS
   131     { CommandBeginLine, CommandEndLine },
   132     { CommandSelectBeginLine, CommandSelectEndLine }
   133   },
   134   { // PARAGRAPHS
   135     { CommandLinePrevious, CommandLineNext },
   136     { CommandSelectLinePrevious, CommandSelectLineNext }
   137   },
   138   { // PARAGRAPH_ENDS
   139     { CommandBeginLine, CommandEndLine },
   140     { CommandSelectBeginLine, CommandSelectEndLine }
   141   },
   142   { // PAGES
   143     { CommandMovePageUp, CommandMovePageDown },
   144     { CommandSelectPageUp, CommandSelectPageDown }
   145   },
   146   { // BUFFER_ENDS
   147     { CommandMoveTop, CommandMoveBottom },
   148     { CommandSelectTop, CommandSelectBottom }
   149   },
   150   { // HORIZONTAL_PAGES (unsupported)
   151     { CommandDoNothing, CommandDoNothing },
   152     { CommandDoNothing, CommandDoNothing }
   153   }
   154 };
   156 static void
   157 move_cursor_cb(GtkWidget *w, GtkMovementStep step, gint count,
   158                gboolean extend_selection, gpointer user_data)
   159 {
   160   g_signal_stop_emission_by_name(w, "move_cursor");
   161   gHandled = true;
   162   bool forward = count > 0;
   163   if (uint32_t(step) >= ArrayLength(sMoveCommands)) {
   164     // unsupported movement type
   165     return;
   166   }
   168   Command command = sMoveCommands[step][extend_selection][forward];
   169   if (!command) {
   170     return; // unsupported command
   171   }
   173   unsigned int absCount = Abs(count);
   174   for (unsigned int i = 0; i < absCount; ++i) {
   175     gCurrentCallback(command, gCurrentCallbackData);
   176   }
   177 }
   179 static void
   180 paste_clipboard_cb(GtkWidget *w, gpointer user_data)
   181 {
   182   gCurrentCallback(CommandPaste, gCurrentCallbackData);
   183   g_signal_stop_emission_by_name(w, "paste_clipboard");
   184   gHandled = true;
   185 }
   187 // GtkTextView-only signals
   188 static void
   189 select_all_cb(GtkWidget *w, gboolean select, gpointer user_data)
   190 {
   191   gCurrentCallback(CommandSelectAll, gCurrentCallbackData);
   192   g_signal_stop_emission_by_name(w, "select_all");
   193   gHandled = true;
   194 }
   196 NativeKeyBindings* NativeKeyBindings::sInstanceForSingleLineEditor = nullptr;
   197 NativeKeyBindings* NativeKeyBindings::sInstanceForMultiLineEditor = nullptr;
   199 // static
   200 NativeKeyBindings*
   201 NativeKeyBindings::GetInstance(NativeKeyBindingsType aType)
   202 {
   203   switch (aType) {
   204     case nsIWidget::NativeKeyBindingsForSingleLineEditor:
   205       if (!sInstanceForSingleLineEditor) {
   206         sInstanceForSingleLineEditor = new NativeKeyBindings();
   207         sInstanceForSingleLineEditor->Init(aType);
   208       }
   209       return sInstanceForSingleLineEditor;
   211     default:
   212       // fallback to multiline editor case in release build
   213       MOZ_ASSERT(false, "aType is invalid or not yet implemented");
   214     case nsIWidget::NativeKeyBindingsForMultiLineEditor:
   215     case nsIWidget::NativeKeyBindingsForRichTextEditor:
   216       if (!sInstanceForMultiLineEditor) {
   217         sInstanceForMultiLineEditor = new NativeKeyBindings();
   218         sInstanceForMultiLineEditor->Init(aType);
   219       }
   220       return sInstanceForMultiLineEditor;
   221   }
   222 }
   224 // static
   225 void
   226 NativeKeyBindings::Shutdown()
   227 {
   228   delete sInstanceForSingleLineEditor;
   229   sInstanceForSingleLineEditor = nullptr;
   230   delete sInstanceForMultiLineEditor;
   231   sInstanceForMultiLineEditor = nullptr;
   232 }
   234 void
   235 NativeKeyBindings::Init(NativeKeyBindingsType  aType)
   236 {
   237   switch (aType) {
   238   case nsIWidget::NativeKeyBindingsForSingleLineEditor:
   239     mNativeTarget = gtk_entry_new();
   240     break;
   241   default:
   242     mNativeTarget = gtk_text_view_new();
   243     if (gtk_major_version > 2 ||
   244         (gtk_major_version == 2 && (gtk_minor_version > 2 ||
   245                                     (gtk_minor_version == 2 &&
   246                                      gtk_micro_version >= 2)))) {
   247       // select_all only exists in gtk >= 2.2.2.  Prior to that,
   248       // ctrl+a is bound to (move to beginning, select to end).
   249       g_signal_connect(mNativeTarget, "select_all",
   250                        G_CALLBACK(select_all_cb), this);
   251     }
   252     break;
   253   }
   255   g_object_ref_sink(mNativeTarget);
   257   g_signal_connect(mNativeTarget, "copy_clipboard",
   258                    G_CALLBACK(copy_clipboard_cb), this);
   259   g_signal_connect(mNativeTarget, "cut_clipboard",
   260                    G_CALLBACK(cut_clipboard_cb), this);
   261   g_signal_connect(mNativeTarget, "delete_from_cursor",
   262                    G_CALLBACK(delete_from_cursor_cb), this);
   263   g_signal_connect(mNativeTarget, "move_cursor",
   264                    G_CALLBACK(move_cursor_cb), this);
   265   g_signal_connect(mNativeTarget, "paste_clipboard",
   266                    G_CALLBACK(paste_clipboard_cb), this);
   267 }
   269 NativeKeyBindings::~NativeKeyBindings()
   270 {
   271   gtk_widget_destroy(mNativeTarget);
   272   g_object_unref(mNativeTarget);
   273 }
   275 bool
   276 NativeKeyBindings::Execute(const WidgetKeyboardEvent& aEvent,
   277                            DoCommandCallback aCallback,
   278                            void* aCallbackData)
   279 {
   280   // If the native key event is set, it must be synthesized for tests.
   281   // We just ignore such events because this behavior depends on system
   282   // settings.
   283   if (!aEvent.mNativeKeyEvent) {
   284     // It must be synthesized event or dispatched DOM event from chrome.
   285     return false;
   286   }
   288   guint keyval;
   290   if (aEvent.charCode) {
   291     keyval = gdk_unicode_to_keyval(aEvent.charCode);
   292   } else {
   293     keyval =
   294       static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent)->keyval;
   295   }
   297   if (ExecuteInternal(aEvent, aCallback, aCallbackData, keyval)) {
   298     return true;
   299   }
   301   for (uint32_t i = 0; i < aEvent.alternativeCharCodes.Length(); ++i) {
   302     uint32_t ch = aEvent.IsShift() ?
   303       aEvent.alternativeCharCodes[i].mShiftedCharCode :
   304       aEvent.alternativeCharCodes[i].mUnshiftedCharCode;
   305     if (ch && ch != aEvent.charCode) {
   306       keyval = gdk_unicode_to_keyval(ch);
   307       if (ExecuteInternal(aEvent, aCallback, aCallbackData, keyval)) {
   308         return true;
   309       }
   310     }
   311   }
   313 /*
   314 gtk_bindings_activate_event is preferable, but it has unresolved bug:
   315 http://bugzilla.gnome.org/show_bug.cgi?id=162726
   316 The bug was already marked as FIXED.  However, somebody reports that the
   317 bug still exists.
   318 Also gtk_bindings_activate may work with some non-shortcuts operations
   319 (todo: check it). See bug 411005 and bug 406407.
   321 Code, which should be used after fixing GNOME bug 162726:
   323   gtk_bindings_activate_event(GTK_OBJECT(mNativeTarget),
   324     static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent));
   325 */
   327   return false;
   328 }
   330 bool
   331 NativeKeyBindings::ExecuteInternal(const WidgetKeyboardEvent& aEvent,
   332                                    DoCommandCallback aCallback,
   333                                    void* aCallbackData,
   334                                    guint aKeyval)
   335 {
   336   guint modifiers =
   337     static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent)->state;
   339   gCurrentCallback = aCallback;
   340   gCurrentCallbackData = aCallbackData;
   342   gHandled = false;
   343 #if (MOZ_WIDGET_GTK == 2)
   344   gtk_bindings_activate(GTK_OBJECT(mNativeTarget),
   345                         aKeyval, GdkModifierType(modifiers));
   346 #else
   347   gtk_bindings_activate(G_OBJECT(mNativeTarget),
   348                         aKeyval, GdkModifierType(modifiers));
   349 #endif
   351   gCurrentCallback = nullptr;
   352   gCurrentCallbackData = nullptr;
   354   return gHandled;
   355 }
   357 } // namespace widget
   358 } // namespace mozilla

mercurial