1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * IBM Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 2004
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Brian Ryner <bryner@brianryner.com>
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either the GNU General Public License Version 2 or later (the "GPL"), or
27 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "mozilla/Util.h"
40 :
41 : #include "nsNativeKeyBindings.h"
42 : #include "nsString.h"
43 : #include "nsMemory.h"
44 : #include "nsGtkKeyUtils.h"
45 : #include "nsGUIEvent.h"
46 :
47 : #include <gtk/gtk.h>
48 : #include <gdk/gdkkeysyms.h>
49 : #include <gdk/gdk.h>
50 :
51 : using namespace mozilla;
52 : using namespace mozilla::widget;
53 :
54 : static nsINativeKeyBindings::DoCommandCallback gCurrentCallback;
55 : static void *gCurrentCallbackData;
56 : static bool gHandled;
57 :
58 : // Common GtkEntry and GtkTextView signals
59 : static void
60 0 : copy_clipboard_cb(GtkWidget *w, gpointer user_data)
61 : {
62 0 : gCurrentCallback("cmd_copy", gCurrentCallbackData);
63 0 : g_signal_stop_emission_by_name(w, "copy_clipboard");
64 0 : gHandled = true;
65 0 : }
66 :
67 : static void
68 0 : cut_clipboard_cb(GtkWidget *w, gpointer user_data)
69 : {
70 0 : gCurrentCallback("cmd_cut", gCurrentCallbackData);
71 0 : g_signal_stop_emission_by_name(w, "cut_clipboard");
72 0 : gHandled = true;
73 0 : }
74 :
75 : // GTK distinguishes between display lines (wrapped, as they appear on the
76 : // screen) and paragraphs, which are runs of text terminated by a newline.
77 : // We don't have this distinction, so we always use editor's notion of
78 : // lines, which are newline-terminated.
79 :
80 : static const char *const sDeleteCommands[][2] = {
81 : // backward, forward
82 : { "cmd_deleteCharBackward", "cmd_deleteCharForward" }, // CHARS
83 : { "cmd_deleteWordBackward", "cmd_deleteWordForward" }, // WORD_ENDS
84 : { "cmd_deleteWordBackward", "cmd_deleteWordForward" }, // WORDS
85 : { "cmd_deleteToBeginningOfLine", "cmd_deleteToEndOfLine" }, // LINES
86 : { "cmd_deleteToBeginningOfLine", "cmd_deleteToEndOfLine" }, // LINE_ENDS
87 : { "cmd_deleteToBeginningOfLine", "cmd_deleteToEndOfLine" }, // PARAGRAPH_ENDS
88 : { "cmd_deleteToBeginningOfLine", "cmd_deleteToEndOfLine" }, // PARAGRAPHS
89 : // This deletes from the end of the previous word to the beginning of the
90 : // next word, but only if the caret is not in a word.
91 : // XXX need to implement in editor
92 : { nsnull, nsnull } // WHITESPACE
93 : };
94 :
95 : static void
96 0 : delete_from_cursor_cb(GtkWidget *w, GtkDeleteType del_type,
97 : gint count, gpointer user_data)
98 : {
99 0 : g_signal_stop_emission_by_name(w, "delete_from_cursor");
100 0 : gHandled = true;
101 :
102 0 : bool forward = count > 0;
103 0 : if (PRUint32(del_type) >= ArrayLength(sDeleteCommands)) {
104 : // unsupported deletion type
105 0 : return;
106 : }
107 :
108 0 : if (del_type == GTK_DELETE_WORDS) {
109 : // This works like word_ends, except we first move the caret to the
110 : // beginning/end of the current word.
111 0 : if (forward) {
112 0 : gCurrentCallback("cmd_wordNext", gCurrentCallbackData);
113 0 : gCurrentCallback("cmd_wordPrevious", gCurrentCallbackData);
114 : } else {
115 0 : gCurrentCallback("cmd_wordPrevious", gCurrentCallbackData);
116 0 : gCurrentCallback("cmd_wordNext", gCurrentCallbackData);
117 : }
118 0 : } else if (del_type == GTK_DELETE_DISPLAY_LINES ||
119 : del_type == GTK_DELETE_PARAGRAPHS) {
120 :
121 : // This works like display_line_ends, except we first move the caret to the
122 : // beginning/end of the current line.
123 0 : if (forward) {
124 0 : gCurrentCallback("cmd_beginLine", gCurrentCallbackData);
125 : } else {
126 0 : gCurrentCallback("cmd_endLine", gCurrentCallbackData);
127 : }
128 : }
129 :
130 0 : const char *cmd = sDeleteCommands[del_type][forward];
131 0 : if (!cmd)
132 0 : return; // unsupported command
133 :
134 0 : count = NS_ABS(count);
135 0 : for (int i = 0; i < count; ++i) {
136 0 : gCurrentCallback(cmd, gCurrentCallbackData);
137 : }
138 : }
139 :
140 : static const char *const sMoveCommands[][2][2] = {
141 : // non-extend { backward, forward }, extend { backward, forward }
142 : // GTK differentiates between logical position, which is prev/next,
143 : // and visual position, which is always left/right.
144 : // We should fix this to work the same way for RTL text input.
145 : { // LOGICAL_POSITIONS
146 : { "cmd_charPrevious", "cmd_charNext" },
147 : { "cmd_selectCharPrevious", "cmd_selectCharNext" }
148 : },
149 : { // VISUAL_POSITIONS
150 : { "cmd_charPrevious", "cmd_charNext" },
151 : { "cmd_selectCharPrevious", "cmd_selectCharNext" }
152 : },
153 : { // WORDS
154 : { "cmd_wordPrevious", "cmd_wordNext" },
155 : { "cmd_selectWordPrevious", "cmd_selectWordNext" }
156 : },
157 : { // DISPLAY_LINES
158 : { "cmd_linePrevious", "cmd_lineNext" },
159 : { "cmd_selectLinePrevious", "cmd_selectLineNext" }
160 : },
161 : { // DISPLAY_LINE_ENDS
162 : { "cmd_beginLine", "cmd_endLine" },
163 : { "cmd_selectBeginLine", "cmd_selectEndLine" }
164 : },
165 : { // PARAGRAPHS
166 : { "cmd_linePrevious", "cmd_lineNext" },
167 : { "cmd_selectLinePrevious", "cmd_selectLineNext" }
168 : },
169 : { // PARAGRAPH_ENDS
170 : { "cmd_beginLine", "cmd_endLine" },
171 : { "cmd_selectBeginLine", "cmd_selectEndLine" }
172 : },
173 : { // PAGES
174 : { "cmd_movePageUp", "cmd_movePageDown" },
175 : { "cmd_selectPageUp", "cmd_selectPageDown" }
176 : },
177 : { // BUFFER_ENDS
178 : { "cmd_moveTop", "cmd_moveBottom" },
179 : { "cmd_selectTop", "cmd_selectBottom" }
180 : },
181 : { // HORIZONTAL_PAGES (unsupported)
182 : { nsnull, nsnull },
183 : { nsnull, nsnull }
184 : }
185 : };
186 :
187 : static void
188 0 : move_cursor_cb(GtkWidget *w, GtkMovementStep step, gint count,
189 : gboolean extend_selection, gpointer user_data)
190 : {
191 0 : g_signal_stop_emission_by_name(w, "move_cursor");
192 0 : gHandled = true;
193 0 : bool forward = count > 0;
194 0 : if (PRUint32(step) >= ArrayLength(sMoveCommands)) {
195 : // unsupported movement type
196 0 : return;
197 : }
198 :
199 0 : const char *cmd = sMoveCommands[step][extend_selection][forward];
200 0 : if (!cmd)
201 0 : return; // unsupported command
202 :
203 :
204 0 : count = NS_ABS(count);
205 0 : for (int i = 0; i < count; ++i) {
206 0 : gCurrentCallback(cmd, gCurrentCallbackData);
207 : }
208 : }
209 :
210 : static void
211 0 : paste_clipboard_cb(GtkWidget *w, gpointer user_data)
212 : {
213 0 : gCurrentCallback("cmd_paste", gCurrentCallbackData);
214 0 : g_signal_stop_emission_by_name(w, "paste_clipboard");
215 0 : gHandled = true;
216 0 : }
217 :
218 : // GtkTextView-only signals
219 : static void
220 0 : select_all_cb(GtkWidget *w, gboolean select, gpointer user_data)
221 : {
222 0 : gCurrentCallback("cmd_selectAll", gCurrentCallbackData);
223 0 : g_signal_stop_emission_by_name(w, "select_all");
224 0 : gHandled = true;
225 0 : }
226 :
227 : void
228 0 : nsNativeKeyBindings::Init(NativeKeyBindingsType aType)
229 : {
230 0 : switch (aType) {
231 : case eKeyBindings_Input:
232 0 : mNativeTarget = gtk_entry_new();
233 0 : break;
234 : case eKeyBindings_TextArea:
235 0 : mNativeTarget = gtk_text_view_new();
236 0 : if (gtk_major_version > 2 ||
237 : (gtk_major_version == 2 && (gtk_minor_version > 2 ||
238 : (gtk_minor_version == 2 &&
239 : gtk_micro_version >= 2)))) {
240 : // select_all only exists in gtk >= 2.2.2. Prior to that,
241 : // ctrl+a is bound to (move to beginning, select to end).
242 0 : g_signal_connect(mNativeTarget, "select_all",
243 0 : G_CALLBACK(select_all_cb), this);
244 : }
245 0 : break;
246 : }
247 :
248 0 : g_object_ref_sink(mNativeTarget);
249 :
250 0 : g_signal_connect(mNativeTarget, "copy_clipboard",
251 0 : G_CALLBACK(copy_clipboard_cb), this);
252 0 : g_signal_connect(mNativeTarget, "cut_clipboard",
253 0 : G_CALLBACK(cut_clipboard_cb), this);
254 0 : g_signal_connect(mNativeTarget, "delete_from_cursor",
255 0 : G_CALLBACK(delete_from_cursor_cb), this);
256 0 : g_signal_connect(mNativeTarget, "move_cursor",
257 0 : G_CALLBACK(move_cursor_cb), this);
258 0 : g_signal_connect(mNativeTarget, "paste_clipboard",
259 0 : G_CALLBACK(paste_clipboard_cb), this);
260 0 : }
261 :
262 0 : nsNativeKeyBindings::~nsNativeKeyBindings()
263 : {
264 0 : gtk_widget_destroy(mNativeTarget);
265 0 : g_object_unref(mNativeTarget);
266 0 : }
267 :
268 0 : NS_IMPL_ISUPPORTS1(nsNativeKeyBindings, nsINativeKeyBindings)
269 :
270 : bool
271 0 : nsNativeKeyBindings::KeyDown(const nsNativeKeyEvent& aEvent,
272 : DoCommandCallback aCallback, void *aCallbackData)
273 : {
274 0 : return false;
275 : }
276 :
277 : bool
278 0 : nsNativeKeyBindings::KeyPress(const nsNativeKeyEvent& aEvent,
279 : DoCommandCallback aCallback, void *aCallbackData)
280 : {
281 : PRUint32 keyCode;
282 :
283 0 : if (aEvent.charCode != 0)
284 0 : keyCode = gdk_unicode_to_keyval(aEvent.charCode);
285 : else
286 0 : keyCode = KeymapWrapper::GuessGDKKeyval(aEvent.keyCode);
287 :
288 0 : if (KeyPressInternal(aEvent, aCallback, aCallbackData, keyCode))
289 0 : return true;
290 :
291 0 : nsKeyEvent *nativeKeyEvent = static_cast<nsKeyEvent*>(aEvent.nativeEvent);
292 0 : if (!nativeKeyEvent ||
293 : (nativeKeyEvent->eventStructType != NS_KEY_EVENT &&
294 : nativeKeyEvent->message != NS_KEY_PRESS)) {
295 0 : return false;
296 : }
297 :
298 0 : for (PRUint32 i = 0; i < nativeKeyEvent->alternativeCharCodes.Length(); ++i) {
299 : PRUint32 ch = nativeKeyEvent->isShift ?
300 0 : nativeKeyEvent->alternativeCharCodes[i].mShiftedCharCode :
301 0 : nativeKeyEvent->alternativeCharCodes[i].mUnshiftedCharCode;
302 0 : if (ch && ch != aEvent.charCode) {
303 0 : keyCode = gdk_unicode_to_keyval(ch);
304 0 : if (KeyPressInternal(aEvent, aCallback, aCallbackData, keyCode))
305 0 : return true;
306 : }
307 : }
308 :
309 : /* gtk_bindings_activate_event is preferable, but it has unresolved bug: http://bugzilla.gnome.org/show_bug.cgi?id=162726
310 : Also gtk_bindings_activate may work with some non-shortcuts operations (todo: check it)
311 : See bugs 411005 406407
312 :
313 : Code, which should be used after fixing http://bugzilla.gnome.org/show_bug.cgi?id=162726:
314 : const nsGUIEvent *guiEvent = static_cast<nsGUIEvent*>(aEvent.nativeEvent);
315 : if (guiEvent &&
316 : (guiEvent->message == NS_KEY_PRESS || guiEvent->message == NS_KEY_UP || guiEvent->message == NS_KEY_DOWN) &&
317 : guiEvent->pluginEvent)
318 : gtk_bindings_activate_event(GTK_OBJECT(mNativeTarget),
319 : static_cast<GdkEventKey*>(guiEvent->pluginEvent));
320 : */
321 :
322 0 : return false;
323 : }
324 :
325 : bool
326 0 : nsNativeKeyBindings::KeyPressInternal(const nsNativeKeyEvent& aEvent,
327 : DoCommandCallback aCallback,
328 : void *aCallbackData,
329 : PRUint32 aKeyCode)
330 : {
331 0 : int modifiers = 0;
332 0 : if (aEvent.altKey)
333 0 : modifiers |= GDK_MOD1_MASK;
334 0 : if (aEvent.ctrlKey)
335 0 : modifiers |= GDK_CONTROL_MASK;
336 0 : if (aEvent.shiftKey)
337 0 : modifiers |= GDK_SHIFT_MASK;
338 : // we don't support meta
339 :
340 0 : gCurrentCallback = aCallback;
341 0 : gCurrentCallbackData = aCallbackData;
342 :
343 0 : gHandled = false;
344 :
345 0 : gtk_bindings_activate(GTK_OBJECT(mNativeTarget),
346 0 : aKeyCode, GdkModifierType(modifiers));
347 :
348 0 : gCurrentCallback = nsnull;
349 0 : gCurrentCallbackData = nsnull;
350 :
351 0 : return gHandled;
352 : }
353 :
354 : bool
355 0 : nsNativeKeyBindings::KeyUp(const nsNativeKeyEvent& aEvent,
356 : DoCommandCallback aCallback, void *aCallbackData)
357 : {
358 0 : return false;
359 : }
|