Bug 932076 - Add check for MediaExtractor creation failure. r=doublec
[gecko.git] / widget / gtk / nsGtkIMModule.cpp
blob392d1d7e84eb6427a77e6cfddb4924c65b0da678
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 nsGtkIMModule* nsGtkIMModule::sLastFocusedModule = nullptr;
65 nsGtkIMModule::nsGtkIMModule(nsWindow* aOwnerWindow) :
66 mOwnerWindow(aOwnerWindow), mLastFocusedWindow(nullptr),
67 mContext(nullptr),
68 mDummyContext(nullptr),
69 mCompositionStart(UINT32_MAX), mProcessingKeyEvent(nullptr),
70 mCompositionState(eCompositionState_NotComposing),
71 mIsIMFocused(false), mIgnoreNativeCompositionEvent(false)
73 #ifdef PR_LOGGING
74 if (!gGtkIMLog) {
75 gGtkIMLog = PR_NewLogModule("nsGtkIMModuleWidgets");
77 #endif
78 Init();
81 void
82 nsGtkIMModule::Init()
84 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
85 ("GtkIMModule(%p): Init, mOwnerWindow=%p",
86 this, mOwnerWindow));
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.
95 // Normal context.
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),
100 this);
101 g_signal_connect(mContext, "retrieve_surrounding",
102 G_CALLBACK(nsGtkIMModule::OnRetrieveSurroundingCallback),
103 this);
104 g_signal_connect(mContext, "delete_surrounding",
105 G_CALLBACK(nsGtkIMModule::OnDeleteSurroundingCallback),
106 this);
107 g_signal_connect(mContext, "commit",
108 G_CALLBACK(nsGtkIMModule::OnCommitCompositionCallback),
109 this);
110 g_signal_connect(mContext, "preedit_start",
111 G_CALLBACK(nsGtkIMModule::OnStartCompositionCallback),
112 this);
113 g_signal_connect(mContext, "preedit_end",
114 G_CALLBACK(nsGtkIMModule::OnEndCompositionCallback),
115 this);
117 // Dummy context
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));
131 void
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);
142 if (mIsIMFocused) {
143 Blur();
145 mLastFocusedWindow = nullptr;
148 if (mOwnerWindow != aWindow) {
149 return;
152 if (sLastFocusedModule == this) {
153 sLastFocusedModule = nullptr;
157 * NOTE:
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.
164 if (mContext) {
165 PrepareToDestroyContext(mContext);
166 gtk_im_context_set_client_window(mContext, nullptr);
167 g_object_unref(mContext);
168 mContext = nullptr;
171 if (mDummyContext) {
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).
205 void
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;
214 #else
215 GtkIMContext *slave = nullptr; //TODO GTK3
216 #endif
217 if (!slave) {
218 return;
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
230 GtkIMContext parent;
231 gpointer private_data;
232 // ... other fields
235 gpointer signal_data =
236 reinterpret_cast<GtkIMContextXIM*>(slave)->private_data;
237 if (!signal_data) {
238 return;
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;
261 void
262 nsGtkIMModule::OnFocusWindow(nsWindow* aWindow)
264 if (MOZ_UNLIKELY(IsDestroyed())) {
265 return;
268 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
269 ("GtkIMModule(%p): OnFocusWindow, aWindow=%p, mLastFocusedWindow=%p",
270 this, aWindow, mLastFocusedWindow));
271 mLastFocusedWindow = aWindow;
272 Focus();
275 void
276 nsGtkIMModule::OnBlurWindow(nsWindow* aWindow)
278 if (MOZ_UNLIKELY(IsDestroyed())) {
279 return;
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) {
287 return;
290 Blur();
293 bool
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())) {
300 return false;
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));
317 return false;
320 GtkIMContext* im = GetContext();
321 if (MOZ_UNLIKELY(!im)) {
322 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
323 (" FAILED, there are no context"));
324 return false;
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;
346 } else {
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
353 // actually.
354 CommitCompositionBy(EmptyString());
355 filterThisEvent = false;
357 } else {
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;
372 void
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();
386 if (aFocus) {
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;
393 void
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"));
404 return;
407 mIgnoreNativeCompositionEvent = true;
408 gtk_im_context_reset(im);
411 nsresult
412 nsGtkIMModule::CommitIMEComposition(nsWindow* aCaller)
414 if (MOZ_UNLIKELY(IsDestroyed())) {
415 return NS_OK;
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));
427 return NS_OK;
430 if (!IsComposing()) {
431 return NS_OK;
434 // XXX We should commit composition ourselves temporary...
435 ResetIME();
436 CommitCompositionBy(mDispatchedCompositionString);
438 return NS_OK;
441 nsresult
442 nsGtkIMModule::CancelIMEComposition(nsWindow* aCaller)
444 if (MOZ_UNLIKELY(IsDestroyed())) {
445 return NS_OK;
448 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
449 ("GtkIMModule(%p): CancelIMEComposition, aCaller=%p",
450 this, aCaller));
452 if (aCaller != mLastFocusedWindow) {
453 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
454 (" FAILED, the caller isn't focused window, mLastFocusedWindow=%p",
455 mLastFocusedWindow));
456 return NS_OK;
459 if (!IsComposing()) {
460 return NS_OK;
463 GtkIMContext *im = GetContext();
464 if (MOZ_UNLIKELY(!im)) {
465 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
466 (" FAILED, there are no context"));
467 return NS_OK;
470 ResetIME();
471 CommitCompositionBy(EmptyString());
473 return NS_OK;
476 void
477 nsGtkIMModule::SetInputContext(nsWindow* aCaller,
478 const InputContext* aContext,
479 const InputContextAction* aAction)
481 if (MOZ_UNLIKELY(IsDestroyed())) {
482 return;
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));
494 return;
497 if (!mContext) {
498 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
499 (" FAILED, there are no context"));
500 return;
504 if (sLastFocusedModule != this) {
505 mInputContext = *aContext;
506 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
507 (" SUCCEEDED, but we're not active"));
508 return;
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);
517 Blur();
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
525 // focus actually.
526 if (changingEnabledState) {
527 Focus();
531 InputContext
532 nsGtkIMModule::GetInputContext()
534 mInputContext.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
535 return mInputContext;
538 /* static */
539 bool
540 nsGtkIMModule::IsVirtualKeyboardOpened()
542 return false;
545 GtkIMContext*
546 nsGtkIMModule::GetContext()
548 if (IsEnabled()) {
549 return mContext;
552 return mDummyContext;
555 bool
556 nsGtkIMModule::IsEnabled()
558 return mInputContext.mIMEState.mEnabled == IMEState::ENABLED ||
559 mInputContext.mIMEState.mEnabled == IMEState::PASSWORD ||
560 mInputContext.mIMEState.mEnabled == IMEState::PLUGIN;
563 bool
564 nsGtkIMModule::IsEditable()
566 return mInputContext.mIMEState.mEnabled == IMEState::ENABLED ||
567 mInputContext.mIMEState.mEnabled == IMEState::PLUGIN ||
568 mInputContext.mIMEState.mEnabled == IMEState::PASSWORD;
571 void
572 nsGtkIMModule::Focus()
574 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
575 ("GtkIMModule(%p): Focus, sLastFocusedModule=%p",
576 this, sLastFocusedModule));
578 if (mIsIMFocused) {
579 NS_ASSERTION(sLastFocusedModule == this,
580 "We're not active, but the IM was focused?");
581 return;
584 GtkIMContext *im = GetContext();
585 if (!im) {
586 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
587 (" FAILED, there are no context"));
588 return;
591 if (sLastFocusedModule && sLastFocusedModule != this) {
592 sLastFocusedModule->Blur();
595 sLastFocusedModule = this;
597 gtk_im_context_focus_in(im);
598 mIsIMFocused = true;
600 if (!IsEnabled()) {
601 // We should release IME focus for uim and scim.
602 // These IMs are using snooper that is released at losing focus.
603 Blur();
607 void
608 nsGtkIMModule::Blur()
610 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
611 ("GtkIMModule(%p): Blur, mIsIMFocused=%s",
612 this, mIsIMFocused ? "YES" : "NO"));
614 if (!mIsIMFocused) {
615 return;
618 GtkIMContext *im = GetContext();
619 if (!im) {
620 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
621 (" FAILED, there are no context"));
622 return;
625 gtk_im_context_focus_out(im);
626 mIsIMFocused = false;
629 /* static */
630 void
631 nsGtkIMModule::OnStartCompositionCallback(GtkIMContext *aContext,
632 nsGtkIMModule* aModule)
634 aModule->OnStartCompositionNative(aContext);
637 void
638 nsGtkIMModule::OnStartCompositionNative(GtkIMContext *aContext)
640 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
641 ("GtkIMModule(%p): OnStartCompositionNative, aContext=%p",
642 this, aContext));
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",
648 GetContext()));
649 return;
652 if (!DispatchCompositionStart()) {
653 return;
655 SetCursorPosition(mCompositionStart);
658 /* static */
659 void
660 nsGtkIMModule::OnEndCompositionCallback(GtkIMContext *aContext,
661 nsGtkIMModule* aModule)
663 aModule->OnEndCompositionNative(aContext);
666 void
667 nsGtkIMModule::OnEndCompositionNative(GtkIMContext *aContext)
669 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
670 ("GtkIMModule(%p): OnEndCompositionNative, aContext=%p",
671 this, aContext));
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",
677 GetContext()));
678 return;
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.
691 return;
694 // Be aware, widget can be gone
695 DispatchCompositionEnd();
698 /* static */
699 void
700 nsGtkIMModule::OnChangeCompositionCallback(GtkIMContext *aContext,
701 nsGtkIMModule* aModule)
703 aModule->OnChangeCompositionNative(aContext);
706 void
707 nsGtkIMModule::OnChangeCompositionNative(GtkIMContext *aContext)
709 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
710 ("GtkIMModule(%p): OnChangeCompositionNative, aContext=%p",
711 this, aContext));
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",
717 GetContext()));
718 return;
721 if (ShouldIgnoreNativeCompositionEvent()) {
722 return;
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);
736 /* static */
737 gboolean
738 nsGtkIMModule::OnRetrieveSurroundingCallback(GtkIMContext *aContext,
739 nsGtkIMModule *aModule)
741 return aModule->OnRetrieveSurroundingNative(aContext);
744 gboolean
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",
754 GetContext()));
755 return FALSE;
758 nsAutoString uniStr;
759 uint32_t cursorPos;
760 if (NS_FAILED(GetCurrentParagraph(uniStr, cursorPos))) {
761 return FALSE;
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(),
768 cursorPosInUTF8);
769 return TRUE;
772 /* static */
773 gboolean
774 nsGtkIMModule::OnDeleteSurroundingCallback(GtkIMContext *aContext,
775 gint aOffset,
776 gint aNChars,
777 nsGtkIMModule *aModule)
779 return aModule->OnDeleteSurroundingNative(aContext, aOffset, aNChars);
782 gboolean
783 nsGtkIMModule::OnDeleteSurroundingNative(GtkIMContext *aContext,
784 gint aOffset,
785 gint aNChars)
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",
794 GetContext()));
795 return FALSE;
798 if (NS_SUCCEEDED(DeleteText(aOffset, (uint32_t)aNChars))) {
799 return TRUE;
802 // failed
803 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
804 (" FAILED, cannot delete text"));
805 return FALSE;
808 /* static */
809 void
810 nsGtkIMModule::OnCommitCompositionCallback(GtkIMContext *aContext,
811 const gchar *aString,
812 nsGtkIMModule* aModule)
814 aModule->OnCommitCompositionNative(aContext, aString);
817 void
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",
832 GetContext()));
833 return;
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]) {
842 return;
845 if (ShouldIgnoreNativeCompositionEvent()) {
846 return;
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
851 // event.
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",
864 this));
865 mFilterKeyEvent = false;
866 return;
870 NS_ConvertUTF8toUTF16 str(commitString);
871 CommitCompositionBy(str); // Be aware, widget can be gone
874 bool
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)) {
884 return false;
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
891 void
892 nsGtkIMModule::GetCompositionString(nsAString &aCompositionString)
894 gchar *preedit_string;
895 gint cursor_pos;
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);
901 } else {
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);
913 bool
914 nsGtkIMModule::DispatchCompositionStart()
916 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
917 ("GtkIMModule(%p): DispatchCompositionStart", this));
919 if (IsComposing()) {
920 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
921 (" WARNING, we're already in composition"));
922 return true;
925 if (!mLastFocusedWindow) {
926 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
927 (" FAILED, there are no focused window in this module"));
928 return false;
931 nsEventStatus status;
932 WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT,
933 mLastFocusedWindow);
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"));
940 return false;
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;
955 bool isCancelled;
956 mLastFocusedWindow->DispatchKeyDownEvent(mProcessingKeyEvent,
957 &isCancelled);
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"));
964 return false;
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,
978 mLastFocusedWindow);
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"));
986 return false;
989 return true;
992 bool
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"));
1003 return false;
1006 if (!mLastFocusedWindow) {
1007 mDispatchedCompositionString.Truncate();
1008 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1009 (" FAILED, there are no focused window in this module"));
1010 return false;
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"));
1027 return false;
1030 return true;
1033 bool
1034 nsGtkIMModule::DispatchTextEvent(const nsAString &aCompositionString,
1035 bool aIsCommit)
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"));
1044 return false;
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()) {
1052 return false;
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"));
1070 return false;
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;
1094 if (!aIsCommit) {
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;
1103 break;
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"));
1121 return false;
1124 SetCursorPosition(targetOffset);
1126 return true;
1129 void
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;
1138 gint cursor_pos;
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);
1147 return;
1150 PangoAttrIterator* iter;
1151 iter = pango_attr_list_get_iterator(feedback_list);
1152 if (!iter) {
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);
1157 return;
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
1167 * a couple.
1169 do {
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) {
1175 continue;
1178 // Get the range of the current attribute(s)
1179 gint start, end;
1180 pango_attr_iterator_range(iter, &start, &end);
1182 TextRange range;
1183 // XIMReverse | XIMUnderline
1184 if (attrUnderline && attrForeground) {
1185 range.mRangeType = NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
1187 // XIMUnderline
1188 else if (attrUnderline) {
1189 range.mRangeType = NS_TEXTRANGE_CONVERTEDTEXT;
1191 // XIMReverse
1192 else if (attrForeground) {
1193 range.mRangeType = NS_TEXTRANGE_SELECTEDRAWTEXT;
1194 } else {
1195 range.mRangeType = NS_TEXTRANGE_RAWINPUT;
1198 gunichar2* uniStr = nullptr;
1199 if (start == 0) {
1200 range.mStartOffset = 0;
1201 } else {
1202 glong uniStrLen;
1203 uniStr = g_utf8_to_utf16(preedit_string, start,
1204 nullptr, &uniStrLen, nullptr);
1205 if (uniStr) {
1206 range.mStartOffset = uniStrLen;
1207 g_free(uniStr);
1208 uniStr = nullptr;
1212 glong uniStrLen;
1213 uniStr = g_utf8_to_utf16(preedit_string + start, end - start,
1214 nullptr, &uniStrLen, nullptr);
1215 if (!uniStr) {
1216 range.mEndOffset = range.mStartOffset;
1217 } else {
1218 range.mEndOffset = range.mStartOffset + uniStrLen;
1219 g_free(uniStr);
1220 uniStr = nullptr;
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));
1231 TextRange range;
1232 if (cursor_pos < 0) {
1233 range.mStartOffset = 0;
1234 } else if (uint32_t(cursor_pos) > mDispatchedCompositionString.Length()) {
1235 range.mStartOffset = mDispatchedCompositionString.Length();
1236 } else {
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);
1253 void
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"));
1263 return;
1266 if (!mLastFocusedWindow) {
1267 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1268 (" FAILED, there are no focused window"));
1269 return;
1272 GtkIMContext *im = GetContext();
1273 if (!im) {
1274 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
1275 (" FAILED, there are no context"));
1276 return;
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"));
1288 return;
1290 nsWindow* rootWindow =
1291 static_cast<nsWindow*>(mLastFocusedWindow->GetTopLevelWidget());
1293 // Get the position of the rootWindow in screen.
1294 gint rootX, rootY;
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.
1302 GdkRectangle area;
1303 area.x = charRect.mReply.mRect.x + rootX - ownerX;
1304 area.y = charRect.mReply.mRect.y + rootY - ownerY;
1305 area.width = 0;
1306 area.height = charRect.mReply.mRect.height;
1308 gtk_im_context_set_cursor_location(im, &area);
1311 nsresult
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
1349 // than INT32_MAX.
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);
1386 if (parEnd < 0) {
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));
1397 return NS_OK;
1400 nsresult
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;
1414 if (!aNChars) {
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.
1425 uint32_t selOffset;
1426 bool wasComposing = IsComposing();
1427 bool editorHadCompositionString = EditorHasCompositionString();
1428 if (wasComposing) {
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;
1441 } else {
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,
1467 0, selOffset));
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;
1478 AppendUTF16toUTF8(
1479 nsDependentSubstring(queryTextContentEvent.mReply.mString, selOffset),
1480 utf8Str);
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);
1495 gchar* charAtEnd =
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) {
1542 return NS_OK;
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) {
1553 return NS_OK;
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;
1564 return NS_OK;
1567 void
1568 nsGtkIMModule::InitEvent(WidgetGUIEvent& aEvent)
1570 aEvent.time = PR_Now() / 1000;
1573 bool
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;