Back to index

lightning-sunbird  0.9+nobinonly
nsNativeKeyBindings.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * IBM Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 2004
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *  Brian Ryner <bryner@brianryner.com>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "nsNativeKeyBindings.h"
00040 #include "nsString.h"
00041 #include "nsMemory.h"
00042 #include "nsGtkKeyUtils.h"
00043 
00044 #include <gtk/gtkentry.h>
00045 #include <gtk/gtktextview.h>
00046 #include <gtk/gtkbindings.h>
00047 #include <gtk/gtkmain.h>
00048 #include <gdk/gdkkeysyms.h>
00049 
00050 static nsINativeKeyBindings::DoCommandCallback gCurrentCallback;
00051 static void *gCurrentCallbackData;
00052 static PRBool gHandled;
00053 
00054 // Common GtkEntry and GtkTextView signals
00055 static void
00056 copy_clipboard_cb(GtkWidget *w, gpointer user_data)
00057 {
00058   gCurrentCallback("cmd_copy", gCurrentCallbackData);
00059   g_signal_stop_emission_by_name(w, "copy_clipboard");
00060   gHandled = PR_TRUE;
00061 }
00062 
00063 static void
00064 cut_clipboard_cb(GtkWidget *w, gpointer user_data)
00065 {
00066   gCurrentCallback("cmd_cut", gCurrentCallbackData);
00067   g_signal_stop_emission_by_name(w, "cut_clipboard");
00068   gHandled = PR_TRUE;
00069 }
00070 
00071 // GTK distinguishes between display lines (wrapped, as they appear on the
00072 // screen) and paragraphs, which are runs of text terminated by a newline.
00073 // We don't have this distinction, so we always use editor's notion of
00074 // lines, which are newline-terminated.
00075 
00076 static const char *const sDeleteCommands[][2] = {
00077   // backward, forward
00078   { "cmd_deleteCharBackward", "cmd_deleteCharForward" },    // CHARS
00079   { "cmd_deleteWordBackward", "cmd_deleteWordForward" },    // WORD_ENDS
00080   { "cmd_deleteWordBackward", "cmd_deleteWordForward" },    // WORDS
00081   { "cmd_deleteToBeginningOfLine", "cmd_deleteToEndOfLine" }, // LINES
00082   { "cmd_deleteToBeginningOfLine", "cmd_deleteToEndOfLine" }, // LINE_ENDS
00083   { "cmd_deleteToBeginningOfLine", "cmd_deleteToEndOfLine" }, // PARAGRAPH_ENDS
00084   { "cmd_deleteToBeginningOfLine", "cmd_deleteToEndOfLine" }, // PARAGRAPHS
00085   // This deletes from the end of the previous word to the beginning of the
00086   // next word, but only if the caret is not in a word.
00087   // XXX need to implement in editor
00088   { nsnull, nsnull } // WHITESPACE
00089 };
00090 
00091 static void
00092 delete_from_cursor_cb(GtkWidget *w, GtkDeleteType del_type,
00093                       gint count, gpointer user_data)
00094 {
00095   g_signal_stop_emission_by_name(w, "delete_from_cursor");
00096   gHandled = PR_TRUE;
00097 
00098   PRBool forward = count > 0;
00099   if (PRUint32(del_type) >= NS_ARRAY_LENGTH(sDeleteCommands)) {
00100     // unsupported deletion type
00101     return;
00102   }
00103 
00104   if (del_type == GTK_DELETE_WORDS) {
00105     // This works like word_ends, except we first move the caret to the
00106     // beginning/end of the current word.
00107     if (forward) {
00108       gCurrentCallback("cmd_wordNext", gCurrentCallbackData);
00109       gCurrentCallback("cmd_wordPrevious", gCurrentCallbackData);
00110     } else {
00111       gCurrentCallback("cmd_wordPrevious", gCurrentCallbackData);
00112       gCurrentCallback("cmd_wordNext", gCurrentCallbackData);
00113     }
00114   } else if (del_type == GTK_DELETE_DISPLAY_LINES ||
00115              del_type == GTK_DELETE_PARAGRAPHS) {
00116 
00117     // This works like display_line_ends, except we first move the caret to the
00118     // beginning/end of the current line.
00119     if (forward) {
00120       gCurrentCallback("cmd_beginLine", gCurrentCallbackData);
00121     } else {
00122       gCurrentCallback("cmd_endLine", gCurrentCallbackData);
00123     }
00124   }
00125 
00126   const char *cmd = sDeleteCommands[del_type][forward];
00127   if (!cmd)
00128     return; // unsupported command
00129 
00130   count = PR_ABS(count);
00131   for (int i = 0; i < count; ++i) {
00132     gCurrentCallback(cmd, gCurrentCallbackData);
00133   }
00134 }
00135 
00136 static const char *const sMoveCommands[][2][2] = {
00137   // non-extend { backward, forward }, extend { backward, forward }
00138   // GTK differentiates between logical position, which is prev/next,
00139   // and visual position, which is always left/right.
00140   // We should fix this to work the same way for RTL text input.
00141   { // LOGICAL_POSITIONS
00142     { "cmd_charPrevious", "cmd_charNext" },
00143     { "cmd_selectCharPrevious", "cmd_selectCharNext" }
00144   },
00145   { // VISUAL_POSITIONS
00146     { "cmd_charPrevious", "cmd_charNext" },
00147     { "cmd_selectCharPrevious", "cmd_selectCharNext" }
00148   },
00149   { // WORDS
00150     { "cmd_wordPrevious", "cmd_wordNext" },
00151     { "cmd_selectWordPrevious", "cmd_selectWordNext" }
00152   },
00153   { // DISPLAY_LINES
00154     { "cmd_linePrevious", "cmd_lineNext" },
00155     { "cmd_selectLinePrevious", "cmd_selectLineNext" }
00156   },
00157   { // DISPLAY_LINE_ENDS
00158     { "cmd_beginLine", "cmd_endLine" },
00159     { "cmd_selectBeginLine", "cmd_selectEndLine" }
00160   },
00161   { // PARAGRAPHS
00162     { "cmd_linePrevious", "cmd_lineNext" },
00163     { "cmd_selectLinePrevious", "cmd_selectLineNext" }
00164   },
00165   { // PARAGRAPH_ENDS
00166     { "cmd_beginLine", "cmd_endLine" },
00167     { "cmd_selectBeginLine", "cmd_selectEndLine" }
00168   },
00169   { // PAGES
00170     { "cmd_movePageUp", "cmd_movePageDown" },
00171     { "cmd_selectPageUp", "cmd_selectPageDown" }
00172   },
00173   { // BUFFER_ENDS
00174     { "cmd_moveTop", "cmd_moveBottom" },
00175     { "cmd_selectTop", "cmd_selectBottom" }
00176   },
00177   { // HORIZONTAL_PAGES (unsupported)
00178     { nsnull, nsnull },
00179     { nsnull, nsnull }
00180   }
00181 };
00182 
00183 static void
00184 move_cursor_cb(GtkWidget *w, GtkMovementStep step, gint count,
00185                gboolean extend_selection, gpointer user_data)
00186 {
00187   g_signal_stop_emission_by_name(w, "move_cursor");
00188   gHandled = PR_TRUE;
00189   PRBool forward = count > 0;
00190   if (PRUint32(step) >= NS_ARRAY_LENGTH(sMoveCommands)) {
00191     // unsupported movement type
00192     return;
00193   }
00194 
00195   const char *cmd = sMoveCommands[step][extend_selection][forward];
00196   if (!cmd)
00197     return; // unsupported command
00198 
00199   
00200   count = PR_ABS(count);
00201   for (int i = 0; i < count; ++i) {
00202     gCurrentCallback(cmd, gCurrentCallbackData);
00203   }
00204 }
00205 
00206 static void
00207 paste_clipboard_cb(GtkWidget *w, gpointer user_data)
00208 {
00209   gCurrentCallback("cmd_paste", gCurrentCallbackData);
00210   g_signal_stop_emission_by_name(w, "paste_clipboard");
00211   gHandled = PR_TRUE;
00212 }
00213 
00214 // GtkTextView-only signals
00215 static void
00216 select_all_cb(GtkWidget *w, gboolean select, gpointer user_data)
00217 {
00218   gCurrentCallback("cmd_selectAll", gCurrentCallbackData);
00219   g_signal_stop_emission_by_name(w, "select_all");
00220   gHandled = PR_TRUE;
00221 }
00222 
00223 void
00224 nsNativeKeyBindings::Init(NativeKeyBindingsType  aType)
00225 {
00226   switch (aType) {
00227   case eKeyBindings_Input:
00228     mNativeTarget = gtk_entry_new();
00229     break;
00230   case eKeyBindings_TextArea:
00231     mNativeTarget = gtk_text_view_new();
00232     if (gtk_major_version > 2 ||
00233         (gtk_major_version == 2 && (gtk_minor_version > 2 ||
00234                                     (gtk_minor_version == 2 &&
00235                                      gtk_micro_version >= 2)))) {
00236       // select_all only exists in gtk >= 2.2.2.  Prior to that,
00237       // ctrl+a is bound to (move to beginning, select to end).
00238       g_signal_connect(G_OBJECT(mNativeTarget), "select_all",
00239                        G_CALLBACK(select_all_cb), this);
00240     }
00241     break;
00242   }
00243 
00244   g_signal_connect(G_OBJECT(mNativeTarget), "copy_clipboard",
00245                    G_CALLBACK(copy_clipboard_cb), this);
00246   g_signal_connect(G_OBJECT(mNativeTarget), "cut_clipboard",
00247                    G_CALLBACK(cut_clipboard_cb), this);
00248   g_signal_connect(G_OBJECT(mNativeTarget), "delete_from_cursor",
00249                    G_CALLBACK(delete_from_cursor_cb), this);
00250   g_signal_connect(G_OBJECT(mNativeTarget), "move_cursor",
00251                    G_CALLBACK(move_cursor_cb), this);
00252   g_signal_connect(G_OBJECT(mNativeTarget), "paste_clipboard",
00253                    G_CALLBACK(paste_clipboard_cb), this);
00254 }
00255 
00256 nsNativeKeyBindings::~nsNativeKeyBindings()
00257 {
00258   gtk_widget_destroy(mNativeTarget);
00259 }
00260 
00261 NS_IMPL_ISUPPORTS1(nsNativeKeyBindings, nsINativeKeyBindings)
00262 
00263 PRBool
00264 nsNativeKeyBindings::KeyDown(const nsNativeKeyEvent& aEvent,
00265                              DoCommandCallback aCallback, void *aCallbackData)
00266 {
00267   return PR_FALSE;
00268 }
00269 
00270 PRBool
00271 nsNativeKeyBindings::KeyPress(const nsNativeKeyEvent& aEvent,
00272                               DoCommandCallback aCallback, void *aCallbackData)
00273 {
00274   PRUint32 keyCode;
00275 
00276   if (aEvent.charCode != 0)
00277     keyCode = gdk_unicode_to_keyval(aEvent.charCode);
00278   else
00279     keyCode = DOMKeyCodeToGdkKeyCode(aEvent.keyCode);
00280 
00281   int modifiers = 0;
00282   if (aEvent.altKey)
00283     modifiers |= GDK_MOD1_MASK;
00284   if (aEvent.ctrlKey)
00285     modifiers |= GDK_CONTROL_MASK;
00286   if (aEvent.shiftKey)
00287     modifiers |= GDK_SHIFT_MASK;
00288   // we don't support meta
00289 
00290   gCurrentCallback = aCallback;
00291   gCurrentCallbackData = aCallbackData;
00292 
00293   gHandled = PR_FALSE;
00294 
00295   gtk_bindings_activate(GTK_OBJECT(mNativeTarget),
00296                         keyCode, GdkModifierType(modifiers));
00297 
00298   gCurrentCallback = nsnull;
00299   gCurrentCallbackData = nsnull;
00300 
00301   return gHandled;
00302 }
00303 
00304 PRBool
00305 nsNativeKeyBindings::KeyUp(const nsNativeKeyEvent& aEvent,
00306                            DoCommandCallback aCallback, void *aCallbackData)
00307 {
00308   return PR_FALSE;
00309 }