Bumping manifests a=b2g-bump
[gecko.git] / widget / gtk / nsGtkIMModule.cpp
blobb308cceb3e066d83ae6075c3d9602ae231ee19ed
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/. */
7 #ifdef MOZ_LOGGING
8 #define FORCE_PR_LOG /* Allow logging in the release build */
9 #endif // MOZ_LOGGING
10 #include "prlog.h"
11 #include "prtime.h"
13 #include "nsGtkIMModule.h"
14 #include "nsWindow.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;
23 #ifdef PR_LOGGING
24 PRLogModuleInfo* gGtkIMLog = nullptr;
26 static const char*
27 GetRangeTypeName(uint32_t aRangeType)
29 switch (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";
40 default:
41 return "UNKNOWN SELECTION TYPE!!";
45 static const char*
46 GetEnabledStateName(uint32_t aState)
48 switch (aState) {
49 case IMEState::DISABLED:
50 return "DISABLED";
51 case IMEState::ENABLED:
52 return "ENABLED";
53 case IMEState::PASSWORD:
54 return "PASSWORD";
55 case IMEState::PLUGIN:
56 return "PLUG_IN";
57 default:
58 return "UNKNOWN ENABLED STATUS!!";
61 #endif
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),
70 mContext(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)
78 #ifdef PR_LOGGING
79 if (!gGtkIMLog) {
80 gGtkIMLog = PR_NewLogModule("nsGtkIMModuleWidgets");
82 #endif
83 static bool sFirstInstance = true;
84 if (sFirstInstance) {
85 sFirstInstance = false;
86 sUseSimpleContext =
87 Preferences::GetBool(
88 "intl.ime.use_simple_context_on_password_field",
89 kUseSimpleContextDefault);
91 Init();
94 void
95 nsGtkIMModule::Init()
97 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
98 ("GtkIMModule(%p): Init, mOwnerWindow=%p",
99 this, mOwnerWindow));
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.
108 // Normal context.
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),
113 this);
114 g_signal_connect(mContext, "retrieve_surrounding",
115 G_CALLBACK(nsGtkIMModule::OnRetrieveSurroundingCallback),
116 this);
117 g_signal_connect(mContext, "delete_surrounding",
118 G_CALLBACK(nsGtkIMModule::OnDeleteSurroundingCallback),
119 this);
120 g_signal_connect(mContext, "commit",
121 G_CALLBACK(nsGtkIMModule::OnCommitCompositionCallback),
122 this);
123 g_signal_connect(mContext, "preedit_start",
124 G_CALLBACK(nsGtkIMModule::OnStartCompositionCallback),
125 this);
126 g_signal_connect(mContext, "preedit_end",
127 G_CALLBACK(nsGtkIMModule::OnEndCompositionCallback),
128 this);
130 // Simple context
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),
136 this);
137 g_signal_connect(mSimpleContext, "retrieve_surrounding",
138 G_CALLBACK(&nsGtkIMModule::OnRetrieveSurroundingCallback),
139 this);
140 g_signal_connect(mSimpleContext, "delete_surrounding",
141 G_CALLBACK(&nsGtkIMModule::OnDeleteSurroundingCallback),
142 this);
143 g_signal_connect(mSimpleContext, "commit",
144 G_CALLBACK(&nsGtkIMModule::OnCommitCompositionCallback),
145 this);
146 g_signal_connect(mSimpleContext, "preedit_start",
147 G_CALLBACK(nsGtkIMModule::OnStartCompositionCallback),
148 this);
149 g_signal_connect(mSimpleContext, "preedit_end",
150 G_CALLBACK(nsGtkIMModule::OnEndCompositionCallback),
151 this);
154 // Dummy context
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));
168 void
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);
179 if (mIsIMFocused) {
180 Blur();
182 mLastFocusedWindow = nullptr;
185 if (mOwnerWindow != aWindow) {
186 return;
189 if (sLastFocusedModule == this) {
190 sLastFocusedModule = nullptr;
194 * NOTE:
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.
201 if (mContext) {
202 PrepareToDestroyContext(mContext);
203 gtk_im_context_set_client_window(mContext, nullptr);
204 g_object_unref(mContext);
205 mContext = nullptr;
208 if (mSimpleContext) {
209 gtk_im_context_set_client_window(mSimpleContext, nullptr);
210 g_object_unref(mSimpleContext);
211 mSimpleContext = nullptr;
214 if (mDummyContext) {
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).
248 void
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;
257 #else
258 GtkIMContext *slave = nullptr; //TODO GTK3
259 #endif
260 if (!slave) {
261 return;
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
273 GtkIMContext parent;
274 gpointer private_data;
275 // ... other fields
278 gpointer signal_data =
279 reinterpret_cast<GtkIMContextXIM*>(slave)->private_data;
280 if (!signal_data) {
281 return;
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;
304 void
305 nsGtkIMModule::OnFocusWindow(nsWindow* aWindow)
307 if (MOZ_UNLIKELY(IsDestroyed())) {
308 return;
311 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
312 ("GtkIMModule(%p): OnFocusWindow, aWindow=%p, mLastFocusedWindow=%p",
313 this, aWindow, mLastFocusedWindow));
314 mLastFocusedWindow = aWindow;
315 Focus();
318 void
319 nsGtkIMModule::OnBlurWindow(nsWindow* aWindow)
321 if (MOZ_UNLIKELY(IsDestroyed())) {
322 return;
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) {
330 return;
333 Blur();
336 bool
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())) {
343 return false;
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));
360 return false;
363 GtkIMContext* im = GetContext();
364 if (MOZ_UNLIKELY(!im)) {
365 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
366 (" FAILED, there are no context"));
367 return false;
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;
389 } else {
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
396 // actually.
397 CommitCompositionBy(EmptyString());
398 filterThisEvent = false;
400 } else {
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;
415 void
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();
429 if (aFocus) {
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;
436 void
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"));
447 return;
450 mIgnoreNativeCompositionEvent = true;
451 gtk_im_context_reset(im);
454 nsresult
455 nsGtkIMModule::CommitIMEComposition(nsWindow* aCaller)
457 if (MOZ_UNLIKELY(IsDestroyed())) {
458 return NS_OK;
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));
470 return NS_OK;
473 if (!IsComposing()) {
474 return NS_OK;
477 // XXX We should commit composition ourselves temporary...
478 ResetIME();
479 CommitCompositionBy(mDispatchedCompositionString);
481 return NS_OK;
484 nsresult
485 nsGtkIMModule::CancelIMEComposition(nsWindow* aCaller)
487 if (MOZ_UNLIKELY(IsDestroyed())) {
488 return NS_OK;
491 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
492 ("GtkIMModule(%p): CancelIMEComposition, aCaller=%p",
493 this, aCaller));
495 if (aCaller != mLastFocusedWindow) {
496 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
497 (" FAILED, the caller isn't focused window, mLastFocusedWindow=%p",
498 mLastFocusedWindow));
499 return NS_OK;
502 if (!IsComposing()) {
503 return NS_OK;
506 GtkIMContext *im = GetContext();
507 if (MOZ_UNLIKELY(!im)) {
508 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
509 (" FAILED, there are no context"));
510 return NS_OK;
513 ResetIME();
514 CommitCompositionBy(EmptyString());
516 return NS_OK;
519 void
520 nsGtkIMModule::OnUpdateComposition(void)
522 if (MOZ_UNLIKELY(IsDestroyed())) {
523 return;
526 SetCursorPosition(mCompositionTargetOffset);
529 void
530 nsGtkIMModule::SetInputContext(nsWindow* aCaller,
531 const InputContext* aContext,
532 const InputContextAction* aAction)
534 if (MOZ_UNLIKELY(IsDestroyed())) {
535 return;
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));
547 return;
550 if (!mContext) {
551 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
552 (" FAILED, there are no context"));
553 return;
557 if (sLastFocusedModule != this) {
558 mInputContext = *aContext;
559 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
560 (" SUCCEEDED, but we're not active"));
561 return;
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);
571 Blur();
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();
581 if (context) {
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
622 // focus actually.
623 Focus();
625 // XXX Should we call Blur() when it's not editable? E.g., it might be
626 // better to close VKB automatically.
630 InputContext
631 nsGtkIMModule::GetInputContext()
633 mInputContext.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
634 return mInputContext;
637 /* static */
638 bool
639 nsGtkIMModule::IsVirtualKeyboardOpened()
641 return false;
644 GtkIMContext*
645 nsGtkIMModule::GetContext()
647 if (IsEnabled()) {
648 return mContext;
650 if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) {
651 return mSimpleContext;
653 return mDummyContext;
656 bool
657 nsGtkIMModule::IsEnabled()
659 return mInputContext.mIMEState.mEnabled == IMEState::ENABLED ||
660 mInputContext.mIMEState.mEnabled == IMEState::PLUGIN ||
661 (!sUseSimpleContext &&
662 mInputContext.mIMEState.mEnabled == IMEState::PASSWORD);
665 bool
666 nsGtkIMModule::IsEditable()
668 return mInputContext.mIMEState.mEnabled == IMEState::ENABLED ||
669 mInputContext.mIMEState.mEnabled == IMEState::PLUGIN ||
670 mInputContext.mIMEState.mEnabled == IMEState::PASSWORD;
673 void
674 nsGtkIMModule::Focus()
676 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
677 ("GtkIMModule(%p): Focus, sLastFocusedModule=%p",
678 this, sLastFocusedModule));
680 if (mIsIMFocused) {
681 NS_ASSERTION(sLastFocusedModule == this,
682 "We're not active, but the IM was focused?");
683 return;
686 GtkIMContext *im = GetContext();
687 if (!im) {
688 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
689 (" FAILED, there are no context"));
690 return;
693 if (sLastFocusedModule && sLastFocusedModule != this) {
694 sLastFocusedModule->Blur();
697 sLastFocusedModule = this;
699 gtk_im_context_focus_in(im);
700 mIsIMFocused = true;
702 if (!IsEnabled()) {
703 // We should release IME focus for uim and scim.
704 // These IMs are using snooper that is released at losing focus.
705 Blur();
709 void
710 nsGtkIMModule::Blur()
712 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
713 ("GtkIMModule(%p): Blur, mIsIMFocused=%s",
714 this, mIsIMFocused ? "YES" : "NO"));
716 if (!mIsIMFocused) {
717 return;
720 GtkIMContext *im = GetContext();
721 if (!im) {
722 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
723 (" FAILED, there are no context"));
724 return;
727 gtk_im_context_focus_out(im);
728 mIsIMFocused = false;
731 /* static */
732 void
733 nsGtkIMModule::OnStartCompositionCallback(GtkIMContext *aContext,
734 nsGtkIMModule* aModule)
736 aModule->OnStartCompositionNative(aContext);
739 void
740 nsGtkIMModule::OnStartCompositionNative(GtkIMContext *aContext)
742 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
743 ("GtkIMModule(%p): OnStartCompositionNative, aContext=%p",
744 this, aContext));
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",
750 GetContext()));
751 return;
754 if (!DispatchCompositionStart()) {
755 return;
757 mCompositionTargetOffset = mCompositionStart;
760 /* static */
761 void
762 nsGtkIMModule::OnEndCompositionCallback(GtkIMContext *aContext,
763 nsGtkIMModule* aModule)
765 aModule->OnEndCompositionNative(aContext);
768 void
769 nsGtkIMModule::OnEndCompositionNative(GtkIMContext *aContext)
771 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
772 ("GtkIMModule(%p): OnEndCompositionNative, aContext=%p",
773 this, aContext));
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",
779 GetContext()));
780 return;
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.
793 return;
796 // Be aware, widget can be gone
797 DispatchCompositionEnd();
800 /* static */
801 void
802 nsGtkIMModule::OnChangeCompositionCallback(GtkIMContext *aContext,
803 nsGtkIMModule* aModule)
805 aModule->OnChangeCompositionNative(aContext);
808 void
809 nsGtkIMModule::OnChangeCompositionNative(GtkIMContext *aContext)
811 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
812 ("GtkIMModule(%p): OnChangeCompositionNative, aContext=%p",
813 this, aContext));
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",
819 GetContext()));
820 return;
823 if (ShouldIgnoreNativeCompositionEvent()) {
824 return;
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);
838 /* static */
839 gboolean
840 nsGtkIMModule::OnRetrieveSurroundingCallback(GtkIMContext *aContext,
841 nsGtkIMModule *aModule)
843 return aModule->OnRetrieveSurroundingNative(aContext);
846 gboolean
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",
856 GetContext()));
857 return FALSE;
860 nsAutoString uniStr;
861 uint32_t cursorPos;
862 if (NS_FAILED(GetCurrentParagraph(uniStr, cursorPos))) {
863 return FALSE;
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(),
870 cursorPosInUTF8);
871 return TRUE;
874 /* static */
875 gboolean
876 nsGtkIMModule::OnDeleteSurroundingCallback(GtkIMContext *aContext,
877 gint aOffset,
878 gint aNChars,
879 nsGtkIMModule *aModule)
881 return aModule->OnDeleteSurroundingNative(aContext, aOffset, aNChars);
884 gboolean
885 nsGtkIMModule::OnDeleteSurroundingNative(GtkIMContext *aContext,
886 gint aOffset,
887 gint aNChars)
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",
896 GetContext()));
897 return FALSE;
900 if (NS_SUCCEEDED(DeleteText(aOffset, (uint32_t)aNChars))) {
901 return TRUE;
904 // failed
905 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
906 (" FAILED, cannot delete text"));
907 return FALSE;
910 /* static */
911 void
912 nsGtkIMModule::OnCommitCompositionCallback(GtkIMContext *aContext,
913 const gchar *aString,
914 nsGtkIMModule* aModule)
916 aModule->OnCommitCompositionNative(aContext, aString);
919 void
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",
934 GetContext()));
935 return;
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]) {
944 return;
947 if (ShouldIgnoreNativeCompositionEvent()) {
948 return;
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
953 // event.
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",
966 this));
967 mFilterKeyEvent = false;
968 return;
972 NS_ConvertUTF8toUTF16 str(commitString);
973 CommitCompositionBy(str); // Be aware, widget can be gone
976 bool
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)) {
986 return false;
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
993 void
994 nsGtkIMModule::GetCompositionString(nsAString &aCompositionString)
996 gchar *preedit_string;
997 gint cursor_pos;
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);
1003 } else {
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);
1015 bool
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"));
1024 return true;
1027 if (!mLastFocusedWindow) {
1028 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1029 (" FAILED, there are no focused window in this module"));
1030 return false;
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"));
1042 return false;
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;
1057 bool isCancelled;
1058 mLastFocusedWindow->DispatchKeyDownEvent(mProcessingKeyEvent,
1059 &isCancelled);
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"));
1066 return false;
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"));
1088 return false;
1091 return true;
1094 bool
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"));
1105 return false;
1108 if (!mLastFocusedWindow) {
1109 mDispatchedCompositionString.Truncate();
1110 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1111 (" FAILED, there are no focused window in this module"));
1112 return false;
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"));
1130 return false;
1133 return true;
1136 bool
1137 nsGtkIMModule::DispatchTextEvent(const nsAString &aCompositionString,
1138 bool aIsCommit)
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"));
1147 return false;
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()) {
1155 return false;
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"));
1173 return false;
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;
1196 if (!aIsCommit) {
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"));
1214 return false;
1217 // We cannot call SetCursorPosition for e10s-aware.
1218 // DispatchEvent is async on e10s, so composition rect isn't updated now
1219 // on tab parent.
1220 mCompositionTargetOffset = targetOffset;
1222 return true;
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;
1234 gint cursor_pos;
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);
1248 if (!iter) {
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
1263 * a couple.
1265 do {
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) {
1271 continue;
1274 // Get the range of the current attribute(s)
1275 gint start, end;
1276 pango_attr_iterator_range(iter, &start, &end);
1278 TextRange range;
1279 // XIMReverse | XIMUnderline
1280 if (attrUnderline && attrForeground) {
1281 range.mRangeType = NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
1283 // XIMUnderline
1284 else if (attrUnderline) {
1285 range.mRangeType = NS_TEXTRANGE_CONVERTEDTEXT;
1287 // XIMReverse
1288 else if (attrForeground) {
1289 range.mRangeType = NS_TEXTRANGE_SELECTEDRAWTEXT;
1290 } else {
1291 range.mRangeType = NS_TEXTRANGE_RAWINPUT;
1294 gunichar2* uniStr = nullptr;
1295 if (start == 0) {
1296 range.mStartOffset = 0;
1297 } else {
1298 glong uniStrLen;
1299 uniStr = g_utf8_to_utf16(preedit_string, start,
1300 nullptr, &uniStrLen, nullptr);
1301 if (uniStr) {
1302 range.mStartOffset = uniStrLen;
1303 g_free(uniStr);
1304 uniStr = nullptr;
1308 glong uniStrLen;
1309 uniStr = g_utf8_to_utf16(preedit_string + start, end - start,
1310 nullptr, &uniStrLen, nullptr);
1311 if (!uniStr) {
1312 range.mEndOffset = range.mStartOffset;
1313 } else {
1314 range.mEndOffset = range.mStartOffset + uniStrLen;
1315 g_free(uniStr);
1316 uniStr = nullptr;
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));
1327 TextRange range;
1328 if (cursor_pos < 0) {
1329 range.mStartOffset = 0;
1330 } else if (uint32_t(cursor_pos) > mDispatchedCompositionString.Length()) {
1331 range.mStartOffset = mDispatchedCompositionString.Length();
1332 } else {
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();
1351 void
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"));
1361 return;
1364 if (!mLastFocusedWindow) {
1365 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1366 (" FAILED, there are no focused window"));
1367 return;
1370 GtkIMContext *im = GetContext();
1371 if (!im) {
1372 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1373 (" FAILED, there are no context"));
1374 return;
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"));
1386 return;
1388 nsWindow* rootWindow =
1389 static_cast<nsWindow*>(mLastFocusedWindow->GetTopLevelWidget());
1391 // Get the position of the rootWindow in screen.
1392 gint rootX, rootY;
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.
1400 GdkRectangle area;
1401 area.x = charRect.mReply.mRect.x + rootX - ownerX;
1402 area.y = charRect.mReply.mRect.y + rootY - ownerY;
1403 area.width = 0;
1404 area.height = charRect.mReply.mRect.height;
1406 gtk_im_context_set_cursor_location(im, &area);
1409 nsresult
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
1447 // than INT32_MAX.
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);
1484 if (parEnd < 0) {
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));
1495 return NS_OK;
1498 nsresult
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;
1512 if (!aNChars) {
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.
1523 uint32_t selOffset;
1524 bool wasComposing = IsComposing();
1525 bool editorHadCompositionString = EditorHasCompositionString();
1526 if (wasComposing) {
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;
1539 } else {
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,
1565 0, selOffset));
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;
1576 AppendUTF16toUTF8(
1577 nsDependentSubstring(queryTextContentEvent.mReply.mString, selOffset),
1578 utf8Str);
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);
1593 gchar* charAtEnd =
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) {
1640 return NS_OK;
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) {
1651 return NS_OK;
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;
1662 return NS_OK;
1665 void
1666 nsGtkIMModule::InitEvent(WidgetGUIEvent& aEvent)
1668 aEvent.time = PR_Now() / 1000;
1671 bool
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;