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 nsGtkIMModule
* nsGtkIMModule::sLastFocusedModule
= nullptr;
65 nsGtkIMModule::nsGtkIMModule(nsWindow
* aOwnerWindow
) :
66 mOwnerWindow(aOwnerWindow
), mLastFocusedWindow(nullptr),
68 mDummyContext(nullptr),
69 mCompositionStart(UINT32_MAX
), mProcessingKeyEvent(nullptr),
70 mCompositionState(eCompositionState_NotComposing
),
71 mIsIMFocused(false), mIgnoreNativeCompositionEvent(false)
75 gGtkIMLog
= PR_NewLogModule("nsGtkIMModuleWidgets");
84 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
85 ("GtkIMModule(%p): Init, mOwnerWindow=%p",
88 MozContainer
* container
= mOwnerWindow
->GetMozContainer();
89 NS_PRECONDITION(container
, "container is null");
90 GdkWindow
* gdkWindow
= gtk_widget_get_window(GTK_WIDGET(container
));
92 // NOTE: gtk_im_*_new() abort (kill) the whole process when it fails.
93 // So, we don't need to check the result.
96 mContext
= gtk_im_multicontext_new();
97 gtk_im_context_set_client_window(mContext
, gdkWindow
);
98 g_signal_connect(mContext
, "preedit_changed",
99 G_CALLBACK(nsGtkIMModule::OnChangeCompositionCallback
),
101 g_signal_connect(mContext
, "retrieve_surrounding",
102 G_CALLBACK(nsGtkIMModule::OnRetrieveSurroundingCallback
),
104 g_signal_connect(mContext
, "delete_surrounding",
105 G_CALLBACK(nsGtkIMModule::OnDeleteSurroundingCallback
),
107 g_signal_connect(mContext
, "commit",
108 G_CALLBACK(nsGtkIMModule::OnCommitCompositionCallback
),
110 g_signal_connect(mContext
, "preedit_start",
111 G_CALLBACK(nsGtkIMModule::OnStartCompositionCallback
),
113 g_signal_connect(mContext
, "preedit_end",
114 G_CALLBACK(nsGtkIMModule::OnEndCompositionCallback
),
118 mDummyContext
= gtk_im_multicontext_new();
119 gtk_im_context_set_client_window(mDummyContext
, gdkWindow
);
122 nsGtkIMModule::~nsGtkIMModule()
124 if (this == sLastFocusedModule
) {
125 sLastFocusedModule
= nullptr;
127 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
128 ("GtkIMModule(%p) was gone", this));
132 nsGtkIMModule::OnDestroyWindow(nsWindow
* aWindow
)
134 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
135 ("GtkIMModule(%p): OnDestroyWindow, aWindow=%p, mLastFocusedWindow=%p, mOwnerWindow=%p, mLastFocusedModule=%p",
136 this, aWindow
, mLastFocusedWindow
, mOwnerWindow
, sLastFocusedModule
));
138 NS_PRECONDITION(aWindow
, "aWindow must not be null");
140 if (mLastFocusedWindow
== aWindow
) {
141 CancelIMEComposition(aWindow
);
145 mLastFocusedWindow
= nullptr;
148 if (mOwnerWindow
!= aWindow
) {
152 if (sLastFocusedModule
== this) {
153 sLastFocusedModule
= nullptr;
158 * The given window is the owner of this, so, we must release the
159 * contexts now. But that might be referred from other nsWindows
160 * (they are children of this. But we don't know why there are the
161 * cases). So, we need to clear the pointers that refers to contexts
162 * and this if the other referrers are still alive. See bug 349727.
165 PrepareToDestroyContext(mContext
);
166 gtk_im_context_set_client_window(mContext
, nullptr);
167 g_object_unref(mContext
);
172 // mContext and mDummyContext have the same slaveType and signal_data
173 // so no need for another workaround_gtk_im_display_closed.
174 gtk_im_context_set_client_window(mDummyContext
, nullptr);
175 g_object_unref(mDummyContext
);
176 mDummyContext
= nullptr;
179 mOwnerWindow
= nullptr;
180 mLastFocusedWindow
= nullptr;
181 mInputContext
.mIMEState
.mEnabled
= IMEState::DISABLED
;
183 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
184 (" SUCCEEDED, Completely destroyed"));
187 // Work around gtk bug http://bugzilla.gnome.org/show_bug.cgi?id=483223:
188 // (and the similar issue of GTK+ IIIM)
189 // The GTK+ XIM and IIIM modules register handlers for the "closed" signal
190 // on the display, but:
191 // * The signal handlers are not disconnected when the module is unloaded.
193 // The GTK+ XIM module has another problem:
194 // * When the signal handler is run (with the module loaded) it tries
195 // XFree (and fails) on a pointer that did not come from Xmalloc.
197 // To prevent these modules from being unloaded, use static variables to
198 // hold ref of GtkIMContext class.
199 // For GTK+ XIM module, to prevent the signal handler from being run,
200 // find the signal handlers and remove them.
202 // GtkIMContextXIMs share XOpenIM connections and display closed signal
203 // handlers (where possible).
206 nsGtkIMModule::PrepareToDestroyContext(GtkIMContext
*aContext
)
208 MozContainer
* container
= mOwnerWindow
->GetMozContainer();
209 NS_PRECONDITION(container
, "The container of the window is null");
211 GtkIMMulticontext
*multicontext
= GTK_IM_MULTICONTEXT(aContext
);
212 #if (MOZ_WIDGET_GTK == 2)
213 GtkIMContext
*slave
= multicontext
->slave
;
215 GtkIMContext
*slave
= nullptr; //TODO GTK3
221 GType slaveType
= G_TYPE_FROM_INSTANCE(slave
);
222 const gchar
*im_type_name
= g_type_name(slaveType
);
223 if (strcmp(im_type_name
, "GtkIMContextXIM") == 0) {
224 if (gtk_check_version(2, 12, 1) == nullptr) {
225 return; // gtk bug has been fixed
228 struct GtkIMContextXIM
231 gpointer private_data
;
235 gpointer signal_data
=
236 reinterpret_cast<GtkIMContextXIM
*>(slave
)->private_data
;
241 g_signal_handlers_disconnect_matched(
242 gtk_widget_get_display(GTK_WIDGET(container
)),
243 G_SIGNAL_MATCH_DATA
, 0, 0, nullptr, nullptr, signal_data
);
245 // Add a reference to prevent the XIM module from being unloaded
246 // and reloaded: each time the module is loaded and used, it
247 // opens (and doesn't close) new XOpenIM connections.
248 static gpointer gtk_xim_context_class
=
249 g_type_class_ref(slaveType
);
250 // Mute unused variable warning:
251 (void)gtk_xim_context_class
;
252 } else if (strcmp(im_type_name
, "GtkIMContextIIIM") == 0) {
253 // Add a reference to prevent the IIIM module from being unloaded
254 static gpointer gtk_iiim_context_class
=
255 g_type_class_ref(slaveType
);
256 // Mute unused variable warning:
257 (void)gtk_iiim_context_class
;
262 nsGtkIMModule::OnFocusWindow(nsWindow
* aWindow
)
264 if (MOZ_UNLIKELY(IsDestroyed())) {
268 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
269 ("GtkIMModule(%p): OnFocusWindow, aWindow=%p, mLastFocusedWindow=%p",
270 this, aWindow
, mLastFocusedWindow
));
271 mLastFocusedWindow
= aWindow
;
276 nsGtkIMModule::OnBlurWindow(nsWindow
* aWindow
)
278 if (MOZ_UNLIKELY(IsDestroyed())) {
282 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
283 ("GtkIMModule(%p): OnBlurWindow, aWindow=%p, mLastFocusedWindow=%p, mIsIMFocused=%s",
284 this, aWindow
, mLastFocusedWindow
, mIsIMFocused
? "YES" : "NO"));
286 if (!mIsIMFocused
|| mLastFocusedWindow
!= aWindow
) {
294 nsGtkIMModule::OnKeyEvent(nsWindow
* aCaller
, GdkEventKey
* aEvent
,
295 bool aKeyDownEventWasSent
/* = false */)
297 NS_PRECONDITION(aEvent
, "aEvent must be non-null");
299 if (!IsEditable() || MOZ_UNLIKELY(IsDestroyed())) {
303 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
304 ("GtkIMModule(%p): OnKeyEvent, aCaller=%p, aKeyDownEventWasSent=%s",
305 this, aCaller
, aKeyDownEventWasSent
? "TRUE" : "FALSE"));
306 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
307 (" aEvent: type=%s, keyval=%s, unicode=0x%X",
308 aEvent
->type
== GDK_KEY_PRESS
? "GDK_KEY_PRESS" :
309 aEvent
->type
== GDK_KEY_RELEASE
? "GDK_KEY_RELEASE" : "Unknown",
310 gdk_keyval_name(aEvent
->keyval
),
311 gdk_keyval_to_unicode(aEvent
->keyval
)));
313 if (aCaller
!= mLastFocusedWindow
) {
314 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
315 (" FAILED, the caller isn't focused window, mLastFocusedWindow=%p",
316 mLastFocusedWindow
));
320 GtkIMContext
* im
= GetContext();
321 if (MOZ_UNLIKELY(!im
)) {
322 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
323 (" FAILED, there are no context"));
327 mKeyDownEventWasSent
= aKeyDownEventWasSent
;
328 mFilterKeyEvent
= true;
329 mProcessingKeyEvent
= aEvent
;
330 gboolean isFiltered
= gtk_im_context_filter_keypress(im
, aEvent
);
331 mProcessingKeyEvent
= nullptr;
333 // We filter the key event if the event was not committed (because
334 // it's probably part of a composition) or if the key event was
335 // committed _and_ changed. This way we still let key press
336 // events go through as simple key press events instead of
337 // composed characters.
338 bool filterThisEvent
= isFiltered
&& mFilterKeyEvent
;
340 if (IsComposing() && !isFiltered
) {
341 if (aEvent
->type
== GDK_KEY_PRESS
) {
342 if (!mDispatchedCompositionString
.IsEmpty()) {
343 // If there is composition string, we shouldn't dispatch
344 // any keydown events during composition.
345 filterThisEvent
= true;
347 // A Hangul input engine for SCIM doesn't emit preedit_end
348 // signal even when composition string becomes empty. On the
349 // other hand, we should allow to make composition with empty
350 // string for other languages because there *might* be such
351 // IM. For compromising this issue, we should dispatch
352 // compositionend event, however, we don't need to reset IM
354 CommitCompositionBy(EmptyString());
355 filterThisEvent
= false;
358 // Key release event may not be consumed by IM, however, we
359 // shouldn't dispatch any keyup event during composition.
360 filterThisEvent
= true;
364 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
365 (" filterThisEvent=%s (isFiltered=%s, mFilterKeyEvent=%s)",
366 filterThisEvent
? "TRUE" : "FALSE", isFiltered
? "YES" : "NO",
367 mFilterKeyEvent
? "YES" : "NO"));
369 return filterThisEvent
;
373 nsGtkIMModule::OnFocusChangeInGecko(bool aFocus
)
375 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
376 ("GtkIMModule(%p): OnFocusChangeInGecko, aFocus=%s, "
377 "mCompositionState=%s, mIsIMFocused=%s, "
378 "mIgnoreNativeCompositionEvent=%s",
379 this, aFocus
? "YES" : "NO", GetCompositionStateName(),
380 mIsIMFocused
? "YES" : "NO",
381 mIgnoreNativeCompositionEvent
? "YES" : "NO"));
383 // We shouldn't carry over the removed string to another editor.
384 mSelectedString
.Truncate();
387 // If we failed to commit forcedely in previous focused editor,
388 // we should reopen the gate for native signals in new focused editor.
389 mIgnoreNativeCompositionEvent
= false;
394 nsGtkIMModule::ResetIME()
396 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
397 ("GtkIMModule(%p): ResetIME, mCompositionState=%s, mIsIMFocused=%s",
398 this, GetCompositionStateName(), mIsIMFocused
? "YES" : "NO"));
400 GtkIMContext
*im
= GetContext();
401 if (MOZ_UNLIKELY(!im
)) {
402 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
403 (" FAILED, there are no context"));
407 mIgnoreNativeCompositionEvent
= true;
408 gtk_im_context_reset(im
);
412 nsGtkIMModule::CommitIMEComposition(nsWindow
* aCaller
)
414 if (MOZ_UNLIKELY(IsDestroyed())) {
418 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
419 ("GtkIMModule(%p): CommitIMEComposition, aCaller=%p, "
420 "mCompositionState=%s",
421 this, aCaller
, GetCompositionStateName()));
423 if (aCaller
!= mLastFocusedWindow
) {
424 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
425 (" WARNING: the caller isn't focused window, mLastFocusedWindow=%p",
426 mLastFocusedWindow
));
430 if (!IsComposing()) {
434 // XXX We should commit composition ourselves temporary...
436 CommitCompositionBy(mDispatchedCompositionString
);
442 nsGtkIMModule::CancelIMEComposition(nsWindow
* aCaller
)
444 if (MOZ_UNLIKELY(IsDestroyed())) {
448 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
449 ("GtkIMModule(%p): CancelIMEComposition, aCaller=%p",
452 if (aCaller
!= mLastFocusedWindow
) {
453 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
454 (" FAILED, the caller isn't focused window, mLastFocusedWindow=%p",
455 mLastFocusedWindow
));
459 if (!IsComposing()) {
463 GtkIMContext
*im
= GetContext();
464 if (MOZ_UNLIKELY(!im
)) {
465 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
466 (" FAILED, there are no context"));
471 CommitCompositionBy(EmptyString());
477 nsGtkIMModule::SetInputContext(nsWindow
* aCaller
,
478 const InputContext
* aContext
,
479 const InputContextAction
* aAction
)
481 if (MOZ_UNLIKELY(IsDestroyed())) {
485 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
486 ("GtkIMModule(%p): SetInputContext, aCaller=%p, aState=%s mHTMLInputType=%s",
487 this, aCaller
, GetEnabledStateName(aContext
->mIMEState
.mEnabled
),
488 NS_ConvertUTF16toUTF8(aContext
->mHTMLInputType
).get()));
490 if (aCaller
!= mLastFocusedWindow
) {
491 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
492 (" FAILED, the caller isn't focused window, mLastFocusedWindow=%p",
493 mLastFocusedWindow
));
498 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
499 (" FAILED, there are no context"));
504 if (sLastFocusedModule
!= this) {
505 mInputContext
= *aContext
;
506 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
507 (" SUCCEEDED, but we're not active"));
511 bool changingEnabledState
=
512 aContext
->mIMEState
.mEnabled
!= mInputContext
.mIMEState
.mEnabled
;
514 // Release current IME focus if IME is enabled.
515 if (changingEnabledState
&& IsEditable()) {
516 CommitIMEComposition(mLastFocusedWindow
);
520 mInputContext
= *aContext
;
522 // Even when aState is not enabled state, we need to set IME focus.
523 // Because some IMs are updating the status bar of them at this time.
524 // Be aware, don't use aWindow here because this method shouldn't move
526 if (changingEnabledState
) {
532 nsGtkIMModule::GetInputContext()
534 mInputContext
.mIMEState
.mOpen
= IMEState::OPEN_STATE_NOT_SUPPORTED
;
535 return mInputContext
;
540 nsGtkIMModule::IsVirtualKeyboardOpened()
546 nsGtkIMModule::GetContext()
552 return mDummyContext
;
556 nsGtkIMModule::IsEnabled()
558 return mInputContext
.mIMEState
.mEnabled
== IMEState::ENABLED
||
559 mInputContext
.mIMEState
.mEnabled
== IMEState::PASSWORD
||
560 mInputContext
.mIMEState
.mEnabled
== IMEState::PLUGIN
;
564 nsGtkIMModule::IsEditable()
566 return mInputContext
.mIMEState
.mEnabled
== IMEState::ENABLED
||
567 mInputContext
.mIMEState
.mEnabled
== IMEState::PLUGIN
||
568 mInputContext
.mIMEState
.mEnabled
== IMEState::PASSWORD
;
572 nsGtkIMModule::Focus()
574 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
575 ("GtkIMModule(%p): Focus, sLastFocusedModule=%p",
576 this, sLastFocusedModule
));
579 NS_ASSERTION(sLastFocusedModule
== this,
580 "We're not active, but the IM was focused?");
584 GtkIMContext
*im
= GetContext();
586 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
587 (" FAILED, there are no context"));
591 if (sLastFocusedModule
&& sLastFocusedModule
!= this) {
592 sLastFocusedModule
->Blur();
595 sLastFocusedModule
= this;
597 gtk_im_context_focus_in(im
);
601 // We should release IME focus for uim and scim.
602 // These IMs are using snooper that is released at losing focus.
608 nsGtkIMModule::Blur()
610 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
611 ("GtkIMModule(%p): Blur, mIsIMFocused=%s",
612 this, mIsIMFocused
? "YES" : "NO"));
618 GtkIMContext
*im
= GetContext();
620 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
621 (" FAILED, there are no context"));
625 gtk_im_context_focus_out(im
);
626 mIsIMFocused
= false;
631 nsGtkIMModule::OnStartCompositionCallback(GtkIMContext
*aContext
,
632 nsGtkIMModule
* aModule
)
634 aModule
->OnStartCompositionNative(aContext
);
638 nsGtkIMModule::OnStartCompositionNative(GtkIMContext
*aContext
)
640 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
641 ("GtkIMModule(%p): OnStartCompositionNative, aContext=%p",
644 // See bug 472635, we should do nothing if IM context doesn't match.
645 if (GetContext() != aContext
) {
646 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
647 (" FAILED, given context doesn't match, GetContext()=%p",
652 if (!DispatchCompositionStart()) {
655 SetCursorPosition(mCompositionStart
);
660 nsGtkIMModule::OnEndCompositionCallback(GtkIMContext
*aContext
,
661 nsGtkIMModule
* aModule
)
663 aModule
->OnEndCompositionNative(aContext
);
667 nsGtkIMModule::OnEndCompositionNative(GtkIMContext
*aContext
)
669 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
670 ("GtkIMModule(%p): OnEndCompositionNative, aContext=%p",
673 // See bug 472635, we should do nothing if IM context doesn't match.
674 if (GetContext() != aContext
) {
675 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
676 (" FAILED, given context doesn't match, GetContext()=%p",
681 bool shouldIgnoreThisEvent
= ShouldIgnoreNativeCompositionEvent();
683 // Finish the cancelling mode here rather than DispatchCompositionEnd()
684 // because DispatchCompositionEnd() is called ourselves when we need to
685 // commit the composition string *before* the focus moves completely.
686 // Note that the native commit can be fired *after* ResetIME().
687 mIgnoreNativeCompositionEvent
= false;
689 if (!IsComposing() || shouldIgnoreThisEvent
) {
690 // If we already handled the commit event, we should do nothing here.
694 // Be aware, widget can be gone
695 DispatchCompositionEnd();
700 nsGtkIMModule::OnChangeCompositionCallback(GtkIMContext
*aContext
,
701 nsGtkIMModule
* aModule
)
703 aModule
->OnChangeCompositionNative(aContext
);
707 nsGtkIMModule::OnChangeCompositionNative(GtkIMContext
*aContext
)
709 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
710 ("GtkIMModule(%p): OnChangeCompositionNative, aContext=%p",
713 // See bug 472635, we should do nothing if IM context doesn't match.
714 if (GetContext() != aContext
) {
715 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
716 (" FAILED, given context doesn't match, GetContext()=%p",
721 if (ShouldIgnoreNativeCompositionEvent()) {
725 nsAutoString compositionString
;
726 GetCompositionString(compositionString
);
727 if (!IsComposing() && compositionString
.IsEmpty()) {
728 mDispatchedCompositionString
.Truncate();
729 return; // Don't start the composition with empty string.
732 // Be aware, widget can be gone
733 DispatchTextEvent(compositionString
, false);
738 nsGtkIMModule::OnRetrieveSurroundingCallback(GtkIMContext
*aContext
,
739 nsGtkIMModule
*aModule
)
741 return aModule
->OnRetrieveSurroundingNative(aContext
);
745 nsGtkIMModule::OnRetrieveSurroundingNative(GtkIMContext
*aContext
)
747 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
748 ("GtkIMModule(%p): OnRetrieveSurroundingNative, aContext=%p, current context=%p",
749 this, aContext
, GetContext()));
751 if (GetContext() != aContext
) {
752 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
753 (" FAILED, given context doesn't match, GetContext()=%p",
760 if (NS_FAILED(GetCurrentParagraph(uniStr
, cursorPos
))) {
764 NS_ConvertUTF16toUTF8
utf8Str(nsDependentSubstring(uniStr
, 0, cursorPos
));
765 uint32_t cursorPosInUTF8
= utf8Str
.Length();
766 AppendUTF16toUTF8(nsDependentSubstring(uniStr
, cursorPos
), utf8Str
);
767 gtk_im_context_set_surrounding(aContext
, utf8Str
.get(), utf8Str
.Length(),
774 nsGtkIMModule::OnDeleteSurroundingCallback(GtkIMContext
*aContext
,
777 nsGtkIMModule
*aModule
)
779 return aModule
->OnDeleteSurroundingNative(aContext
, aOffset
, aNChars
);
783 nsGtkIMModule::OnDeleteSurroundingNative(GtkIMContext
*aContext
,
787 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
788 ("GtkIMModule(%p): OnDeleteSurroundingNative, aContext=%p, current context=%p",
789 this, aContext
, GetContext()));
791 if (GetContext() != aContext
) {
792 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
793 (" FAILED, given context doesn't match, GetContext()=%p",
798 if (NS_SUCCEEDED(DeleteText(aOffset
, (uint32_t)aNChars
))) {
803 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
804 (" FAILED, cannot delete text"));
810 nsGtkIMModule::OnCommitCompositionCallback(GtkIMContext
*aContext
,
811 const gchar
*aString
,
812 nsGtkIMModule
* aModule
)
814 aModule
->OnCommitCompositionNative(aContext
, aString
);
818 nsGtkIMModule::OnCommitCompositionNative(GtkIMContext
*aContext
,
819 const gchar
*aUTF8Char
)
821 const gchar emptyStr
= 0;
822 const gchar
*commitString
= aUTF8Char
? aUTF8Char
: &emptyStr
;
824 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
825 ("GtkIMModule(%p): OnCommitCompositionNative, aContext=%p, current context=%p, commitString=\"%s\"",
826 this, aContext
, GetContext(), commitString
));
828 // See bug 472635, we should do nothing if IM context doesn't match.
829 if (GetContext() != aContext
) {
830 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
831 (" FAILED, given context doesn't match, GetContext()=%p",
836 // If we are not in composition and committing with empty string,
837 // we need to do nothing because if we continued to handle this
838 // signal, we would dispatch compositionstart, text, compositionend
839 // events with empty string. Of course, they are unnecessary events
840 // for Web applications and our editor.
841 if (!IsComposing() && !commitString
[0]) {
845 if (ShouldIgnoreNativeCompositionEvent()) {
849 // If IME doesn't change their keyevent that generated this commit,
850 // don't send it through XIM - just send it as a normal key press
852 if (!IsComposing() && mProcessingKeyEvent
) {
853 char keyval_utf8
[8]; /* should have at least 6 bytes of space */
854 gint keyval_utf8_len
;
855 guint32 keyval_unicode
;
857 keyval_unicode
= gdk_keyval_to_unicode(mProcessingKeyEvent
->keyval
);
858 keyval_utf8_len
= g_unichar_to_utf8(keyval_unicode
, keyval_utf8
);
859 keyval_utf8
[keyval_utf8_len
] = '\0';
861 if (!strcmp(commitString
, keyval_utf8
)) {
862 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
863 ("GtkIMModule(%p): OnCommitCompositionNative, we'll send normal key event",
865 mFilterKeyEvent
= false;
870 NS_ConvertUTF8toUTF16
str(commitString
);
871 CommitCompositionBy(str
); // Be aware, widget can be gone
875 nsGtkIMModule::CommitCompositionBy(const nsAString
& aString
)
877 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
878 ("GtkIMModule(%p): CommitCompositionBy, aString=\"%s\", "
879 "mDispatchedCompositionString=\"%s\"",
880 this, NS_ConvertUTF16toUTF8(aString
).get(),
881 NS_ConvertUTF16toUTF8(mDispatchedCompositionString
).get()));
883 if (!DispatchTextEvent(aString
, true)) {
886 // We should dispatch the compositionend event here because some IMEs
887 // might not fire "preedit_end" native event.
888 return DispatchCompositionEnd(); // Be aware, widget can be gone
892 nsGtkIMModule::GetCompositionString(nsAString
&aCompositionString
)
894 gchar
*preedit_string
;
896 PangoAttrList
*feedback_list
;
897 gtk_im_context_get_preedit_string(GetContext(), &preedit_string
,
898 &feedback_list
, &cursor_pos
);
899 if (preedit_string
&& *preedit_string
) {
900 CopyUTF8toUTF16(preedit_string
, aCompositionString
);
902 aCompositionString
.Truncate();
905 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
906 ("GtkIMModule(%p): GetCompositionString, result=\"%s\"",
907 this, preedit_string
));
909 pango_attr_list_unref(feedback_list
);
910 g_free(preedit_string
);
914 nsGtkIMModule::DispatchCompositionStart()
916 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
917 ("GtkIMModule(%p): DispatchCompositionStart", this));
920 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
921 (" WARNING, we're already in composition"));
925 if (!mLastFocusedWindow
) {
926 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
927 (" FAILED, there are no focused window in this module"));
931 nsEventStatus status
;
932 WidgetQueryContentEvent
selection(true, NS_QUERY_SELECTED_TEXT
,
934 InitEvent(selection
);
935 mLastFocusedWindow
->DispatchEvent(&selection
, status
);
937 if (!selection
.mSucceeded
|| selection
.mReply
.mOffset
== UINT32_MAX
) {
938 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
939 (" FAILED, cannot query the selection offset"));
943 // XXX The composition start point might be changed by composition events
944 // even though we strongly hope it doesn't happen.
945 // Every composition event should have the start offset for the result
946 // because it may high cost if we query the offset every time.
947 mCompositionStart
= selection
.mReply
.mOffset
;
948 mDispatchedCompositionString
.Truncate();
950 if (mProcessingKeyEvent
&& !mKeyDownEventWasSent
&&
951 mProcessingKeyEvent
->type
== GDK_KEY_PRESS
) {
952 // If this composition is started by a native keydown event, we need to
953 // dispatch our keydown event here (before composition start).
954 nsCOMPtr
<nsIWidget
> kungFuDeathGrip
= mLastFocusedWindow
;
956 mLastFocusedWindow
->DispatchKeyDownEvent(mProcessingKeyEvent
,
958 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
959 (" keydown event is dispatched"));
960 if (static_cast<nsWindow
*>(kungFuDeathGrip
.get())->IsDestroyed() ||
961 kungFuDeathGrip
!= mLastFocusedWindow
) {
962 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
963 (" NOTE, the focused widget was destroyed/changed by keydown event"));
968 if (mIgnoreNativeCompositionEvent
) {
969 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
970 (" WARNING, mIgnoreNativeCompositionEvent is already TRUE, but we forcedly reset"));
971 mIgnoreNativeCompositionEvent
= false;
974 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
975 (" mCompositionStart=%u", mCompositionStart
));
976 mCompositionState
= eCompositionState_CompositionStartDispatched
;
977 WidgetCompositionEvent
compEvent(true, NS_COMPOSITION_START
,
979 InitEvent(compEvent
);
980 nsCOMPtr
<nsIWidget
> kungFuDeathGrip
= mLastFocusedWindow
;
981 mLastFocusedWindow
->DispatchEvent(&compEvent
, status
);
982 if (static_cast<nsWindow
*>(kungFuDeathGrip
.get())->IsDestroyed() ||
983 kungFuDeathGrip
!= mLastFocusedWindow
) {
984 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
985 (" NOTE, the focused widget was destroyed/changed by compositionstart event"));
993 nsGtkIMModule::DispatchCompositionEnd()
995 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
996 ("GtkIMModule(%p): DispatchCompositionEnd, "
997 "mDispatchedCompositionString=\"%s\"",
998 this, NS_ConvertUTF16toUTF8(mDispatchedCompositionString
).get()));
1000 if (!IsComposing()) {
1001 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1002 (" WARNING, we have alrady finished the composition"));
1006 if (!mLastFocusedWindow
) {
1007 mDispatchedCompositionString
.Truncate();
1008 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1009 (" FAILED, there are no focused window in this module"));
1013 WidgetCompositionEvent
compEvent(true, NS_COMPOSITION_END
,
1014 mLastFocusedWindow
);
1015 InitEvent(compEvent
);
1016 compEvent
.data
= mDispatchedCompositionString
;
1017 nsEventStatus status
;
1018 nsCOMPtr
<nsIWidget
> kungFuDeathGrip
= mLastFocusedWindow
;
1019 mLastFocusedWindow
->DispatchEvent(&compEvent
, status
);
1020 mCompositionState
= eCompositionState_NotComposing
;
1021 mCompositionStart
= UINT32_MAX
;
1022 mDispatchedCompositionString
.Truncate();
1023 if (static_cast<nsWindow
*>(kungFuDeathGrip
.get())->IsDestroyed() ||
1024 kungFuDeathGrip
!= mLastFocusedWindow
) {
1025 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1026 (" NOTE, the focused widget was destroyed/changed by compositionend event"));
1034 nsGtkIMModule::DispatchTextEvent(const nsAString
&aCompositionString
,
1037 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1038 ("GtkIMModule(%p): DispatchTextEvent, aIsCommit=%s",
1039 this, aIsCommit
? "TRUE" : "FALSE"));
1041 if (!mLastFocusedWindow
) {
1042 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1043 (" FAILED, there are no focused window in this module"));
1047 if (!IsComposing()) {
1048 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1049 (" The composition wasn't started, force starting..."));
1050 nsCOMPtr
<nsIWidget
> kungFuDeathGrip
= mLastFocusedWindow
;
1051 if (!DispatchCompositionStart()) {
1056 nsEventStatus status
;
1057 nsRefPtr
<nsWindow
> lastFocusedWindow
= mLastFocusedWindow
;
1059 if (aCompositionString
!= mDispatchedCompositionString
) {
1060 WidgetCompositionEvent
compositionUpdate(true, NS_COMPOSITION_UPDATE
,
1061 mLastFocusedWindow
);
1062 InitEvent(compositionUpdate
);
1063 compositionUpdate
.data
= aCompositionString
;
1064 mDispatchedCompositionString
= aCompositionString
;
1065 mLastFocusedWindow
->DispatchEvent(&compositionUpdate
, status
);
1066 if (lastFocusedWindow
->IsDestroyed() ||
1067 lastFocusedWindow
!= mLastFocusedWindow
) {
1068 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1069 (" NOTE, the focused widget was destroyed/changed by compositionupdate"));
1074 // Store the selected string which will be removed by following text event.
1075 if (mCompositionState
== eCompositionState_CompositionStartDispatched
) {
1076 // XXX We should assume, for now, any web applications don't change
1077 // selection at handling this text event.
1078 WidgetQueryContentEvent
querySelectedTextEvent(true,
1079 NS_QUERY_SELECTED_TEXT
,
1080 mLastFocusedWindow
);
1081 mLastFocusedWindow
->DispatchEvent(&querySelectedTextEvent
, status
);
1082 if (querySelectedTextEvent
.mSucceeded
) {
1083 mSelectedString
= querySelectedTextEvent
.mReply
.mString
;
1084 mCompositionStart
= querySelectedTextEvent
.mReply
.mOffset
;
1088 WidgetTextEvent
textEvent(true, NS_TEXT_TEXT
, mLastFocusedWindow
);
1089 InitEvent(textEvent
);
1091 uint32_t targetOffset
= mCompositionStart
;
1093 nsAutoTArray
<TextRange
, 4> textRanges
;
1095 // NOTE: SetTextRangeList() assumes that mDispatchedCompositionString
1096 // has been updated already.
1097 SetTextRangeList(textRanges
);
1098 for (uint32_t i
= 0; i
< textRanges
.Length(); i
++) {
1099 TextRange
& range
= textRanges
[i
];
1100 if (range
.mRangeType
== NS_TEXTRANGE_SELECTEDRAWTEXT
||
1101 range
.mRangeType
== NS_TEXTRANGE_SELECTEDCONVERTEDTEXT
) {
1102 targetOffset
+= range
.mStartOffset
;
1108 textEvent
.rangeCount
= textRanges
.Length();
1109 textEvent
.rangeArray
= textRanges
.Elements();
1110 textEvent
.theText
= mDispatchedCompositionString
.get();
1112 mCompositionState
= aIsCommit
?
1113 eCompositionState_CommitTextEventDispatched
:
1114 eCompositionState_TextEventDispatched
;
1116 mLastFocusedWindow
->DispatchEvent(&textEvent
, status
);
1117 if (lastFocusedWindow
->IsDestroyed() ||
1118 lastFocusedWindow
!= mLastFocusedWindow
) {
1119 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1120 (" NOTE, the focused widget was destroyed/changed by text event"));
1124 SetCursorPosition(targetOffset
);
1130 nsGtkIMModule::SetTextRangeList(nsTArray
<TextRange
> &aTextRangeList
)
1132 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1133 ("GtkIMModule(%p): SetTextRangeList", this));
1135 NS_PRECONDITION(aTextRangeList
.IsEmpty(), "aTextRangeList must be empty");
1137 gchar
*preedit_string
;
1139 PangoAttrList
*feedback_list
;
1140 gtk_im_context_get_preedit_string(GetContext(), &preedit_string
,
1141 &feedback_list
, &cursor_pos
);
1142 if (!preedit_string
|| !*preedit_string
) {
1143 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1144 (" preedit_string is null"));
1145 pango_attr_list_unref(feedback_list
);
1146 g_free(preedit_string
);
1150 PangoAttrIterator
* iter
;
1151 iter
= pango_attr_list_get_iterator(feedback_list
);
1153 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1154 (" FAILED, iterator couldn't be allocated"));
1155 pango_attr_list_unref(feedback_list
);
1156 g_free(preedit_string
);
1161 * Depend on gtk2's implementation on XIM support.
1162 * In aFeedback got from gtk2, there are only three types of data:
1163 * PANGO_ATTR_UNDERLINE, PANGO_ATTR_FOREGROUND, PANGO_ATTR_BACKGROUND.
1164 * Corresponding to XIMUnderline, XIMReverse.
1165 * Don't take PANGO_ATTR_BACKGROUND into account, since
1166 * PANGO_ATTR_BACKGROUND and PANGO_ATTR_FOREGROUND are always
1170 PangoAttribute
* attrUnderline
=
1171 pango_attr_iterator_get(iter
, PANGO_ATTR_UNDERLINE
);
1172 PangoAttribute
* attrForeground
=
1173 pango_attr_iterator_get(iter
, PANGO_ATTR_FOREGROUND
);
1174 if (!attrUnderline
&& !attrForeground
) {
1178 // Get the range of the current attribute(s)
1180 pango_attr_iterator_range(iter
, &start
, &end
);
1183 // XIMReverse | XIMUnderline
1184 if (attrUnderline
&& attrForeground
) {
1185 range
.mRangeType
= NS_TEXTRANGE_SELECTEDCONVERTEDTEXT
;
1188 else if (attrUnderline
) {
1189 range
.mRangeType
= NS_TEXTRANGE_CONVERTEDTEXT
;
1192 else if (attrForeground
) {
1193 range
.mRangeType
= NS_TEXTRANGE_SELECTEDRAWTEXT
;
1195 range
.mRangeType
= NS_TEXTRANGE_RAWINPUT
;
1198 gunichar2
* uniStr
= nullptr;
1200 range
.mStartOffset
= 0;
1203 uniStr
= g_utf8_to_utf16(preedit_string
, start
,
1204 nullptr, &uniStrLen
, nullptr);
1206 range
.mStartOffset
= uniStrLen
;
1213 uniStr
= g_utf8_to_utf16(preedit_string
+ start
, end
- start
,
1214 nullptr, &uniStrLen
, nullptr);
1216 range
.mEndOffset
= range
.mStartOffset
;
1218 range
.mEndOffset
= range
.mStartOffset
+ uniStrLen
;
1223 aTextRangeList
.AppendElement(range
);
1225 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1226 (" mStartOffset=%u, mEndOffset=%u, mRangeType=%s",
1227 range
.mStartOffset
, range
.mEndOffset
,
1228 GetRangeTypeName(range
.mRangeType
)));
1229 } while (pango_attr_iterator_next(iter
));
1232 if (cursor_pos
< 0) {
1233 range
.mStartOffset
= 0;
1234 } else if (uint32_t(cursor_pos
) > mDispatchedCompositionString
.Length()) {
1235 range
.mStartOffset
= mDispatchedCompositionString
.Length();
1237 range
.mStartOffset
= uint32_t(cursor_pos
);
1239 range
.mEndOffset
= range
.mStartOffset
;
1240 range
.mRangeType
= NS_TEXTRANGE_CARETPOSITION
;
1241 aTextRangeList
.AppendElement(range
);
1243 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1244 (" mStartOffset=%u, mEndOffset=%u, mRangeType=%s",
1245 range
.mStartOffset
, range
.mEndOffset
,
1246 GetRangeTypeName(range
.mRangeType
)));
1248 pango_attr_iterator_destroy(iter
);
1249 pango_attr_list_unref(feedback_list
);
1250 g_free(preedit_string
);
1254 nsGtkIMModule::SetCursorPosition(uint32_t aTargetOffset
)
1256 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1257 ("GtkIMModule(%p): SetCursorPosition, aTargetOffset=%u",
1258 this, aTargetOffset
));
1260 if (aTargetOffset
== UINT32_MAX
) {
1261 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1262 (" FAILED, aTargetOffset is wrong offset"));
1266 if (!mLastFocusedWindow
) {
1267 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1268 (" FAILED, there are no focused window"));
1272 GtkIMContext
*im
= GetContext();
1274 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1275 (" FAILED, there are no context"));
1279 WidgetQueryContentEvent
charRect(true, NS_QUERY_TEXT_RECT
,
1280 mLastFocusedWindow
);
1281 charRect
.InitForQueryTextRect(aTargetOffset
, 1);
1282 InitEvent(charRect
);
1283 nsEventStatus status
;
1284 mLastFocusedWindow
->DispatchEvent(&charRect
, status
);
1285 if (!charRect
.mSucceeded
) {
1286 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1287 (" FAILED, NS_QUERY_TEXT_RECT was failed"));
1290 nsWindow
* rootWindow
=
1291 static_cast<nsWindow
*>(mLastFocusedWindow
->GetTopLevelWidget());
1293 // Get the position of the rootWindow in screen.
1295 gdk_window_get_origin(rootWindow
->GetGdkWindow(), &rootX
, &rootY
);
1297 // Get the position of IM context owner window in screen.
1298 gint ownerX
, ownerY
;
1299 gdk_window_get_origin(mOwnerWindow
->GetGdkWindow(), &ownerX
, &ownerY
);
1301 // Compute the caret position in the IM owner window.
1303 area
.x
= charRect
.mReply
.mRect
.x
+ rootX
- ownerX
;
1304 area
.y
= charRect
.mReply
.mRect
.y
+ rootY
- ownerY
;
1306 area
.height
= charRect
.mReply
.mRect
.height
;
1308 gtk_im_context_set_cursor_location(im
, &area
);
1312 nsGtkIMModule::GetCurrentParagraph(nsAString
& aText
, uint32_t& aCursorPos
)
1314 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1315 ("GtkIMModule(%p): GetCurrentParagraph, mCompositionState=%s",
1316 this, GetCompositionStateName()));
1318 if (!mLastFocusedWindow
) {
1319 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1320 (" FAILED, there are no focused window in this module"));
1321 return NS_ERROR_NULL_POINTER
;
1324 nsEventStatus status
;
1326 uint32_t selOffset
= mCompositionStart
;
1327 uint32_t selLength
= mSelectedString
.Length();
1329 // If focused editor doesn't have composition string, we should use
1330 // current selection.
1331 if (!EditorHasCompositionString()) {
1332 // Query cursor position & selection
1333 WidgetQueryContentEvent
querySelectedTextEvent(true,
1334 NS_QUERY_SELECTED_TEXT
,
1335 mLastFocusedWindow
);
1336 mLastFocusedWindow
->DispatchEvent(&querySelectedTextEvent
, status
);
1337 NS_ENSURE_TRUE(querySelectedTextEvent
.mSucceeded
, NS_ERROR_FAILURE
);
1339 selOffset
= querySelectedTextEvent
.mReply
.mOffset
;
1340 selLength
= querySelectedTextEvent
.mReply
.mString
.Length();
1343 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1344 (" selOffset=%u, selLength=%u",
1345 selOffset
, selLength
));
1347 // XXX nsString::Find and nsString::RFind take int32_t for offset, so,
1348 // we cannot support this request when the current offset is larger
1350 if (selOffset
> INT32_MAX
|| selLength
> INT32_MAX
||
1351 selOffset
+ selLength
> INT32_MAX
) {
1352 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1353 (" FAILED, The selection is out of range"));
1354 return NS_ERROR_FAILURE
;
1357 // Get all text contents of the focused editor
1358 WidgetQueryContentEvent
queryTextContentEvent(true,
1359 NS_QUERY_TEXT_CONTENT
,
1360 mLastFocusedWindow
);
1361 queryTextContentEvent
.InitForQueryTextContent(0, UINT32_MAX
);
1362 mLastFocusedWindow
->DispatchEvent(&queryTextContentEvent
, status
);
1363 NS_ENSURE_TRUE(queryTextContentEvent
.mSucceeded
, NS_ERROR_FAILURE
);
1365 nsAutoString
textContent(queryTextContentEvent
.mReply
.mString
);
1366 if (selOffset
+ selLength
> textContent
.Length()) {
1367 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1368 (" FAILED, The selection is invalid, textContent.Length()=%u",
1369 textContent
.Length()));
1370 return NS_ERROR_FAILURE
;
1373 // Remove composing string and restore the selected string because
1374 // GtkEntry doesn't remove selected string until committing, however,
1375 // our editor does it. We should emulate the behavior for IME.
1376 if (EditorHasCompositionString() &&
1377 mDispatchedCompositionString
!= mSelectedString
) {
1378 textContent
.Replace(mCompositionStart
,
1379 mDispatchedCompositionString
.Length(), mSelectedString
);
1382 // Get only the focused paragraph, by looking for newlines
1383 int32_t parStart
= (selOffset
== 0) ? 0 :
1384 textContent
.RFind("\n", false, selOffset
- 1, -1) + 1;
1385 int32_t parEnd
= textContent
.Find("\n", false, selOffset
+ selLength
, -1);
1387 parEnd
= textContent
.Length();
1389 aText
= nsDependentSubstring(textContent
, parStart
, parEnd
- parStart
);
1390 aCursorPos
= selOffset
- uint32_t(parStart
);
1392 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1393 (" aText=%s, aText.Length()=%u, aCursorPos=%u",
1394 NS_ConvertUTF16toUTF8(aText
).get(),
1395 aText
.Length(), aCursorPos
));
1401 nsGtkIMModule::DeleteText(const int32_t aOffset
, const uint32_t aNChars
)
1403 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1404 ("GtkIMModule(%p): DeleteText, aOffset=%d, aNChars=%d, "
1405 "mCompositionState=%s",
1406 this, aOffset
, aNChars
, GetCompositionStateName()));
1408 if (!mLastFocusedWindow
) {
1409 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1410 (" FAILED, there are no focused window in this module"));
1411 return NS_ERROR_NULL_POINTER
;
1415 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1416 (" FAILED, aNChars must not be zero"));
1417 return NS_ERROR_INVALID_ARG
;
1420 nsRefPtr
<nsWindow
> lastFocusedWindow(mLastFocusedWindow
);
1421 nsEventStatus status
;
1423 // First, we should cancel current composition because editor cannot
1424 // handle changing selection and deleting text.
1426 bool wasComposing
= IsComposing();
1427 bool editorHadCompositionString
= EditorHasCompositionString();
1429 selOffset
= mCompositionStart
;
1430 if (editorHadCompositionString
&&
1431 !DispatchTextEvent(mSelectedString
, false)) {
1432 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1433 (" FAILED, quitting from DeletText"));
1434 return NS_ERROR_FAILURE
;
1436 if (!DispatchCompositionEnd()) {
1437 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1438 (" FAILED, quitting from DeletText"));
1439 return NS_ERROR_FAILURE
;
1442 // Query cursor position & selection
1443 WidgetQueryContentEvent
querySelectedTextEvent(true,
1444 NS_QUERY_SELECTED_TEXT
,
1445 mLastFocusedWindow
);
1446 lastFocusedWindow
->DispatchEvent(&querySelectedTextEvent
, status
);
1447 NS_ENSURE_TRUE(querySelectedTextEvent
.mSucceeded
, NS_ERROR_FAILURE
);
1449 selOffset
= querySelectedTextEvent
.mReply
.mOffset
;
1452 // Get all text contents of the focused editor
1453 WidgetQueryContentEvent
queryTextContentEvent(true,
1454 NS_QUERY_TEXT_CONTENT
,
1455 mLastFocusedWindow
);
1456 queryTextContentEvent
.InitForQueryTextContent(0, UINT32_MAX
);
1457 mLastFocusedWindow
->DispatchEvent(&queryTextContentEvent
, status
);
1458 NS_ENSURE_TRUE(queryTextContentEvent
.mSucceeded
, NS_ERROR_FAILURE
);
1459 if (queryTextContentEvent
.mReply
.mString
.IsEmpty()) {
1460 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1461 (" FAILED, there is no contents"));
1462 return NS_ERROR_FAILURE
;
1465 NS_ConvertUTF16toUTF8
utf8Str(
1466 nsDependentSubstring(queryTextContentEvent
.mReply
.mString
,
1468 glong offsetInUTF8Characters
=
1469 g_utf8_strlen(utf8Str
.get(), utf8Str
.Length()) + aOffset
;
1470 if (offsetInUTF8Characters
< 0) {
1471 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1472 (" FAILED, aOffset is too small for current cursor pos "
1473 "(computed offset: %d)",
1474 offsetInUTF8Characters
));
1475 return NS_ERROR_FAILURE
;
1479 nsDependentSubstring(queryTextContentEvent
.mReply
.mString
, selOffset
),
1481 glong countOfCharactersInUTF8
=
1482 g_utf8_strlen(utf8Str
.get(), utf8Str
.Length());
1483 glong endInUTF8Characters
=
1484 offsetInUTF8Characters
+ aNChars
;
1485 if (countOfCharactersInUTF8
< endInUTF8Characters
) {
1486 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1487 (" FAILED, aNChars is too large for current contents "
1488 "(content length: %d, computed end offset: %d)",
1489 countOfCharactersInUTF8
, endInUTF8Characters
));
1490 return NS_ERROR_FAILURE
;
1493 gchar
* charAtOffset
=
1494 g_utf8_offset_to_pointer(utf8Str
.get(), offsetInUTF8Characters
);
1496 g_utf8_offset_to_pointer(utf8Str
.get(), endInUTF8Characters
);
1498 // Set selection to delete
1499 WidgetSelectionEvent
selectionEvent(true, NS_SELECTION_SET
,
1500 mLastFocusedWindow
);
1502 nsDependentCSubstring
utf8StrBeforeOffset(utf8Str
, 0,
1503 charAtOffset
- utf8Str
.get());
1504 selectionEvent
.mOffset
=
1505 NS_ConvertUTF8toUTF16(utf8StrBeforeOffset
).Length();
1507 nsDependentCSubstring
utf8DeletingStr(utf8Str
,
1508 utf8StrBeforeOffset
.Length(),
1509 charAtEnd
- charAtOffset
);
1510 selectionEvent
.mLength
=
1511 NS_ConvertUTF8toUTF16(utf8DeletingStr
).Length();
1513 selectionEvent
.mReversed
= false;
1514 selectionEvent
.mExpandToClusterBoundary
= false;
1515 lastFocusedWindow
->DispatchEvent(&selectionEvent
, status
);
1517 if (!selectionEvent
.mSucceeded
||
1518 lastFocusedWindow
!= mLastFocusedWindow
||
1519 lastFocusedWindow
->Destroyed()) {
1520 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1521 (" FAILED, setting selection caused focus change "
1522 "or window destroyed"));
1523 return NS_ERROR_FAILURE
;
1526 // Delete the selection
1527 WidgetContentCommandEvent
contentCommandEvent(true,
1528 NS_CONTENT_COMMAND_DELETE
,
1529 mLastFocusedWindow
);
1530 mLastFocusedWindow
->DispatchEvent(&contentCommandEvent
, status
);
1532 if (!contentCommandEvent
.mSucceeded
||
1533 lastFocusedWindow
!= mLastFocusedWindow
||
1534 lastFocusedWindow
->Destroyed()) {
1535 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1536 (" FAILED, deleting the selection caused focus change "
1537 "or window destroyed"));
1538 return NS_ERROR_FAILURE
;
1541 if (!wasComposing
) {
1545 // Restore the composition at new caret position.
1546 if (!DispatchCompositionStart()) {
1547 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1548 (" FAILED, resterting composition start"));
1549 return NS_ERROR_FAILURE
;
1552 if (!editorHadCompositionString
) {
1556 nsAutoString compositionString
;
1557 GetCompositionString(compositionString
);
1558 if (!DispatchTextEvent(compositionString
, true)) {
1559 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1560 (" FAILED, restoring composition string"));
1561 return NS_ERROR_FAILURE
;
1568 nsGtkIMModule::InitEvent(WidgetGUIEvent
& aEvent
)
1570 aEvent
.time
= PR_Now() / 1000;
1574 nsGtkIMModule::ShouldIgnoreNativeCompositionEvent()
1576 PR_LOG(gGtkIMLog
, PR_LOG_ALWAYS
,
1577 ("GtkIMModule(%p): ShouldIgnoreNativeCompositionEvent, mLastFocusedWindow=%p, mIgnoreNativeCompositionEvent=%s",
1578 this, mLastFocusedWindow
,
1579 mIgnoreNativeCompositionEvent
? "YES" : "NO"));
1581 if (!mLastFocusedWindow
) {
1582 return true; // cannot continue
1585 return mIgnoreNativeCompositionEvent
;