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"
13 #include "nsGtkKeyUtils.h"
16 #include <gdk/gdkkeysyms.h>
22 static nsIWidget::DoCommandCallback gCurrentCallback
;
23 static void *gCurrentCallbackData
;
26 // Common GtkEntry and GtkTextView signals
28 copy_clipboard_cb(GtkWidget
*w
, gpointer user_data
)
30 gCurrentCallback(CommandCopy
, gCurrentCallbackData
);
31 g_signal_stop_emission_by_name(w
, "copy_clipboard");
36 cut_clipboard_cb(GtkWidget
*w
, gpointer user_data
)
38 gCurrentCallback(CommandCut
, gCurrentCallbackData
);
39 g_signal_stop_emission_by_name(w
, "cut_clipboard");
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] = {
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
64 delete_from_cursor_cb(GtkWidget
*w
, GtkDeleteType del_type
,
65 gint count
, gpointer user_data
)
67 g_signal_stop_emission_by_name(w
, "delete_from_cursor");
70 bool forward
= count
> 0;
71 if (uint32_t(del_type
) >= ArrayLength(sDeleteCommands
)) {
72 // unsupported deletion type
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.
80 gCurrentCallback(CommandWordNext
, gCurrentCallbackData
);
81 gCurrentCallback(CommandWordPrevious
, gCurrentCallbackData
);
83 gCurrentCallback(CommandWordPrevious
, gCurrentCallbackData
);
84 gCurrentCallback(CommandWordNext
, gCurrentCallbackData
);
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.
92 gCurrentCallback(CommandBeginLine
, gCurrentCallbackData
);
94 gCurrentCallback(CommandEndLine
, gCurrentCallbackData
);
98 Command command
= sDeleteCommands
[del_type
][forward
];
100 return; // unsupported command
103 unsigned int absCount
= Abs(count
);
104 for (unsigned int i
= 0; i
< absCount
; ++i
) {
105 gCurrentCallback(command
, gCurrentCallbackData
);
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
}
118 { // VISUAL_POSITIONS
119 { CommandCharPrevious
, CommandCharNext
},
120 { CommandSelectCharPrevious
, CommandSelectCharNext
}
123 { CommandWordPrevious
, CommandWordNext
},
124 { CommandSelectWordPrevious
, CommandSelectWordNext
}
127 { CommandLinePrevious
, CommandLineNext
},
128 { CommandSelectLinePrevious
, CommandSelectLineNext
}
130 { // DISPLAY_LINE_ENDS
131 { CommandBeginLine
, CommandEndLine
},
132 { CommandSelectBeginLine
, CommandSelectEndLine
}
135 { CommandLinePrevious
, CommandLineNext
},
136 { CommandSelectLinePrevious
, CommandSelectLineNext
}
139 { CommandBeginLine
, CommandEndLine
},
140 { CommandSelectBeginLine
, CommandSelectEndLine
}
143 { CommandMovePageUp
, CommandMovePageDown
},
144 { CommandSelectPageUp
, CommandSelectPageDown
}
147 { CommandMoveTop
, CommandMoveBottom
},
148 { CommandSelectTop
, CommandSelectBottom
}
150 { // HORIZONTAL_PAGES (unsupported)
151 { CommandDoNothing
, CommandDoNothing
},
152 { CommandDoNothing
, CommandDoNothing
}
157 move_cursor_cb(GtkWidget
*w
, GtkMovementStep step
, gint count
,
158 gboolean extend_selection
, gpointer user_data
)
160 g_signal_stop_emission_by_name(w
, "move_cursor");
162 bool forward
= count
> 0;
163 if (uint32_t(step
) >= ArrayLength(sMoveCommands
)) {
164 // unsupported movement type
168 Command command
= sMoveCommands
[step
][extend_selection
][forward
];
170 return; // unsupported command
173 unsigned int absCount
= Abs(count
);
174 for (unsigned int i
= 0; i
< absCount
; ++i
) {
175 gCurrentCallback(command
, gCurrentCallbackData
);
180 paste_clipboard_cb(GtkWidget
*w
, gpointer user_data
)
182 gCurrentCallback(CommandPaste
, gCurrentCallbackData
);
183 g_signal_stop_emission_by_name(w
, "paste_clipboard");
187 // GtkTextView-only signals
189 select_all_cb(GtkWidget
*w
, gboolean select
, gpointer user_data
)
191 gCurrentCallback(CommandSelectAll
, gCurrentCallbackData
);
192 g_signal_stop_emission_by_name(w
, "select_all");
196 NativeKeyBindings
* NativeKeyBindings::sInstanceForSingleLineEditor
= nullptr;
197 NativeKeyBindings
* NativeKeyBindings::sInstanceForMultiLineEditor
= nullptr;
201 NativeKeyBindings::GetInstance(NativeKeyBindingsType aType
)
204 case nsIWidget::NativeKeyBindingsForSingleLineEditor
:
205 if (!sInstanceForSingleLineEditor
) {
206 sInstanceForSingleLineEditor
= new NativeKeyBindings();
207 sInstanceForSingleLineEditor
->Init(aType
);
209 return sInstanceForSingleLineEditor
;
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
);
220 return sInstanceForMultiLineEditor
;
226 NativeKeyBindings::Shutdown()
228 delete sInstanceForSingleLineEditor
;
229 sInstanceForSingleLineEditor
= nullptr;
230 delete sInstanceForMultiLineEditor
;
231 sInstanceForMultiLineEditor
= nullptr;
235 NativeKeyBindings::Init(NativeKeyBindingsType aType
)
238 case nsIWidget::NativeKeyBindingsForSingleLineEditor
:
239 mNativeTarget
= gtk_entry_new();
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);
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);
269 NativeKeyBindings::~NativeKeyBindings()
271 gtk_widget_destroy(mNativeTarget
);
272 g_object_unref(mNativeTarget
);
276 NativeKeyBindings::Execute(const WidgetKeyboardEvent
& aEvent
,
277 DoCommandCallback aCallback
,
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
283 if (!aEvent
.mNativeKeyEvent
) {
284 // It must be synthesized event or dispatched DOM event from chrome.
290 if (aEvent
.charCode
) {
291 keyval
= gdk_unicode_to_keyval(aEvent
.charCode
);
294 static_cast<GdkEventKey
*>(aEvent
.mNativeKeyEvent
)->keyval
;
297 if (ExecuteInternal(aEvent
, aCallback
, aCallbackData
, keyval
)) {
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
)) {
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
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));
331 NativeKeyBindings::ExecuteInternal(const WidgetKeyboardEvent
& aEvent
,
332 DoCommandCallback aCallback
,
337 static_cast<GdkEventKey
*>(aEvent
.mNativeKeyEvent
)->state
;
339 gCurrentCallback
= aCallback
;
340 gCurrentCallbackData
= aCallbackData
;
343 #if (MOZ_WIDGET_GTK == 2)
344 gtk_bindings_activate(GTK_OBJECT(mNativeTarget
),
345 aKeyval
, GdkModifierType(modifiers
));
347 gtk_bindings_activate(G_OBJECT(mNativeTarget
),
348 aKeyval
, GdkModifierType(modifiers
));
351 gCurrentCallback
= nullptr;
352 gCurrentCallbackData
= nullptr;
357 } // namespace widget
358 } // namespace mozilla