1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim: set ts=4 et sw=4 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #define FORCE_PR_LOG /* Allow logging in the release build */
13 #include "nsGtkIMModule.h"
15 #include "mozilla/Likely.h"
16 #include "mozilla/MiscEvents.h"
17 #include "mozilla/Preferences.h"
18 #include "mozilla/TextEvents.h"
20 using namespace mozilla
;
21 using namespace mozilla::widget
;
24 PRLogModuleInfo
* gGtkIMLog
= nullptr;
27 GetRangeTypeName(uint32_t aRangeType
)
30 case NS_TEXTRANGE_RAWINPUT
:
31 return "NS_TEXTRANGE_RAWINPUT";
32 case NS_TEXTRANGE_CONVERTEDTEXT
:
33 return "NS_TEXTRANGE_CONVERTEDTEXT";
34 case NS_TEXTRANGE_SELECTEDRAWTEXT
:
35 return "NS_TEXTRANGE_SELECTEDRAWTEXT";
36 case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT
:
37 return "NS_TEXTRANGE_SELECTEDCONVERTEDTEXT";
38 case NS_TEXTRANGE_CARETPOSITION
:
39 return "NS_TEXTRANGE_CARETPOSITION";
41 return "UNKNOWN SELECTION TYPE!!";
46 GetEnabledStateName(uint32_t aState
)
49 case IMEState::DISABLED
:
51 case IMEState::ENABLED
:
53 case IMEState::PASSWORD
:
55 case IMEState::PLUGIN
:
58 return "UNKNOWN ENABLED STATUS!!";
63 const static bool kUseSimpleContextDefault
= MOZ_WIDGET_GTK
== 2;
65 nsGtkIMModule
* nsGtkIMModule::sLastFocusedModule
= nullptr;
66 bool nsGtkIMModule::sUseSimpleContext
;
68 nsGtkIMModule::nsGtkIMModule(nsWindow
* aOwnerWindow
) :
69 mOwnerWindow(aOwnerWindow
), mLastFocusedWindow(nullptr),
71 mSimpleContext(nullptr),
72 mDummyContext(nullptr),
73 mCompositionStart(UINT32_MAX
), mProcessingKeyEvent(nullptr),
74 mCompositionTargetOffset(UINT32_MAX
),
75 mCompositionState(eCompositionState_NotComposing
),
76 mIsIMFocused(false), mIgnoreNativeCompositionEvent(false)
80 gGtkIMLog
= PR_NewLogModule("nsGtkIMModuleWidgets");
83 static bool sFirstInstance
= true;
85 sFirstInstance
= false;
88 "intl.ime.use_simple_context_on_password_field",
89 kUseSimpleContextDefault
);
97 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
98 ("GtkIMModule(%p): Init, mOwnerWindow=%p",
101 MozContainer
* container
= mOwnerWindow
->GetMozContainer();
102 NS_PRECONDITION(container
, "container is null");
103 GdkWindow
* gdkWindow
= gtk_widget_get_window(GTK_WIDGET(container
));
105 // NOTE: gtk_im_*_new() abort (kill) the whole process when it fails.
106 // So, we don't need to check the result.
109 mContext
= gtk_im_multicontext_new();
110 gtk_im_context_set_client_window(mContext
, gdkWindow
);
111 g_signal_connect(mContext
, "preedit_changed",
112 G_CALLBACK(nsGtkIMModule::OnChangeCompositionCallback
),
114 g_signal_connect(mContext
, "retrieve_surrounding",
115 G_CALLBACK(nsGtkIMModule::OnRetrieveSurroundingCallback
),
117 g_signal_connect(mContext
, "delete_surrounding",
118 G_CALLBACK(nsGtkIMModule::OnDeleteSurroundingCallback
),
120 g_signal_connect(mContext
, "commit",
121 G_CALLBACK(nsGtkIMModule::OnCommitCompositionCallback
),
123 g_signal_connect(mContext
, "preedit_start",
124 G_CALLBACK(nsGtkIMModule::OnStartCompositionCallback
),
126 g_signal_connect(mContext
, "preedit_end",
127 G_CALLBACK(nsGtkIMModule::OnEndCompositionCallback
),
131 if (sUseSimpleContext
) {
132 mSimpleContext
= gtk_im_context_simple_new();
133 gtk_im_context_set_client_window(mSimpleContext
, gdkWindow
);
134 g_signal_connect(mSimpleContext
, "preedit_changed",
135 G_CALLBACK(&nsGtkIMModule::OnChangeCompositionCallback
),
137 g_signal_connect(mSimpleContext
, "retrieve_surrounding",
138 G_CALLBACK(&nsGtkIMModule::OnRetrieveSurroundingCallback
),
140 g_signal_connect(mSimpleContext
, "delete_surrounding",
141 G_CALLBACK(&nsGtkIMModule::OnDeleteSurroundingCallback
),
143 g_signal_connect(mSimpleContext
, "commit",
144 G_CALLBACK(&nsGtkIMModule::OnCommitCompositionCallback
),
146 g_signal_connect(mSimpleContext
, "preedit_start",
147 G_CALLBACK(nsGtkIMModule::OnStartCompositionCallback
),
149 g_signal_connect(mSimpleContext
, "preedit_end",
150 G_CALLBACK(nsGtkIMModule::OnEndCompositionCallback
),
155 mDummyContext
= gtk_im_multicontext_new();
156 gtk_im_context_set_client_window(mDummyContext
, gdkWindow
);
159 nsGtkIMModule::~nsGtkIMModule()
161 if (this == sLastFocusedModule
) {
162 sLastFocusedModule
= nullptr;
164 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
165 ("GtkIMModule(%p) was gone", this));
169 nsGtkIMModule::OnDestroyWindow(nsWindow
* aWindow
)
171 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
172 ("GtkIMModule(%p): OnDestroyWindow, aWindow=%p, mLastFocusedWindow=%p, mOwnerWindow=%p, mLastFocusedModule=%p",
173 this, aWindow
, mLastFocusedWindow
, mOwnerWindow
, sLastFocusedModule
));
175 NS_PRECONDITION(aWindow
, "aWindow must not be null");
177 if (mLastFocusedWindow
== aWindow
) {
178 CancelIMEComposition(aWindow
);
182 mLastFocusedWindow
= nullptr;
185 if (mOwnerWindow
!= aWindow
) {
189 if (sLastFocusedModule
== this) {
190 sLastFocusedModule
= nullptr;
195 * The given window is the owner of this, so, we must release the
196 * contexts now. But that might be referred from other nsWindows
197 * (they are children of this. But we don't know why there are the
198 * cases). So, we need to clear the pointers that refers to contexts
199 * and this if the other referrers are still alive. See bug 349727.
202 PrepareToDestroyContext(mContext
);
203 gtk_im_context_set_client_window(mContext
, nullptr);
204 g_object_unref(mContext
);
208 if (mSimpleContext
) {
209 gtk_im_context_set_client_window(mSimpleContext
, nullptr);
210 g_object_unref(mSimpleContext
);
211 mSimpleContext
= nullptr;
215 // mContext and mDummyContext have the same slaveType and signal_data
216 // so no need for another workaround_gtk_im_display_closed.
217 gtk_im_context_set_client_window(mDummyContext
, nullptr);
218 g_object_unref(mDummyContext
);
219 mDummyContext
= nullptr;
222 mOwnerWindow
= nullptr;
223 mLastFocusedWindow
= nullptr;
224 mInputContext
.mIMEState
.mEnabled
= IMEState::DISABLED
;
226 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
227 (" SUCCEEDED, Completely destroyed"));
230 // Work around gtk bug http://bugzilla.gnome.org/show_bug.cgi?id=483223:
231 // (and the similar issue of GTK+ IIIM)
232 // The GTK+ XIM and IIIM modules register handlers for the "closed" signal
233 // on the display, but:
234 // * The signal handlers are not disconnected when the module is unloaded.
236 // The GTK+ XIM module has another problem:
237 // * When the signal handler is run (with the module loaded) it tries
238 // XFree (and fails) on a pointer that did not come from Xmalloc.
240 // To prevent these modules from being unloaded, use static variables to
241 // hold ref of GtkIMContext class.
242 // For GTK+ XIM module, to prevent the signal handler from being run,
243 // find the signal handlers and remove them.
245 // GtkIMContextXIMs share XOpenIM connections and display closed signal
246 // handlers (where possible).
249 nsGtkIMModule::PrepareToDestroyContext(GtkIMContext
*aContext
)
251 MozContainer
* container
= mOwnerWindow
->GetMozContainer();
252 NS_PRECONDITION(container
, "The container of the window is null");
254 #if (MOZ_WIDGET_GTK == 2)
255 GtkIMMulticontext
*multicontext
= GTK_IM_MULTICONTEXT(aContext
);
256 GtkIMContext
*slave
= multicontext
->slave
;
258 GtkIMContext
*slave
= nullptr; //TODO GTK3
264 GType slaveType
= G_TYPE_FROM_INSTANCE(slave
);
265 const gchar
*im_type_name
= g_type_name(slaveType
);
266 if (strcmp(im_type_name
, "GtkIMContextXIM") == 0) {
267 if (gtk_check_version(2, 12, 1) == nullptr) {
268 return; // gtk bug has been fixed
271 struct GtkIMContextXIM
274 gpointer private_data
;
278 gpointer signal_data
=
279 reinterpret_cast<GtkIMContextXIM
*>(slave
)->private_data
;
284 g_signal_handlers_disconnect_matched(
285 gtk_widget_get_display(GTK_WIDGET(container
)),
286 G_SIGNAL_MATCH_DATA
, 0, 0, nullptr, nullptr, signal_data
);
288 // Add a reference to prevent the XIM module from being unloaded
289 // and reloaded: each time the module is loaded and used, it
290 // opens (and doesn't close) new XOpenIM connections.
291 static gpointer gtk_xim_context_class
=
292 g_type_class_ref(slaveType
);
293 // Mute unused variable warning:
294 (void)gtk_xim_context_class
;
295 } else if (strcmp(im_type_name
, "GtkIMContextIIIM") == 0) {
296 // Add a reference to prevent the IIIM module from being unloaded
297 static gpointer gtk_iiim_context_class
=
298 g_type_class_ref(slaveType
);
299 // Mute unused variable warning:
300 (void)gtk_iiim_context_class
;
305 nsGtkIMModule::OnFocusWindow(nsWindow
* aWindow
)
307 if (MOZ_UNLIKELY(IsDestroyed())) {
311 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
312 ("GtkIMModule(%p): OnFocusWindow, aWindow=%p, mLastFocusedWindow=%p",
313 this, aWindow
, mLastFocusedWindow
));
314 mLastFocusedWindow
= aWindow
;
319 nsGtkIMModule::OnBlurWindow(nsWindow
* aWindow
)
321 if (MOZ_UNLIKELY(IsDestroyed())) {
325 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
326 ("GtkIMModule(%p): OnBlurWindow, aWindow=%p, mLastFocusedWindow=%p, mIsIMFocused=%s",
327 this, aWindow
, mLastFocusedWindow
, mIsIMFocused
? "YES" : "NO"));
329 if (!mIsIMFocused
|| mLastFocusedWindow
!= aWindow
) {
337 nsGtkIMModule::OnKeyEvent(nsWindow
* aCaller
, GdkEventKey
* aEvent
,
338 bool aKeyDownEventWasSent
/* = false */)
340 NS_PRECONDITION(aEvent
, "aEvent must be non-null");
342 if (!IsEditable() || MOZ_UNLIKELY(IsDestroyed())) {
346 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
347 ("GtkIMModule(%p): OnKeyEvent, aCaller=%p, aKeyDownEventWasSent=%s",
348 this, aCaller
, aKeyDownEventWasSent
? "TRUE" : "FALSE"));
349 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
350 (" aEvent: type=%s, keyval=%s, unicode=0x%X",
351 aEvent
->type
== GDK_KEY_PRESS
? "GDK_KEY_PRESS" :
352 aEvent
->type
== GDK_KEY_RELEASE
? "GDK_KEY_RELEASE" : "Unknown",
353 gdk_keyval_name(aEvent
->keyval
),
354 gdk_keyval_to_unicode(aEvent
->keyval
)));
356 if (aCaller
!= mLastFocusedWindow
) {
357 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
358 (" FAILED, the caller isn't focused window, mLastFocusedWindow=%p",
359 mLastFocusedWindow
));
363 GtkIMContext
* im
= GetContext();
364 if (MOZ_UNLIKELY(!im
)) {
365 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
366 (" FAILED, there are no context"));
370 mKeyDownEventWasSent
= aKeyDownEventWasSent
;
371 mFilterKeyEvent
= true;
372 mProcessingKeyEvent
= aEvent
;
373 gboolean isFiltered
= gtk_im_context_filter_keypress(im
, aEvent
);
374 mProcessingKeyEvent
= nullptr;
376 // We filter the key event if the event was not committed (because
377 // it's probably part of a composition) or if the key event was
378 // committed _and_ changed. This way we still let key press
379 // events go through as simple key press events instead of
380 // composed characters.
381 bool filterThisEvent
= isFiltered
&& mFilterKeyEvent
;
383 if (IsComposing() && !isFiltered
) {
384 if (aEvent
->type
== GDK_KEY_PRESS
) {
385 if (!mDispatchedCompositionString
.IsEmpty()) {
386 // If there is composition string, we shouldn't dispatch
387 // any keydown events during composition.
388 filterThisEvent
= true;
390 // A Hangul input engine for SCIM doesn't emit preedit_end
391 // signal even when composition string becomes empty. On the
392 // other hand, we should allow to make composition with empty
393 // string for other languages because there *might* be such
394 // IM. For compromising this issue, we should dispatch
395 // compositionend event, however, we don't need to reset IM
397 CommitCompositionBy(EmptyString());
398 filterThisEvent
= false;
401 // Key release event may not be consumed by IM, however, we
402 // shouldn't dispatch any keyup event during composition.
403 filterThisEvent
= true;
407 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
408 (" filterThisEvent=%s (isFiltered=%s, mFilterKeyEvent=%s)",
409 filterThisEvent
? "TRUE" : "FALSE", isFiltered
? "YES" : "NO",
410 mFilterKeyEvent
? "YES" : "NO"));
412 return filterThisEvent
;
416 nsGtkIMModule::OnFocusChangeInGecko(bool aFocus
)
418 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
419 ("GtkIMModule(%p): OnFocusChangeInGecko, aFocus=%s, "
420 "mCompositionState=%s, mIsIMFocused=%s, "
421 "mIgnoreNativeCompositionEvent=%s",
422 this, aFocus
? "YES" : "NO", GetCompositionStateName(),
423 mIsIMFocused
? "YES" : "NO",
424 mIgnoreNativeCompositionEvent
? "YES" : "NO"));
426 // We shouldn't carry over the removed string to another editor.
427 mSelectedString
.Truncate();
430 // If we failed to commit forcedely in previous focused editor,
431 // we should reopen the gate for native signals in new focused editor.
432 mIgnoreNativeCompositionEvent
= false;
437 nsGtkIMModule::ResetIME()
439 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
440 ("GtkIMModule(%p): ResetIME, mCompositionState=%s, mIsIMFocused=%s",
441 this, GetCompositionStateName(), mIsIMFocused
? "YES" : "NO"));
443 GtkIMContext
*im
= GetContext();
444 if (MOZ_UNLIKELY(!im
)) {
445 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
446 (" FAILED, there are no context"));
450 mIgnoreNativeCompositionEvent
= true;
451 gtk_im_context_reset(im
);
455 nsGtkIMModule::CommitIMEComposition(nsWindow
* aCaller
)
457 if (MOZ_UNLIKELY(IsDestroyed())) {
461 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
462 ("GtkIMModule(%p): CommitIMEComposition, aCaller=%p, "
463 "mCompositionState=%s",
464 this, aCaller
, GetCompositionStateName()));
466 if (aCaller
!= mLastFocusedWindow
) {
467 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
468 (" WARNING: the caller isn't focused window, mLastFocusedWindow=%p",
469 mLastFocusedWindow
));
473 if (!IsComposing()) {
477 // XXX We should commit composition ourselves temporary...
479 CommitCompositionBy(mDispatchedCompositionString
);
485 nsGtkIMModule::CancelIMEComposition(nsWindow
* aCaller
)
487 if (MOZ_UNLIKELY(IsDestroyed())) {
491 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
492 ("GtkIMModule(%p): CancelIMEComposition, aCaller=%p",
495 if (aCaller
!= mLastFocusedWindow
) {
496 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
497 (" FAILED, the caller isn't focused window, mLastFocusedWindow=%p",
498 mLastFocusedWindow
));
502 if (!IsComposing()) {
506 GtkIMContext
*im
= GetContext();
507 if (MOZ_UNLIKELY(!im
)) {
508 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
509 (" FAILED, there are no context"));
514 CommitCompositionBy(EmptyString());
520 nsGtkIMModule::OnUpdateComposition(void)
522 if (MOZ_UNLIKELY(IsDestroyed())) {
526 SetCursorPosition(mCompositionTargetOffset
);
530 nsGtkIMModule::SetInputContext(nsWindow
* aCaller
,
531 const InputContext
* aContext
,
532 const InputContextAction
* aAction
)
534 if (MOZ_UNLIKELY(IsDestroyed())) {
538 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
539 ("GtkIMModule(%p): SetInputContext, aCaller=%p, aState=%s mHTMLInputType=%s",
540 this, aCaller
, GetEnabledStateName(aContext
->mIMEState
.mEnabled
),
541 NS_ConvertUTF16toUTF8(aContext
->mHTMLInputType
).get()));
543 if (aCaller
!= mLastFocusedWindow
) {
544 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
545 (" FAILED, the caller isn't focused window, mLastFocusedWindow=%p",
546 mLastFocusedWindow
));
551 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
552 (" FAILED, there are no context"));
557 if (sLastFocusedModule
!= this) {
558 mInputContext
= *aContext
;
559 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
560 (" SUCCEEDED, but we're not active"));
564 bool changingEnabledState
=
565 aContext
->mIMEState
.mEnabled
!= mInputContext
.mIMEState
.mEnabled
||
566 aContext
->mHTMLInputType
!= mInputContext
.mHTMLInputType
;
568 // Release current IME focus if IME is enabled.
569 if (changingEnabledState
&& IsEditable()) {
570 CommitIMEComposition(mLastFocusedWindow
);
574 mInputContext
= *aContext
;
576 if (changingEnabledState
) {
577 #if (MOZ_WIDGET_GTK == 3)
578 static bool sInputPurposeSupported
= !gtk_check_version(3, 6, 0);
579 if (sInputPurposeSupported
&& IsEditable()) {
580 GtkIMContext
* context
= GetContext();
582 GtkInputPurpose purpose
= GTK_INPUT_PURPOSE_FREE_FORM
;
583 const nsString
& inputType
= mInputContext
.mHTMLInputType
;
584 // Password case has difficult issue. Desktop IMEs disable
585 // composition if input-purpose is password.
586 // For disabling IME on |ime-mode: disabled;|, we need to check
587 // mEnabled value instead of inputType value. This hack also
588 // enables composition on
589 // <input type="password" style="ime-mode: enabled;">.
590 // This is right behavior of ime-mode on desktop.
592 // On the other hand, IME for tablet devices may provide a
593 // specific software keyboard for password field. If so,
594 // the behavior might look strange on both:
595 // <input type="text" style="ime-mode: disabled;">
596 // <input type="password" style="ime-mode: enabled;">
598 // Temporarily, we should focus on desktop environment for now.
599 // I.e., let's ignore tablet devices for now. When somebody
600 // reports actual trouble on tablet devices, we should try to
601 // look for a way to solve actual problem.
602 if (mInputContext
.mIMEState
.mEnabled
== IMEState::PASSWORD
) {
603 purpose
= GTK_INPUT_PURPOSE_PASSWORD
;
604 } else if (inputType
.EqualsLiteral("email")) {
605 purpose
= GTK_INPUT_PURPOSE_EMAIL
;
606 } else if (inputType
.EqualsLiteral("url")) {
607 purpose
= GTK_INPUT_PURPOSE_URL
;
608 } else if (inputType
.EqualsLiteral("tel")) {
609 purpose
= GTK_INPUT_PURPOSE_PHONE
;
610 } else if (inputType
.EqualsLiteral("number")) {
611 purpose
= GTK_INPUT_PURPOSE_NUMBER
;
614 g_object_set(context
, "input-purpose", purpose
, nullptr);
617 #endif // #if (MOZ_WIDGET_GTK == 3)
619 // Even when aState is not enabled state, we need to set IME focus.
620 // Because some IMs are updating the status bar of them at this time.
621 // Be aware, don't use aWindow here because this method shouldn't move
625 // XXX Should we call Blur() when it's not editable? E.g., it might be
626 // better to close VKB automatically.
631 nsGtkIMModule::GetInputContext()
633 mInputContext
.mIMEState
.mOpen
= IMEState::OPEN_STATE_NOT_SUPPORTED
;
634 return mInputContext
;
639 nsGtkIMModule::IsVirtualKeyboardOpened()
645 nsGtkIMModule::GetContext()
650 if (mInputContext
.mIMEState
.mEnabled
== IMEState::PASSWORD
) {
651 return mSimpleContext
;
653 return mDummyContext
;
657 nsGtkIMModule::IsEnabled()
659 return mInputContext
.mIMEState
.mEnabled
== IMEState::ENABLED
||
660 mInputContext
.mIMEState
.mEnabled
== IMEState::PLUGIN
||
661 (!sUseSimpleContext
&&
662 mInputContext
.mIMEState
.mEnabled
== IMEState::PASSWORD
);
666 nsGtkIMModule::IsEditable()
668 return mInputContext
.mIMEState
.mEnabled
== IMEState::ENABLED
||
669 mInputContext
.mIMEState
.mEnabled
== IMEState::PLUGIN
||
670 mInputContext
.mIMEState
.mEnabled
== IMEState::PASSWORD
;
674 nsGtkIMModule::Focus()
676 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
677 ("GtkIMModule(%p): Focus, sLastFocusedModule=%p",
678 this, sLastFocusedModule
));
681 NS_ASSERTION(sLastFocusedModule
== this,
682 "We're not active, but the IM was focused?");
686 GtkIMContext
*im
= GetContext();
688 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
689 (" FAILED, there are no context"));
693 if (sLastFocusedModule
&& sLastFocusedModule
!= this) {
694 sLastFocusedModule
->Blur();
697 sLastFocusedModule
= this;
699 gtk_im_context_focus_in(im
);
703 // We should release IME focus for uim and scim.
704 // These IMs are using snooper that is released at losing focus.
710 nsGtkIMModule::Blur()
712 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
713 ("GtkIMModule(%p): Blur, mIsIMFocused=%s",
714 this, mIsIMFocused
? "YES" : "NO"));
720 GtkIMContext
*im
= GetContext();
722 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
723 (" FAILED, there are no context"));
727 gtk_im_context_focus_out(im
);
728 mIsIMFocused
= false;
733 nsGtkIMModule::OnStartCompositionCallback(GtkIMContext
*aContext
,
734 nsGtkIMModule
* aModule
)
736 aModule
->OnStartCompositionNative(aContext
);
740 nsGtkIMModule::OnStartCompositionNative(GtkIMContext
*aContext
)
742 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
743 ("GtkIMModule(%p): OnStartCompositionNative, aContext=%p",
746 // See bug 472635, we should do nothing if IM context doesn't match.
747 if (GetContext() != aContext
) {
748 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
749 (" FAILED, given context doesn't match, GetContext()=%p",
754 if (!DispatchCompositionStart()) {
757 mCompositionTargetOffset
= mCompositionStart
;
762 nsGtkIMModule::OnEndCompositionCallback(GtkIMContext
*aContext
,
763 nsGtkIMModule
* aModule
)
765 aModule
->OnEndCompositionNative(aContext
);
769 nsGtkIMModule::OnEndCompositionNative(GtkIMContext
*aContext
)
771 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
772 ("GtkIMModule(%p): OnEndCompositionNative, aContext=%p",
775 // See bug 472635, we should do nothing if IM context doesn't match.
776 if (GetContext() != aContext
) {
777 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
778 (" FAILED, given context doesn't match, GetContext()=%p",
783 bool shouldIgnoreThisEvent
= ShouldIgnoreNativeCompositionEvent();
785 // Finish the cancelling mode here rather than DispatchCompositionEnd()
786 // because DispatchCompositionEnd() is called ourselves when we need to
787 // commit the composition string *before* the focus moves completely.
788 // Note that the native commit can be fired *after* ResetIME().
789 mIgnoreNativeCompositionEvent
= false;
791 if (!IsComposing() || shouldIgnoreThisEvent
) {
792 // If we already handled the commit event, we should do nothing here.
796 // Be aware, widget can be gone
797 DispatchCompositionEnd();
802 nsGtkIMModule::OnChangeCompositionCallback(GtkIMContext
*aContext
,
803 nsGtkIMModule
* aModule
)
805 aModule
->OnChangeCompositionNative(aContext
);
809 nsGtkIMModule::OnChangeCompositionNative(GtkIMContext
*aContext
)
811 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
812 ("GtkIMModule(%p): OnChangeCompositionNative, aContext=%p",
815 // See bug 472635, we should do nothing if IM context doesn't match.
816 if (GetContext() != aContext
) {
817 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
818 (" FAILED, given context doesn't match, GetContext()=%p",
823 if (ShouldIgnoreNativeCompositionEvent()) {
827 nsAutoString compositionString
;
828 GetCompositionString(compositionString
);
829 if (!IsComposing() && compositionString
.IsEmpty()) {
830 mDispatchedCompositionString
.Truncate();
831 return; // Don't start the composition with empty string.
834 // Be aware, widget can be gone
835 DispatchTextEvent(compositionString
, false);
840 nsGtkIMModule::OnRetrieveSurroundingCallback(GtkIMContext
*aContext
,
841 nsGtkIMModule
*aModule
)
843 return aModule
->OnRetrieveSurroundingNative(aContext
);
847 nsGtkIMModule::OnRetrieveSurroundingNative(GtkIMContext
*aContext
)
849 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
850 ("GtkIMModule(%p): OnRetrieveSurroundingNative, aContext=%p, current context=%p",
851 this, aContext
, GetContext()));
853 if (GetContext() != aContext
) {
854 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
855 (" FAILED, given context doesn't match, GetContext()=%p",
862 if (NS_FAILED(GetCurrentParagraph(uniStr
, cursorPos
))) {
866 NS_ConvertUTF16toUTF8
utf8Str(nsDependentSubstring(uniStr
, 0, cursorPos
));
867 uint32_t cursorPosInUTF8
= utf8Str
.Length();
868 AppendUTF16toUTF8(nsDependentSubstring(uniStr
, cursorPos
), utf8Str
);
869 gtk_im_context_set_surrounding(aContext
, utf8Str
.get(), utf8Str
.Length(),
876 nsGtkIMModule::OnDeleteSurroundingCallback(GtkIMContext
*aContext
,
879 nsGtkIMModule
*aModule
)
881 return aModule
->OnDeleteSurroundingNative(aContext
, aOffset
, aNChars
);
885 nsGtkIMModule::OnDeleteSurroundingNative(GtkIMContext
*aContext
,
889 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
890 ("GtkIMModule(%p): OnDeleteSurroundingNative, aContext=%p, current context=%p",
891 this, aContext
, GetContext()));
893 if (GetContext() != aContext
) {
894 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
895 (" FAILED, given context doesn't match, GetContext()=%p",
900 if (NS_SUCCEEDED(DeleteText(aOffset
, (uint32_t)aNChars
))) {
905 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
906 (" FAILED, cannot delete text"));
912 nsGtkIMModule::OnCommitCompositionCallback(GtkIMContext
*aContext
,
913 const gchar
*aString
,
914 nsGtkIMModule
* aModule
)
916 aModule
->OnCommitCompositionNative(aContext
, aString
);
920 nsGtkIMModule::OnCommitCompositionNative(GtkIMContext
*aContext
,
921 const gchar
*aUTF8Char
)
923 const gchar emptyStr
= 0;
924 const gchar
*commitString
= aUTF8Char
? aUTF8Char
: &emptyStr
;
926 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
927 ("GtkIMModule(%p): OnCommitCompositionNative, aContext=%p, current context=%p, commitString=\"%s\"",
928 this, aContext
, GetContext(), commitString
));
930 // See bug 472635, we should do nothing if IM context doesn't match.
931 if (GetContext() != aContext
) {
932 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
933 (" FAILED, given context doesn't match, GetContext()=%p",
938 // If we are not in composition and committing with empty string,
939 // we need to do nothing because if we continued to handle this
940 // signal, we would dispatch compositionstart, text, compositionend
941 // events with empty string. Of course, they are unnecessary events
942 // for Web applications and our editor.
943 if (!IsComposing() && !commitString
[0]) {
947 if (ShouldIgnoreNativeCompositionEvent()) {
951 // If IME doesn't change their keyevent that generated this commit,
952 // don't send it through XIM - just send it as a normal key press
954 if (!IsComposing() && mProcessingKeyEvent
) {
955 char keyval_utf8
[8]; /* should have at least 6 bytes of space */
956 gint keyval_utf8_len
;
957 guint32 keyval_unicode
;
959 keyval_unicode
= gdk_keyval_to_unicode(mProcessingKeyEvent
->keyval
);
960 keyval_utf8_len
= g_unichar_to_utf8(keyval_unicode
, keyval_utf8
);
961 keyval_utf8
[keyval_utf8_len
] = '\0';
963 if (!strcmp(commitString
, keyval_utf8
)) {
964 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
965 ("GtkIMModule(%p): OnCommitCompositionNative, we'll send normal key event",
967 mFilterKeyEvent
= false;
972 NS_ConvertUTF8toUTF16
str(commitString
);
973 CommitCompositionBy(str
); // Be aware, widget can be gone
977 nsGtkIMModule::CommitCompositionBy(const nsAString
& aString
)
979 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
980 ("GtkIMModule(%p): CommitCompositionBy, aString=\"%s\", "
981 "mDispatchedCompositionString=\"%s\"",
982 this, NS_ConvertUTF16toUTF8(aString
).get(),
983 NS_ConvertUTF16toUTF8(mDispatchedCompositionString
).get()));
985 if (!DispatchTextEvent(aString
, true)) {
988 // We should dispatch the compositionend event here because some IMEs
989 // might not fire "preedit_end" native event.
990 return DispatchCompositionEnd(); // Be aware, widget can be gone
994 nsGtkIMModule::GetCompositionString(nsAString
&aCompositionString
)
996 gchar
*preedit_string
;
998 PangoAttrList
*feedback_list
;
999 gtk_im_context_get_preedit_string(GetContext(), &preedit_string
,
1000 &feedback_list
, &cursor_pos
);
1001 if (preedit_string
&& *preedit_string
) {
1002 CopyUTF8toUTF16(preedit_string
, aCompositionString
);
1004 aCompositionString
.Truncate();
1007 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1008 ("GtkIMModule(%p): GetCompositionString, result=\"%s\"",
1009 this, preedit_string
));
1011 pango_attr_list_unref(feedback_list
);
1012 g_free(preedit_string
);
1016 nsGtkIMModule::DispatchCompositionStart()
1018 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1019 ("GtkIMModule(%p): DispatchCompositionStart", this));
1021 if (IsComposing()) {
1022 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1023 (" WARNING, we're already in composition"));
1027 if (!mLastFocusedWindow
) {
1028 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1029 (" FAILED, there are no focused window in this module"));
1033 nsEventStatus status
;
1034 WidgetQueryContentEvent
selection(true, NS_QUERY_SELECTED_TEXT
,
1035 mLastFocusedWindow
);
1036 InitEvent(selection
);
1037 mLastFocusedWindow
->DispatchEvent(&selection
, status
);
1039 if (!selection
.mSucceeded
|| selection
.mReply
.mOffset
== UINT32_MAX
) {
1040 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1041 (" FAILED, cannot query the selection offset"));
1045 // XXX The composition start point might be changed by composition events
1046 // even though we strongly hope it doesn't happen.
1047 // Every composition event should have the start offset for the result
1048 // because it may high cost if we query the offset every time.
1049 mCompositionStart
= selection
.mReply
.mOffset
;
1050 mDispatchedCompositionString
.Truncate();
1052 if (mProcessingKeyEvent
&& !mKeyDownEventWasSent
&&
1053 mProcessingKeyEvent
->type
== GDK_KEY_PRESS
) {
1054 // If this composition is started by a native keydown event, we need to
1055 // dispatch our keydown event here (before composition start).
1056 nsCOMPtr
<nsIWidget
> kungFuDeathGrip
= mLastFocusedWindow
;
1058 mLastFocusedWindow
->DispatchKeyDownEvent(mProcessingKeyEvent
,
1060 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1061 (" keydown event is dispatched"));
1062 if (static_cast<nsWindow
*>(kungFuDeathGrip
.get())->IsDestroyed() ||
1063 kungFuDeathGrip
!= mLastFocusedWindow
) {
1064 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1065 (" NOTE, the focused widget was destroyed/changed by keydown event"));
1070 if (mIgnoreNativeCompositionEvent
) {
1071 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1072 (" WARNING, mIgnoreNativeCompositionEvent is already TRUE, but we forcedly reset"));
1073 mIgnoreNativeCompositionEvent
= false;
1076 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1077 (" mCompositionStart=%u", mCompositionStart
));
1078 mCompositionState
= eCompositionState_CompositionStartDispatched
;
1079 WidgetCompositionEvent
compEvent(true, NS_COMPOSITION_START
,
1080 mLastFocusedWindow
);
1081 InitEvent(compEvent
);
1082 nsCOMPtr
<nsIWidget
> kungFuDeathGrip
= mLastFocusedWindow
;
1083 mLastFocusedWindow
->DispatchEvent(&compEvent
, status
);
1084 if (static_cast<nsWindow
*>(kungFuDeathGrip
.get())->IsDestroyed() ||
1085 kungFuDeathGrip
!= mLastFocusedWindow
) {
1086 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1087 (" NOTE, the focused widget was destroyed/changed by compositionstart event"));
1095 nsGtkIMModule::DispatchCompositionEnd()
1097 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1098 ("GtkIMModule(%p): DispatchCompositionEnd, "
1099 "mDispatchedCompositionString=\"%s\"",
1100 this, NS_ConvertUTF16toUTF8(mDispatchedCompositionString
).get()));
1102 if (!IsComposing()) {
1103 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1104 (" WARNING, we have alrady finished the composition"));
1108 if (!mLastFocusedWindow
) {
1109 mDispatchedCompositionString
.Truncate();
1110 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1111 (" FAILED, there are no focused window in this module"));
1115 WidgetCompositionEvent
compEvent(true, NS_COMPOSITION_END
,
1116 mLastFocusedWindow
);
1117 InitEvent(compEvent
);
1118 compEvent
.data
= mDispatchedCompositionString
;
1119 nsEventStatus status
;
1120 nsCOMPtr
<nsIWidget
> kungFuDeathGrip
= mLastFocusedWindow
;
1121 mLastFocusedWindow
->DispatchEvent(&compEvent
, status
);
1122 mCompositionState
= eCompositionState_NotComposing
;
1123 mCompositionStart
= UINT32_MAX
;
1124 mCompositionTargetOffset
= UINT32_MAX
;
1125 mDispatchedCompositionString
.Truncate();
1126 if (static_cast<nsWindow
*>(kungFuDeathGrip
.get())->IsDestroyed() ||
1127 kungFuDeathGrip
!= mLastFocusedWindow
) {
1128 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1129 (" NOTE, the focused widget was destroyed/changed by compositionend event"));
1137 nsGtkIMModule::DispatchTextEvent(const nsAString
&aCompositionString
,
1140 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1141 ("GtkIMModule(%p): DispatchTextEvent, aIsCommit=%s",
1142 this, aIsCommit
? "TRUE" : "FALSE"));
1144 if (!mLastFocusedWindow
) {
1145 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1146 (" FAILED, there are no focused window in this module"));
1150 if (!IsComposing()) {
1151 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1152 (" The composition wasn't started, force starting..."));
1153 nsCOMPtr
<nsIWidget
> kungFuDeathGrip
= mLastFocusedWindow
;
1154 if (!DispatchCompositionStart()) {
1159 nsEventStatus status
;
1160 nsRefPtr
<nsWindow
> lastFocusedWindow
= mLastFocusedWindow
;
1162 if (aCompositionString
!= mDispatchedCompositionString
) {
1163 WidgetCompositionEvent
compositionUpdate(true, NS_COMPOSITION_UPDATE
,
1164 mLastFocusedWindow
);
1165 InitEvent(compositionUpdate
);
1166 compositionUpdate
.data
= aCompositionString
;
1167 mDispatchedCompositionString
= aCompositionString
;
1168 mLastFocusedWindow
->DispatchEvent(&compositionUpdate
, status
);
1169 if (lastFocusedWindow
->IsDestroyed() ||
1170 lastFocusedWindow
!= mLastFocusedWindow
) {
1171 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1172 (" NOTE, the focused widget was destroyed/changed by compositionupdate"));
1177 // Store the selected string which will be removed by following text event.
1178 if (mCompositionState
== eCompositionState_CompositionStartDispatched
) {
1179 // XXX We should assume, for now, any web applications don't change
1180 // selection at handling this text event.
1181 WidgetQueryContentEvent
querySelectedTextEvent(true,
1182 NS_QUERY_SELECTED_TEXT
,
1183 mLastFocusedWindow
);
1184 mLastFocusedWindow
->DispatchEvent(&querySelectedTextEvent
, status
);
1185 if (querySelectedTextEvent
.mSucceeded
) {
1186 mSelectedString
= querySelectedTextEvent
.mReply
.mString
;
1187 mCompositionStart
= querySelectedTextEvent
.mReply
.mOffset
;
1191 WidgetTextEvent
textEvent(true, NS_TEXT_TEXT
, mLastFocusedWindow
);
1192 InitEvent(textEvent
);
1194 uint32_t targetOffset
= mCompositionStart
;
1197 // NOTE: SetTextRangeList() assumes that mDispatchedCompositionString
1198 // has been updated already.
1199 textEvent
.mRanges
= CreateTextRangeArray();
1200 targetOffset
+= textEvent
.mRanges
->TargetClauseOffset();
1203 textEvent
.theText
= mDispatchedCompositionString
.get();
1205 mCompositionState
= aIsCommit
?
1206 eCompositionState_CommitTextEventDispatched
:
1207 eCompositionState_TextEventDispatched
;
1209 mLastFocusedWindow
->DispatchEvent(&textEvent
, status
);
1210 if (lastFocusedWindow
->IsDestroyed() ||
1211 lastFocusedWindow
!= mLastFocusedWindow
) {
1212 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1213 (" NOTE, the focused widget was destroyed/changed by text event"));
1217 // We cannot call SetCursorPosition for e10s-aware.
1218 // DispatchEvent is async on e10s, so composition rect isn't updated now
1220 mCompositionTargetOffset
= targetOffset
;
1225 already_AddRefed
<TextRangeArray
>
1226 nsGtkIMModule::CreateTextRangeArray()
1228 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1229 ("GtkIMModule(%p): CreateTextRangeArray", this));
1231 nsRefPtr
<TextRangeArray
> textRangeArray
= new TextRangeArray();
1233 gchar
*preedit_string
;
1235 PangoAttrList
*feedback_list
;
1236 gtk_im_context_get_preedit_string(GetContext(), &preedit_string
,
1237 &feedback_list
, &cursor_pos
);
1238 if (!preedit_string
|| !*preedit_string
) {
1239 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1240 (" preedit_string is null"));
1241 pango_attr_list_unref(feedback_list
);
1242 g_free(preedit_string
);
1243 return textRangeArray
.forget();
1246 PangoAttrIterator
* iter
;
1247 iter
= pango_attr_list_get_iterator(feedback_list
);
1249 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1250 (" FAILED, iterator couldn't be allocated"));
1251 pango_attr_list_unref(feedback_list
);
1252 g_free(preedit_string
);
1253 return textRangeArray
.forget();
1257 * Depend on gtk2's implementation on XIM support.
1258 * In aFeedback got from gtk2, there are only three types of data:
1259 * PANGO_ATTR_UNDERLINE, PANGO_ATTR_FOREGROUND, PANGO_ATTR_BACKGROUND.
1260 * Corresponding to XIMUnderline, XIMReverse.
1261 * Don't take PANGO_ATTR_BACKGROUND into account, since
1262 * PANGO_ATTR_BACKGROUND and PANGO_ATTR_FOREGROUND are always
1266 PangoAttribute
* attrUnderline
=
1267 pango_attr_iterator_get(iter
, PANGO_ATTR_UNDERLINE
);
1268 PangoAttribute
* attrForeground
=
1269 pango_attr_iterator_get(iter
, PANGO_ATTR_FOREGROUND
);
1270 if (!attrUnderline
&& !attrForeground
) {
1274 // Get the range of the current attribute(s)
1276 pango_attr_iterator_range(iter
, &start
, &end
);
1279 // XIMReverse | XIMUnderline
1280 if (attrUnderline
&& attrForeground
) {
1281 range
.mRangeType
= NS_TEXTRANGE_SELECTEDCONVERTEDTEXT
;
1284 else if (attrUnderline
) {
1285 range
.mRangeType
= NS_TEXTRANGE_CONVERTEDTEXT
;
1288 else if (attrForeground
) {
1289 range
.mRangeType
= NS_TEXTRANGE_SELECTEDRAWTEXT
;
1291 range
.mRangeType
= NS_TEXTRANGE_RAWINPUT
;
1294 gunichar2
* uniStr
= nullptr;
1296 range
.mStartOffset
= 0;
1299 uniStr
= g_utf8_to_utf16(preedit_string
, start
,
1300 nullptr, &uniStrLen
, nullptr);
1302 range
.mStartOffset
= uniStrLen
;
1309 uniStr
= g_utf8_to_utf16(preedit_string
+ start
, end
- start
,
1310 nullptr, &uniStrLen
, nullptr);
1312 range
.mEndOffset
= range
.mStartOffset
;
1314 range
.mEndOffset
= range
.mStartOffset
+ uniStrLen
;
1319 textRangeArray
->AppendElement(range
);
1321 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1322 (" mStartOffset=%u, mEndOffset=%u, mRangeType=%s",
1323 range
.mStartOffset
, range
.mEndOffset
,
1324 GetRangeTypeName(range
.mRangeType
)));
1325 } while (pango_attr_iterator_next(iter
));
1328 if (cursor_pos
< 0) {
1329 range
.mStartOffset
= 0;
1330 } else if (uint32_t(cursor_pos
) > mDispatchedCompositionString
.Length()) {
1331 range
.mStartOffset
= mDispatchedCompositionString
.Length();
1333 range
.mStartOffset
= uint32_t(cursor_pos
);
1335 range
.mEndOffset
= range
.mStartOffset
;
1336 range
.mRangeType
= NS_TEXTRANGE_CARETPOSITION
;
1337 textRangeArray
->AppendElement(range
);
1339 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1340 (" mStartOffset=%u, mEndOffset=%u, mRangeType=%s",
1341 range
.mStartOffset
, range
.mEndOffset
,
1342 GetRangeTypeName(range
.mRangeType
)));
1344 pango_attr_iterator_destroy(iter
);
1345 pango_attr_list_unref(feedback_list
);
1346 g_free(preedit_string
);
1348 return textRangeArray
.forget();
1352 nsGtkIMModule::SetCursorPosition(uint32_t aTargetOffset
)
1354 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1355 ("GtkIMModule(%p): SetCursorPosition, aTargetOffset=%u",
1356 this, aTargetOffset
));
1358 if (aTargetOffset
== UINT32_MAX
) {
1359 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1360 (" FAILED, aTargetOffset is wrong offset"));
1364 if (!mLastFocusedWindow
) {
1365 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1366 (" FAILED, there are no focused window"));
1370 GtkIMContext
*im
= GetContext();
1372 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1373 (" FAILED, there are no context"));
1377 WidgetQueryContentEvent
charRect(true, NS_QUERY_TEXT_RECT
,
1378 mLastFocusedWindow
);
1379 charRect
.InitForQueryTextRect(aTargetOffset
, 1);
1380 InitEvent(charRect
);
1381 nsEventStatus status
;
1382 mLastFocusedWindow
->DispatchEvent(&charRect
, status
);
1383 if (!charRect
.mSucceeded
) {
1384 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1385 (" FAILED, NS_QUERY_TEXT_RECT was failed"));
1388 nsWindow
* rootWindow
=
1389 static_cast<nsWindow
*>(mLastFocusedWindow
->GetTopLevelWidget());
1391 // Get the position of the rootWindow in screen.
1393 gdk_window_get_origin(rootWindow
->GetGdkWindow(), &rootX
, &rootY
);
1395 // Get the position of IM context owner window in screen.
1396 gint ownerX
, ownerY
;
1397 gdk_window_get_origin(mOwnerWindow
->GetGdkWindow(), &ownerX
, &ownerY
);
1399 // Compute the caret position in the IM owner window.
1401 area
.x
= charRect
.mReply
.mRect
.x
+ rootX
- ownerX
;
1402 area
.y
= charRect
.mReply
.mRect
.y
+ rootY
- ownerY
;
1404 area
.height
= charRect
.mReply
.mRect
.height
;
1406 gtk_im_context_set_cursor_location(im
, &area
);
1410 nsGtkIMModule::GetCurrentParagraph(nsAString
& aText
, uint32_t& aCursorPos
)
1412 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1413 ("GtkIMModule(%p): GetCurrentParagraph, mCompositionState=%s",
1414 this, GetCompositionStateName()));
1416 if (!mLastFocusedWindow
) {
1417 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1418 (" FAILED, there are no focused window in this module"));
1419 return NS_ERROR_NULL_POINTER
;
1422 nsEventStatus status
;
1424 uint32_t selOffset
= mCompositionStart
;
1425 uint32_t selLength
= mSelectedString
.Length();
1427 // If focused editor doesn't have composition string, we should use
1428 // current selection.
1429 if (!EditorHasCompositionString()) {
1430 // Query cursor position & selection
1431 WidgetQueryContentEvent
querySelectedTextEvent(true,
1432 NS_QUERY_SELECTED_TEXT
,
1433 mLastFocusedWindow
);
1434 mLastFocusedWindow
->DispatchEvent(&querySelectedTextEvent
, status
);
1435 NS_ENSURE_TRUE(querySelectedTextEvent
.mSucceeded
, NS_ERROR_FAILURE
);
1437 selOffset
= querySelectedTextEvent
.mReply
.mOffset
;
1438 selLength
= querySelectedTextEvent
.mReply
.mString
.Length();
1441 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1442 (" selOffset=%u, selLength=%u",
1443 selOffset
, selLength
));
1445 // XXX nsString::Find and nsString::RFind take int32_t for offset, so,
1446 // we cannot support this request when the current offset is larger
1448 if (selOffset
> INT32_MAX
|| selLength
> INT32_MAX
||
1449 selOffset
+ selLength
> INT32_MAX
) {
1450 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1451 (" FAILED, The selection is out of range"));
1452 return NS_ERROR_FAILURE
;
1455 // Get all text contents of the focused editor
1456 WidgetQueryContentEvent
queryTextContentEvent(true,
1457 NS_QUERY_TEXT_CONTENT
,
1458 mLastFocusedWindow
);
1459 queryTextContentEvent
.InitForQueryTextContent(0, UINT32_MAX
);
1460 mLastFocusedWindow
->DispatchEvent(&queryTextContentEvent
, status
);
1461 NS_ENSURE_TRUE(queryTextContentEvent
.mSucceeded
, NS_ERROR_FAILURE
);
1463 nsAutoString
textContent(queryTextContentEvent
.mReply
.mString
);
1464 if (selOffset
+ selLength
> textContent
.Length()) {
1465 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1466 (" FAILED, The selection is invalid, textContent.Length()=%u",
1467 textContent
.Length()));
1468 return NS_ERROR_FAILURE
;
1471 // Remove composing string and restore the selected string because
1472 // GtkEntry doesn't remove selected string until committing, however,
1473 // our editor does it. We should emulate the behavior for IME.
1474 if (EditorHasCompositionString() &&
1475 mDispatchedCompositionString
!= mSelectedString
) {
1476 textContent
.Replace(mCompositionStart
,
1477 mDispatchedCompositionString
.Length(), mSelectedString
);
1480 // Get only the focused paragraph, by looking for newlines
1481 int32_t parStart
= (selOffset
== 0) ? 0 :
1482 textContent
.RFind("\n", false, selOffset
- 1, -1) + 1;
1483 int32_t parEnd
= textContent
.Find("\n", false, selOffset
+ selLength
, -1);
1485 parEnd
= textContent
.Length();
1487 aText
= nsDependentSubstring(textContent
, parStart
, parEnd
- parStart
);
1488 aCursorPos
= selOffset
- uint32_t(parStart
);
1490 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1491 (" aText=%s, aText.Length()=%u, aCursorPos=%u",
1492 NS_ConvertUTF16toUTF8(aText
).get(),
1493 aText
.Length(), aCursorPos
));
1499 nsGtkIMModule::DeleteText(const int32_t aOffset
, const uint32_t aNChars
)
1501 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1502 ("GtkIMModule(%p): DeleteText, aOffset=%d, aNChars=%d, "
1503 "mCompositionState=%s",
1504 this, aOffset
, aNChars
, GetCompositionStateName()));
1506 if (!mLastFocusedWindow
) {
1507 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1508 (" FAILED, there are no focused window in this module"));
1509 return NS_ERROR_NULL_POINTER
;
1513 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1514 (" FAILED, aNChars must not be zero"));
1515 return NS_ERROR_INVALID_ARG
;
1518 nsRefPtr
<nsWindow
> lastFocusedWindow(mLastFocusedWindow
);
1519 nsEventStatus status
;
1521 // First, we should cancel current composition because editor cannot
1522 // handle changing selection and deleting text.
1524 bool wasComposing
= IsComposing();
1525 bool editorHadCompositionString
= EditorHasCompositionString();
1527 selOffset
= mCompositionStart
;
1528 if (editorHadCompositionString
&&
1529 !DispatchTextEvent(mSelectedString
, false)) {
1530 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1531 (" FAILED, quitting from DeletText"));
1532 return NS_ERROR_FAILURE
;
1534 if (!DispatchCompositionEnd()) {
1535 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1536 (" FAILED, quitting from DeletText"));
1537 return NS_ERROR_FAILURE
;
1540 // Query cursor position & selection
1541 WidgetQueryContentEvent
querySelectedTextEvent(true,
1542 NS_QUERY_SELECTED_TEXT
,
1543 mLastFocusedWindow
);
1544 lastFocusedWindow
->DispatchEvent(&querySelectedTextEvent
, status
);
1545 NS_ENSURE_TRUE(querySelectedTextEvent
.mSucceeded
, NS_ERROR_FAILURE
);
1547 selOffset
= querySelectedTextEvent
.mReply
.mOffset
;
1550 // Get all text contents of the focused editor
1551 WidgetQueryContentEvent
queryTextContentEvent(true,
1552 NS_QUERY_TEXT_CONTENT
,
1553 mLastFocusedWindow
);
1554 queryTextContentEvent
.InitForQueryTextContent(0, UINT32_MAX
);
1555 mLastFocusedWindow
->DispatchEvent(&queryTextContentEvent
, status
);
1556 NS_ENSURE_TRUE(queryTextContentEvent
.mSucceeded
, NS_ERROR_FAILURE
);
1557 if (queryTextContentEvent
.mReply
.mString
.IsEmpty()) {
1558 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1559 (" FAILED, there is no contents"));
1560 return NS_ERROR_FAILURE
;
1563 NS_ConvertUTF16toUTF8
utf8Str(
1564 nsDependentSubstring(queryTextContentEvent
.mReply
.mString
,
1566 glong offsetInUTF8Characters
=
1567 g_utf8_strlen(utf8Str
.get(), utf8Str
.Length()) + aOffset
;
1568 if (offsetInUTF8Characters
< 0) {
1569 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1570 (" FAILED, aOffset is too small for current cursor pos "
1571 "(computed offset: %d)",
1572 offsetInUTF8Characters
));
1573 return NS_ERROR_FAILURE
;
1577 nsDependentSubstring(queryTextContentEvent
.mReply
.mString
, selOffset
),
1579 glong countOfCharactersInUTF8
=
1580 g_utf8_strlen(utf8Str
.get(), utf8Str
.Length());
1581 glong endInUTF8Characters
=
1582 offsetInUTF8Characters
+ aNChars
;
1583 if (countOfCharactersInUTF8
< endInUTF8Characters
) {
1584 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1585 (" FAILED, aNChars is too large for current contents "
1586 "(content length: %d, computed end offset: %d)",
1587 countOfCharactersInUTF8
, endInUTF8Characters
));
1588 return NS_ERROR_FAILURE
;
1591 gchar
* charAtOffset
=
1592 g_utf8_offset_to_pointer(utf8Str
.get(), offsetInUTF8Characters
);
1594 g_utf8_offset_to_pointer(utf8Str
.get(), endInUTF8Characters
);
1596 // Set selection to delete
1597 WidgetSelectionEvent
selectionEvent(true, NS_SELECTION_SET
,
1598 mLastFocusedWindow
);
1600 nsDependentCSubstring
utf8StrBeforeOffset(utf8Str
, 0,
1601 charAtOffset
- utf8Str
.get());
1602 selectionEvent
.mOffset
=
1603 NS_ConvertUTF8toUTF16(utf8StrBeforeOffset
).Length();
1605 nsDependentCSubstring
utf8DeletingStr(utf8Str
,
1606 utf8StrBeforeOffset
.Length(),
1607 charAtEnd
- charAtOffset
);
1608 selectionEvent
.mLength
=
1609 NS_ConvertUTF8toUTF16(utf8DeletingStr
).Length();
1611 selectionEvent
.mReversed
= false;
1612 selectionEvent
.mExpandToClusterBoundary
= false;
1613 lastFocusedWindow
->DispatchEvent(&selectionEvent
, status
);
1615 if (!selectionEvent
.mSucceeded
||
1616 lastFocusedWindow
!= mLastFocusedWindow
||
1617 lastFocusedWindow
->Destroyed()) {
1618 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1619 (" FAILED, setting selection caused focus change "
1620 "or window destroyed"));
1621 return NS_ERROR_FAILURE
;
1624 // Delete the selection
1625 WidgetContentCommandEvent
contentCommandEvent(true,
1626 NS_CONTENT_COMMAND_DELETE
,
1627 mLastFocusedWindow
);
1628 mLastFocusedWindow
->DispatchEvent(&contentCommandEvent
, status
);
1630 if (!contentCommandEvent
.mSucceeded
||
1631 lastFocusedWindow
!= mLastFocusedWindow
||
1632 lastFocusedWindow
->Destroyed()) {
1633 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1634 (" FAILED, deleting the selection caused focus change "
1635 "or window destroyed"));
1636 return NS_ERROR_FAILURE
;
1639 if (!wasComposing
) {
1643 // Restore the composition at new caret position.
1644 if (!DispatchCompositionStart()) {
1645 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1646 (" FAILED, resterting composition start"));
1647 return NS_ERROR_FAILURE
;
1650 if (!editorHadCompositionString
) {
1654 nsAutoString compositionString
;
1655 GetCompositionString(compositionString
);
1656 if (!DispatchTextEvent(compositionString
, true)) {
1657 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1658 (" FAILED, restoring composition string"));
1659 return NS_ERROR_FAILURE
;
1666 nsGtkIMModule::InitEvent(WidgetGUIEvent
& aEvent
)
1668 aEvent
.time
= PR_Now() / 1000;
1672 nsGtkIMModule::ShouldIgnoreNativeCompositionEvent()
1674 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1675 ("GtkIMModule(%p): ShouldIgnoreNativeCompositionEvent, mLastFocusedWindow=%p, mIgnoreNativeCompositionEvent=%s",
1676 this, mLastFocusedWindow
,
1677 mIgnoreNativeCompositionEvent
? "YES" : "NO"));
1679 if (!mLastFocusedWindow
) {
1680 return true; // cannot continue
1683 return mIgnoreNativeCompositionEvent
;