LCOV - code coverage report
Current view: top level - widget/gtk - NativeKeyBindings.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 133 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 12 0.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13