1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* vim:expandtab:shiftwidth=4:tabstop=4:
3 : */
4 : /* ***** BEGIN LICENSE BLOCK *****
5 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 : *
7 : * The contents of this file are subject to the Mozilla Public License Version
8 : * 1.1 (the "License"); you may not use this file except in compliance with
9 : * the License. You may obtain a copy of the License at
10 : * http://www.mozilla.org/MPL/
11 : *
12 : * Software distributed under the License is distributed on an "AS IS" basis,
13 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 : * for the specific language governing rights and limitations under the
15 : * License.
16 : *
17 : * The Original Code is mozilla.org code.
18 : *
19 : * The Initial Developer of the Original Code is Christopher Blizzard
20 : * <blizzard@mozilla.org>. Portions created by the Initial Developer
21 : * are Copyright (C) 2001 the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Mats Palmgren <mats.palmgren@bredband.net>
25 : * Masayuki Nakano <masayuki@d-toybox.com>
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either the GNU General Public License Version 2 or later (the "GPL"), or
29 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : #ifdef MOZ_PLATFORM_MAEMO
42 : #define MAEMO_CHANGES
43 : #endif
44 :
45 : #ifdef MOZ_LOGGING
46 : #define FORCE_PR_LOG /* Allow logging in the release build */
47 : #endif // MOZ_LOGGING
48 : #include "prlog.h"
49 :
50 : #include "nsGtkIMModule.h"
51 : #include "nsWindow.h"
52 : #include "mozilla/Preferences.h"
53 :
54 : #ifdef MOZ_PLATFORM_MAEMO
55 : #include "nsServiceManagerUtils.h"
56 : #include "nsIObserverService.h"
57 : #include "mozilla/Services.h"
58 : #endif
59 :
60 : using namespace mozilla;
61 : using namespace mozilla::widget;
62 :
63 : #ifdef PR_LOGGING
64 : PRLogModuleInfo* gGtkIMLog = nsnull;
65 :
66 : static const char*
67 0 : GetRangeTypeName(PRUint32 aRangeType)
68 : {
69 0 : switch (aRangeType) {
70 : case NS_TEXTRANGE_RAWINPUT:
71 0 : return "NS_TEXTRANGE_RAWINPUT";
72 : case NS_TEXTRANGE_CONVERTEDTEXT:
73 0 : return "NS_TEXTRANGE_CONVERTEDTEXT";
74 : case NS_TEXTRANGE_SELECTEDRAWTEXT:
75 0 : return "NS_TEXTRANGE_SELECTEDRAWTEXT";
76 : case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT:
77 0 : return "NS_TEXTRANGE_SELECTEDCONVERTEDTEXT";
78 : case NS_TEXTRANGE_CARETPOSITION:
79 0 : return "NS_TEXTRANGE_CARETPOSITION";
80 : default:
81 0 : return "UNKNOWN SELECTION TYPE!!";
82 : }
83 : }
84 :
85 : static const char*
86 0 : GetEnabledStateName(PRUint32 aState)
87 : {
88 0 : switch (aState) {
89 : case IMEState::DISABLED:
90 0 : return "DISABLED";
91 : case IMEState::ENABLED:
92 0 : return "ENABLED";
93 : case IMEState::PASSWORD:
94 0 : return "PASSWORD";
95 : case IMEState::PLUGIN:
96 0 : return "PLUG_IN";
97 : default:
98 0 : return "UNKNOWN ENABLED STATUS!!";
99 : }
100 : }
101 : #endif
102 :
103 : nsGtkIMModule* nsGtkIMModule::sLastFocusedModule = nsnull;
104 :
105 : #ifdef MOZ_PLATFORM_MAEMO
106 : static bool gIsVirtualKeyboardOpened = false;
107 : #endif
108 :
109 0 : nsGtkIMModule::nsGtkIMModule(nsWindow* aOwnerWindow) :
110 : mOwnerWindow(aOwnerWindow), mLastFocusedWindow(nsnull),
111 : mContext(nsnull),
112 : #ifndef NS_IME_ENABLED_ON_PASSWORD_FIELD
113 : mSimpleContext(nsnull),
114 : #endif
115 : mDummyContext(nsnull),
116 : mCompositionStart(PR_UINT32_MAX), mProcessingKeyEvent(nsnull),
117 : mCompositionState(eCompositionState_NotComposing),
118 0 : mIsIMFocused(false), mIgnoreNativeCompositionEvent(false)
119 : {
120 : #ifdef PR_LOGGING
121 0 : if (!gGtkIMLog) {
122 0 : gGtkIMLog = PR_NewLogModule("nsGtkIMModuleWidgets");
123 : }
124 : #endif
125 0 : Init();
126 0 : }
127 :
128 : void
129 0 : nsGtkIMModule::Init()
130 : {
131 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
132 : ("GtkIMModule(%p): Init, mOwnerWindow=%p",
133 : this, mOwnerWindow));
134 :
135 0 : MozContainer* container = mOwnerWindow->GetMozContainer();
136 0 : NS_PRECONDITION(container, "container is null");
137 0 : GdkWindow* gdkWindow = GTK_WIDGET(container)->window;
138 :
139 : // NOTE: gtk_im_*_new() abort (kill) the whole process when it fails.
140 : // So, we don't need to check the result.
141 :
142 : // Normal context.
143 0 : mContext = gtk_im_multicontext_new();
144 0 : gtk_im_context_set_client_window(mContext, gdkWindow);
145 0 : g_signal_connect(mContext, "preedit_changed",
146 : G_CALLBACK(nsGtkIMModule::OnChangeCompositionCallback),
147 0 : this);
148 0 : g_signal_connect(mContext, "retrieve_surrounding",
149 : G_CALLBACK(nsGtkIMModule::OnRetrieveSurroundingCallback),
150 0 : this);
151 0 : g_signal_connect(mContext, "delete_surrounding",
152 : G_CALLBACK(nsGtkIMModule::OnDeleteSurroundingCallback),
153 0 : this);
154 0 : g_signal_connect(mContext, "commit",
155 : G_CALLBACK(nsGtkIMModule::OnCommitCompositionCallback),
156 0 : this);
157 0 : g_signal_connect(mContext, "preedit_start",
158 : G_CALLBACK(nsGtkIMModule::OnStartCompositionCallback),
159 0 : this);
160 0 : g_signal_connect(mContext, "preedit_end",
161 : G_CALLBACK(nsGtkIMModule::OnEndCompositionCallback),
162 0 : this);
163 :
164 : #ifndef NS_IME_ENABLED_ON_PASSWORD_FIELD
165 : // Simple context
166 0 : mSimpleContext = gtk_im_context_simple_new();
167 0 : gtk_im_context_set_client_window(mSimpleContext, gdkWindow);
168 0 : g_signal_connect(mSimpleContext, "preedit_changed",
169 : G_CALLBACK(&nsGtkIMModule::OnChangeCompositionCallback),
170 0 : this);
171 0 : g_signal_connect(mSimpleContext, "retrieve_surrounding",
172 : G_CALLBACK(&nsGtkIMModule::OnRetrieveSurroundingCallback),
173 0 : this);
174 0 : g_signal_connect(mSimpleContext, "delete_surrounding",
175 : G_CALLBACK(&nsGtkIMModule::OnDeleteSurroundingCallback),
176 0 : this);
177 0 : g_signal_connect(mSimpleContext, "commit",
178 : G_CALLBACK(&nsGtkIMModule::OnCommitCompositionCallback),
179 0 : this);
180 0 : g_signal_connect(mSimpleContext, "preedit_start",
181 : G_CALLBACK(nsGtkIMModule::OnStartCompositionCallback),
182 0 : this);
183 0 : g_signal_connect(mSimpleContext, "preedit_end",
184 : G_CALLBACK(nsGtkIMModule::OnEndCompositionCallback),
185 0 : this);
186 : #endif // NS_IME_ENABLED_ON_PASSWORD_FIELD
187 :
188 : // Dummy context
189 0 : mDummyContext = gtk_im_multicontext_new();
190 0 : gtk_im_context_set_client_window(mDummyContext, gdkWindow);
191 0 : }
192 :
193 0 : nsGtkIMModule::~nsGtkIMModule()
194 : {
195 0 : if (this == sLastFocusedModule) {
196 0 : sLastFocusedModule = nsnull;
197 : }
198 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
199 : ("GtkIMModule(%p) was gone", this));
200 0 : }
201 :
202 : void
203 0 : nsGtkIMModule::OnDestroyWindow(nsWindow* aWindow)
204 : {
205 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
206 : ("GtkIMModule(%p): OnDestroyWindow, aWindow=%p, mLastFocusedWindow=%p, mOwnerWindow=%p, mLastFocusedModule=%p",
207 : this, aWindow, mLastFocusedWindow, mOwnerWindow, sLastFocusedModule));
208 :
209 0 : NS_PRECONDITION(aWindow, "aWindow must not be null");
210 :
211 0 : if (mLastFocusedWindow == aWindow) {
212 0 : CancelIMEComposition(aWindow);
213 0 : if (mIsIMFocused) {
214 0 : Blur();
215 : }
216 0 : mLastFocusedWindow = nsnull;
217 : }
218 :
219 0 : if (mOwnerWindow != aWindow) {
220 0 : return;
221 : }
222 :
223 0 : if (sLastFocusedModule == this) {
224 0 : sLastFocusedModule = nsnull;
225 : }
226 :
227 : /**
228 : * NOTE:
229 : * The given window is the owner of this, so, we must release the
230 : * contexts now. But that might be referred from other nsWindows
231 : * (they are children of this. But we don't know why there are the
232 : * cases). So, we need to clear the pointers that refers to contexts
233 : * and this if the other referrers are still alive. See bug 349727.
234 : */
235 0 : if (mContext) {
236 0 : PrepareToDestroyContext(mContext);
237 0 : gtk_im_context_set_client_window(mContext, nsnull);
238 0 : g_object_unref(mContext);
239 0 : mContext = nsnull;
240 : }
241 :
242 : #ifndef NS_IME_ENABLED_ON_PASSWORD_FIELD
243 0 : if (mSimpleContext) {
244 0 : gtk_im_context_set_client_window(mSimpleContext, nsnull);
245 0 : g_object_unref(mSimpleContext);
246 0 : mSimpleContext = nsnull;
247 : }
248 : #endif // NS_IME_ENABLED_ON_PASSWORD_FIELD
249 :
250 0 : if (mDummyContext) {
251 : // mContext and mDummyContext have the same slaveType and signal_data
252 : // so no need for another workaround_gtk_im_display_closed.
253 0 : gtk_im_context_set_client_window(mDummyContext, nsnull);
254 0 : g_object_unref(mDummyContext);
255 0 : mDummyContext = nsnull;
256 : }
257 :
258 0 : mOwnerWindow = nsnull;
259 0 : mLastFocusedWindow = nsnull;
260 0 : mInputContext.mIMEState.mEnabled = IMEState::DISABLED;
261 :
262 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
263 : (" SUCCEEDED, Completely destroyed"));
264 : }
265 :
266 : // Work around gtk bug http://bugzilla.gnome.org/show_bug.cgi?id=483223:
267 : // (and the similar issue of GTK+ IIIM)
268 : // The GTK+ XIM and IIIM modules register handlers for the "closed" signal
269 : // on the display, but:
270 : // * The signal handlers are not disconnected when the module is unloaded.
271 : //
272 : // The GTK+ XIM module has another problem:
273 : // * When the signal handler is run (with the module loaded) it tries
274 : // XFree (and fails) on a pointer that did not come from Xmalloc.
275 : //
276 : // To prevent these modules from being unloaded, use static variables to
277 : // hold ref of GtkIMContext class.
278 : // For GTK+ XIM module, to prevent the signal handler from being run,
279 : // find the signal handlers and remove them.
280 : //
281 : // GtkIMContextXIMs share XOpenIM connections and display closed signal
282 : // handlers (where possible).
283 :
284 : void
285 0 : nsGtkIMModule::PrepareToDestroyContext(GtkIMContext *aContext)
286 : {
287 0 : MozContainer* container = mOwnerWindow->GetMozContainer();
288 0 : NS_PRECONDITION(container, "The container of the window is null");
289 :
290 0 : GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT(aContext);
291 0 : GtkIMContext *slave = multicontext->slave;
292 0 : if (!slave) {
293 0 : return;
294 : }
295 :
296 0 : GType slaveType = G_TYPE_FROM_INSTANCE(slave);
297 0 : const gchar *im_type_name = g_type_name(slaveType);
298 0 : if (strcmp(im_type_name, "GtkIMContextXIM") == 0) {
299 0 : if (gtk_check_version(2, 12, 1) == NULL) {
300 0 : return; // gtk bug has been fixed
301 : }
302 :
303 : struct GtkIMContextXIM
304 : {
305 : GtkIMContext parent;
306 : gpointer private_data;
307 : // ... other fields
308 : };
309 :
310 : gpointer signal_data =
311 0 : reinterpret_cast<GtkIMContextXIM*>(slave)->private_data;
312 0 : if (!signal_data) {
313 0 : return;
314 : }
315 :
316 : g_signal_handlers_disconnect_matched(
317 0 : gtk_widget_get_display(GTK_WIDGET(container)),
318 0 : G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, signal_data);
319 :
320 : // Add a reference to prevent the XIM module from being unloaded
321 : // and reloaded: each time the module is loaded and used, it
322 : // opens (and doesn't close) new XOpenIM connections.
323 : static gpointer gtk_xim_context_class =
324 0 : g_type_class_ref(slaveType);
325 : // Mute unused variable warning:
326 0 : gtk_xim_context_class = gtk_xim_context_class;
327 0 : } else if (strcmp(im_type_name, "GtkIMContextIIIM") == 0) {
328 : // Add a reference to prevent the IIIM module from being unloaded
329 : static gpointer gtk_iiim_context_class =
330 0 : g_type_class_ref(slaveType);
331 : // Mute unused variable warning:
332 0 : gtk_iiim_context_class = gtk_iiim_context_class;
333 : }
334 : }
335 :
336 : void
337 0 : nsGtkIMModule::OnFocusWindow(nsWindow* aWindow)
338 : {
339 0 : if (NS_UNLIKELY(IsDestroyed())) {
340 0 : return;
341 : }
342 :
343 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
344 : ("GtkIMModule(%p): OnFocusWindow, aWindow=%p, mLastFocusedWindow=%p",
345 : this, aWindow, mLastFocusedWindow));
346 0 : mLastFocusedWindow = aWindow;
347 0 : Focus();
348 : }
349 :
350 : void
351 0 : nsGtkIMModule::OnBlurWindow(nsWindow* aWindow)
352 : {
353 0 : if (NS_UNLIKELY(IsDestroyed())) {
354 0 : return;
355 : }
356 :
357 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
358 : ("GtkIMModule(%p): OnBlurWindow, aWindow=%p, mLastFocusedWindow=%p, mIsIMFocused=%s",
359 : this, aWindow, mLastFocusedWindow, mIsIMFocused ? "YES" : "NO"));
360 :
361 0 : if (!mIsIMFocused || mLastFocusedWindow != aWindow) {
362 0 : return;
363 : }
364 :
365 0 : Blur();
366 : }
367 :
368 : bool
369 0 : nsGtkIMModule::OnKeyEvent(nsWindow* aCaller, GdkEventKey* aEvent,
370 : bool aKeyDownEventWasSent /* = false */)
371 : {
372 0 : NS_PRECONDITION(aEvent, "aEvent must be non-null");
373 :
374 0 : if (!IsEditable() || NS_UNLIKELY(IsDestroyed())) {
375 0 : return false;
376 : }
377 :
378 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
379 : ("GtkIMModule(%p): OnKeyEvent, aCaller=%p, aKeyDownEventWasSent=%s",
380 : this, aCaller, aKeyDownEventWasSent ? "TRUE" : "FALSE"));
381 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
382 : (" aEvent: type=%s, keyval=%s, unicode=0x%X",
383 : aEvent->type == GDK_KEY_PRESS ? "GDK_KEY_PRESS" :
384 : aEvent->type == GDK_KEY_RELEASE ? "GDK_KEY_RELEASE" : "Unknown",
385 : gdk_keyval_name(aEvent->keyval),
386 : gdk_keyval_to_unicode(aEvent->keyval)));
387 :
388 0 : if (aCaller != mLastFocusedWindow) {
389 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
390 : (" FAILED, the caller isn't focused window, mLastFocusedWindow=%p",
391 : mLastFocusedWindow));
392 0 : return false;
393 : }
394 :
395 0 : GtkIMContext* im = GetContext();
396 0 : if (NS_UNLIKELY(!im)) {
397 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
398 : (" FAILED, there are no context"));
399 0 : return false;
400 : }
401 :
402 0 : mKeyDownEventWasSent = aKeyDownEventWasSent;
403 0 : mFilterKeyEvent = true;
404 0 : mProcessingKeyEvent = aEvent;
405 0 : gboolean isFiltered = gtk_im_context_filter_keypress(im, aEvent);
406 0 : mProcessingKeyEvent = nsnull;
407 :
408 : // We filter the key event if the event was not committed (because
409 : // it's probably part of a composition) or if the key event was
410 : // committed _and_ changed. This way we still let key press
411 : // events go through as simple key press events instead of
412 : // composed characters.
413 0 : bool filterThisEvent = isFiltered && mFilterKeyEvent;
414 :
415 0 : if (IsComposing() && !isFiltered) {
416 0 : if (aEvent->type == GDK_KEY_PRESS) {
417 0 : if (!mDispatchedCompositionString.IsEmpty()) {
418 : // If there is composition string, we shouldn't dispatch
419 : // any keydown events during composition.
420 0 : filterThisEvent = true;
421 : } else {
422 : // A Hangul input engine for SCIM doesn't emit preedit_end
423 : // signal even when composition string becomes empty. On the
424 : // other hand, we should allow to make composition with empty
425 : // string for other languages because there *might* be such
426 : // IM. For compromising this issue, we should dispatch
427 : // compositionend event, however, we don't need to reset IM
428 : // actually.
429 0 : CommitCompositionBy(EmptyString());
430 0 : filterThisEvent = false;
431 : }
432 : } else {
433 : // Key release event may not be consumed by IM, however, we
434 : // shouldn't dispatch any keyup event during composition.
435 0 : filterThisEvent = true;
436 : }
437 : }
438 :
439 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
440 : (" filterThisEvent=%s (isFiltered=%s, mFilterKeyEvent=%s)",
441 : filterThisEvent ? "TRUE" : "FALSE", isFiltered ? "YES" : "NO",
442 : mFilterKeyEvent ? "YES" : "NO"));
443 :
444 0 : return filterThisEvent;
445 : }
446 :
447 : void
448 0 : nsGtkIMModule::OnFocusChangeInGecko(bool aFocus)
449 : {
450 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
451 : ("GtkIMModule(%p): OnFocusChangeInGecko, aFocus=%s, "
452 : "mCompositionState=%s, mIsIMFocused=%s, "
453 : "mIgnoreNativeCompositionEvent=%s",
454 : this, aFocus ? "YES" : "NO", GetCompositionStateName(),
455 : mIsIMFocused ? "YES" : "NO",
456 : mIgnoreNativeCompositionEvent ? "YES" : "NO"));
457 :
458 : // We shouldn't carry over the removed string to another editor.
459 0 : mSelectedString.Truncate();
460 :
461 0 : if (aFocus) {
462 : // If we failed to commit forcedely in previous focused editor,
463 : // we should reopen the gate for native signals in new focused editor.
464 0 : mIgnoreNativeCompositionEvent = false;
465 : }
466 0 : }
467 :
468 : void
469 0 : nsGtkIMModule::ResetIME()
470 : {
471 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
472 : ("GtkIMModule(%p): ResetIME, mCompositionState=%s, mIsIMFocused=%s",
473 : this, GetCompositionStateName(), mIsIMFocused ? "YES" : "NO"));
474 :
475 0 : GtkIMContext *im = GetContext();
476 0 : if (NS_UNLIKELY(!im)) {
477 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
478 : (" FAILED, there are no context"));
479 0 : return;
480 : }
481 :
482 0 : mIgnoreNativeCompositionEvent = true;
483 0 : gtk_im_context_reset(im);
484 : }
485 :
486 : nsresult
487 0 : nsGtkIMModule::ResetInputState(nsWindow* aCaller)
488 : {
489 0 : if (NS_UNLIKELY(IsDestroyed())) {
490 0 : return NS_OK;
491 : }
492 :
493 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
494 : ("GtkIMModule(%p): ResetInputState, aCaller=%p, mCompositionState=%s",
495 : this, aCaller, GetCompositionStateName()));
496 :
497 0 : if (aCaller != mLastFocusedWindow) {
498 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
499 : (" WARNING: the caller isn't focused window, mLastFocusedWindow=%p",
500 : mLastFocusedWindow));
501 0 : return NS_OK;
502 : }
503 :
504 0 : if (!IsComposing()) {
505 0 : return NS_OK;
506 : }
507 :
508 : // XXX We should commit composition ourselves temporary...
509 0 : ResetIME();
510 0 : CommitCompositionBy(mDispatchedCompositionString);
511 :
512 0 : return NS_OK;
513 : }
514 :
515 : nsresult
516 0 : nsGtkIMModule::CancelIMEComposition(nsWindow* aCaller)
517 : {
518 0 : if (NS_UNLIKELY(IsDestroyed())) {
519 0 : return NS_OK;
520 : }
521 :
522 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
523 : ("GtkIMModule(%p): CancelIMEComposition, aCaller=%p",
524 : this, aCaller));
525 :
526 0 : if (aCaller != mLastFocusedWindow) {
527 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
528 : (" FAILED, the caller isn't focused window, mLastFocusedWindow=%p",
529 : mLastFocusedWindow));
530 0 : return NS_OK;
531 : }
532 :
533 0 : if (!IsComposing()) {
534 0 : return NS_OK;
535 : }
536 :
537 0 : GtkIMContext *im = GetContext();
538 0 : if (NS_UNLIKELY(!im)) {
539 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
540 : (" FAILED, there are no context"));
541 0 : return NS_OK;
542 : }
543 :
544 0 : ResetIME();
545 0 : CommitCompositionBy(EmptyString());
546 :
547 0 : return NS_OK;
548 : }
549 :
550 : void
551 0 : nsGtkIMModule::SetInputContext(nsWindow* aCaller,
552 : const InputContext* aContext,
553 : const InputContextAction* aAction)
554 : {
555 0 : if (NS_UNLIKELY(IsDestroyed())) {
556 0 : return;
557 : }
558 :
559 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
560 : ("GtkIMModule(%p): SetInputContext, aCaller=%p, aState=%s mHTMLInputType=%s",
561 : this, aCaller, GetEnabledStateName(aContext->mIMEState.mEnabled),
562 : NS_ConvertUTF16toUTF8(aContext->mHTMLInputType).get()));
563 :
564 0 : if (aCaller != mLastFocusedWindow) {
565 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
566 : (" FAILED, the caller isn't focused window, mLastFocusedWindow=%p",
567 : mLastFocusedWindow));
568 0 : return;
569 : }
570 :
571 0 : if (!mContext) {
572 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
573 : (" FAILED, there are no context"));
574 0 : return;
575 : }
576 :
577 :
578 0 : if (sLastFocusedModule != this) {
579 0 : mInputContext = *aContext;
580 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
581 : (" SUCCEEDED, but we're not active"));
582 0 : return;
583 : }
584 :
585 : bool changingEnabledState =
586 0 : aContext->mIMEState.mEnabled != mInputContext.mIMEState.mEnabled;
587 :
588 : // Release current IME focus if IME is enabled.
589 0 : if (changingEnabledState && IsEditable()) {
590 0 : ResetInputState(mLastFocusedWindow);
591 0 : Blur();
592 : }
593 :
594 0 : mInputContext = *aContext;
595 :
596 : // Even when aState is not enabled state, we need to set IME focus.
597 : // Because some IMs are updating the status bar of them at this time.
598 : // Be aware, don't use aWindow here because this method shouldn't move
599 : // focus actually.
600 0 : if (changingEnabledState) {
601 0 : Focus();
602 : }
603 :
604 : #if (MOZ_PLATFORM_MAEMO == 5)
605 : GtkIMContext *im = GetContext();
606 : if (im) {
607 : if (IsEnabled()) {
608 : // Ensure that opening the virtual keyboard is allowed for this specific
609 : // InputContext depending on the content.ime.strict.policy pref
610 : if (mInputContext.mIMEState.mEnabled != IMEState::DISABLED &&
611 : mInputContext.mIMEState.mEnabled != IMEState::PLUGIN &&
612 : Preferences::GetBool("content.ime.strict_policy", false) &&
613 : !aAction->ContentGotFocusByTrustedCause() &&
614 : !aAction->UserMightRequestOpenVKB()) {
615 : return;
616 : }
617 :
618 : // It is not desired that the hildon's autocomplete mechanism displays
619 : // user previous entered passwds, so lets make completions invisible
620 : // in these cases.
621 : int mode;
622 : g_object_get(im, "hildon-input-mode", &mode, NULL);
623 :
624 : if (mInputContext.mIMEState.mEnabled == IMEState::ENABLED ||
625 : mInputContext.mIMEState.mEnabled == IMEState::PLUGIN) {
626 : mode &= ~HILDON_GTK_INPUT_MODE_INVISIBLE;
627 : } else if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) {
628 : mode |= HILDON_GTK_INPUT_MODE_INVISIBLE;
629 : }
630 :
631 : // Turn off auto-capitalization for editboxes
632 : mode &= ~HILDON_GTK_INPUT_MODE_AUTOCAP;
633 :
634 : // Turn off predictive dictionaries for editboxes
635 : mode &= ~HILDON_GTK_INPUT_MODE_DICTIONARY;
636 :
637 : g_object_set(im, "hildon-input-mode",
638 : (HildonGtkInputMode)mode, NULL);
639 : gIsVirtualKeyboardOpened = true;
640 : hildon_gtk_im_context_show(im);
641 : } else {
642 : gIsVirtualKeyboardOpened = false;
643 : hildon_gtk_im_context_hide(im);
644 : }
645 : }
646 :
647 : nsCOMPtr<nsIObserverService> observerService =
648 : mozilla::services::GetObserverService();
649 : if (observerService) {
650 : nsAutoString rectBuf;
651 : PRInt32 x, y, w, h;
652 : gdk_window_get_position(aCaller->GetGdkWindow(), &x, &y);
653 : gdk_window_get_size(aCaller->GetGdkWindow(), &w, &h);
654 : rectBuf.Assign(NS_LITERAL_STRING("{\"left\": "));
655 : rectBuf.AppendInt(x);
656 : rectBuf.Append(NS_LITERAL_STRING(", \"top\": "));
657 : rectBuf.AppendInt(y);
658 : rectBuf.Append(NS_LITERAL_STRING(", \"right\": "));
659 : rectBuf.AppendInt(w);
660 : rectBuf.Append(NS_LITERAL_STRING(", \"bottom\": "));
661 : rectBuf.AppendInt(h);
662 : rectBuf.Append(NS_LITERAL_STRING("}"));
663 : observerService->NotifyObservers(nsnull, "softkb-change",
664 : rectBuf.get());
665 : }
666 : #endif
667 : }
668 :
669 : InputContext
670 0 : nsGtkIMModule::GetInputContext()
671 : {
672 0 : mInputContext.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
673 0 : return mInputContext;
674 : }
675 :
676 : /* static */
677 : bool
678 0 : nsGtkIMModule::IsVirtualKeyboardOpened()
679 : {
680 : #ifdef MOZ_PLATFORM_MAEMO
681 : return gIsVirtualKeyboardOpened;
682 : #else
683 0 : return false;
684 : #endif
685 : }
686 :
687 : GtkIMContext*
688 0 : nsGtkIMModule::GetContext()
689 : {
690 0 : if (IsEnabled()) {
691 0 : return mContext;
692 : }
693 :
694 : #ifndef NS_IME_ENABLED_ON_PASSWORD_FIELD
695 0 : if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) {
696 0 : return mSimpleContext;
697 : }
698 : #endif // NS_IME_ENABLED_ON_PASSWORD_FIELD
699 :
700 0 : return mDummyContext;
701 : }
702 :
703 : bool
704 0 : nsGtkIMModule::IsEnabled()
705 : {
706 : return mInputContext.mIMEState.mEnabled == IMEState::ENABLED ||
707 : #ifdef NS_IME_ENABLED_ON_PASSWORD_FIELD
708 : mInputContext.mIMEState.mEnabled == IMEState::PASSWORD ||
709 : #endif // NS_IME_ENABLED_ON_PASSWORD_FIELD
710 0 : mInputContext.mIMEState.mEnabled == IMEState::PLUGIN;
711 : }
712 :
713 : bool
714 0 : nsGtkIMModule::IsEditable()
715 : {
716 : return mInputContext.mIMEState.mEnabled == IMEState::ENABLED ||
717 : mInputContext.mIMEState.mEnabled == IMEState::PLUGIN ||
718 0 : mInputContext.mIMEState.mEnabled == IMEState::PASSWORD;
719 : }
720 :
721 : void
722 0 : nsGtkIMModule::Focus()
723 : {
724 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
725 : ("GtkIMModule(%p): Focus, sLastFocusedModule=%p",
726 : this, sLastFocusedModule));
727 :
728 0 : if (mIsIMFocused) {
729 0 : NS_ASSERTION(sLastFocusedModule == this,
730 : "We're not active, but the IM was focused?");
731 0 : return;
732 : }
733 :
734 0 : GtkIMContext *im = GetContext();
735 0 : if (!im) {
736 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
737 : (" FAILED, there are no context"));
738 0 : return;
739 : }
740 :
741 0 : if (sLastFocusedModule && sLastFocusedModule != this) {
742 0 : sLastFocusedModule->Blur();
743 : }
744 :
745 0 : sLastFocusedModule = this;
746 :
747 0 : gtk_im_context_focus_in(im);
748 0 : mIsIMFocused = true;
749 :
750 0 : if (!IsEnabled()) {
751 : // We should release IME focus for uim and scim.
752 : // These IMs are using snooper that is released at losing focus.
753 0 : Blur();
754 : }
755 : }
756 :
757 : void
758 0 : nsGtkIMModule::Blur()
759 : {
760 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
761 : ("GtkIMModule(%p): Blur, mIsIMFocused=%s",
762 : this, mIsIMFocused ? "YES" : "NO"));
763 :
764 0 : if (!mIsIMFocused) {
765 0 : return;
766 : }
767 :
768 0 : GtkIMContext *im = GetContext();
769 0 : if (!im) {
770 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
771 : (" FAILED, there are no context"));
772 0 : return;
773 : }
774 :
775 0 : gtk_im_context_focus_out(im);
776 0 : mIsIMFocused = false;
777 : }
778 :
779 : /* static */
780 : void
781 0 : nsGtkIMModule::OnStartCompositionCallback(GtkIMContext *aContext,
782 : nsGtkIMModule* aModule)
783 : {
784 0 : aModule->OnStartCompositionNative(aContext);
785 0 : }
786 :
787 : void
788 0 : nsGtkIMModule::OnStartCompositionNative(GtkIMContext *aContext)
789 : {
790 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
791 : ("GtkIMModule(%p): OnStartCompositionNative, aContext=%p",
792 : this, aContext));
793 :
794 : // See bug 472635, we should do nothing if IM context doesn't match.
795 0 : if (GetContext() != aContext) {
796 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
797 : (" FAILED, given context doesn't match, GetContext()=%p",
798 : GetContext()));
799 0 : return;
800 : }
801 :
802 0 : if (!DispatchCompositionStart()) {
803 0 : return;
804 : }
805 0 : SetCursorPosition(mCompositionStart);
806 : }
807 :
808 : /* static */
809 : void
810 0 : nsGtkIMModule::OnEndCompositionCallback(GtkIMContext *aContext,
811 : nsGtkIMModule* aModule)
812 : {
813 0 : aModule->OnEndCompositionNative(aContext);
814 0 : }
815 :
816 : void
817 0 : nsGtkIMModule::OnEndCompositionNative(GtkIMContext *aContext)
818 : {
819 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
820 : ("GtkIMModule(%p): OnEndCompositionNative, aContext=%p",
821 : this, aContext));
822 :
823 : // See bug 472635, we should do nothing if IM context doesn't match.
824 0 : if (GetContext() != aContext) {
825 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
826 : (" FAILED, given context doesn't match, GetContext()=%p",
827 : GetContext()));
828 0 : return;
829 : }
830 :
831 0 : bool shouldIgnoreThisEvent = ShouldIgnoreNativeCompositionEvent();
832 :
833 : // Finish the cancelling mode here rather than DispatchCompositionEnd()
834 : // because DispatchCompositionEnd() is called ourselves when we need to
835 : // commit the composition string *before* the focus moves completely.
836 : // Note that the native commit can be fired *after* ResetIME().
837 0 : mIgnoreNativeCompositionEvent = false;
838 :
839 0 : if (!IsComposing() || shouldIgnoreThisEvent) {
840 : // If we already handled the commit event, we should do nothing here.
841 0 : return;
842 : }
843 :
844 : // Be aware, widget can be gone
845 0 : DispatchCompositionEnd();
846 : }
847 :
848 : /* static */
849 : void
850 0 : nsGtkIMModule::OnChangeCompositionCallback(GtkIMContext *aContext,
851 : nsGtkIMModule* aModule)
852 : {
853 0 : aModule->OnChangeCompositionNative(aContext);
854 0 : }
855 :
856 : void
857 0 : nsGtkIMModule::OnChangeCompositionNative(GtkIMContext *aContext)
858 : {
859 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
860 : ("GtkIMModule(%p): OnChangeCompositionNative, aContext=%p",
861 : this, aContext));
862 :
863 : // See bug 472635, we should do nothing if IM context doesn't match.
864 0 : if (GetContext() != aContext) {
865 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
866 : (" FAILED, given context doesn't match, GetContext()=%p",
867 : GetContext()));
868 0 : return;
869 : }
870 :
871 0 : if (ShouldIgnoreNativeCompositionEvent()) {
872 0 : return;
873 : }
874 :
875 0 : nsAutoString compositionString;
876 0 : GetCompositionString(compositionString);
877 0 : if (!IsComposing() && compositionString.IsEmpty()) {
878 0 : mDispatchedCompositionString.Truncate();
879 : return; // Don't start the composition with empty string.
880 : }
881 :
882 : // Be aware, widget can be gone
883 0 : DispatchTextEvent(compositionString, false);
884 : }
885 :
886 : /* static */
887 : gboolean
888 0 : nsGtkIMModule::OnRetrieveSurroundingCallback(GtkIMContext *aContext,
889 : nsGtkIMModule *aModule)
890 : {
891 0 : return aModule->OnRetrieveSurroundingNative(aContext);
892 : }
893 :
894 : gboolean
895 0 : nsGtkIMModule::OnRetrieveSurroundingNative(GtkIMContext *aContext)
896 : {
897 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
898 : ("GtkIMModule(%p): OnRetrieveSurroundingNative, aContext=%p, current context=%p",
899 : this, aContext, GetContext()));
900 :
901 0 : if (GetContext() != aContext) {
902 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
903 : (" FAILED, given context doesn't match, GetContext()=%p",
904 : GetContext()));
905 0 : return FALSE;
906 : }
907 :
908 0 : nsAutoString uniStr;
909 : PRUint32 cursorPos;
910 0 : if (NS_FAILED(GetCurrentParagraph(uniStr, cursorPos))) {
911 0 : return FALSE;
912 : }
913 :
914 : glong wbytes;
915 0 : gchar *utf8_str = g_utf16_to_utf8((const gunichar2 *)uniStr.get(),
916 0 : uniStr.Length(), NULL, &wbytes, NULL);
917 0 : if (utf8_str == NULL) {
918 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
919 : (" failed to convert utf16 string to utf8"));
920 0 : return FALSE;
921 : }
922 : gtk_im_context_set_surrounding(aContext, utf8_str, wbytes,
923 0 : g_utf8_offset_to_pointer(utf8_str, cursorPos) - utf8_str);
924 0 : g_free(utf8_str);
925 :
926 0 : return TRUE;
927 : }
928 :
929 : /* static */
930 : gboolean
931 0 : nsGtkIMModule::OnDeleteSurroundingCallback(GtkIMContext *aContext,
932 : gint aOffset,
933 : gint aNChars,
934 : nsGtkIMModule *aModule)
935 : {
936 0 : return aModule->OnDeleteSurroundingNative(aContext, aOffset, aNChars);
937 : }
938 :
939 : gboolean
940 0 : nsGtkIMModule::OnDeleteSurroundingNative(GtkIMContext *aContext,
941 : gint aOffset,
942 : gint aNChars)
943 : {
944 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
945 : ("GtkIMModule(%p): OnDeleteSurroundingNative, aContext=%p, current context=%p",
946 : this, aContext, GetContext()));
947 :
948 0 : if (GetContext() != aContext) {
949 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
950 : (" FAILED, given context doesn't match, GetContext()=%p",
951 : GetContext()));
952 0 : return FALSE;
953 : }
954 :
955 0 : if (NS_SUCCEEDED(DeleteText(aOffset, (PRUint32)aNChars))) {
956 0 : return TRUE;
957 : }
958 :
959 : // failed
960 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
961 : (" FAILED, cannot delete text"));
962 0 : return FALSE;
963 : }
964 :
965 : /* static */
966 : void
967 0 : nsGtkIMModule::OnCommitCompositionCallback(GtkIMContext *aContext,
968 : const gchar *aString,
969 : nsGtkIMModule* aModule)
970 : {
971 0 : aModule->OnCommitCompositionNative(aContext, aString);
972 0 : }
973 :
974 : void
975 0 : nsGtkIMModule::OnCommitCompositionNative(GtkIMContext *aContext,
976 : const gchar *aUTF8Char)
977 : {
978 0 : const gchar emptyStr = 0;
979 0 : const gchar *commitString = aUTF8Char ? aUTF8Char : &emptyStr;
980 :
981 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
982 : ("GtkIMModule(%p): OnCommitCompositionNative, aContext=%p, current context=%p, commitString=\"%s\"",
983 : this, aContext, GetContext(), commitString));
984 :
985 : // See bug 472635, we should do nothing if IM context doesn't match.
986 0 : if (GetContext() != aContext) {
987 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
988 : (" FAILED, given context doesn't match, GetContext()=%p",
989 : GetContext()));
990 0 : return;
991 : }
992 :
993 : // If we are not in composition and committing with empty string,
994 : // we need to do nothing because if we continued to handle this
995 : // signal, we would dispatch compositionstart, text, compositionend
996 : // events with empty string. Of course, they are unnecessary events
997 : // for Web applications and our editor.
998 0 : if (!IsComposing() && !commitString[0]) {
999 0 : return;
1000 : }
1001 :
1002 0 : if (ShouldIgnoreNativeCompositionEvent()) {
1003 0 : return;
1004 : }
1005 :
1006 : // If IME doesn't change their keyevent that generated this commit,
1007 : // don't send it through XIM - just send it as a normal key press
1008 : // event.
1009 0 : if (!IsComposing() && mProcessingKeyEvent) {
1010 : char keyval_utf8[8]; /* should have at least 6 bytes of space */
1011 : gint keyval_utf8_len;
1012 : guint32 keyval_unicode;
1013 :
1014 0 : keyval_unicode = gdk_keyval_to_unicode(mProcessingKeyEvent->keyval);
1015 0 : keyval_utf8_len = g_unichar_to_utf8(keyval_unicode, keyval_utf8);
1016 0 : keyval_utf8[keyval_utf8_len] = '\0';
1017 :
1018 0 : if (!strcmp(commitString, keyval_utf8)) {
1019 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1020 : ("GtkIMModule(%p): OnCommitCompositionNative, we'll send normal key event",
1021 : this));
1022 0 : mFilterKeyEvent = false;
1023 0 : return;
1024 : }
1025 : }
1026 :
1027 0 : NS_ConvertUTF8toUTF16 str(commitString);
1028 0 : CommitCompositionBy(str); // Be aware, widget can be gone
1029 : }
1030 :
1031 : bool
1032 0 : nsGtkIMModule::CommitCompositionBy(const nsAString& aString)
1033 : {
1034 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1035 : ("GtkIMModule(%p): CommitCompositionBy, aString=\"%s\", "
1036 : "mDispatchedCompositionString=\"%s\"",
1037 : this, NS_ConvertUTF16toUTF8(aString).get(),
1038 : NS_ConvertUTF16toUTF8(mDispatchedCompositionString).get()));
1039 :
1040 0 : if (!DispatchTextEvent(aString, true)) {
1041 0 : return false;
1042 : }
1043 : // We should dispatch the compositionend event here because some IMEs
1044 : // might not fire "preedit_end" native event.
1045 0 : return DispatchCompositionEnd(); // Be aware, widget can be gone
1046 : }
1047 :
1048 : void
1049 0 : nsGtkIMModule::GetCompositionString(nsAString &aCompositionString)
1050 : {
1051 : gchar *preedit_string;
1052 : gint cursor_pos;
1053 : PangoAttrList *feedback_list;
1054 : gtk_im_context_get_preedit_string(GetContext(), &preedit_string,
1055 0 : &feedback_list, &cursor_pos);
1056 0 : if (preedit_string && *preedit_string) {
1057 0 : CopyUTF8toUTF16(preedit_string, aCompositionString);
1058 : } else {
1059 0 : aCompositionString.Truncate();
1060 : }
1061 :
1062 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1063 : ("GtkIMModule(%p): GetCompositionString, result=\"%s\"",
1064 : this, preedit_string));
1065 :
1066 0 : pango_attr_list_unref(feedback_list);
1067 0 : g_free(preedit_string);
1068 0 : }
1069 :
1070 : bool
1071 0 : nsGtkIMModule::DispatchCompositionStart()
1072 : {
1073 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1074 : ("GtkIMModule(%p): DispatchCompositionStart", this));
1075 :
1076 0 : if (IsComposing()) {
1077 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1078 : (" WARNING, we're already in composition"));
1079 0 : return true;
1080 : }
1081 :
1082 0 : if (!mLastFocusedWindow) {
1083 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1084 : (" FAILED, there are no focused window in this module"));
1085 0 : return false;
1086 : }
1087 :
1088 : nsEventStatus status;
1089 : nsQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT,
1090 0 : mLastFocusedWindow);
1091 0 : InitEvent(selection);
1092 0 : mLastFocusedWindow->DispatchEvent(&selection, status);
1093 :
1094 0 : if (!selection.mSucceeded || selection.mReply.mOffset == PR_UINT32_MAX) {
1095 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1096 : (" FAILED, cannot query the selection offset"));
1097 0 : return false;
1098 : }
1099 :
1100 : // XXX The composition start point might be changed by composition events
1101 : // even though we strongly hope it doesn't happen.
1102 : // Every composition event should have the start offset for the result
1103 : // because it may high cost if we query the offset every time.
1104 0 : mCompositionStart = selection.mReply.mOffset;
1105 0 : mDispatchedCompositionString.Truncate();
1106 :
1107 0 : if (mProcessingKeyEvent && !mKeyDownEventWasSent &&
1108 : mProcessingKeyEvent->type == GDK_KEY_PRESS) {
1109 : // If this composition is started by a native keydown event, we need to
1110 : // dispatch our keydown event here (before composition start).
1111 0 : nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow;
1112 : bool isCancelled;
1113 : mLastFocusedWindow->DispatchKeyDownEvent(mProcessingKeyEvent,
1114 0 : &isCancelled);
1115 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1116 : (" keydown event is dispatched"));
1117 0 : if (static_cast<nsWindow*>(kungFuDeathGrip.get())->IsDestroyed() ||
1118 0 : kungFuDeathGrip != mLastFocusedWindow) {
1119 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1120 : (" NOTE, the focused widget was destroyed/changed by keydown event"));
1121 0 : return false;
1122 : }
1123 : }
1124 :
1125 0 : if (mIgnoreNativeCompositionEvent) {
1126 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1127 : (" WARNING, mIgnoreNativeCompositionEvent is already TRUE, but we forcedly reset"));
1128 0 : mIgnoreNativeCompositionEvent = false;
1129 : }
1130 :
1131 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1132 : (" mCompositionStart=%u", mCompositionStart));
1133 0 : mCompositionState = eCompositionState_CompositionStartDispatched;
1134 : nsCompositionEvent compEvent(true, NS_COMPOSITION_START,
1135 0 : mLastFocusedWindow);
1136 0 : InitEvent(compEvent);
1137 0 : nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow;
1138 0 : mLastFocusedWindow->DispatchEvent(&compEvent, status);
1139 0 : if (static_cast<nsWindow*>(kungFuDeathGrip.get())->IsDestroyed() ||
1140 0 : kungFuDeathGrip != mLastFocusedWindow) {
1141 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1142 : (" NOTE, the focused widget was destroyed/changed by compositionstart event"));
1143 0 : return false;
1144 : }
1145 :
1146 0 : return true;
1147 : }
1148 :
1149 : bool
1150 0 : nsGtkIMModule::DispatchCompositionEnd()
1151 : {
1152 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1153 : ("GtkIMModule(%p): DispatchCompositionEnd, "
1154 : "mDispatchedCompositionString=\"%s\"",
1155 : this, NS_ConvertUTF16toUTF8(mDispatchedCompositionString).get()));
1156 :
1157 0 : if (!IsComposing()) {
1158 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1159 : (" WARNING, we have alrady finished the composition"));
1160 0 : return false;
1161 : }
1162 :
1163 0 : if (!mLastFocusedWindow) {
1164 0 : mDispatchedCompositionString.Truncate();
1165 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1166 : (" FAILED, there are no focused window in this module"));
1167 0 : return false;
1168 : }
1169 :
1170 : nsCompositionEvent compEvent(true, NS_COMPOSITION_END,
1171 0 : mLastFocusedWindow);
1172 0 : InitEvent(compEvent);
1173 0 : compEvent.data = mDispatchedCompositionString;
1174 : nsEventStatus status;
1175 0 : nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow;
1176 0 : mLastFocusedWindow->DispatchEvent(&compEvent, status);
1177 0 : mCompositionState = eCompositionState_NotComposing;
1178 0 : mCompositionStart = PR_UINT32_MAX;
1179 0 : mDispatchedCompositionString.Truncate();
1180 0 : if (static_cast<nsWindow*>(kungFuDeathGrip.get())->IsDestroyed() ||
1181 0 : kungFuDeathGrip != mLastFocusedWindow) {
1182 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1183 : (" NOTE, the focused widget was destroyed/changed by compositionend event"));
1184 0 : return false;
1185 : }
1186 :
1187 0 : return true;
1188 : }
1189 :
1190 : bool
1191 0 : nsGtkIMModule::DispatchTextEvent(const nsAString &aCompositionString,
1192 : bool aIsCommit)
1193 : {
1194 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1195 : ("GtkIMModule(%p): DispatchTextEvent, aIsCommit=%s",
1196 : this, aIsCommit ? "TRUE" : "FALSE"));
1197 :
1198 0 : if (!mLastFocusedWindow) {
1199 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1200 : (" FAILED, there are no focused window in this module"));
1201 0 : return false;
1202 : }
1203 :
1204 0 : if (!IsComposing()) {
1205 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1206 : (" The composition wasn't started, force starting..."));
1207 0 : nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow;
1208 0 : if (!DispatchCompositionStart()) {
1209 0 : return false;
1210 : }
1211 : }
1212 :
1213 : nsEventStatus status;
1214 0 : nsRefPtr<nsWindow> lastFocusedWindow = mLastFocusedWindow;
1215 :
1216 0 : if (aCompositionString != mDispatchedCompositionString) {
1217 : nsCompositionEvent compositionUpdate(true, NS_COMPOSITION_UPDATE,
1218 0 : mLastFocusedWindow);
1219 0 : InitEvent(compositionUpdate);
1220 0 : compositionUpdate.data = aCompositionString;
1221 0 : mDispatchedCompositionString = aCompositionString;
1222 0 : mLastFocusedWindow->DispatchEvent(&compositionUpdate, status);
1223 0 : if (lastFocusedWindow->IsDestroyed() ||
1224 0 : lastFocusedWindow != mLastFocusedWindow) {
1225 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1226 : (" NOTE, the focused widget was destroyed/changed by compositionupdate"));
1227 0 : return false;
1228 : }
1229 : }
1230 :
1231 : // Store the selected string which will be removed by following text event.
1232 0 : if (mCompositionState == eCompositionState_CompositionStartDispatched) {
1233 : // XXX We should assume, for now, any web applications don't change
1234 : // selection at handling this text event.
1235 : nsQueryContentEvent querySelectedTextEvent(true,
1236 : NS_QUERY_SELECTED_TEXT,
1237 0 : mLastFocusedWindow);
1238 0 : mLastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status);
1239 0 : if (querySelectedTextEvent.mSucceeded) {
1240 0 : mSelectedString = querySelectedTextEvent.mReply.mString;
1241 0 : mCompositionStart = querySelectedTextEvent.mReply.mOffset;
1242 : }
1243 : }
1244 :
1245 0 : nsTextEvent textEvent(true, NS_TEXT_TEXT, mLastFocusedWindow);
1246 0 : InitEvent(textEvent);
1247 :
1248 0 : PRUint32 targetOffset = mCompositionStart;
1249 :
1250 0 : nsAutoTArray<nsTextRange, 4> textRanges;
1251 0 : if (!aIsCommit) {
1252 : // NOTE: SetTextRangeList() assumes that mDispatchedCompositionString
1253 : // has been updated already.
1254 0 : SetTextRangeList(textRanges);
1255 0 : for (PRUint32 i = 0; i < textRanges.Length(); i++) {
1256 0 : nsTextRange& range = textRanges[i];
1257 0 : if (range.mRangeType == NS_TEXTRANGE_SELECTEDRAWTEXT ||
1258 : range.mRangeType == NS_TEXTRANGE_SELECTEDCONVERTEDTEXT) {
1259 0 : targetOffset += range.mStartOffset;
1260 0 : break;
1261 : }
1262 : }
1263 : }
1264 :
1265 0 : textEvent.rangeCount = textRanges.Length();
1266 0 : textEvent.rangeArray = textRanges.Elements();
1267 0 : textEvent.theText = mDispatchedCompositionString.get();
1268 :
1269 : mCompositionState = aIsCommit ?
1270 : eCompositionState_CommitTextEventDispatched :
1271 0 : eCompositionState_TextEventDispatched;
1272 :
1273 0 : mLastFocusedWindow->DispatchEvent(&textEvent, status);
1274 0 : if (lastFocusedWindow->IsDestroyed() ||
1275 0 : lastFocusedWindow != mLastFocusedWindow) {
1276 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1277 : (" NOTE, the focused widget was destroyed/changed by text event"));
1278 0 : return false;
1279 : }
1280 :
1281 0 : SetCursorPosition(targetOffset);
1282 :
1283 0 : return true;
1284 : }
1285 :
1286 : void
1287 0 : nsGtkIMModule::SetTextRangeList(nsTArray<nsTextRange> &aTextRangeList)
1288 : {
1289 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1290 : ("GtkIMModule(%p): SetTextRangeList", this));
1291 :
1292 0 : NS_PRECONDITION(aTextRangeList.IsEmpty(), "aTextRangeList must be empty");
1293 :
1294 : gchar *preedit_string;
1295 : gint cursor_pos;
1296 : PangoAttrList *feedback_list;
1297 : gtk_im_context_get_preedit_string(GetContext(), &preedit_string,
1298 0 : &feedback_list, &cursor_pos);
1299 0 : if (!preedit_string || !*preedit_string) {
1300 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1301 : (" preedit_string is null"));
1302 0 : pango_attr_list_unref(feedback_list);
1303 0 : g_free(preedit_string);
1304 0 : return;
1305 : }
1306 :
1307 : PangoAttrIterator* iter;
1308 0 : iter = pango_attr_list_get_iterator(feedback_list);
1309 0 : if (!iter) {
1310 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1311 : (" FAILED, iterator couldn't be allocated"));
1312 0 : pango_attr_list_unref(feedback_list);
1313 0 : g_free(preedit_string);
1314 0 : return;
1315 : }
1316 :
1317 : /*
1318 : * Depend on gtk2's implementation on XIM support.
1319 : * In aFeedback got from gtk2, there are only three types of data:
1320 : * PANGO_ATTR_UNDERLINE, PANGO_ATTR_FOREGROUND, PANGO_ATTR_BACKGROUND.
1321 : * Corresponding to XIMUnderline, XIMReverse.
1322 : * Don't take PANGO_ATTR_BACKGROUND into account, since
1323 : * PANGO_ATTR_BACKGROUND and PANGO_ATTR_FOREGROUND are always
1324 : * a couple.
1325 : */
1326 0 : do {
1327 : PangoAttribute* attrUnderline =
1328 0 : pango_attr_iterator_get(iter, PANGO_ATTR_UNDERLINE);
1329 : PangoAttribute* attrForeground =
1330 0 : pango_attr_iterator_get(iter, PANGO_ATTR_FOREGROUND);
1331 0 : if (!attrUnderline && !attrForeground) {
1332 0 : continue;
1333 : }
1334 :
1335 : // Get the range of the current attribute(s)
1336 : gint start, end;
1337 0 : pango_attr_iterator_range(iter, &start, &end);
1338 :
1339 0 : nsTextRange range;
1340 : // XIMReverse | XIMUnderline
1341 0 : if (attrUnderline && attrForeground) {
1342 0 : range.mRangeType = NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
1343 : }
1344 : // XIMUnderline
1345 0 : else if (attrUnderline) {
1346 0 : range.mRangeType = NS_TEXTRANGE_CONVERTEDTEXT;
1347 : }
1348 : // XIMReverse
1349 0 : else if (attrForeground) {
1350 0 : range.mRangeType = NS_TEXTRANGE_SELECTEDRAWTEXT;
1351 : } else {
1352 0 : range.mRangeType = NS_TEXTRANGE_RAWINPUT;
1353 : }
1354 :
1355 0 : gunichar2* uniStr = nsnull;
1356 0 : if (start == 0) {
1357 0 : range.mStartOffset = 0;
1358 : } else {
1359 : glong uniStrLen;
1360 : uniStr = g_utf8_to_utf16(preedit_string, start,
1361 0 : NULL, &uniStrLen, NULL);
1362 0 : if (uniStr) {
1363 0 : range.mStartOffset = uniStrLen;
1364 0 : g_free(uniStr);
1365 0 : uniStr = nsnull;
1366 : }
1367 : }
1368 :
1369 : glong uniStrLen;
1370 : uniStr = g_utf8_to_utf16(preedit_string + start, end - start,
1371 0 : NULL, &uniStrLen, NULL);
1372 0 : if (!uniStr) {
1373 0 : range.mEndOffset = range.mStartOffset;
1374 : } else {
1375 0 : range.mEndOffset = range.mStartOffset + uniStrLen;
1376 0 : g_free(uniStr);
1377 0 : uniStr = nsnull;
1378 : }
1379 :
1380 0 : aTextRangeList.AppendElement(range);
1381 :
1382 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1383 : (" mStartOffset=%u, mEndOffset=%u, mRangeType=%s",
1384 : range.mStartOffset, range.mEndOffset,
1385 : GetRangeTypeName(range.mRangeType)));
1386 0 : } while (pango_attr_iterator_next(iter));
1387 :
1388 0 : nsTextRange range;
1389 0 : if (cursor_pos < 0) {
1390 0 : range.mStartOffset = 0;
1391 0 : } else if (PRUint32(cursor_pos) > mDispatchedCompositionString.Length()) {
1392 0 : range.mStartOffset = mDispatchedCompositionString.Length();
1393 : } else {
1394 0 : range.mStartOffset = PRUint32(cursor_pos);
1395 : }
1396 0 : range.mEndOffset = range.mStartOffset;
1397 0 : range.mRangeType = NS_TEXTRANGE_CARETPOSITION;
1398 0 : aTextRangeList.AppendElement(range);
1399 :
1400 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1401 : (" mStartOffset=%u, mEndOffset=%u, mRangeType=%s",
1402 : range.mStartOffset, range.mEndOffset,
1403 : GetRangeTypeName(range.mRangeType)));
1404 :
1405 0 : pango_attr_iterator_destroy(iter);
1406 0 : pango_attr_list_unref(feedback_list);
1407 0 : g_free(preedit_string);
1408 : }
1409 :
1410 : void
1411 0 : nsGtkIMModule::SetCursorPosition(PRUint32 aTargetOffset)
1412 : {
1413 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1414 : ("GtkIMModule(%p): SetCursorPosition, aTargetOffset=%u",
1415 : this, aTargetOffset));
1416 :
1417 0 : if (aTargetOffset == PR_UINT32_MAX) {
1418 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1419 : (" FAILED, aTargetOffset is wrong offset"));
1420 0 : return;
1421 : }
1422 :
1423 0 : if (!mLastFocusedWindow) {
1424 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1425 : (" FAILED, there are no focused window"));
1426 0 : return;
1427 : }
1428 :
1429 0 : GtkIMContext *im = GetContext();
1430 0 : if (!im) {
1431 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1432 : (" FAILED, there are no context"));
1433 0 : return;
1434 : }
1435 :
1436 : nsQueryContentEvent charRect(true, NS_QUERY_TEXT_RECT,
1437 0 : mLastFocusedWindow);
1438 0 : charRect.InitForQueryTextRect(aTargetOffset, 1);
1439 0 : InitEvent(charRect);
1440 : nsEventStatus status;
1441 0 : mLastFocusedWindow->DispatchEvent(&charRect, status);
1442 0 : if (!charRect.mSucceeded) {
1443 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1444 : (" FAILED, NS_QUERY_TEXT_RECT was failed"));
1445 : return;
1446 : }
1447 : nsWindow* rootWindow =
1448 0 : static_cast<nsWindow*>(mLastFocusedWindow->GetTopLevelWidget());
1449 :
1450 : // Get the position of the rootWindow in screen.
1451 : gint rootX, rootY;
1452 0 : gdk_window_get_origin(rootWindow->GetGdkWindow(), &rootX, &rootY);
1453 :
1454 : // Get the position of IM context owner window in screen.
1455 : gint ownerX, ownerY;
1456 0 : gdk_window_get_origin(mOwnerWindow->GetGdkWindow(), &ownerX, &ownerY);
1457 :
1458 : // Compute the caret position in the IM owner window.
1459 : GdkRectangle area;
1460 0 : area.x = charRect.mReply.mRect.x + rootX - ownerX;
1461 0 : area.y = charRect.mReply.mRect.y + rootY - ownerY;
1462 0 : area.width = 0;
1463 0 : area.height = charRect.mReply.mRect.height;
1464 :
1465 0 : gtk_im_context_set_cursor_location(im, &area);
1466 : }
1467 :
1468 : nsresult
1469 0 : nsGtkIMModule::GetCurrentParagraph(nsAString& aText, PRUint32& aCursorPos)
1470 : {
1471 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1472 : ("GtkIMModule(%p): GetCurrentParagraph, mCompositionState=%s",
1473 : this, GetCompositionStateName()));
1474 :
1475 0 : if (!mLastFocusedWindow) {
1476 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1477 : (" FAILED, there are no focused window in this module"));
1478 0 : return NS_ERROR_NULL_POINTER;
1479 : }
1480 :
1481 : nsEventStatus status;
1482 :
1483 0 : PRUint32 selOffset = mCompositionStart;
1484 0 : PRUint32 selLength = mSelectedString.Length();
1485 :
1486 : // If focused editor doesn't have composition string, we should use
1487 : // current selection.
1488 0 : if (!EditorHasCompositionString()) {
1489 : // Query cursor position & selection
1490 : nsQueryContentEvent querySelectedTextEvent(true,
1491 : NS_QUERY_SELECTED_TEXT,
1492 0 : mLastFocusedWindow);
1493 0 : mLastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status);
1494 0 : NS_ENSURE_TRUE(querySelectedTextEvent.mSucceeded, NS_ERROR_FAILURE);
1495 :
1496 0 : selOffset = querySelectedTextEvent.mReply.mOffset;
1497 0 : selLength = querySelectedTextEvent.mReply.mString.Length();
1498 : }
1499 :
1500 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1501 : (" selOffset=%u, selLength=%u",
1502 : selOffset, selLength));
1503 :
1504 : // XXX nsString::Find and nsString::RFind take PRInt32 for offset, so,
1505 : // we cannot support this request when the current offset is larger
1506 : // than PR_INT32_MAX.
1507 0 : if (selOffset > PR_INT32_MAX || selLength > PR_INT32_MAX ||
1508 : selOffset + selLength > PR_INT32_MAX) {
1509 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1510 : (" FAILED, The selection is out of range"));
1511 0 : return NS_ERROR_FAILURE;
1512 : }
1513 :
1514 : // Get all text contents of the focused editor
1515 : nsQueryContentEvent queryTextContentEvent(true,
1516 : NS_QUERY_TEXT_CONTENT,
1517 0 : mLastFocusedWindow);
1518 0 : queryTextContentEvent.InitForQueryTextContent(0, PR_UINT32_MAX);
1519 0 : mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status);
1520 0 : NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE);
1521 :
1522 0 : nsAutoString textContent(queryTextContentEvent.mReply.mString);
1523 0 : if (selOffset + selLength > textContent.Length()) {
1524 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1525 : (" FAILED, The selection is invalid, textContent.Length()=%u",
1526 : textContent.Length()));
1527 0 : return NS_ERROR_FAILURE;
1528 : }
1529 :
1530 : // Remove composing string and restore the selected string because
1531 : // GtkEntry doesn't remove selected string until committing, however,
1532 : // our editor does it. We should emulate the behavior for IME.
1533 0 : if (EditorHasCompositionString() &&
1534 0 : mDispatchedCompositionString != mSelectedString) {
1535 : textContent.Replace(mCompositionStart,
1536 0 : mDispatchedCompositionString.Length(), mSelectedString);
1537 : }
1538 :
1539 : // Get only the focused paragraph, by looking for newlines
1540 : PRInt32 parStart = (selOffset == 0) ? 0 :
1541 0 : textContent.RFind("\n", false, selOffset - 1, -1) + 1;
1542 0 : PRInt32 parEnd = textContent.Find("\n", false, selOffset + selLength, -1);
1543 0 : if (parEnd < 0) {
1544 0 : parEnd = textContent.Length();
1545 : }
1546 0 : aText = nsDependentSubstring(textContent, parStart, parEnd - parStart);
1547 0 : aCursorPos = selOffset - PRUint32(parStart);
1548 :
1549 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1550 : (" aText=%s, aText.Length()=%u, aCursorPos=%u",
1551 : NS_ConvertUTF16toUTF8(aText).get(),
1552 : aText.Length(), aCursorPos));
1553 :
1554 0 : return NS_OK;
1555 : }
1556 :
1557 : nsresult
1558 0 : nsGtkIMModule::DeleteText(const PRInt32 aOffset, const PRUint32 aNChars)
1559 : {
1560 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1561 : ("GtkIMModule(%p): DeleteText, aOffset=%d, aNChars=%d, "
1562 : "mCompositionState=%s",
1563 : this, aOffset, aNChars, GetCompositionStateName()));
1564 :
1565 0 : if (!mLastFocusedWindow) {
1566 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1567 : (" FAILED, there are no focused window in this module"));
1568 0 : return NS_ERROR_NULL_POINTER;
1569 : }
1570 :
1571 0 : nsRefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
1572 : nsEventStatus status;
1573 :
1574 : // First, we should cancel current composition because editor cannot
1575 : // handle changing selection and deleting text.
1576 : PRUint32 selOffset;
1577 0 : bool wasComposing = IsComposing();
1578 0 : bool editorHadCompositionString = EditorHasCompositionString();
1579 0 : if (wasComposing) {
1580 0 : selOffset = mCompositionStart;
1581 0 : if (editorHadCompositionString &&
1582 0 : !DispatchTextEvent(mSelectedString, false)) {
1583 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1584 : (" FAILED, quitting from DeletText"));
1585 0 : return NS_ERROR_FAILURE;
1586 : }
1587 0 : if (!DispatchCompositionEnd()) {
1588 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1589 : (" FAILED, quitting from DeletText"));
1590 0 : return NS_ERROR_FAILURE;
1591 : }
1592 : } else {
1593 : // Query cursor position & selection
1594 : nsQueryContentEvent querySelectedTextEvent(true,
1595 : NS_QUERY_SELECTED_TEXT,
1596 0 : mLastFocusedWindow);
1597 0 : lastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status);
1598 0 : NS_ENSURE_TRUE(querySelectedTextEvent.mSucceeded, NS_ERROR_FAILURE);
1599 :
1600 0 : selOffset = querySelectedTextEvent.mReply.mOffset;
1601 : }
1602 :
1603 : // Set selection to delete
1604 : nsSelectionEvent selectionEvent(true, NS_SELECTION_SET,
1605 0 : mLastFocusedWindow);
1606 0 : selectionEvent.mOffset = selOffset + aOffset;
1607 0 : selectionEvent.mLength = aNChars;
1608 0 : selectionEvent.mReversed = false;
1609 0 : selectionEvent.mExpandToClusterBoundary = false;
1610 0 : lastFocusedWindow->DispatchEvent(&selectionEvent, status);
1611 :
1612 0 : if (!selectionEvent.mSucceeded ||
1613 0 : lastFocusedWindow != mLastFocusedWindow ||
1614 0 : lastFocusedWindow->Destroyed()) {
1615 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1616 : (" FAILED, setting selection caused focus change "
1617 : "or window destroyed"));
1618 0 : return NS_ERROR_FAILURE;
1619 : }
1620 :
1621 : // Delete the selection
1622 : nsContentCommandEvent contentCommandEvent(true,
1623 : NS_CONTENT_COMMAND_DELETE,
1624 0 : mLastFocusedWindow);
1625 0 : mLastFocusedWindow->DispatchEvent(&contentCommandEvent, status);
1626 :
1627 0 : if (!contentCommandEvent.mSucceeded ||
1628 0 : lastFocusedWindow != mLastFocusedWindow ||
1629 0 : lastFocusedWindow->Destroyed()) {
1630 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1631 : (" FAILED, deleting the selection caused focus change "
1632 : "or window destroyed"));
1633 0 : return NS_ERROR_FAILURE;
1634 : }
1635 :
1636 0 : if (!wasComposing) {
1637 0 : return NS_OK;
1638 : }
1639 :
1640 : // Restore the composition at new caret position.
1641 0 : if (!DispatchCompositionStart()) {
1642 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1643 : (" FAILED, resterting composition start"));
1644 0 : return NS_ERROR_FAILURE;
1645 : }
1646 :
1647 0 : if (!editorHadCompositionString) {
1648 0 : return NS_OK;
1649 : }
1650 :
1651 0 : nsAutoString compositionString;
1652 0 : GetCompositionString(compositionString);
1653 0 : if (!DispatchTextEvent(compositionString, true)) {
1654 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1655 : (" FAILED, restoring composition string"));
1656 0 : return NS_ERROR_FAILURE;
1657 : }
1658 :
1659 0 : return NS_OK;
1660 : }
1661 :
1662 : void
1663 0 : nsGtkIMModule::InitEvent(nsGUIEvent &aEvent)
1664 : {
1665 0 : aEvent.time = PR_Now() / 1000;
1666 0 : }
1667 :
1668 : bool
1669 0 : nsGtkIMModule::ShouldIgnoreNativeCompositionEvent()
1670 : {
1671 0 : PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1672 : ("GtkIMModule(%p): ShouldIgnoreNativeCompositionEvent, mLastFocusedWindow=%p, mIgnoreNativeCompositionEvent=%s",
1673 : this, mLastFocusedWindow,
1674 : mIgnoreNativeCompositionEvent ? "YES" : "NO"));
1675 :
1676 0 : if (!mLastFocusedWindow) {
1677 0 : return true; // cannot continue
1678 : }
1679 :
1680 0 : return mIgnoreNativeCompositionEvent;
1681 : }
|