michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim: set ts=4 et sw=4 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifdef MOZ_LOGGING michael@0: #define FORCE_PR_LOG /* Allow logging in the release build */ michael@0: #endif // MOZ_LOGGING michael@0: #include "prlog.h" michael@0: #include "prtime.h" michael@0: michael@0: #include "nsGtkIMModule.h" michael@0: #include "nsWindow.h" michael@0: #include "mozilla/Likely.h" michael@0: #include "mozilla/MiscEvents.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/TextEvents.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::widget; michael@0: michael@0: #ifdef PR_LOGGING michael@0: PRLogModuleInfo* gGtkIMLog = nullptr; michael@0: michael@0: static const char* michael@0: GetRangeTypeName(uint32_t aRangeType) michael@0: { michael@0: switch (aRangeType) { michael@0: case NS_TEXTRANGE_RAWINPUT: michael@0: return "NS_TEXTRANGE_RAWINPUT"; michael@0: case NS_TEXTRANGE_CONVERTEDTEXT: michael@0: return "NS_TEXTRANGE_CONVERTEDTEXT"; michael@0: case NS_TEXTRANGE_SELECTEDRAWTEXT: michael@0: return "NS_TEXTRANGE_SELECTEDRAWTEXT"; michael@0: case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT: michael@0: return "NS_TEXTRANGE_SELECTEDCONVERTEDTEXT"; michael@0: case NS_TEXTRANGE_CARETPOSITION: michael@0: return "NS_TEXTRANGE_CARETPOSITION"; michael@0: default: michael@0: return "UNKNOWN SELECTION TYPE!!"; michael@0: } michael@0: } michael@0: michael@0: static const char* michael@0: GetEnabledStateName(uint32_t aState) michael@0: { michael@0: switch (aState) { michael@0: case IMEState::DISABLED: michael@0: return "DISABLED"; michael@0: case IMEState::ENABLED: michael@0: return "ENABLED"; michael@0: case IMEState::PASSWORD: michael@0: return "PASSWORD"; michael@0: case IMEState::PLUGIN: michael@0: return "PLUG_IN"; michael@0: default: michael@0: return "UNKNOWN ENABLED STATUS!!"; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: const static bool kUseSimpleContextDefault = MOZ_WIDGET_GTK == 2; michael@0: michael@0: nsGtkIMModule* nsGtkIMModule::sLastFocusedModule = nullptr; michael@0: bool nsGtkIMModule::sUseSimpleContext; michael@0: michael@0: nsGtkIMModule::nsGtkIMModule(nsWindow* aOwnerWindow) : michael@0: mOwnerWindow(aOwnerWindow), mLastFocusedWindow(nullptr), michael@0: mContext(nullptr), michael@0: mSimpleContext(nullptr), michael@0: mDummyContext(nullptr), michael@0: mCompositionStart(UINT32_MAX), mProcessingKeyEvent(nullptr), michael@0: mCompositionTargetOffset(UINT32_MAX), michael@0: mCompositionState(eCompositionState_NotComposing), michael@0: mIsIMFocused(false), mIgnoreNativeCompositionEvent(false) michael@0: { michael@0: #ifdef PR_LOGGING michael@0: if (!gGtkIMLog) { michael@0: gGtkIMLog = PR_NewLogModule("nsGtkIMModuleWidgets"); michael@0: } michael@0: #endif michael@0: static bool sFirstInstance = true; michael@0: if (sFirstInstance) { michael@0: sFirstInstance = false; michael@0: sUseSimpleContext = michael@0: Preferences::GetBool( michael@0: "intl.ime.use_simple_context_on_password_field", michael@0: kUseSimpleContextDefault); michael@0: } michael@0: Init(); michael@0: } michael@0: michael@0: void michael@0: nsGtkIMModule::Init() michael@0: { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): Init, mOwnerWindow=%p", michael@0: this, mOwnerWindow)); michael@0: michael@0: MozContainer* container = mOwnerWindow->GetMozContainer(); michael@0: NS_PRECONDITION(container, "container is null"); michael@0: GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(container)); michael@0: michael@0: // NOTE: gtk_im_*_new() abort (kill) the whole process when it fails. michael@0: // So, we don't need to check the result. michael@0: michael@0: // Normal context. michael@0: mContext = gtk_im_multicontext_new(); michael@0: gtk_im_context_set_client_window(mContext, gdkWindow); michael@0: g_signal_connect(mContext, "preedit_changed", michael@0: G_CALLBACK(nsGtkIMModule::OnChangeCompositionCallback), michael@0: this); michael@0: g_signal_connect(mContext, "retrieve_surrounding", michael@0: G_CALLBACK(nsGtkIMModule::OnRetrieveSurroundingCallback), michael@0: this); michael@0: g_signal_connect(mContext, "delete_surrounding", michael@0: G_CALLBACK(nsGtkIMModule::OnDeleteSurroundingCallback), michael@0: this); michael@0: g_signal_connect(mContext, "commit", michael@0: G_CALLBACK(nsGtkIMModule::OnCommitCompositionCallback), michael@0: this); michael@0: g_signal_connect(mContext, "preedit_start", michael@0: G_CALLBACK(nsGtkIMModule::OnStartCompositionCallback), michael@0: this); michael@0: g_signal_connect(mContext, "preedit_end", michael@0: G_CALLBACK(nsGtkIMModule::OnEndCompositionCallback), michael@0: this); michael@0: michael@0: // Simple context michael@0: if (sUseSimpleContext) { michael@0: mSimpleContext = gtk_im_context_simple_new(); michael@0: gtk_im_context_set_client_window(mSimpleContext, gdkWindow); michael@0: g_signal_connect(mSimpleContext, "preedit_changed", michael@0: G_CALLBACK(&nsGtkIMModule::OnChangeCompositionCallback), michael@0: this); michael@0: g_signal_connect(mSimpleContext, "retrieve_surrounding", michael@0: G_CALLBACK(&nsGtkIMModule::OnRetrieveSurroundingCallback), michael@0: this); michael@0: g_signal_connect(mSimpleContext, "delete_surrounding", michael@0: G_CALLBACK(&nsGtkIMModule::OnDeleteSurroundingCallback), michael@0: this); michael@0: g_signal_connect(mSimpleContext, "commit", michael@0: G_CALLBACK(&nsGtkIMModule::OnCommitCompositionCallback), michael@0: this); michael@0: g_signal_connect(mSimpleContext, "preedit_start", michael@0: G_CALLBACK(nsGtkIMModule::OnStartCompositionCallback), michael@0: this); michael@0: g_signal_connect(mSimpleContext, "preedit_end", michael@0: G_CALLBACK(nsGtkIMModule::OnEndCompositionCallback), michael@0: this); michael@0: } michael@0: michael@0: // Dummy context michael@0: mDummyContext = gtk_im_multicontext_new(); michael@0: gtk_im_context_set_client_window(mDummyContext, gdkWindow); michael@0: } michael@0: michael@0: nsGtkIMModule::~nsGtkIMModule() michael@0: { michael@0: if (this == sLastFocusedModule) { michael@0: sLastFocusedModule = nullptr; michael@0: } michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p) was gone", this)); michael@0: } michael@0: michael@0: void michael@0: nsGtkIMModule::OnDestroyWindow(nsWindow* aWindow) michael@0: { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): OnDestroyWindow, aWindow=%p, mLastFocusedWindow=%p, mOwnerWindow=%p, mLastFocusedModule=%p", michael@0: this, aWindow, mLastFocusedWindow, mOwnerWindow, sLastFocusedModule)); michael@0: michael@0: NS_PRECONDITION(aWindow, "aWindow must not be null"); michael@0: michael@0: if (mLastFocusedWindow == aWindow) { michael@0: CancelIMEComposition(aWindow); michael@0: if (mIsIMFocused) { michael@0: Blur(); michael@0: } michael@0: mLastFocusedWindow = nullptr; michael@0: } michael@0: michael@0: if (mOwnerWindow != aWindow) { michael@0: return; michael@0: } michael@0: michael@0: if (sLastFocusedModule == this) { michael@0: sLastFocusedModule = nullptr; michael@0: } michael@0: michael@0: /** michael@0: * NOTE: michael@0: * The given window is the owner of this, so, we must release the michael@0: * contexts now. But that might be referred from other nsWindows michael@0: * (they are children of this. But we don't know why there are the michael@0: * cases). So, we need to clear the pointers that refers to contexts michael@0: * and this if the other referrers are still alive. See bug 349727. michael@0: */ michael@0: if (mContext) { michael@0: PrepareToDestroyContext(mContext); michael@0: gtk_im_context_set_client_window(mContext, nullptr); michael@0: g_object_unref(mContext); michael@0: mContext = nullptr; michael@0: } michael@0: michael@0: if (mSimpleContext) { michael@0: gtk_im_context_set_client_window(mSimpleContext, nullptr); michael@0: g_object_unref(mSimpleContext); michael@0: mSimpleContext = nullptr; michael@0: } michael@0: michael@0: if (mDummyContext) { michael@0: // mContext and mDummyContext have the same slaveType and signal_data michael@0: // so no need for another workaround_gtk_im_display_closed. michael@0: gtk_im_context_set_client_window(mDummyContext, nullptr); michael@0: g_object_unref(mDummyContext); michael@0: mDummyContext = nullptr; michael@0: } michael@0: michael@0: mOwnerWindow = nullptr; michael@0: mLastFocusedWindow = nullptr; michael@0: mInputContext.mIMEState.mEnabled = IMEState::DISABLED; michael@0: michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" SUCCEEDED, Completely destroyed")); michael@0: } michael@0: michael@0: // Work around gtk bug http://bugzilla.gnome.org/show_bug.cgi?id=483223: michael@0: // (and the similar issue of GTK+ IIIM) michael@0: // The GTK+ XIM and IIIM modules register handlers for the "closed" signal michael@0: // on the display, but: michael@0: // * The signal handlers are not disconnected when the module is unloaded. michael@0: // michael@0: // The GTK+ XIM module has another problem: michael@0: // * When the signal handler is run (with the module loaded) it tries michael@0: // XFree (and fails) on a pointer that did not come from Xmalloc. michael@0: // michael@0: // To prevent these modules from being unloaded, use static variables to michael@0: // hold ref of GtkIMContext class. michael@0: // For GTK+ XIM module, to prevent the signal handler from being run, michael@0: // find the signal handlers and remove them. michael@0: // michael@0: // GtkIMContextXIMs share XOpenIM connections and display closed signal michael@0: // handlers (where possible). michael@0: michael@0: void michael@0: nsGtkIMModule::PrepareToDestroyContext(GtkIMContext *aContext) michael@0: { michael@0: MozContainer* container = mOwnerWindow->GetMozContainer(); michael@0: NS_PRECONDITION(container, "The container of the window is null"); michael@0: michael@0: GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT(aContext); michael@0: #if (MOZ_WIDGET_GTK == 2) michael@0: GtkIMContext *slave = multicontext->slave; michael@0: #else michael@0: GtkIMContext *slave = nullptr; //TODO GTK3 michael@0: #endif michael@0: if (!slave) { michael@0: return; michael@0: } michael@0: michael@0: GType slaveType = G_TYPE_FROM_INSTANCE(slave); michael@0: const gchar *im_type_name = g_type_name(slaveType); michael@0: if (strcmp(im_type_name, "GtkIMContextXIM") == 0) { michael@0: if (gtk_check_version(2, 12, 1) == nullptr) { michael@0: return; // gtk bug has been fixed michael@0: } michael@0: michael@0: struct GtkIMContextXIM michael@0: { michael@0: GtkIMContext parent; michael@0: gpointer private_data; michael@0: // ... other fields michael@0: }; michael@0: michael@0: gpointer signal_data = michael@0: reinterpret_cast(slave)->private_data; michael@0: if (!signal_data) { michael@0: return; michael@0: } michael@0: michael@0: g_signal_handlers_disconnect_matched( michael@0: gtk_widget_get_display(GTK_WIDGET(container)), michael@0: G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, signal_data); michael@0: michael@0: // Add a reference to prevent the XIM module from being unloaded michael@0: // and reloaded: each time the module is loaded and used, it michael@0: // opens (and doesn't close) new XOpenIM connections. michael@0: static gpointer gtk_xim_context_class = michael@0: g_type_class_ref(slaveType); michael@0: // Mute unused variable warning: michael@0: (void)gtk_xim_context_class; michael@0: } else if (strcmp(im_type_name, "GtkIMContextIIIM") == 0) { michael@0: // Add a reference to prevent the IIIM module from being unloaded michael@0: static gpointer gtk_iiim_context_class = michael@0: g_type_class_ref(slaveType); michael@0: // Mute unused variable warning: michael@0: (void)gtk_iiim_context_class; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsGtkIMModule::OnFocusWindow(nsWindow* aWindow) michael@0: { michael@0: if (MOZ_UNLIKELY(IsDestroyed())) { michael@0: return; michael@0: } michael@0: michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): OnFocusWindow, aWindow=%p, mLastFocusedWindow=%p", michael@0: this, aWindow, mLastFocusedWindow)); michael@0: mLastFocusedWindow = aWindow; michael@0: Focus(); michael@0: } michael@0: michael@0: void michael@0: nsGtkIMModule::OnBlurWindow(nsWindow* aWindow) michael@0: { michael@0: if (MOZ_UNLIKELY(IsDestroyed())) { michael@0: return; michael@0: } michael@0: michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): OnBlurWindow, aWindow=%p, mLastFocusedWindow=%p, mIsIMFocused=%s", michael@0: this, aWindow, mLastFocusedWindow, mIsIMFocused ? "YES" : "NO")); michael@0: michael@0: if (!mIsIMFocused || mLastFocusedWindow != aWindow) { michael@0: return; michael@0: } michael@0: michael@0: Blur(); michael@0: } michael@0: michael@0: bool michael@0: nsGtkIMModule::OnKeyEvent(nsWindow* aCaller, GdkEventKey* aEvent, michael@0: bool aKeyDownEventWasSent /* = false */) michael@0: { michael@0: NS_PRECONDITION(aEvent, "aEvent must be non-null"); michael@0: michael@0: if (!IsEditable() || MOZ_UNLIKELY(IsDestroyed())) { michael@0: return false; michael@0: } michael@0: michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): OnKeyEvent, aCaller=%p, aKeyDownEventWasSent=%s", michael@0: this, aCaller, aKeyDownEventWasSent ? "TRUE" : "FALSE")); michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" aEvent: type=%s, keyval=%s, unicode=0x%X", michael@0: aEvent->type == GDK_KEY_PRESS ? "GDK_KEY_PRESS" : michael@0: aEvent->type == GDK_KEY_RELEASE ? "GDK_KEY_RELEASE" : "Unknown", michael@0: gdk_keyval_name(aEvent->keyval), michael@0: gdk_keyval_to_unicode(aEvent->keyval))); michael@0: michael@0: if (aCaller != mLastFocusedWindow) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, the caller isn't focused window, mLastFocusedWindow=%p", michael@0: mLastFocusedWindow)); michael@0: return false; michael@0: } michael@0: michael@0: GtkIMContext* im = GetContext(); michael@0: if (MOZ_UNLIKELY(!im)) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, there are no context")); michael@0: return false; michael@0: } michael@0: michael@0: mKeyDownEventWasSent = aKeyDownEventWasSent; michael@0: mFilterKeyEvent = true; michael@0: mProcessingKeyEvent = aEvent; michael@0: gboolean isFiltered = gtk_im_context_filter_keypress(im, aEvent); michael@0: mProcessingKeyEvent = nullptr; michael@0: michael@0: // We filter the key event if the event was not committed (because michael@0: // it's probably part of a composition) or if the key event was michael@0: // committed _and_ changed. This way we still let key press michael@0: // events go through as simple key press events instead of michael@0: // composed characters. michael@0: bool filterThisEvent = isFiltered && mFilterKeyEvent; michael@0: michael@0: if (IsComposing() && !isFiltered) { michael@0: if (aEvent->type == GDK_KEY_PRESS) { michael@0: if (!mDispatchedCompositionString.IsEmpty()) { michael@0: // If there is composition string, we shouldn't dispatch michael@0: // any keydown events during composition. michael@0: filterThisEvent = true; michael@0: } else { michael@0: // A Hangul input engine for SCIM doesn't emit preedit_end michael@0: // signal even when composition string becomes empty. On the michael@0: // other hand, we should allow to make composition with empty michael@0: // string for other languages because there *might* be such michael@0: // IM. For compromising this issue, we should dispatch michael@0: // compositionend event, however, we don't need to reset IM michael@0: // actually. michael@0: CommitCompositionBy(EmptyString()); michael@0: filterThisEvent = false; michael@0: } michael@0: } else { michael@0: // Key release event may not be consumed by IM, however, we michael@0: // shouldn't dispatch any keyup event during composition. michael@0: filterThisEvent = true; michael@0: } michael@0: } michael@0: michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" filterThisEvent=%s (isFiltered=%s, mFilterKeyEvent=%s)", michael@0: filterThisEvent ? "TRUE" : "FALSE", isFiltered ? "YES" : "NO", michael@0: mFilterKeyEvent ? "YES" : "NO")); michael@0: michael@0: return filterThisEvent; michael@0: } michael@0: michael@0: void michael@0: nsGtkIMModule::OnFocusChangeInGecko(bool aFocus) michael@0: { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): OnFocusChangeInGecko, aFocus=%s, " michael@0: "mCompositionState=%s, mIsIMFocused=%s, " michael@0: "mIgnoreNativeCompositionEvent=%s", michael@0: this, aFocus ? "YES" : "NO", GetCompositionStateName(), michael@0: mIsIMFocused ? "YES" : "NO", michael@0: mIgnoreNativeCompositionEvent ? "YES" : "NO")); michael@0: michael@0: // We shouldn't carry over the removed string to another editor. michael@0: mSelectedString.Truncate(); michael@0: michael@0: if (aFocus) { michael@0: // If we failed to commit forcedely in previous focused editor, michael@0: // we should reopen the gate for native signals in new focused editor. michael@0: mIgnoreNativeCompositionEvent = false; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsGtkIMModule::ResetIME() michael@0: { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): ResetIME, mCompositionState=%s, mIsIMFocused=%s", michael@0: this, GetCompositionStateName(), mIsIMFocused ? "YES" : "NO")); michael@0: michael@0: GtkIMContext *im = GetContext(); michael@0: if (MOZ_UNLIKELY(!im)) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, there are no context")); michael@0: return; michael@0: } michael@0: michael@0: mIgnoreNativeCompositionEvent = true; michael@0: gtk_im_context_reset(im); michael@0: } michael@0: michael@0: nsresult michael@0: nsGtkIMModule::CommitIMEComposition(nsWindow* aCaller) michael@0: { michael@0: if (MOZ_UNLIKELY(IsDestroyed())) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): CommitIMEComposition, aCaller=%p, " michael@0: "mCompositionState=%s", michael@0: this, aCaller, GetCompositionStateName())); michael@0: michael@0: if (aCaller != mLastFocusedWindow) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" WARNING: the caller isn't focused window, mLastFocusedWindow=%p", michael@0: mLastFocusedWindow)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!IsComposing()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // XXX We should commit composition ourselves temporary... michael@0: ResetIME(); michael@0: CommitCompositionBy(mDispatchedCompositionString); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsGtkIMModule::CancelIMEComposition(nsWindow* aCaller) michael@0: { michael@0: if (MOZ_UNLIKELY(IsDestroyed())) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): CancelIMEComposition, aCaller=%p", michael@0: this, aCaller)); michael@0: michael@0: if (aCaller != mLastFocusedWindow) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, the caller isn't focused window, mLastFocusedWindow=%p", michael@0: mLastFocusedWindow)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!IsComposing()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: GtkIMContext *im = GetContext(); michael@0: if (MOZ_UNLIKELY(!im)) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, there are no context")); michael@0: return NS_OK; michael@0: } michael@0: michael@0: ResetIME(); michael@0: CommitCompositionBy(EmptyString()); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsGtkIMModule::OnUpdateComposition(void) michael@0: { michael@0: if (MOZ_UNLIKELY(IsDestroyed())) { michael@0: return; michael@0: } michael@0: michael@0: SetCursorPosition(mCompositionTargetOffset); michael@0: } michael@0: michael@0: void michael@0: nsGtkIMModule::SetInputContext(nsWindow* aCaller, michael@0: const InputContext* aContext, michael@0: const InputContextAction* aAction) michael@0: { michael@0: if (MOZ_UNLIKELY(IsDestroyed())) { michael@0: return; michael@0: } michael@0: michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): SetInputContext, aCaller=%p, aState=%s mHTMLInputType=%s", michael@0: this, aCaller, GetEnabledStateName(aContext->mIMEState.mEnabled), michael@0: NS_ConvertUTF16toUTF8(aContext->mHTMLInputType).get())); michael@0: michael@0: if (aCaller != mLastFocusedWindow) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, the caller isn't focused window, mLastFocusedWindow=%p", michael@0: mLastFocusedWindow)); michael@0: return; michael@0: } michael@0: michael@0: if (!mContext) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, there are no context")); michael@0: return; michael@0: } michael@0: michael@0: michael@0: if (sLastFocusedModule != this) { michael@0: mInputContext = *aContext; michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" SUCCEEDED, but we're not active")); michael@0: return; michael@0: } michael@0: michael@0: bool changingEnabledState = michael@0: aContext->mIMEState.mEnabled != mInputContext.mIMEState.mEnabled || michael@0: aContext->mHTMLInputType != mInputContext.mHTMLInputType; michael@0: michael@0: // Release current IME focus if IME is enabled. michael@0: if (changingEnabledState && IsEditable()) { michael@0: CommitIMEComposition(mLastFocusedWindow); michael@0: Blur(); michael@0: } michael@0: michael@0: mInputContext = *aContext; michael@0: michael@0: if (changingEnabledState) { michael@0: #if (MOZ_WIDGET_GTK == 3) michael@0: static bool sInputPurposeSupported = !gtk_check_version(3, 6, 0); michael@0: if (sInputPurposeSupported && IsEditable()) { michael@0: GtkIMContext* context = GetContext(); michael@0: if (context) { michael@0: GtkInputPurpose purpose = GTK_INPUT_PURPOSE_FREE_FORM; michael@0: const nsString& inputType = mInputContext.mHTMLInputType; michael@0: // Password case has difficult issue. Desktop IMEs disable michael@0: // composition if input-purpose is password. michael@0: // For disabling IME on |ime-mode: disabled;|, we need to check michael@0: // mEnabled value instead of inputType value. This hack also michael@0: // enables composition on michael@0: // . michael@0: // This is right behavior of ime-mode on desktop. michael@0: // michael@0: // On the other hand, IME for tablet devices may provide a michael@0: // specific software keyboard for password field. If so, michael@0: // the behavior might look strange on both: michael@0: // michael@0: // michael@0: // michael@0: // Temporarily, we should focus on desktop environment for now. michael@0: // I.e., let's ignore tablet devices for now. When somebody michael@0: // reports actual trouble on tablet devices, we should try to michael@0: // look for a way to solve actual problem. michael@0: if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) { michael@0: purpose = GTK_INPUT_PURPOSE_PASSWORD; michael@0: } else if (inputType.EqualsLiteral("email")) { michael@0: purpose = GTK_INPUT_PURPOSE_EMAIL; michael@0: } else if (inputType.EqualsLiteral("url")) { michael@0: purpose = GTK_INPUT_PURPOSE_URL; michael@0: } else if (inputType.EqualsLiteral("tel")) { michael@0: purpose = GTK_INPUT_PURPOSE_PHONE; michael@0: } else if (inputType.EqualsLiteral("number")) { michael@0: purpose = GTK_INPUT_PURPOSE_NUMBER; michael@0: } michael@0: michael@0: g_object_set(context, "input-purpose", purpose, nullptr); michael@0: } michael@0: } michael@0: #endif // #if (MOZ_WIDGET_GTK == 3) michael@0: michael@0: // Even when aState is not enabled state, we need to set IME focus. michael@0: // Because some IMs are updating the status bar of them at this time. michael@0: // Be aware, don't use aWindow here because this method shouldn't move michael@0: // focus actually. michael@0: Focus(); michael@0: michael@0: // XXX Should we call Blur() when it's not editable? E.g., it might be michael@0: // better to close VKB automatically. michael@0: } michael@0: } michael@0: michael@0: InputContext michael@0: nsGtkIMModule::GetInputContext() michael@0: { michael@0: mInputContext.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED; michael@0: return mInputContext; michael@0: } michael@0: michael@0: /* static */ michael@0: bool michael@0: nsGtkIMModule::IsVirtualKeyboardOpened() michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: GtkIMContext* michael@0: nsGtkIMModule::GetContext() michael@0: { michael@0: if (IsEnabled()) { michael@0: return mContext; michael@0: } michael@0: if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) { michael@0: return mSimpleContext; michael@0: } michael@0: return mDummyContext; michael@0: } michael@0: michael@0: bool michael@0: nsGtkIMModule::IsEnabled() michael@0: { michael@0: return mInputContext.mIMEState.mEnabled == IMEState::ENABLED || michael@0: mInputContext.mIMEState.mEnabled == IMEState::PLUGIN || michael@0: (!sUseSimpleContext && michael@0: mInputContext.mIMEState.mEnabled == IMEState::PASSWORD); michael@0: } michael@0: michael@0: bool michael@0: nsGtkIMModule::IsEditable() michael@0: { michael@0: return mInputContext.mIMEState.mEnabled == IMEState::ENABLED || michael@0: mInputContext.mIMEState.mEnabled == IMEState::PLUGIN || michael@0: mInputContext.mIMEState.mEnabled == IMEState::PASSWORD; michael@0: } michael@0: michael@0: void michael@0: nsGtkIMModule::Focus() michael@0: { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): Focus, sLastFocusedModule=%p", michael@0: this, sLastFocusedModule)); michael@0: michael@0: if (mIsIMFocused) { michael@0: NS_ASSERTION(sLastFocusedModule == this, michael@0: "We're not active, but the IM was focused?"); michael@0: return; michael@0: } michael@0: michael@0: GtkIMContext *im = GetContext(); michael@0: if (!im) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, there are no context")); michael@0: return; michael@0: } michael@0: michael@0: if (sLastFocusedModule && sLastFocusedModule != this) { michael@0: sLastFocusedModule->Blur(); michael@0: } michael@0: michael@0: sLastFocusedModule = this; michael@0: michael@0: gtk_im_context_focus_in(im); michael@0: mIsIMFocused = true; michael@0: michael@0: if (!IsEnabled()) { michael@0: // We should release IME focus for uim and scim. michael@0: // These IMs are using snooper that is released at losing focus. michael@0: Blur(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsGtkIMModule::Blur() michael@0: { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): Blur, mIsIMFocused=%s", michael@0: this, mIsIMFocused ? "YES" : "NO")); michael@0: michael@0: if (!mIsIMFocused) { michael@0: return; michael@0: } michael@0: michael@0: GtkIMContext *im = GetContext(); michael@0: if (!im) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, there are no context")); michael@0: return; michael@0: } michael@0: michael@0: gtk_im_context_focus_out(im); michael@0: mIsIMFocused = false; michael@0: } michael@0: michael@0: /* static */ michael@0: void michael@0: nsGtkIMModule::OnStartCompositionCallback(GtkIMContext *aContext, michael@0: nsGtkIMModule* aModule) michael@0: { michael@0: aModule->OnStartCompositionNative(aContext); michael@0: } michael@0: michael@0: void michael@0: nsGtkIMModule::OnStartCompositionNative(GtkIMContext *aContext) michael@0: { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): OnStartCompositionNative, aContext=%p", michael@0: this, aContext)); michael@0: michael@0: // See bug 472635, we should do nothing if IM context doesn't match. michael@0: if (GetContext() != aContext) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, given context doesn't match, GetContext()=%p", michael@0: GetContext())); michael@0: return; michael@0: } michael@0: michael@0: if (!DispatchCompositionStart()) { michael@0: return; michael@0: } michael@0: mCompositionTargetOffset = mCompositionStart; michael@0: } michael@0: michael@0: /* static */ michael@0: void michael@0: nsGtkIMModule::OnEndCompositionCallback(GtkIMContext *aContext, michael@0: nsGtkIMModule* aModule) michael@0: { michael@0: aModule->OnEndCompositionNative(aContext); michael@0: } michael@0: michael@0: void michael@0: nsGtkIMModule::OnEndCompositionNative(GtkIMContext *aContext) michael@0: { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): OnEndCompositionNative, aContext=%p", michael@0: this, aContext)); michael@0: michael@0: // See bug 472635, we should do nothing if IM context doesn't match. michael@0: if (GetContext() != aContext) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, given context doesn't match, GetContext()=%p", michael@0: GetContext())); michael@0: return; michael@0: } michael@0: michael@0: bool shouldIgnoreThisEvent = ShouldIgnoreNativeCompositionEvent(); michael@0: michael@0: // Finish the cancelling mode here rather than DispatchCompositionEnd() michael@0: // because DispatchCompositionEnd() is called ourselves when we need to michael@0: // commit the composition string *before* the focus moves completely. michael@0: // Note that the native commit can be fired *after* ResetIME(). michael@0: mIgnoreNativeCompositionEvent = false; michael@0: michael@0: if (!IsComposing() || shouldIgnoreThisEvent) { michael@0: // If we already handled the commit event, we should do nothing here. michael@0: return; michael@0: } michael@0: michael@0: // Be aware, widget can be gone michael@0: DispatchCompositionEnd(); michael@0: } michael@0: michael@0: /* static */ michael@0: void michael@0: nsGtkIMModule::OnChangeCompositionCallback(GtkIMContext *aContext, michael@0: nsGtkIMModule* aModule) michael@0: { michael@0: aModule->OnChangeCompositionNative(aContext); michael@0: } michael@0: michael@0: void michael@0: nsGtkIMModule::OnChangeCompositionNative(GtkIMContext *aContext) michael@0: { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): OnChangeCompositionNative, aContext=%p", michael@0: this, aContext)); michael@0: michael@0: // See bug 472635, we should do nothing if IM context doesn't match. michael@0: if (GetContext() != aContext) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, given context doesn't match, GetContext()=%p", michael@0: GetContext())); michael@0: return; michael@0: } michael@0: michael@0: if (ShouldIgnoreNativeCompositionEvent()) { michael@0: return; michael@0: } michael@0: michael@0: nsAutoString compositionString; michael@0: GetCompositionString(compositionString); michael@0: if (!IsComposing() && compositionString.IsEmpty()) { michael@0: mDispatchedCompositionString.Truncate(); michael@0: return; // Don't start the composition with empty string. michael@0: } michael@0: michael@0: // Be aware, widget can be gone michael@0: DispatchTextEvent(compositionString, false); michael@0: } michael@0: michael@0: /* static */ michael@0: gboolean michael@0: nsGtkIMModule::OnRetrieveSurroundingCallback(GtkIMContext *aContext, michael@0: nsGtkIMModule *aModule) michael@0: { michael@0: return aModule->OnRetrieveSurroundingNative(aContext); michael@0: } michael@0: michael@0: gboolean michael@0: nsGtkIMModule::OnRetrieveSurroundingNative(GtkIMContext *aContext) michael@0: { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): OnRetrieveSurroundingNative, aContext=%p, current context=%p", michael@0: this, aContext, GetContext())); michael@0: michael@0: if (GetContext() != aContext) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, given context doesn't match, GetContext()=%p", michael@0: GetContext())); michael@0: return FALSE; michael@0: } michael@0: michael@0: nsAutoString uniStr; michael@0: uint32_t cursorPos; michael@0: if (NS_FAILED(GetCurrentParagraph(uniStr, cursorPos))) { michael@0: return FALSE; michael@0: } michael@0: michael@0: NS_ConvertUTF16toUTF8 utf8Str(nsDependentSubstring(uniStr, 0, cursorPos)); michael@0: uint32_t cursorPosInUTF8 = utf8Str.Length(); michael@0: AppendUTF16toUTF8(nsDependentSubstring(uniStr, cursorPos), utf8Str); michael@0: gtk_im_context_set_surrounding(aContext, utf8Str.get(), utf8Str.Length(), michael@0: cursorPosInUTF8); michael@0: return TRUE; michael@0: } michael@0: michael@0: /* static */ michael@0: gboolean michael@0: nsGtkIMModule::OnDeleteSurroundingCallback(GtkIMContext *aContext, michael@0: gint aOffset, michael@0: gint aNChars, michael@0: nsGtkIMModule *aModule) michael@0: { michael@0: return aModule->OnDeleteSurroundingNative(aContext, aOffset, aNChars); michael@0: } michael@0: michael@0: gboolean michael@0: nsGtkIMModule::OnDeleteSurroundingNative(GtkIMContext *aContext, michael@0: gint aOffset, michael@0: gint aNChars) michael@0: { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): OnDeleteSurroundingNative, aContext=%p, current context=%p", michael@0: this, aContext, GetContext())); michael@0: michael@0: if (GetContext() != aContext) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, given context doesn't match, GetContext()=%p", michael@0: GetContext())); michael@0: return FALSE; michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(DeleteText(aOffset, (uint32_t)aNChars))) { michael@0: return TRUE; michael@0: } michael@0: michael@0: // failed michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, cannot delete text")); michael@0: return FALSE; michael@0: } michael@0: michael@0: /* static */ michael@0: void michael@0: nsGtkIMModule::OnCommitCompositionCallback(GtkIMContext *aContext, michael@0: const gchar *aString, michael@0: nsGtkIMModule* aModule) michael@0: { michael@0: aModule->OnCommitCompositionNative(aContext, aString); michael@0: } michael@0: michael@0: void michael@0: nsGtkIMModule::OnCommitCompositionNative(GtkIMContext *aContext, michael@0: const gchar *aUTF8Char) michael@0: { michael@0: const gchar emptyStr = 0; michael@0: const gchar *commitString = aUTF8Char ? aUTF8Char : &emptyStr; michael@0: michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): OnCommitCompositionNative, aContext=%p, current context=%p, commitString=\"%s\"", michael@0: this, aContext, GetContext(), commitString)); michael@0: michael@0: // See bug 472635, we should do nothing if IM context doesn't match. michael@0: if (GetContext() != aContext) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, given context doesn't match, GetContext()=%p", michael@0: GetContext())); michael@0: return; michael@0: } michael@0: michael@0: // If we are not in composition and committing with empty string, michael@0: // we need to do nothing because if we continued to handle this michael@0: // signal, we would dispatch compositionstart, text, compositionend michael@0: // events with empty string. Of course, they are unnecessary events michael@0: // for Web applications and our editor. michael@0: if (!IsComposing() && !commitString[0]) { michael@0: return; michael@0: } michael@0: michael@0: if (ShouldIgnoreNativeCompositionEvent()) { michael@0: return; michael@0: } michael@0: michael@0: // If IME doesn't change their keyevent that generated this commit, michael@0: // don't send it through XIM - just send it as a normal key press michael@0: // event. michael@0: if (!IsComposing() && mProcessingKeyEvent) { michael@0: char keyval_utf8[8]; /* should have at least 6 bytes of space */ michael@0: gint keyval_utf8_len; michael@0: guint32 keyval_unicode; michael@0: michael@0: keyval_unicode = gdk_keyval_to_unicode(mProcessingKeyEvent->keyval); michael@0: keyval_utf8_len = g_unichar_to_utf8(keyval_unicode, keyval_utf8); michael@0: keyval_utf8[keyval_utf8_len] = '\0'; michael@0: michael@0: if (!strcmp(commitString, keyval_utf8)) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): OnCommitCompositionNative, we'll send normal key event", michael@0: this)); michael@0: mFilterKeyEvent = false; michael@0: return; michael@0: } michael@0: } michael@0: michael@0: NS_ConvertUTF8toUTF16 str(commitString); michael@0: CommitCompositionBy(str); // Be aware, widget can be gone michael@0: } michael@0: michael@0: bool michael@0: nsGtkIMModule::CommitCompositionBy(const nsAString& aString) michael@0: { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): CommitCompositionBy, aString=\"%s\", " michael@0: "mDispatchedCompositionString=\"%s\"", michael@0: this, NS_ConvertUTF16toUTF8(aString).get(), michael@0: NS_ConvertUTF16toUTF8(mDispatchedCompositionString).get())); michael@0: michael@0: if (!DispatchTextEvent(aString, true)) { michael@0: return false; michael@0: } michael@0: // We should dispatch the compositionend event here because some IMEs michael@0: // might not fire "preedit_end" native event. michael@0: return DispatchCompositionEnd(); // Be aware, widget can be gone michael@0: } michael@0: michael@0: void michael@0: nsGtkIMModule::GetCompositionString(nsAString &aCompositionString) michael@0: { michael@0: gchar *preedit_string; michael@0: gint cursor_pos; michael@0: PangoAttrList *feedback_list; michael@0: gtk_im_context_get_preedit_string(GetContext(), &preedit_string, michael@0: &feedback_list, &cursor_pos); michael@0: if (preedit_string && *preedit_string) { michael@0: CopyUTF8toUTF16(preedit_string, aCompositionString); michael@0: } else { michael@0: aCompositionString.Truncate(); michael@0: } michael@0: michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): GetCompositionString, result=\"%s\"", michael@0: this, preedit_string)); michael@0: michael@0: pango_attr_list_unref(feedback_list); michael@0: g_free(preedit_string); michael@0: } michael@0: michael@0: bool michael@0: nsGtkIMModule::DispatchCompositionStart() michael@0: { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): DispatchCompositionStart", this)); michael@0: michael@0: if (IsComposing()) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" WARNING, we're already in composition")); michael@0: return true; michael@0: } michael@0: michael@0: if (!mLastFocusedWindow) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, there are no focused window in this module")); michael@0: return false; michael@0: } michael@0: michael@0: nsEventStatus status; michael@0: WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, michael@0: mLastFocusedWindow); michael@0: InitEvent(selection); michael@0: mLastFocusedWindow->DispatchEvent(&selection, status); michael@0: michael@0: if (!selection.mSucceeded || selection.mReply.mOffset == UINT32_MAX) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, cannot query the selection offset")); michael@0: return false; michael@0: } michael@0: michael@0: // XXX The composition start point might be changed by composition events michael@0: // even though we strongly hope it doesn't happen. michael@0: // Every composition event should have the start offset for the result michael@0: // because it may high cost if we query the offset every time. michael@0: mCompositionStart = selection.mReply.mOffset; michael@0: mDispatchedCompositionString.Truncate(); michael@0: michael@0: if (mProcessingKeyEvent && !mKeyDownEventWasSent && michael@0: mProcessingKeyEvent->type == GDK_KEY_PRESS) { michael@0: // If this composition is started by a native keydown event, we need to michael@0: // dispatch our keydown event here (before composition start). michael@0: nsCOMPtr kungFuDeathGrip = mLastFocusedWindow; michael@0: bool isCancelled; michael@0: mLastFocusedWindow->DispatchKeyDownEvent(mProcessingKeyEvent, michael@0: &isCancelled); michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" keydown event is dispatched")); michael@0: if (static_cast(kungFuDeathGrip.get())->IsDestroyed() || michael@0: kungFuDeathGrip != mLastFocusedWindow) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" NOTE, the focused widget was destroyed/changed by keydown event")); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (mIgnoreNativeCompositionEvent) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" WARNING, mIgnoreNativeCompositionEvent is already TRUE, but we forcedly reset")); michael@0: mIgnoreNativeCompositionEvent = false; michael@0: } michael@0: michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" mCompositionStart=%u", mCompositionStart)); michael@0: mCompositionState = eCompositionState_CompositionStartDispatched; michael@0: WidgetCompositionEvent compEvent(true, NS_COMPOSITION_START, michael@0: mLastFocusedWindow); michael@0: InitEvent(compEvent); michael@0: nsCOMPtr kungFuDeathGrip = mLastFocusedWindow; michael@0: mLastFocusedWindow->DispatchEvent(&compEvent, status); michael@0: if (static_cast(kungFuDeathGrip.get())->IsDestroyed() || michael@0: kungFuDeathGrip != mLastFocusedWindow) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" NOTE, the focused widget was destroyed/changed by compositionstart event")); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsGtkIMModule::DispatchCompositionEnd() michael@0: { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): DispatchCompositionEnd, " michael@0: "mDispatchedCompositionString=\"%s\"", michael@0: this, NS_ConvertUTF16toUTF8(mDispatchedCompositionString).get())); michael@0: michael@0: if (!IsComposing()) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" WARNING, we have alrady finished the composition")); michael@0: return false; michael@0: } michael@0: michael@0: if (!mLastFocusedWindow) { michael@0: mDispatchedCompositionString.Truncate(); michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, there are no focused window in this module")); michael@0: return false; michael@0: } michael@0: michael@0: WidgetCompositionEvent compEvent(true, NS_COMPOSITION_END, michael@0: mLastFocusedWindow); michael@0: InitEvent(compEvent); michael@0: compEvent.data = mDispatchedCompositionString; michael@0: nsEventStatus status; michael@0: nsCOMPtr kungFuDeathGrip = mLastFocusedWindow; michael@0: mLastFocusedWindow->DispatchEvent(&compEvent, status); michael@0: mCompositionState = eCompositionState_NotComposing; michael@0: mCompositionStart = UINT32_MAX; michael@0: mCompositionTargetOffset = UINT32_MAX; michael@0: mDispatchedCompositionString.Truncate(); michael@0: if (static_cast(kungFuDeathGrip.get())->IsDestroyed() || michael@0: kungFuDeathGrip != mLastFocusedWindow) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" NOTE, the focused widget was destroyed/changed by compositionend event")); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsGtkIMModule::DispatchTextEvent(const nsAString &aCompositionString, michael@0: bool aIsCommit) michael@0: { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): DispatchTextEvent, aIsCommit=%s", michael@0: this, aIsCommit ? "TRUE" : "FALSE")); michael@0: michael@0: if (!mLastFocusedWindow) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, there are no focused window in this module")); michael@0: return false; michael@0: } michael@0: michael@0: if (!IsComposing()) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" The composition wasn't started, force starting...")); michael@0: nsCOMPtr kungFuDeathGrip = mLastFocusedWindow; michael@0: if (!DispatchCompositionStart()) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: nsEventStatus status; michael@0: nsRefPtr lastFocusedWindow = mLastFocusedWindow; michael@0: michael@0: if (aCompositionString != mDispatchedCompositionString) { michael@0: WidgetCompositionEvent compositionUpdate(true, NS_COMPOSITION_UPDATE, michael@0: mLastFocusedWindow); michael@0: InitEvent(compositionUpdate); michael@0: compositionUpdate.data = aCompositionString; michael@0: mDispatchedCompositionString = aCompositionString; michael@0: mLastFocusedWindow->DispatchEvent(&compositionUpdate, status); michael@0: if (lastFocusedWindow->IsDestroyed() || michael@0: lastFocusedWindow != mLastFocusedWindow) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" NOTE, the focused widget was destroyed/changed by compositionupdate")); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Store the selected string which will be removed by following text event. michael@0: if (mCompositionState == eCompositionState_CompositionStartDispatched) { michael@0: // XXX We should assume, for now, any web applications don't change michael@0: // selection at handling this text event. michael@0: WidgetQueryContentEvent querySelectedTextEvent(true, michael@0: NS_QUERY_SELECTED_TEXT, michael@0: mLastFocusedWindow); michael@0: mLastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status); michael@0: if (querySelectedTextEvent.mSucceeded) { michael@0: mSelectedString = querySelectedTextEvent.mReply.mString; michael@0: mCompositionStart = querySelectedTextEvent.mReply.mOffset; michael@0: } michael@0: } michael@0: michael@0: WidgetTextEvent textEvent(true, NS_TEXT_TEXT, mLastFocusedWindow); michael@0: InitEvent(textEvent); michael@0: michael@0: uint32_t targetOffset = mCompositionStart; michael@0: michael@0: if (!aIsCommit) { michael@0: // NOTE: SetTextRangeList() assumes that mDispatchedCompositionString michael@0: // has been updated already. michael@0: textEvent.mRanges = CreateTextRangeArray(); michael@0: targetOffset += textEvent.mRanges->TargetClauseOffset(); michael@0: } michael@0: michael@0: textEvent.theText = mDispatchedCompositionString.get(); michael@0: michael@0: mCompositionState = aIsCommit ? michael@0: eCompositionState_CommitTextEventDispatched : michael@0: eCompositionState_TextEventDispatched; michael@0: michael@0: mLastFocusedWindow->DispatchEvent(&textEvent, status); michael@0: if (lastFocusedWindow->IsDestroyed() || michael@0: lastFocusedWindow != mLastFocusedWindow) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" NOTE, the focused widget was destroyed/changed by text event")); michael@0: return false; michael@0: } michael@0: michael@0: // We cannot call SetCursorPosition for e10s-aware. michael@0: // DispatchEvent is async on e10s, so composition rect isn't updated now michael@0: // on tab parent. michael@0: mCompositionTargetOffset = targetOffset; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsGtkIMModule::CreateTextRangeArray() michael@0: { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): CreateTextRangeArray", this)); michael@0: michael@0: nsRefPtr textRangeArray = new TextRangeArray(); michael@0: michael@0: gchar *preedit_string; michael@0: gint cursor_pos; michael@0: PangoAttrList *feedback_list; michael@0: gtk_im_context_get_preedit_string(GetContext(), &preedit_string, michael@0: &feedback_list, &cursor_pos); michael@0: if (!preedit_string || !*preedit_string) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" preedit_string is null")); michael@0: pango_attr_list_unref(feedback_list); michael@0: g_free(preedit_string); michael@0: return textRangeArray.forget(); michael@0: } michael@0: michael@0: PangoAttrIterator* iter; michael@0: iter = pango_attr_list_get_iterator(feedback_list); michael@0: if (!iter) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, iterator couldn't be allocated")); michael@0: pango_attr_list_unref(feedback_list); michael@0: g_free(preedit_string); michael@0: return textRangeArray.forget(); michael@0: } michael@0: michael@0: /* michael@0: * Depend on gtk2's implementation on XIM support. michael@0: * In aFeedback got from gtk2, there are only three types of data: michael@0: * PANGO_ATTR_UNDERLINE, PANGO_ATTR_FOREGROUND, PANGO_ATTR_BACKGROUND. michael@0: * Corresponding to XIMUnderline, XIMReverse. michael@0: * Don't take PANGO_ATTR_BACKGROUND into account, since michael@0: * PANGO_ATTR_BACKGROUND and PANGO_ATTR_FOREGROUND are always michael@0: * a couple. michael@0: */ michael@0: do { michael@0: PangoAttribute* attrUnderline = michael@0: pango_attr_iterator_get(iter, PANGO_ATTR_UNDERLINE); michael@0: PangoAttribute* attrForeground = michael@0: pango_attr_iterator_get(iter, PANGO_ATTR_FOREGROUND); michael@0: if (!attrUnderline && !attrForeground) { michael@0: continue; michael@0: } michael@0: michael@0: // Get the range of the current attribute(s) michael@0: gint start, end; michael@0: pango_attr_iterator_range(iter, &start, &end); michael@0: michael@0: TextRange range; michael@0: // XIMReverse | XIMUnderline michael@0: if (attrUnderline && attrForeground) { michael@0: range.mRangeType = NS_TEXTRANGE_SELECTEDCONVERTEDTEXT; michael@0: } michael@0: // XIMUnderline michael@0: else if (attrUnderline) { michael@0: range.mRangeType = NS_TEXTRANGE_CONVERTEDTEXT; michael@0: } michael@0: // XIMReverse michael@0: else if (attrForeground) { michael@0: range.mRangeType = NS_TEXTRANGE_SELECTEDRAWTEXT; michael@0: } else { michael@0: range.mRangeType = NS_TEXTRANGE_RAWINPUT; michael@0: } michael@0: michael@0: gunichar2* uniStr = nullptr; michael@0: if (start == 0) { michael@0: range.mStartOffset = 0; michael@0: } else { michael@0: glong uniStrLen; michael@0: uniStr = g_utf8_to_utf16(preedit_string, start, michael@0: nullptr, &uniStrLen, nullptr); michael@0: if (uniStr) { michael@0: range.mStartOffset = uniStrLen; michael@0: g_free(uniStr); michael@0: uniStr = nullptr; michael@0: } michael@0: } michael@0: michael@0: glong uniStrLen; michael@0: uniStr = g_utf8_to_utf16(preedit_string + start, end - start, michael@0: nullptr, &uniStrLen, nullptr); michael@0: if (!uniStr) { michael@0: range.mEndOffset = range.mStartOffset; michael@0: } else { michael@0: range.mEndOffset = range.mStartOffset + uniStrLen; michael@0: g_free(uniStr); michael@0: uniStr = nullptr; michael@0: } michael@0: michael@0: textRangeArray->AppendElement(range); michael@0: michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" mStartOffset=%u, mEndOffset=%u, mRangeType=%s", michael@0: range.mStartOffset, range.mEndOffset, michael@0: GetRangeTypeName(range.mRangeType))); michael@0: } while (pango_attr_iterator_next(iter)); michael@0: michael@0: TextRange range; michael@0: if (cursor_pos < 0) { michael@0: range.mStartOffset = 0; michael@0: } else if (uint32_t(cursor_pos) > mDispatchedCompositionString.Length()) { michael@0: range.mStartOffset = mDispatchedCompositionString.Length(); michael@0: } else { michael@0: range.mStartOffset = uint32_t(cursor_pos); michael@0: } michael@0: range.mEndOffset = range.mStartOffset; michael@0: range.mRangeType = NS_TEXTRANGE_CARETPOSITION; michael@0: textRangeArray->AppendElement(range); michael@0: michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" mStartOffset=%u, mEndOffset=%u, mRangeType=%s", michael@0: range.mStartOffset, range.mEndOffset, michael@0: GetRangeTypeName(range.mRangeType))); michael@0: michael@0: pango_attr_iterator_destroy(iter); michael@0: pango_attr_list_unref(feedback_list); michael@0: g_free(preedit_string); michael@0: michael@0: return textRangeArray.forget(); michael@0: } michael@0: michael@0: void michael@0: nsGtkIMModule::SetCursorPosition(uint32_t aTargetOffset) michael@0: { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): SetCursorPosition, aTargetOffset=%u", michael@0: this, aTargetOffset)); michael@0: michael@0: if (aTargetOffset == UINT32_MAX) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, aTargetOffset is wrong offset")); michael@0: return; michael@0: } michael@0: michael@0: if (!mLastFocusedWindow) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, there are no focused window")); michael@0: return; michael@0: } michael@0: michael@0: GtkIMContext *im = GetContext(); michael@0: if (!im) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, there are no context")); michael@0: return; michael@0: } michael@0: michael@0: WidgetQueryContentEvent charRect(true, NS_QUERY_TEXT_RECT, michael@0: mLastFocusedWindow); michael@0: charRect.InitForQueryTextRect(aTargetOffset, 1); michael@0: InitEvent(charRect); michael@0: nsEventStatus status; michael@0: mLastFocusedWindow->DispatchEvent(&charRect, status); michael@0: if (!charRect.mSucceeded) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, NS_QUERY_TEXT_RECT was failed")); michael@0: return; michael@0: } michael@0: nsWindow* rootWindow = michael@0: static_cast(mLastFocusedWindow->GetTopLevelWidget()); michael@0: michael@0: // Get the position of the rootWindow in screen. michael@0: gint rootX, rootY; michael@0: gdk_window_get_origin(rootWindow->GetGdkWindow(), &rootX, &rootY); michael@0: michael@0: // Get the position of IM context owner window in screen. michael@0: gint ownerX, ownerY; michael@0: gdk_window_get_origin(mOwnerWindow->GetGdkWindow(), &ownerX, &ownerY); michael@0: michael@0: // Compute the caret position in the IM owner window. michael@0: GdkRectangle area; michael@0: area.x = charRect.mReply.mRect.x + rootX - ownerX; michael@0: area.y = charRect.mReply.mRect.y + rootY - ownerY; michael@0: area.width = 0; michael@0: area.height = charRect.mReply.mRect.height; michael@0: michael@0: gtk_im_context_set_cursor_location(im, &area); michael@0: } michael@0: michael@0: nsresult michael@0: nsGtkIMModule::GetCurrentParagraph(nsAString& aText, uint32_t& aCursorPos) michael@0: { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): GetCurrentParagraph, mCompositionState=%s", michael@0: this, GetCompositionStateName())); michael@0: michael@0: if (!mLastFocusedWindow) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, there are no focused window in this module")); michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: michael@0: nsEventStatus status; michael@0: michael@0: uint32_t selOffset = mCompositionStart; michael@0: uint32_t selLength = mSelectedString.Length(); michael@0: michael@0: // If focused editor doesn't have composition string, we should use michael@0: // current selection. michael@0: if (!EditorHasCompositionString()) { michael@0: // Query cursor position & selection michael@0: WidgetQueryContentEvent querySelectedTextEvent(true, michael@0: NS_QUERY_SELECTED_TEXT, michael@0: mLastFocusedWindow); michael@0: mLastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status); michael@0: NS_ENSURE_TRUE(querySelectedTextEvent.mSucceeded, NS_ERROR_FAILURE); michael@0: michael@0: selOffset = querySelectedTextEvent.mReply.mOffset; michael@0: selLength = querySelectedTextEvent.mReply.mString.Length(); michael@0: } michael@0: michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" selOffset=%u, selLength=%u", michael@0: selOffset, selLength)); michael@0: michael@0: // XXX nsString::Find and nsString::RFind take int32_t for offset, so, michael@0: // we cannot support this request when the current offset is larger michael@0: // than INT32_MAX. michael@0: if (selOffset > INT32_MAX || selLength > INT32_MAX || michael@0: selOffset + selLength > INT32_MAX) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, The selection is out of range")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Get all text contents of the focused editor michael@0: WidgetQueryContentEvent queryTextContentEvent(true, michael@0: NS_QUERY_TEXT_CONTENT, michael@0: mLastFocusedWindow); michael@0: queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX); michael@0: mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status); michael@0: NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE); michael@0: michael@0: nsAutoString textContent(queryTextContentEvent.mReply.mString); michael@0: if (selOffset + selLength > textContent.Length()) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, The selection is invalid, textContent.Length()=%u", michael@0: textContent.Length())); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Remove composing string and restore the selected string because michael@0: // GtkEntry doesn't remove selected string until committing, however, michael@0: // our editor does it. We should emulate the behavior for IME. michael@0: if (EditorHasCompositionString() && michael@0: mDispatchedCompositionString != mSelectedString) { michael@0: textContent.Replace(mCompositionStart, michael@0: mDispatchedCompositionString.Length(), mSelectedString); michael@0: } michael@0: michael@0: // Get only the focused paragraph, by looking for newlines michael@0: int32_t parStart = (selOffset == 0) ? 0 : michael@0: textContent.RFind("\n", false, selOffset - 1, -1) + 1; michael@0: int32_t parEnd = textContent.Find("\n", false, selOffset + selLength, -1); michael@0: if (parEnd < 0) { michael@0: parEnd = textContent.Length(); michael@0: } michael@0: aText = nsDependentSubstring(textContent, parStart, parEnd - parStart); michael@0: aCursorPos = selOffset - uint32_t(parStart); michael@0: michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" aText=%s, aText.Length()=%u, aCursorPos=%u", michael@0: NS_ConvertUTF16toUTF8(aText).get(), michael@0: aText.Length(), aCursorPos)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsGtkIMModule::DeleteText(const int32_t aOffset, const uint32_t aNChars) michael@0: { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): DeleteText, aOffset=%d, aNChars=%d, " michael@0: "mCompositionState=%s", michael@0: this, aOffset, aNChars, GetCompositionStateName())); michael@0: michael@0: if (!mLastFocusedWindow) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, there are no focused window in this module")); michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: michael@0: if (!aNChars) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, aNChars must not be zero")); michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: nsRefPtr lastFocusedWindow(mLastFocusedWindow); michael@0: nsEventStatus status; michael@0: michael@0: // First, we should cancel current composition because editor cannot michael@0: // handle changing selection and deleting text. michael@0: uint32_t selOffset; michael@0: bool wasComposing = IsComposing(); michael@0: bool editorHadCompositionString = EditorHasCompositionString(); michael@0: if (wasComposing) { michael@0: selOffset = mCompositionStart; michael@0: if (editorHadCompositionString && michael@0: !DispatchTextEvent(mSelectedString, false)) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, quitting from DeletText")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: if (!DispatchCompositionEnd()) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, quitting from DeletText")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } else { michael@0: // Query cursor position & selection michael@0: WidgetQueryContentEvent querySelectedTextEvent(true, michael@0: NS_QUERY_SELECTED_TEXT, michael@0: mLastFocusedWindow); michael@0: lastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status); michael@0: NS_ENSURE_TRUE(querySelectedTextEvent.mSucceeded, NS_ERROR_FAILURE); michael@0: michael@0: selOffset = querySelectedTextEvent.mReply.mOffset; michael@0: } michael@0: michael@0: // Get all text contents of the focused editor michael@0: WidgetQueryContentEvent queryTextContentEvent(true, michael@0: NS_QUERY_TEXT_CONTENT, michael@0: mLastFocusedWindow); michael@0: queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX); michael@0: mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status); michael@0: NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE); michael@0: if (queryTextContentEvent.mReply.mString.IsEmpty()) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, there is no contents")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_ConvertUTF16toUTF8 utf8Str( michael@0: nsDependentSubstring(queryTextContentEvent.mReply.mString, michael@0: 0, selOffset)); michael@0: glong offsetInUTF8Characters = michael@0: g_utf8_strlen(utf8Str.get(), utf8Str.Length()) + aOffset; michael@0: if (offsetInUTF8Characters < 0) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, aOffset is too small for current cursor pos " michael@0: "(computed offset: %d)", michael@0: offsetInUTF8Characters)); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: AppendUTF16toUTF8( michael@0: nsDependentSubstring(queryTextContentEvent.mReply.mString, selOffset), michael@0: utf8Str); michael@0: glong countOfCharactersInUTF8 = michael@0: g_utf8_strlen(utf8Str.get(), utf8Str.Length()); michael@0: glong endInUTF8Characters = michael@0: offsetInUTF8Characters + aNChars; michael@0: if (countOfCharactersInUTF8 < endInUTF8Characters) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, aNChars is too large for current contents " michael@0: "(content length: %d, computed end offset: %d)", michael@0: countOfCharactersInUTF8, endInUTF8Characters)); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: gchar* charAtOffset = michael@0: g_utf8_offset_to_pointer(utf8Str.get(), offsetInUTF8Characters); michael@0: gchar* charAtEnd = michael@0: g_utf8_offset_to_pointer(utf8Str.get(), endInUTF8Characters); michael@0: michael@0: // Set selection to delete michael@0: WidgetSelectionEvent selectionEvent(true, NS_SELECTION_SET, michael@0: mLastFocusedWindow); michael@0: michael@0: nsDependentCSubstring utf8StrBeforeOffset(utf8Str, 0, michael@0: charAtOffset - utf8Str.get()); michael@0: selectionEvent.mOffset = michael@0: NS_ConvertUTF8toUTF16(utf8StrBeforeOffset).Length(); michael@0: michael@0: nsDependentCSubstring utf8DeletingStr(utf8Str, michael@0: utf8StrBeforeOffset.Length(), michael@0: charAtEnd - charAtOffset); michael@0: selectionEvent.mLength = michael@0: NS_ConvertUTF8toUTF16(utf8DeletingStr).Length(); michael@0: michael@0: selectionEvent.mReversed = false; michael@0: selectionEvent.mExpandToClusterBoundary = false; michael@0: lastFocusedWindow->DispatchEvent(&selectionEvent, status); michael@0: michael@0: if (!selectionEvent.mSucceeded || michael@0: lastFocusedWindow != mLastFocusedWindow || michael@0: lastFocusedWindow->Destroyed()) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, setting selection caused focus change " michael@0: "or window destroyed")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Delete the selection michael@0: WidgetContentCommandEvent contentCommandEvent(true, michael@0: NS_CONTENT_COMMAND_DELETE, michael@0: mLastFocusedWindow); michael@0: mLastFocusedWindow->DispatchEvent(&contentCommandEvent, status); michael@0: michael@0: if (!contentCommandEvent.mSucceeded || michael@0: lastFocusedWindow != mLastFocusedWindow || michael@0: lastFocusedWindow->Destroyed()) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, deleting the selection caused focus change " michael@0: "or window destroyed")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (!wasComposing) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Restore the composition at new caret position. michael@0: if (!DispatchCompositionStart()) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, resterting composition start")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (!editorHadCompositionString) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoString compositionString; michael@0: GetCompositionString(compositionString); michael@0: if (!DispatchTextEvent(compositionString, true)) { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: (" FAILED, restoring composition string")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsGtkIMModule::InitEvent(WidgetGUIEvent& aEvent) michael@0: { michael@0: aEvent.time = PR_Now() / 1000; michael@0: } michael@0: michael@0: bool michael@0: nsGtkIMModule::ShouldIgnoreNativeCompositionEvent() michael@0: { michael@0: PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, michael@0: ("GtkIMModule(%p): ShouldIgnoreNativeCompositionEvent, mLastFocusedWindow=%p, mIgnoreNativeCompositionEvent=%s", michael@0: this, mLastFocusedWindow, michael@0: mIgnoreNativeCompositionEvent ? "YES" : "NO")); michael@0: michael@0: if (!mLastFocusedWindow) { michael@0: return true; // cannot continue michael@0: } michael@0: michael@0: return mIgnoreNativeCompositionEvent; michael@0: }